github.com/streamdal/segmentio-kafka-go@v0.4.47-streamdal/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 }