github.com/datastax/go-cassandra-native-protocol@v0.0.0-20220706104457-5e8aad05cf90/frame/frame.go (about) 1 // Copyright 2020 DataStax 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 frame 16 17 import ( 18 "bytes" 19 "encoding/hex" 20 "fmt" 21 22 "github.com/datastax/go-cassandra-native-protocol/message" 23 "github.com/datastax/go-cassandra-native-protocol/primitive" 24 ) 25 26 // Frame is a high-level representation of a frame, where the body is fully decoded. 27 // Note that frames are called "envelopes" in protocol v5 specs. 28 // +k8s:deepcopy-gen=true 29 type Frame struct { 30 Header *Header 31 Body *Body 32 } 33 34 // RawFrame is a low-level representation of a frame, where the body is not decoded. 35 // Note that frames are called "envelopes" in protocol v5 specs. 36 // +k8s:deepcopy-gen=true 37 type RawFrame struct { 38 Header *Header 39 Body []byte 40 } 41 42 // Header is the header of a frame. 43 // +k8s:deepcopy-gen=true 44 type Header struct { 45 IsResponse bool 46 Version primitive.ProtocolVersion 47 Flags primitive.HeaderFlag 48 // The stream id. A stream id is a signed byte (protocol versions 1 and 2) or a signed 16-bit integer (protocol 49 // versions 3 and higher). Note that the protocol specs refer to the stream id as a primitive [short] integer, 50 // but in fact stream ids are signed integers. Indeed, server-initiated messages, such as EVENT messages, have 51 // negative stream ids. For this reason, stream ids are represented as signed 16-bit integers in this library. 52 StreamId int16 53 // The OpCode is an unsigned byte that distinguishes the type of payload that a frame contains. 54 OpCode primitive.OpCode 55 // The encoded body length. This is a computed value that users should not set themselves. When encoding a frame, 56 // this field is not read but is rather dynamically computed from the actual body length. When decoding a frame, 57 // this field is always correctly set to the exact decoded body length. 58 BodyLength int32 59 } 60 61 // Body is the body of a frame. 62 // +k8s:deepcopy-gen=true 63 type Body struct { 64 // The tracing id. Only valid for response frames, ignored otherwise. 65 TracingId *primitive.UUID 66 // The custom payload, or nil if no custom payload is defined. 67 // Custom payloads are only valid from Protocol Version 4 onwards. 68 CustomPayload map[string][]byte 69 // Query warnings, if any. Query warnings are only valid for response frames, and only from Protocol Version 4 onwards. 70 Warnings []string 71 // The body message. 72 Message message.Message 73 } 74 75 // NewFrame Creates a new Frame with the given version, stream id and message. 76 func NewFrame(version primitive.ProtocolVersion, streamId int16, message message.Message) *Frame { 77 var flags primitive.HeaderFlag 78 if version.IsBeta() { 79 flags = flags.Add(primitive.HeaderFlagUseBeta) 80 } 81 return &Frame{ 82 Header: &Header{ 83 IsResponse: message.IsResponse(), 84 Version: version, 85 Flags: flags, 86 StreamId: streamId, 87 OpCode: message.GetOpCode(), 88 BodyLength: 0, // will be set later when encoding 89 }, 90 Body: &Body{ 91 Message: message, 92 }, 93 } 94 } 95 96 // SetCustomPayload Sets a new custom payload on this frame, adjusting the header flags accordingly. If nil, the existing payload, 97 // if any, will be removed along with the corresponding header flag. 98 // Note: custom payloads cannot be used with protocol versions lesser than 4. 99 func (f *Frame) SetCustomPayload(customPayload map[string][]byte) { 100 if len(customPayload) > 0 { 101 f.Header.Flags = f.Header.Flags.Add(primitive.HeaderFlagCustomPayload) 102 } else { 103 f.Header.Flags = f.Header.Flags.Remove(primitive.HeaderFlagCustomPayload) 104 } 105 f.Body.CustomPayload = customPayload 106 } 107 108 // SetWarnings Sets new query warnings on this frame, adjusting the header flags accordingly. If nil, the existing warnings, 109 // if any, will be removed along with the corresponding header flag. 110 // Note: query warnings cannot be used with protocol versions lesser than 4. 111 func (f *Frame) SetWarnings(warnings []string) { 112 if len(warnings) > 0 { 113 f.Header.Flags = f.Header.Flags.Add(primitive.HeaderFlagWarning) 114 } else { 115 f.Header.Flags = f.Header.Flags.Remove(primitive.HeaderFlagWarning) 116 } 117 f.Body.Warnings = warnings 118 } 119 120 // SetTracingId Sets a new tracing id on this frame, adjusting the header flags accordingly. If nil, the existing tracing id, 121 // if any, will be removed along with the corresponding header flag. 122 // Note: tracing ids can only be used with response frames. 123 func (f *Frame) SetTracingId(tracingId *primitive.UUID) { 124 if tracingId != nil { 125 f.Header.Flags = f.Header.Flags.Add(primitive.HeaderFlagTracing) 126 } else { 127 f.Header.Flags = f.Header.Flags.Remove(primitive.HeaderFlagTracing) 128 } 129 f.Body.TracingId = tracingId 130 } 131 132 // RequestTracingId Configures this frame to request a tracing id from the server, adjusting the header flags accordingly. 133 // Note: this method should only be used for request frames. 134 func (f *Frame) RequestTracingId(tracing bool) { 135 if tracing { 136 f.Header.Flags = f.Header.Flags.Add(primitive.HeaderFlagTracing) 137 } else { 138 f.Header.Flags = f.Header.Flags.Remove(primitive.HeaderFlagTracing) 139 } 140 } 141 142 // SetCompress Configures this frame to use compression, adjusting the header flags accordingly. 143 // Note: this method will not enable compression on frames that cannot be compressed. 144 // Also, enabling compression on a frame does not guarantee that the frame will be properly compressed: 145 // the frame codec must also be configured to use a BodyCompressor. 146 func (f *Frame) SetCompress(compress bool) { 147 if compress && isCompressible(f.Body.Message.GetOpCode()) { 148 f.Header.Flags = f.Header.Flags.Add(primitive.HeaderFlagCompressed) 149 } else { 150 f.Header.Flags = f.Header.Flags.Remove(primitive.HeaderFlagCompressed) 151 } 152 } 153 154 func (f *Frame) String() string { 155 return fmt.Sprintf("{header: %v, body: %v}", f.Header, f.Body) 156 } 157 158 func (f *RawFrame) String() string { 159 return fmt.Sprintf("{header: %v, body: %v}", f.Header, f.Body) 160 } 161 162 func (h *Header) String() string { 163 return fmt.Sprintf("{response: %v, version: %v, flags: %08b, stream id: %v, opcode: %v, body length: %v}", 164 h.IsResponse, h.Version, h.Flags, h.StreamId, h.OpCode, h.BodyLength) 165 } 166 167 func (b *Body) String() string { 168 return fmt.Sprintf("{tracing id: %v, payload: %v, warnings: %v, message: %v}", 169 b.TracingId, b.CustomPayload, b.Warnings, b.Message) 170 } 171 172 // Dump encodes and dumps the contents of this frame, for debugging purposes. 173 func (f *Frame) Dump() (string, error) { 174 buffer := bytes.Buffer{} 175 if err := NewCodec().EncodeFrame(f, &buffer); err != nil { 176 return "", err 177 } else { 178 return hex.Dump(buffer.Bytes()), nil 179 } 180 } 181 182 // Dump encodes and dumps the contents of this frame, for debugging purposes. 183 func (f *RawFrame) Dump() (string, error) { 184 buffer := bytes.Buffer{} 185 if err := NewRawCodec().EncodeRawFrame(f, &buffer); err != nil { 186 return "", err 187 } else { 188 return hex.Dump(buffer.Bytes()), nil 189 } 190 } 191 192 func isCompressible(opCode primitive.OpCode) bool { 193 // STARTUP should never be compressed as per protocol specs 194 return opCode != primitive.OpCodeStartup && 195 // OPTIONS and READY are empty and as such do not benefit from compression 196 opCode != primitive.OpCodeOptions && 197 opCode != primitive.OpCodeReady 198 }