• 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 <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