Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add Seek() method to Decoder #136

Merged
merged 1 commit into from
Jan 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.
wmorgan6796 marked this conversation as resolved.
Show resolved Hide resolved
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