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