Skip to content

Commit 627ea6a

Browse files
author
Dylan Bourque
authored
feat: add Seek() method to Decoder
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
1 parent ae8034a commit 627ea6a

File tree

2 files changed

+178
-0
lines changed

2 files changed

+178
-0
lines changed

decoder.go

+33
Original file line numberDiff line numberDiff line change
@@ -66,11 +66,44 @@ func NewDecoder(p []byte) *Decoder {
6666
}
6767
}
6868

69+
// Mode returns the current decoding mode, safe vs fastest.
70+
func (d *Decoder) Mode() DecoderMode {
71+
return d.mode
72+
}
73+
6974
// SetMode configures the decoding behavior, safe vs fastest.
7075
func (d *Decoder) SetMode(m DecoderMode) {
7176
d.mode = m
7277
}
7378

79+
// Seek sets the position of the next read operation to [offset], interpreted according to [whence]:
80+
// [io.SeekStart] means relative to the start of the data, [io.SeekCurrent] means relative to the
81+
// current offset, and [io.SeekEnd] means relative to the end.
82+
//
83+
// This low-level operation is provided to support advanced/custom usages of the decoder and it is up
84+
// to the caller to ensure that the resulting offset will point to a valid location in the data stream.
85+
func (d *Decoder) Seek(offset int64, whence int) (int64, error) {
86+
pos := int(offset)
87+
switch whence {
88+
case io.SeekStart:
89+
// no adjustment needed
90+
case io.SeekCurrent:
91+
// shift relative to current read offset
92+
pos += d.offset
93+
case io.SeekEnd:
94+
// shift relative to EOF
95+
pos += len(d.p)
96+
default:
97+
return int64(d.offset), fmt.Errorf("invalid value (%d) for whence", whence)
98+
}
99+
// verify bounds then update the read position
100+
if pos < 0 || pos > len(d.p) {
101+
return int64(d.offset), fmt.Errorf("seek position (%d) out of bounds", pos)
102+
}
103+
d.offset = pos
104+
return int64(d.offset), nil
105+
}
106+
74107
// Reset moves the read offset back to the beginning of the encoded data
75108
func (d *Decoder) Reset() {
76109
d.offset = 0

decoder_test.go

+145
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,151 @@ import (
1111
"github.com/CrowdStrike/csproto"
1212
)
1313

14+
func TestDecoderSeek(t *testing.T) {
15+
testData := []byte{0x08, 0x01, 0x10, 0x00, 0x1A, 0xE, 0x74, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x61, 0x20, 0x74, 0x65, 0x73, 0x74}
16+
dec := csproto.NewDecoder(testData)
17+
18+
t.Run("seek start", func(t *testing.T) {
19+
t.Run("negative seek", func(t *testing.T) {
20+
dec.Reset()
21+
_, _, _ = dec.DecodeTag()
22+
_, _ = dec.DecodeBool()
23+
24+
startPos := int64(dec.Offset())
25+
pos, err := dec.Seek(-1, io.SeekStart)
26+
assert.Error(t, err, "cannot seek to before BOF")
27+
assert.Equal(t, startPos, pos, "read position should not change")
28+
})
29+
t.Run("zero seek", func(t *testing.T) {
30+
dec.Reset()
31+
_, _, _ = dec.DecodeTag()
32+
_, _ = dec.DecodeBool()
33+
34+
pos, err := dec.Seek(0, io.SeekStart)
35+
assert.NoError(t, err)
36+
assert.Equal(t, int64(0), pos, "new read position should be BOF")
37+
})
38+
t.Run("invalid positive seek", func(t *testing.T) {
39+
dec.Reset()
40+
_, _, _ = dec.DecodeTag()
41+
_, _ = dec.DecodeBool()
42+
43+
startPos := int64(dec.Offset())
44+
pos, err := dec.Seek(int64(len(testData)+1), io.SeekStart)
45+
assert.Error(t, err, "cannot seek to after EOF")
46+
assert.Equal(t, startPos, pos, "read position should not change")
47+
})
48+
t.Run("valid positive seek", func(t *testing.T) {
49+
dec.Reset()
50+
51+
pos, err := dec.Seek(2, io.SeekStart)
52+
assert.NoError(t, err)
53+
assert.Equal(t, int(pos), dec.Offset())
54+
assert.True(t, dec.More())
55+
})
56+
})
57+
t.Run("seek current", func(t *testing.T) {
58+
t.Run("invalid negative seek", func(t *testing.T) {
59+
dec.Reset()
60+
_, _, _ = dec.DecodeTag()
61+
_, _ = dec.DecodeBool()
62+
63+
startPos := int64(dec.Offset())
64+
pos, err := dec.Seek(-1*(startPos+1), io.SeekCurrent)
65+
assert.Error(t, err, "cannot seek to before BOF")
66+
assert.Equal(t, startPos, pos, "read position should not change")
67+
})
68+
t.Run("valid negative seek", func(t *testing.T) {
69+
dec.Reset()
70+
_, _, _ = dec.DecodeTag()
71+
_, _ = dec.DecodeBool()
72+
73+
startPos := int64(dec.Offset())
74+
pos, err := dec.Seek(-1*startPos, io.SeekCurrent)
75+
assert.NoError(t, err)
76+
assert.Equal(t, int64(0), pos)
77+
})
78+
t.Run("zero seek", func(t *testing.T) {
79+
dec.Reset()
80+
_, _, _ = dec.DecodeTag()
81+
_, _ = dec.DecodeBool()
82+
83+
startPos := int64(dec.Offset())
84+
pos, err := dec.Seek(0, io.SeekCurrent)
85+
assert.NoError(t, err)
86+
assert.Equal(t, int64(startPos), pos)
87+
})
88+
t.Run("invalid positive seek", func(t *testing.T) {
89+
dec.Reset()
90+
_, _, _ = dec.DecodeTag()
91+
_, _ = dec.DecodeBool()
92+
93+
startPos := int64(dec.Offset())
94+
pos, err := dec.Seek(int64(len(testData)), io.SeekCurrent)
95+
assert.Error(t, err, "cannot seek to after EOF")
96+
assert.Equal(t, startPos, pos, "read position should not change")
97+
})
98+
t.Run("valid positive seek", func(t *testing.T) {
99+
dec.Reset()
100+
_, _, _ = dec.DecodeTag()
101+
_, _ = dec.DecodeBool()
102+
103+
pos, err := dec.Seek(2, io.SeekCurrent)
104+
assert.NoError(t, err)
105+
assert.Equal(t, int(pos), dec.Offset())
106+
assert.True(t, dec.More())
107+
})
108+
})
109+
t.Run("seek end", func(t *testing.T) {
110+
t.Run("positive seek", func(t *testing.T) {
111+
dec.Reset()
112+
_, _, _ = dec.DecodeTag()
113+
_, _ = dec.DecodeBool()
114+
startPos := dec.Offset()
115+
116+
pos, err := dec.Seek(1, io.SeekEnd)
117+
assert.Error(t, err, "cannot seek to after EOF")
118+
assert.Equal(t, int64(startPos), pos, "read position should not change")
119+
})
120+
t.Run("zero seek", func(t *testing.T) {
121+
dec.Reset()
122+
_, _, _ = dec.DecodeTag()
123+
_, _ = dec.DecodeBool()
124+
125+
pos, err := dec.Seek(0, io.SeekEnd)
126+
assert.NoError(t, err)
127+
assert.Equal(t, int64(len(testData)), pos, "read position should be at EOF")
128+
assert.False(t, dec.More())
129+
})
130+
t.Run("invalid negative seek", func(t *testing.T) {
131+
dec.Reset()
132+
_, _, _ = dec.DecodeTag()
133+
_, _ = dec.DecodeBool()
134+
startPos := dec.Offset()
135+
136+
pos, err := dec.Seek(int64(-1*(len(testData)+1)), io.SeekEnd)
137+
assert.Error(t, err, "cannot seek to before BOF")
138+
assert.Equal(t, int64(startPos), pos, "read position should be at EOF")
139+
})
140+
t.Run("valid negative seek", func(t *testing.T) {
141+
dec.Reset()
142+
_, _, _ = dec.DecodeTag()
143+
_, _ = dec.DecodeBool()
144+
145+
pos, err := dec.Seek(-16, io.SeekEnd)
146+
assert.NoError(t, err)
147+
assert.Equal(t, int64(4), pos, "read position should be at EOF")
148+
})
149+
})
150+
t.Run("invalid whence", func(t *testing.T) {
151+
dec.Reset()
152+
startPos := dec.Offset()
153+
pos, err := dec.Seek(0, 1138)
154+
assert.Error(t, err, "cannot seek with invalid 'whence'")
155+
assert.Equal(t, int64(startPos), pos, "read position should not change")
156+
})
157+
}
158+
14159
func TestDecodeBool(t *testing.T) {
15160
cases := []struct {
16161
name string

0 commit comments

Comments
 (0)