mirror of https://github.com/hashicorp/boundary
parent
b73d4bd0d2
commit
658bb27eeb
@ -0,0 +1,111 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package bsr
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/hashicorp/boundary/internal/errors"
|
||||
)
|
||||
|
||||
// sizes
|
||||
const (
|
||||
lengthSize = 4
|
||||
protocolSize = 4
|
||||
chunkTypeSize = 4
|
||||
directionSize = 1
|
||||
|
||||
chunkBaseSize = lengthSize + protocolSize + chunkTypeSize + directionSize + timestampSize
|
||||
crcSize = 4
|
||||
)
|
||||
|
||||
// Chunk Types
|
||||
const (
|
||||
ChunkHeader ChunkType = "HEAD"
|
||||
ChunkEnd ChunkType = "DONE"
|
||||
)
|
||||
|
||||
// ChunkType identifies the type of a chunk.
|
||||
type ChunkType string
|
||||
|
||||
// ValidChunkType checks ifa given ChunkType is valid.
|
||||
func ValidChunkType(c ChunkType) bool {
|
||||
return len(c) <= chunkTypeSize
|
||||
}
|
||||
|
||||
// Chunk is a section of a bsr data file.
|
||||
type Chunk interface {
|
||||
// GetLength returns the length of the chunk data.
|
||||
GetLength() uint32
|
||||
// GetProtocol returns the protocol of the recorded data.
|
||||
GetProtocol() Protocol
|
||||
// GetType returns the chunk type.
|
||||
GetType() ChunkType
|
||||
// GetDirection returns the direction of the data in the chunk.
|
||||
GetDirection() Direction
|
||||
// GetTimestamp returns the timestamp of a Chunk.
|
||||
GetTimestamp() *Timestamp
|
||||
|
||||
// MarshalData serializes the data portion of a chunk.
|
||||
MarshalData(context.Context) ([]byte, error)
|
||||
}
|
||||
|
||||
// BaseChunk contains the common fields of all chunk types.
|
||||
type BaseChunk struct {
|
||||
Protocol Protocol
|
||||
Direction Direction
|
||||
Timestamp *Timestamp
|
||||
Type ChunkType
|
||||
|
||||
length uint32
|
||||
}
|
||||
|
||||
// NewBaseChunk creates a BaseChunk.
|
||||
func NewBaseChunk(ctx context.Context, p Protocol, d Direction, t *Timestamp, typ ChunkType) (*BaseChunk, error) {
|
||||
const op = "bsr.NewBaseChunk"
|
||||
if !ValidProtocol(p) {
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, "protocol name cannot be greater than 4 characters")
|
||||
}
|
||||
if !ValidDirection(d) {
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, "invalid direction")
|
||||
}
|
||||
if t == nil {
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, "timestamp must not be nil")
|
||||
}
|
||||
if !ValidChunkType(typ) {
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, "chunk type cannot be greater than 4 characters")
|
||||
}
|
||||
|
||||
return &BaseChunk{
|
||||
Protocol: p,
|
||||
Direction: d,
|
||||
Timestamp: t,
|
||||
Type: typ,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetLength returns the length of the chunk data.
|
||||
func (b *BaseChunk) GetLength() uint32 {
|
||||
return b.length
|
||||
}
|
||||
|
||||
// GetProtocol returns the protocol of the recorded data.
|
||||
func (b *BaseChunk) GetProtocol() Protocol {
|
||||
return b.Protocol
|
||||
}
|
||||
|
||||
// GetType returns the chunk type.
|
||||
func (b *BaseChunk) GetType() ChunkType {
|
||||
return b.Type
|
||||
}
|
||||
|
||||
// GetDirection returns the direction of the data in the chunk.
|
||||
func (b *BaseChunk) GetDirection() Direction {
|
||||
return b.Direction
|
||||
}
|
||||
|
||||
// GetTimestamp returns the timestamp of a Chunk.
|
||||
func (b *BaseChunk) GetTimestamp() *Timestamp {
|
||||
return b.Timestamp
|
||||
}
|
||||
@ -0,0 +1,39 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package bsr
|
||||
|
||||
import "context"
|
||||
|
||||
// EndChunk identifies the end of the chunks in a BSR data file.
|
||||
// An EndChunk in a bsr data file is represented as:
|
||||
//
|
||||
// uint32 length 4 bytes
|
||||
// uint32 protocol 4 bytes
|
||||
// uint32 chunk_type 4 bytes
|
||||
// uint8 direction 1 byte
|
||||
// timest timestamp 12 bytes
|
||||
// data 0 bytes
|
||||
// uint32 crc 4 bytes
|
||||
type EndChunk struct {
|
||||
*BaseChunk
|
||||
}
|
||||
|
||||
// MarshalData returns an empty byte slice.
|
||||
func (c *EndChunk) MarshalData(_ context.Context) ([]byte, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// NewEnd creates an EndChunk.
|
||||
func NewEnd(ctx context.Context, p Protocol, d Direction, t *Timestamp) (*EndChunk, error) {
|
||||
const op = "bsr.NewHeader"
|
||||
|
||||
bc, err := NewBaseChunk(ctx, p, d, t, ChunkEnd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &EndChunk{
|
||||
BaseChunk: bc,
|
||||
}, nil
|
||||
}
|
||||
@ -0,0 +1,128 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package bsr_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/boundary/internal/bsr"
|
||||
"github.com/hashicorp/boundary/internal/errors"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestNewEndChunk(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
now := time.Now()
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
p bsr.Protocol
|
||||
d bsr.Direction
|
||||
t *bsr.Timestamp
|
||||
want *bsr.EndChunk
|
||||
wantErr error
|
||||
}{
|
||||
{
|
||||
"valid-nocompression-noencrpytion",
|
||||
bsr.Protocol("TEST"),
|
||||
bsr.Inbound,
|
||||
bsr.NewTimestamp(now),
|
||||
&bsr.EndChunk{
|
||||
BaseChunk: &bsr.BaseChunk{
|
||||
Protocol: bsr.Protocol("TEST"),
|
||||
Direction: bsr.Inbound,
|
||||
Timestamp: bsr.NewTimestamp(now),
|
||||
Type: bsr.ChunkEnd,
|
||||
},
|
||||
},
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"valid-gzip-noencrpytion",
|
||||
bsr.Protocol("TEST"),
|
||||
bsr.Inbound,
|
||||
bsr.NewTimestamp(now),
|
||||
&bsr.EndChunk{
|
||||
BaseChunk: &bsr.BaseChunk{
|
||||
Protocol: bsr.Protocol("TEST"),
|
||||
Direction: bsr.Inbound,
|
||||
Timestamp: bsr.NewTimestamp(now),
|
||||
Type: bsr.ChunkEnd,
|
||||
},
|
||||
},
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"invalid-protocol",
|
||||
bsr.Protocol("TEST_INVALID"),
|
||||
bsr.Inbound,
|
||||
bsr.NewTimestamp(now),
|
||||
nil,
|
||||
errors.New(ctx, errors.InvalidParameter, "bsr.NewBaseChunk", "protocol name cannot be greater than 4 characters"),
|
||||
},
|
||||
{
|
||||
"invalid-direction",
|
||||
bsr.Protocol("TEST"),
|
||||
bsr.UnknownDirection,
|
||||
bsr.NewTimestamp(now),
|
||||
nil,
|
||||
errors.New(ctx, errors.InvalidParameter, "bsr.NewBaseChunk", "invalid direction"),
|
||||
},
|
||||
{
|
||||
"invalid-timestamp",
|
||||
bsr.Protocol("TEST"),
|
||||
bsr.Inbound,
|
||||
nil,
|
||||
nil,
|
||||
errors.New(ctx, errors.InvalidParameter, "bsr.NewBaseChunk", "timestamp must not be nil"),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
got, err := bsr.NewEnd(ctx, tc.p, tc.d, tc.t)
|
||||
if tc.wantErr != nil {
|
||||
assert.EqualError(t, tc.wantErr, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, tc.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestEndMarshalData(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
now := time.Now()
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
h *bsr.EndChunk
|
||||
want []byte
|
||||
}{
|
||||
{
|
||||
"nocompression-noencrpytion",
|
||||
&bsr.EndChunk{
|
||||
BaseChunk: &bsr.BaseChunk{
|
||||
Protocol: bsr.Protocol("TEST"),
|
||||
Direction: bsr.Inbound,
|
||||
Timestamp: bsr.NewTimestamp(now),
|
||||
Type: bsr.ChunkEnd,
|
||||
},
|
||||
},
|
||||
nil,
|
||||
},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
got, err := tc.h.MarshalData(ctx)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, tc.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,63 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package bsr
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/hashicorp/boundary/internal/errors"
|
||||
)
|
||||
|
||||
// HeaderChunk is the first chunk in a BSR data file.
|
||||
// A HeaderChunk in a bsr data file is represented as:
|
||||
//
|
||||
// uint32 length 4 bytes
|
||||
// uint32 protocol 4 bytes
|
||||
// uint32 chunk_type 4 bytes
|
||||
// uint8 direction 1 byte
|
||||
// timest timestamp 12 bytes
|
||||
// uint8 compression 1 byte
|
||||
// uint8 encryption 1 byte
|
||||
// session_id variable
|
||||
// uint32 crc 4 bytes
|
||||
type HeaderChunk struct {
|
||||
*BaseChunk
|
||||
Compression Compression
|
||||
Encryption Encryption
|
||||
SessionId string
|
||||
}
|
||||
|
||||
// MarshalData serializes a HeaderChunk.
|
||||
func (h *HeaderChunk) MarshalData(_ context.Context) ([]byte, error) {
|
||||
b := make([]byte, 0, len(h.SessionId)+compressionSize+encryptionSize)
|
||||
b = append(b, byte(h.Compression))
|
||||
b = append(b, byte(h.Encryption))
|
||||
b = append(b, []byte(h.SessionId)...)
|
||||
return b, nil
|
||||
}
|
||||
|
||||
// NewHeader creates a HeaderChunk.
|
||||
func NewHeader(ctx context.Context, p Protocol, d Direction, t *Timestamp, c Compression, e Encryption, sessionId string) (*HeaderChunk, error) {
|
||||
const op = "bsr.NewHeader"
|
||||
|
||||
bc, err := NewBaseChunk(ctx, p, d, t, ChunkHeader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !ValidCompression(c) {
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, "invalid compression")
|
||||
}
|
||||
|
||||
if !ValidEncryption(e) {
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, "invalid encryption")
|
||||
}
|
||||
|
||||
return &HeaderChunk{
|
||||
BaseChunk: bc,
|
||||
Compression: c,
|
||||
Encryption: e,
|
||||
SessionId: sessionId,
|
||||
}, nil
|
||||
}
|
||||
@ -0,0 +1,192 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package bsr_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/boundary/internal/bsr"
|
||||
"github.com/hashicorp/boundary/internal/errors"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestNewHeaderChunk(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
now := time.Now()
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
p bsr.Protocol
|
||||
d bsr.Direction
|
||||
t *bsr.Timestamp
|
||||
c bsr.Compression
|
||||
e bsr.Encryption
|
||||
sessionId string
|
||||
want *bsr.HeaderChunk
|
||||
wantErr error
|
||||
}{
|
||||
{
|
||||
"valid-nocompression-noencrpytion",
|
||||
bsr.Protocol("TEST"),
|
||||
bsr.Inbound,
|
||||
bsr.NewTimestamp(now),
|
||||
bsr.NoCompression,
|
||||
bsr.NoEncryption,
|
||||
"sess_123456789",
|
||||
&bsr.HeaderChunk{
|
||||
BaseChunk: &bsr.BaseChunk{
|
||||
Protocol: bsr.Protocol("TEST"),
|
||||
Direction: bsr.Inbound,
|
||||
Timestamp: bsr.NewTimestamp(now),
|
||||
Type: bsr.ChunkHeader,
|
||||
},
|
||||
Compression: bsr.NoCompression,
|
||||
Encryption: bsr.NoEncryption,
|
||||
SessionId: "sess_123456789",
|
||||
},
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"valid-gzip-noencrpytion",
|
||||
bsr.Protocol("TEST"),
|
||||
bsr.Inbound,
|
||||
bsr.NewTimestamp(now),
|
||||
bsr.GzipCompression,
|
||||
bsr.NoEncryption,
|
||||
"sess_123456789",
|
||||
&bsr.HeaderChunk{
|
||||
BaseChunk: &bsr.BaseChunk{
|
||||
Protocol: bsr.Protocol("TEST"),
|
||||
Direction: bsr.Inbound,
|
||||
Timestamp: bsr.NewTimestamp(now),
|
||||
Type: bsr.ChunkHeader,
|
||||
},
|
||||
Compression: bsr.GzipCompression,
|
||||
Encryption: bsr.NoEncryption,
|
||||
SessionId: "sess_123456789",
|
||||
},
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"invalid-protocol",
|
||||
bsr.Protocol("TEST_INVALID"),
|
||||
bsr.Inbound,
|
||||
bsr.NewTimestamp(now),
|
||||
bsr.NoCompression,
|
||||
bsr.NoEncryption,
|
||||
"sess_123456789",
|
||||
nil,
|
||||
errors.New(ctx, errors.InvalidParameter, "bsr.NewBaseChunk", "protocol name cannot be greater than 4 characters"),
|
||||
},
|
||||
{
|
||||
"invalid-direction",
|
||||
bsr.Protocol("TEST"),
|
||||
bsr.UnknownDirection,
|
||||
bsr.NewTimestamp(now),
|
||||
bsr.NoCompression,
|
||||
bsr.NoEncryption,
|
||||
"sess_123456789",
|
||||
nil,
|
||||
errors.New(ctx, errors.InvalidParameter, "bsr.NewBaseChunk", "invalid direction"),
|
||||
},
|
||||
{
|
||||
"invalid-timestamp",
|
||||
bsr.Protocol("TEST"),
|
||||
bsr.Inbound,
|
||||
nil,
|
||||
bsr.NoCompression,
|
||||
bsr.NoEncryption,
|
||||
"sess_123456789",
|
||||
nil,
|
||||
errors.New(ctx, errors.InvalidParameter, "bsr.NewBaseChunk", "timestamp must not be nil"),
|
||||
},
|
||||
{
|
||||
"invalid-compression",
|
||||
bsr.Protocol("TEST"),
|
||||
bsr.Inbound,
|
||||
bsr.NewTimestamp(now),
|
||||
bsr.Compression(255),
|
||||
bsr.NoEncryption,
|
||||
"sess_123456789",
|
||||
nil,
|
||||
errors.New(ctx, errors.InvalidParameter, "bsr.NewHeader", "invalid compression"),
|
||||
},
|
||||
{
|
||||
"invalid-encryption",
|
||||
bsr.Protocol("TEST"),
|
||||
bsr.Inbound,
|
||||
bsr.NewTimestamp(now),
|
||||
bsr.NoCompression,
|
||||
bsr.Encryption(255),
|
||||
"sess_123456789",
|
||||
nil,
|
||||
errors.New(ctx, errors.InvalidParameter, "bsr.NewHeader", "invalid encryption"),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
got, err := bsr.NewHeader(ctx, tc.p, tc.d, tc.t, tc.c, tc.e, tc.sessionId)
|
||||
if tc.wantErr != nil {
|
||||
assert.EqualError(t, tc.wantErr, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, tc.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestHeaderMarshalData(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
now := time.Now()
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
h *bsr.HeaderChunk
|
||||
want []byte
|
||||
}{
|
||||
{
|
||||
"nocompression-noencrpytion",
|
||||
&bsr.HeaderChunk{
|
||||
BaseChunk: &bsr.BaseChunk{
|
||||
Protocol: bsr.Protocol("TEST"),
|
||||
Direction: bsr.Inbound,
|
||||
Timestamp: bsr.NewTimestamp(now),
|
||||
Type: bsr.ChunkHeader,
|
||||
},
|
||||
Compression: bsr.NoCompression,
|
||||
Encryption: bsr.NoEncryption,
|
||||
SessionId: "sess_123456789",
|
||||
},
|
||||
[]byte("\x00\x00sess_123456789"),
|
||||
},
|
||||
{
|
||||
"gzip-noencrpytion",
|
||||
&bsr.HeaderChunk{
|
||||
BaseChunk: &bsr.BaseChunk{
|
||||
Protocol: bsr.Protocol("TEST"),
|
||||
Direction: bsr.Inbound,
|
||||
Timestamp: bsr.NewTimestamp(now),
|
||||
Type: bsr.ChunkHeader,
|
||||
},
|
||||
Compression: bsr.GzipCompression,
|
||||
Encryption: bsr.NoEncryption,
|
||||
SessionId: "sess_123456789",
|
||||
},
|
||||
[]byte("\x01\x00sess_123456789"),
|
||||
},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
got, err := tc.h.MarshalData(ctx)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, tc.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,100 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package bsr_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/boundary/internal/bsr"
|
||||
"github.com/hashicorp/boundary/internal/errors"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestNewBaseChunk(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
now := time.Now()
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
p bsr.Protocol
|
||||
d bsr.Direction
|
||||
t *bsr.Timestamp
|
||||
typ bsr.ChunkType
|
||||
want *bsr.BaseChunk
|
||||
wantErr error
|
||||
}{
|
||||
{
|
||||
"valid",
|
||||
bsr.Protocol("TEST"),
|
||||
bsr.Inbound,
|
||||
bsr.NewTimestamp(now),
|
||||
bsr.ChunkType("TEST"),
|
||||
&bsr.BaseChunk{
|
||||
Protocol: bsr.Protocol("TEST"),
|
||||
Direction: bsr.Inbound,
|
||||
Timestamp: bsr.NewTimestamp(now),
|
||||
Type: bsr.ChunkType("TEST"),
|
||||
},
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"invalid-protocol",
|
||||
bsr.Protocol("TEST_INVALID"),
|
||||
bsr.Inbound,
|
||||
bsr.NewTimestamp(now),
|
||||
bsr.ChunkType("TEST"),
|
||||
nil,
|
||||
errors.New(ctx, errors.InvalidParameter, "bsr.NewBaseChunk", "protocol name cannot be greater than 4 characters"),
|
||||
},
|
||||
{
|
||||
"invalid-direction",
|
||||
bsr.Protocol("TEST"),
|
||||
bsr.UnknownDirection,
|
||||
bsr.NewTimestamp(now),
|
||||
bsr.ChunkType("TEST"),
|
||||
nil,
|
||||
errors.New(ctx, errors.InvalidParameter, "bsr.NewBaseChunk", "invalid direction"),
|
||||
},
|
||||
{
|
||||
"invalid-timestamp",
|
||||
bsr.Protocol("TEST"),
|
||||
bsr.Inbound,
|
||||
nil,
|
||||
bsr.ChunkType("TEST"),
|
||||
nil,
|
||||
errors.New(ctx, errors.InvalidParameter, "bsr.NewBaseChunk", "timestamp must not be nil"),
|
||||
},
|
||||
{
|
||||
"invalid-chunk-type",
|
||||
bsr.Protocol("TEST"),
|
||||
bsr.Inbound,
|
||||
bsr.NewTimestamp(now),
|
||||
bsr.ChunkType("TEST_INVALID"),
|
||||
nil,
|
||||
errors.New(ctx, errors.InvalidParameter, "bsr.NewBaseChunk", "chunk type cannot be greater than 4 characters"),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
got, err := bsr.NewBaseChunk(ctx, tc.p, tc.d, tc.t, tc.typ)
|
||||
if tc.wantErr != nil {
|
||||
assert.EqualError(t, tc.wantErr, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, tc.want, got)
|
||||
|
||||
assert.Equal(t, tc.want.Protocol, got.GetProtocol())
|
||||
assert.Equal(t, tc.want.Direction, got.GetDirection())
|
||||
assert.Equal(t, tc.want.Timestamp, got.GetTimestamp())
|
||||
assert.Equal(t, tc.want.Type, got.GetType())
|
||||
assert.Equal(t, tc.want.GetLength(), got.GetLength())
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,54 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package bsr
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
)
|
||||
|
||||
const (
|
||||
compressionSize = 1
|
||||
)
|
||||
|
||||
// Compression is used to identify the compression used for the data in chunks.
|
||||
type Compression uint8
|
||||
|
||||
// Supported compression methods.
|
||||
const (
|
||||
NoCompression Compression = iota
|
||||
GzipCompression
|
||||
)
|
||||
|
||||
func (c Compression) String() string {
|
||||
switch c {
|
||||
case NoCompression:
|
||||
return "no compression"
|
||||
case GzipCompression:
|
||||
return "gzip"
|
||||
default:
|
||||
return "unknown compression"
|
||||
}
|
||||
}
|
||||
|
||||
// ValidCompression checks if a given Compression is valid.
|
||||
func ValidCompression(c Compression) bool {
|
||||
switch c {
|
||||
case NoCompression, GzipCompression:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type nullCompressionWriter struct {
|
||||
*bytes.Buffer
|
||||
}
|
||||
|
||||
func (w *nullCompressionWriter) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func newNullCompressionWriter(b *bytes.Buffer) io.WriteCloser {
|
||||
return &nullCompressionWriter{Buffer: b}
|
||||
}
|
||||
@ -0,0 +1,30 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package bsr
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestNullCompressionWriter(t *testing.T) {
|
||||
var buf bytes.Buffer
|
||||
var compressor io.WriteCloser
|
||||
|
||||
expect := []byte("uncompressed data")
|
||||
compressor = newNullCompressionWriter(&buf)
|
||||
|
||||
wrote, err := compressor.Write(expect)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, len(expect), wrote)
|
||||
|
||||
err = compressor.Close()
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, expect, buf.Bytes())
|
||||
}
|
||||
@ -0,0 +1,73 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package bsr_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/boundary/internal/bsr"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestValidCompression(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
in bsr.Compression
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
bsr.NoCompression.String(),
|
||||
bsr.NoCompression,
|
||||
true,
|
||||
},
|
||||
{
|
||||
bsr.GzipCompression.String(),
|
||||
bsr.GzipCompression,
|
||||
true,
|
||||
},
|
||||
{
|
||||
"something else",
|
||||
bsr.Compression(255),
|
||||
false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
got := bsr.ValidCompression(tc.in)
|
||||
assert.Equal(t, tc.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCompressionString(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
in bsr.Compression
|
||||
want string
|
||||
}{
|
||||
{
|
||||
bsr.NoCompression.String(),
|
||||
bsr.NoCompression,
|
||||
"no compression",
|
||||
},
|
||||
{
|
||||
bsr.GzipCompression.String(),
|
||||
bsr.GzipCompression,
|
||||
"gzip",
|
||||
},
|
||||
{
|
||||
"something else",
|
||||
bsr.Compression(255),
|
||||
"unknown compression",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
got := tc.in.String()
|
||||
assert.Equal(t, tc.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,35 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package bsr
|
||||
|
||||
// Direction identifies the directionality of the data captured
|
||||
// in the chunk.
|
||||
type Direction uint8
|
||||
|
||||
// Directions
|
||||
const (
|
||||
UnknownDirection Direction = iota
|
||||
Inbound
|
||||
Outbound
|
||||
)
|
||||
|
||||
func (d Direction) String() string {
|
||||
switch d {
|
||||
case Inbound:
|
||||
return "inbound"
|
||||
case Outbound:
|
||||
return "outbound"
|
||||
default:
|
||||
return "unknown direction"
|
||||
}
|
||||
}
|
||||
|
||||
// ValidDirection checks if a given Direction is valid.
|
||||
func ValidDirection(d Direction) bool {
|
||||
switch d {
|
||||
case Inbound, Outbound:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
@ -0,0 +1,83 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package bsr_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/boundary/internal/bsr"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestValidDirection(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
in bsr.Direction
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
bsr.Inbound.String(),
|
||||
bsr.Inbound,
|
||||
true,
|
||||
},
|
||||
{
|
||||
bsr.Outbound.String(),
|
||||
bsr.Outbound,
|
||||
true,
|
||||
},
|
||||
{
|
||||
bsr.UnknownDirection.String(),
|
||||
bsr.UnknownDirection,
|
||||
false,
|
||||
},
|
||||
{
|
||||
"something else",
|
||||
bsr.Direction(255),
|
||||
false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
got := bsr.ValidDirection(tc.in)
|
||||
assert.Equal(t, tc.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDirectionString(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
in bsr.Direction
|
||||
want string
|
||||
}{
|
||||
{
|
||||
bsr.Inbound.String(),
|
||||
bsr.Inbound,
|
||||
"inbound",
|
||||
},
|
||||
{
|
||||
bsr.Outbound.String(),
|
||||
bsr.Outbound,
|
||||
"outbound",
|
||||
},
|
||||
{
|
||||
bsr.UnknownDirection.String(),
|
||||
bsr.UnknownDirection,
|
||||
"unknown direction",
|
||||
},
|
||||
{
|
||||
"something else",
|
||||
bsr.Direction(255),
|
||||
"unknown direction",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
got := tc.in.String()
|
||||
assert.Equal(t, tc.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,7 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
/*
|
||||
Package bsr is used to read and write boundary session recordings.
|
||||
*/
|
||||
package bsr
|
||||
@ -0,0 +1,34 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package bsr
|
||||
|
||||
const (
|
||||
encryptionSize = 1
|
||||
)
|
||||
|
||||
// Encryption is used to identify the encryption used for the data in chunks.
|
||||
type Encryption uint8
|
||||
|
||||
// Supported encryption methods.
|
||||
const (
|
||||
NoEncryption Encryption = iota
|
||||
)
|
||||
|
||||
func (e Encryption) String() string {
|
||||
switch e {
|
||||
case NoEncryption:
|
||||
return "no encryption"
|
||||
default:
|
||||
return "unknown encryption"
|
||||
}
|
||||
}
|
||||
|
||||
// ValidEncryption checks if a given Encryption is valid.
|
||||
func ValidEncryption(e Encryption) bool {
|
||||
switch e {
|
||||
case NoEncryption:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
@ -0,0 +1,63 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package bsr_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/boundary/internal/bsr"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestValidEncrpytion(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
in bsr.Encryption
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
bsr.NoEncryption.String(),
|
||||
bsr.NoEncryption,
|
||||
true,
|
||||
},
|
||||
{
|
||||
"something else",
|
||||
bsr.Encryption(255),
|
||||
false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
got := bsr.ValidEncryption(tc.in)
|
||||
assert.Equal(t, tc.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestEncryptionString(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
in bsr.Encryption
|
||||
want string
|
||||
}{
|
||||
{
|
||||
bsr.NoEncryption.String(),
|
||||
bsr.NoEncryption,
|
||||
"no encryption",
|
||||
},
|
||||
{
|
||||
"something else",
|
||||
bsr.Encryption(255),
|
||||
"unknown encryption",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
got := tc.in.String()
|
||||
assert.Equal(t, tc.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,21 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package bsr
|
||||
|
||||
const (
|
||||
// Magic is the magic string / magic number / file signature used to
|
||||
// identify a BSR data file.
|
||||
//
|
||||
// See: https://en.wikipedia.org/wiki/File_format#Magic_number
|
||||
Magic magic = magic("\x89BSR\r\n\x1a\n")
|
||||
|
||||
magicSize = len(Magic)
|
||||
)
|
||||
|
||||
type magic string
|
||||
|
||||
// Bytes returns the magic as a []byte.
|
||||
func (s magic) Bytes() []byte {
|
||||
return []byte(s)
|
||||
}
|
||||
@ -0,0 +1,16 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package bsr_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/boundary/internal/bsr"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestMagic(t *testing.T) {
|
||||
assert.Equal(t, string(bsr.Magic), "\x89BSR\r\n\x1a\n")
|
||||
assert.Equal(t, bsr.Magic.Bytes(), []byte("\x89BSR\r\n\x1a\n"))
|
||||
}
|
||||
@ -0,0 +1,12 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package bsr
|
||||
|
||||
// Protocol identifies the protocol of the data captured in a chunk.
|
||||
type Protocol string
|
||||
|
||||
// ValidProtocol checks if a given Protocol is valid.
|
||||
func ValidProtocol(p Protocol) bool {
|
||||
return len(p) <= protocolSize
|
||||
}
|
||||
@ -0,0 +1,37 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package bsr_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/boundary/internal/bsr"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestValidProtocol(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
in bsr.Protocol
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
"Valid",
|
||||
bsr.Protocol("VALI"),
|
||||
true,
|
||||
},
|
||||
{
|
||||
"Invalid",
|
||||
bsr.Protocol("INVALID"),
|
||||
false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
got := bsr.ValidProtocol(tc.in)
|
||||
assert.Equal(t, tc.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,49 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package bsr
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
secondSize = 8
|
||||
nanosecondSize = 4
|
||||
|
||||
timestampSize = secondSize + nanosecondSize
|
||||
)
|
||||
|
||||
// Timestamp is a time.Time that can be marshaled/unmarshaled to/from a bsr data file.
|
||||
// A Timestamp in a bsr data file is represented as:
|
||||
//
|
||||
// uint64 seconds 8 bytes
|
||||
// uint32 nanoseconds 4 bytes
|
||||
//
|
||||
// Where seconds is the number of seconds since unix epoch (Jan 1, 1970 00:00:00)
|
||||
// and nanoseconds are the number of nanoseconds since the last second.
|
||||
// This means the BSR cannot have times earlier than unix epoch.
|
||||
type Timestamp time.Time
|
||||
|
||||
// NewTimestamp creates a Timestamp.
|
||||
func NewTimestamp(t time.Time) *Timestamp {
|
||||
tt := Timestamp(t)
|
||||
return &tt
|
||||
}
|
||||
|
||||
func (t *Timestamp) marshal() []byte {
|
||||
tt := time.Time(*t)
|
||||
seconds := uint64(tt.Unix())
|
||||
nanoseconds := uint32(tt.Nanosecond())
|
||||
|
||||
d := make([]byte, 0, timestampSize)
|
||||
d = binary.BigEndian.AppendUint64(d, seconds)
|
||||
d = binary.BigEndian.AppendUint32(d, nanoseconds)
|
||||
return d
|
||||
}
|
||||
|
||||
// AsTime returns a time.Time for a Timestamp.
|
||||
func (t *Timestamp) AsTime() time.Time {
|
||||
return time.Time(*t)
|
||||
}
|
||||
Loading…
Reference in new issue