// 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 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 Encoder::Encode() { return EncodeFrom(0); } Result 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(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 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(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(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(std::numeric_limits::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