| Abhay Kumar | a2ae599 | 2025-11-10 14:02:24 +0000 | [diff] [blame^] | 1 | // 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. |
| 6 | package protodelim |
| 7 | |
| 8 | import ( |
| 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. |
| 20 | type 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. |
| 24 | func (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]. |
| 46 | func 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. |
| 51 | type 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 | |
| 61 | const 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]. |
| 65 | type 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 | |
| 74 | func (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]. |
| 80 | type 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. |
| 94 | func (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]. |
| 158 | func UnmarshalFrom(r Reader, m proto.Message) error { |
| 159 | return UnmarshalOptions{}.UnmarshalFrom(r, m) |
| 160 | } |