github.com/segmentio/kafka-go@v0.4.48-0.20240318174348-3f6244eb34fd/protocol/response.go (about)

     1  package protocol
     2  
     3  import (
     4  	"crypto/tls"
     5  	"encoding/binary"
     6  	"errors"
     7  	"fmt"
     8  	"io"
     9  )
    10  
    11  func ReadResponse(r io.Reader, apiKey ApiKey, apiVersion int16) (correlationID int32, msg Message, err error) {
    12  	if i := int(apiKey); i < 0 || i >= len(apiTypes) {
    13  		err = fmt.Errorf("unsupported api key: %d", i)
    14  		return
    15  	}
    16  
    17  	t := &apiTypes[apiKey]
    18  	if t == nil {
    19  		err = fmt.Errorf("unsupported api: %s", apiNames[apiKey])
    20  		return
    21  	}
    22  
    23  	minVersion := t.minVersion()
    24  	maxVersion := t.maxVersion()
    25  
    26  	if apiVersion < minVersion || apiVersion > maxVersion {
    27  		err = fmt.Errorf("unsupported %s version: v%d not in range v%d-v%d", apiKey, apiVersion, minVersion, maxVersion)
    28  		return
    29  	}
    30  
    31  	d := &decoder{reader: r, remain: 4}
    32  	size := d.readInt32()
    33  
    34  	if err = d.err; err != nil {
    35  		err = dontExpectEOF(err)
    36  		return
    37  	}
    38  
    39  	d.remain = int(size)
    40  	correlationID = d.readInt32()
    41  	if err = d.err; err != nil {
    42  		if errors.Is(err, io.ErrUnexpectedEOF) {
    43  			// If a Writer/Reader is configured without TLS and connects
    44  			// to a broker expecting TLS the only message we return to the
    45  			// caller is io.ErrUnexpetedEOF which is opaque. This section
    46  			// tries to determine if that's what has happened.
    47  			// We first deconstruct the initial 4 bytes of the message
    48  			// from the size which was read earlier.
    49  			// Next, we examine those bytes to see if they looks like a TLS
    50  			// error message. If they do we wrap the io.ErrUnexpectedEOF
    51  			// with some context.
    52  			if looksLikeUnexpectedTLS(size) {
    53  				err = fmt.Errorf("%w: broker appears to be expecting TLS", io.ErrUnexpectedEOF)
    54  			}
    55  			return
    56  		}
    57  		err = dontExpectEOF(err)
    58  		return
    59  	}
    60  
    61  	res := &t.responses[apiVersion-minVersion]
    62  
    63  	if res.flexible {
    64  		// In the flexible case, there's a tag buffer at the end of the response header
    65  		taggedCount := int(d.readUnsignedVarInt())
    66  		for i := 0; i < taggedCount; i++ {
    67  			d.readUnsignedVarInt() // tagID
    68  			size := d.readUnsignedVarInt()
    69  
    70  			// Just throw away the values for now
    71  			d.read(int(size))
    72  		}
    73  	}
    74  
    75  	msg = res.new()
    76  	res.decode(d, valueOf(msg))
    77  	d.discardAll()
    78  
    79  	if err = d.err; err != nil {
    80  		err = dontExpectEOF(err)
    81  	}
    82  
    83  	return
    84  }
    85  
    86  func WriteResponse(w io.Writer, apiVersion int16, correlationID int32, msg Message) error {
    87  	apiKey := msg.ApiKey()
    88  
    89  	if i := int(apiKey); i < 0 || i >= len(apiTypes) {
    90  		return fmt.Errorf("unsupported api key: %d", i)
    91  	}
    92  
    93  	t := &apiTypes[apiKey]
    94  	if t == nil {
    95  		return fmt.Errorf("unsupported api: %s", apiNames[apiKey])
    96  	}
    97  
    98  	if typedMessage, ok := msg.(OverrideTypeMessage); ok {
    99  		typeKey := typedMessage.TypeKey()
   100  		overrideType := overrideApiTypes[apiKey][typeKey]
   101  		t = &overrideType
   102  	}
   103  
   104  	minVersion := t.minVersion()
   105  	maxVersion := t.maxVersion()
   106  
   107  	if apiVersion < minVersion || apiVersion > maxVersion {
   108  		return fmt.Errorf("unsupported %s version: v%d not in range v%d-v%d", apiKey, apiVersion, minVersion, maxVersion)
   109  	}
   110  
   111  	r := &t.responses[apiVersion-minVersion]
   112  	v := valueOf(msg)
   113  	b := newPageBuffer()
   114  	defer b.unref()
   115  
   116  	e := &encoder{writer: b}
   117  	e.writeInt32(0) // placeholder for the response size
   118  	e.writeInt32(correlationID)
   119  	if r.flexible {
   120  		// Flexible messages use extra space for a tag buffer,
   121  		// which begins with a size value. Since we're not writing any fields into the
   122  		// latter, we can just write zero for now.
   123  		//
   124  		// See
   125  		// https://cwiki.apache.org/confluence/display/KAFKA/KIP-482%3A+The+Kafka+Protocol+should+Support+Optional+Tagged+Fields
   126  		// for details.
   127  		e.writeUnsignedVarInt(0)
   128  	}
   129  	r.encode(e, v)
   130  	err := e.err
   131  
   132  	if err == nil {
   133  		size := packUint32(uint32(b.Size()) - 4)
   134  		b.WriteAt(size[:], 0)
   135  		_, err = b.WriteTo(w)
   136  	}
   137  
   138  	return err
   139  }
   140  
   141  const (
   142  	tlsAlertByte byte = 0x15
   143  )
   144  
   145  // looksLikeUnexpectedTLS returns true if the size passed in resemble
   146  // the TLS alert message that is returned to a client which sends
   147  // an invalid ClientHello message.
   148  func looksLikeUnexpectedTLS(size int32) bool {
   149  	var sizeBytes [4]byte
   150  	binary.BigEndian.PutUint32(sizeBytes[:], uint32(size))
   151  
   152  	if sizeBytes[0] != tlsAlertByte {
   153  		return false
   154  	}
   155  	version := int(sizeBytes[1])<<8 | int(sizeBytes[2])
   156  	return version <= tls.VersionTLS13 && version >= tls.VersionTLS10
   157  }