github.com/mutagen-io/mutagen@v0.18.0-rc1/pkg/encoding/protobuf.go (about)

     1  package encoding
     2  
     3  import (
     4  	"encoding/binary"
     5  	"errors"
     6  	"fmt"
     7  	"io"
     8  
     9  	"google.golang.org/protobuf/encoding/protowire"
    10  	"google.golang.org/protobuf/proto"
    11  
    12  	"github.com/mutagen-io/mutagen/pkg/stream"
    13  )
    14  
    15  const (
    16  	// protobufEncoderInitialBufferSize is the initial buffer size for encoders.
    17  	protobufEncoderInitialBufferSize = 32 * 1024
    18  
    19  	// protobufEncoderMaximumPersistentBufferSize is the maximum buffer size
    20  	// that the encoder will keep allocated.
    21  	protobufEncoderMaximumPersistentBufferSize = 1024 * 1024
    22  
    23  	// protobufDecoderInitialBufferSize is the initial buffer size for decoders.
    24  	protobufDecoderInitialBufferSize = 32 * 1024
    25  
    26  	// protobufDecoderMaximumAllowedMessageSize is the maximum message size that
    27  	// we'll attempt to read from the wire.
    28  	protobufDecoderMaximumAllowedMessageSize = 100 * 1024 * 1024
    29  
    30  	// protobufDecoderMaximumPersistentBufferSize is the maximum buffer size
    31  	// that the decoder will keep allocated.
    32  	protobufDecoderMaximumPersistentBufferSize = 1024 * 1024
    33  )
    34  
    35  // LoadAndUnmarshalProtobuf loads data from the specified path and decodes it
    36  // into the specified Protocol Buffers message.
    37  func LoadAndUnmarshalProtobuf(path string, message proto.Message) error {
    38  	return LoadAndUnmarshal(path, func(data []byte) error {
    39  		return proto.Unmarshal(data, message)
    40  	})
    41  }
    42  
    43  // MarshalAndSaveProtobuf marshals the specified Protocol Buffers message and
    44  // saves it to the specified path.
    45  func MarshalAndSaveProtobuf(path string, message proto.Message) error {
    46  	return MarshalAndSave(path, func() ([]byte, error) {
    47  		return proto.Marshal(message)
    48  	})
    49  }
    50  
    51  // ProtobufEncoder is a stream encoder for Protocol Buffers messages.
    52  type ProtobufEncoder struct {
    53  	// writer is the underlying writer.
    54  	writer io.Writer
    55  	// buffer is a reusable encoding buffer.
    56  	buffer []byte
    57  	// sizer is a Protocol Buffers marshaling configuration for computing sizes.
    58  	sizer proto.MarshalOptions
    59  	// encoder is a Protocol Buffers marshaling configuration for encoding.
    60  	encoder proto.MarshalOptions
    61  }
    62  
    63  // NewProtobufEncoder creates a new Protocol Buffers stream encoder.
    64  func NewProtobufEncoder(writer io.Writer) *ProtobufEncoder {
    65  	return &ProtobufEncoder{
    66  		writer:  writer,
    67  		buffer:  make([]byte, 0, protobufEncoderInitialBufferSize),
    68  		sizer:   proto.MarshalOptions{},
    69  		encoder: proto.MarshalOptions{UseCachedSize: true},
    70  	}
    71  }
    72  
    73  // Encode encodes and writes a length-prefixed Protocol Buffers message to the
    74  // underlying stream. If this fails, the encoder should be considered corrupted.
    75  func (e *ProtobufEncoder) Encode(message proto.Message) error {
    76  	// Always make sure that the buffer's capacity stays within the limit of
    77  	// what we're willing to carry around once we're done.
    78  	defer func() {
    79  		if cap(e.buffer) > protobufEncoderMaximumPersistentBufferSize {
    80  			e.buffer = make([]byte, 0, protobufEncoderMaximumPersistentBufferSize)
    81  		} else {
    82  			e.buffer = e.buffer[:0]
    83  		}
    84  	}()
    85  
    86  	// Encode the message size.
    87  	e.buffer = protowire.AppendVarint(e.buffer, uint64(e.sizer.Size(message)))
    88  
    89  	// Encode the message.
    90  	var err error
    91  	e.buffer, err = e.encoder.MarshalAppend(e.buffer, message)
    92  	if err != nil {
    93  		return fmt.Errorf("unable to marshal message: %w", err)
    94  	}
    95  
    96  	// Write the data to the wire.
    97  	if _, err = e.writer.Write(e.buffer); err != nil {
    98  		return fmt.Errorf("unable to write message: %w", err)
    99  	}
   100  
   101  	// Success.
   102  	return nil
   103  }
   104  
   105  // ProtobufDecoder is a stream decoder for Protocol Buffers messages.
   106  type ProtobufDecoder struct {
   107  	// reader is the underlying reader.
   108  	reader stream.DualModeReader
   109  	// buffer is a reusable receive buffer for decoding messages.
   110  	buffer []byte
   111  }
   112  
   113  // NewProtobufDecoder creates a new Protocol Buffers stream decoder.
   114  func NewProtobufDecoder(reader stream.DualModeReader) *ProtobufDecoder {
   115  	return &ProtobufDecoder{
   116  		reader: reader,
   117  		buffer: make([]byte, protobufDecoderInitialBufferSize),
   118  	}
   119  }
   120  
   121  // bufferWithSize returns a buffer with the specified size, opting to reuse a
   122  // cached buffer if possible.
   123  func (d *ProtobufDecoder) bufferWithSize(size int) []byte {
   124  	// If we can satisfy this request with our existing buffer, then use that.
   125  	if cap(d.buffer) >= size {
   126  		return d.buffer[:size]
   127  	}
   128  
   129  	// Otherwise allocate a new buffer.
   130  	result := make([]byte, size)
   131  
   132  	// If this buffer doesn't exceed the maximum size that we're willing to keep
   133  	// around in memory, then store it.
   134  	if size <= protobufDecoderMaximumPersistentBufferSize {
   135  		d.buffer = result
   136  	}
   137  
   138  	// Done.
   139  	return result
   140  }
   141  
   142  // Decode decodes a length-prefixed Protocol Buffers message from the underlying
   143  // stream. If this fails, the decoder should be considered corrupted.
   144  func (d *ProtobufDecoder) Decode(message proto.Message) error {
   145  	// Read the next message length.
   146  	length, err := binary.ReadUvarint(d.reader)
   147  	if err != nil {
   148  		return fmt.Errorf("unable to read message length: %w", err)
   149  	}
   150  
   151  	// Check if the message is too long to read.
   152  	if length > protobufDecoderMaximumAllowedMessageSize {
   153  		return errors.New("message size too large")
   154  	}
   155  
   156  	// Grab a buffer to read the message.
   157  	messageBytes := d.bufferWithSize(int(length))
   158  
   159  	// Read the message bytes.
   160  	if _, err := io.ReadFull(d.reader, messageBytes); err != nil {
   161  		return fmt.Errorf("unable to read message: %w", err)
   162  	}
   163  
   164  	// Unmarshal the message.
   165  	if err := proto.Unmarshal(messageBytes, message); err != nil {
   166  		return fmt.Errorf("unable to unmarshal message: %w", err)
   167  	}
   168  
   169  	// Success.
   170  	return nil
   171  }
   172  
   173  // EncodeProtobuf encodes a single Protocol Buffers message that can be read by
   174  // ProtobufDecoder or DecodeProtobuf. It is a useful shorthand for creating a
   175  // ProtobufEncoder and writing a single message. For multiple message sends, it
   176  // is far more efficient to use a ProtobufEncoder directly and repeatedly.
   177  func EncodeProtobuf(writer io.Writer, message proto.Message) error {
   178  	return NewProtobufEncoder(writer).Encode(message)
   179  }
   180  
   181  // DecodeProtobuf reads and decodes a single Protocol Buffers message as written
   182  // by ProtobufEncoder or EncodeProtobuf. It is a useful shorthand for creating a
   183  // ProtobufDecoder and reading a single message. For multiple message reads, it
   184  // is far more efficient to use a ProtobufDecoder directly and repeatedly.
   185  func DecodeProtobuf(reader stream.DualModeReader, message proto.Message) error {
   186  	return NewProtobufDecoder(reader).Decode(message)
   187  }