// Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: BUSL-1.1 package bsr_test import ( "bytes" "context" "fmt" "io" "testing" "time" "github.com/hashicorp/boundary/internal/bsr" "github.com/hashicorp/go-kms-wrapping/v2/extras/crypto" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestNewChunkScanner(t *testing.T) { ctx := context.Background() cases := []struct { name string reader io.Reader opts []bsr.Option wantErr error }{ { "valid", bytes.NewBuffer([]byte( string(bsr.Magic) + "", )), nil, nil, }, { "valid-with-sha256sum", bytes.NewBuffer([]byte( string(bsr.Magic) + "", )), []bsr.Option{bsr.WithSha256Sum([]byte("797cf38f8e3efa3da3ae95449466ffd62cfe084abd1c1134f9e74474e79570f0"))}, nil, }, { "nil-reader", nil, []bsr.Option{bsr.WithSha256Sum([]byte("797cf38f8e3efa3da3ae95449466ffd62cfe084abd1c1134f9e74474e79570f0"))}, fmt.Errorf("bsr.NewChunkScanner: reader is nil: invalid parameter"), }, { "no-magic", bytes.NewBuffer([]byte("NOT_BSR_MAGIC")), nil, fmt.Errorf("bsr.NewChunkScanner: bsr.ReadMagic: invalid magic string"), }, { "not-enough-data", bytes.NewBuffer(bsr.Magic.Bytes()[:2]), nil, fmt.Errorf("bsr.NewChunkScanner: bsr.ReadMagic: crypto.(Sha256SumReader).Read: EOF"), }, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { _, err := bsr.NewChunkScanner(ctx, tc.reader, tc.opts...) if tc.wantErr != nil { require.EqualError(t, err, tc.wantErr.Error()) return } require.NoError(t, err) }) } } func TestChunkScanner(t *testing.T) { ctx := context.Background() ts := time.Date(2023, time.March, 16, 10, 47, 3, 14, time.UTC) cases := []struct { name string reader io.Reader opts []bsr.Option want bsr.Chunk wantErr error }{ { "header", bytes.NewBuffer([]byte( string(bsr.Magic) + "\x00\x00\x00\x10" + // length "TEST" + // protocol "HEAD" + // type "\x01" + // direction "\x00\x00\x00\x00\x64\x12\xf3\xa7" + // time seconds "\x00\x00\x00\x0e" + // time nanoseconds "\x00" + // compression method "\x00" + // encryption method "sess_123456789" + // data "\xbe\x4c\x7c\x20" + // crc "", )), nil, &bsr.HeaderChunk{ BaseChunk: &bsr.BaseChunk{ Protocol: "TEST", Direction: bsr.Inbound, Timestamp: bsr.NewTimestamp(ts), Type: bsr.ChunkHeader, }, Compression: bsr.NoCompression, Encryption: bsr.NoEncryption, SessionId: "sess_123456789", }, nil, }, { "checksum-eof", bytes.NewBuffer([]byte( string(bsr.Magic) + "\x00\x00\x00\x10" + // length "TEST" + // protocol "HEAD" + // type "\x01" + // direction "\x00\x00\x00\x00\x64\x12\xf3\xa7" + // time seconds "\x00\x00\x00\x0e" + // time nanoseconds "\x00" + // compression method "\x00" + // encryption method "sess_123456789" + // data "\xbe\x4c\x7c\x20" + // crc "", )), []bsr.Option{bsr.WithSha256Sum([]byte("foo"))}, &bsr.HeaderChunk{ BaseChunk: &bsr.BaseChunk{ Protocol: "TEST", Direction: bsr.Inbound, Timestamp: bsr.NewTimestamp(ts), Type: bsr.ChunkHeader, }, Compression: bsr.NoCompression, Encryption: bsr.NoEncryption, SessionId: "sess_123456789", }, nil, }, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { scanner, err := bsr.NewChunkScanner(ctx, tc.reader, tc.opts...) require.NoError(t, err) got, err := scanner.Scan(ctx) if tc.wantErr != nil { require.EqualError(t, err, tc.wantErr.Error()) return } require.Equal(t, tc.want, got) }) } } func TestChunkWalk(t *testing.T) { ctx := context.Background() ts := time.Date(2023, time.March, 16, 10, 47, 3, 14, time.UTC) headerTest := []byte( string(bsr.Magic) + "\x00\x00\x00\x10" + // length "TEST" + // protocol "HEAD" + // type "\x01" + // direction "\x00\x00\x00\x00\x64\x12\xf3\xa7" + // time seconds "\x00\x00\x00\x0e" + // time nanoseconds "\x00" + // compression method "\x00" + // encryption method "sess_123456789" + // data "\xbe\x4c\x7c\x20" + // crc "" + // test "\x00\x00\x00\x03" + // length "TEST" + // protocol "TEST" + // type "\x01" + // direction "\x00\x00\x00\x00\x64\x12\xf3\xa7" + // time seconds "\x00\x00\x00\x0e" + // time nanoseconds "foo" + // data "\xa4\x6e\x48\x70" + // crc "", ) headerTestEnd := append(headerTest, []byte( ""+ // end "\x00\x00\x00\x00"+ // length "TEST"+ // protocol "DONE"+ // type "\x01"+ // direction "\x00\x00\x00\x00\x64\x12\xf3\xa7"+ // time seconds "\x00\x00\x00\x13"+ // time nanoseconds "\x50\x91\xfe\x72"+ // crc "", )...) cases := []struct { name string reader io.Reader opts []bsr.Option f func(t *testing.T, got *[]bsr.Chunk) bsr.ChunkReadFunc want []bsr.Chunk wantErr error }{ { "no-chunk", bytes.NewBuffer([]byte( string(bsr.Magic) + "", )), nil, func(t *testing.T, _ *[]bsr.Chunk) bsr.ChunkReadFunc { return func(_ context.Context, c bsr.Chunk) error { assert.Fail(t, "read func should not be called") return nil } }, []bsr.Chunk{}, nil, }, { "header-test-end", bytes.NewBuffer(headerTestEnd), nil, func(t *testing.T, got *[]bsr.Chunk) bsr.ChunkReadFunc { return func(_ context.Context, c bsr.Chunk) error { *got = append(*got, c) return nil } }, []bsr.Chunk{ &bsr.HeaderChunk{ BaseChunk: &bsr.BaseChunk{ Protocol: "TEST", Direction: bsr.Inbound, Timestamp: bsr.NewTimestamp(ts), Type: bsr.ChunkHeader, }, Compression: bsr.NoCompression, Encryption: bsr.NoEncryption, SessionId: "sess_123456789", }, &testChunk{ BaseChunk: &bsr.BaseChunk{ Protocol: "TEST", Direction: bsr.Inbound, Timestamp: bsr.NewTimestamp(ts), Type: "TEST", }, Data: []byte("foo"), }, &bsr.EndChunk{ BaseChunk: &bsr.BaseChunk{ Protocol: "TEST", Direction: bsr.Inbound, Timestamp: bsr.NewTimestamp(ts.Add(time.Nanosecond * 5)), Type: bsr.ChunkEnd, }, }, }, nil, }, { "header-test-end-checksumed", bytes.NewBuffer(headerTestEnd), []bsr.Option{bsr.WithSha256Sum(func() []byte { b, err := crypto.Sha256Sum(ctx, bytes.NewBuffer(headerTestEnd), crypto.WithHexEncoding(true)) require.NoError(t, err) return b }())}, func(t *testing.T, got *[]bsr.Chunk) bsr.ChunkReadFunc { return func(_ context.Context, c bsr.Chunk) error { *got = append(*got, c) return nil } }, []bsr.Chunk{ &bsr.HeaderChunk{ BaseChunk: &bsr.BaseChunk{ Protocol: "TEST", Direction: bsr.Inbound, Timestamp: bsr.NewTimestamp(ts), Type: bsr.ChunkHeader, }, Compression: bsr.NoCompression, Encryption: bsr.NoEncryption, SessionId: "sess_123456789", }, &testChunk{ BaseChunk: &bsr.BaseChunk{ Protocol: "TEST", Direction: bsr.Inbound, Timestamp: bsr.NewTimestamp(ts), Type: "TEST", }, Data: []byte("foo"), }, &bsr.EndChunk{ BaseChunk: &bsr.BaseChunk{ Protocol: "TEST", Direction: bsr.Inbound, Timestamp: bsr.NewTimestamp(ts.Add(time.Nanosecond * 5)), Type: bsr.ChunkEnd, }, }, }, nil, }, { "header-test-no-end", bytes.NewBuffer(headerTest), nil, func(t *testing.T, got *[]bsr.Chunk) bsr.ChunkReadFunc { return func(_ context.Context, c bsr.Chunk) error { *got = append(*got, c) return nil } }, []bsr.Chunk{ &bsr.HeaderChunk{ BaseChunk: &bsr.BaseChunk{ Protocol: "TEST", Direction: bsr.Inbound, Timestamp: bsr.NewTimestamp(ts), Type: bsr.ChunkHeader, }, Compression: bsr.NoCompression, Encryption: bsr.NoEncryption, SessionId: "sess_123456789", }, &testChunk{ BaseChunk: &bsr.BaseChunk{ Protocol: "TEST", Direction: bsr.Inbound, Timestamp: bsr.NewTimestamp(ts), Type: "TEST", }, Data: []byte("foo"), }, }, nil, }, { "header-test-no-end-checksumed", bytes.NewBuffer(headerTest), []bsr.Option{bsr.WithSha256Sum(func() []byte { b, err := crypto.Sha256Sum(ctx, bytes.NewBuffer(headerTest), crypto.WithHexEncoding(true)) require.NoError(t, err) return b }())}, func(t *testing.T, got *[]bsr.Chunk) bsr.ChunkReadFunc { return func(_ context.Context, c bsr.Chunk) error { *got = append(*got, c) return nil } }, []bsr.Chunk{ &bsr.HeaderChunk{ BaseChunk: &bsr.BaseChunk{ Protocol: "TEST", Direction: bsr.Inbound, Timestamp: bsr.NewTimestamp(ts), Type: bsr.ChunkHeader, }, Compression: bsr.NoCompression, Encryption: bsr.NoEncryption, SessionId: "sess_123456789", }, &testChunk{ BaseChunk: &bsr.BaseChunk{ Protocol: "TEST", Direction: bsr.Inbound, Timestamp: bsr.NewTimestamp(ts), Type: "TEST", }, Data: []byte("foo"), }, }, nil, }, { "func-errors", bytes.NewBuffer([]byte( string(bsr.Magic) + "\x00\x00\x00\x10" + // length "TEST" + // protocol "HEAD" + // type "\x01" + // direction "\x00\x00\x00\x00\x64\x12\xf3\xa7" + // time seconds "\x00\x00\x00\x0e" + // time nanoseconds "\x00" + // compression method "\x00" + // encryption method "sess_123456789" + // data "\xbe\x4c\x7c\x20" + // crc "", )), nil, func(t *testing.T, got *[]bsr.Chunk) bsr.ChunkReadFunc { return func(_ context.Context, c bsr.Chunk) error { return fmt.Errorf("error in walk function") } }, nil, fmt.Errorf("bsr.ChunkWalk: error in walk function"), }, { "incorrect-checksum", bytes.NewBuffer(headerTestEnd), []bsr.Option{bsr.WithSha256Sum([]byte("f2ca1bb6c7e907d06dafe4687e579fce76b37e4e93b7605022da52e6ccc26fd2"))}, func(t *testing.T, got *[]bsr.Chunk) bsr.ChunkReadFunc { return func(_ context.Context, c bsr.Chunk) error { *got = append(*got, c) return nil } }, nil, fmt.Errorf("bsr.ChunkWalk: bsr.(ChunkScanner).Scan: computed checksum did NOT match"), }, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { scanner, err := bsr.NewChunkScanner(ctx, tc.reader, tc.opts...) require.NoError(t, err) got := make([]bsr.Chunk, 0, len(tc.want)) err = bsr.ChunkWalk(ctx, scanner, tc.f(t, &got)) if tc.wantErr != nil { require.EqualError(t, err, tc.wantErr.Error()) return } require.NoError(t, err) require.Equal(t, tc.want, got) }) } }