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 }