Skip to content

Commit 6180d88

Browse files
authored
feat(go): implement binary reader/writer (#2986)
1 parent 707ab24 commit 6180d88

5 files changed

Lines changed: 959 additions & 0 deletions

File tree

Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
1+
// Licensed to the Apache Software Foundation (ASF) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The ASF licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
18+
package codec
19+
20+
import (
21+
"encoding"
22+
"encoding/binary"
23+
"fmt"
24+
"math"
25+
"runtime"
26+
)
27+
28+
// Reader is a cursor over a byte slice. The first out-of-bounds read sets err;
29+
// all subsequent reads are no-ops. Call Err() once after all reads to check.
30+
type Reader struct {
31+
p []byte
32+
pos int
33+
err error
34+
}
35+
36+
func NewReader(p []byte) *Reader {
37+
return &Reader{p: p}
38+
}
39+
40+
// overrun sets r.err to a descriptive error message including the caller's
41+
// file and line number.
42+
func (r *Reader) overrun(need int) {
43+
_, file, line, _ := runtime.Caller(2)
44+
r.err = fmt.Errorf(
45+
"reader: need %d bytes at offset %d, only %d remaining (%s:%d)",
46+
need, r.pos, len(r.p)-r.pos, file, line)
47+
}
48+
49+
func (r *Reader) U8() uint8 {
50+
if r.err != nil {
51+
return 0
52+
}
53+
if r.pos+1 > len(r.p) {
54+
r.overrun(1)
55+
return 0
56+
}
57+
v := r.p[r.pos]
58+
r.pos++
59+
return v
60+
}
61+
62+
func (r *Reader) U16() uint16 {
63+
if r.err != nil {
64+
return 0
65+
}
66+
if r.pos+2 > len(r.p) {
67+
r.overrun(2)
68+
return 0
69+
}
70+
v := binary.LittleEndian.Uint16(r.p[r.pos : r.pos+2])
71+
r.pos += 2
72+
return v
73+
}
74+
75+
func (r *Reader) U32() uint32 {
76+
if r.err != nil {
77+
return 0
78+
}
79+
if r.pos+4 > len(r.p) {
80+
r.overrun(4)
81+
return 0
82+
}
83+
v := binary.LittleEndian.Uint32(r.p[r.pos : r.pos+4])
84+
r.pos += 4
85+
return v
86+
}
87+
88+
func (r *Reader) U64() uint64 {
89+
if r.err != nil {
90+
return 0
91+
}
92+
if r.pos+8 > len(r.p) {
93+
r.overrun(8)
94+
return 0
95+
}
96+
v := binary.LittleEndian.Uint64(r.p[r.pos : r.pos+8])
97+
r.pos += 8
98+
return v
99+
}
100+
101+
func (r *Reader) F32() float32 {
102+
if r.err != nil {
103+
return 0
104+
}
105+
if r.pos+4 > len(r.p) {
106+
r.overrun(4)
107+
return 0
108+
}
109+
v := math.Float32frombits(binary.LittleEndian.Uint32(r.p[r.pos : r.pos+4]))
110+
r.pos += 4
111+
return v
112+
}
113+
114+
// str reads exactly n bytes and returns a copy as a string.
115+
func (r *Reader) str(n int) string {
116+
v := string(r.p[r.pos : r.pos+n])
117+
r.pos += n
118+
return v
119+
}
120+
121+
// raw reads exactly n bytes and returns a copy.
122+
func (r *Reader) raw(n int) []byte {
123+
v := make([]byte, n)
124+
copy(v, r.p[r.pos:r.pos+n])
125+
r.pos += n
126+
return v
127+
}
128+
129+
// Raw reads exactly n bytes and returns a copy.
130+
func (r *Reader) Raw(n int) []byte {
131+
if r.err != nil {
132+
return nil
133+
}
134+
if r.pos+n > len(r.p) {
135+
r.overrun(n)
136+
return nil
137+
}
138+
return r.raw(n)
139+
}
140+
141+
// Str reads exactly n bytes and returns a copy as a string. Use U8LenStr or
142+
// U32LenStr instead if the data is length-prefixed:
143+
//
144+
// [length: 1 byte][data: N bytes] → U8LenStr
145+
// [length: 4 bytes][data: N bytes] → U32LenStr
146+
func (r *Reader) Str(n int) string {
147+
if r.err != nil {
148+
return ""
149+
}
150+
if r.pos+n > len(r.p) {
151+
r.overrun(n)
152+
return ""
153+
}
154+
return r.str(n)
155+
}
156+
157+
// U32LenStr reads a length-prefixed string where the length is a 4-byte
158+
// little-endian unsigned integer.
159+
func (r *Reader) U32LenStr() string {
160+
if r.err != nil {
161+
return ""
162+
}
163+
if r.pos+4 > len(r.p) {
164+
r.overrun(4)
165+
return ""
166+
}
167+
n := int(binary.LittleEndian.Uint32(r.p[r.pos : r.pos+4]))
168+
r.pos += 4
169+
if r.pos+n > len(r.p) {
170+
r.overrun(n)
171+
return ""
172+
}
173+
return r.str(n)
174+
}
175+
176+
// U8LenStr reads a length-prefixed string where the length is a single byte.
177+
func (r *Reader) U8LenStr() string {
178+
if r.err != nil {
179+
return ""
180+
}
181+
if r.pos+1 > len(r.p) {
182+
r.overrun(1)
183+
return ""
184+
}
185+
n := int(r.p[r.pos])
186+
r.pos++
187+
if r.pos+n > len(r.p) {
188+
r.overrun(n)
189+
return ""
190+
}
191+
return r.str(n)
192+
}
193+
194+
// Obj reads n bytes and decodes them into v.
195+
func (r *Reader) Obj(n int, v encoding.BinaryUnmarshaler) {
196+
if r.err != nil {
197+
return
198+
}
199+
if r.pos+n > len(r.p) {
200+
r.overrun(n)
201+
return
202+
}
203+
err := v.UnmarshalBinary(r.raw(n))
204+
if err != nil {
205+
_, file, line, _ := runtime.Caller(1)
206+
r.err = fmt.Errorf("%w (%s:%d)", err, file, line)
207+
return
208+
}
209+
}
210+
211+
// Remaining returns the number of unread bytes.
212+
func (r *Reader) Remaining() int {
213+
return len(r.p) - r.pos
214+
}
215+
216+
// Err returns the first error encountered during reading, or nil.
217+
func (r *Reader) Err() error { return r.err }

0 commit comments

Comments
 (0)