A ChunkDecoder can be used to BSR Chunks. Each call to Decode will read the next chunk from the decoder's io.Reader. It handles decompressing any compressed chunk data, and verifying the CRCs of each chunk.
470 lines
12 KiB
Go
470 lines
12 KiB
Go
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
package bsr_test
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"errors"
|
|
"io"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/hashicorp/boundary/internal/bsr"
|
|
)
|
|
|
|
func init() {
|
|
bsr.RegisterChunkType("TEST", "TEST", func(_ context.Context, bc *bsr.BaseChunk, data []byte) (bsr.Chunk, error) {
|
|
return &testChunk{
|
|
BaseChunk: bc,
|
|
Data: data,
|
|
}, nil
|
|
})
|
|
bsr.RegisterChunkType("TEST", "ERRR", func(_ context.Context, bc *bsr.BaseChunk, data []byte) (bsr.Chunk, error) {
|
|
return nil, errors.New("error in decode function")
|
|
})
|
|
}
|
|
|
|
func TestChunkDecoder(t *testing.T) {
|
|
ctx := context.Background()
|
|
|
|
ts := time.Date(2023, time.March, 16, 10, 47, 3, 14, time.UTC)
|
|
|
|
cases := []struct {
|
|
name string
|
|
encoded []byte
|
|
want []bsr.Chunk
|
|
}{
|
|
{
|
|
"header-no-compression",
|
|
[]byte(
|
|
"" + // so everything else aligns better
|
|
"\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.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",
|
|
},
|
|
},
|
|
},
|
|
{
|
|
"header-end-no-compression",
|
|
[]byte(
|
|
"" + // header
|
|
"\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
|
|
"" + // 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
|
|
"",
|
|
),
|
|
[]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",
|
|
},
|
|
&bsr.EndChunk{
|
|
BaseChunk: &bsr.BaseChunk{
|
|
Protocol: "TEST",
|
|
Direction: bsr.Inbound,
|
|
Timestamp: bsr.NewTimestamp(ts.Add(time.Nanosecond * 5)),
|
|
Type: bsr.ChunkEnd,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
"header-test-end-no-compression",
|
|
[]byte(
|
|
"" + // header
|
|
"\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
|
|
"" + // 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
|
|
"",
|
|
),
|
|
[]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,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
"header-test-end-gzip-compression",
|
|
[]byte(
|
|
"" + // header
|
|
"\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
|
|
"\x01" + // compression method
|
|
"\x00" + // encryption method
|
|
"sess_123456789" + // data
|
|
"\x10\x24\xed\xb1" + // crc
|
|
"" + // test
|
|
"\x00\x00\x00\x1b" + // length
|
|
"TEST" + // protocol
|
|
"TEST" + // type
|
|
"\x01" + // direction
|
|
"\x00\x00\x00\x00\x64\x12\xf3\xa7" + // time seconds
|
|
"\x00\x00\x00\x0e" + // time nanoseconds
|
|
gziped("foo") + // data
|
|
"\x29\x8e\x22\x12" + // crc
|
|
"" + // 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
|
|
"",
|
|
),
|
|
[]bsr.Chunk{
|
|
&bsr.HeaderChunk{
|
|
BaseChunk: &bsr.BaseChunk{
|
|
Protocol: "TEST",
|
|
Direction: bsr.Inbound,
|
|
Timestamp: bsr.NewTimestamp(ts),
|
|
Type: bsr.ChunkHeader,
|
|
},
|
|
Compression: bsr.GzipCompression,
|
|
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,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
buf := bytes.NewBuffer(tc.encoded)
|
|
dec, err := bsr.NewChunkDecoder(ctx, buf)
|
|
require.NoError(t, err)
|
|
|
|
got := make([]bsr.Chunk, 0, len(tc.want))
|
|
for {
|
|
c, err := dec.Decode(ctx)
|
|
if err == io.EOF {
|
|
break
|
|
}
|
|
require.NoError(t, err)
|
|
got = append(got, c)
|
|
}
|
|
|
|
assert.Equal(t, tc.want, got)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestNewChunkDecoderErrors(t *testing.T) {
|
|
ctx := context.Background()
|
|
cases := []struct {
|
|
name string
|
|
r io.Reader
|
|
want error
|
|
}{
|
|
{
|
|
"nil-reader",
|
|
nil,
|
|
errors.New("bsr.NewChunkDecoder: reader cannot be nil: invalid parameter"),
|
|
},
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
_, err := bsr.NewChunkDecoder(ctx, tc.r)
|
|
assert.EqualError(t, err, tc.want.Error())
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestChunkDecoderDecodeErrors(t *testing.T) {
|
|
ctx := context.Background()
|
|
cases := []struct {
|
|
name string
|
|
r io.Reader
|
|
want error
|
|
}{
|
|
{
|
|
"unknown-chunk-type",
|
|
bytes.NewBuffer([]byte(
|
|
"" + // test
|
|
"\x00\x00\x00\x03" + // length
|
|
"TEST" + // protocol
|
|
"UNKW" + // type
|
|
"\x01" + // direction
|
|
"\x00\x00\x00\x00\x64\x12\xf3\xa7" + // time seconds
|
|
"\x00\x00\x00\x0e" + // time nanoseconds
|
|
"foo" + // data
|
|
"\x01\x96\x41\x8a" + // crc
|
|
"",
|
|
)),
|
|
errors.New("bsr.(ChunkDecoder).Decode: unsupported chunk type UNKW for protocol TEST: error decoding chunk"),
|
|
},
|
|
{
|
|
"unknown-chunk-type-for-protocol",
|
|
bytes.NewBuffer([]byte(
|
|
"" + // test
|
|
"\x00\x00\x00\x03" + // length
|
|
"UNKW" + // protocol
|
|
"TEST" + // type
|
|
"\x01" + // direction
|
|
"\x00\x00\x00\x00\x64\x12\xf3\xa7" + // time seconds
|
|
"\x00\x00\x00\x0e" + // time nanoseconds
|
|
"foo" + // data
|
|
"\x38\xbd\xb9\xee" + // crc
|
|
"",
|
|
)),
|
|
errors.New("bsr.(ChunkDecoder).Decode: unsupported chunk type TEST for protocol UNKW: error decoding chunk"),
|
|
},
|
|
{
|
|
"chuck-missing-base-fields",
|
|
bytes.NewBuffer([]byte(
|
|
"" + // so everything else aligns better
|
|
"\x00\x00\x00\x10" + // length
|
|
"TEST" + // protocol
|
|
"HEAD" + // type
|
|
"",
|
|
)),
|
|
errors.New("bsr.(ChunkDecoder).Decode: unexpected EOF: error decoding chunk"),
|
|
},
|
|
{
|
|
"chuck-missing-data",
|
|
bytes.NewBuffer([]byte(
|
|
"" + // so everything else aligns better
|
|
"\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
|
|
"",
|
|
)),
|
|
errors.New("bsr.(ChunkDecoder).Decode: unexpected EOF: missing data: error decoding chunk"),
|
|
},
|
|
{
|
|
"chuck-partial-missing-data",
|
|
bytes.NewBuffer([]byte(
|
|
"" + // so everything else aligns better
|
|
"\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
|
|
"",
|
|
)),
|
|
errors.New("bsr.(ChunkDecoder).Decode: unexpected EOF: missing data: error decoding chunk"),
|
|
},
|
|
{
|
|
"chuck-missing-crc",
|
|
bytes.NewBuffer([]byte(
|
|
"" + // so everything else aligns better
|
|
"\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
|
|
"",
|
|
)),
|
|
errors.New("bsr.(ChunkDecoder).Decode: unexpected EOF: missing crc: error decoding chunk"),
|
|
},
|
|
{
|
|
"chuck-partial-missing-crc",
|
|
bytes.NewBuffer([]byte(
|
|
"" + // so everything else aligns better
|
|
"\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" + // crc
|
|
"",
|
|
)),
|
|
errors.New("bsr.(ChunkDecoder).Decode: unexpected EOF: missing crc: error decoding chunk"),
|
|
},
|
|
{
|
|
"chuck-crc-mismatch",
|
|
bytes.NewBuffer([]byte(
|
|
"" + // so everything else aligns better
|
|
"\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\x00" + // crc
|
|
"",
|
|
)),
|
|
errors.New("bsr.(ChunkDecoder).Decode: chunk crc did not match: error decoding chunk"),
|
|
},
|
|
{
|
|
"chuck-invalid-direction",
|
|
bytes.NewBuffer([]byte(
|
|
"" + // so everything else aligns better
|
|
"\x00\x00\x00\x10" + // length
|
|
"TEST" + // protocol
|
|
"HEAD" + // type
|
|
"\x00" + // 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
|
|
"\xdd\x4b\xa5\x04" + // crc
|
|
"",
|
|
)),
|
|
errors.New("bsr.(ChunkDecoder).Decode: bsr.NewBaseChunk: invalid direction: invalid parameter: error decoding chunk"),
|
|
},
|
|
{
|
|
"chuck-decode-function-error",
|
|
bytes.NewBuffer([]byte(
|
|
"" + // so everything else aligns better
|
|
"\x00\x00\x00\x03" + // length
|
|
"TEST" + // protocol
|
|
"ERRR" + // type
|
|
"\x01" + // direction
|
|
"\x00\x00\x00\x00\x64\x12\xf3\xa7" + // time seconds
|
|
"\x00\x00\x00\x0e" + // time nanoseconds
|
|
"foo" + // data
|
|
"\x30\xd5\x69\xbb" + // crc
|
|
"",
|
|
)),
|
|
errors.New("bsr.(ChunkDecoder).Decode: error in decode function: error decoding chunk"),
|
|
},
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
dec, err := bsr.NewChunkDecoder(ctx, tc.r)
|
|
require.NoError(t, err)
|
|
|
|
_, err = dec.Decode(ctx)
|
|
assert.EqualError(t, err, tc.want.Error())
|
|
})
|
|
}
|
|
}
|