github.com/looshlee/beatles@v0.0.0-20220727174639-742810ab631c/pkg/kafka/request.go (about) 1 // Copyright 2017 Authors of Cilium 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package kafka 16 17 import ( 18 "bytes" 19 "encoding/binary" 20 "encoding/json" 21 "fmt" 22 "io" 23 24 "github.com/cilium/cilium/pkg/flowdebug" 25 26 "github.com/optiopay/kafka/proto" 27 ) 28 29 // RequestMessage represents a Kafka request message 30 type RequestMessage struct { 31 kind int16 32 version int16 33 rawMsg []byte 34 request interface{} 35 } 36 37 // CorrelationID represents the correlation id as defined in the Kafka protocol 38 // specification 39 type CorrelationID uint32 40 41 // GetAPIKey returns the kind of Kafka request 42 func (req *RequestMessage) GetAPIKey() int16 { 43 return req.kind 44 } 45 46 // GetRaw returns the raw Kafka request 47 func (req *RequestMessage) GetRaw() []byte { 48 return req.rawMsg 49 } 50 51 // GetVersion returns the version Kafka request 52 func (req *RequestMessage) GetVersion() int16 { 53 return req.version 54 } 55 56 // GetCorrelationID returns the Kafka request correlationID 57 func (req *RequestMessage) GetCorrelationID() CorrelationID { 58 if len(req.rawMsg) >= 12 { 59 return CorrelationID(binary.BigEndian.Uint32(req.rawMsg[8:12])) 60 } 61 62 return CorrelationID(0) 63 } 64 65 // SetCorrelationID modified the correlation ID of the Kafka request 66 func (req *RequestMessage) SetCorrelationID(id CorrelationID) { 67 if len(req.rawMsg) >= 12 { 68 binary.BigEndian.PutUint32(req.rawMsg[8:12], uint32(id)) 69 } 70 } 71 72 func (req *RequestMessage) extractVersion() int16 { 73 return int16(binary.BigEndian.Uint16(req.rawMsg[6:8])) 74 } 75 76 // String returns a human readable representation of the request message 77 func (req *RequestMessage) String() string { 78 b, err := json.Marshal(req.request) 79 if err != nil { 80 return err.Error() 81 } 82 83 return fmt.Sprintf("apiKey=%d,apiVersion=%d,len=%d: %s", 84 req.kind, req.version, len(req.rawMsg), string(b)) 85 } 86 87 // GetTopics returns the Kafka request list of topics 88 func (req *RequestMessage) GetTopics() []string { 89 if req.request == nil { 90 return nil 91 } 92 93 switch val := req.request.(type) { 94 case *proto.ProduceReq: 95 return produceTopics(val) 96 case *proto.FetchReq: 97 return fetchTopics(val) 98 case *proto.OffsetReq: 99 return offsetTopics(val) 100 case *proto.MetadataReq: 101 return metadataTopics(val) 102 case *proto.OffsetCommitReq: 103 return offsetCommitTopics(val) 104 case *proto.OffsetFetchReq: 105 return offsetFetchTopics(val) 106 } 107 return nil 108 } 109 110 func produceTopics(req *proto.ProduceReq) []string { 111 topics := make([]string, len(req.Topics)) 112 for k, topic := range req.Topics { 113 topics[k] = topic.Name 114 } 115 return topics 116 } 117 118 func fetchTopics(req *proto.FetchReq) []string { 119 topics := make([]string, len(req.Topics)) 120 for k, topic := range req.Topics { 121 topics[k] = topic.Name 122 } 123 return topics 124 } 125 126 func offsetTopics(req *proto.OffsetReq) []string { 127 topics := make([]string, len(req.Topics)) 128 for k, topic := range req.Topics { 129 topics[k] = topic.Name 130 } 131 return topics 132 } 133 134 func metadataTopics(req *proto.MetadataReq) []string { 135 topics := req.Topics 136 return topics 137 } 138 139 func offsetCommitTopics(req *proto.OffsetCommitReq) []string { 140 topics := make([]string, len(req.Topics)) 141 for k, topic := range req.Topics { 142 topics[k] = topic.Name 143 } 144 return topics 145 } 146 147 func offsetFetchTopics(req *proto.OffsetFetchReq) []string { 148 topics := make([]string, len(req.Topics)) 149 for k, topic := range req.Topics { 150 topics[k] = topic.Name 151 } 152 return topics 153 } 154 155 // CreateResponse creates a response message based on the provided request 156 // message. The response will have the specified error code set in all topics 157 // and embedded partitions. 158 func (req *RequestMessage) CreateResponse(err error) (*ResponseMessage, error) { 159 switch val := req.request.(type) { 160 case *proto.ProduceReq: 161 return createProduceResponse(val, err) 162 case *proto.FetchReq: 163 return createFetchResponse(val, err) 164 case *proto.OffsetReq: 165 return createOffsetResponse(val, err) 166 case *proto.MetadataReq: 167 return createMetadataResponse(val, err) 168 case *proto.ConsumerMetadataReq: 169 return createConsumerMetadataResponse(val, err) 170 case *proto.OffsetCommitReq: 171 return createOffsetCommitResponse(val, err) 172 case *proto.OffsetFetchReq: 173 return createOffsetFetchResponse(val, err) 174 case nil: 175 return nil, fmt.Errorf("unsupported request API key %d", req.kind) 176 default: 177 // The switch cases above must correspond exactly to the switch cases 178 // in ReadRequest. 179 log.Panic(fmt.Sprintf("Kafka API key not handled: %d", req.kind)) 180 } 181 return nil, nil 182 } 183 184 // ReadRequest will read a Kafka request from an io.Reader and return the 185 // message or an error. 186 func ReadRequest(reader io.Reader) (*RequestMessage, error) { 187 req := &RequestMessage{} 188 var err error 189 190 req.kind, req.rawMsg, err = proto.ReadReq(reader) 191 if err != nil { 192 return nil, err 193 } 194 195 if len(req.rawMsg) < 12 { 196 return nil, 197 fmt.Errorf("unexpected end of request (length < 12 bytes)") 198 } 199 req.version = req.extractVersion() 200 201 var nilSlice []byte 202 buf := bytes.NewBuffer(append(nilSlice, req.rawMsg...)) 203 204 switch req.kind { 205 case proto.ProduceReqKind: 206 req.request, err = proto.ReadProduceReq(buf) 207 case proto.FetchReqKind: 208 req.request, err = proto.ReadFetchReq(buf) 209 case proto.OffsetReqKind: 210 req.request, err = proto.ReadOffsetReq(buf) 211 case proto.MetadataReqKind: 212 req.request, err = proto.ReadMetadataReq(buf) 213 case proto.ConsumerMetadataReqKind: 214 req.request, err = proto.ReadConsumerMetadataReq(buf) 215 case proto.OffsetCommitReqKind: 216 req.request, err = proto.ReadOffsetCommitReq(buf) 217 case proto.OffsetFetchReqKind: 218 req.request, err = proto.ReadOffsetFetchReq(buf) 219 default: 220 log.WithField(fieldRequest, req.String()).Debugf("Unknown Kafka request API key: %d", req.kind) 221 } 222 223 if err != nil { 224 flowdebug.Log(log.WithField(fieldRequest, req.String()).WithError(err), 225 "Ignoring Kafka message due to parse error") 226 return nil, err 227 } 228 return req, nil 229 }