diff --git a/internal/bsr/errors.go b/internal/bsr/errors.go index 143fe0cc9f..8d63d7cdd8 100644 --- a/internal/bsr/errors.go +++ b/internal/bsr/errors.go @@ -41,4 +41,7 @@ var ( // ErrChecksum indicates that a checksum did not match. ErrChecksum = errors.New("computed checksum did NOT match") + + // ErrTimestampDecode indicates an error decoding a timestamp + ErrTimestampDecode = errors.New("error decoding timestamp") ) diff --git a/internal/bsr/timestamp.go b/internal/bsr/timestamp.go index 259e5de029..7096dbdcd7 100644 --- a/internal/bsr/timestamp.go +++ b/internal/bsr/timestamp.go @@ -12,6 +12,8 @@ import ( const ( secondSize = 8 nanosecondSize = 4 + // Values over 999999999 are 1 second and over + nanosecondLimit = 999999999 timestampSize = secondSize + nanosecondSize ) @@ -52,6 +54,9 @@ func decodeTimestamp(data []byte) (*Timestamp, error) { seconds, data = binary.BigEndian.Uint64(data[:secondSize]), data[secondSize:] nanoseconds, data = binary.BigEndian.Uint32(data[:nanosecondSize]), data[nanosecondSize:] + if nanoseconds > nanosecondLimit { + return nil, fmt.Errorf("%s: nanosecond value of %d exceeds the max nanosecond value of %d: %w", op, nanoseconds, nanosecondLimit, ErrTimestampDecode) + } if len(data) != 0 { return nil, fmt.Errorf("%s: extra data", op) } diff --git a/internal/bsr/timestamp_test.go b/internal/bsr/timestamp_test.go new file mode 100644 index 0000000000..14fce37c33 --- /dev/null +++ b/internal/bsr/timestamp_test.go @@ -0,0 +1,62 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package bsr + +import ( + "encoding/binary" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_DecodeTimestamp(t *testing.T) { + cases := []struct { + name string + nanos uint32 + wantNanos int + wantErr bool + wantErrContains string + }{ + { + name: "valid case 1", + nanos: uint32(999999999), + wantNanos: 999999999, + }, + { + name: "valid case 2", + nanos: uint32(5), + wantNanos: 5, + }, + { + name: "nanosecond overflow case 1", + nanos: uint32(1000000000), + wantErr: true, + wantErrContains: "bsr.decodeTimestamp: nanosecond value of 1000000000 exceeds the max nanosecond value of 999999999: error decoding timestamp", + }, + { + name: "nanosecond overflow case 2", + nanos: uint32(2000000000), + wantErr: true, + wantErrContains: "bsr.decodeTimestamp: nanosecond value of 2000000000 exceeds the max nanosecond value of 999999999: error decoding timestamp", + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + data := make([]byte, timestampSize) + seconds := uint64(0) + binary.BigEndian.PutUint64(data[:secondSize], seconds) + binary.BigEndian.PutUint32(data[secondSize:], tc.nanos) + got, err := decodeTimestamp(data) + if tc.wantErr { + require.Error(t, err) + assert.EqualError(t, err, tc.wantErrContains) + return + } + require.NoError(t, err) + require.Equal(t, tc.wantNanos, got.AsTime().Nanosecond()) + }) + } +}