blob: 96b9b7ddcc16e692581a38ebfdcb1677987b5187 [file] [log] [blame]
Alexei Frolovbbf164c2019-12-16 12:51:59 -08001// Copyright 2019 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
17namespace pw::protobuf {
18
19Status Encoder::WriteUint64(uint32_t field_number, uint64_t value) {
Alexei Frolov311c6202020-01-06 11:16:20 -080020 std::byte* original_cursor = cursor_;
Alexei Frolovbbf164c2019-12-16 12:51:59 -080021 WriteFieldKey(field_number, WireType::kVarint);
22 Status status = WriteVarint(value);
23 IncreaseParentSize(cursor_ - original_cursor);
24 return status;
25}
26
27// Encodes a base-128 varint to the buffer.
Rob Mohr85cf08f2019-12-20 11:54:08 -080028Status Encoder::WriteVarint(uint64_t value) {
Alexei Frolovbbf164c2019-12-16 12:51:59 -080029 if (!encode_status_.ok()) {
30 return encode_status_;
31 }
32
Wyatt Heplere2cbadf2020-06-22 11:21:45 -070033 std::span varint_buf = buffer_.last(RemainingSize());
Alexei Frolovbbf164c2019-12-16 12:51:59 -080034 if (varint_buf.empty()) {
35 encode_status_ = Status::RESOURCE_EXHAUSTED;
36 return encode_status_;
37 }
38
Rob Mohr85cf08f2019-12-20 11:54:08 -080039 size_t written = pw::varint::EncodeLittleEndianBase128(value, varint_buf);
Alexei Frolovbbf164c2019-12-16 12:51:59 -080040 if (written == 0) {
41 encode_status_ = Status::RESOURCE_EXHAUSTED;
42 return encode_status_;
43 }
44
45 cursor_ += written;
46 return Status::OK;
47}
48
49Status Encoder::WriteRawBytes(const std::byte* ptr, size_t size) {
50 if (!encode_status_.ok()) {
51 return encode_status_;
52 }
53
54 if (size > RemainingSize()) {
55 encode_status_ = Status::RESOURCE_EXHAUSTED;
56 return encode_status_;
57 }
58
Alexei Frolov33a1e8f2020-05-26 08:39:32 -070059 // Memmove the value into place as it's possible that it shares the encode
60 // buffer on a memory-constrained system.
61 std::memmove(cursor_, ptr, size);
62
Alexei Frolovbbf164c2019-12-16 12:51:59 -080063 cursor_ += size;
64 return Status::OK;
65}
66
67Status Encoder::Push(uint32_t field_number) {
68 if (!encode_status_.ok()) {
69 return encode_status_;
70 }
71
72 if (blob_count_ == blob_locations_.size() || depth_ == blob_stack_.size()) {
73 encode_status_ = Status::RESOURCE_EXHAUSTED;
74 return encode_status_;
75 }
76
77 // Write the key for the nested field.
Alexei Frolov311c6202020-01-06 11:16:20 -080078 std::byte* original_cursor = cursor_;
Alexei Frolovbbf164c2019-12-16 12:51:59 -080079 if (Status status = WriteFieldKey(field_number, WireType::kDelimited);
80 !status.ok()) {
81 encode_status_ = status;
82 return status;
83 }
84
85 if (sizeof(SizeType) > RemainingSize()) {
86 // Rollback if there isn't enough space.
87 cursor_ = original_cursor;
88 encode_status_ = Status::RESOURCE_EXHAUSTED;
89 return encode_status_;
90 }
91
92 // Update parent size with the written key.
93 IncreaseParentSize(cursor_ - original_cursor);
94
95 union {
Alexei Frolov311c6202020-01-06 11:16:20 -080096 std::byte* cursor;
Alexei Frolovbbf164c2019-12-16 12:51:59 -080097 SizeType* size_cursor;
98 };
99
100 // Create a size entry for the new blob and append it to both the nesting
101 // stack and location list.
102 cursor = cursor_;
103 *size_cursor = 0;
104 blob_locations_[blob_count_++] = size_cursor;
105 blob_stack_[depth_++] = size_cursor;
106
107 cursor_ += sizeof(*size_cursor);
108 return Status::OK;
109}
110
111Status Encoder::Pop() {
112 if (!encode_status_.ok()) {
113 return encode_status_;
114 }
115
116 if (depth_ == 0) {
117 encode_status_ = Status::FAILED_PRECONDITION;
118 return encode_status_;
119 }
120
121 // Update the parent's size with how much total space the child will take
122 // after its size field is varint encoded.
123 SizeType child_size = *blob_stack_[--depth_];
124 IncreaseParentSize(child_size + VarintSizeBytes(child_size));
125
126 return Status::OK;
127}
128
Wyatt Heplere2cbadf2020-06-22 11:21:45 -0700129Status Encoder::Encode(std::span<const std::byte>* out) {
Alexei Frolovbbf164c2019-12-16 12:51:59 -0800130 if (!encode_status_.ok()) {
Wyatt Heplere2cbadf2020-06-22 11:21:45 -0700131 *out = std::span<const std::byte>();
Alexei Frolovbbf164c2019-12-16 12:51:59 -0800132 return encode_status_;
133 }
134
135 if (blob_count_ == 0) {
136 // If there are no nested blobs, the buffer already contains a valid proto.
137 *out = buffer_.first(EncodedSize());
138 return Status::OK;
139 }
140
141 union {
Alexei Frolov311c6202020-01-06 11:16:20 -0800142 std::byte* read_cursor;
Alexei Frolovbbf164c2019-12-16 12:51:59 -0800143 SizeType* size_cursor;
144 };
145
146 // Starting from the first blob, encode each size field as a varint and
147 // shift all subsequent data downwards.
148 unsigned int blob = 0;
149 size_cursor = blob_locations_[blob];
Alexei Frolov311c6202020-01-06 11:16:20 -0800150 std::byte* write_cursor = read_cursor;
Alexei Frolovbbf164c2019-12-16 12:51:59 -0800151
152 while (read_cursor < cursor_) {
153 SizeType nested_size = *size_cursor;
154
Wyatt Heplere2cbadf2020-06-22 11:21:45 -0700155 std::span<std::byte> varint_buf(write_cursor, sizeof(*size_cursor));
Alexei Frolovbbf164c2019-12-16 12:51:59 -0800156 size_t varint_size =
157 pw::varint::EncodeLittleEndianBase128(nested_size, varint_buf);
158
159 // Place the write cursor after the encoded varint and the read cursor at
160 // the location of the next proto field.
161 write_cursor += varint_size;
162 read_cursor += varint_buf.size();
163
164 size_t to_copy;
165
166 if (blob == blob_count_ - 1) {
167 to_copy = cursor_ - read_cursor;
168 } else {
Alexei Frolov311c6202020-01-06 11:16:20 -0800169 std::byte* end = reinterpret_cast<std::byte*>(blob_locations_[blob + 1]);
Alexei Frolovbbf164c2019-12-16 12:51:59 -0800170 to_copy = end - read_cursor;
171 }
172
Alexei Frolov33a1e8f2020-05-26 08:39:32 -0700173 std::memmove(write_cursor, read_cursor, to_copy);
Alexei Frolovbbf164c2019-12-16 12:51:59 -0800174 write_cursor += to_copy;
175 read_cursor += to_copy;
176
177 ++blob;
178 }
179
180 // Point the cursor to the end of the encoded proto.
181 cursor_ = write_cursor;
182 *out = buffer_.first(EncodedSize());
183 return Status::OK;
184}
185
186} // namespace pw::protobuf