blob: 2ef36bbcf92ac2166808590eb355fe7966198f1f [file] [log] [blame]
Abhay Kumara2ae5992025-11-10 14:02:24 +00001// Copyright 2022 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5// Package protodelim marshals and unmarshals varint size-delimited messages.
6package protodelim
7
8import (
9 "bufio"
10 "encoding/binary"
11 "fmt"
12 "io"
13
14 "google.golang.org/protobuf/encoding/protowire"
15 "google.golang.org/protobuf/internal/errors"
16 "google.golang.org/protobuf/proto"
17)
18
19// MarshalOptions is a configurable varint size-delimited marshaler.
20type MarshalOptions struct{ proto.MarshalOptions }
21
22// MarshalTo writes a varint size-delimited wire-format message to w.
23// If w returns an error, MarshalTo returns it unchanged.
24func (o MarshalOptions) MarshalTo(w io.Writer, m proto.Message) (int, error) {
25 msgBytes, err := o.MarshalOptions.Marshal(m)
26 if err != nil {
27 return 0, err
28 }
29
30 sizeBytes := protowire.AppendVarint(nil, uint64(len(msgBytes)))
31 sizeWritten, err := w.Write(sizeBytes)
32 if err != nil {
33 return sizeWritten, err
34 }
35 msgWritten, err := w.Write(msgBytes)
36 if err != nil {
37 return sizeWritten + msgWritten, err
38 }
39 return sizeWritten + msgWritten, nil
40}
41
42// MarshalTo writes a varint size-delimited wire-format message to w
43// with the default options.
44//
45// See the documentation for [MarshalOptions.MarshalTo].
46func MarshalTo(w io.Writer, m proto.Message) (int, error) {
47 return MarshalOptions{}.MarshalTo(w, m)
48}
49
50// UnmarshalOptions is a configurable varint size-delimited unmarshaler.
51type UnmarshalOptions struct {
52 proto.UnmarshalOptions
53
54 // MaxSize is the maximum size in wire-format bytes of a single message.
55 // Unmarshaling a message larger than MaxSize will return an error.
56 // A zero MaxSize will default to 4 MiB.
57 // Setting MaxSize to -1 disables the limit.
58 MaxSize int64
59}
60
61const defaultMaxSize = 4 << 20 // 4 MiB, corresponds to the default gRPC max request/response size
62
63// SizeTooLargeError is an error that is returned when the unmarshaler encounters a message size
64// that is larger than its configured [UnmarshalOptions.MaxSize].
65type SizeTooLargeError struct {
66 // Size is the varint size of the message encountered
67 // that was larger than the provided MaxSize.
68 Size uint64
69
70 // MaxSize is the MaxSize limit configured in UnmarshalOptions, which Size exceeded.
71 MaxSize uint64
72}
73
74func (e *SizeTooLargeError) Error() string {
75 return fmt.Sprintf("message size %d exceeded unmarshaler's maximum configured size %d", e.Size, e.MaxSize)
76}
77
78// Reader is the interface expected by [UnmarshalFrom].
79// It is implemented by *[bufio.Reader].
80type Reader interface {
81 io.Reader
82 io.ByteReader
83}
84
85// UnmarshalFrom parses and consumes a varint size-delimited wire-format message
86// from r.
87// The provided message must be mutable (e.g., a non-nil pointer to a message).
88//
89// The error is [io.EOF] error only if no bytes are read.
90// If an EOF happens after reading some but not all the bytes,
91// UnmarshalFrom returns a non-io.EOF error.
92// In particular if r returns a non-io.EOF error, UnmarshalFrom returns it unchanged,
93// and if only a size is read with no subsequent message, [io.ErrUnexpectedEOF] is returned.
94func (o UnmarshalOptions) UnmarshalFrom(r Reader, m proto.Message) error {
95 var sizeArr [binary.MaxVarintLen64]byte
96 sizeBuf := sizeArr[:0]
97 for i := range sizeArr {
98 b, err := r.ReadByte()
99 if err != nil {
100 // Immediate EOF is unexpected.
101 if err == io.EOF && i != 0 {
102 break
103 }
104 return err
105 }
106 sizeBuf = append(sizeBuf, b)
107 if b < 0x80 {
108 break
109 }
110 }
111 size, n := protowire.ConsumeVarint(sizeBuf)
112 if n < 0 {
113 return protowire.ParseError(n)
114 }
115
116 maxSize := o.MaxSize
117 if maxSize == 0 {
118 maxSize = defaultMaxSize
119 }
120 if maxSize != -1 && size > uint64(maxSize) {
121 return errors.Wrap(&SizeTooLargeError{Size: size, MaxSize: uint64(maxSize)}, "")
122 }
123
124 var b []byte
125 var err error
126 if br, ok := r.(*bufio.Reader); ok {
127 // Use the []byte from the bufio.Reader instead of having to allocate one.
128 // This reduces CPU usage and allocated bytes.
129 b, err = br.Peek(int(size))
130 if err == nil {
131 defer br.Discard(int(size))
132 } else {
133 b = nil
134 }
135 }
136 if b == nil {
137 b = make([]byte, size)
138 _, err = io.ReadFull(r, b)
139 }
140
141 if err == io.EOF {
142 return io.ErrUnexpectedEOF
143 }
144 if err != nil {
145 return err
146 }
147 if err := o.Unmarshal(b, m); err != nil {
148 return err
149 }
150 return nil
151}
152
153// UnmarshalFrom parses and consumes a varint size-delimited wire-format message
154// from r with the default options.
155// The provided message must be mutable (e.g., a non-nil pointer to a message).
156//
157// See the documentation for [UnmarshalOptions.UnmarshalFrom].
158func UnmarshalFrom(r Reader, m proto.Message) error {
159 return UnmarshalOptions{}.UnmarshalFrom(r, m)
160}