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  }