| // Copyright 2019 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. |
| #pragma once |
| |
| #include <cstddef> |
| #include <cstring> |
| #include <span> |
| |
| #include "pw_protobuf/wire_format.h" |
| #include "pw_status/status.h" |
| #include "pw_varint/varint.h" |
| |
| namespace pw::protobuf { |
| |
| // A streaming protobuf encoder which encodes to a user-specified buffer. |
| class Encoder { |
| public: |
| // TODO(frolv): Right now, only one intermediate size is supported. However, |
| // this can be wasteful, as it requires 4 or 8 bytes of space per nested |
| // message. This can be templated to minimize the overhead. |
| using SizeType = size_t; |
| |
| constexpr Encoder(std::span<std::byte> buffer, |
| std::span<SizeType*> locations, |
| std::span<SizeType*> stack) |
| : buffer_(buffer), |
| cursor_(buffer.data()), |
| blob_locations_(locations), |
| blob_count_(0), |
| blob_stack_(stack), |
| depth_(0), |
| encode_status_(Status::OK) {} |
| |
| // Disallow copy/assign to avoid confusion about who owns the buffer. |
| Encoder(const Encoder& other) = delete; |
| Encoder& operator=(const Encoder& other) = delete; |
| |
| // Per the protobuf specification, valid field numbers range between 1 and |
| // 2**29 - 1, inclusive. The numbers 19000-19999 are reserved for internal |
| // use. |
| constexpr static uint32_t kMaxFieldNumber = (1u << 29) - 1; |
| constexpr static uint32_t kFirstReservedNumber = 19000; |
| constexpr static uint32_t kLastReservedNumber = 19999; |
| |
| // Writes a proto uint32 key-value pair. |
| Status WriteUint32(uint32_t field_number, uint32_t value) { |
| return WriteUint64(field_number, value); |
| } |
| |
| // Writes a repeated uint32 using packed encoding. |
| Status WritePackedUint32(uint32_t field_number, |
| std::span<const uint32_t> values) { |
| return WritePackedVarints(field_number, values, /*zigzag=*/false); |
| } |
| |
| // Writes a proto uint64 key-value pair. |
| Status WriteUint64(uint32_t field_number, uint64_t value); |
| |
| // Writes a repeated uint64 using packed encoding. |
| Status WritePackedUint64(uint64_t field_number, |
| std::span<const uint64_t> values) { |
| return WritePackedVarints(field_number, values, /*zigzag=*/false); |
| } |
| |
| // Writes a proto int32 key-value pair. |
| Status WriteInt32(uint32_t field_number, int32_t value) { |
| return WriteUint64(field_number, value); |
| } |
| |
| // Writes a repeated int32 using packed encoding. |
| Status WritePackedInt32(uint32_t field_number, |
| std::span<const int32_t> values) { |
| return WritePackedVarints( |
| field_number, |
| std::span(reinterpret_cast<const uint32_t*>(values.data()), |
| values.size()), |
| /*zigzag=*/false); |
| } |
| |
| // Writes a proto int64 key-value pair. |
| Status WriteInt64(uint32_t field_number, int64_t value) { |
| return WriteUint64(field_number, value); |
| } |
| |
| // Writes a repeated int64 using packed encoding. |
| Status WritePackedInt64(uint32_t field_number, |
| std::span<const int64_t> values) { |
| return WritePackedVarints( |
| field_number, |
| std::span(reinterpret_cast<const uint64_t*>(values.data()), |
| values.size()), |
| /*zigzag=*/false); |
| } |
| |
| // Writes a proto sint32 key-value pair. |
| Status WriteSint32(uint32_t field_number, int32_t value) { |
| return WriteUint64(field_number, varint::ZigZagEncode(value)); |
| } |
| |
| // Writes a repeated sint32 using packed encoding. |
| Status WritePackedSint32(uint32_t field_number, |
| std::span<const int32_t> values) { |
| return WritePackedVarints( |
| field_number, |
| std::span(reinterpret_cast<const uint32_t*>(values.data()), |
| values.size()), |
| /*zigzag=*/true); |
| } |
| |
| // Writes a proto sint64 key-value pair. |
| Status WriteSint64(uint32_t field_number, int64_t value) { |
| return WriteUint64(field_number, varint::ZigZagEncode(value)); |
| } |
| |
| // Writes a repeated sint64 using packed encoding. |
| Status WritePackedSint64(uint32_t field_number, |
| std::span<const int64_t> values) { |
| return WritePackedVarints( |
| field_number, |
| std::span(reinterpret_cast<const uint64_t*>(values.data()), |
| values.size()), |
| /*zigzag=*/true); |
| } |
| |
| // Writes a proto bool key-value pair. |
| Status WriteBool(uint32_t field_number, bool value) { |
| return WriteUint32(field_number, static_cast<uint32_t>(value)); |
| } |
| |
| // Writes a proto fixed32 key-value pair. |
| Status WriteFixed32(uint32_t field_number, uint32_t value) { |
| std::byte* original_cursor = cursor_; |
| WriteFieldKey(field_number, WireType::kFixed32); |
| Status status = WriteRawBytes(value); |
| IncreaseParentSize(cursor_ - original_cursor); |
| return status; |
| } |
| |
| // Writes a repeated fixed32 field using packed encoding. |
| Status WritePackedFixed32(uint32_t field_number, |
| std::span<const uint32_t> values) { |
| return WriteBytes(field_number, std::as_bytes(values)); |
| } |
| |
| // Writes a proto fixed64 key-value pair. |
| Status WriteFixed64(uint32_t field_number, uint64_t value) { |
| std::byte* original_cursor = cursor_; |
| WriteFieldKey(field_number, WireType::kFixed64); |
| Status status = WriteRawBytes(value); |
| IncreaseParentSize(cursor_ - original_cursor); |
| return status; |
| } |
| |
| // Writes a repeated fixed64 field using packed encoding. |
| Status WritePackedFixed64(uint32_t field_number, |
| std::span<const uint64_t> values) { |
| return WriteBytes(field_number, std::as_bytes(values)); |
| } |
| |
| // Writes a proto sfixed32 key-value pair. |
| Status WriteSfixed32(uint32_t field_number, int32_t value) { |
| return WriteFixed32(field_number, static_cast<uint32_t>(value)); |
| } |
| |
| // Writes a repeated sfixed32 field using packed encoding. |
| Status WritePackedSfixed32(uint32_t field_number, |
| std::span<const int32_t> values) { |
| return WriteBytes(field_number, std::as_bytes(values)); |
| } |
| |
| // Writes a proto sfixed64 key-value pair. |
| Status WriteSfixed64(uint32_t field_number, int64_t value) { |
| return WriteFixed64(field_number, static_cast<uint64_t>(value)); |
| } |
| |
| // Writes a repeated sfixed64 field using packed encoding. |
| Status WritePackedSfixed64(uint32_t field_number, |
| std::span<const int64_t> values) { |
| return WriteBytes(field_number, std::as_bytes(values)); |
| } |
| |
| // Writes a proto float key-value pair. |
| Status WriteFloat(uint32_t field_number, float value) { |
| static_assert(sizeof(float) == sizeof(uint32_t), |
| "Float and uint32_t are not the same size"); |
| std::byte* original_cursor = cursor_; |
| WriteFieldKey(field_number, WireType::kFixed32); |
| Status status = WriteRawBytes(value); |
| IncreaseParentSize(cursor_ - original_cursor); |
| return status; |
| } |
| |
| // Writes a repeated float field using packed encoding. |
| Status WritePackedFloat(uint32_t field_number, |
| std::span<const float> values) { |
| return WriteBytes(field_number, std::as_bytes(values)); |
| } |
| |
| // Writes a proto double key-value pair. |
| Status WriteDouble(uint32_t field_number, double value) { |
| static_assert(sizeof(double) == sizeof(uint64_t), |
| "Double and uint64_t are not the same size"); |
| std::byte* original_cursor = cursor_; |
| WriteFieldKey(field_number, WireType::kFixed64); |
| Status status = WriteRawBytes(value); |
| IncreaseParentSize(cursor_ - original_cursor); |
| return status; |
| } |
| |
| // Writes a repeated double field using packed encoding. |
| Status WritePackedDouble(uint32_t field_number, |
| std::span<const double> values) { |
| return WriteBytes(field_number, std::as_bytes(values)); |
| } |
| |
| // Writes a proto bytes key-value pair. |
| Status WriteBytes(uint32_t field_number, std::span<const std::byte> value) { |
| std::byte* original_cursor = cursor_; |
| WriteFieldKey(field_number, WireType::kDelimited); |
| WriteVarint(value.size_bytes()); |
| Status status = WriteRawBytes(value.data(), value.size_bytes()); |
| IncreaseParentSize(cursor_ - original_cursor); |
| return status; |
| } |
| |
| // Writes a proto string key-value pair. |
| Status WriteString(uint32_t field_number, const char* value, size_t size) { |
| return WriteBytes(field_number, std::as_bytes(std::span(value, size))); |
| } |
| |
| Status WriteString(uint32_t field_number, const char* value) { |
| return WriteString(field_number, value, strlen(value)); |
| } |
| |
| // Begins writing a sub-message with a specified field number. |
| Status Push(uint32_t field_number); |
| |
| // Finishes writing a sub-message. |
| Status Pop(); |
| |
| // Returns the total encoded size of the proto message. |
| size_t EncodedSize() const { return cursor_ - buffer_.data(); } |
| |
| // Returns the number of bytes remaining in the buffer. |
| size_t RemainingSize() const { return buffer_.size() - EncodedSize(); } |
| |
| // Resets write index to the start of the buffer. This invalidates any spans |
| // obtained from Encode(). |
| void Clear() { |
| cursor_ = buffer_.data(); |
| encode_status_ = Status::OK; |
| blob_count_ = 0; |
| depth_ = 0; |
| } |
| |
| // Runs a final encoding pass over the intermediary data and returns the |
| // encoded protobuf message. |
| Status Encode(std::span<const std::byte>* out); |
| |
| private: |
| constexpr bool ValidFieldNumber(uint32_t field_number) const { |
| return field_number != 0 && field_number <= kMaxFieldNumber && |
| !(field_number >= kFirstReservedNumber && |
| field_number <= kLastReservedNumber); |
| } |
| |
| // Encodes the key for a proto field consisting of its number and wire type. |
| Status WriteFieldKey(uint32_t field_number, WireType wire_type) { |
| if (!ValidFieldNumber(field_number)) { |
| encode_status_ = Status::INVALID_ARGUMENT; |
| return encode_status_; |
| } |
| |
| return WriteVarint(MakeKey(field_number, wire_type)); |
| } |
| |
| Status WriteVarint(uint64_t value); |
| |
| Status WriteZigzagVarint(int64_t value) { |
| return WriteVarint(varint::ZigZagEncode(value)); |
| } |
| |
| template <typename T> |
| Status WriteRawBytes(const T& value) { |
| return WriteRawBytes(reinterpret_cast<const std::byte*>(&value), |
| sizeof(value)); |
| } |
| |
| Status WriteRawBytes(const std::byte* ptr, size_t size); |
| |
| // Writes a list of varints to the buffer in length-delimited packed encoding. |
| // If zigzag is true, zig-zag encodes each of the varints. |
| template <typename T> |
| Status WritePackedVarints(uint32_t field_number, |
| std::span<T> values, |
| bool zigzag) { |
| if (Status status = Push(field_number); !status.ok()) { |
| return status; |
| } |
| |
| std::byte* original_cursor = cursor_; |
| for (T value : values) { |
| if (zigzag) { |
| WriteZigzagVarint(static_cast<std::make_signed_t<T>>(value)); |
| } else { |
| WriteVarint(value); |
| } |
| } |
| IncreaseParentSize(cursor_ - original_cursor); |
| |
| return Pop(); |
| } |
| |
| // Adds to the parent proto's size field in the buffer. |
| void IncreaseParentSize(size_t bytes) { |
| if (depth_ > 0) { |
| *blob_stack_[depth_ - 1] += bytes; |
| } |
| } |
| |
| // Returns the size of `n` encoded as a varint. |
| size_t VarintSizeBytes(uint64_t n) { |
| size_t size_bytes = 1; |
| while (n > 127) { |
| ++size_bytes; |
| n >>= 7; |
| } |
| return size_bytes; |
| } |
| |
| // The buffer into which the proto is encoded. |
| std::span<std::byte> buffer_; |
| std::byte* cursor_; |
| |
| // List of pointers to sub-messages' delimiting size fields. |
| std::span<SizeType*> blob_locations_; |
| size_t blob_count_; |
| |
| // Stack of current nested message size locations. Push() operations add a new |
| // entry to this stack and Pop() operations remove one. |
| std::span<SizeType*> blob_stack_; |
| size_t depth_; |
| |
| Status encode_status_; |
| }; |
| |
| // A proto encoder with its own blob stack. |
| template <size_t kMaxNestedDepth = 1, size_t kMaxBlobs = 1> |
| class NestedEncoder : public Encoder { |
| public: |
| NestedEncoder(std::span<std::byte> buffer) |
| : Encoder(buffer, blobs_, stack_) {} |
| |
| // Disallow copy/assign to avoid confusion about who owns the buffer. |
| NestedEncoder(const NestedEncoder& other) = delete; |
| NestedEncoder& operator=(const NestedEncoder& other) = delete; |
| |
| private: |
| std::array<size_t*, kMaxBlobs> blobs_; |
| std::array<size_t*, kMaxNestedDepth> stack_; |
| }; |
| |
| // Explicit template argument deduction to hide warnings. |
| NestedEncoder()->NestedEncoder<>; |
| |
| } // namespace pw::protobuf |