// Copyright 2022 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // Package protodelim marshals and unmarshals varint size-delimited messages. package protodelim import ( "bufio" "encoding/binary" "fmt" "io" "google.golang.org/protobuf/encoding/protowire" "google.golang.org/protobuf/internal/errors" "google.golang.org/protobuf/proto" ) // MarshalOptions is a configurable varint size-delimited marshaler. type MarshalOptions struct{ proto.MarshalOptions } // MarshalTo writes a varint size-delimited wire-format message to w. // If w returns an error, MarshalTo returns it unchanged. func (o MarshalOptions) MarshalTo(w io.Writer, m proto.Message) (int, error) { msgBytes, err := o.MarshalOptions.Marshal(m) if err != nil { return 0, err } sizeBytes := protowire.AppendVarint(nil, uint64(len(msgBytes))) sizeWritten, err := w.Write(sizeBytes) if err != nil { return sizeWritten, err } msgWritten, err := w.Write(msgBytes) if err != nil { return sizeWritten + msgWritten, err } return sizeWritten + msgWritten, nil } // MarshalTo writes a varint size-delimited wire-format message to w // with the default options. // // See the documentation for [MarshalOptions.MarshalTo]. func MarshalTo(w io.Writer, m proto.Message) (int, error) { return MarshalOptions{}.MarshalTo(w, m) } // UnmarshalOptions is a configurable varint size-delimited unmarshaler. type UnmarshalOptions struct { proto.UnmarshalOptions // MaxSize is the maximum size in wire-format bytes of a single message. // Unmarshaling a message larger than MaxSize will return an error. // A zero MaxSize will default to 4 MiB. // Setting MaxSize to -1 disables the limit. MaxSize int64 } const defaultMaxSize = 4 << 20 // 4 MiB, corresponds to the default gRPC max request/response size // SizeTooLargeError is an error that is returned when the unmarshaler encounters a message size // that is larger than its configured [UnmarshalOptions.MaxSize]. type SizeTooLargeError struct { // Size is the varint size of the message encountered // that was larger than the provided MaxSize. Size uint64 // MaxSize is the MaxSize limit configured in UnmarshalOptions, which Size exceeded. MaxSize uint64 } func (e *SizeTooLargeError) Error() string { return fmt.Sprintf("message size %d exceeded unmarshaler's maximum configured size %d", e.Size, e.MaxSize) } // Reader is the interface expected by [UnmarshalFrom]. // It is implemented by *[bufio.Reader]. type Reader interface { io.Reader io.ByteReader } // UnmarshalFrom parses and consumes a varint size-delimited wire-format message // from r. // The provided message must be mutable (e.g., a non-nil pointer to a message). // // The error is [io.EOF] error only if no bytes are read. // If an EOF happens after reading some but not all the bytes, // UnmarshalFrom returns a non-io.EOF error. // In particular if r returns a non-io.EOF error, UnmarshalFrom returns it unchanged, // and if only a size is read with no subsequent message, [io.ErrUnexpectedEOF] is returned. func (o UnmarshalOptions) UnmarshalFrom(r Reader, m proto.Message) error { var sizeArr [binary.MaxVarintLen64]byte sizeBuf := sizeArr[:0] for i := range sizeArr { b, err := r.ReadByte() if err != nil { // Immediate EOF is unexpected. if err == io.EOF && i != 0 { break } return err } sizeBuf = append(sizeBuf, b) if b < 0x80 { break } } size, n := protowire.ConsumeVarint(sizeBuf) if n < 0 { return protowire.ParseError(n) } maxSize := o.MaxSize if maxSize == 0 { maxSize = defaultMaxSize } if maxSize != -1 && size > uint64(maxSize) { return errors.Wrap(&SizeTooLargeError{Size: size, MaxSize: uint64(maxSize)}, "") } var b []byte var err error if br, ok := r.(*bufio.Reader); ok { // Use the []byte from the bufio.Reader instead of having to allocate one. // This reduces CPU usage and allocated bytes. b, err = br.Peek(int(size)) if err == nil { defer br.Discard(int(size)) } else { b = nil } } if b == nil { b = make([]byte, size) _, err = io.ReadFull(r, b) } if err == io.EOF { return io.ErrUnexpectedEOF } if err != nil { return err } if err := o.Unmarshal(b, m); err != nil { return err } return nil } // UnmarshalFrom parses and consumes a varint size-delimited wire-format message // from r with the default options. // The provided message must be mutable (e.g., a non-nil pointer to a message). // // See the documentation for [UnmarshalOptions.UnmarshalFrom]. func UnmarshalFrom(r Reader, m proto.Message) error { return UnmarshalOptions{}.UnmarshalFrom(r, m) }