Skip to content

Commit

Permalink
feat: add Seek() method to Decoder
Browse files Browse the repository at this point in the history
add new `Seek()` method (satisfying `io.Seeker`) to `Decoder` with associated tests
also add `Mode()` to expose the read mode (fast vs safe) to consumers

Issue: #136
  • Loading branch information
Dylan Bourque authored Jan 2, 2024
1 parent ae8034a commit 627ea6a
Show file tree
Hide file tree
Showing 2 changed files with 178 additions and 0 deletions.
33 changes: 33 additions & 0 deletions decoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,11 +66,44 @@ func NewDecoder(p []byte) *Decoder {
}
}

// Mode returns the current decoding mode, safe vs fastest.
func (d *Decoder) Mode() DecoderMode {
return d.mode
}

// SetMode configures the decoding behavior, safe vs fastest.
func (d *Decoder) SetMode(m DecoderMode) {
d.mode = m
}

// Seek sets the position of the next read operation to [offset], interpreted according to [whence]:
// [io.SeekStart] means relative to the start of the data, [io.SeekCurrent] means relative to the
// current offset, and [io.SeekEnd] means relative to the end.
//
// This low-level operation is provided to support advanced/custom usages of the decoder and it is up
// to the caller to ensure that the resulting offset will point to a valid location in the data stream.
func (d *Decoder) Seek(offset int64, whence int) (int64, error) {
pos := int(offset)
switch whence {
case io.SeekStart:
// no adjustment needed
case io.SeekCurrent:
// shift relative to current read offset
pos += d.offset
case io.SeekEnd:
// shift relative to EOF
pos += len(d.p)
default:
return int64(d.offset), fmt.Errorf("invalid value (%d) for whence", whence)
}
// verify bounds then update the read position
if pos < 0 || pos > len(d.p) {
return int64(d.offset), fmt.Errorf("seek position (%d) out of bounds", pos)
}
d.offset = pos
return int64(d.offset), nil
}

// Reset moves the read offset back to the beginning of the encoded data
func (d *Decoder) Reset() {
d.offset = 0
Expand Down
145 changes: 145 additions & 0 deletions decoder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,151 @@ import (
"github.com/CrowdStrike/csproto"
)

func TestDecoderSeek(t *testing.T) {
testData := []byte{0x08, 0x01, 0x10, 0x00, 0x1A, 0xE, 0x74, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x61, 0x20, 0x74, 0x65, 0x73, 0x74}
dec := csproto.NewDecoder(testData)

t.Run("seek start", func(t *testing.T) {
t.Run("negative seek", func(t *testing.T) {
dec.Reset()
_, _, _ = dec.DecodeTag()
_, _ = dec.DecodeBool()

startPos := int64(dec.Offset())
pos, err := dec.Seek(-1, io.SeekStart)
assert.Error(t, err, "cannot seek to before BOF")
assert.Equal(t, startPos, pos, "read position should not change")
})
t.Run("zero seek", func(t *testing.T) {
dec.Reset()
_, _, _ = dec.DecodeTag()
_, _ = dec.DecodeBool()

pos, err := dec.Seek(0, io.SeekStart)
assert.NoError(t, err)
assert.Equal(t, int64(0), pos, "new read position should be BOF")
})
t.Run("invalid positive seek", func(t *testing.T) {
dec.Reset()
_, _, _ = dec.DecodeTag()
_, _ = dec.DecodeBool()

startPos := int64(dec.Offset())
pos, err := dec.Seek(int64(len(testData)+1), io.SeekStart)
assert.Error(t, err, "cannot seek to after EOF")
assert.Equal(t, startPos, pos, "read position should not change")
})
t.Run("valid positive seek", func(t *testing.T) {
dec.Reset()

pos, err := dec.Seek(2, io.SeekStart)
assert.NoError(t, err)
assert.Equal(t, int(pos), dec.Offset())
assert.True(t, dec.More())
})
})
t.Run("seek current", func(t *testing.T) {
t.Run("invalid negative seek", func(t *testing.T) {
dec.Reset()
_, _, _ = dec.DecodeTag()
_, _ = dec.DecodeBool()

startPos := int64(dec.Offset())
pos, err := dec.Seek(-1*(startPos+1), io.SeekCurrent)
assert.Error(t, err, "cannot seek to before BOF")
assert.Equal(t, startPos, pos, "read position should not change")
})
t.Run("valid negative seek", func(t *testing.T) {
dec.Reset()
_, _, _ = dec.DecodeTag()
_, _ = dec.DecodeBool()

startPos := int64(dec.Offset())
pos, err := dec.Seek(-1*startPos, io.SeekCurrent)
assert.NoError(t, err)
assert.Equal(t, int64(0), pos)
})
t.Run("zero seek", func(t *testing.T) {
dec.Reset()
_, _, _ = dec.DecodeTag()
_, _ = dec.DecodeBool()

startPos := int64(dec.Offset())
pos, err := dec.Seek(0, io.SeekCurrent)
assert.NoError(t, err)
assert.Equal(t, int64(startPos), pos)
})
t.Run("invalid positive seek", func(t *testing.T) {
dec.Reset()
_, _, _ = dec.DecodeTag()
_, _ = dec.DecodeBool()

startPos := int64(dec.Offset())
pos, err := dec.Seek(int64(len(testData)), io.SeekCurrent)
assert.Error(t, err, "cannot seek to after EOF")
assert.Equal(t, startPos, pos, "read position should not change")
})
t.Run("valid positive seek", func(t *testing.T) {
dec.Reset()
_, _, _ = dec.DecodeTag()
_, _ = dec.DecodeBool()

pos, err := dec.Seek(2, io.SeekCurrent)
assert.NoError(t, err)
assert.Equal(t, int(pos), dec.Offset())
assert.True(t, dec.More())
})
})
t.Run("seek end", func(t *testing.T) {
t.Run("positive seek", func(t *testing.T) {
dec.Reset()
_, _, _ = dec.DecodeTag()
_, _ = dec.DecodeBool()
startPos := dec.Offset()

pos, err := dec.Seek(1, io.SeekEnd)
assert.Error(t, err, "cannot seek to after EOF")
assert.Equal(t, int64(startPos), pos, "read position should not change")
})
t.Run("zero seek", func(t *testing.T) {
dec.Reset()
_, _, _ = dec.DecodeTag()
_, _ = dec.DecodeBool()

pos, err := dec.Seek(0, io.SeekEnd)
assert.NoError(t, err)
assert.Equal(t, int64(len(testData)), pos, "read position should be at EOF")
assert.False(t, dec.More())
})
t.Run("invalid negative seek", func(t *testing.T) {
dec.Reset()
_, _, _ = dec.DecodeTag()
_, _ = dec.DecodeBool()
startPos := dec.Offset()

pos, err := dec.Seek(int64(-1*(len(testData)+1)), io.SeekEnd)
assert.Error(t, err, "cannot seek to before BOF")
assert.Equal(t, int64(startPos), pos, "read position should be at EOF")
})
t.Run("valid negative seek", func(t *testing.T) {
dec.Reset()
_, _, _ = dec.DecodeTag()
_, _ = dec.DecodeBool()

pos, err := dec.Seek(-16, io.SeekEnd)
assert.NoError(t, err)
assert.Equal(t, int64(4), pos, "read position should be at EOF")
})
})
t.Run("invalid whence", func(t *testing.T) {
dec.Reset()
startPos := dec.Offset()
pos, err := dec.Seek(0, 1138)
assert.Error(t, err, "cannot seek with invalid 'whence'")
assert.Equal(t, int64(startPos), pos, "read position should not change")
})
}

func TestDecodeBool(t *testing.T) {
cases := []struct {
name string
Expand Down

0 comments on commit 627ea6a

Please sign in to comment.