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 <limits>
18
19 namespace pw::protobuf {
20
WriteUint64(uint32_t field_number,uint64_t value)21 Status Encoder::WriteUint64(uint32_t field_number, uint64_t value) {
22 std::byte* original_cursor = cursor_;
23 WriteFieldKey(field_number, WireType::kVarint);
24 WriteVarint(value);
25 return IncreaseParentSize(cursor_ - original_cursor);
26 }
27
28 // Encodes a base-128 varint to the buffer.
WriteVarint(uint64_t value)29 Status Encoder::WriteVarint(uint64_t value) {
30 if (!encode_status_.ok()) {
31 return encode_status_;
32 }
33
34 std::span varint_buf = buffer_.last(RemainingSize());
35 if (varint_buf.empty()) {
36 encode_status_ = Status::ResourceExhausted();
37 return encode_status_;
38 }
39
40 size_t written = pw::varint::EncodeLittleEndianBase128(value, varint_buf);
41 if (written == 0) {
42 encode_status_ = Status::ResourceExhausted();
43 return encode_status_;
44 }
45
46 cursor_ += written;
47 return OkStatus();
48 }
49
WriteRawBytes(const std::byte * ptr,size_t size)50 Status Encoder::WriteRawBytes(const std::byte* ptr, size_t size) {
51 if (!encode_status_.ok()) {
52 return encode_status_;
53 }
54
55 if (size > RemainingSize()) {
56 encode_status_ = Status::ResourceExhausted();
57 return encode_status_;
58 }
59
60 // Memmove the value into place as it's possible that it shares the encode
61 // buffer on a memory-constrained system.
62 std::memmove(cursor_, ptr, size);
63
64 cursor_ += size;
65 return OkStatus();
66 }
67
Push(uint32_t field_number)68 Status Encoder::Push(uint32_t field_number) {
69 if (!encode_status_.ok()) {
70 return encode_status_;
71 }
72
73 if (blob_count_ == blob_locations_.size() || depth_ == blob_stack_.size()) {
74 encode_status_ = Status::ResourceExhausted();
75 return encode_status_;
76 }
77
78 // Write the key for the nested field.
79 std::byte* original_cursor = cursor_;
80 if (Status status = WriteFieldKey(field_number, WireType::kDelimited);
81 !status.ok()) {
82 encode_status_ = status;
83 return status;
84 }
85
86 if (sizeof(SizeType) > RemainingSize()) {
87 // Rollback if there isn't enough space.
88 cursor_ = original_cursor;
89 encode_status_ = Status::ResourceExhausted();
90 return encode_status_;
91 }
92
93 // Update parent size with the written key.
94 PW_TRY(IncreaseParentSize(cursor_ - original_cursor));
95
96 union {
97 std::byte* cursor;
98 SizeType* size_cursor;
99 };
100
101 // Create a size entry for the new blob and append it to both the nesting
102 // stack and location list.
103 cursor = cursor_;
104 *size_cursor = 0;
105 blob_locations_[blob_count_++] = size_cursor;
106 blob_stack_[depth_++] = size_cursor;
107
108 cursor_ += sizeof(*size_cursor);
109 return OkStatus();
110 }
111
Pop()112 Status Encoder::Pop() {
113 if (!encode_status_.ok()) {
114 return encode_status_;
115 }
116
117 if (depth_ == 0) {
118 encode_status_ = Status::FailedPrecondition();
119 return encode_status_;
120 }
121
122 // Update the parent's size with how much total space the child will take
123 // after its size field is varint encoded.
124 SizeType child_size = *blob_stack_[--depth_];
125 PW_TRY(IncreaseParentSize(child_size + VarintSizeBytes(child_size)));
126
127 // Encode the child
128 if (Status status = EncodeFrom(blob_count_ - 1).status(); !status.ok()) {
129 encode_status_ = status;
130 return encode_status_;
131 }
132 blob_count_--;
133
134 return OkStatus();
135 }
136
Encode()137 Result<ConstByteSpan> Encoder::Encode() { return EncodeFrom(0); }
138
EncodeFrom(size_t blob)139 Result<ConstByteSpan> Encoder::EncodeFrom(size_t blob) {
140 if (!encode_status_.ok()) {
141 return encode_status_;
142 }
143
144 if (blob >= blob_count_) {
145 // If there are no nested blobs, the buffer already contains a valid proto.
146 return Result<ConstByteSpan>(buffer_.first(EncodedSize()));
147 }
148
149 union {
150 std::byte* read_cursor;
151 SizeType* size_cursor;
152 };
153
154 // Starting from the first blob, encode each size field as a varint and
155 // shift all subsequent data downwards.
156 size_cursor = blob_locations_[blob];
157 std::byte* write_cursor = read_cursor;
158
159 while (read_cursor < cursor_) {
160 SizeType nested_size = *size_cursor;
161
162 std::span<std::byte> varint_buf(write_cursor, sizeof(*size_cursor));
163 size_t varint_size =
164 pw::varint::EncodeLittleEndianBase128(nested_size, varint_buf);
165
166 // Place the write cursor after the encoded varint and the read cursor at
167 // the location of the next proto field.
168 write_cursor += varint_size;
169 read_cursor += varint_buf.size();
170
171 size_t to_copy;
172
173 if (blob == blob_count_ - 1) {
174 to_copy = cursor_ - read_cursor;
175 } else {
176 std::byte* end = reinterpret_cast<std::byte*>(blob_locations_[blob + 1]);
177 to_copy = end - read_cursor;
178 }
179
180 std::memmove(write_cursor, read_cursor, to_copy);
181 write_cursor += to_copy;
182 read_cursor += to_copy;
183
184 ++blob;
185 }
186
187 // Point the cursor to the end of the encoded proto.
188 cursor_ = write_cursor;
189 return Result<ConstByteSpan>(buffer_.first(EncodedSize()));
190 }
191
IncreaseParentSize(size_t size_bytes)192 Status Encoder::IncreaseParentSize(size_t size_bytes) {
193 if (!encode_status_.ok()) {
194 return encode_status_;
195 }
196
197 if (depth_ == 0) {
198 return OkStatus();
199 }
200
201 size_t current_size = *blob_stack_[depth_ - 1];
202
203 constexpr size_t max_size =
204 std::min(varint::MaxValueInBytes(sizeof(SizeType)),
205 static_cast<uint64_t>(std::numeric_limits<uint32_t>::max()));
206
207 if (size_bytes > max_size || current_size > max_size - size_bytes) {
208 encode_status_ = Status::OutOfRange();
209 return encode_status_;
210 }
211
212 *blob_stack_[depth_ - 1] = current_size + size_bytes;
213 return OkStatus();
214 }
215
216 } // namespace pw::protobuf
217