Skip to content

Commit 7c0096c

Browse files
committed
Turn this package into a wrapper for protobuf/encoding/protodelim
Since Go Protobuf v1.30.0, the protodelim package is available upstream. The only notable API difference is that protodelim does not return the number of bytes read, which is why I added the countingReader type to pbutil/decode.go.
1 parent 5a0f916 commit 7c0096c

File tree

5 files changed

+63
-84
lines changed

5 files changed

+63
-84
lines changed

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,5 @@ go 1.19
44

55
require (
66
github.com/google/go-cmp v0.5.9
7-
google.golang.org/protobuf v1.28.1
7+
google.golang.org/protobuf v1.31.0
88
)

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,5 @@ github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
44
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
55
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
66
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
7-
google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
8-
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
7+
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
8+
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=

pbutil/decode.go

Lines changed: 34 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,40 @@
1515
package pbutil
1616

1717
import (
18-
"encoding/binary"
19-
"errors"
2018
"io"
2119

20+
"google.golang.org/protobuf/encoding/protodelim"
2221
"google.golang.org/protobuf/proto"
2322
)
2423

25-
// TODO: Give error package name prefix in next minor release.
26-
var errInvalidVarint = errors.New("invalid varint32 encountered")
24+
type countingReader struct {
25+
r io.Reader
26+
n int
27+
}
28+
29+
// implements protodelim.Reader
30+
func (r *countingReader) Read(p []byte) (n int, err error) {
31+
n, err = r.r.Read(p)
32+
if n > 0 {
33+
r.n += n
34+
}
35+
return n, err
36+
}
37+
38+
// implements protodelim.Reader
39+
func (c *countingReader) ReadByte() (byte, error) {
40+
var buf [1]byte
41+
for {
42+
n, err := c.Read(buf[:])
43+
if n == 0 && err == nil {
44+
// io.Reader states: Callers should treat a return of 0 and nil as
45+
// indicating that nothing happened; in particular it does not
46+
// indicate EOF.
47+
continue
48+
}
49+
return buf[0], err
50+
}
51+
}
2752

2853
// ReadDelimited decodes a message from the provided length-delimited stream,
2954
// where the length is encoded as 32-bit varint prefix to the message body.
@@ -37,45 +62,10 @@ var errInvalidVarint = errors.New("invalid varint32 encountered")
3762
// of the stream has been reached in doing so. In that case, any subsequent
3863
// calls return (0, io.EOF).
3964
func ReadDelimited(r io.Reader, m proto.Message) (n int, err error) {
40-
// TODO: Consider allowing the caller to specify a decode buffer in the
41-
// next major version.
42-
43-
// TODO: Consider using error wrapping to annotate error state in pass-
44-
// through cases in the next minor version.
45-
46-
// Per AbstractParser#parsePartialDelimitedFrom with
47-
// CodedInputStream#readRawVarint32.
48-
var headerBuf [binary.MaxVarintLen32]byte
49-
var bytesRead, varIntBytes int
50-
var messageLength uint64
51-
for varIntBytes == 0 { // i.e. no varint has been decoded yet.
52-
if bytesRead >= len(headerBuf) {
53-
return bytesRead, errInvalidVarint
54-
}
55-
// We have to read byte by byte here to avoid reading more bytes
56-
// than required. Each read byte is appended to what we have
57-
// read before.
58-
newBytesRead, err := r.Read(headerBuf[bytesRead : bytesRead+1])
59-
if newBytesRead == 0 {
60-
if err != nil {
61-
return bytesRead, err
62-
}
63-
// A Reader should not return (0, nil); but if it does, it should
64-
// be treated as no-op according to the Reader contract.
65-
continue
66-
}
67-
bytesRead += newBytesRead
68-
// Now present everything read so far to the varint decoder and
69-
// see if a varint can be decoded already.
70-
messageLength, varIntBytes = binary.Uvarint(headerBuf[:bytesRead])
71-
}
72-
73-
messageBuf := make([]byte, messageLength)
74-
newBytesRead, err := io.ReadFull(r, messageBuf)
75-
bytesRead += newBytesRead
76-
if err != nil {
77-
return bytesRead, err
65+
cr := &countingReader{r: r}
66+
opts := protodelim.UnmarshalOptions{
67+
MaxSize: -1,
7868
}
79-
80-
return bytesRead, proto.Unmarshal(messageBuf, m)
69+
err = opts.UnmarshalFrom(cr, m)
70+
return cr.n, err
8171
}

pbutil/decode_test.go

Lines changed: 24 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ package pbutil
1616

1717
import (
1818
"bytes"
19+
"encoding/binary"
1920
"errors"
2021
"io"
2122
"testing"
@@ -29,29 +30,34 @@ import (
2930

3031
func TestReadDelimitedIllegalVarint(t *testing.T) {
3132
var tests = []struct {
32-
in []byte
33-
n int
34-
err error
33+
name string
34+
in []byte
35+
n int
3536
}{
3637
{
37-
in: []byte{255, 255, 255, 255, 255},
38-
n: 5,
39-
err: errInvalidVarint,
38+
name: "all 0xFF",
39+
in: []byte{255, 255, 255, 255, 255},
40+
n: 5,
4041
},
42+
43+
// Ensure ReadDelimited eventually stops parsing a varint instead of
44+
// looping as long as the input bytes have the continuation bit set.
4145
{
42-
in: []byte{255, 255, 255, 255, 255, 255},
43-
n: 5,
44-
err: errInvalidVarint,
46+
name: "infinite continuation bits",
47+
in: bytes.Repeat([]byte{255}, 2*binary.MaxVarintLen64),
48+
n: binary.MaxVarintLen64,
4549
},
4650
}
4751
for _, test := range tests {
48-
n, err := ReadDelimited(bytes.NewReader(test.in), nil)
49-
if got, want := n, test.n; !cmp.Equal(got, want) {
50-
t.Errorf("ReadDelimited(%#v, nil) = %#v, ?; want = %#v, ?", test.in, got, want)
51-
}
52-
if got, want := err, test.err; !errors.Is(got, want) {
53-
t.Errorf("ReadDelimited(%#v, nil) = ?, %#v; want = ?, %#v", test.in, got, want)
54-
}
52+
t.Run(test.name, func(t *testing.T) {
53+
n, err := ReadDelimited(bytes.NewReader(test.in), nil)
54+
if got, want := n, test.n; !cmp.Equal(got, want) {
55+
t.Errorf("ReadDelimited(%#v, nil) = %#v, ?; want = %#v, ?", test.in, got, want)
56+
}
57+
if err == nil {
58+
t.Errorf("ReadDelimited(%#v) unexpectedly did not result in an error", test.in)
59+
}
60+
})
5561
}
5662
}
5763

@@ -61,7 +67,7 @@ func TestReadDelimitedPrematureHeader(t *testing.T) {
6167
if got, want := n, 1; !cmp.Equal(got, want) {
6268
t.Errorf("ReadDelimited(%#v, nil) = %#v, ?; want = %#v, ?", data[0:1], got, want)
6369
}
64-
if got, want := err, io.EOF; !errors.Is(got, want) {
70+
if got, want := err, io.ErrUnexpectedEOF; !errors.Is(got, want) {
6571
t.Errorf("ReadDelimited(%#v, nil) = ?, %#v; want = ?, %#v", data[0:1], got, want)
6672
}
6773
}
@@ -83,7 +89,7 @@ func TestReadDelimitedPrematureHeaderIncremental(t *testing.T) {
8389
if got, want := n, 1; !cmp.Equal(got, want) {
8490
t.Errorf("ReadDelimited(%#v, nil) = %#v, ?; want = %#v, ?", data[0:1], got, want)
8591
}
86-
if got, want := err, io.EOF; !errors.Is(got, want) {
92+
if got, want := err, io.ErrUnexpectedEOF; !errors.Is(got, want) {
8793
t.Errorf("ReadDelimited(%#v, nil) = ?, %#v; want = ?, %#v", data[0:1], got, want)
8894
}
8995
}

pbutil/encode.go

Lines changed: 2 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,9 @@
1515
package pbutil
1616

1717
import (
18-
"encoding/binary"
1918
"io"
2019

20+
"google.golang.org/protobuf/encoding/protodelim"
2121
"google.golang.org/protobuf/proto"
2222
)
2323

@@ -28,22 +28,5 @@ import (
2828
// number of bytes written and any applicable error. This is roughly
2929
// equivalent to the companion Java API's MessageLite#writeDelimitedTo.
3030
func WriteDelimited(w io.Writer, m proto.Message) (n int, err error) {
31-
// TODO: Consider allowing the caller to specify an encode buffer in the
32-
// next major version.
33-
34-
buffer, err := proto.Marshal(m)
35-
if err != nil {
36-
return 0, err
37-
}
38-
39-
var buf [binary.MaxVarintLen32]byte
40-
encodedLength := binary.PutUvarint(buf[:], uint64(len(buffer)))
41-
42-
sync, err := w.Write(buf[:encodedLength])
43-
if err != nil {
44-
return sync, err
45-
}
46-
47-
n, err = w.Write(buffer)
48-
return n + sync, err
31+
return protodelim.MarshalTo(w, m)
4932
}

0 commit comments

Comments
 (0)