• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2021 The Pigweed Authors
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License"); you may not
4 // use this file except in compliance with the License. You may obtain a copy of
5 // the License at
6 //
7 //     https://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12 // License for the specific language governing permissions and limitations under
13 // the License.
14 
15 #include "pw_protobuf/encoder.h"
16 
17 #include <cstddef>
18 #include <cstring>
19 #include <span>
20 
21 #include "pw_assert/check.h"
22 #include "pw_bytes/span.h"
23 #include "pw_protobuf/serialized_size.h"
24 #include "pw_protobuf/wire_format.h"
25 #include "pw_status/status.h"
26 #include "pw_status/try.h"
27 #include "pw_stream/memory_stream.h"
28 #include "pw_stream/stream.h"
29 #include "pw_varint/varint.h"
30 
31 namespace pw::protobuf {
32 
GetNestedEncoder(uint32_t field_number)33 StreamEncoder StreamEncoder::GetNestedEncoder(uint32_t field_number) {
34   PW_CHECK(!nested_encoder_open());
35   PW_CHECK(ValidFieldNumber(field_number));
36 
37   nested_field_number_ = field_number;
38 
39   // Pass the unused space of the scratch buffer to the nested encoder to use
40   // as their scratch buffer.
41   size_t key_size =
42       varint::EncodedSize(FieldKey(field_number, WireType::kDelimited));
43   size_t reserved_size = key_size + config::kMaxVarintSize;
44   size_t max_size = std::min(memory_writer_.ConservativeWriteLimit(),
45                              writer_.ConservativeWriteLimit());
46   // Account for reserved bytes.
47   max_size = max_size > reserved_size ? max_size - reserved_size : 0;
48   // Cap based on max varint size.
49   max_size = std::min(varint::MaxValueInBytes(config::kMaxVarintSize),
50                       static_cast<uint64_t>(max_size));
51 
52   ByteSpan nested_buffer;
53   if (max_size > 0) {
54     nested_buffer = ByteSpan(
55         memory_writer_.data() + reserved_size + memory_writer_.bytes_written(),
56         max_size);
57   } else {
58     nested_buffer = ByteSpan();
59   }
60   return StreamEncoder(*this, nested_buffer);
61 }
62 
~StreamEncoder()63 StreamEncoder::~StreamEncoder() {
64   // If this was an invalidated StreamEncoder which cannot be used, permit the
65   // object to be cleanly destructed by doing nothing.
66   if (nested_field_number_ == kFirstReservedNumber) {
67     return;
68   }
69 
70   PW_CHECK(
71       !nested_encoder_open(),
72       "Tried to destruct a proto encoder with an active submessage encoder");
73 
74   if (parent_ != nullptr) {
75     parent_->CloseNestedMessage(*this);
76   }
77 }
78 
CloseNestedMessage(StreamEncoder & nested)79 void StreamEncoder::CloseNestedMessage(StreamEncoder& nested) {
80   PW_DCHECK_PTR_EQ(nested.parent_,
81                    this,
82                    "CloseNestedMessage() called on the wrong Encoder parent");
83 
84   // Make the nested encoder look like it has an open child to block writes for
85   // the remainder of the object's life.
86   nested.nested_field_number_ = kFirstReservedNumber;
87   nested.parent_ = nullptr;
88   // Temporarily cache the field number of the child so we can re-enable
89   // writing to this encoder.
90   uint32_t temp_field_number = nested_field_number_;
91   nested_field_number_ = 0;
92 
93   // TODO(amontanez): If a submessage fails, we could optionally discard
94   // it and continue happily. For now, we'll always invalidate the entire
95   // encoder if a single submessage fails.
96   status_.Update(nested.status_);
97   if (!status_.ok()) {
98     return;
99   }
100 
101   if (varint::EncodedSize(nested.memory_writer_.bytes_written()) >
102       config::kMaxVarintSize) {
103     status_ = Status::OutOfRange();
104     return;
105   }
106 
107   status_ = WriteLengthDelimitedField(temp_field_number,
108                                       nested.memory_writer_.WrittenData());
109 }
110 
WriteVarintField(uint32_t field_number,uint64_t value)111 Status StreamEncoder::WriteVarintField(uint32_t field_number, uint64_t value) {
112   PW_TRY(UpdateStatusForWrite(
113       field_number, WireType::kVarint, varint::EncodedSize(value)));
114 
115   WriteVarint(FieldKey(field_number, WireType::kVarint))
116       .IgnoreError();  // TODO(pwbug/387): Handle Status properly
117   return WriteVarint(value);
118 }
119 
WriteLengthDelimitedField(uint32_t field_number,ConstByteSpan data)120 Status StreamEncoder::WriteLengthDelimitedField(uint32_t field_number,
121                                                 ConstByteSpan data) {
122   PW_TRY(UpdateStatusForWrite(field_number, WireType::kDelimited, data.size()));
123   status_.Update(WriteLengthDelimitedKeyAndLengthPrefix(
124       field_number, data.size(), writer_));
125   PW_TRY(status_);
126   if (Status status = writer_.Write(data); !status.ok()) {
127     status_ = status;
128   }
129   return status_;
130 }
131 
WriteLengthDelimitedFieldFromStream(uint32_t field_number,stream::Reader & bytes_reader,size_t num_bytes,ByteSpan stream_pipe_buffer)132 Status StreamEncoder::WriteLengthDelimitedFieldFromStream(
133     uint32_t field_number,
134     stream::Reader& bytes_reader,
135     size_t num_bytes,
136     ByteSpan stream_pipe_buffer) {
137   PW_CHECK_UINT_GT(
138       stream_pipe_buffer.size(), 0, "Transfer buffer cannot be 0 size");
139   PW_TRY(UpdateStatusForWrite(field_number, WireType::kDelimited, num_bytes));
140   status_.Update(
141       WriteLengthDelimitedKeyAndLengthPrefix(field_number, num_bytes, writer_));
142   PW_TRY(status_);
143 
144   // Stream data from `bytes_reader` to `writer_`.
145   // TODO(pwbug/468): move the following logic to pw_stream/copy.h at a later
146   // time.
147   for (size_t bytes_written = 0; bytes_written < num_bytes;) {
148     const size_t chunk_size_bytes =
149         std::min(num_bytes - bytes_written, stream_pipe_buffer.size_bytes());
150     const Result<ByteSpan> read_result =
151         bytes_reader.Read(stream_pipe_buffer.data(), chunk_size_bytes);
152     status_.Update(read_result.status());
153     PW_TRY(status_);
154 
155     status_.Update(writer_.Write(read_result.value()));
156     PW_TRY(status_);
157 
158     bytes_written += read_result.value().size();
159   }
160 
161   return OkStatus();
162 }
163 
WriteFixed(uint32_t field_number,ConstByteSpan data)164 Status StreamEncoder::WriteFixed(uint32_t field_number, ConstByteSpan data) {
165   WireType type =
166       data.size() == sizeof(uint32_t) ? WireType::kFixed32 : WireType::kFixed64;
167 
168   PW_TRY(UpdateStatusForWrite(field_number, type, data.size()));
169 
170   WriteVarint(FieldKey(field_number, type))
171       .IgnoreError();  // TODO(pwbug/387): Handle Status properly
172   if (Status status = writer_.Write(data); !status.ok()) {
173     status_ = status;
174   }
175   return status_;
176 }
177 
WritePackedFixed(uint32_t field_number,std::span<const std::byte> values,size_t elem_size)178 Status StreamEncoder::WritePackedFixed(uint32_t field_number,
179                                        std::span<const std::byte> values,
180                                        size_t elem_size) {
181   if (values.empty()) {
182     return status_;
183   }
184 
185   PW_CHECK_NOTNULL(values.data());
186   PW_DCHECK(elem_size == sizeof(uint32_t) || elem_size == sizeof(uint64_t));
187 
188   PW_TRY(UpdateStatusForWrite(
189       field_number, WireType::kDelimited, values.size_bytes()));
190   WriteVarint(FieldKey(field_number, WireType::kDelimited))
191       .IgnoreError();  // TODO(pwbug/387): Handle Status properly
192   WriteVarint(values.size_bytes())
193       .IgnoreError();  // TODO(pwbug/387): Handle Status properly
194 
195   for (auto val_start = values.begin(); val_start != values.end();
196        val_start += elem_size) {
197     // Allocates 8 bytes so both 4-byte and 8-byte types can be encoded as
198     // little-endian for serialization.
199     std::array<std::byte, sizeof(uint64_t)> data;
200     if (std::endian::native == std::endian::little) {
201       std::copy(val_start, val_start + elem_size, std::begin(data));
202     } else {
203       std::reverse_copy(val_start, val_start + elem_size, std::begin(data));
204     }
205     status_.Update(writer_.Write(std::span(data).first(elem_size)));
206     PW_TRY(status_);
207   }
208   return status_;
209 }
210 
UpdateStatusForWrite(uint32_t field_number,WireType type,size_t data_size)211 Status StreamEncoder::UpdateStatusForWrite(uint32_t field_number,
212                                            WireType type,
213                                            size_t data_size) {
214   PW_CHECK(!nested_encoder_open());
215   PW_TRY(status_);
216 
217   if (!ValidFieldNumber(field_number)) {
218     return status_ = Status::InvalidArgument();
219   }
220 
221   const Result<size_t> field_size = SizeOfField(field_number, type, data_size);
222   status_.Update(field_size.status());
223   PW_TRY(status_);
224 
225   if (field_size.value() > writer_.ConservativeWriteLimit()) {
226     status_ = Status::ResourceExhausted();
227   }
228 
229   return status_;
230 }
231 
232 }  // namespace pw::protobuf
233