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  }