You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
217 lines
5.8 KiB
217 lines
5.8 KiB
// Copyright 2021 The Pigweed Authors
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
|
// use this file except in compliance with the License. You may obtain a copy of
|
|
// the License at
|
|
//
|
|
// https://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
// License for the specific language governing permissions and limitations under
|
|
// the License.
|
|
|
|
#include "pw_protobuf/encoder.h"
|
|
|
|
#include <limits>
|
|
|
|
namespace pw::protobuf {
|
|
|
|
Status Encoder::WriteUint64(uint32_t field_number, uint64_t value) {
|
|
std::byte* original_cursor = cursor_;
|
|
WriteFieldKey(field_number, WireType::kVarint);
|
|
WriteVarint(value);
|
|
return IncreaseParentSize(cursor_ - original_cursor);
|
|
}
|
|
|
|
// Encodes a base-128 varint to the buffer.
|
|
Status Encoder::WriteVarint(uint64_t value) {
|
|
if (!encode_status_.ok()) {
|
|
return encode_status_;
|
|
}
|
|
|
|
std::span varint_buf = buffer_.last(RemainingSize());
|
|
if (varint_buf.empty()) {
|
|
encode_status_ = Status::ResourceExhausted();
|
|
return encode_status_;
|
|
}
|
|
|
|
size_t written = pw::varint::EncodeLittleEndianBase128(value, varint_buf);
|
|
if (written == 0) {
|
|
encode_status_ = Status::ResourceExhausted();
|
|
return encode_status_;
|
|
}
|
|
|
|
cursor_ += written;
|
|
return OkStatus();
|
|
}
|
|
|
|
Status Encoder::WriteRawBytes(const std::byte* ptr, size_t size) {
|
|
if (!encode_status_.ok()) {
|
|
return encode_status_;
|
|
}
|
|
|
|
if (size > RemainingSize()) {
|
|
encode_status_ = Status::ResourceExhausted();
|
|
return encode_status_;
|
|
}
|
|
|
|
// Memmove the value into place as it's possible that it shares the encode
|
|
// buffer on a memory-constrained system.
|
|
std::memmove(cursor_, ptr, size);
|
|
|
|
cursor_ += size;
|
|
return OkStatus();
|
|
}
|
|
|
|
Status Encoder::Push(uint32_t field_number) {
|
|
if (!encode_status_.ok()) {
|
|
return encode_status_;
|
|
}
|
|
|
|
if (blob_count_ == blob_locations_.size() || depth_ == blob_stack_.size()) {
|
|
encode_status_ = Status::ResourceExhausted();
|
|
return encode_status_;
|
|
}
|
|
|
|
// Write the key for the nested field.
|
|
std::byte* original_cursor = cursor_;
|
|
if (Status status = WriteFieldKey(field_number, WireType::kDelimited);
|
|
!status.ok()) {
|
|
encode_status_ = status;
|
|
return status;
|
|
}
|
|
|
|
if (sizeof(SizeType) > RemainingSize()) {
|
|
// Rollback if there isn't enough space.
|
|
cursor_ = original_cursor;
|
|
encode_status_ = Status::ResourceExhausted();
|
|
return encode_status_;
|
|
}
|
|
|
|
// Update parent size with the written key.
|
|
PW_TRY(IncreaseParentSize(cursor_ - original_cursor));
|
|
|
|
union {
|
|
std::byte* cursor;
|
|
SizeType* size_cursor;
|
|
};
|
|
|
|
// Create a size entry for the new blob and append it to both the nesting
|
|
// stack and location list.
|
|
cursor = cursor_;
|
|
*size_cursor = 0;
|
|
blob_locations_[blob_count_++] = size_cursor;
|
|
blob_stack_[depth_++] = size_cursor;
|
|
|
|
cursor_ += sizeof(*size_cursor);
|
|
return OkStatus();
|
|
}
|
|
|
|
Status Encoder::Pop() {
|
|
if (!encode_status_.ok()) {
|
|
return encode_status_;
|
|
}
|
|
|
|
if (depth_ == 0) {
|
|
encode_status_ = Status::FailedPrecondition();
|
|
return encode_status_;
|
|
}
|
|
|
|
// Update the parent's size with how much total space the child will take
|
|
// after its size field is varint encoded.
|
|
SizeType child_size = *blob_stack_[--depth_];
|
|
PW_TRY(IncreaseParentSize(child_size + VarintSizeBytes(child_size)));
|
|
|
|
// Encode the child
|
|
if (Status status = EncodeFrom(blob_count_ - 1).status(); !status.ok()) {
|
|
encode_status_ = status;
|
|
return encode_status_;
|
|
}
|
|
blob_count_--;
|
|
|
|
return OkStatus();
|
|
}
|
|
|
|
Result<ConstByteSpan> Encoder::Encode() { return EncodeFrom(0); }
|
|
|
|
Result<ConstByteSpan> Encoder::EncodeFrom(size_t blob) {
|
|
if (!encode_status_.ok()) {
|
|
return encode_status_;
|
|
}
|
|
|
|
if (blob >= blob_count_) {
|
|
// If there are no nested blobs, the buffer already contains a valid proto.
|
|
return Result<ConstByteSpan>(buffer_.first(EncodedSize()));
|
|
}
|
|
|
|
union {
|
|
std::byte* read_cursor;
|
|
SizeType* size_cursor;
|
|
};
|
|
|
|
// Starting from the first blob, encode each size field as a varint and
|
|
// shift all subsequent data downwards.
|
|
size_cursor = blob_locations_[blob];
|
|
std::byte* write_cursor = read_cursor;
|
|
|
|
while (read_cursor < cursor_) {
|
|
SizeType nested_size = *size_cursor;
|
|
|
|
std::span<std::byte> varint_buf(write_cursor, sizeof(*size_cursor));
|
|
size_t varint_size =
|
|
pw::varint::EncodeLittleEndianBase128(nested_size, varint_buf);
|
|
|
|
// Place the write cursor after the encoded varint and the read cursor at
|
|
// the location of the next proto field.
|
|
write_cursor += varint_size;
|
|
read_cursor += varint_buf.size();
|
|
|
|
size_t to_copy;
|
|
|
|
if (blob == blob_count_ - 1) {
|
|
to_copy = cursor_ - read_cursor;
|
|
} else {
|
|
std::byte* end = reinterpret_cast<std::byte*>(blob_locations_[blob + 1]);
|
|
to_copy = end - read_cursor;
|
|
}
|
|
|
|
std::memmove(write_cursor, read_cursor, to_copy);
|
|
write_cursor += to_copy;
|
|
read_cursor += to_copy;
|
|
|
|
++blob;
|
|
}
|
|
|
|
// Point the cursor to the end of the encoded proto.
|
|
cursor_ = write_cursor;
|
|
return Result<ConstByteSpan>(buffer_.first(EncodedSize()));
|
|
}
|
|
|
|
Status Encoder::IncreaseParentSize(size_t size_bytes) {
|
|
if (!encode_status_.ok()) {
|
|
return encode_status_;
|
|
}
|
|
|
|
if (depth_ == 0) {
|
|
return OkStatus();
|
|
}
|
|
|
|
size_t current_size = *blob_stack_[depth_ - 1];
|
|
|
|
constexpr size_t max_size =
|
|
std::min(varint::MaxValueInBytes(sizeof(SizeType)),
|
|
static_cast<uint64_t>(std::numeric_limits<uint32_t>::max()));
|
|
|
|
if (size_bytes > max_size || current_size > max_size - size_bytes) {
|
|
encode_status_ = Status::OutOfRange();
|
|
return encode_status_;
|
|
}
|
|
|
|
*blob_stack_[depth_ - 1] = current_size + size_bytes;
|
|
return OkStatus();
|
|
}
|
|
|
|
} // namespace pw::protobuf
|