├── .github └── workflows │ └── go.yml ├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── client ├── auth.go ├── auth_test.go ├── client.go ├── client_local_test.go ├── client_remote_test.go ├── compression.go ├── connection.go ├── doc.go ├── handlers.go ├── handlers_test.go ├── handshake.go ├── handshake_test.go ├── inflight.go ├── main_test.go ├── prepare.go ├── prepare_test.go ├── server.go ├── server_test.go ├── system.go └── system_test.go ├── compression ├── lz4 │ └── lz4.go └── snappy │ └── snappy.go ├── crc ├── crc24.go ├── crc24_test.go ├── crc32.go └── crc32_test.go ├── datacodec ├── bigint.go ├── bigint_test.go ├── blob.go ├── blob_test.go ├── boolean.go ├── boolean_test.go ├── codec.go ├── codec_test.go ├── collection.go ├── collection_test.go ├── conversions.go ├── conversions_test.go ├── date.go ├── date_test.go ├── decimal.go ├── decimal_test.go ├── doc.go ├── double.go ├── double_test.go ├── duration.go ├── duration_test.go ├── errors.go ├── extractors.go ├── extractors_test.go ├── float.go ├── float_test.go ├── inet.go ├── inet_test.go ├── injectors.go ├── injectors_test.go ├── int.go ├── int_test.go ├── main_test.go ├── map.go ├── map_test.go ├── math.go ├── math_test.go ├── mock_codec_test.go ├── mock_extractor_test.go ├── mock_injector_test.go ├── mock_key_value_extractor_test.go ├── mock_key_value_injector_test.go ├── mocks.md ├── reflection.go ├── smallint.go ├── smallint_test.go ├── time.go ├── time_test.go ├── timestamp.go ├── timestamp_test.go ├── tinyint.go ├── tinyint_test.go ├── tuple.go ├── tuple_test.go ├── udt.go ├── udt_test.go ├── uuid.go ├── uuid_test.go ├── varchar.go ├── varchar_test.go ├── varint.go └── varint_test.go ├── datatype ├── custom.go ├── custom_test.go ├── datatype.go ├── deepcopy_generated.go ├── doc.go ├── list.go ├── list_test.go ├── map.go ├── map_test.go ├── primitive.go ├── primitive_test.go ├── set.go ├── set_test.go ├── tuple.go ├── tuple_test.go ├── udt.go └── udt_test.go ├── deepcopy-header.txt ├── frame ├── codec.go ├── codec_test.go ├── compressor.go ├── convert.go ├── decode.go ├── deepcopy_generated.go ├── doc.go ├── encode.go ├── frame.go └── frame_test.go ├── go.mod ├── go.sum ├── message ├── auth_challenge.go ├── auth_challenge_test.go ├── auth_response.go ├── auth_response_test.go ├── auth_success.go ├── auth_success_test.go ├── authenticate.go ├── authenticate_test.go ├── batch.go ├── batch_test.go ├── deepcopy_generated.go ├── doc.go ├── dse_continuous_paging_options.go ├── dse_continuous_paging_options_test.go ├── dse_revise_request.go ├── dse_revise_request_test.go ├── error.go ├── error_test.go ├── event.go ├── event_test.go ├── execute.go ├── execute_test.go ├── main.go ├── main_test.go ├── message.go ├── options.go ├── options_test.go ├── prepare.go ├── prepare_test.go ├── query.go ├── query_options.go ├── query_test.go ├── ready.go ├── ready_test.go ├── register.go ├── register_test.go ├── result.go ├── result_metadata.go ├── result_other_test.go ├── result_prepared_test.go ├── result_rows_test.go ├── result_schema_change_test.go ├── startup.go ├── startup_test.go ├── supported.go └── supported_test.go ├── primitive ├── bytes.go ├── bytes_map.go ├── bytes_map_test.go ├── bytes_test.go ├── constants.go ├── constants_test.go ├── deepcopy_generated.go ├── doc.go ├── inet.go ├── inet_addr.go ├── inet_addr_test.go ├── inet_test.go ├── integers.go ├── integers_test.go ├── long_string.go ├── long_string_test.go ├── reasonmap.go ├── reasonmap_test.go ├── short_bytes.go ├── short_bytes_test.go ├── streamid.go ├── streamid_test.go ├── string.go ├── string_list.go ├── string_list_test.go ├── string_map.go ├── string_map_test.go ├── string_multimap.go ├── string_multimap_test.go ├── string_test.go ├── util.go ├── uuid.go ├── uuid_test.go ├── values.go ├── values_test.go ├── vint.go └── vint_test.go ├── segment ├── codec.go ├── compressor.go ├── decode.go ├── decode_test.go ├── deepcopy_generated.go ├── encode.go ├── encode_test.go └── segment.go └── specs ├── dse_protocol_v1.spec ├── dse_protocol_v2.spec ├── native_protocol_v1.spec ├── native_protocol_v2.spec ├── native_protocol_v3.spec ├── native_protocol_v4.spec └── native_protocol_v5.spec /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | name: Go 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | 11 | build: 12 | name: Build 13 | runs-on: ubuntu-latest 14 | steps: 15 | 16 | - name: Set up Go 1.x 17 | uses: actions/setup-go@v2 18 | with: 19 | go-version: ^1.16 20 | 21 | - name: Check out code into the Go module directory 22 | uses: actions/checkout@v2 23 | 24 | - name: Get dependencies 25 | run: | 26 | go get -v -t -d ./... 27 | if [ -f Gopkg.toml ]; then 28 | curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh 29 | dep ensure 30 | fi 31 | 32 | - name: Check license 33 | run: | 34 | go install github.com/google/addlicense@latest 35 | addlicense -check **/*.go 36 | 37 | - name: Build 38 | run: go build -v ./... 39 | 40 | - name: Test 41 | run: go test -v ./... 42 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .settings 2 | .DS_Store 3 | /.idea 4 | *.iml 5 | /go-cassandra-native-protocol 6 | /build/ 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Cassandra Native Protocol Bindings for Go 2 | 3 | [![Go Build Status](https://github.com/datastax/go-cassandra-native-protocol/workflows/Go/badge.svg)](https://github.com/datastax/go-cassandra-native-protocol/actions) 4 | [![Go Report Card](https://goreportcard.com/badge/github.com/datastax/go-cassandra-native-protocol)](https://goreportcard.com/report/github.com/datastax/go-cassandra-native-protocol) 5 | 6 | This project contains all the logic required to encode and decode Apache Cassandra(R)'s CQL native protocol frames in 7 | Go. 8 | 9 | It currently supports: 10 | 11 | - Cassandra CQL protocol versions 2 to 5. 12 | - DSE (DataStax Enterprise) protocol versions 1 and 2. 13 | 14 | This project originated as an attempt to port the DataStax Cassandra Java driver's 15 | [native-protocol](https://github.com/datastax/native-protocol) project to the Go language. 16 | -------------------------------------------------------------------------------- /client/auth.go: -------------------------------------------------------------------------------- 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 client 16 | 17 | import ( 18 | "bytes" 19 | "fmt" 20 | ) 21 | 22 | // AuthCredentials encapsulates a username and a password to use with plain-text authenticators. 23 | type AuthCredentials struct { 24 | Username string 25 | Password string 26 | } 27 | 28 | func (c *AuthCredentials) String() string { 29 | return fmt.Sprintf("AuthCredentials{username: %v}", c.Username) 30 | } 31 | 32 | // Marshal serializes the current credentials to an authentication token with the expected format for 33 | // PasswordAuthenticator. 34 | func (c *AuthCredentials) Marshal() []byte { 35 | token := bytes.NewBuffer(make([]byte, 0, len(c.Username)+len(c.Password)+2)) 36 | token.WriteByte(0) 37 | token.WriteString(c.Username) 38 | token.WriteByte(0) 39 | token.WriteString(c.Password) 40 | return token.Bytes() 41 | } 42 | 43 | // Unmarshal deserializes an authentication token with the expected format for PasswordAuthenticator into the current 44 | // AuthCredentials. 45 | func (c *AuthCredentials) Unmarshal(token []byte) error { 46 | token = append(token, 0) 47 | source := bytes.NewBuffer(token) 48 | if _, err := source.ReadByte(); err != nil { 49 | return err 50 | } else if username, err := source.ReadString(0); err != nil { 51 | return err 52 | } else if password, err := source.ReadString(0); err != nil { 53 | return err 54 | } else { 55 | c.Username = username[:len(username)-1] 56 | c.Password = password[:len(password)-1] 57 | return nil 58 | } 59 | } 60 | 61 | func (c AuthCredentials) Copy() *AuthCredentials { 62 | return &c 63 | } 64 | 65 | // A simple authenticator to perform plain-text authentications for CQL clients. 66 | type PlainTextAuthenticator struct { 67 | Credentials *AuthCredentials 68 | } 69 | 70 | var ( 71 | expectedChallenge = []byte("PLAIN-START") 72 | mechanism = []byte("PLAIN") 73 | ) 74 | 75 | func (a *PlainTextAuthenticator) InitialResponse(authenticator string) ([]byte, error) { 76 | switch authenticator { 77 | case "com.datastax.bdp.cassandra.auth.DseAuthenticator": 78 | return mechanism, nil 79 | case "org.apache.cassandra.auth.PasswordAuthenticator": 80 | return a.Credentials.Marshal(), nil 81 | } 82 | return nil, fmt.Errorf("unknown authenticator: %v", authenticator) 83 | } 84 | 85 | func (a *PlainTextAuthenticator) EvaluateChallenge(challenge []byte) ([]byte, error) { 86 | if challenge == nil || bytes.Compare(challenge, expectedChallenge) != 0 { 87 | return nil, fmt.Errorf("incorrect SASL challenge from server, expecting PLAIN-START, got: %v", string(challenge)) 88 | } 89 | return a.Credentials.Marshal(), nil 90 | } 91 | -------------------------------------------------------------------------------- /client/auth_test.go: -------------------------------------------------------------------------------- 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 client 16 | 17 | import ( 18 | "testing" 19 | 20 | "github.com/stretchr/testify/assert" 21 | ) 22 | 23 | func TestAuthCredentials_Copy(t *testing.T) { 24 | 25 | credentials := &AuthCredentials{ 26 | Username: "user1", 27 | Password: "pass1", 28 | } 29 | 30 | cpy := credentials.Copy() 31 | assert.Equal(t, credentials, cpy) 32 | 33 | cpy.Username = "user2" 34 | cpy.Password = "pass2" 35 | 36 | assert.NotEqual(t, credentials, cpy) 37 | 38 | } 39 | 40 | func TestAuthCredentials_Marshal(t *testing.T) { 41 | 42 | credentials := &AuthCredentials{ 43 | Username: "user1", 44 | Password: "pass1", 45 | } 46 | 47 | bytes := credentials.Marshal() 48 | 49 | assert.Equal(t, []byte{ 50 | 0, 51 | byte('u'), byte('s'), byte('e'), byte('r'), byte('1'), 52 | 0, 53 | byte('p'), byte('a'), byte('s'), byte('s'), byte('1'), 54 | }, bytes) 55 | } 56 | 57 | func TestAuthCredentials_Unmarshal(t *testing.T) { 58 | 59 | credentials := &AuthCredentials{} 60 | 61 | err := credentials.Unmarshal([]byte{ 62 | 0, 63 | byte('u'), byte('s'), byte('e'), byte('r'), byte('1'), 64 | 0, 65 | byte('p'), byte('a'), byte('s'), byte('s'), byte('1'), 66 | }) 67 | 68 | assert.Nil(t, err) 69 | assert.Equal(t, "user1", credentials.Username) 70 | assert.Equal(t, "pass1", credentials.Password) 71 | } 72 | -------------------------------------------------------------------------------- /client/compression.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 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 client 16 | 17 | import ( 18 | "github.com/datastax/go-cassandra-native-protocol/compression/lz4" 19 | "github.com/datastax/go-cassandra-native-protocol/compression/snappy" 20 | "github.com/datastax/go-cassandra-native-protocol/frame" 21 | "github.com/datastax/go-cassandra-native-protocol/primitive" 22 | "github.com/datastax/go-cassandra-native-protocol/segment" 23 | ) 24 | 25 | func NewBodyCompressor(c primitive.Compression) frame.BodyCompressor { 26 | switch c { 27 | case primitive.CompressionNone: 28 | return nil 29 | case primitive.CompressionLz4: 30 | return &lz4.Compressor{} 31 | case primitive.CompressionSnappy: 32 | return &snappy.Compressor{} 33 | default: 34 | return nil 35 | } 36 | } 37 | 38 | func NewPayloadCompressor(c primitive.Compression) segment.PayloadCompressor { 39 | switch c { 40 | case primitive.CompressionNone: 41 | return nil 42 | case primitive.CompressionLz4: 43 | return &lz4.Compressor{} 44 | case primitive.CompressionSnappy: 45 | // Snappy not supported for payload compression 46 | return nil 47 | default: 48 | return nil 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /client/doc.go: -------------------------------------------------------------------------------- 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 | /* 16 | 17 | Package client contains basic utilities to exchange native protocol frames with compatible endpoints. 18 | 19 | The main type in this package is CqlClient, a simple CQL client that can be used to test any CQL-compatible backend. 20 | 21 | Please note that code in this package is intended mostly to help driver implementors test their libraries; it should 22 | not be used in production. 23 | 24 | */ 25 | package client 26 | -------------------------------------------------------------------------------- /client/handshake_test.go: -------------------------------------------------------------------------------- 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 client_test 16 | 17 | import ( 18 | "context" 19 | "github.com/datastax/go-cassandra-native-protocol/client" 20 | "github.com/datastax/go-cassandra-native-protocol/primitive" 21 | "github.com/stretchr/testify/assert" 22 | "github.com/stretchr/testify/require" 23 | "testing" 24 | "time" 25 | ) 26 | 27 | func TestHandshakeHandler_NoAuth(t *testing.T) { 28 | 29 | server := client.NewCqlServer("127.0.0.1:9043", nil) 30 | server.RequestHandlers = []client.RequestHandler{client.HandshakeHandler} 31 | 32 | clt := client.NewCqlClient("127.0.0.1:9043", nil) 33 | 34 | ctx, cancelFn := context.WithCancel(context.Background()) 35 | 36 | err := server.Start(ctx) 37 | require.NoError(t, err) 38 | 39 | clientConn, err := clt.Connect(ctx) 40 | require.NoError(t, err) 41 | require.NotNil(t, clientConn) 42 | 43 | err = clientConn.InitiateHandshake(primitive.ProtocolVersion4, client.ManagedStreamId) 44 | require.NoError(t, err) 45 | 46 | cancelFn() 47 | 48 | assert.Eventually(t, clientConn.IsClosed, time.Second*10, time.Millisecond*10) 49 | assert.Eventually(t, server.IsClosed, time.Second*10, time.Millisecond*10) 50 | 51 | } 52 | 53 | func TestHandshakeHandler_Auth(t *testing.T) { 54 | 55 | server := client.NewCqlServer("127.0.0.1:9043", &client.AuthCredentials{ 56 | Username: "user1", 57 | Password: "pass1", 58 | }) 59 | server.RequestHandlers = []client.RequestHandler{client.HandshakeHandler} 60 | 61 | clt := client.NewCqlClient("127.0.0.1:9043", &client.AuthCredentials{ 62 | Username: "user1", 63 | Password: "pass1", 64 | }) 65 | 66 | ctx, cancelFn := context.WithCancel(context.Background()) 67 | 68 | err := server.Start(ctx) 69 | require.NoError(t, err) 70 | 71 | clientConn, err := clt.Connect(ctx) 72 | require.NoError(t, err) 73 | require.NotNil(t, clientConn) 74 | 75 | err = clientConn.InitiateHandshake(primitive.ProtocolVersion4, client.ManagedStreamId) 76 | require.NoError(t, err) 77 | 78 | cancelFn() 79 | 80 | assert.Eventually(t, clientConn.IsClosed, time.Second*10, time.Millisecond*10) 81 | assert.Eventually(t, server.IsClosed, time.Second*10, time.Millisecond*10) 82 | 83 | } 84 | -------------------------------------------------------------------------------- /client/main_test.go: -------------------------------------------------------------------------------- 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 client_test 16 | 17 | import ( 18 | "flag" 19 | "github.com/datastax/go-cassandra-native-protocol/client" 20 | "github.com/datastax/go-cassandra-native-protocol/primitive" 21 | "github.com/rs/zerolog" 22 | "github.com/rs/zerolog/log" 23 | "math" 24 | "os" 25 | "sync/atomic" 26 | "testing" 27 | ) 28 | 29 | var remoteAvailable bool 30 | var logLevel int 31 | 32 | func TestMain(m *testing.M) { 33 | parseFlags() 34 | setLogLevel() 35 | createStreamIdGenerators() 36 | os.Exit(m.Run()) 37 | } 38 | 39 | func parseFlags() { 40 | flag.IntVar( 41 | &logLevel, 42 | "logLevel", 43 | int(zerolog.ErrorLevel), 44 | "the log level to use (default: info)", 45 | ) 46 | flag.BoolVar( 47 | &remoteAvailable, 48 | "remote", 49 | false, 50 | "whether a remote cluster is available on localhost:9042", 51 | ) 52 | flag.Parse() 53 | } 54 | 55 | func setLogLevel() { 56 | zerolog.SetGlobalLevel(zerolog.Level(logLevel)) 57 | log.Logger = log.Output(zerolog.ConsoleWriter{ 58 | Out: os.Stderr, 59 | TimeFormat: zerolog.TimeFormatUnix, 60 | }) 61 | } 62 | 63 | var compressions = []primitive.Compression{primitive.CompressionNone, primitive.CompressionLz4, primitive.CompressionSnappy} 64 | 65 | var streamIdGenerators map[string]func(int, primitive.ProtocolVersion) int16 66 | 67 | func createStreamIdGenerators() { 68 | var managed = func(clientId int, version primitive.ProtocolVersion) int16 { 69 | return client.ManagedStreamId 70 | } 71 | var fixed = func(clientId int, version primitive.ProtocolVersion) int16 { 72 | if int16(clientId) == client.ManagedStreamId { 73 | panic("stream id 0") 74 | } 75 | return int16(clientId) 76 | } 77 | counter := uint32(1) 78 | var incremental = func(clientId int, version primitive.ProtocolVersion) int16 { 79 | var max uint32 80 | if version <= primitive.ProtocolVersion2 { 81 | max = math.MaxInt8 82 | } else { 83 | max = math.MaxInt16 84 | } 85 | for { 86 | current := counter 87 | next := current + 1 88 | if next > max { 89 | next = 1 90 | } 91 | if atomic.CompareAndSwapUint32(&counter, current, next) { 92 | return int16(next) 93 | } 94 | } 95 | } 96 | streamIdGenerators = map[string]func(int, primitive.ProtocolVersion) int16{ 97 | "managed": managed, 98 | "fixed": fixed, 99 | "incremental": incremental, 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /client/prepare.go: -------------------------------------------------------------------------------- 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 client 16 | 17 | import ( 18 | "github.com/rs/zerolog/log" 19 | 20 | "github.com/datastax/go-cassandra-native-protocol/frame" 21 | "github.com/datastax/go-cassandra-native-protocol/message" 22 | ) 23 | 24 | // A RequestHandler to handle PREPARE and EXECUTE requests for the given query string, effectively emulating the 25 | // behavior of a statement being prepared, then executed. 26 | // When a PREPARE request targets the query string, it is intercepted and the handler replies with a PreparedResult. 27 | // The prepared id is simply the query string bytes, and the metadata is the metadata provided to the function. 28 | // When an EXECUTE request targets the same query string: 29 | // - If the request was previously prepared, returns a Rows RESULT response; the actual data returned is produced by 30 | // invoking the provided rows factory function, which allows the result to be customized according to the bound 31 | // variables provided with the EXECUTE message. 32 | // - If the request was not prepared, returns an Unprepared ERROR response. 33 | func NewPreparedStatementHandler( 34 | query string, 35 | variables *message.VariablesMetadata, 36 | columns *message.RowsMetadata, 37 | rows func(options *message.QueryOptions) message.RowSet, 38 | ) RequestHandler { 39 | prepared := false 40 | return func(request *frame.Frame, conn *CqlServerConnection, ctx RequestHandlerContext) (response *frame.Frame) { 41 | version := request.Header.Version 42 | id := request.Header.StreamId 43 | switch msg := request.Body.Message.(type) { 44 | case *message.Prepare: 45 | if msg.Query == query { 46 | log.Debug().Msgf("%v: [prepare handler]: intercepted PREPARE", conn) 47 | result := &message.PreparedResult{ 48 | PreparedQueryId: []byte(query), 49 | VariablesMetadata: variables, 50 | ResultMetadata: columns, 51 | } 52 | prepared = true 53 | response = frame.NewFrame(version, id, result) 54 | log.Debug().Msgf("%v: [prepare handler]: returning %v", conn, response) 55 | } 56 | case *message.Execute: 57 | if string(msg.QueryId) == query { 58 | log.Debug().Msgf("%v: [prepare handler]: intercepted EXECUTE", conn) 59 | if prepared { 60 | result := &message.RowsResult{ 61 | Metadata: columns, 62 | Data: rows(msg.Options), 63 | } 64 | response = frame.NewFrame(version, id, result) 65 | } else { 66 | result := &message.Unprepared{ 67 | ErrorMessage: "Unprepared query: " + query, 68 | Id: []byte(query), 69 | } 70 | response = frame.NewFrame(version, id, result) 71 | } 72 | log.Debug().Msgf("%v: [prepare handler]: returning %v", conn, response) 73 | } 74 | } 75 | return 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /compression/snappy/snappy.go: -------------------------------------------------------------------------------- 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 snappy 16 | 17 | import ( 18 | "bytes" 19 | "fmt" 20 | "io" 21 | 22 | "github.com/golang/snappy" 23 | ) 24 | 25 | // Compressor satisfies frame.BodyCompressor for the SNAPPY algorithm. 26 | type Compressor struct{} 27 | 28 | func (l Compressor) CompressWithLength(source io.Reader, dest io.Writer) error { 29 | if uncompressedMessage, err := bufferFromReader(source); err != nil { 30 | return fmt.Errorf("cannot read uncompressed message: %w", err) 31 | } else { 32 | compressedMessage := snappy.Encode(nil, uncompressedMessage.Bytes()) 33 | if _, err := dest.Write(compressedMessage); err != nil { 34 | return fmt.Errorf("cannot write compressed message: %w", err) 35 | } 36 | return nil 37 | } 38 | } 39 | 40 | func (l Compressor) DecompressWithLength(source io.Reader, dest io.Writer) error { 41 | if compressedMessage, err := bufferFromReader(source); err != nil { 42 | return fmt.Errorf("cannot read compressed message: %w", err) 43 | } else { 44 | if decompressedMessage, err := snappy.Decode(nil, compressedMessage.Bytes()); err != nil { 45 | return fmt.Errorf("cannot decompress message: %w", err) 46 | } else if _, err := dest.Write(decompressedMessage); err != nil { 47 | return fmt.Errorf("cannot write decompressed message: %w", err) 48 | } 49 | return nil 50 | } 51 | } 52 | 53 | func bufferFromReader(source io.Reader) (*bytes.Buffer, error) { 54 | var buf *bytes.Buffer 55 | switch s := source.(type) { 56 | case *bytes.Buffer: 57 | buf = s 58 | default: 59 | buf = &bytes.Buffer{} 60 | if _, err := buf.ReadFrom(s); err != nil { 61 | return nil, err 62 | } 63 | } 64 | return buf, nil 65 | } 66 | -------------------------------------------------------------------------------- /crc/crc24.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 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 crc 16 | 17 | // Copied and adapted from the server-side version: 18 | // https://github.com/apache/cassandra/blob/cassandra-4.0/src/java/org/apache/cassandra/net/Crc.java 19 | 20 | const crc24Init uint32 = 0x875060 21 | const crc24Poly uint32 = 0x1974F0B 22 | 23 | // ChecksumKoopman returns the CRC-24 checksum of the given data. 24 | // The parameter bytes is an up to 8-byte register containing bytes to compute the CRC over; bits will be read 25 | // least-significant to most significant. 26 | // The parameter len is the number of bytes, greater than 0 and fewer than 9, to be read from bytes. 27 | // The polynomial is chosen from https://users.ece.cmu.edu/~koopman/crc/index.html, by Philip Koopman, 28 | // and is licensed under the Creative Commons Attribution 4.0 International License 29 | // (https://creativecommons.org/licenses/by/4.0). 30 | // Koopman's own notation to represent the polynomial has been changed. 31 | // This polynomial provides hamming distance of 8 for messages up to length 105 bits; 32 | // we only support 8-64 bits at present, with an expected range of 40-48. 33 | func ChecksumKoopman(data uint64, len int) uint32 { 34 | crc := crc24Init 35 | for i := 0; i < len; i++ { 36 | crc ^= (uint32)(data) << 16 37 | data >>= 8 38 | for j := 0; j < 8; j++ { 39 | crc <<= 1 40 | if (crc & 0x1000000) != 0 { 41 | crc ^= crc24Poly 42 | } 43 | } 44 | } 45 | return crc 46 | } 47 | -------------------------------------------------------------------------------- /crc/crc24_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 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 crc 16 | 17 | import ( 18 | "fmt" 19 | "testing" 20 | ) 21 | 22 | func TestChecksumKoopman(t *testing.T) { 23 | tests := []struct { 24 | data uint64 25 | len int 26 | want uint32 27 | }{ 28 | // zero data 29 | {0, 0, 8867936}, 30 | {0, 1, 59277}, 31 | {0, 3, 8251255}, 32 | {0, 5, 11185162}, 33 | {0, 8, 9640737}, 34 | // data = Long.MaxValue 35 | {9223372036854775807, 0, 8867936}, 36 | {9223372036854775807, 1, 1294145}, 37 | {9223372036854775807, 3, 8029951}, 38 | {9223372036854775807, 5, 9326200}, 39 | {9223372036854775807, 8, 5032370}, 40 | // random data 41 | {131077, 3, 10131737}, 42 | {17181442053, 5, 3672222}, 43 | {34359607301, 5, 14445742}, 44 | } 45 | for _, tt := range tests { 46 | t.Run(fmt.Sprintf("data %v len %v", tt.data, tt.len), func(t *testing.T) { 47 | if got := ChecksumKoopman(tt.data, tt.len); got != tt.want { 48 | t.Errorf("ChecksumKoopman() = %v, want %v", got, tt.want) 49 | } 50 | }) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /crc/crc32.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 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 crc 16 | 17 | import "hash/crc32" 18 | 19 | var table = crc32.MakeTable(crc32.IEEE) 20 | var initialBytes = []byte{0xFA, 0x2D, 0x55, 0xCA} 21 | var initialChecksum = crc32.Update(0, table, initialBytes) 22 | 23 | // ChecksumIEEE returns the CRC-32 checksum of the given data. The algorithm is the one expected by Cassandra: 24 | // the actual checksum is computed over the combination of 4 fixed initial bytes and the given byte slice. 25 | func ChecksumIEEE(data []byte) uint32 { 26 | return crc32.Update(initialChecksum, table, data) 27 | } 28 | -------------------------------------------------------------------------------- /crc/crc32_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 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 crc 16 | 17 | import ( 18 | "testing" 19 | 20 | "github.com/stretchr/testify/assert" 21 | ) 22 | 23 | func TestChecksumIEEE(t *testing.T) { 24 | tests := []struct { 25 | name string 26 | data []byte 27 | expected uint32 28 | }{ 29 | { 30 | "zero", 31 | []byte{}, 32 | initialChecksum, 33 | }, 34 | // test cases below are snapshots taken from actual payloads processed by the DataStax Java driver 35 | { 36 | "QUERY (SELECT cluster_name FROM system.local)", 37 | []byte{ 38 | 6, 16, 0, 0, 7, 0, 0, 0, 47, 0, 0, 0, 37, 83, 69, 76, 69, 67, 84, 32, 99, 108, 117, 115, 116, 101, 114, 39 | 95, 110, 97, 109, 101, 32, 70, 82, 79, 77, 32, 115, 121, 115, 116, 101, 109, 46, 108, 111, 99, 97, 108, 40 | 0, 1, 0, 0, 0, 0}, 41 | 37932456, 42 | }, 43 | { 44 | "QUERY (SELECT * FROM system.local) + QUERY (SELECT * FROM system.peers_v2)", 45 | []byte{ 46 | 6, 16, 0, 0, 7, 0, 0, 0, 36, 0, 0, 0, 26, 83, 69, 76, 69, 67, 84, 32, 42, 32, 70, 82, 79, 77, 32, 115, 47 | 121, 115, 116, 101, 109, 46, 108, 111, 99, 97, 108, 0, 1, 0, 0, 0, 0, 6, 16, 0, 1, 7, 0, 0, 0, 39, 0, 48 | 0, 0, 29, 83, 69, 76, 69, 67, 84, 32, 42, 32, 70, 82, 79, 77, 32, 115, 121, 115, 116, 101, 109, 46, 49 | 112, 101, 101, 114, 115, 95, 118, 50, 0, 1, 0, 0, 0, 0}, 50 | // Java driver CRC32 for this payload is the (signed) int -642004664; 51 | // Go's uint32 equivalent is -642004664 & 0xffffffff = 3652962632. 52 | uint32(int64(-642004664) & int64(0xffffffff)), 53 | }, 54 | } 55 | for _, tt := range tests { 56 | t.Run(tt.name, func(t *testing.T) { 57 | actual := ChecksumIEEE(tt.data) 58 | assert.Equal(t, tt.expected, actual) 59 | }) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /datacodec/blob.go: -------------------------------------------------------------------------------- 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 datacodec 16 | 17 | import ( 18 | "github.com/datastax/go-cassandra-native-protocol/datatype" 19 | "github.com/datastax/go-cassandra-native-protocol/primitive" 20 | ) 21 | 22 | // Blob is a codec for the CQL blob type. Its preferred Go type is []byte, but it can encode from and decode to 23 | // string as well. When given a []byte source or destination, the encoding and decoding operations are actually no-ops, 24 | // that is: the []byte value is passed along as is. 25 | var Blob Codec = &blobCodec{dataType: datatype.Blob} 26 | 27 | // PassThrough is another name for the Blob codec. 28 | var PassThrough = Blob 29 | 30 | // NewCustom returns a codec for the CQL custom type. Its preferred Go type is []byte, but it can encode from and decode 31 | // to string as well. This codec is identical to the Blob codec. 32 | func NewCustom(customType *datatype.Custom) Codec { 33 | return &blobCodec{dataType: customType} 34 | } 35 | 36 | type blobCodec struct { 37 | dataType datatype.DataType 38 | } 39 | 40 | func (c *blobCodec) DataType() datatype.DataType { 41 | return c.dataType 42 | } 43 | 44 | func (c *blobCodec) Encode(source interface{}, version primitive.ProtocolVersion) (dest []byte, err error) { 45 | if dest, err = convertToBytes(source); err != nil { 46 | err = errCannotEncode(source, c.DataType(), version, err) 47 | } 48 | return 49 | } 50 | 51 | func (c *blobCodec) Decode(source []byte, dest interface{}, version primitive.ProtocolVersion) (wasNull bool, err error) { 52 | if wasNull, err = convertFromBytes(source, dest); err != nil { 53 | err = errCannotDecode(dest, c.DataType(), version, err) 54 | } 55 | return 56 | } 57 | 58 | func convertToBytes(source interface{}) (val []byte, err error) { 59 | switch s := source.(type) { 60 | case string: 61 | val = []byte(s) 62 | case []byte: 63 | val = s 64 | case *string: 65 | if s != nil { 66 | val = []byte(*s) 67 | } 68 | case *[]byte: 69 | if s != nil { 70 | val = *s 71 | } 72 | case nil: 73 | default: 74 | err = ErrConversionNotSupported 75 | } 76 | if err != nil { 77 | err = errSourceConversionFailed(source, val, err) 78 | } 79 | return 80 | } 81 | 82 | func convertFromBytes(val []byte, dest interface{}) (wasNull bool, err error) { 83 | wasNull = val == nil 84 | switch d := dest.(type) { 85 | case *interface{}: 86 | if d == nil { 87 | err = ErrNilDestination 88 | } else if wasNull { 89 | *d = nil 90 | } else { 91 | *d = val 92 | } 93 | case *string: 94 | if d == nil { 95 | err = ErrNilDestination 96 | } else if wasNull { 97 | *d = "" 98 | } else { 99 | *d = string(val) 100 | } 101 | case *[]byte: 102 | if d == nil { 103 | err = ErrNilDestination 104 | } else if wasNull { 105 | *d = nil 106 | } else { 107 | *d = val 108 | } 109 | default: 110 | err = errDestinationInvalid(dest) 111 | } 112 | if err != nil { 113 | err = errDestinationConversionFailed(val, dest, err) 114 | } 115 | return 116 | } 117 | -------------------------------------------------------------------------------- /datacodec/extractors.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 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 datacodec 16 | 17 | import ( 18 | "errors" 19 | "reflect" 20 | "strings" 21 | ) 22 | 23 | // A utility to extract elements from container types like slices, arrays, structs and maps, using a unified API. 24 | type extractor interface { 25 | 26 | // getElem returns the element at the given key. 27 | getElem(index int, key interface{}) (interface{}, error) 28 | } 29 | 30 | // A utility to extract keys from maps. 31 | type keyValueExtractor interface { 32 | extractor 33 | 34 | // getKey returns the key at the given index. 35 | getKey(index int) interface{} 36 | } 37 | 38 | type sliceExtractor struct { 39 | source reflect.Value 40 | } 41 | 42 | type mapExtractor struct { 43 | source reflect.Value 44 | keys []reflect.Value // to keep iteration order constant 45 | } 46 | 47 | type structExtractor struct { 48 | source reflect.Value 49 | } 50 | 51 | func newSliceExtractor(source reflect.Value) (extractor, error) { 52 | if source.Kind() != reflect.Slice && source.Kind() != reflect.Array { 53 | return nil, errors.New("expected slice or array, got: " + source.Type().String()) 54 | } else if source.Kind() == reflect.Slice && source.IsNil() { 55 | return nil, errors.New("slice is nil") 56 | } 57 | return &sliceExtractor{source}, nil 58 | } 59 | 60 | func newStructExtractor(source reflect.Value) (keyValueExtractor, error) { 61 | if source.Kind() != reflect.Struct { 62 | return nil, errors.New("expected struct, got: " + source.Type().String()) 63 | } 64 | return &structExtractor{source}, nil 65 | } 66 | 67 | func newMapExtractor(source reflect.Value) (keyValueExtractor, error) { 68 | if source.Kind() != reflect.Map { 69 | return nil, errors.New("expected map, got: " + source.Type().String()) 70 | } else if source.IsNil() { 71 | return nil, errors.New("map is nil") 72 | } 73 | return &mapExtractor{source, source.MapKeys()}, nil 74 | } 75 | 76 | func (e *sliceExtractor) getElem(index int, _ interface{}) (interface{}, error) { 77 | if index < 0 || index >= e.source.Len() { 78 | return nil, errSliceIndexOutOfRange(e.source.Type().Kind() == reflect.Slice, index) 79 | } 80 | return e.source.Index(index).Interface(), nil 81 | } 82 | 83 | func (e *structExtractor) getKey(index int) interface{} { 84 | fieldName := e.source.Type().Field(index).Name 85 | field, _ := e.source.Type().FieldByName(fieldName) 86 | tag := field.Tag.Get("cassandra") 87 | if tag != "" { 88 | return tag 89 | } 90 | return strings.ToLower(fieldName) 91 | } 92 | 93 | func (e *structExtractor) getElem(_ int, key interface{}) (interface{}, error) { 94 | var field reflect.Value 95 | if name, ok := key.(string); ok { 96 | field = locateFieldByName(e.source, name) 97 | } else if index, ok := key.(int); ok { 98 | field = locateFieldByIndex(e.source, index) 99 | } 100 | if !field.IsValid() || !field.CanInterface() { 101 | return nil, errStructFieldInvalid(e.source, key) 102 | } 103 | return field.Interface(), nil 104 | } 105 | 106 | func (e *mapExtractor) getKey(index int) interface{} { 107 | return e.keys[index].Interface() 108 | } 109 | 110 | func (e *mapExtractor) getElem(_ int, key interface{}) (interface{}, error) { 111 | keyType := e.source.Type().Key() 112 | keyValue := reflect.ValueOf(key) 113 | if key == nil { 114 | keyValue = reflect.Zero(keyType) 115 | } 116 | if !keyValue.Type().AssignableTo(keyType) { 117 | return nil, errWrongElementType("map key", keyType, keyValue.Type()) 118 | } 119 | value := e.source.MapIndex(keyValue) 120 | if !value.IsValid() || !value.CanInterface() { 121 | return nil, nil // key not found 122 | } 123 | return value.Interface(), nil 124 | } 125 | -------------------------------------------------------------------------------- /datacodec/float.go: -------------------------------------------------------------------------------- 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 datacodec 16 | 17 | import ( 18 | "encoding/binary" 19 | "math" 20 | 21 | "github.com/datastax/go-cassandra-native-protocol/datatype" 22 | "github.com/datastax/go-cassandra-native-protocol/primitive" 23 | ) 24 | 25 | // Float is a codec for the CQL float type. Its preferred Go type is float32, but it can encode from and decode 26 | // to float64 as well. 27 | var Float Codec = &floatCodec{} 28 | 29 | type floatCodec struct{} 30 | 31 | func (c *floatCodec) DataType() datatype.DataType { 32 | return datatype.Float 33 | } 34 | 35 | func (c *floatCodec) Encode(source interface{}, version primitive.ProtocolVersion) (dest []byte, err error) { 36 | var val float32 37 | var wasNil bool 38 | if val, wasNil, err = convertToFloat32(source); err == nil && !wasNil { 39 | dest = writeFloat32(val) 40 | } 41 | if err != nil { 42 | err = errCannotEncode(source, c.DataType(), version, err) 43 | } 44 | return 45 | } 46 | 47 | func (c *floatCodec) Decode(source []byte, dest interface{}, version primitive.ProtocolVersion) (wasNull bool, err error) { 48 | var val float32 49 | if val, wasNull, err = readFloat32(source); err == nil { 50 | err = convertFromFloat32(val, wasNull, dest) 51 | } 52 | if err != nil { 53 | err = errCannotDecode(dest, c.DataType(), version, err) 54 | } 55 | return 56 | } 57 | 58 | func convertToFloat32(source interface{}) (val float32, wasNil bool, err error) { 59 | switch s := source.(type) { 60 | case float64: 61 | val, err = float64ToFloat32(s) 62 | case float32: 63 | val = s 64 | case *float64: 65 | if wasNil = s == nil; !wasNil { 66 | val, err = float64ToFloat32(*s) 67 | } 68 | case *float32: 69 | if wasNil = s == nil; !wasNil { 70 | val = *s 71 | } 72 | case nil: 73 | wasNil = true 74 | default: 75 | err = ErrConversionNotSupported 76 | } 77 | if err != nil { 78 | err = errSourceConversionFailed(source, val, err) 79 | } 80 | return 81 | } 82 | 83 | func convertFromFloat32(val float32, wasNull bool, dest interface{}) (err error) { 84 | switch d := dest.(type) { 85 | case *interface{}: 86 | if d == nil { 87 | err = ErrNilDestination 88 | } else if wasNull { 89 | *d = nil 90 | } else { 91 | *d = val 92 | } 93 | case *float64: 94 | if d == nil { 95 | err = ErrNilDestination 96 | } else if wasNull { 97 | *d = 0 98 | } else { 99 | *d = float64(val) 100 | } 101 | case *float32: 102 | if d == nil { 103 | err = ErrNilDestination 104 | } else if wasNull { 105 | *d = 0 106 | } else { 107 | *d = val 108 | } 109 | default: 110 | err = errDestinationInvalid(dest) 111 | } 112 | if err != nil { 113 | err = errDestinationConversionFailed(val, dest, err) 114 | } 115 | return 116 | } 117 | 118 | const lengthOfFloat = primitive.LengthOfInt 119 | 120 | func writeFloat32(val float32) (dest []byte) { 121 | dest = make([]byte, lengthOfFloat) 122 | binary.BigEndian.PutUint32(dest, math.Float32bits(val)) 123 | return 124 | } 125 | 126 | func readFloat32(source []byte) (val float32, wasNull bool, err error) { 127 | length := len(source) 128 | if length == 0 { 129 | wasNull = true 130 | } else if length != lengthOfFloat { 131 | err = errWrongFixedLength(lengthOfFloat, length) 132 | } else { 133 | val = math.Float32frombits(binary.BigEndian.Uint32(source)) 134 | } 135 | if err != nil { 136 | err = errCannotRead(val, err) 137 | } 138 | return 139 | } 140 | -------------------------------------------------------------------------------- /datacodec/math.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 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 datacodec 16 | 17 | import ( 18 | "math" 19 | ) 20 | 21 | // Adapted from java.lang.Math#addExact(long, long). 22 | // Returns the sum of its arguments, and a boolean indicating whether the sum overflowed. 23 | func addExact(x, y int64) (int64, bool) { 24 | r := x + y 25 | if ((x ^ r) & (y ^ r)) < 0 { 26 | return 0, true 27 | } 28 | return r, false 29 | } 30 | 31 | // Adapted from: 32 | // https://stackoverflow.com/questions/50744681/testing-overflow-in-integer-multiplication. 33 | // Returns the product of its arguments, and a boolean indicating whether the product overflowed. 34 | // Another interesting implementation can be found in java.lang.Math#multiplyExact(long, long). 35 | func multiplyExact(x, y int64) (int64, bool) { 36 | if x == 0 || y == 0 || x == 1 || y == 1 { 37 | return x * y, false 38 | } else if x == math.MinInt64 || y == math.MinInt64 { 39 | return 0, true 40 | } else { 41 | r := x * y 42 | if r/y != x { 43 | return 0, true 44 | } 45 | return r, false 46 | } 47 | } 48 | 49 | // Adapted from java.lang.Math#floorDiv(int, int). 50 | // Returns the largest (closest to positive infinity) long value that is less than or equal to the algebraic quotient. 51 | // There is one special case, if the dividend is the Long.MIN_VALUE and the divisor is -1, then integer overflow occurs 52 | // and the result is equal to the Long.MIN_VALUE. 53 | // Normal integer division operates under the round to zero rounding mode (truncation). This operation instead acts 54 | // under the round toward negative infinity (floor) rounding mode. The floor rounding mode gives different results than 55 | // truncation when the exact result is negative. 56 | func floorDiv(x, y int64) int64 { 57 | r := x / y 58 | // if the signs are different and modulo not zero, round down 59 | if (x^y) < 0 && (r*y != x) { 60 | r-- 61 | } 62 | return r 63 | } 64 | 65 | // Adapted from java.lang.Math#floorMod(int, int). 66 | // Returns the floor modulus of the long arguments. 67 | // The floor modulus is x - (floorDiv(x, y) * y), has the same sign as the divisor y, and is in the range of 68 | // -abs(y) < r < +abs(y). 69 | func floorMod(x, y int64) int64 { 70 | return x - floorDiv(x, y)*y 71 | } 72 | -------------------------------------------------------------------------------- /datacodec/mock_codec_test.go: -------------------------------------------------------------------------------- 1 | // Code generated by mockery v2.12.3. DO NOT EDIT. 2 | 3 | package datacodec 4 | 5 | import ( 6 | mock "github.com/stretchr/testify/mock" 7 | 8 | datatype "github.com/datastax/go-cassandra-native-protocol/datatype" 9 | 10 | primitive "github.com/datastax/go-cassandra-native-protocol/primitive" 11 | ) 12 | 13 | // mockCodec is an autogenerated mock type for the Codec type 14 | type mockCodec struct { 15 | mock.Mock 16 | } 17 | 18 | // DataType provides a mock function with given fields: 19 | func (_m *mockCodec) DataType() datatype.DataType { 20 | ret := _m.Called() 21 | 22 | var r0 datatype.DataType 23 | if rf, ok := ret.Get(0).(func() datatype.DataType); ok { 24 | r0 = rf() 25 | } else { 26 | if ret.Get(0) != nil { 27 | r0 = ret.Get(0).(datatype.DataType) 28 | } 29 | } 30 | 31 | return r0 32 | } 33 | 34 | // Decode provides a mock function with given fields: source, dest, version 35 | func (_m *mockCodec) Decode(source []byte, dest interface{}, version primitive.ProtocolVersion) (bool, error) { 36 | ret := _m.Called(source, dest, version) 37 | 38 | var r0 bool 39 | if rf, ok := ret.Get(0).(func([]byte, interface{}, primitive.ProtocolVersion) bool); ok { 40 | r0 = rf(source, dest, version) 41 | } else { 42 | r0 = ret.Get(0).(bool) 43 | } 44 | 45 | var r1 error 46 | if rf, ok := ret.Get(1).(func([]byte, interface{}, primitive.ProtocolVersion) error); ok { 47 | r1 = rf(source, dest, version) 48 | } else { 49 | r1 = ret.Error(1) 50 | } 51 | 52 | return r0, r1 53 | } 54 | 55 | // Encode provides a mock function with given fields: source, version 56 | func (_m *mockCodec) Encode(source interface{}, version primitive.ProtocolVersion) ([]byte, error) { 57 | ret := _m.Called(source, version) 58 | 59 | var r0 []byte 60 | if rf, ok := ret.Get(0).(func(interface{}, primitive.ProtocolVersion) []byte); ok { 61 | r0 = rf(source, version) 62 | } else { 63 | if ret.Get(0) != nil { 64 | r0 = ret.Get(0).([]byte) 65 | } 66 | } 67 | 68 | var r1 error 69 | if rf, ok := ret.Get(1).(func(interface{}, primitive.ProtocolVersion) error); ok { 70 | r1 = rf(source, version) 71 | } else { 72 | r1 = ret.Error(1) 73 | } 74 | 75 | return r0, r1 76 | } 77 | 78 | type newMockCodecT interface { 79 | mock.TestingT 80 | Cleanup(func()) 81 | } 82 | 83 | // newMockCodec creates a new instance of mockCodec. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. 84 | func newMockCodec(t newMockCodecT) *mockCodec { 85 | mock := &mockCodec{} 86 | mock.Mock.Test(t) 87 | 88 | t.Cleanup(func() { mock.AssertExpectations(t) }) 89 | 90 | return mock 91 | } 92 | -------------------------------------------------------------------------------- /datacodec/mock_extractor_test.go: -------------------------------------------------------------------------------- 1 | // Code generated by mockery v2.12.3. DO NOT EDIT. 2 | 3 | package datacodec 4 | 5 | import mock "github.com/stretchr/testify/mock" 6 | 7 | // mockExtractor is an autogenerated mock type for the extractor type 8 | type mockExtractor struct { 9 | mock.Mock 10 | } 11 | 12 | // getElem provides a mock function with given fields: index, key 13 | func (_m *mockExtractor) getElem(index int, key interface{}) (interface{}, error) { 14 | ret := _m.Called(index, key) 15 | 16 | var r0 interface{} 17 | if rf, ok := ret.Get(0).(func(int, interface{}) interface{}); ok { 18 | r0 = rf(index, key) 19 | } else { 20 | if ret.Get(0) != nil { 21 | r0 = ret.Get(0).(interface{}) 22 | } 23 | } 24 | 25 | var r1 error 26 | if rf, ok := ret.Get(1).(func(int, interface{}) error); ok { 27 | r1 = rf(index, key) 28 | } else { 29 | r1 = ret.Error(1) 30 | } 31 | 32 | return r0, r1 33 | } 34 | 35 | type newMockExtractorT interface { 36 | mock.TestingT 37 | Cleanup(func()) 38 | } 39 | 40 | // newMockExtractor creates a new instance of mockExtractor. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. 41 | func newMockExtractor(t newMockExtractorT) *mockExtractor { 42 | mock := &mockExtractor{} 43 | mock.Mock.Test(t) 44 | 45 | t.Cleanup(func() { mock.AssertExpectations(t) }) 46 | 47 | return mock 48 | } 49 | -------------------------------------------------------------------------------- /datacodec/mock_injector_test.go: -------------------------------------------------------------------------------- 1 | // Code generated by mockery v2.12.3. DO NOT EDIT. 2 | 3 | package datacodec 4 | 5 | import mock "github.com/stretchr/testify/mock" 6 | 7 | // mockInjector is an autogenerated mock type for the injector type 8 | type mockInjector struct { 9 | mock.Mock 10 | } 11 | 12 | // setElem provides a mock function with given fields: index, key, value, keyWasNull, valueWasNull 13 | func (_m *mockInjector) setElem(index int, key interface{}, value interface{}, keyWasNull bool, valueWasNull bool) error { 14 | ret := _m.Called(index, key, value, keyWasNull, valueWasNull) 15 | 16 | var r0 error 17 | if rf, ok := ret.Get(0).(func(int, interface{}, interface{}, bool, bool) error); ok { 18 | r0 = rf(index, key, value, keyWasNull, valueWasNull) 19 | } else { 20 | r0 = ret.Error(0) 21 | } 22 | 23 | return r0 24 | } 25 | 26 | // zeroElem provides a mock function with given fields: index, key 27 | func (_m *mockInjector) zeroElem(index int, key interface{}) (interface{}, error) { 28 | ret := _m.Called(index, key) 29 | 30 | var r0 interface{} 31 | if rf, ok := ret.Get(0).(func(int, interface{}) interface{}); ok { 32 | r0 = rf(index, key) 33 | } else { 34 | if ret.Get(0) != nil { 35 | r0 = ret.Get(0).(interface{}) 36 | } 37 | } 38 | 39 | var r1 error 40 | if rf, ok := ret.Get(1).(func(int, interface{}) error); ok { 41 | r1 = rf(index, key) 42 | } else { 43 | r1 = ret.Error(1) 44 | } 45 | 46 | return r0, r1 47 | } 48 | 49 | type newMockInjectorT interface { 50 | mock.TestingT 51 | Cleanup(func()) 52 | } 53 | 54 | // newMockInjector creates a new instance of mockInjector. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. 55 | func newMockInjector(t newMockInjectorT) *mockInjector { 56 | mock := &mockInjector{} 57 | mock.Mock.Test(t) 58 | 59 | t.Cleanup(func() { mock.AssertExpectations(t) }) 60 | 61 | return mock 62 | } 63 | -------------------------------------------------------------------------------- /datacodec/mock_key_value_extractor_test.go: -------------------------------------------------------------------------------- 1 | // Code generated by mockery v2.12.3. DO NOT EDIT. 2 | 3 | package datacodec 4 | 5 | import mock "github.com/stretchr/testify/mock" 6 | 7 | // mockKeyValueExtractor is an autogenerated mock type for the keyValueExtractor type 8 | type mockKeyValueExtractor struct { 9 | mock.Mock 10 | } 11 | 12 | // getElem provides a mock function with given fields: index, key 13 | func (_m *mockKeyValueExtractor) getElem(index int, key interface{}) (interface{}, error) { 14 | ret := _m.Called(index, key) 15 | 16 | var r0 interface{} 17 | if rf, ok := ret.Get(0).(func(int, interface{}) interface{}); ok { 18 | r0 = rf(index, key) 19 | } else { 20 | if ret.Get(0) != nil { 21 | r0 = ret.Get(0).(interface{}) 22 | } 23 | } 24 | 25 | var r1 error 26 | if rf, ok := ret.Get(1).(func(int, interface{}) error); ok { 27 | r1 = rf(index, key) 28 | } else { 29 | r1 = ret.Error(1) 30 | } 31 | 32 | return r0, r1 33 | } 34 | 35 | // getKey provides a mock function with given fields: index 36 | func (_m *mockKeyValueExtractor) getKey(index int) interface{} { 37 | ret := _m.Called(index) 38 | 39 | var r0 interface{} 40 | if rf, ok := ret.Get(0).(func(int) interface{}); ok { 41 | r0 = rf(index) 42 | } else { 43 | if ret.Get(0) != nil { 44 | r0 = ret.Get(0).(interface{}) 45 | } 46 | } 47 | 48 | return r0 49 | } 50 | 51 | type newMockKeyValueExtractorT interface { 52 | mock.TestingT 53 | Cleanup(func()) 54 | } 55 | 56 | // newMockKeyValueExtractor creates a new instance of mockKeyValueExtractor. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. 57 | func newMockKeyValueExtractor(t newMockKeyValueExtractorT) *mockKeyValueExtractor { 58 | mock := &mockKeyValueExtractor{} 59 | mock.Mock.Test(t) 60 | 61 | t.Cleanup(func() { mock.AssertExpectations(t) }) 62 | 63 | return mock 64 | } 65 | -------------------------------------------------------------------------------- /datacodec/mock_key_value_injector_test.go: -------------------------------------------------------------------------------- 1 | // Code generated by mockery v2.12.3. DO NOT EDIT. 2 | 3 | package datacodec 4 | 5 | import mock "github.com/stretchr/testify/mock" 6 | 7 | // mockKeyValueInjector is an autogenerated mock type for the keyValueInjector type 8 | type mockKeyValueInjector struct { 9 | mock.Mock 10 | } 11 | 12 | // setElem provides a mock function with given fields: index, key, value, keyWasNull, valueWasNull 13 | func (_m *mockKeyValueInjector) setElem(index int, key interface{}, value interface{}, keyWasNull bool, valueWasNull bool) error { 14 | ret := _m.Called(index, key, value, keyWasNull, valueWasNull) 15 | 16 | var r0 error 17 | if rf, ok := ret.Get(0).(func(int, interface{}, interface{}, bool, bool) error); ok { 18 | r0 = rf(index, key, value, keyWasNull, valueWasNull) 19 | } else { 20 | r0 = ret.Error(0) 21 | } 22 | 23 | return r0 24 | } 25 | 26 | // zeroElem provides a mock function with given fields: index, key 27 | func (_m *mockKeyValueInjector) zeroElem(index int, key interface{}) (interface{}, error) { 28 | ret := _m.Called(index, key) 29 | 30 | var r0 interface{} 31 | if rf, ok := ret.Get(0).(func(int, interface{}) interface{}); ok { 32 | r0 = rf(index, key) 33 | } else { 34 | if ret.Get(0) != nil { 35 | r0 = ret.Get(0).(interface{}) 36 | } 37 | } 38 | 39 | var r1 error 40 | if rf, ok := ret.Get(1).(func(int, interface{}) error); ok { 41 | r1 = rf(index, key) 42 | } else { 43 | r1 = ret.Error(1) 44 | } 45 | 46 | return r0, r1 47 | } 48 | 49 | // zeroKey provides a mock function with given fields: index 50 | func (_m *mockKeyValueInjector) zeroKey(index int) (interface{}, error) { 51 | ret := _m.Called(index) 52 | 53 | var r0 interface{} 54 | if rf, ok := ret.Get(0).(func(int) interface{}); ok { 55 | r0 = rf(index) 56 | } else { 57 | if ret.Get(0) != nil { 58 | r0 = ret.Get(0).(interface{}) 59 | } 60 | } 61 | 62 | var r1 error 63 | if rf, ok := ret.Get(1).(func(int) error); ok { 64 | r1 = rf(index) 65 | } else { 66 | r1 = ret.Error(1) 67 | } 68 | 69 | return r0, r1 70 | } 71 | 72 | type newMockKeyValueInjectorT interface { 73 | mock.TestingT 74 | Cleanup(func()) 75 | } 76 | 77 | // newMockKeyValueInjector creates a new instance of mockKeyValueInjector. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. 78 | func newMockKeyValueInjector(t newMockKeyValueInjectorT) *mockKeyValueInjector { 79 | mock := &mockKeyValueInjector{} 80 | mock.Mock.Test(t) 81 | 82 | t.Cleanup(func() { mock.AssertExpectations(t) }) 83 | 84 | return mock 85 | } 86 | -------------------------------------------------------------------------------- /datacodec/mocks.md: -------------------------------------------------------------------------------- 1 | ### Generating mocks 2 | 3 | Mock objects are generated with [Mockery](https://github.com/vektra/mockery). 4 | 5 | To install Mockery on macOS: 6 | 7 | brew install mockery 8 | 9 | For more installation options, see [installation](https://github.com/vektra/mockery#installation). Note that using `go 10 | install` is not officially supported by Mockery. 11 | 12 | To re-generate the mocks, run the following commands: 13 | 14 | mockery --dir=./datacodec --name=Codec --output=./datacodec --outpkg=datacodec --filename=mock_codec_test.go --structname=mockCodec 15 | mockery --dir=./datacodec --name=extractor --output=./datacodec --outpkg=datacodec --filename=mock_extractor_test.go --structname=mockExtractor 16 | mockery --dir=./datacodec --name=injector --output=./datacodec --outpkg=datacodec --filename=mock_injector_test.go --structname=mockInjector 17 | mockery --dir=./datacodec --name=keyValueExtractor --output=./datacodec --outpkg=datacodec --filename=mock_key_value_extractor_test.go --structname=mockKeyValueExtractor 18 | mockery --dir=./datacodec --name=keyValueInjector --output=./datacodec --outpkg=datacodec --filename=mock_key_value_injector_test.go --structname=mockKeyValueInjector 19 | 20 | The Makefile has a `mocks` target that will regenerate all mock objects. 21 | -------------------------------------------------------------------------------- /datacodec/varchar.go: -------------------------------------------------------------------------------- 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 datacodec 16 | 17 | import ( 18 | "github.com/datastax/go-cassandra-native-protocol/datatype" 19 | "github.com/datastax/go-cassandra-native-protocol/primitive" 20 | ) 21 | 22 | // Varchar is a codec for the CQL varchar (or text) type. Its preferred Go type is string, but it can encode 23 | // from and decode to []byte and []rune as well. 24 | var Varchar Codec = &stringCodec{datatype.Varchar} 25 | 26 | // Ascii is a codec for the CQL ascii type. Its preferred Go type is string, but it can encode from 27 | // and decode to []byte and []rune as well. 28 | // The returned codec does not actually enforce that all strings are valid ASCII; it's the caller's responsibility to 29 | // ensure that they are valid. 30 | var Ascii Codec = &stringCodec{datatype.Ascii} 31 | 32 | type stringCodec struct { 33 | dataType datatype.DataType 34 | } 35 | 36 | func (c *stringCodec) DataType() datatype.DataType { 37 | return c.dataType 38 | } 39 | 40 | func (c *stringCodec) Encode(source interface{}, version primitive.ProtocolVersion) (dest []byte, err error) { 41 | if dest, err = convertToStringBytes(source); err != nil { 42 | err = errCannotEncode(source, c.DataType(), version, err) 43 | } 44 | return 45 | } 46 | 47 | func (c *stringCodec) Decode(source []byte, dest interface{}, version primitive.ProtocolVersion) (wasNull bool, err error) { 48 | if wasNull, err = convertFromStringBytes(source, dest); err != nil { 49 | err = errCannotDecode(dest, c.DataType(), version, err) 50 | } 51 | return 52 | } 53 | 54 | func convertToStringBytes(source interface{}) (val []byte, err error) { 55 | switch s := source.(type) { 56 | case string: 57 | val = []byte(s) 58 | case []byte: 59 | val = s 60 | case []rune: 61 | val = []byte(string(s)) 62 | case *string: 63 | if s != nil { 64 | val = []byte(*s) 65 | } 66 | case *[]byte: 67 | if s != nil { 68 | val = *s 69 | } 70 | case *[]rune: 71 | if s != nil { 72 | val = []byte(string(*s)) 73 | } 74 | case nil: 75 | default: 76 | err = ErrConversionNotSupported 77 | } 78 | if err != nil { 79 | err = errSourceConversionFailed(source, val, err) 80 | } 81 | return 82 | } 83 | 84 | func convertFromStringBytes(val []byte, dest interface{}) (wasNull bool, err error) { 85 | wasNull = val == nil 86 | switch d := dest.(type) { 87 | case *interface{}: 88 | if d == nil { 89 | err = ErrNilDestination 90 | } else if wasNull { 91 | *d = nil 92 | } else { 93 | *d = string(val) 94 | } 95 | case *string: 96 | if d == nil { 97 | err = ErrNilDestination 98 | } else if wasNull { 99 | *d = "" 100 | } else { 101 | *d = string(val) 102 | } 103 | case *[]byte: 104 | if d == nil { 105 | err = ErrNilDestination 106 | } else if wasNull { 107 | *d = nil 108 | } else { 109 | *d = val 110 | } 111 | case *[]rune: 112 | if d == nil { 113 | err = ErrNilDestination 114 | } else if wasNull { 115 | *d = nil 116 | } else { 117 | *d = []rune(string(val)) 118 | } 119 | default: 120 | err = errDestinationInvalid(dest) 121 | } 122 | if err != nil { 123 | err = errDestinationConversionFailed(val, dest, err) 124 | } 125 | return 126 | } 127 | -------------------------------------------------------------------------------- /datatype/custom.go: -------------------------------------------------------------------------------- 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 datatype 16 | 17 | import ( 18 | "fmt" 19 | "io" 20 | 21 | "github.com/datastax/go-cassandra-native-protocol/primitive" 22 | ) 23 | 24 | // Custom is a data type that represents a CQL custom type. 25 | // +k8s:deepcopy-gen=true 26 | // +k8s:deepcopy-gen:interfaces=github.com/datastax/go-cassandra-native-protocol/datatype.DataType 27 | type Custom struct { 28 | ClassName string 29 | } 30 | 31 | func NewCustom(className string) *Custom { 32 | return &Custom{ClassName: className} 33 | } 34 | 35 | func (t *Custom) Code() primitive.DataTypeCode { 36 | return primitive.DataTypeCodeCustom 37 | } 38 | 39 | func (t *Custom) String() string { 40 | return t.AsCql() 41 | } 42 | 43 | func (t *Custom) AsCql() string { 44 | return fmt.Sprintf("'%v'", t.ClassName) 45 | } 46 | 47 | func writeCustomType(t DataType, dest io.Writer, _ primitive.ProtocolVersion) (err error) { 48 | if customType, ok := t.(*Custom); !ok { 49 | return fmt.Errorf("expected *Custom, got %T", t) 50 | } else if err = primitive.WriteString(customType.ClassName, dest); err != nil { 51 | return fmt.Errorf("cannot write custom type class name: %w", err) 52 | } 53 | return nil 54 | } 55 | 56 | func lengthOfCustomType(t DataType, _ primitive.ProtocolVersion) (length int, err error) { 57 | if customType, ok := t.(*Custom); !ok { 58 | return -1, fmt.Errorf("expected *Custom, got %T", t) 59 | } else { 60 | length += primitive.LengthOfString(customType.ClassName) 61 | } 62 | return length, nil 63 | } 64 | 65 | func readCustomType(source io.Reader, _ primitive.ProtocolVersion) (t DataType, err error) { 66 | customType := &Custom{} 67 | if customType.ClassName, err = primitive.ReadString(source); err != nil { 68 | return nil, fmt.Errorf("cannot read custom type class name: %w", err) 69 | } 70 | return customType, nil 71 | } 72 | -------------------------------------------------------------------------------- /datatype/custom_test.go: -------------------------------------------------------------------------------- 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 datatype 16 | 17 | import ( 18 | "bytes" 19 | "errors" 20 | "fmt" 21 | "testing" 22 | 23 | "github.com/stretchr/testify/assert" 24 | 25 | "github.com/datastax/go-cassandra-native-protocol/primitive" 26 | ) 27 | 28 | func TestCustomType(t *testing.T) { 29 | customType := NewCustom("foo.bar.qix") 30 | assert.Equal(t, primitive.DataTypeCodeCustom, customType.Code()) 31 | assert.Equal(t, "foo.bar.qix", customType.ClassName) 32 | } 33 | 34 | func TestWriteCustomType(t *testing.T) { 35 | for _, version := range primitive.SupportedProtocolVersions() { 36 | t.Run(version.String(), func(t *testing.T) { 37 | tests := []struct { 38 | name string 39 | input DataType 40 | expected []byte 41 | err error 42 | }{ 43 | {"simple custom", NewCustom("hello"), []byte{0, 5, byte('h'), byte('e'), byte('l'), byte('l'), byte('o')}, nil}, 44 | {"nil custom", nil, nil, errors.New("expected *Custom, got ")}, 45 | } 46 | for _, test := range tests { 47 | t.Run(test.name, func(t *testing.T) { 48 | var dest = &bytes.Buffer{} 49 | var err error 50 | err = writeCustomType(test.input, dest, version) 51 | actual := dest.Bytes() 52 | assert.Equal(t, test.expected, actual) 53 | assert.Equal(t, test.err, err) 54 | }) 55 | } 56 | }) 57 | } 58 | } 59 | 60 | func TestLengthOfCustomType(t *testing.T) { 61 | for _, version := range primitive.SupportedProtocolVersions() { 62 | t.Run(version.String(), func(t *testing.T) { 63 | tests := []struct { 64 | name string 65 | input DataType 66 | expected int 67 | err error 68 | }{ 69 | {"simple custom", NewCustom("hello"), primitive.LengthOfString("hello"), nil}, 70 | {"nil custom", nil, -1, errors.New("expected *Custom, got ")}, 71 | } 72 | for _, test := range tests { 73 | t.Run(test.name, func(t *testing.T) { 74 | var actual int 75 | var err error 76 | actual, err = lengthOfCustomType(test.input, version) 77 | assert.Equal(t, test.expected, actual) 78 | assert.Equal(t, test.err, err) 79 | }) 80 | } 81 | }) 82 | } 83 | } 84 | 85 | func TestReadCustomType(t *testing.T) { 86 | for _, version := range primitive.SupportedProtocolVersions() { 87 | t.Run(version.String(), func(t *testing.T) { 88 | tests := []struct { 89 | name string 90 | input []byte 91 | expected DataType 92 | err error 93 | }{ 94 | {"simple custom", []byte{0, 5, byte('h'), byte('e'), byte('l'), byte('l'), byte('o')}, NewCustom("hello"), nil}, 95 | { 96 | "cannot read custom", 97 | []byte{}, 98 | nil, 99 | fmt.Errorf("cannot read custom type class name: %w", 100 | fmt.Errorf("cannot read [string] length: %w", 101 | fmt.Errorf("cannot read [short]: %w", 102 | errors.New("EOF")))), 103 | }, 104 | } 105 | for _, test := range tests { 106 | t.Run(test.name, func(t *testing.T) { 107 | var source = bytes.NewBuffer(test.input) 108 | var actual DataType 109 | var err error 110 | actual, err = readCustomType(source, version) 111 | assert.Equal(t, test.expected, actual) 112 | assert.Equal(t, test.err, err) 113 | }) 114 | } 115 | }) 116 | } 117 | } 118 | 119 | func TestCustomTypeDeepCopy(t *testing.T) { 120 | ct := NewCustom("foo.bar.qix") 121 | clonedCustomType := ct.DeepCopy() 122 | assert.Equal(t, ct, clonedCustomType) 123 | clonedCustomType.ClassName = "123" 124 | assert.Equal(t, "123", clonedCustomType.ClassName) 125 | assert.Equal(t, "foo.bar.qix", ct.ClassName) 126 | } 127 | -------------------------------------------------------------------------------- /datatype/doc.go: -------------------------------------------------------------------------------- 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 | /* 16 | 17 | Package datatype contains interfaces, types for interacting with CQL data type definitions, as well as functions to 18 | read and write CQL data type signatures, as defined in section 6 of the CQL protocol specifications. 19 | 20 | */ 21 | package datatype 22 | -------------------------------------------------------------------------------- /datatype/list.go: -------------------------------------------------------------------------------- 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 datatype 16 | 17 | import ( 18 | "fmt" 19 | "io" 20 | 21 | "github.com/datastax/go-cassandra-native-protocol/primitive" 22 | ) 23 | 24 | // List is a data type that represents a CQL list type. 25 | // +k8s:deepcopy-gen=true 26 | // +k8s:deepcopy-gen:interfaces=github.com/datastax/go-cassandra-native-protocol/datatype.DataType 27 | type List struct { 28 | ElementType DataType 29 | } 30 | 31 | func NewList(elementType DataType) *List { 32 | return &List{ElementType: elementType} 33 | } 34 | 35 | func (t *List) Code() primitive.DataTypeCode { 36 | return primitive.DataTypeCodeList 37 | } 38 | 39 | func (t *List) String() string { 40 | return t.AsCql() 41 | } 42 | 43 | func (t *List) AsCql() string { 44 | return fmt.Sprintf("list<%v>", t.ElementType.AsCql()) 45 | } 46 | 47 | func writeListType(t DataType, dest io.Writer, version primitive.ProtocolVersion) (err error) { 48 | if listType, ok := t.(*List); !ok { 49 | return fmt.Errorf("expected *List, got %T", t) 50 | } else if err = WriteDataType(listType.ElementType, dest, version); err != nil { 51 | return fmt.Errorf("cannot write list element type: %w", err) 52 | } 53 | return nil 54 | } 55 | 56 | func lengthOfListType(t DataType, version primitive.ProtocolVersion) (length int, err error) { 57 | if listType, ok := t.(*List); !ok { 58 | return -1, fmt.Errorf("expected *List, got %T", t) 59 | } else if elementLength, err := LengthOfDataType(listType.ElementType, version); err != nil { 60 | return -1, fmt.Errorf("cannot compute length of list element type: %w", err) 61 | } else { 62 | length += elementLength 63 | } 64 | return length, nil 65 | } 66 | 67 | func readListType(source io.Reader, version primitive.ProtocolVersion) (decoded DataType, err error) { 68 | listType := &List{} 69 | if listType.ElementType, err = ReadDataType(source, version); err != nil { 70 | return nil, fmt.Errorf("cannot read list element type: %w", err) 71 | } 72 | return listType, nil 73 | } 74 | -------------------------------------------------------------------------------- /datatype/map.go: -------------------------------------------------------------------------------- 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 datatype 16 | 17 | import ( 18 | "fmt" 19 | "io" 20 | 21 | "github.com/datastax/go-cassandra-native-protocol/primitive" 22 | ) 23 | 24 | // Map is a data type that represents a CQL map type. 25 | // +k8s:deepcopy-gen=true 26 | // +k8s:deepcopy-gen:interfaces=github.com/datastax/go-cassandra-native-protocol/datatype.DataType 27 | type Map struct { 28 | KeyType DataType 29 | ValueType DataType 30 | } 31 | 32 | func (t *Map) Code() primitive.DataTypeCode { 33 | return primitive.DataTypeCodeMap 34 | } 35 | 36 | func (t *Map) String() string { 37 | return t.AsCql() 38 | } 39 | 40 | func (t *Map) AsCql() string { 41 | return fmt.Sprintf("map<%v,%v>", t.KeyType.AsCql(), t.ValueType.AsCql()) 42 | } 43 | 44 | func NewMap(keyType DataType, valueType DataType) *Map { 45 | return &Map{KeyType: keyType, ValueType: valueType} 46 | } 47 | 48 | func writeMapType(t DataType, dest io.Writer, version primitive.ProtocolVersion) (err error) { 49 | mapType, ok := t.(*Map) 50 | if !ok { 51 | return fmt.Errorf("expected *Map, got %T", t) 52 | } else if err = WriteDataType(mapType.KeyType, dest, version); err != nil { 53 | return fmt.Errorf("cannot write map key type: %w", err) 54 | } else if err = WriteDataType(mapType.ValueType, dest, version); err != nil { 55 | return fmt.Errorf("cannot write map value type: %w", err) 56 | } 57 | return nil 58 | } 59 | 60 | func lengthOfMapType(t DataType, version primitive.ProtocolVersion) (length int, err error) { 61 | mapType, ok := t.(*Map) 62 | if !ok { 63 | return -1, fmt.Errorf("expected *Map, got %T", t) 64 | } 65 | if keyLength, err := LengthOfDataType(mapType.KeyType, version); err != nil { 66 | return -1, fmt.Errorf("cannot compute length of map key type: %w", err) 67 | } else { 68 | length += keyLength 69 | } 70 | if valueLength, err := LengthOfDataType(mapType.ValueType, version); err != nil { 71 | return -1, fmt.Errorf("cannot compute length of map value type: %w", err) 72 | } else { 73 | length += valueLength 74 | } 75 | return length, nil 76 | } 77 | 78 | func readMapType(source io.Reader, version primitive.ProtocolVersion) (decoded DataType, err error) { 79 | mapType := &Map{} 80 | if mapType.KeyType, err = ReadDataType(source, version); err != nil { 81 | return nil, fmt.Errorf("cannot read map key type: %w", err) 82 | } else if mapType.ValueType, err = ReadDataType(source, version); err != nil { 83 | return nil, fmt.Errorf("cannot read map value type: %w", err) 84 | } 85 | return mapType, nil 86 | } 87 | -------------------------------------------------------------------------------- /datatype/primitive.go: -------------------------------------------------------------------------------- 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 datatype 16 | 17 | import ( 18 | "github.com/datastax/go-cassandra-native-protocol/primitive" 19 | ) 20 | 21 | var ( 22 | Ascii = &PrimitiveType{code: primitive.DataTypeCodeAscii} 23 | Bigint = &PrimitiveType{code: primitive.DataTypeCodeBigint} 24 | Blob = &PrimitiveType{code: primitive.DataTypeCodeBlob} 25 | Boolean = &PrimitiveType{code: primitive.DataTypeCodeBoolean} 26 | Counter = &PrimitiveType{code: primitive.DataTypeCodeCounter} 27 | Date = &PrimitiveType{code: primitive.DataTypeCodeDate} 28 | Decimal = &PrimitiveType{code: primitive.DataTypeCodeDecimal} 29 | Double = &PrimitiveType{code: primitive.DataTypeCodeDouble} 30 | Duration = &PrimitiveType{code: primitive.DataTypeCodeDuration} 31 | Float = &PrimitiveType{code: primitive.DataTypeCodeFloat} 32 | Inet = &PrimitiveType{code: primitive.DataTypeCodeInet} 33 | Int = &PrimitiveType{code: primitive.DataTypeCodeInt} 34 | Smallint = &PrimitiveType{code: primitive.DataTypeCodeSmallint} 35 | Time = &PrimitiveType{code: primitive.DataTypeCodeTime} 36 | Timestamp = &PrimitiveType{code: primitive.DataTypeCodeTimestamp} 37 | Timeuuid = &PrimitiveType{code: primitive.DataTypeCodeTimeuuid} 38 | Tinyint = &PrimitiveType{code: primitive.DataTypeCodeTinyint} 39 | Uuid = &PrimitiveType{code: primitive.DataTypeCodeUuid} 40 | Varchar = &PrimitiveType{code: primitive.DataTypeCodeVarchar} 41 | Varint = &PrimitiveType{code: primitive.DataTypeCodeVarint} 42 | ) 43 | 44 | // PrimitiveType is a datatype that is represented by a CQL scalar type. 45 | // +k8s:deepcopy-gen=true 46 | // +k8s:deepcopy-gen:interfaces=github.com/datastax/go-cassandra-native-protocol/datatype.DataType 47 | type PrimitiveType struct { 48 | code primitive.DataTypeCode 49 | } 50 | 51 | func (t *PrimitiveType) Code() primitive.DataTypeCode { 52 | return t.code 53 | } 54 | 55 | func (t *PrimitiveType) String() string { 56 | return t.AsCql() 57 | } 58 | 59 | func (t *PrimitiveType) AsCql() string { 60 | switch t.Code() { 61 | case primitive.DataTypeCodeAscii: 62 | return "ascii" 63 | case primitive.DataTypeCodeBigint: 64 | return "bigint" 65 | case primitive.DataTypeCodeBlob: 66 | return "blob" 67 | case primitive.DataTypeCodeBoolean: 68 | return "boolean" 69 | case primitive.DataTypeCodeCounter: 70 | return "counter" 71 | case primitive.DataTypeCodeDecimal: 72 | return "decimal" 73 | case primitive.DataTypeCodeDouble: 74 | return "double" 75 | case primitive.DataTypeCodeFloat: 76 | return "float" 77 | case primitive.DataTypeCodeInt: 78 | return "int" 79 | case primitive.DataTypeCodeTimestamp: 80 | return "timestamp" 81 | case primitive.DataTypeCodeUuid: 82 | return "uuid" 83 | case primitive.DataTypeCodeVarchar: 84 | return "varchar" 85 | case primitive.DataTypeCodeVarint: 86 | return "varint" 87 | case primitive.DataTypeCodeTimeuuid: 88 | return "timeuuid" 89 | case primitive.DataTypeCodeInet: 90 | return "inet" 91 | case primitive.DataTypeCodeDate: 92 | return "date" 93 | case primitive.DataTypeCodeTime: 94 | return "time" 95 | case primitive.DataTypeCodeSmallint: 96 | return "smallint" 97 | case primitive.DataTypeCodeTinyint: 98 | return "tinyint" 99 | case primitive.DataTypeCodeDuration: 100 | return "duration" 101 | } 102 | return "?" 103 | } 104 | -------------------------------------------------------------------------------- /datatype/primitive_test.go: -------------------------------------------------------------------------------- 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 datatype 16 | 17 | import ( 18 | "testing" 19 | 20 | "github.com/stretchr/testify/assert" 21 | 22 | "github.com/datastax/go-cassandra-native-protocol/primitive" 23 | ) 24 | 25 | func TestPrimitiveType(t *testing.T) { 26 | tests := []struct { 27 | name string 28 | input *PrimitiveType 29 | expected primitive.DataTypeCode 30 | }{ 31 | {"Ascii", Ascii, primitive.DataTypeCodeAscii}, 32 | {"Bigint", Bigint, primitive.DataTypeCodeBigint}, 33 | {"Blob", Blob, primitive.DataTypeCodeBlob}, 34 | {"Boolean", Boolean, primitive.DataTypeCodeBoolean}, 35 | {"Counter", Counter, primitive.DataTypeCodeCounter}, 36 | {"Decimal", Decimal, primitive.DataTypeCodeDecimal}, 37 | {"Double", Double, primitive.DataTypeCodeDouble}, 38 | {"Float", Float, primitive.DataTypeCodeFloat}, 39 | {"Int", Int, primitive.DataTypeCodeInt}, 40 | {"Timestamp", Timestamp, primitive.DataTypeCodeTimestamp}, 41 | {"Uuid", Uuid, primitive.DataTypeCodeUuid}, 42 | {"Varchar", Varchar, primitive.DataTypeCodeVarchar}, 43 | {"Varint", Varint, primitive.DataTypeCodeVarint}, 44 | {"Timeuuid", Timeuuid, primitive.DataTypeCodeTimeuuid}, 45 | {"Inet", Inet, primitive.DataTypeCodeInet}, 46 | {"Date", Date, primitive.DataTypeCodeDate}, 47 | {"Time", Time, primitive.DataTypeCodeTime}, 48 | {"Smallint", Smallint, primitive.DataTypeCodeSmallint}, 49 | {"Tinyint", Tinyint, primitive.DataTypeCodeTinyint}, 50 | {"Duration", Duration, primitive.DataTypeCodeDuration}, 51 | } 52 | for _, test := range tests { 53 | t.Run(test.name, func(t *testing.T) { 54 | actual := test.input.Code() 55 | assert.Equal(t, test.expected, actual) 56 | }) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /datatype/set.go: -------------------------------------------------------------------------------- 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 datatype 16 | 17 | import ( 18 | "fmt" 19 | "io" 20 | 21 | "github.com/datastax/go-cassandra-native-protocol/primitive" 22 | ) 23 | 24 | // Set is a data type that represents a CQL set type. 25 | // +k8s:deepcopy-gen=true 26 | // +k8s:deepcopy-gen:interfaces=github.com/datastax/go-cassandra-native-protocol/datatype.DataType 27 | type Set struct { 28 | ElementType DataType 29 | } 30 | 31 | func (t *Set) Code() primitive.DataTypeCode { 32 | return primitive.DataTypeCodeSet 33 | } 34 | 35 | func (t *Set) String() string { 36 | return t.AsCql() 37 | } 38 | 39 | func (t *Set) AsCql() string { 40 | return fmt.Sprintf("set<%v>", t.ElementType.AsCql()) 41 | } 42 | 43 | func NewSet(elementType DataType) *Set { 44 | return &Set{ElementType: elementType} 45 | } 46 | 47 | func writeSetType(t DataType, dest io.Writer, version primitive.ProtocolVersion) (err error) { 48 | if setType, ok := t.(*Set); !ok { 49 | return fmt.Errorf("expected *Set, got %T", t) 50 | } else if err = WriteDataType(setType.ElementType, dest, version); err != nil { 51 | return fmt.Errorf("cannot write set element type: %w", err) 52 | } 53 | return nil 54 | } 55 | 56 | func lengthOfSetType(t DataType, version primitive.ProtocolVersion) (length int, err error) { 57 | if setType, ok := t.(*Set); !ok { 58 | return -1, fmt.Errorf("expected *Set, got %T", t) 59 | } else if elementLength, err := LengthOfDataType(setType.ElementType, version); err != nil { 60 | return -1, fmt.Errorf("cannot compute length of set element type: %w", err) 61 | } else { 62 | length += elementLength 63 | } 64 | return length, nil 65 | } 66 | 67 | func readSetType(source io.Reader, version primitive.ProtocolVersion) (decoded DataType, err error) { 68 | setType := &Set{} 69 | if setType.ElementType, err = ReadDataType(source, version); err != nil { 70 | return nil, fmt.Errorf("cannot read set element type: %w", err) 71 | } 72 | return setType, nil 73 | } 74 | -------------------------------------------------------------------------------- /datatype/tuple.go: -------------------------------------------------------------------------------- 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 datatype 16 | 17 | import ( 18 | "bytes" 19 | "fmt" 20 | "io" 21 | 22 | "github.com/datastax/go-cassandra-native-protocol/primitive" 23 | ) 24 | 25 | // Tuple is a data type that represents a CQL tuple type. 26 | // +k8s:deepcopy-gen=true 27 | // +k8s:deepcopy-gen:interfaces=github.com/datastax/go-cassandra-native-protocol/datatype.DataType 28 | type Tuple struct { 29 | FieldTypes []DataType 30 | } 31 | 32 | func NewTuple(fieldTypes ...DataType) *Tuple { 33 | return &Tuple{FieldTypes: fieldTypes} 34 | } 35 | 36 | func (t *Tuple) Code() primitive.DataTypeCode { 37 | return primitive.DataTypeCodeTuple 38 | } 39 | 40 | func (t *Tuple) String() string { 41 | return t.AsCql() 42 | } 43 | 44 | func (t *Tuple) AsCql() string { 45 | buf := &bytes.Buffer{} 46 | buf.WriteString("tuple<") 47 | for i, elementType := range t.FieldTypes { 48 | if i > 0 { 49 | buf.WriteString(",") 50 | } 51 | buf.WriteString(elementType.AsCql()) 52 | } 53 | buf.WriteString(">") 54 | return buf.String() 55 | } 56 | 57 | func writeTupleType(t DataType, dest io.Writer, version primitive.ProtocolVersion) (err error) { 58 | tupleType, ok := t.(*Tuple) 59 | if !ok { 60 | return fmt.Errorf("expected *Tuple, got %T", t) 61 | } else if err = primitive.WriteShort(uint16(len(tupleType.FieldTypes)), dest); err != nil { 62 | return fmt.Errorf("cannot write tuple type field count: %w", err) 63 | } 64 | for i, fieldType := range tupleType.FieldTypes { 65 | if err = WriteDataType(fieldType, dest, version); err != nil { 66 | return fmt.Errorf("cannot write tuple field %d: %w", i, err) 67 | } 68 | } 69 | return nil 70 | } 71 | 72 | func lengthOfTupleType(t DataType, version primitive.ProtocolVersion) (int, error) { 73 | if tupleType, ok := t.(*Tuple); !ok { 74 | return -1, fmt.Errorf("expected *Tuple, got %T", t) 75 | } else { 76 | length := primitive.LengthOfShort // field count 77 | for i, fieldType := range tupleType.FieldTypes { 78 | if fieldLength, err := LengthOfDataType(fieldType, version); err != nil { 79 | return -1, fmt.Errorf("cannot compute length of tuple field %d: %w", i, err) 80 | } else { 81 | length += fieldLength 82 | } 83 | } 84 | return length, nil 85 | } 86 | } 87 | 88 | func readTupleType(source io.Reader, version primitive.ProtocolVersion) (DataType, error) { 89 | if fieldCount, err := primitive.ReadShort(source); err != nil { 90 | return nil, fmt.Errorf("cannot read tuple field count: %w", err) 91 | } else { 92 | tupleType := &Tuple{} 93 | tupleType.FieldTypes = make([]DataType, fieldCount) 94 | for i := 0; i < int(fieldCount); i++ { 95 | if tupleType.FieldTypes[i], err = ReadDataType(source, version); err != nil { 96 | return nil, fmt.Errorf("cannot read tuple field %d: %w", i, err) 97 | } 98 | } 99 | return tupleType, nil 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /deepcopy-header.txt: -------------------------------------------------------------------------------- 1 | // Copyright YEAR 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 | -------------------------------------------------------------------------------- /frame/compressor.go: -------------------------------------------------------------------------------- 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 | "io" 19 | ) 20 | 21 | type BodyCompressor interface { 22 | 23 | // CompressWithLength compresses the source, reading it fully, and writes the compressed length and the compressed 24 | // result to dest. This is Cassandra's expected format of compressed frame bodies. 25 | CompressWithLength(source io.Reader, dest io.Writer) error 26 | 27 | // DecompressWithLength reads the compressed length then decompresses the source, reading it fully, and writes the 28 | // decompressed result to dest. This is Cassandra's expected format of compressed frame bodies. 29 | DecompressWithLength(source io.Reader, dest io.Writer) error 30 | } 31 | -------------------------------------------------------------------------------- /frame/convert.go: -------------------------------------------------------------------------------- 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 | "fmt" 20 | ) 21 | 22 | func (c *codec) ConvertToRawFrame(frame *Frame) (*RawFrame, error) { 23 | var body bytes.Buffer 24 | if err := c.EncodeBody(frame.Header, frame.Body, &body); err != nil { 25 | return nil, fmt.Errorf("cannot encode body: %w", err) 26 | } 27 | frame.Header.BodyLength = int32(body.Len()) 28 | return &RawFrame{ 29 | Header: frame.Header, 30 | Body: body.Bytes(), 31 | }, nil 32 | } 33 | 34 | func (c *codec) ConvertFromRawFrame(frame *RawFrame) (*Frame, error) { 35 | if body, err := c.DecodeBody(frame.Header, bytes.NewBuffer(frame.Body)); err != nil { 36 | return nil, fmt.Errorf("cannot decode body: %w", err) 37 | } else { 38 | return &Frame{ 39 | Header: frame.Header, 40 | Body: body, 41 | }, nil 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /frame/deepcopy_generated.go: -------------------------------------------------------------------------------- 1 | //go:build !ignore_autogenerated 2 | // +build !ignore_autogenerated 3 | 4 | // Copyright 2022 DataStax 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | 18 | // Code generated by deepcopy-gen. DO NOT EDIT. 19 | 20 | package frame 21 | 22 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 23 | func (in *Body) DeepCopyInto(out *Body) { 24 | *out = *in 25 | if in.TracingId != nil { 26 | in, out := &in.TracingId, &out.TracingId 27 | *out = (*in).DeepCopy() 28 | } 29 | if in.CustomPayload != nil { 30 | in, out := &in.CustomPayload, &out.CustomPayload 31 | *out = make(map[string][]byte, len(*in)) 32 | for key, val := range *in { 33 | var outVal []byte 34 | if val == nil { 35 | (*out)[key] = nil 36 | } else { 37 | in, out := &val, &outVal 38 | *out = make([]byte, len(*in)) 39 | copy(*out, *in) 40 | } 41 | (*out)[key] = outVal 42 | } 43 | } 44 | if in.Warnings != nil { 45 | in, out := &in.Warnings, &out.Warnings 46 | *out = make([]string, len(*in)) 47 | copy(*out, *in) 48 | } 49 | if in.Message != nil { 50 | out.Message = in.Message.DeepCopyMessage() 51 | } 52 | return 53 | } 54 | 55 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Body. 56 | func (in *Body) DeepCopy() *Body { 57 | if in == nil { 58 | return nil 59 | } 60 | out := new(Body) 61 | in.DeepCopyInto(out) 62 | return out 63 | } 64 | 65 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 66 | func (in *Frame) DeepCopyInto(out *Frame) { 67 | *out = *in 68 | if in.Header != nil { 69 | in, out := &in.Header, &out.Header 70 | *out = new(Header) 71 | **out = **in 72 | } 73 | if in.Body != nil { 74 | in, out := &in.Body, &out.Body 75 | *out = new(Body) 76 | (*in).DeepCopyInto(*out) 77 | } 78 | return 79 | } 80 | 81 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Frame. 82 | func (in *Frame) DeepCopy() *Frame { 83 | if in == nil { 84 | return nil 85 | } 86 | out := new(Frame) 87 | in.DeepCopyInto(out) 88 | return out 89 | } 90 | 91 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 92 | func (in *Header) DeepCopyInto(out *Header) { 93 | *out = *in 94 | return 95 | } 96 | 97 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Header. 98 | func (in *Header) DeepCopy() *Header { 99 | if in == nil { 100 | return nil 101 | } 102 | out := new(Header) 103 | in.DeepCopyInto(out) 104 | return out 105 | } 106 | 107 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 108 | func (in *RawFrame) DeepCopyInto(out *RawFrame) { 109 | *out = *in 110 | if in.Header != nil { 111 | in, out := &in.Header, &out.Header 112 | *out = new(Header) 113 | **out = **in 114 | } 115 | if in.Body != nil { 116 | in, out := &in.Body, &out.Body 117 | *out = make([]byte, len(*in)) 118 | copy(*out, *in) 119 | } 120 | return 121 | } 122 | 123 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RawFrame. 124 | func (in *RawFrame) DeepCopy() *RawFrame { 125 | if in == nil { 126 | return nil 127 | } 128 | out := new(RawFrame) 129 | in.DeepCopyInto(out) 130 | return out 131 | } 132 | -------------------------------------------------------------------------------- /frame/doc.go: -------------------------------------------------------------------------------- 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 | /* 16 | 17 | Package frame contains interfaces, types and functions to read and write CQL protocol frames, 18 | as defined in sections 1 and 2 of the CQL protocol specifications. 19 | 20 | */ 21 | package frame 22 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/datastax/go-cassandra-native-protocol 2 | 3 | go 1.17 4 | 5 | require ( 6 | github.com/golang/snappy v0.0.3 7 | github.com/pierrec/lz4/v4 v4.0.3 8 | github.com/rs/zerolog v1.20.0 9 | github.com/stretchr/testify v1.7.0 10 | ) 11 | 12 | require ( 13 | github.com/davecgh/go-spew v1.1.1 // indirect 14 | github.com/kr/pretty v0.2.0 // indirect 15 | github.com/pmezard/go-difflib v1.0.0 // indirect 16 | github.com/stretchr/objx v0.1.0 // indirect 17 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect 18 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect 19 | ) 20 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= 2 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 4 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 5 | github.com/golang/snappy v0.0.3 h1:fHPg5GQYlCeLIPB9BZqMVR5nR9A+IM5zcgeTdjMYmLA= 6 | github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= 7 | github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs= 8 | github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 9 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 10 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= 11 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 12 | github.com/pierrec/lz4/v4 v4.0.3 h1:vNQKSVZNYUEAvRY9FaUXAF1XPbSOHJtDTiP41kzDz2E= 13 | github.com/pierrec/lz4/v4 v4.0.3/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= 14 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 15 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 16 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 17 | github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= 18 | github.com/rs/zerolog v1.20.0 h1:38k9hgtUBdxFwE34yS8rTHmHBa4eN16E4DJlv177LNs= 19 | github.com/rs/zerolog v1.20.0/go.mod h1:IzD0RJ65iWH0w97OQQebJEvTZYvsCUm9WVLWBQrJRjo= 20 | github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4= 21 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 22 | github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= 23 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 24 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 25 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 26 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 27 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 28 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 29 | golang.org/x/tools v0.0.0-20190828213141-aed303cbaa74/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 30 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 31 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 32 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= 33 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 34 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= 35 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 36 | -------------------------------------------------------------------------------- /message/auth_challenge.go: -------------------------------------------------------------------------------- 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 message 16 | 17 | import ( 18 | "errors" 19 | "fmt" 20 | "io" 21 | 22 | "github.com/datastax/go-cassandra-native-protocol/primitive" 23 | ) 24 | 25 | // AuthChallenge is a response sent in reply to an AuthResponse request, when the server requires additional 26 | // authentication data. It must be followed by an AuthResponse request message. 27 | // +k8s:deepcopy-gen=true 28 | // +k8s:deepcopy-gen:interfaces=github.com/datastax/go-cassandra-native-protocol/message.Message 29 | type AuthChallenge struct { 30 | Token []byte 31 | } 32 | 33 | func (m *AuthChallenge) IsResponse() bool { 34 | return true 35 | } 36 | 37 | func (m *AuthChallenge) GetOpCode() primitive.OpCode { 38 | return primitive.OpCodeAuthChallenge 39 | } 40 | 41 | func (m *AuthChallenge) String() string { 42 | return "AUTH_CHALLENGE" 43 | } 44 | 45 | type authChallengeCodec struct{} 46 | 47 | func (c *authChallengeCodec) Encode(msg Message, dest io.Writer, _ primitive.ProtocolVersion) error { 48 | authChallenge, ok := msg.(*AuthChallenge) 49 | if !ok { 50 | return errors.New(fmt.Sprintf("expected *message.AuthChallenge, got %T", msg)) 51 | } 52 | return primitive.WriteBytes(authChallenge.Token, dest) 53 | } 54 | 55 | func (c *authChallengeCodec) EncodedLength(msg Message, _ primitive.ProtocolVersion) (int, error) { 56 | authChallenge, ok := msg.(*AuthChallenge) 57 | if !ok { 58 | return -1, errors.New(fmt.Sprintf("expected *message.AuthChallenge, got %T", msg)) 59 | } 60 | return primitive.LengthOfBytes(authChallenge.Token), nil 61 | } 62 | 63 | func (c *authChallengeCodec) Decode(source io.Reader, _ primitive.ProtocolVersion) (Message, error) { 64 | if token, err := primitive.ReadBytes(source); err != nil { 65 | return nil, err 66 | } else { 67 | return &AuthChallenge{Token: token}, nil 68 | } 69 | } 70 | 71 | func (c *authChallengeCodec) GetOpCode() primitive.OpCode { 72 | return primitive.OpCodeAuthChallenge 73 | } 74 | -------------------------------------------------------------------------------- /message/auth_response.go: -------------------------------------------------------------------------------- 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 message 16 | 17 | import ( 18 | "errors" 19 | "fmt" 20 | "io" 21 | 22 | "github.com/datastax/go-cassandra-native-protocol/primitive" 23 | ) 24 | 25 | // AuthResponse is a request message sent in reply to an Authenticate or AuthChallenge response, and contains the 26 | // authentication data requested by the server. The server will then reply with either an AuthSuccess response message, 27 | // or with an AuthChallenge response message, if it requires additional authentication data. 28 | // +k8s:deepcopy-gen=true 29 | // +k8s:deepcopy-gen:interfaces=github.com/datastax/go-cassandra-native-protocol/message.Message 30 | type AuthResponse struct { 31 | 32 | // Token is a protocol [bytes]; the details of what this token contains (and when it can be null/empty, if ever) 33 | // depends on the actual authenticator used. 34 | Token []byte 35 | } 36 | 37 | func (m *AuthResponse) IsResponse() bool { 38 | return false 39 | } 40 | 41 | func (m *AuthResponse) GetOpCode() primitive.OpCode { 42 | return primitive.OpCodeAuthResponse 43 | } 44 | 45 | func (m *AuthResponse) String() string { 46 | return "AUTH_RESPONSE" 47 | } 48 | 49 | type authResponseCodec struct{} 50 | 51 | func (c *authResponseCodec) Encode(msg Message, dest io.Writer, _ primitive.ProtocolVersion) error { 52 | authResponse, ok := msg.(*AuthResponse) 53 | if !ok { 54 | return errors.New(fmt.Sprintf("expected *message.AuthResponse, got %T", msg)) 55 | } 56 | return primitive.WriteBytes(authResponse.Token, dest) 57 | } 58 | 59 | func (c *authResponseCodec) EncodedLength(msg Message, _ primitive.ProtocolVersion) (int, error) { 60 | authResponse, ok := msg.(*AuthResponse) 61 | if !ok { 62 | return -1, errors.New(fmt.Sprintf("expected *message.AuthResponse, got %T", msg)) 63 | } 64 | return primitive.LengthOfBytes(authResponse.Token), nil 65 | } 66 | 67 | func (c *authResponseCodec) Decode(source io.Reader, _ primitive.ProtocolVersion) (Message, error) { 68 | if token, err := primitive.ReadBytes(source); err != nil { 69 | return nil, err 70 | } else { 71 | return &AuthResponse{Token: token}, nil 72 | } 73 | } 74 | 75 | func (c *authResponseCodec) GetOpCode() primitive.OpCode { 76 | return primitive.OpCodeAuthResponse 77 | } 78 | -------------------------------------------------------------------------------- /message/auth_response_test.go: -------------------------------------------------------------------------------- 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 message 16 | 17 | import ( 18 | "bytes" 19 | "errors" 20 | "testing" 21 | 22 | "github.com/stretchr/testify/assert" 23 | 24 | "github.com/datastax/go-cassandra-native-protocol/primitive" 25 | ) 26 | 27 | func TestAuthResponse_DeepCopy(t *testing.T) { 28 | token := []byte{0xca, 0xfe, 0xba, 0xbe} 29 | msg := &AuthResponse{ 30 | Token: token, 31 | } 32 | cloned := msg.DeepCopy() 33 | assert.Equal(t, msg, cloned) 34 | cloned.Token = []byte{0xcb, 0xfd, 0xbc, 0xba} 35 | assert.Equal(t, []byte{0xca, 0xfe, 0xba, 0xbe}, token) 36 | assert.Equal(t, []byte{0xcb, 0xfd, 0xbc, 0xba}, cloned.Token) 37 | assert.NotEqual(t, msg, cloned) 38 | } 39 | 40 | func TestAuthResponseCodec_Encode(t *testing.T) { 41 | token := []byte{0xca, 0xfe, 0xba, 0xbe} 42 | codec := &authResponseCodec{} 43 | for _, version := range primitive.SupportedProtocolVersions() { 44 | t.Run(version.String(), func(t *testing.T) { 45 | tests := []encodeTestCase{ 46 | { 47 | "simple auth response", 48 | &AuthResponse{token}, 49 | []byte{0, 0, 0, 4, 0xca, 0xfe, 0xba, 0xbe}, 50 | nil, 51 | }, 52 | { 53 | "not an auth response", 54 | &AuthChallenge{token}, 55 | nil, 56 | errors.New("expected *message.AuthResponse, got *message.AuthChallenge"), 57 | }, 58 | { 59 | "auth response empty token", 60 | &AuthResponse{[]byte{}}, 61 | []byte{0, 0, 0, 0}, 62 | nil, 63 | }, 64 | { 65 | "auth response nil token", 66 | &AuthResponse{nil}, 67 | []byte{0xff, 0xff, 0xff, 0xff}, 68 | nil, 69 | }, 70 | } 71 | for _, tt := range tests { 72 | t.Run(tt.name, func(t *testing.T) { 73 | dest := &bytes.Buffer{} 74 | err := codec.Encode(tt.input, dest, version) 75 | assert.Equal(t, tt.expected, dest.Bytes()) 76 | assert.Equal(t, tt.err, err) 77 | }) 78 | } 79 | }) 80 | } 81 | } 82 | 83 | func TestAuthResponseCodec_EncodedLength(t *testing.T) { 84 | token := []byte{0xca, 0xfe, 0xba, 0xbe} 85 | codec := &authResponseCodec{} 86 | for _, version := range primitive.SupportedProtocolVersions() { 87 | t.Run(version.String(), func(t *testing.T) { 88 | tests := []encodedLengthTestCase{ 89 | { 90 | "simple auth response", 91 | &AuthResponse{token}, 92 | primitive.LengthOfBytes(token), 93 | nil, 94 | }, 95 | { 96 | "not an auth response", 97 | &AuthChallenge{token}, 98 | -1, 99 | errors.New("expected *message.AuthResponse, got *message.AuthChallenge"), 100 | }, 101 | { 102 | "auth response nil token", 103 | &AuthResponse{nil}, 104 | primitive.LengthOfBytes(nil), 105 | nil, 106 | }, 107 | } 108 | for _, tt := range tests { 109 | t.Run(tt.name, func(t *testing.T) { 110 | actual, err := codec.EncodedLength(tt.input, version) 111 | assert.Equal(t, tt.expected, actual) 112 | assert.Equal(t, tt.err, err) 113 | }) 114 | } 115 | }) 116 | } 117 | } 118 | 119 | func TestAuthResponseCodec_Decode(t *testing.T) { 120 | token := []byte{0xca, 0xfe, 0xba, 0xbe} 121 | codec := &authResponseCodec{} 122 | for _, version := range primitive.SupportedProtocolVersions() { 123 | t.Run(version.String(), func(t *testing.T) { 124 | tests := []decodeTestCase{ 125 | { 126 | "simple auth response", 127 | []byte{0, 0, 0, 4, 0xca, 0xfe, 0xba, 0xbe}, 128 | &AuthResponse{token}, 129 | nil, 130 | }, 131 | } 132 | for _, tt := range tests { 133 | t.Run(tt.name, func(t *testing.T) { 134 | source := bytes.NewBuffer(tt.input) 135 | actual, err := codec.Decode(source, version) 136 | assert.Equal(t, tt.expected, actual) 137 | assert.Equal(t, tt.err, err) 138 | }) 139 | } 140 | }) 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /message/auth_success.go: -------------------------------------------------------------------------------- 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 message 16 | 17 | import ( 18 | "errors" 19 | "fmt" 20 | "io" 21 | 22 | "github.com/datastax/go-cassandra-native-protocol/primitive" 23 | ) 24 | 25 | // AuthSuccess is a response message sent in reply to an AuthResponse request, to indicate that the authentication was 26 | // successful. 27 | // +k8s:deepcopy-gen=true 28 | // +k8s:deepcopy-gen:interfaces=github.com/datastax/go-cassandra-native-protocol/message.Message 29 | type AuthSuccess struct { 30 | Token []byte 31 | } 32 | 33 | func (m *AuthSuccess) IsResponse() bool { 34 | return true 35 | } 36 | 37 | func (m *AuthSuccess) GetOpCode() primitive.OpCode { 38 | return primitive.OpCodeAuthSuccess 39 | } 40 | 41 | func (m *AuthSuccess) String() string { 42 | return "AUTH_SUCCESS" 43 | } 44 | 45 | type authSuccessCodec struct{} 46 | 47 | func (c *authSuccessCodec) Encode(msg Message, dest io.Writer, _ primitive.ProtocolVersion) error { 48 | authSuccess, ok := msg.(*AuthSuccess) 49 | if !ok { 50 | return errors.New(fmt.Sprintf("expected *message.AuthSuccess, got %T", msg)) 51 | } 52 | // protocol specs allow the token to be null on AUTH SUCCESS 53 | return primitive.WriteBytes(authSuccess.Token, dest) 54 | } 55 | 56 | func (c *authSuccessCodec) EncodedLength(msg Message, _ primitive.ProtocolVersion) (int, error) { 57 | authSuccess, ok := msg.(*AuthSuccess) 58 | if !ok { 59 | return -1, errors.New(fmt.Sprintf("expected *message.AuthSuccess, got %T", msg)) 60 | } 61 | return primitive.LengthOfBytes(authSuccess.Token), nil 62 | } 63 | 64 | func (c *authSuccessCodec) Decode(source io.Reader, _ primitive.ProtocolVersion) (Message, error) { 65 | if token, err := primitive.ReadBytes(source); err != nil { 66 | return nil, err 67 | } else { 68 | return &AuthSuccess{Token: token}, nil 69 | } 70 | } 71 | 72 | func (c *authSuccessCodec) GetOpCode() primitive.OpCode { 73 | return primitive.OpCodeAuthSuccess 74 | } 75 | -------------------------------------------------------------------------------- /message/auth_success_test.go: -------------------------------------------------------------------------------- 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 message 16 | 17 | import ( 18 | "bytes" 19 | "errors" 20 | "testing" 21 | 22 | "github.com/stretchr/testify/assert" 23 | 24 | "github.com/datastax/go-cassandra-native-protocol/primitive" 25 | ) 26 | 27 | func TestAuthSuccess_DeepCopy(t *testing.T) { 28 | token := []byte{0xca, 0xfe, 0xba, 0xbe} 29 | msg := &AuthSuccess{ 30 | Token: token, 31 | } 32 | cloned := msg.DeepCopy() 33 | assert.Equal(t, msg, cloned) 34 | cloned.Token = []byte{0xcb, 0xfd, 0xbc, 0xba} 35 | assert.Equal(t, []byte{0xca, 0xfe, 0xba, 0xbe}, token) 36 | assert.Equal(t, []byte{0xcb, 0xfd, 0xbc, 0xba}, cloned.Token) 37 | assert.NotEqual(t, msg, cloned) 38 | } 39 | 40 | func TestAuthSuccessCodec_Encode(t *testing.T) { 41 | token := []byte{0xca, 0xfe, 0xba, 0xbe} 42 | codec := &authSuccessCodec{} 43 | for _, version := range primitive.SupportedProtocolVersions() { 44 | t.Run(version.String(), func(t *testing.T) { 45 | tests := []encodeTestCase{ 46 | { 47 | "simple auth success", 48 | &AuthSuccess{token}, 49 | []byte{0, 0, 0, 4, 0xca, 0xfe, 0xba, 0xbe}, 50 | nil, 51 | }, 52 | { 53 | "not an auth success", 54 | &AuthChallenge{token}, 55 | nil, 56 | errors.New("expected *message.AuthSuccess, got *message.AuthChallenge"), 57 | }, 58 | { 59 | "auth success empty token", 60 | &AuthSuccess{[]byte{}}, 61 | []byte{0, 0, 0, 0}, 62 | nil, 63 | }, 64 | { 65 | "auth success nil token", 66 | &AuthSuccess{nil}, 67 | []byte{0xff, 0xff, 0xff, 0xff}, 68 | nil, 69 | }, 70 | } 71 | for _, tt := range tests { 72 | t.Run(tt.name, func(t *testing.T) { 73 | dest := &bytes.Buffer{} 74 | err := codec.Encode(tt.input, dest, version) 75 | assert.Equal(t, tt.expected, dest.Bytes()) 76 | assert.Equal(t, tt.err, err) 77 | }) 78 | } 79 | }) 80 | } 81 | } 82 | 83 | func TestAuthSuccessCodec_EncodedLength(t *testing.T) { 84 | token := []byte{0xca, 0xfe, 0xba, 0xbe} 85 | codec := &authSuccessCodec{} 86 | for _, version := range primitive.SupportedProtocolVersions() { 87 | t.Run(version.String(), func(t *testing.T) { 88 | tests := []encodedLengthTestCase{ 89 | { 90 | "simple auth success", 91 | &AuthSuccess{token}, 92 | primitive.LengthOfBytes(token), 93 | nil, 94 | }, 95 | { 96 | "not an auth success", 97 | &AuthResponse{token}, 98 | -1, 99 | errors.New("expected *message.AuthSuccess, got *message.AuthResponse"), 100 | }, 101 | { 102 | "auth success nil token", 103 | &AuthSuccess{nil}, 104 | primitive.LengthOfBytes(nil), 105 | nil, 106 | }, 107 | } 108 | for _, tt := range tests { 109 | t.Run(tt.name, func(t *testing.T) { 110 | actual, err := codec.EncodedLength(tt.input, version) 111 | assert.Equal(t, tt.expected, actual) 112 | assert.Equal(t, tt.err, err) 113 | }) 114 | } 115 | }) 116 | } 117 | } 118 | 119 | func TestAuthSuccessCodec_Decode(t *testing.T) { 120 | token := []byte{0xca, 0xfe, 0xba, 0xbe} 121 | codec := &authSuccessCodec{} 122 | for _, version := range primitive.SupportedProtocolVersions() { 123 | t.Run(version.String(), func(t *testing.T) { 124 | tests := []decodeTestCase{ 125 | { 126 | "simple auth success", 127 | []byte{0, 0, 0, 4, 0xca, 0xfe, 0xba, 0xbe}, 128 | &AuthSuccess{token}, 129 | nil, 130 | }, 131 | } 132 | for _, tt := range tests { 133 | t.Run(tt.name, func(t *testing.T) { 134 | source := bytes.NewBuffer(tt.input) 135 | actual, err := codec.Decode(source, version) 136 | assert.Equal(t, tt.expected, actual) 137 | assert.Equal(t, tt.err, err) 138 | }) 139 | } 140 | }) 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /message/authenticate.go: -------------------------------------------------------------------------------- 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 message 16 | 17 | import ( 18 | "errors" 19 | "fmt" 20 | "io" 21 | 22 | "github.com/datastax/go-cassandra-native-protocol/primitive" 23 | ) 24 | 25 | // Authenticate is a response sent in reply to a Startup request when the server requires authentication. It must be 26 | // followed by an AuthResponse request message. 27 | // +k8s:deepcopy-gen=true 28 | // +k8s:deepcopy-gen:interfaces=github.com/datastax/go-cassandra-native-protocol/message.Message 29 | type Authenticate struct { 30 | Authenticator string 31 | } 32 | 33 | func (m *Authenticate) IsResponse() bool { 34 | return true 35 | } 36 | 37 | func (m *Authenticate) GetOpCode() primitive.OpCode { 38 | return primitive.OpCodeAuthenticate 39 | } 40 | 41 | func (m *Authenticate) String() string { 42 | return "AUTHENTICATE " + m.Authenticator 43 | } 44 | 45 | type authenticateCodec struct{} 46 | 47 | func (c *authenticateCodec) Encode(msg Message, dest io.Writer, _ primitive.ProtocolVersion) error { 48 | authenticate, ok := msg.(*Authenticate) 49 | if !ok { 50 | return errors.New(fmt.Sprintf("expected *message.Authenticate, got %T", msg)) 51 | } 52 | if authenticate.Authenticator == "" { 53 | return errors.New("AUTHENTICATE authenticator cannot be empty") 54 | } 55 | return primitive.WriteString(authenticate.Authenticator, dest) 56 | } 57 | 58 | func (c *authenticateCodec) EncodedLength(msg Message, _ primitive.ProtocolVersion) (int, error) { 59 | authenticate, ok := msg.(*Authenticate) 60 | if !ok { 61 | return -1, errors.New(fmt.Sprintf("expected *message.Authenticate, got %T", msg)) 62 | } 63 | return primitive.LengthOfString(authenticate.Authenticator), nil 64 | } 65 | 66 | func (c *authenticateCodec) Decode(source io.Reader, _ primitive.ProtocolVersion) (Message, error) { 67 | if authenticator, err := primitive.ReadString(source); err != nil { 68 | return nil, err 69 | } else { 70 | return &Authenticate{Authenticator: authenticator}, nil 71 | } 72 | } 73 | 74 | func (c *authenticateCodec) GetOpCode() primitive.OpCode { 75 | return primitive.OpCodeAuthenticate 76 | } 77 | -------------------------------------------------------------------------------- /message/authenticate_test.go: -------------------------------------------------------------------------------- 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 message 16 | 17 | import ( 18 | "bytes" 19 | "errors" 20 | "testing" 21 | 22 | "github.com/stretchr/testify/assert" 23 | 24 | "github.com/datastax/go-cassandra-native-protocol/primitive" 25 | ) 26 | 27 | func TestAuthenticate_DeepCopy(t *testing.T) { 28 | msg := &Authenticate{ 29 | Authenticator: "auth", 30 | } 31 | cloned := msg.DeepCopy() 32 | assert.Equal(t, msg, cloned) 33 | cloned.Authenticator = "auth2" 34 | assert.Equal(t, "auth", msg.Authenticator) 35 | assert.Equal(t, "auth2", cloned.Authenticator) 36 | assert.NotEqual(t, msg, cloned) 37 | } 38 | 39 | func TestAuthenticateCodec_Encode(t *testing.T) { 40 | codec := &authenticateCodec{} 41 | for _, version := range primitive.SupportedProtocolVersions() { 42 | t.Run(version.String(), func(t *testing.T) { 43 | tests := []encodeTestCase{ 44 | { 45 | "simple authenticate", 46 | &Authenticate{"dummy"}, 47 | []byte{0, 5, d, u, m, m, y}, 48 | nil, 49 | }, 50 | { 51 | "not an authenticate", 52 | &AuthChallenge{[]byte{0xca, 0xfe, 0xba, 0xbe}}, 53 | nil, 54 | errors.New("expected *message.Authenticate, got *message.AuthChallenge"), 55 | }, 56 | { 57 | "authenticate nil authenticator", 58 | &Authenticate{""}, 59 | nil, 60 | errors.New("AUTHENTICATE authenticator cannot be empty"), 61 | }, 62 | } 63 | for _, tt := range tests { 64 | t.Run(tt.name, func(t *testing.T) { 65 | dest := &bytes.Buffer{} 66 | err := codec.Encode(tt.input, dest, version) 67 | assert.Equal(t, tt.expected, dest.Bytes()) 68 | assert.Equal(t, tt.err, err) 69 | }) 70 | } 71 | }) 72 | } 73 | } 74 | 75 | func TestAuthenticateCodec_EncodedLength(t *testing.T) { 76 | codec := &authenticateCodec{} 77 | for _, version := range primitive.SupportedProtocolVersions() { 78 | t.Run(version.String(), func(t *testing.T) { 79 | tests := []encodedLengthTestCase{ 80 | { 81 | "simple authenticate", 82 | &Authenticate{"dummy"}, 83 | primitive.LengthOfString("dummy"), 84 | nil, 85 | }, 86 | { 87 | "not an authenticate", 88 | &AuthChallenge{[]byte{0xca, 0xfe, 0xba, 0xbe}}, 89 | -1, 90 | errors.New("expected *message.Authenticate, got *message.AuthChallenge"), 91 | }, 92 | { 93 | "authenticate nil authenticator", 94 | &Authenticate{""}, 95 | primitive.LengthOfString(""), 96 | nil, 97 | }, 98 | } 99 | for _, tt := range tests { 100 | t.Run(tt.name, func(t *testing.T) { 101 | actual, err := codec.EncodedLength(tt.input, version) 102 | assert.Equal(t, tt.expected, actual) 103 | assert.Equal(t, tt.err, err) 104 | }) 105 | } 106 | }) 107 | } 108 | } 109 | 110 | func TestAuthenticateCodec_Decode(t *testing.T) { 111 | codec := &authenticateCodec{} 112 | for _, version := range primitive.SupportedProtocolVersions() { 113 | t.Run(version.String(), func(t *testing.T) { 114 | tests := []decodeTestCase{ 115 | { 116 | "simple authenticate", 117 | []byte{0, 5, d, u, m, m, y}, 118 | &Authenticate{"dummy"}, 119 | nil, 120 | }, 121 | } 122 | for _, tt := range tests { 123 | t.Run(tt.name, func(t *testing.T) { 124 | source := bytes.NewBuffer(tt.input) 125 | actual, err := codec.Decode(source, version) 126 | assert.Equal(t, tt.expected, actual) 127 | assert.Equal(t, tt.err, err) 128 | }) 129 | } 130 | }) 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /message/doc.go: -------------------------------------------------------------------------------- 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 | /* 16 | 17 | Package message contains interfaces, types and functions to read and write CQL protocol messages, 18 | as defined in section 4 of the CQL protocol specifications. 19 | 20 | */ 21 | package message 22 | -------------------------------------------------------------------------------- /message/dse_continuous_paging_options.go: -------------------------------------------------------------------------------- 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 message 16 | 17 | import ( 18 | "fmt" 19 | "io" 20 | 21 | "github.com/datastax/go-cassandra-native-protocol/primitive" 22 | ) 23 | 24 | // ContinuousPagingOptions holds options for DSE continuous paging feature. Valid for QUERY and EXECUTE messages. 25 | // See QueryOptions. 26 | // +k8s:deepcopy-gen=true 27 | type ContinuousPagingOptions struct { 28 | // The maximum number of pages that the server will send to the client in total, or zero to indicate no limit. 29 | // Valid for both DSE v1 and v2. 30 | MaxPages int32 31 | // The maximum number of pages per second, or zero to indicate no limit. 32 | // Valid for both DSE v1 and v2. 33 | PagesPerSecond int32 34 | // The number of pages that the client is ready to receive, or zero to indicate no limit. 35 | // Valid for DSE v2 only. 36 | // See Revise. 37 | NextPages int32 38 | } 39 | 40 | func EncodeContinuousPagingOptions(options *ContinuousPagingOptions, dest io.Writer, version primitive.ProtocolVersion) (err error) { 41 | if err = primitive.CheckDseProtocolVersion(version); err != nil { 42 | return err 43 | } else if err = primitive.WriteInt(options.MaxPages, dest); err != nil { 44 | return fmt.Errorf("cannot write max num pages: %w", err) 45 | } else if err = primitive.WriteInt(options.PagesPerSecond, dest); err != nil { 46 | return fmt.Errorf("cannot write pages per second: %w", err) 47 | } else if version >= primitive.ProtocolVersionDse2 { 48 | if err = primitive.WriteInt(options.NextPages, dest); err != nil { 49 | return fmt.Errorf("cannot write next pages: %w", err) 50 | } 51 | } 52 | return nil 53 | } 54 | 55 | func LengthOfContinuousPagingOptions(_ *ContinuousPagingOptions, version primitive.ProtocolVersion) (length int, err error) { 56 | if err = primitive.CheckDseProtocolVersion(version); err != nil { 57 | return -1, err 58 | } 59 | length += primitive.LengthOfInt // max num pages 60 | length += primitive.LengthOfInt // pages per second 61 | if version >= primitive.ProtocolVersionDse2 { 62 | length += primitive.LengthOfInt // next pages 63 | } 64 | return length, nil 65 | } 66 | 67 | func DecodeContinuousPagingOptions(source io.Reader, version primitive.ProtocolVersion) (options *ContinuousPagingOptions, err error) { 68 | if err = primitive.CheckDseProtocolVersion(version); err != nil { 69 | return nil, err 70 | } 71 | options = &ContinuousPagingOptions{} 72 | if options.MaxPages, err = primitive.ReadInt(source); err != nil { 73 | return nil, fmt.Errorf("cannot read max num pages: %w", err) 74 | } else if options.PagesPerSecond, err = primitive.ReadInt(source); err != nil { 75 | return nil, fmt.Errorf("cannot read pages per second: %w", err) 76 | } else if version >= primitive.ProtocolVersionDse2 { 77 | if options.NextPages, err = primitive.ReadInt(source); err != nil { 78 | return nil, fmt.Errorf("cannot read next pages: %w", err) 79 | } 80 | } 81 | return options, nil 82 | } 83 | -------------------------------------------------------------------------------- /message/main.go: -------------------------------------------------------------------------------- 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 message 16 | 17 | var DefaultMessageCodecs = []Codec{ 18 | &startupCodec{}, 19 | &optionsCodec{}, 20 | &queryCodec{}, 21 | &prepareCodec{}, 22 | &executeCodec{}, 23 | ®isterCodec{}, 24 | &batchCodec{}, 25 | &authResponseCodec{}, 26 | &errorCodec{}, 27 | &readyCodec{}, 28 | &authenticateCodec{}, 29 | &supportedCodec{}, 30 | &resultCodec{}, 31 | &eventCodec{}, 32 | &authChallengeCodec{}, 33 | &authSuccessCodec{}, 34 | // DSE-specific 35 | &reviseCodec{}, 36 | } 37 | -------------------------------------------------------------------------------- /message/main_test.go: -------------------------------------------------------------------------------- 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 message 16 | 17 | import "github.com/datastax/go-cassandra-native-protocol/primitive" 18 | 19 | //goland:noinspection GoUnusedConst 20 | const ( 21 | __ = byte('_') 22 | dot = byte('.') 23 | _0 = byte('0') 24 | _1 = byte('1') 25 | _2 = byte('2') 26 | _3 = byte('3') 27 | _4 = byte('4') 28 | _5 = byte('5') 29 | _6 = byte('6') 30 | _7 = byte('7') 31 | _8 = byte('8') 32 | _9 = byte('9') 33 | a = byte('a') 34 | b = byte('b') 35 | c = byte('c') 36 | d = byte('d') 37 | e = byte('e') 38 | f = byte('f') 39 | g = byte('g') 40 | h = byte('h') 41 | i = byte('i') 42 | j = byte('j') 43 | k = byte('k') 44 | l = byte('l') 45 | m = byte('m') 46 | n = byte('n') 47 | o = byte('o') 48 | p = byte('p') 49 | q = byte('q') 50 | r = byte('r') 51 | s = byte('s') 52 | t = byte('t') 53 | u = byte('u') 54 | v = byte('v') 55 | w = byte('w') 56 | x = byte('x') 57 | y = byte('y') 58 | z = byte('z') 59 | A = byte('A') 60 | B = byte('B') 61 | C = byte('C') 62 | D = byte('D') 63 | E = byte('E') 64 | F = byte('F') 65 | G = byte('G') 66 | H = byte('H') 67 | I = byte('I') 68 | J = byte('J') 69 | K = byte('K') 70 | L = byte('L') 71 | M = byte('M') 72 | N = byte('N') 73 | O = byte('O') 74 | P = byte('P') 75 | Q = byte('Q') 76 | R = byte('R') 77 | S = byte('S') 78 | T = byte('T') 79 | U = byte('U') 80 | V = byte('V') 81 | W = byte('W') 82 | X = byte('X') 83 | Y = byte('Y') 84 | Z = byte('Z') 85 | ) 86 | 87 | type encodeTestCase struct { 88 | name string 89 | input Message 90 | expected []byte 91 | err error 92 | } 93 | 94 | type decodeTestCase struct { 95 | name string 96 | input []byte 97 | expected Message 98 | err error 99 | } 100 | 101 | type encodedLengthTestCase struct { 102 | name string 103 | input Message 104 | expected int 105 | err error 106 | } 107 | 108 | func int32Ptr(x int32) *int32 { return &x } 109 | func int64Ptr(x int64) *int64 { return &x } 110 | func consistencyLevelPtr(x primitive.ConsistencyLevel) *primitive.ConsistencyLevel { return &x } 111 | -------------------------------------------------------------------------------- /message/message.go: -------------------------------------------------------------------------------- 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 message 16 | 17 | import ( 18 | "io" 19 | 20 | "github.com/datastax/go-cassandra-native-protocol/primitive" 21 | ) 22 | 23 | type Message interface { 24 | IsResponse() bool 25 | GetOpCode() primitive.OpCode 26 | DeepCopyMessage() Message 27 | } 28 | 29 | type Encoder interface { 30 | Encode(msg Message, dest io.Writer, version primitive.ProtocolVersion) error 31 | EncodedLength(msg Message, version primitive.ProtocolVersion) (int, error) 32 | } 33 | 34 | type Decoder interface { 35 | Decode(source io.Reader, version primitive.ProtocolVersion) (Message, error) 36 | } 37 | 38 | type Codec interface { 39 | Encoder 40 | Decoder 41 | GetOpCode() primitive.OpCode 42 | } 43 | -------------------------------------------------------------------------------- /message/options.go: -------------------------------------------------------------------------------- 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 message 16 | 17 | import ( 18 | "errors" 19 | "fmt" 20 | "io" 21 | 22 | "github.com/datastax/go-cassandra-native-protocol/primitive" 23 | ) 24 | 25 | // Options is a request message to obtain supported features from the server. The response to such a request is 26 | // Supported. 27 | // +k8s:deepcopy-gen=true 28 | // +k8s:deepcopy-gen:interfaces=github.com/datastax/go-cassandra-native-protocol/message.Message 29 | type Options struct { 30 | } 31 | 32 | func (m *Options) IsResponse() bool { 33 | return false 34 | } 35 | 36 | func (m *Options) GetOpCode() primitive.OpCode { 37 | return primitive.OpCodeOptions 38 | } 39 | 40 | func (m *Options) String() string { 41 | return "OPTIONS" 42 | } 43 | 44 | type optionsCodec struct{} 45 | 46 | func (c *optionsCodec) Encode(msg Message, _ io.Writer, _ primitive.ProtocolVersion) error { 47 | _, ok := msg.(*Options) 48 | if !ok { 49 | return errors.New(fmt.Sprintf("expected *message.Options, got %T", msg)) 50 | } 51 | return nil 52 | } 53 | 54 | func (c *optionsCodec) EncodedLength(msg Message, _ primitive.ProtocolVersion) (int, error) { 55 | _, ok := msg.(*Options) 56 | if !ok { 57 | return -1, errors.New(fmt.Sprintf("expected *message.Options, got %T", msg)) 58 | } 59 | return 0, nil 60 | } 61 | 62 | func (c *optionsCodec) Decode(_ io.Reader, _ primitive.ProtocolVersion) (Message, error) { 63 | return &Options{}, nil 64 | } 65 | 66 | func (c *optionsCodec) GetOpCode() primitive.OpCode { 67 | return primitive.OpCodeOptions 68 | } 69 | -------------------------------------------------------------------------------- /message/options_test.go: -------------------------------------------------------------------------------- 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 message 16 | 17 | import ( 18 | "bytes" 19 | "errors" 20 | "testing" 21 | 22 | "github.com/stretchr/testify/assert" 23 | 24 | "github.com/datastax/go-cassandra-native-protocol/primitive" 25 | ) 26 | 27 | func TestOptionsCodec_Encode(t *testing.T) { 28 | codec := &optionsCodec{} 29 | for _, version := range primitive.SupportedProtocolVersions() { 30 | t.Run(version.String(), func(t *testing.T) { 31 | tests := []encodeTestCase{ 32 | { 33 | "options simple", 34 | &Options{}, 35 | nil, 36 | nil, 37 | }, 38 | { 39 | "not an options", 40 | &Ready{}, 41 | nil, 42 | errors.New("expected *message.Options, got *message.Ready"), 43 | }, 44 | } 45 | for _, tt := range tests { 46 | t.Run(tt.name, func(t *testing.T) { 47 | dest := &bytes.Buffer{} 48 | err := codec.Encode(tt.input, dest, version) 49 | assert.Equal(t, tt.expected, dest.Bytes()) 50 | assert.Equal(t, tt.err, err) 51 | }) 52 | } 53 | }) 54 | } 55 | } 56 | 57 | func TestOptionsCodec_EncodedLength(t *testing.T) { 58 | codec := &optionsCodec{} 59 | for _, version := range primitive.SupportedProtocolVersions() { 60 | t.Run(version.String(), func(t *testing.T) { 61 | tests := []encodedLengthTestCase{ 62 | { 63 | "options simple", 64 | &Options{}, 65 | 0, 66 | nil, 67 | }, 68 | { 69 | "not an options", 70 | &Ready{}, 71 | -1, 72 | errors.New("expected *message.Options, got *message.Ready"), 73 | }, 74 | } 75 | for _, tt := range tests { 76 | t.Run(tt.name, func(t *testing.T) { 77 | actual, err := codec.EncodedLength(tt.input, version) 78 | assert.Equal(t, tt.expected, actual) 79 | assert.Equal(t, tt.err, err) 80 | }) 81 | } 82 | }) 83 | } 84 | } 85 | 86 | func TestOptionsCodec_Decode(t *testing.T) { 87 | codec := &optionsCodec{} 88 | for _, version := range primitive.SupportedProtocolVersions() { 89 | t.Run(version.String(), func(t *testing.T) { 90 | tests := []decodeTestCase{ 91 | { 92 | "options simple", 93 | []byte{}, 94 | &Options{}, 95 | nil, 96 | }, 97 | } 98 | for _, tt := range tests { 99 | t.Run(tt.name, func(t *testing.T) { 100 | source := bytes.NewBuffer(tt.input) 101 | actual, err := codec.Decode(source, version) 102 | assert.Equal(t, tt.expected, actual) 103 | assert.Equal(t, tt.err, err) 104 | }) 105 | } 106 | }) 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /message/prepare.go: -------------------------------------------------------------------------------- 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 message 16 | 17 | import ( 18 | "errors" 19 | "fmt" 20 | "io" 21 | 22 | "github.com/datastax/go-cassandra-native-protocol/primitive" 23 | ) 24 | 25 | // Prepare is a request to prepare a CQL statement. 26 | // +k8s:deepcopy-gen=true 27 | // +k8s:deepcopy-gen:interfaces=github.com/datastax/go-cassandra-native-protocol/message.Message 28 | type Prepare struct { 29 | // The CQL query to prepare. 30 | Query string 31 | // The keyspace that the query should be executed in. 32 | // Introduced in Protocol Version 5, also present in DSE protocol v2. 33 | Keyspace string 34 | } 35 | 36 | func (m *Prepare) IsResponse() bool { 37 | return false 38 | } 39 | 40 | func (m *Prepare) GetOpCode() primitive.OpCode { 41 | return primitive.OpCodePrepare 42 | } 43 | 44 | func (m *Prepare) String() string { 45 | return fmt.Sprintf("PREPARE (%v, %v)", m.Query, m.Keyspace) 46 | } 47 | 48 | func (m *Prepare) Flags() primitive.PrepareFlag { 49 | var flags primitive.PrepareFlag 50 | if m.Keyspace != "" { 51 | flags = flags.Add(primitive.PrepareFlagWithKeyspace) 52 | } 53 | return flags 54 | } 55 | 56 | type prepareCodec struct{} 57 | 58 | func (c *prepareCodec) Encode(msg Message, dest io.Writer, version primitive.ProtocolVersion) (err error) { 59 | prepare, ok := msg.(*Prepare) 60 | if !ok { 61 | return errors.New(fmt.Sprintf("expected *message.Prepare, got %T", msg)) 62 | } 63 | if prepare.Query == "" { 64 | return errors.New("cannot write PREPARE empty query string") 65 | } else if err = primitive.WriteLongString(prepare.Query, dest); err != nil { 66 | return fmt.Errorf("cannot write PREPARE query string: %w", err) 67 | } 68 | if version.SupportsPrepareFlags() { 69 | flags := prepare.Flags() 70 | if err = primitive.WriteInt(int32(flags), dest); err != nil { 71 | return fmt.Errorf("cannot write PREPARE flags: %w", err) 72 | } 73 | if flags.Contains(primitive.PrepareFlagWithKeyspace) { 74 | if prepare.Keyspace == "" { 75 | return errors.New("cannot write empty keyspace") 76 | } else if err = primitive.WriteString(prepare.Keyspace, dest); err != nil { 77 | return fmt.Errorf("cannot write PREPARE keyspace: %w", err) 78 | } 79 | } 80 | } 81 | return 82 | } 83 | 84 | func (c *prepareCodec) EncodedLength(msg Message, version primitive.ProtocolVersion) (size int, err error) { 85 | prepare, ok := msg.(*Prepare) 86 | if !ok { 87 | return -1, errors.New(fmt.Sprintf("expected *message.Prepare, got %T", msg)) 88 | } 89 | size += primitive.LengthOfLongString(prepare.Query) 90 | if version.SupportsPrepareFlags() { 91 | size += primitive.LengthOfInt // flags 92 | if prepare.Keyspace != "" { 93 | size += primitive.LengthOfString(prepare.Keyspace) 94 | } 95 | } 96 | return size, nil 97 | } 98 | 99 | func (c *prepareCodec) Decode(source io.Reader, version primitive.ProtocolVersion) (msg Message, err error) { 100 | prepare := &Prepare{} 101 | if prepare.Query, err = primitive.ReadLongString(source); err != nil { 102 | return nil, fmt.Errorf("cannot read PREPARE query: %w", err) 103 | } 104 | if version.SupportsPrepareFlags() { 105 | var flags primitive.PrepareFlag 106 | var f int32 107 | if f, err = primitive.ReadInt(source); err != nil { 108 | return nil, fmt.Errorf("cannot read PREPARE flags: %w", err) 109 | } 110 | flags = primitive.PrepareFlag(f) 111 | if flags.Contains(primitive.PrepareFlagWithKeyspace) { 112 | if prepare.Keyspace, err = primitive.ReadString(source); err != nil { 113 | return nil, fmt.Errorf("cannot read PREPARE keyspace: %w", err) 114 | } 115 | } 116 | } 117 | return prepare, nil 118 | } 119 | 120 | func (c *prepareCodec) GetOpCode() primitive.OpCode { 121 | return primitive.OpCodePrepare 122 | } 123 | -------------------------------------------------------------------------------- /message/query.go: -------------------------------------------------------------------------------- 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 message 16 | 17 | import ( 18 | "errors" 19 | "fmt" 20 | "io" 21 | 22 | "github.com/datastax/go-cassandra-native-protocol/primitive" 23 | ) 24 | 25 | // Query is a request that executes a CQL query. 26 | // +k8s:deepcopy-gen=true 27 | // +k8s:deepcopy-gen:interfaces=github.com/datastax/go-cassandra-native-protocol/message.Message 28 | type Query struct { 29 | Query string 30 | Options *QueryOptions 31 | } 32 | 33 | func (q *Query) String() string { 34 | return fmt.Sprintf("QUERY %s", q.Query) 35 | } 36 | 37 | func (q *Query) IsResponse() bool { 38 | return false 39 | } 40 | 41 | func (q *Query) GetOpCode() primitive.OpCode { 42 | return primitive.OpCodeQuery 43 | } 44 | 45 | type queryCodec struct{} 46 | 47 | func (c *queryCodec) Encode(msg Message, dest io.Writer, version primitive.ProtocolVersion) error { 48 | query, ok := msg.(*Query) 49 | if !ok { 50 | return errors.New(fmt.Sprintf("expected *message.Query, got %T", msg)) 51 | } 52 | if err := primitive.WriteLongString(query.Query, dest); err != nil { 53 | return fmt.Errorf("cannot write QUERY query string: %w", err) 54 | } 55 | if err := EncodeQueryOptions(query.Options, dest, version); err != nil { 56 | return fmt.Errorf("cannot write QUERY options: %w", err) 57 | } 58 | return nil 59 | } 60 | 61 | func (c *queryCodec) EncodedLength(msg Message, version primitive.ProtocolVersion) (int, error) { 62 | query, ok := msg.(*Query) 63 | if !ok { 64 | return -1, errors.New(fmt.Sprintf("expected *message.Query, got %T", msg)) 65 | } 66 | lengthOfQuery := primitive.LengthOfLongString(query.Query) 67 | lengthOfQueryOptions, err := LengthOfQueryOptions(query.Options, version) 68 | if err != nil { 69 | return -1, fmt.Errorf("cannot compute size of QUERY message: %w", err) 70 | } 71 | return lengthOfQuery + lengthOfQueryOptions, nil 72 | } 73 | 74 | func (c *queryCodec) Decode(source io.Reader, version primitive.ProtocolVersion) (Message, error) { 75 | if query, err := primitive.ReadLongString(source); err != nil { 76 | return nil, err 77 | } else if options, err := DecodeQueryOptions(source, version); err != nil { 78 | return nil, err 79 | } else { 80 | return &Query{Query: query, Options: options}, nil 81 | } 82 | } 83 | 84 | func (c *queryCodec) GetOpCode() primitive.OpCode { 85 | return primitive.OpCodeQuery 86 | } 87 | -------------------------------------------------------------------------------- /message/ready.go: -------------------------------------------------------------------------------- 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 message 16 | 17 | import ( 18 | "errors" 19 | "fmt" 20 | "io" 21 | 22 | "github.com/datastax/go-cassandra-native-protocol/primitive" 23 | ) 24 | 25 | // Ready is a response sent when the coordinator replies to a Startup request without requiring authentication. 26 | // +k8s:deepcopy-gen=true 27 | // +k8s:deepcopy-gen:interfaces=github.com/datastax/go-cassandra-native-protocol/message.Message 28 | type Ready struct { 29 | } 30 | 31 | func (m *Ready) IsResponse() bool { 32 | return true 33 | } 34 | 35 | func (m *Ready) GetOpCode() primitive.OpCode { 36 | return primitive.OpCodeReady 37 | } 38 | 39 | func (m *Ready) String() string { 40 | return "READY" 41 | } 42 | 43 | type readyCodec struct{} 44 | 45 | func (c *readyCodec) Encode(msg Message, _ io.Writer, _ primitive.ProtocolVersion) error { 46 | _, ok := msg.(*Ready) 47 | if !ok { 48 | return errors.New(fmt.Sprintf("expected *message.Ready, got %T", msg)) 49 | } 50 | return nil 51 | } 52 | 53 | func (c *readyCodec) EncodedLength(msg Message, _ primitive.ProtocolVersion) (int, error) { 54 | _, ok := msg.(*Ready) 55 | if !ok { 56 | return -1, errors.New(fmt.Sprintf("expected *message.Ready, got %T", msg)) 57 | } 58 | return 0, nil 59 | } 60 | 61 | func (c *readyCodec) Decode(_ io.Reader, _ primitive.ProtocolVersion) (Message, error) { 62 | return &Ready{}, nil 63 | } 64 | 65 | func (c *readyCodec) GetOpCode() primitive.OpCode { 66 | return primitive.OpCodeReady 67 | } 68 | -------------------------------------------------------------------------------- /message/ready_test.go: -------------------------------------------------------------------------------- 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 message 16 | 17 | import ( 18 | "bytes" 19 | "errors" 20 | "testing" 21 | 22 | "github.com/stretchr/testify/assert" 23 | 24 | "github.com/datastax/go-cassandra-native-protocol/primitive" 25 | ) 26 | 27 | func TestReadyCodec_Encode(t *testing.T) { 28 | codec := &readyCodec{} 29 | for _, version := range primitive.SupportedProtocolVersions() { 30 | t.Run(version.String(), func(t *testing.T) { 31 | tests := []encodeTestCase{ 32 | { 33 | "ready simple", 34 | &Ready{}, 35 | nil, 36 | nil, 37 | }, 38 | { 39 | "not a ready", 40 | &Options{}, 41 | nil, 42 | errors.New("expected *message.Ready, got *message.Options"), 43 | }, 44 | } 45 | for _, tt := range tests { 46 | t.Run(tt.name, func(t *testing.T) { 47 | dest := &bytes.Buffer{} 48 | err := codec.Encode(tt.input, dest, version) 49 | assert.Equal(t, tt.expected, dest.Bytes()) 50 | assert.Equal(t, tt.err, err) 51 | }) 52 | } 53 | }) 54 | } 55 | } 56 | 57 | func TestReadyCodec_EncodedLength(t *testing.T) { 58 | codec := &readyCodec{} 59 | for _, version := range primitive.SupportedProtocolVersions() { 60 | t.Run(version.String(), func(t *testing.T) { 61 | tests := []encodedLengthTestCase{ 62 | { 63 | "ready simple", 64 | &Ready{}, 65 | 0, 66 | nil, 67 | }, 68 | { 69 | "not a ready", 70 | &Options{}, 71 | -1, 72 | errors.New("expected *message.Ready, got *message.Options"), 73 | }, 74 | } 75 | for _, tt := range tests { 76 | t.Run(tt.name, func(t *testing.T) { 77 | actual, err := codec.EncodedLength(tt.input, version) 78 | assert.Equal(t, tt.expected, actual) 79 | assert.Equal(t, tt.err, err) 80 | }) 81 | } 82 | }) 83 | } 84 | } 85 | 86 | func TestReadyCodec_Decode(t *testing.T) { 87 | codec := &readyCodec{} 88 | for _, version := range primitive.SupportedProtocolVersions() { 89 | t.Run(version.String(), func(t *testing.T) { 90 | tests := []decodeTestCase{ 91 | { 92 | "ready simple", 93 | []byte{}, 94 | &Ready{}, 95 | nil, 96 | }, 97 | } 98 | for _, tt := range tests { 99 | t.Run(tt.name, func(t *testing.T) { 100 | source := bytes.NewBuffer(tt.input) 101 | actual, err := codec.Decode(source, version) 102 | assert.Equal(t, tt.expected, actual) 103 | assert.Equal(t, tt.err, err) 104 | }) 105 | } 106 | }) 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /message/register.go: -------------------------------------------------------------------------------- 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 message 16 | 17 | import ( 18 | "errors" 19 | "fmt" 20 | "io" 21 | 22 | "github.com/datastax/go-cassandra-native-protocol/primitive" 23 | ) 24 | 25 | // Register is a request to register the client as a listener for the specified event types. 26 | // +k8s:deepcopy-gen=true 27 | // +k8s:deepcopy-gen:interfaces=github.com/datastax/go-cassandra-native-protocol/message.Message 28 | type Register struct { 29 | EventTypes []primitive.EventType 30 | } 31 | 32 | func (m *Register) IsResponse() bool { 33 | return false 34 | } 35 | 36 | func (m *Register) GetOpCode() primitive.OpCode { 37 | return primitive.OpCodeRegister 38 | } 39 | 40 | func (m *Register) String() string { 41 | return fmt.Sprint("REGISTER ", m.EventTypes) 42 | } 43 | 44 | type registerCodec struct{} 45 | 46 | func (c *registerCodec) Encode(msg Message, dest io.Writer, _ primitive.ProtocolVersion) error { 47 | register, ok := msg.(*Register) 48 | if !ok { 49 | return errors.New(fmt.Sprintf("expected *message.Register, got %T", msg)) 50 | } 51 | if len(register.EventTypes) == 0 { 52 | return errors.New("REGISTER messages must have at least one event type") 53 | } 54 | for _, eventType := range register.EventTypes { 55 | if err := primitive.CheckValidEventType(eventType); err != nil { 56 | return err 57 | } 58 | } 59 | return primitive.WriteStringList(asStringList(register.EventTypes), dest) 60 | } 61 | 62 | func (c *registerCodec) EncodedLength(msg Message, _ primitive.ProtocolVersion) (int, error) { 63 | register, ok := msg.(*Register) 64 | if !ok { 65 | return -1, errors.New(fmt.Sprintf("expected *message.Register, got %T", msg)) 66 | } 67 | return primitive.LengthOfStringList(asStringList(register.EventTypes)), nil 68 | } 69 | 70 | func (c *registerCodec) Decode(source io.Reader, _ primitive.ProtocolVersion) (Message, error) { 71 | if eventTypes, err := primitive.ReadStringList(source); err != nil { 72 | return nil, err 73 | } else { 74 | for _, eventType := range eventTypes { 75 | if err := primitive.CheckValidEventType(primitive.EventType(eventType)); err != nil { 76 | return nil, err 77 | } 78 | } 79 | return &Register{EventTypes: fromStringList(eventTypes)}, nil 80 | } 81 | } 82 | 83 | func (c *registerCodec) GetOpCode() primitive.OpCode { 84 | return primitive.OpCodeRegister 85 | } 86 | 87 | func asStringList(eventTypes []primitive.EventType) []string { 88 | strings := make([]string, len(eventTypes)) 89 | for i, eventType := range eventTypes { 90 | strings[i] = string(eventType) 91 | } 92 | return strings 93 | } 94 | 95 | func fromStringList(strings []string) []primitive.EventType { 96 | eventTypes := make([]primitive.EventType, len(strings)) 97 | for i, eventType := range strings { 98 | eventTypes[i] = primitive.EventType(eventType) 99 | } 100 | return eventTypes 101 | } 102 | -------------------------------------------------------------------------------- /message/result_other_test.go: -------------------------------------------------------------------------------- 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 message 16 | 17 | import ( 18 | "bytes" 19 | "testing" 20 | 21 | "github.com/stretchr/testify/assert" 22 | 23 | "github.com/datastax/go-cassandra-native-protocol/primitive" 24 | ) 25 | 26 | func TestSetKeyspaceResult_DeepCopy(t *testing.T) { 27 | msg := &SetKeyspaceResult{ 28 | Keyspace: "ks1", 29 | } 30 | 31 | cloned := msg.DeepCopy() 32 | assert.Equal(t, msg, cloned) 33 | 34 | cloned.Keyspace = "ks2" 35 | 36 | assert.NotEqual(t, msg, cloned) 37 | 38 | assert.Equal(t, "ks1", msg.Keyspace) 39 | assert.Equal(t, "ks2", cloned.Keyspace) 40 | } 41 | 42 | func TestResultCodec_Encode_Other(test *testing.T) { 43 | codec := &resultCodec{} 44 | for _, version := range primitive.SupportedProtocolVersions() { 45 | test.Run(version.String(), func(test *testing.T) { 46 | tests := []encodeTestCase{ 47 | { 48 | "void result", 49 | &VoidResult{}, 50 | []byte{ 51 | 0, 0, 0, 1, // result type 52 | }, 53 | nil, 54 | }, 55 | { 56 | "set keyspace result", 57 | &SetKeyspaceResult{Keyspace: "ks1"}, 58 | []byte{ 59 | 0, 0, 0, 3, // result type 60 | 0, 3, k, s, _1, 61 | }, 62 | nil, 63 | }, 64 | } 65 | for _, tt := range tests { 66 | test.Run(tt.name, func(t *testing.T) { 67 | dest := &bytes.Buffer{} 68 | err := codec.Encode(tt.input, dest, version) 69 | assert.Equal(t, tt.expected, dest.Bytes()) 70 | assert.Equal(t, tt.err, err) 71 | }) 72 | } 73 | }) 74 | } 75 | } 76 | 77 | func TestResultCodec_EncodedLength_Other(test *testing.T) { 78 | codec := &resultCodec{} 79 | for _, version := range primitive.SupportedProtocolVersions() { 80 | test.Run(version.String(), func(test *testing.T) { 81 | tests := []encodedLengthTestCase{ 82 | { 83 | "void result", 84 | &VoidResult{}, 85 | primitive.LengthOfInt, 86 | nil, 87 | }, 88 | { 89 | "set keyspace result", 90 | &SetKeyspaceResult{Keyspace: "ks1"}, 91 | primitive.LengthOfInt + primitive.LengthOfString("ks1"), 92 | nil, 93 | }, 94 | } 95 | for _, tt := range tests { 96 | test.Run(tt.name, func(t *testing.T) { 97 | actual, err := codec.EncodedLength(tt.input, version) 98 | assert.Equal(t, tt.expected, actual) 99 | assert.Equal(t, tt.err, err) 100 | }) 101 | } 102 | }) 103 | } 104 | } 105 | 106 | func TestResultCodec_Decode_Other(test *testing.T) { 107 | codec := &resultCodec{} 108 | for _, version := range primitive.SupportedProtocolVersions() { 109 | test.Run(version.String(), func(test *testing.T) { 110 | tests := []decodeTestCase{ 111 | { 112 | "void result", 113 | []byte{ 114 | 0, 0, 0, 1, // result type 115 | }, 116 | &VoidResult{}, 117 | nil, 118 | }, 119 | { 120 | "set keyspace result", 121 | []byte{ 122 | 0, 0, 0, 3, // result type 123 | 0, 3, k, s, _1, 124 | }, 125 | &SetKeyspaceResult{Keyspace: "ks1"}, 126 | nil, 127 | }, 128 | } 129 | for _, tt := range tests { 130 | test.Run(tt.name, func(t *testing.T) { 131 | source := bytes.NewBuffer(tt.input) 132 | actual, err := codec.Decode(source, version) 133 | assert.Equal(t, tt.expected, actual) 134 | assert.Equal(t, tt.err, err) 135 | }) 136 | } 137 | }) 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /message/supported.go: -------------------------------------------------------------------------------- 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 message 16 | 17 | import ( 18 | "errors" 19 | "fmt" 20 | "io" 21 | 22 | "github.com/datastax/go-cassandra-native-protocol/primitive" 23 | ) 24 | 25 | const ( 26 | // SupportedProtocolVersions is a Supported.Options multimap key returned by Cassandra from protocol v5 onwards. 27 | // It holds the list of native protocol versions that are supported, encoded as the version number followed by a 28 | // slash and the version description. For example: 3/v3, 4/v4, 5/v5-beta. If a version is in beta, it will have the 29 | // word "beta" in its description. 30 | SupportedProtocolVersions = "PROTOCOL_VERSIONS" 31 | ) 32 | 33 | // Supported is a response message sent in reply to an Options request. 34 | // +k8s:deepcopy-gen=true 35 | // +k8s:deepcopy-gen:interfaces=github.com/datastax/go-cassandra-native-protocol/message.Message 36 | type Supported struct { 37 | // This multimap gives for each of the supported Startup options, the list of supported values. 38 | // See Startup.Options for details about supported option keys. 39 | Options map[string][]string 40 | } 41 | 42 | func (m *Supported) IsResponse() bool { 43 | return true 44 | } 45 | 46 | func (m *Supported) GetOpCode() primitive.OpCode { 47 | return primitive.OpCodeSupported 48 | } 49 | 50 | func (m *Supported) String() string { 51 | return fmt.Sprintf("SUPPORTED %v", m.Options) 52 | } 53 | 54 | type supportedCodec struct{} 55 | 56 | func (c *supportedCodec) Encode(msg Message, dest io.Writer, _ primitive.ProtocolVersion) error { 57 | supported, ok := msg.(*Supported) 58 | if !ok { 59 | return errors.New(fmt.Sprintf("expected *message.Supported, got %T", msg)) 60 | } 61 | if err := primitive.WriteStringMultiMap(supported.Options, dest); err != nil { 62 | return err 63 | } 64 | return nil 65 | } 66 | 67 | func (c *supportedCodec) EncodedLength(msg Message, _ primitive.ProtocolVersion) (int, error) { 68 | supported, ok := msg.(*Supported) 69 | if !ok { 70 | return -1, errors.New(fmt.Sprintf("expected *message.Supported, got %T", msg)) 71 | } 72 | return primitive.LengthOfStringMultiMap(supported.Options), nil 73 | } 74 | 75 | func (c *supportedCodec) Decode(source io.Reader, _ primitive.ProtocolVersion) (Message, error) { 76 | if options, err := primitive.ReadStringMultiMap(source); err != nil { 77 | return nil, err 78 | } else { 79 | return &Supported{Options: options}, nil 80 | } 81 | } 82 | 83 | func (c *supportedCodec) GetOpCode() primitive.OpCode { 84 | return primitive.OpCodeSupported 85 | } 86 | -------------------------------------------------------------------------------- /primitive/bytes.go: -------------------------------------------------------------------------------- 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 primitive 16 | 17 | import ( 18 | "errors" 19 | "fmt" 20 | "io" 21 | ) 22 | 23 | // [bytes] 24 | 25 | func ReadBytes(source io.Reader) ([]byte, error) { 26 | if length, err := ReadInt(source); err != nil { 27 | return nil, fmt.Errorf("cannot read [bytes] length: %w", err) 28 | } else if length < 0 { 29 | return nil, nil 30 | } else if length == 0 { 31 | return []byte{}, nil 32 | } else { 33 | decoded := make([]byte, length) 34 | if _, err := io.ReadFull(source, decoded); err != nil { 35 | return nil, fmt.Errorf("cannot read [bytes] content: %w", err) 36 | } 37 | return decoded, nil 38 | } 39 | } 40 | 41 | func WriteBytes(b []byte, dest io.Writer) error { 42 | if b == nil { 43 | if err := WriteInt(-1, dest); err != nil { 44 | return fmt.Errorf("cannot write null [bytes]: %w", err) 45 | } 46 | } else { 47 | length := len(b) 48 | if err := WriteInt(int32(length), dest); err != nil { 49 | return fmt.Errorf("cannot write [bytes] length: %w", err) 50 | } else if n, err := dest.Write(b); err != nil { 51 | return fmt.Errorf("cannot write [bytes] content: %w", err) 52 | } else if n < length { 53 | return errors.New("not enough capacity to write [bytes] content") 54 | } 55 | } 56 | return nil 57 | } 58 | 59 | func LengthOfBytes(b []byte) int { 60 | return LengthOfInt + len(b) 61 | } 62 | -------------------------------------------------------------------------------- /primitive/bytes_map.go: -------------------------------------------------------------------------------- 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 primitive 16 | 17 | import ( 18 | "fmt" 19 | "io" 20 | ) 21 | 22 | // [bytes map] 23 | 24 | func ReadBytesMap(source io.Reader) (map[string][]byte, error) { 25 | if length, err := ReadShort(source); err != nil { 26 | return nil, fmt.Errorf("cannot read [bytes map] length: %w", err) 27 | } else { 28 | decoded := make(map[string][]byte, length) 29 | for i := uint16(0); i < length; i++ { 30 | if key, err := ReadString(source); err != nil { 31 | return nil, fmt.Errorf("cannot read [bytes map] entry %d key: %w", i, err) 32 | } else if value, err := ReadBytes(source); err != nil { 33 | return nil, fmt.Errorf("cannot read [bytes map] entry %d value: %w", i, err) 34 | } else { 35 | decoded[key] = value 36 | } 37 | } 38 | return decoded, nil 39 | } 40 | } 41 | 42 | func WriteBytesMap(m map[string][]byte, dest io.Writer) error { 43 | if err := WriteShort(uint16(len(m)), dest); err != nil { 44 | return fmt.Errorf("cannot write [bytes map] length: %w", err) 45 | } 46 | for key, value := range m { 47 | if err := WriteString(key, dest); err != nil { 48 | return fmt.Errorf("cannot write [bytes map] entry '%v' key: %w", key, err) 49 | } 50 | if err := WriteBytes(value, dest); err != nil { 51 | return fmt.Errorf("cannot write [bytes map] entry '%v' value: %w", value, err) 52 | } 53 | } 54 | return nil 55 | } 56 | 57 | func LengthOfBytesMap(m map[string][]byte) int { 58 | length := LengthOfShort 59 | for key, value := range m { 60 | length += LengthOfString(key) + LengthOfBytes(value) 61 | } 62 | return length 63 | } 64 | -------------------------------------------------------------------------------- /primitive/bytes_test.go: -------------------------------------------------------------------------------- 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 primitive 16 | 17 | import ( 18 | "bytes" 19 | "errors" 20 | "fmt" 21 | "io/ioutil" 22 | "testing" 23 | 24 | "github.com/stretchr/testify/assert" 25 | ) 26 | 27 | func TestReadBytes(t *testing.T) { 28 | tests := []struct { 29 | name string 30 | source []byte 31 | expected []byte 32 | remaining []byte 33 | err error 34 | }{ 35 | {"empty bytes", []byte{0, 0, 0, 0}, []byte{}, []byte{}, nil}, 36 | {"nil bytes", []byte{0xff, 0xff, 0xff, 0xff}, nil, []byte{}, nil}, 37 | {"singleton bytes", []byte{0, 0, 0, 1, 1}, []byte{1}, []byte{}, nil}, 38 | {"simple bytes", []byte{0, 0, 0, 2, 1, 2}, []byte{1, 2}, []byte{}, nil}, 39 | { 40 | "cannot read bytes length", 41 | []byte{0, 0, 0}, 42 | nil, 43 | []byte{}, 44 | fmt.Errorf("cannot read [bytes] length: %w", fmt.Errorf("cannot read [int]: %w", errors.New("unexpected EOF"))), 45 | }, 46 | { 47 | "cannot read bytes content", 48 | []byte{0, 0, 0, 2, 1}, 49 | nil, 50 | []byte{}, 51 | fmt.Errorf("cannot read [bytes] content: %w", errors.New("unexpected EOF")), 52 | }, 53 | } 54 | for _, tt := range tests { 55 | t.Run(tt.name, func(t *testing.T) { 56 | buf := bytes.NewReader(tt.source) 57 | actual, err := ReadBytes(buf) 58 | assert.Equal(t, tt.err, err) 59 | assert.Equal(t, tt.expected, actual) 60 | 61 | remaining, _ := ioutil.ReadAll(buf) 62 | assert.Equal(t, tt.remaining, remaining) 63 | }) 64 | } 65 | } 66 | 67 | func TestWriteBytes(t *testing.T) { 68 | tests := []struct { 69 | name string 70 | input []byte 71 | expected []byte 72 | err error 73 | }{ 74 | { 75 | "empty bytes", 76 | []byte{}, 77 | []byte{0, 0, 0, 0}, 78 | nil, 79 | }, 80 | { 81 | "nil bytes", 82 | nil, 83 | []byte{0xff, 0xff, 0xff, 0xff}, 84 | nil, 85 | }, 86 | { 87 | "singleton bytes", 88 | []byte{1}, 89 | []byte{0, 0, 0, 1, 1}, 90 | nil, 91 | }, 92 | { 93 | "simple bytes", 94 | []byte{1, 2}, 95 | []byte{0, 0, 0, 2, 1, 2}, 96 | nil, 97 | }, 98 | } 99 | for _, tt := range tests { 100 | t.Run(tt.name, func(t *testing.T) { 101 | buf := &bytes.Buffer{} 102 | err := WriteBytes(tt.input, buf) 103 | assert.Equal(t, tt.expected, buf.Bytes()) 104 | assert.Equal(t, tt.err, err) 105 | }) 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /primitive/constants_test.go: -------------------------------------------------------------------------------- 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 primitive 16 | 17 | import "testing" 18 | 19 | func TestProtocolVersion_String(t *testing.T) { 20 | tests := []struct { 21 | name string 22 | v ProtocolVersion 23 | want string 24 | }{ 25 | {"v2", ProtocolVersion2, "ProtocolVersion OSS 2"}, 26 | {"v3", ProtocolVersion3, "ProtocolVersion OSS 3"}, 27 | {"v4", ProtocolVersion4, "ProtocolVersion OSS 4"}, 28 | {"v5", ProtocolVersion5, "ProtocolVersion OSS 5"}, 29 | {"DSE v1", ProtocolVersionDse1, "ProtocolVersion DSE 1"}, 30 | {"DSE v2", ProtocolVersionDse2, "ProtocolVersion DSE 2"}, 31 | {"unknown", ProtocolVersion(6), "ProtocolVersion ? [0X06]"}, 32 | } 33 | for _, tt := range tests { 34 | t.Run(tt.name, func(t *testing.T) { 35 | if got := tt.v.String(); got != tt.want { 36 | t.Errorf("String() = %v, want %v", got, tt.want) 37 | } 38 | }) 39 | } 40 | } 41 | 42 | func TestDataTypeCode_IsValid(t *testing.T) { 43 | tests := []struct { 44 | name string 45 | dtc DataTypeCode 46 | shouldBeValid bool 47 | }{ 48 | {"DataTypeCodeCustom", DataTypeCodeCustom, true}, 49 | {"DataTypeCodeAscii", DataTypeCodeAscii, true}, 50 | {"DataTypeCodeBigint", DataTypeCodeBigint, true}, 51 | {"DataTypeCodeBlob", DataTypeCodeBlob, true}, 52 | {"DataTypeCodeBoolean", DataTypeCodeBoolean, true}, 53 | {"DataTypeCodeCounter", DataTypeCodeCounter, true}, 54 | {"DataTypeCodeDecimal", DataTypeCodeDecimal, true}, 55 | {"DataTypeCodeDouble", DataTypeCodeDouble, true}, 56 | {"DataTypeCodeFloat", DataTypeCodeFloat, true}, 57 | {"DataTypeCodeInt", DataTypeCodeInt, true}, 58 | {"DataTypeCodeText", DataTypeCodeText, true}, 59 | {"DataTypeCodeTimestamp", DataTypeCodeTimestamp, true}, 60 | {"DataTypeCodeUuid", DataTypeCodeUuid, true}, 61 | {"DataTypeCodeVarchar", DataTypeCodeVarchar, true}, 62 | {"DataTypeCodeVarint", DataTypeCodeVarint, true}, 63 | {"DataTypeCodeTimeuuid", DataTypeCodeTimeuuid, true}, 64 | {"DataTypeCodeInet", DataTypeCodeInet, true}, 65 | {"DataTypeCodeDate", DataTypeCodeDate, true}, 66 | {"DataTypeCodeTime", DataTypeCodeTime, true}, 67 | {"DataTypeCodeSmallint", DataTypeCodeSmallint, true}, 68 | {"DataTypeCodeTinyint", DataTypeCodeTinyint, true}, 69 | {"DataTypeCodeDuration", DataTypeCodeDuration, true}, 70 | {"DataTypeCodeList", DataTypeCodeList, true}, 71 | {"DataTypeCodeMap", DataTypeCodeMap, true}, 72 | {"DataTypeCodeSet", DataTypeCodeSet, true}, 73 | {"DataTypeCodeUdt", DataTypeCodeUdt, true}, 74 | {"DataTypeCodeTuple", DataTypeCodeTuple, true}, 75 | {"Nonsense", DataTypeCode(0x0023), false}, 76 | } 77 | 78 | for _, tt := range tests { 79 | t.Run(tt.name, func(t *testing.T) { 80 | if isValid := tt.dtc.IsValid(); isValid != tt.shouldBeValid { 81 | t.Errorf("IsValid() = %v, shouldBeValid %v", isValid, tt.shouldBeValid) 82 | } 83 | }) 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /primitive/deepcopy_generated.go: -------------------------------------------------------------------------------- 1 | //go:build !ignore_autogenerated 2 | // +build !ignore_autogenerated 3 | 4 | // Copyright 2022 DataStax 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | 18 | // Code generated by deepcopy-gen. DO NOT EDIT. 19 | 20 | package primitive 21 | 22 | import ( 23 | net "net" 24 | ) 25 | 26 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 27 | func (in *FailureReason) DeepCopyInto(out *FailureReason) { 28 | *out = *in 29 | if in.Endpoint != nil { 30 | in, out := &in.Endpoint, &out.Endpoint 31 | *out = make(net.IP, len(*in)) 32 | copy(*out, *in) 33 | } 34 | return 35 | } 36 | 37 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FailureReason. 38 | func (in *FailureReason) DeepCopy() *FailureReason { 39 | if in == nil { 40 | return nil 41 | } 42 | out := new(FailureReason) 43 | in.DeepCopyInto(out) 44 | return out 45 | } 46 | 47 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 48 | func (in *Inet) DeepCopyInto(out *Inet) { 49 | *out = *in 50 | if in.Addr != nil { 51 | in, out := &in.Addr, &out.Addr 52 | *out = make(net.IP, len(*in)) 53 | copy(*out, *in) 54 | } 55 | return 56 | } 57 | 58 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Inet. 59 | func (in *Inet) DeepCopy() *Inet { 60 | if in == nil { 61 | return nil 62 | } 63 | out := new(Inet) 64 | in.DeepCopyInto(out) 65 | return out 66 | } 67 | 68 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 69 | func (in *Value) DeepCopyInto(out *Value) { 70 | *out = *in 71 | if in.Contents != nil { 72 | in, out := &in.Contents, &out.Contents 73 | *out = make([]byte, len(*in)) 74 | copy(*out, *in) 75 | } 76 | return 77 | } 78 | 79 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Value. 80 | func (in *Value) DeepCopy() *Value { 81 | if in == nil { 82 | return nil 83 | } 84 | out := new(Value) 85 | in.DeepCopyInto(out) 86 | return out 87 | } 88 | -------------------------------------------------------------------------------- /primitive/doc.go: -------------------------------------------------------------------------------- 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 | /* 16 | 17 | Package primitive contains types and functions to read and write CQL protocol primitive structures, 18 | as defined in section 3 of the CQL protocol specifications. 19 | 20 | */ 21 | package primitive 22 | -------------------------------------------------------------------------------- /primitive/inet.go: -------------------------------------------------------------------------------- 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 primitive 16 | 17 | import ( 18 | "errors" 19 | "fmt" 20 | "io" 21 | "net" 22 | ) 23 | 24 | // [inet] (net.IP + port) 25 | 26 | // Inet is the [inet] protocol type. It is the combination of a net.IP + port number. 27 | // +k8s:deepcopy-gen=true 28 | type Inet struct { 29 | Addr net.IP 30 | Port int32 31 | } 32 | 33 | func (i Inet) String() string { 34 | return fmt.Sprintf("%v:%v", i.Addr, i.Port) 35 | } 36 | 37 | func ReadInet(source io.Reader) (*Inet, error) { 38 | if addr, err := ReadInetAddr(source); err != nil { 39 | return nil, fmt.Errorf("cannot read [inet] address: %w", err) 40 | } else if port, err := ReadInt(source); err != nil { 41 | return nil, fmt.Errorf("cannot read [inet] port number: %w", err) 42 | } else { 43 | return &Inet{Addr: addr, Port: port}, nil 44 | } 45 | } 46 | 47 | func WriteInet(inet *Inet, dest io.Writer) error { 48 | if inet == nil { 49 | return errors.New("cannot write nil [inet]") 50 | } 51 | if err := WriteInetAddr(inet.Addr, dest); err != nil { 52 | return fmt.Errorf("cannot write [inet] address: %w", err) 53 | } else if err := WriteInt(inet.Port, dest); err != nil { 54 | return fmt.Errorf("cannot write [inet] port number: %w", err) 55 | } 56 | return nil 57 | } 58 | 59 | func LengthOfInet(inet *Inet) (length int, err error) { 60 | if inet == nil { 61 | return -1, errors.New("cannot compute nil [inet] length") 62 | } 63 | length, err = LengthOfInetAddr(inet.Addr) 64 | if err != nil { 65 | return -1, err 66 | } 67 | return length + LengthOfInt, nil 68 | } 69 | -------------------------------------------------------------------------------- /primitive/inet_addr.go: -------------------------------------------------------------------------------- 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 primitive 16 | 17 | import ( 18 | "errors" 19 | "fmt" 20 | "io" 21 | "net" 22 | ) 23 | 24 | // [inetaddr] is modeled by net.IP 25 | 26 | func ReadInetAddr(source io.Reader) (net.IP, error) { 27 | if length, err := ReadByte(source); err != nil { 28 | return nil, fmt.Errorf("cannot read [inetaddr] length: %w", err) 29 | } else { 30 | if length == net.IPv4len { 31 | decoded := make([]byte, net.IPv4len) 32 | if _, err := io.ReadFull(source, decoded); err != nil { 33 | return nil, fmt.Errorf("cannot read [inetaddr] IPv4 content: %w", err) 34 | } 35 | return net.IPv4(decoded[0], decoded[1], decoded[2], decoded[3]), nil 36 | } else if length == net.IPv6len { 37 | decoded := make([]byte, net.IPv6len) 38 | if _, err := io.ReadFull(source, decoded); err != nil { 39 | return nil, fmt.Errorf("cannot read [inetaddr] IPv6 content: %w", err) 40 | } 41 | return decoded, nil 42 | } else { 43 | return nil, errors.New("unknown inet address length: " + string(length)) 44 | } 45 | } 46 | } 47 | 48 | func WriteInetAddr(inetAddr net.IP, dest io.Writer) error { 49 | if inetAddr == nil { 50 | return errors.New("cannot write nil [inetaddr]") 51 | } 52 | var length byte 53 | if inetAddr.To4() != nil { 54 | length = net.IPv4len 55 | } else { 56 | length = net.IPv6len 57 | } 58 | if err := WriteByte(length, dest); err != nil { 59 | return fmt.Errorf("cannot write [inetaddr] length: %w", err) 60 | } 61 | if length == net.IPv4len { 62 | if n, err := dest.Write(inetAddr.To4()); err != nil { 63 | return fmt.Errorf("cannot write [inetaddr] IPv4 content: %w", err) 64 | } else if n < net.IPv4len { 65 | return errors.New("not enough capacity to write [inetaddr] IPv4 content") 66 | } 67 | } else { 68 | if n, err := dest.Write(inetAddr.To16()); err != nil { 69 | return fmt.Errorf("cannot write [inetaddr] IPv6 content: %w", err) 70 | } else if n < net.IPv6len { 71 | return errors.New("not enough capacity to write [inetaddr] IPv content") 72 | } 73 | } 74 | return nil 75 | } 76 | 77 | func LengthOfInetAddr(inetAddr net.IP) (length int, err error) { 78 | if inetAddr == nil { 79 | return -1, errors.New("cannot compute nil [inetaddr] length") 80 | } 81 | length = LengthOfByte 82 | if inetAddr.To4() != nil { 83 | length += net.IPv4len 84 | } else { 85 | length += net.IPv6len 86 | } 87 | return length, nil 88 | } 89 | -------------------------------------------------------------------------------- /primitive/integers.go: -------------------------------------------------------------------------------- 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 primitive 16 | 17 | import ( 18 | "encoding/binary" 19 | "fmt" 20 | "io" 21 | ) 22 | 23 | const ( 24 | LengthOfByte = 1 25 | LengthOfShort = 2 26 | LengthOfInt = 4 27 | LengthOfLong = 8 28 | ) 29 | 30 | // [byte] ([byte] is not defined in protocol specs but is used by other primitives) 31 | 32 | func ReadByte(source io.Reader) (decoded uint8, err error) { 33 | if err = binary.Read(source, binary.BigEndian, &decoded); err != nil { 34 | err = fmt.Errorf("cannot read [byte]: %w", err) 35 | } 36 | return decoded, err 37 | } 38 | 39 | func WriteByte(b uint8, dest io.Writer) error { 40 | if err := binary.Write(dest, binary.BigEndian, b); err != nil { 41 | return fmt.Errorf("cannot write [byte]: %w", err) 42 | } 43 | return nil 44 | } 45 | 46 | // [short] 47 | 48 | func ReadShort(source io.Reader) (decoded uint16, err error) { 49 | if err = binary.Read(source, binary.BigEndian, &decoded); err != nil { 50 | err = fmt.Errorf("cannot read [short]: %w", err) 51 | } 52 | return decoded, err 53 | } 54 | 55 | func WriteShort(i uint16, dest io.Writer) error { 56 | if err := binary.Write(dest, binary.BigEndian, i); err != nil { 57 | return fmt.Errorf("cannot write [short]: %w", err) 58 | } 59 | return nil 60 | } 61 | 62 | // [int] 63 | 64 | func ReadInt(source io.Reader) (decoded int32, err error) { 65 | if err = binary.Read(source, binary.BigEndian, &decoded); err != nil { 66 | err = fmt.Errorf("cannot read [int]: %w", err) 67 | } 68 | return decoded, err 69 | } 70 | 71 | func WriteInt(i int32, dest io.Writer) error { 72 | if err := binary.Write(dest, binary.BigEndian, i); err != nil { 73 | return fmt.Errorf("cannot write [int]: %w", err) 74 | } 75 | return nil 76 | } 77 | 78 | // [long] 79 | 80 | func ReadLong(source io.Reader) (decoded int64, err error) { 81 | if err = binary.Read(source, binary.BigEndian, &decoded); err != nil { 82 | err = fmt.Errorf("cannot read [long]: %w", err) 83 | } 84 | return decoded, err 85 | } 86 | 87 | func WriteLong(l int64, dest io.Writer) error { 88 | if err := binary.Write(dest, binary.BigEndian, l); err != nil { 89 | return fmt.Errorf("cannot write [long]: %w", err) 90 | } 91 | return nil 92 | } 93 | -------------------------------------------------------------------------------- /primitive/long_string.go: -------------------------------------------------------------------------------- 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 primitive 16 | 17 | import ( 18 | "errors" 19 | "fmt" 20 | "io" 21 | ) 22 | 23 | // [long string] 24 | 25 | func ReadLongString(source io.Reader) (string, error) { 26 | if length, err := ReadInt(source); err != nil { 27 | return "", fmt.Errorf("cannot read [long string] length: %w", err) 28 | } else if length <= 0 { 29 | return "", nil 30 | } else { 31 | decoded := make([]byte, length) 32 | if _, err := io.ReadFull(source, decoded); err != nil { 33 | return "", fmt.Errorf("cannot read [long string] content: %w", err) 34 | } 35 | return string(decoded), nil 36 | } 37 | } 38 | 39 | func WriteLongString(s string, dest io.Writer) error { 40 | length := len(s) 41 | if err := WriteInt(int32(length), dest); err != nil { 42 | return fmt.Errorf("cannot write [long string] length: %w", err) 43 | } else if n, err := dest.Write([]byte(s)); err != nil { 44 | return fmt.Errorf("cannot write [long string] length: %w", err) 45 | } else if n < length { 46 | return errors.New("not enough capacity to write [long string] content") 47 | } 48 | return nil 49 | } 50 | 51 | func LengthOfLongString(s string) int { 52 | return LengthOfInt + len(s) 53 | } 54 | -------------------------------------------------------------------------------- /primitive/long_string_test.go: -------------------------------------------------------------------------------- 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 primitive 16 | 17 | import ( 18 | "bytes" 19 | "errors" 20 | "fmt" 21 | "io/ioutil" 22 | "testing" 23 | 24 | "github.com/stretchr/testify/assert" 25 | ) 26 | 27 | func TestReadLongString(t *testing.T) { 28 | tests := []struct { 29 | name string 30 | source []byte 31 | expected string 32 | remaining []byte 33 | err error 34 | }{ 35 | {"simple string", []byte{0, 0, 0, 5, h, e, l, l, o}, "hello", []byte{}, nil}, 36 | {"string with remaining", []byte{0, 0, 0, 5, h, e, l, l, o, 1, 2, 3, 4}, "hello", []byte{1, 2, 3, 4}, nil}, 37 | {"empty string", []byte{0, 0, 0, 0}, "", []byte{}, nil}, 38 | {"non-ASCII string", []byte{ 39 | 0, 0, 0, 15, // length 40 | 0xce, 0xb3, 0xce, 0xb5, 0xce, 0xb9, 0xce, 0xac, //γειά 41 | 0x20, // space 42 | 0xcf, 0x83, 0xce, 0xbf, 0xcf, 0x85, // σου 43 | }, "γειά σου", []byte{}, nil}, 44 | { 45 | "cannot read length", 46 | []byte{0, 0, 0}, 47 | "", 48 | []byte{}, 49 | fmt.Errorf("cannot read [long string] length: %w", fmt.Errorf("cannot read [int]: %w", errors.New("unexpected EOF"))), 50 | }, 51 | { 52 | "cannot read string", 53 | []byte{0, 0, 0, 5, h, e, l, l}, 54 | "", 55 | []byte{}, 56 | fmt.Errorf("cannot read [long string] content: %w", errors.New("unexpected EOF")), 57 | }, 58 | } 59 | for _, tt := range tests { 60 | t.Run(tt.name, func(t *testing.T) { 61 | buf := bytes.NewReader(tt.source) 62 | actual, err := ReadLongString(buf) 63 | assert.Equal(t, tt.expected, actual) 64 | assert.Equal(t, tt.err, err) 65 | remaining, _ := ioutil.ReadAll(buf) 66 | assert.Equal(t, tt.remaining, remaining) 67 | }) 68 | } 69 | } 70 | 71 | func TestWriteLongString(t *testing.T) { 72 | tests := []struct { 73 | name string 74 | input string 75 | expected []byte 76 | err error 77 | }{ 78 | { 79 | "simple string", 80 | "hello", 81 | []byte{0, 0, 0, 5, h, e, l, l, o}, 82 | nil, 83 | }, 84 | { 85 | "empty string", 86 | "", 87 | []byte{0, 0, 0, 0}, 88 | nil, 89 | }, 90 | { 91 | "non-ASCII string", 92 | "γειά σου", 93 | []byte{ 94 | 0, 0, 0, 15, // length 95 | 0xce, 0xb3, 0xce, 0xb5, 0xce, 0xb9, 0xce, 0xac, //γειά 96 | 0x20, // space 97 | 0xcf, 0x83, 0xce, 0xbf, 0xcf, 0x85, // σου 98 | }, 99 | nil, 100 | }, 101 | } 102 | for _, tt := range tests { 103 | t.Run(tt.name, func(t *testing.T) { 104 | buf := &bytes.Buffer{} 105 | err := WriteLongString(tt.input, buf) 106 | assert.Equal(t, tt.expected, buf.Bytes()) 107 | assert.Equal(t, tt.err, err) 108 | }) 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /primitive/reasonmap.go: -------------------------------------------------------------------------------- 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 primitive 16 | 17 | import ( 18 | "fmt" 19 | "io" 20 | "net" 21 | ) 22 | 23 | // is a map of endpoint to failure reason codes, available from Protocol Version 5 onwards. 24 | // The map is encoded starting with an [int] n followed by n pairs of where 25 | // is an [inetaddr] and is a [short]. 26 | // Note: a reasonmap is inherently a map, but it is not modeled as a map in Go because [inetaddr] 27 | // is not a valid map key type. 28 | 29 | // FailureReason is a map entry for a ; it contains the endpoint that failed and the corresponding failure 30 | // code. 31 | // +k8s:deepcopy-gen=true 32 | type FailureReason struct { 33 | Endpoint net.IP 34 | Code FailureCode 35 | } 36 | 37 | func ReadReasonMap(source io.Reader) ([]*FailureReason, error) { 38 | if length, err := ReadInt(source); err != nil { 39 | return nil, fmt.Errorf("cannot read reason map length: %w", err) 40 | } else { 41 | reasonMap := make([]*FailureReason, length) 42 | for i := 0; i < int(length); i++ { 43 | if addr, err := ReadInetAddr(source); err != nil { 44 | return nil, fmt.Errorf("cannot read reason map key for element %d: %w", i, err) 45 | } else if code, err := ReadShort(source); err != nil { 46 | return nil, fmt.Errorf("cannot read reason map value for element %d: %w", i, err) 47 | } else if err := CheckValidFailureCode(FailureCode(code)); err != nil { 48 | return nil, err 49 | } else { 50 | reasonMap[i] = &FailureReason{addr, FailureCode(code)} 51 | } 52 | } 53 | return reasonMap, err 54 | } 55 | } 56 | 57 | func WriteReasonMap(reasonMap []*FailureReason, dest io.Writer) error { 58 | if err := WriteInt(int32(len(reasonMap)), dest); err != nil { 59 | return fmt.Errorf("cannot write reason map length: %w", err) 60 | } 61 | for i, reason := range reasonMap { 62 | if err := WriteInetAddr(reason.Endpoint, dest); err != nil { 63 | return fmt.Errorf("cannot write reason map key for element %d: %w", i, err) 64 | } else if err := CheckValidFailureCode(reason.Code); err != nil { 65 | return err 66 | } else if err = WriteShort(uint16(reason.Code), dest); err != nil { 67 | return fmt.Errorf("cannot write reason map value for element %d: %w", i, err) 68 | } 69 | } 70 | return nil 71 | } 72 | 73 | func LengthOfReasonMap(reasonMap []*FailureReason) (int, error) { 74 | length := LengthOfInt 75 | for i, reason := range reasonMap { 76 | if inetAddrLength, err := LengthOfInetAddr(reason.Endpoint); err != nil { 77 | return -1, fmt.Errorf("cannot compute length of reason map key for element %d: %w", i, err) 78 | } else { 79 | length += inetAddrLength + LengthOfShort 80 | } 81 | } 82 | return length, nil 83 | } 84 | -------------------------------------------------------------------------------- /primitive/short_bytes.go: -------------------------------------------------------------------------------- 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 primitive 16 | 17 | import ( 18 | "errors" 19 | "fmt" 20 | "io" 21 | ) 22 | 23 | // [short bytes] 24 | 25 | func ReadShortBytes(source io.Reader) ([]byte, error) { 26 | if length, err := ReadShort(source); err != nil { 27 | return nil, fmt.Errorf("cannot read [short bytes] length: %w", err) 28 | } else if length < 0 { 29 | return nil, nil 30 | } else if length == 0 { 31 | return []byte{}, nil 32 | } else { 33 | decoded := make([]byte, length) 34 | if _, err := io.ReadFull(source, decoded); err != nil { 35 | return nil, fmt.Errorf("cannot read [short bytes] content: %w", err) 36 | } 37 | return decoded, nil 38 | } 39 | } 40 | 41 | func WriteShortBytes(b []byte, dest io.Writer) error { 42 | length := len(b) 43 | if err := WriteShort(uint16(length), dest); err != nil { 44 | return fmt.Errorf("cannot write [short bytes] length: %w", err) 45 | } else if n, err := dest.Write(b); err != nil { 46 | return fmt.Errorf("cannot write [short bytes] content: %w", err) 47 | } else if n < length { 48 | return errors.New("not enough capacity to write [short bytes] content") 49 | } 50 | return nil 51 | } 52 | 53 | func LengthOfShortBytes(b []byte) int { 54 | return LengthOfShort + len(b) 55 | } 56 | -------------------------------------------------------------------------------- /primitive/short_bytes_test.go: -------------------------------------------------------------------------------- 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 primitive 16 | 17 | import ( 18 | "bytes" 19 | "errors" 20 | "fmt" 21 | "io/ioutil" 22 | "testing" 23 | 24 | "github.com/stretchr/testify/assert" 25 | ) 26 | 27 | func TestReadShortBytes(t *testing.T) { 28 | tests := []struct { 29 | name string 30 | source []byte 31 | expected []byte 32 | remaining []byte 33 | err error 34 | }{ 35 | {"empty short bytes", []byte{0, 0}, []byte{}, []byte{}, nil}, 36 | {"singleton short bytes", []byte{0, 1, 1}, []byte{1}, []byte{}, nil}, 37 | {"simple short bytes", []byte{0, 2, 1, 2}, []byte{1, 2}, []byte{}, nil}, 38 | { 39 | "cannot read short bytes length", 40 | []byte{0}, 41 | nil, 42 | []byte{}, 43 | fmt.Errorf("cannot read [short bytes] length: %w", fmt.Errorf("cannot read [short]: %w", errors.New("unexpected EOF"))), 44 | }, 45 | { 46 | "cannot read short bytes content", 47 | []byte{0, 2, 1}, 48 | nil, 49 | []byte{}, 50 | fmt.Errorf("cannot read [short bytes] content: %w", errors.New("unexpected EOF")), 51 | }, 52 | } 53 | for _, tt := range tests { 54 | t.Run(tt.name, func(t *testing.T) { 55 | buf := bytes.NewReader(tt.source) 56 | actual, err := ReadShortBytes(buf) 57 | assert.Equal(t, tt.expected, actual) 58 | assert.Equal(t, tt.err, err) 59 | remaining, _ := ioutil.ReadAll(buf) 60 | assert.Equal(t, tt.remaining, remaining) 61 | }) 62 | } 63 | } 64 | 65 | func TestWriteShortBytes(t *testing.T) { 66 | tests := []struct { 67 | name string 68 | input []byte 69 | expected []byte 70 | err error 71 | }{ 72 | { 73 | "empty short bytes", 74 | []byte{}, 75 | []byte{0, 0}, 76 | nil, 77 | }, 78 | // not officially allowed by the specs, but better safe than sorry 79 | { 80 | "nil short bytes", 81 | nil, 82 | []byte{0, 0}, 83 | nil, 84 | }, 85 | { 86 | "singleton short bytes", 87 | []byte{1}, 88 | []byte{0, 1, 1}, 89 | nil, 90 | }, 91 | { 92 | "simple short bytes", 93 | []byte{1, 2}, 94 | []byte{0, 2, 1, 2}, 95 | nil, 96 | }, 97 | } 98 | for _, tt := range tests { 99 | t.Run(tt.name, func(t *testing.T) { 100 | buf := &bytes.Buffer{} 101 | err := WriteShortBytes(tt.input, buf) 102 | assert.Equal(t, tt.expected, buf.Bytes()) 103 | assert.Equal(t, tt.err, err) 104 | }) 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /primitive/streamid.go: -------------------------------------------------------------------------------- 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 primitive 16 | 17 | import ( 18 | "fmt" 19 | "io" 20 | "math" 21 | ) 22 | 23 | // ReadStreamId reads a stream id from the given source, using the given version to determine if the stream id 24 | // is a 16-bit integer (versions 3+) or an 8-bit integer (versions 1 and 2). 25 | func ReadStreamId(source io.Reader, version ProtocolVersion) (int16, error) { 26 | if version >= ProtocolVersion3 { 27 | id, err := ReadShort(source) 28 | return int16(id), err 29 | } else { 30 | id, err := ReadByte(source) 31 | return int16(int8(id)), err 32 | } 33 | } 34 | 35 | // WriteStreamId writes the given stream id to the given destination, using the given version to determine if the 36 | // stream id is a 16-bit integer (versions 3+) or an 8-bit integer (versions 1 and 2). 37 | func WriteStreamId(streamId int16, dest io.Writer, version ProtocolVersion) error { 38 | if version >= ProtocolVersion3 { 39 | return WriteShort(uint16(streamId), dest) 40 | } else if streamId > math.MaxInt8 || streamId < math.MinInt8 { 41 | return fmt.Errorf("stream id out of range for %v: %v", version, streamId) 42 | } else { 43 | return WriteByte(uint8(streamId), dest) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /primitive/string.go: -------------------------------------------------------------------------------- 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 primitive 16 | 17 | import ( 18 | "errors" 19 | "fmt" 20 | "io" 21 | ) 22 | 23 | // [string] 24 | 25 | func ReadString(source io.Reader) (string, error) { 26 | if length, err := ReadShort(source); err != nil { 27 | return "", fmt.Errorf("cannot read [string] length: %w", err) 28 | } else if length <= 0 { 29 | return "", nil 30 | } else { 31 | decoded := make([]byte, length) 32 | if _, err := io.ReadFull(source, decoded); err != nil { 33 | return "", fmt.Errorf("cannot read [string] content: %w", err) 34 | } 35 | return string(decoded), nil 36 | } 37 | } 38 | 39 | func WriteString(s string, dest io.Writer) error { 40 | length := len(s) 41 | if err := WriteShort(uint16(length), dest); err != nil { 42 | return fmt.Errorf("cannot write [string] length: %w", err) 43 | } else if n, err := dest.Write([]byte(s)); err != nil { 44 | return fmt.Errorf("cannot write [string] length: %w", err) 45 | } else if n < length { 46 | return errors.New("not enough capacity to write [string] content") 47 | } 48 | return nil 49 | } 50 | 51 | func LengthOfString(s string) int { 52 | return LengthOfShort + len(s) 53 | } 54 | -------------------------------------------------------------------------------- /primitive/string_list.go: -------------------------------------------------------------------------------- 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 primitive 16 | 17 | import ( 18 | "fmt" 19 | "io" 20 | ) 21 | 22 | // [string list] 23 | 24 | func ReadStringList(source io.Reader) (decoded []string, err error) { 25 | var length uint16 26 | length, err = ReadShort(source) 27 | if err != nil { 28 | return nil, fmt.Errorf("cannot read [string list] length: %w", err) 29 | } 30 | 31 | if length < 0 { 32 | return nil, nil 33 | } else if length == 0 { 34 | return []string{}, nil 35 | } 36 | 37 | decoded = make([]string, length) 38 | for i := uint16(0); i < length; i++ { 39 | var str string 40 | str, err = ReadString(source) 41 | if err != nil { 42 | return nil, fmt.Errorf("cannot read [string list] element %d: %w", i, err) 43 | } 44 | decoded[i] = str 45 | } 46 | return decoded, nil 47 | } 48 | 49 | func WriteStringList(list []string, dest io.Writer) error { 50 | length := len(list) 51 | if err := WriteShort(uint16(length), dest); err != nil { 52 | return fmt.Errorf("cannot write [string list] length: %w", err) 53 | } 54 | for i, s := range list { 55 | if err := WriteString(s, dest); err != nil { 56 | return fmt.Errorf("cannot write [string list] element %d: %w", i, err) 57 | } 58 | } 59 | return nil 60 | } 61 | 62 | func LengthOfStringList(list []string) int { 63 | length := LengthOfShort 64 | for _, s := range list { 65 | length += LengthOfString(s) 66 | } 67 | return length 68 | } 69 | -------------------------------------------------------------------------------- /primitive/string_list_test.go: -------------------------------------------------------------------------------- 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 primitive 16 | 17 | import ( 18 | "bytes" 19 | "errors" 20 | "fmt" 21 | "io/ioutil" 22 | "testing" 23 | 24 | "github.com/stretchr/testify/assert" 25 | ) 26 | 27 | func TestReadStringList(t *testing.T) { 28 | tests := []struct { 29 | name string 30 | source []byte 31 | expected []string 32 | remaining []byte 33 | err error 34 | }{ 35 | {"empty string list", []byte{0, 0}, []string{}, []byte{}, nil}, 36 | {"singleton string list", []byte{ 37 | 0, 1, // length 38 | 0, 5, h, e, l, l, o, // hello 39 | }, []string{"hello"}, []byte{}, nil}, 40 | {"simple string list", []byte{ 41 | 0, 2, // length 42 | 0, 5, h, e, l, l, o, // hello 43 | 0, 5, w, o, r, l, d, // world 44 | }, []string{"hello", "world"}, []byte{}, nil}, 45 | {"empty elements", []byte{ 46 | 0, 2, // length 47 | 0, 0, // elt 1 48 | 0, 0, // elt 2 49 | }, []string{"", ""}, []byte{}, nil}, 50 | { 51 | "cannot read list length", 52 | []byte{0}, 53 | nil, 54 | []byte{}, 55 | fmt.Errorf("cannot read [string list] length: %w", fmt.Errorf("cannot read [short]: %w", errors.New("unexpected EOF"))), 56 | }, 57 | { 58 | "cannot read list element", 59 | []byte{0, 1, 0, 5, h, e, l, l}, 60 | nil, 61 | []byte{}, 62 | fmt.Errorf("cannot read [string list] element 0: %w", fmt.Errorf("cannot read [string] content: %w", errors.New("unexpected EOF"))), 63 | }, 64 | } 65 | for _, tt := range tests { 66 | t.Run(tt.name, func(t *testing.T) { 67 | buf := bytes.NewReader(tt.source) 68 | actual, err := ReadStringList(buf) 69 | assert.Equal(t, tt.expected, actual) 70 | assert.Equal(t, tt.err, err) 71 | remaining, _ := ioutil.ReadAll(buf) 72 | assert.Equal(t, tt.remaining, remaining) 73 | }) 74 | } 75 | } 76 | 77 | func TestWriteStringList(t *testing.T) { 78 | tests := []struct { 79 | name string 80 | input []string 81 | expected []byte 82 | err error 83 | }{ 84 | { 85 | "empty string list", 86 | []string{}, 87 | []byte{0, 0}, 88 | nil, 89 | }, 90 | { 91 | "nil string list", 92 | nil, 93 | []byte{0, 0}, 94 | nil, 95 | }, 96 | { 97 | "singleton string list", 98 | []string{"hello"}, 99 | []byte{ 100 | 0, 1, // length 101 | 0, 5, h, e, l, l, o, // hello 102 | }, 103 | nil, 104 | }, 105 | { 106 | "simple string list", 107 | []string{"hello", "world"}, 108 | []byte{ 109 | 0, 2, // length 110 | 0, 5, h, e, l, l, o, // hello 111 | 0, 5, w, o, r, l, d, // world 112 | }, 113 | nil, 114 | }, 115 | { 116 | "empty elements", 117 | []string{"", ""}, 118 | []byte{ 119 | 0, 2, // length 120 | 0, 0, // elt 1 121 | 0, 0, // elt 2 122 | }, 123 | nil, 124 | }, 125 | } 126 | for _, tt := range tests { 127 | t.Run(tt.name, func(t *testing.T) { 128 | buf := &bytes.Buffer{} 129 | err := WriteStringList(tt.input, buf) 130 | assert.Equal(t, tt.expected, buf.Bytes()) 131 | assert.Equal(t, tt.err, err) 132 | }) 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /primitive/string_map.go: -------------------------------------------------------------------------------- 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 primitive 16 | 17 | import ( 18 | "fmt" 19 | "io" 20 | ) 21 | 22 | // [string map] 23 | 24 | func ReadStringMap(source io.Reader) (map[string]string, error) { 25 | if length, err := ReadShort(source); err != nil { 26 | return nil, fmt.Errorf("cannot read [string map] length: %w", err) 27 | } else { 28 | decoded := make(map[string]string, length) 29 | for i := uint16(0); i < length; i++ { 30 | if key, err := ReadString(source); err != nil { 31 | return nil, fmt.Errorf("cannot read [string map] entry %d key: %w", i, err) 32 | } else if value, err := ReadString(source); err != nil { 33 | return nil, fmt.Errorf("cannot read [string map] entry %d value: %w", i, err) 34 | } else { 35 | decoded[key] = value 36 | } 37 | } 38 | return decoded, nil 39 | } 40 | } 41 | 42 | func WriteStringMap(m map[string]string, dest io.Writer) error { 43 | if err := WriteShort(uint16(len(m)), dest); err != nil { 44 | return fmt.Errorf("cannot write [string map] length: %w", err) 45 | } 46 | for key, value := range m { 47 | if err := WriteString(key, dest); err != nil { 48 | return fmt.Errorf("cannot write [string map] entry '%v' key: %w", key, err) 49 | } 50 | if err := WriteString(value, dest); err != nil { 51 | return fmt.Errorf("cannot write [string map] entry '%v' value: %w", key, err) 52 | } 53 | } 54 | return nil 55 | } 56 | 57 | func LengthOfStringMap(m map[string]string) int { 58 | length := LengthOfShort 59 | for key, value := range m { 60 | length += LengthOfString(key) + LengthOfString(value) 61 | } 62 | return length 63 | } 64 | -------------------------------------------------------------------------------- /primitive/string_multimap.go: -------------------------------------------------------------------------------- 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 primitive 16 | 17 | import ( 18 | "fmt" 19 | "io" 20 | ) 21 | 22 | // [string multimap] 23 | 24 | func ReadStringMultiMap(source io.Reader) (decoded map[string][]string, err error) { 25 | if length, err := ReadShort(source); err != nil { 26 | return nil, fmt.Errorf("cannot read [string multimap] length: %w", err) 27 | } else { 28 | decoded := make(map[string][]string, length) 29 | for i := uint16(0); i < length; i++ { 30 | if key, err := ReadString(source); err != nil { 31 | return nil, fmt.Errorf("cannot read [string multimap] entry %d key: %w", i, err) 32 | } else if value, err := ReadStringList(source); err != nil { 33 | return nil, fmt.Errorf("cannot read [string multimap] entry %d value: %w", i, err) 34 | } else { 35 | decoded[key] = value 36 | } 37 | } 38 | return decoded, nil 39 | } 40 | } 41 | 42 | func WriteStringMultiMap(m map[string][]string, dest io.Writer) error { 43 | if err := WriteShort(uint16(len(m)), dest); err != nil { 44 | return fmt.Errorf("cannot write [string multimap] length: %w", err) 45 | } 46 | for key, value := range m { 47 | if err := WriteString(key, dest); err != nil { 48 | return fmt.Errorf("cannot write [string multimap] entry '%v' key: %w", key, err) 49 | } 50 | if err := WriteStringList(value, dest); err != nil { 51 | return fmt.Errorf("cannot write [string multimap] entry '%v' value: %w", key, err) 52 | } 53 | } 54 | return nil 55 | } 56 | 57 | func LengthOfStringMultiMap(m map[string][]string) int { 58 | length := LengthOfShort 59 | for key, value := range m { 60 | length += LengthOfString(key) + LengthOfStringList(value) 61 | } 62 | return length 63 | } 64 | -------------------------------------------------------------------------------- /primitive/string_test.go: -------------------------------------------------------------------------------- 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 primitive 16 | 17 | import ( 18 | "bytes" 19 | "errors" 20 | "fmt" 21 | "io/ioutil" 22 | "testing" 23 | 24 | "github.com/stretchr/testify/assert" 25 | ) 26 | 27 | const ( 28 | d = byte('d') 29 | e = byte('e') 30 | h = byte('h') 31 | k = byte('k') 32 | l = byte('l') 33 | m = byte('m') 34 | n = byte('n') 35 | o = byte('o') 36 | r = byte('r') 37 | u = byte('u') 38 | w = byte('w') 39 | ) 40 | 41 | func TestReadString(t *testing.T) { 42 | tests := []struct { 43 | name string 44 | source []byte 45 | expected string 46 | remaining []byte 47 | err error 48 | }{ 49 | {"simple string", []byte{0, 5, h, e, l, l, o}, "hello", []byte{}, nil}, 50 | {"string with remaining", []byte{0, 5, h, e, l, l, o, 1, 2, 3, 4}, "hello", []byte{1, 2, 3, 4}, nil}, 51 | {"empty string", []byte{0, 0}, "", []byte{}, nil}, 52 | {"non-ASCII string", []byte{ 53 | 0, 15, // length 54 | 0xce, 0xb3, 0xce, 0xb5, 0xce, 0xb9, 0xce, 0xac, //γειά 55 | 0x20, // space 56 | 0xcf, 0x83, 0xce, 0xbf, 0xcf, 0x85, // σου 57 | }, "γειά σου", []byte{}, nil}, 58 | { 59 | "cannot read length", 60 | []byte{0}, 61 | "", 62 | []byte{}, 63 | fmt.Errorf("cannot read [string] length: %w", fmt.Errorf("cannot read [short]: %w", errors.New("unexpected EOF"))), 64 | }, 65 | { 66 | "cannot read string", 67 | []byte{0, 5, h, e, l, l}, 68 | "", 69 | []byte{}, 70 | fmt.Errorf("cannot read [string] content: %w", errors.New("unexpected EOF")), 71 | }, 72 | } 73 | for _, tt := range tests { 74 | t.Run(tt.name, func(t *testing.T) { 75 | buf := bytes.NewReader(tt.source) 76 | actual, err := ReadString(buf) 77 | assert.Equal(t, tt.expected, actual) 78 | assert.Equal(t, tt.err, err) 79 | remaining, _ := ioutil.ReadAll(buf) 80 | assert.Equal(t, tt.remaining, remaining) 81 | }) 82 | } 83 | } 84 | 85 | func TestWriteString(t *testing.T) { 86 | tests := []struct { 87 | name string 88 | input string 89 | expected []byte 90 | err error 91 | }{ 92 | { 93 | "simple string", 94 | "hello", 95 | []byte{0, 5, h, e, l, l, o}, 96 | nil, 97 | }, 98 | {"empty string", "", []byte{0, 0}, nil}, 99 | {"non-ASCII string", "γειά σου", []byte{ 100 | 0, 15, // length 101 | 0xce, 0xb3, 0xce, 0xb5, 0xce, 0xb9, 0xce, 0xac, //γειά 102 | 0x20, // space 103 | 0xcf, 0x83, 0xce, 0xbf, 0xcf, 0x85, // σου 104 | }, nil}, 105 | } 106 | for _, tt := range tests { 107 | t.Run(tt.name, func(t *testing.T) { 108 | buf := &bytes.Buffer{} 109 | err := WriteString(tt.input, buf) 110 | assert.Equal(t, tt.expected, buf.Bytes()) 111 | assert.Equal(t, tt.err, err) 112 | }) 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /primitive/uuid.go: -------------------------------------------------------------------------------- 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 primitive 16 | 17 | import ( 18 | "errors" 19 | "fmt" 20 | "io" 21 | ) 22 | 23 | // [uuid] 24 | 25 | const LengthOfUuid = 16 26 | 27 | type UUID [16]byte 28 | 29 | func (u *UUID) DeepCopy() *UUID { 30 | if u == nil { 31 | return nil 32 | } 33 | newUuid := *u 34 | return &newUuid 35 | } 36 | 37 | func (u *UUID) String() string { 38 | if u == nil { 39 | return "" 40 | } 41 | 42 | var offsets = [...]int{0, 2, 4, 6, 9, 11, 14, 16, 19, 21, 24, 26, 28, 30, 32, 34} 43 | const hexString = "0123456789abcdef" 44 | r := make([]byte, 36) 45 | for i, b := range u { 46 | r[offsets[i]] = hexString[b>>4] 47 | r[offsets[i]+1] = hexString[b&0xF] 48 | } 49 | r[8] = '-' 50 | r[13] = '-' 51 | r[18] = '-' 52 | r[23] = '-' 53 | return string(r) 54 | } 55 | 56 | // Bytes returns the raw byte slice for this UUID. A UUID is always 128 bits 57 | // (16 bytes) long. 58 | func (u *UUID) Bytes() []byte { 59 | return u[:] 60 | } 61 | 62 | // ParseUuid parses a 32 digit hexadecimal number (that might contain hyphens) 63 | // representing an UUID. 64 | func ParseUuid(input string) (*UUID, error) { 65 | var u UUID 66 | i := 0 67 | for _, r := range input { 68 | switch { 69 | case r == '-' && i&1 == 0: 70 | continue 71 | case r >= '0' && r <= '9' && i < 32: 72 | u[i/2] |= byte(r-'0') << uint(4-i&1*4) 73 | case r >= 'a' && r <= 'f' && i < 32: 74 | u[i/2] |= byte(r-'a'+10) << uint(4-i&1*4) 75 | case r >= 'A' && r <= 'F' && i < 32: 76 | u[i/2] |= byte(r-'A'+10) << uint(4-i&1*4) 77 | default: 78 | return nil, fmt.Errorf("invalid UUID: %q", input) 79 | } 80 | i += 1 81 | } 82 | if i != 32 { 83 | return nil, fmt.Errorf("invalid UUID: %q", input) 84 | } 85 | return &u, nil 86 | } 87 | 88 | func ReadUuid(source io.Reader) (*UUID, error) { 89 | decoded := new(UUID) 90 | if _, err := io.ReadFull(source, decoded[:]); err != nil { 91 | return nil, fmt.Errorf("cannot read [uuid] content: %w", err) 92 | } 93 | return decoded, nil 94 | } 95 | 96 | func WriteUuid(uuid *UUID, dest io.Writer) error { 97 | if uuid == nil { 98 | return errors.New("cannot write nil [uuid]") 99 | } else if n, err := dest.Write(uuid[:]); err != nil { 100 | return fmt.Errorf("cannot write [uuid] content: %w", err) 101 | } else if n < LengthOfUuid { 102 | return errors.New("not enough capacity to write [uuid] content") 103 | } 104 | return nil 105 | } 106 | -------------------------------------------------------------------------------- /primitive/uuid_test.go: -------------------------------------------------------------------------------- 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 primitive 16 | 17 | import ( 18 | "bytes" 19 | "errors" 20 | "fmt" 21 | "testing" 22 | 23 | "github.com/stretchr/testify/assert" 24 | ) 25 | 26 | var uuid = UUID{0xC0, 0xD1, 0xD2, 0x1E, 0xBB, 0x01, 0x41, 0x96, 0x86, 0xDB, 0xBC, 0x31, 0x7B, 0xC1, 0x79, 0x6A} 27 | var uuidBytes = [16]byte{0xC0, 0xD1, 0xD2, 0x1E, 0xBB, 0x01, 0x41, 0x96, 0x86, 0xDB, 0xBC, 0x31, 0x7B, 0xC1, 0x79, 0x6A} 28 | 29 | func TestReadUuid(t *testing.T) { 30 | tests := []struct { 31 | name string 32 | source []byte 33 | expected *UUID 34 | remaining []byte 35 | err error 36 | }{ 37 | {"simple UUID", uuidBytes[:], &uuid, []byte{}, nil}, 38 | {"UUID with remaining", append(uuidBytes[:], 1, 2, 3, 4), &uuid, []byte{1, 2, 3, 4}, nil}, 39 | { 40 | "cannot read UUID", 41 | uuidBytes[:15], 42 | nil, 43 | []byte{}, 44 | fmt.Errorf("cannot read [uuid] content: %w", errors.New("unexpected EOF")), 45 | }, 46 | } 47 | for _, tt := range tests { 48 | t.Run(tt.name, func(t *testing.T) { 49 | buf := bytes.NewBuffer(tt.source) 50 | actual, err := ReadUuid(buf) 51 | assert.Equal(t, tt.expected, actual) 52 | assert.Equal(t, tt.remaining, buf.Bytes()) 53 | assert.Equal(t, tt.err, err) 54 | }) 55 | } 56 | } 57 | 58 | func TestWriteUuid(t *testing.T) { 59 | tests := []struct { 60 | name string 61 | input *UUID 62 | expected []byte 63 | err error 64 | }{ 65 | { 66 | "simple UUID", 67 | &uuid, 68 | uuidBytes[:], 69 | nil, 70 | }, 71 | { 72 | "UUID with remaining", 73 | &uuid, 74 | uuidBytes[:], 75 | nil, 76 | }, 77 | { 78 | "nil UUID", 79 | nil, 80 | nil, 81 | errors.New("cannot write nil [uuid]"), 82 | }, 83 | } 84 | for _, tt := range tests { 85 | t.Run(tt.name, func(t *testing.T) { 86 | buf := &bytes.Buffer{} 87 | err := WriteUuid(tt.input, buf) 88 | assert.Equal(t, tt.expected, buf.Bytes()) 89 | assert.Equal(t, tt.err, err) 90 | }) 91 | } 92 | } 93 | 94 | func TestUUID_DeepCopy(t *testing.T) { 95 | u := &UUID{0, 1, 2, 3, 4, 5, 6} 96 | cloned := u.DeepCopy() 97 | 98 | assert.Equal(t, u, cloned) 99 | 100 | cloned[1] = 9 101 | assert.NotEqual(t, u, cloned) 102 | } 103 | -------------------------------------------------------------------------------- /segment/codec.go: -------------------------------------------------------------------------------- 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 segment 16 | 17 | import ( 18 | "io" 19 | ) 20 | 21 | const ( 22 | UncompressedHeaderLength = 3 23 | CompressedHeaderLength = 5 24 | ) 25 | 26 | const ( 27 | Crc24Length = 3 28 | Crc32Length = 4 29 | ) 30 | 31 | type Encoder interface { 32 | 33 | // EncodeSegment encodes the entire segment. 34 | EncodeSegment(segment *Segment, dest io.Writer) error 35 | } 36 | 37 | type Decoder interface { 38 | 39 | // DecodeSegment decodes the entire segment. 40 | DecodeSegment(source io.Reader) (*Segment, error) 41 | } 42 | 43 | // Codec exposes basic encoding and decoding operations for Segment instances. It should be the preferred interface to 44 | // use in typical client applications such as drivers. 45 | type Codec interface { 46 | Encoder 47 | Decoder 48 | } 49 | 50 | type codec struct { 51 | compressor PayloadCompressor 52 | } 53 | 54 | func NewCodec() Codec { 55 | return NewCodecWithCompression(nil) 56 | } 57 | 58 | func NewCodecWithCompression(compressor PayloadCompressor) Codec { 59 | return &codec{compressor: compressor} 60 | } 61 | -------------------------------------------------------------------------------- /segment/compressor.go: -------------------------------------------------------------------------------- 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 segment 16 | 17 | import ( 18 | "io" 19 | ) 20 | 21 | type PayloadCompressor interface { 22 | 23 | // Compress compresses the source, reading it fully, and writes the compressed result to dest. 24 | Compress(source io.Reader, dest io.Writer) error 25 | 26 | // Decompress decompresses the source, reading it fully, and writes the decompressed result to dest. 27 | Decompress(source io.Reader, dest io.Writer) error 28 | } 29 | -------------------------------------------------------------------------------- /segment/deepcopy_generated.go: -------------------------------------------------------------------------------- 1 | //go:build !ignore_autogenerated 2 | // +build !ignore_autogenerated 3 | 4 | // Copyright 2022 DataStax 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | 18 | // Code generated by deepcopy-gen. DO NOT EDIT. 19 | 20 | package segment 21 | 22 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 23 | func (in *Header) DeepCopyInto(out *Header) { 24 | *out = *in 25 | return 26 | } 27 | 28 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Header. 29 | func (in *Header) DeepCopy() *Header { 30 | if in == nil { 31 | return nil 32 | } 33 | out := new(Header) 34 | in.DeepCopyInto(out) 35 | return out 36 | } 37 | 38 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 39 | func (in *Payload) DeepCopyInto(out *Payload) { 40 | *out = *in 41 | if in.UncompressedData != nil { 42 | in, out := &in.UncompressedData, &out.UncompressedData 43 | *out = make([]byte, len(*in)) 44 | copy(*out, *in) 45 | } 46 | return 47 | } 48 | 49 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Payload. 50 | func (in *Payload) DeepCopy() *Payload { 51 | if in == nil { 52 | return nil 53 | } 54 | out := new(Payload) 55 | in.DeepCopyInto(out) 56 | return out 57 | } 58 | 59 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 60 | func (in *Segment) DeepCopyInto(out *Segment) { 61 | *out = *in 62 | if in.Header != nil { 63 | in, out := &in.Header, &out.Header 64 | *out = new(Header) 65 | **out = **in 66 | } 67 | if in.Payload != nil { 68 | in, out := &in.Payload, &out.Payload 69 | *out = new(Payload) 70 | (*in).DeepCopyInto(*out) 71 | } 72 | return 73 | } 74 | 75 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Segment. 76 | func (in *Segment) DeepCopy() *Segment { 77 | if in == nil { 78 | return nil 79 | } 80 | out := new(Segment) 81 | in.DeepCopyInto(out) 82 | return out 83 | } 84 | --------------------------------------------------------------------------------