| // Copyright 2020 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 <span> |
| #include <string_view> |
| |
| #include "pw_protobuf/wire_format.h" |
| #include "pw_status/status.h" |
| #include "pw_varint/varint.h" |
| |
| // This file defines a low-level event-based protobuf wire format decoder. |
| // The decoder processes an encoded message by iterating over its fields. The |
| // caller can extract the values of any fields it cares about. |
| // |
| // The decoder does not provide any in-memory data structures to represent a |
| // protobuf message's data. More sophisticated APIs can be built on top of the |
| // low-level decoder to provide additional functionality, if desired. |
| // |
| // Example usage: |
| // |
| // Decoder decoder(proto); |
| // while (decoder.Next().ok()) { |
| // switch (decoder.FieldNumber()) { |
| // case 1: |
| // decoder.ReadUint32(&my_uint32); |
| // break; |
| // // ... and other fields. |
| // } |
| // } |
| // |
| namespace pw::protobuf { |
| |
| class Decoder { |
| public: |
| constexpr Decoder(std::span<const std::byte> proto) |
| : proto_(proto), previous_field_consumed_(true) {} |
| |
| Decoder(const Decoder& other) = delete; |
| Decoder& operator=(const Decoder& other) = delete; |
| |
| // Advances to the next field in the proto. |
| // |
| // If Next() returns OK, there is guaranteed to be a valid protobuf field at |
| // the current cursor position. |
| // |
| // Return values: |
| // |
| // OK: Advanced to a valid proto field. |
| // OUT_OF_RANGE: Reached the end of the proto message. |
| // DATA_LOSS: Invalid protobuf data. |
| // |
| Status Next(); |
| |
| // Returns the field number of the field at the current cursor position. |
| uint32_t FieldNumber() const; |
| |
| // Reads a proto int32 value from the current cursor. |
| Status ReadInt32(int32_t* out) { |
| return ReadUint32(reinterpret_cast<uint32_t*>(out)); |
| } |
| |
| // Reads a proto uint32 value from the current cursor. |
| Status ReadUint32(uint32_t* out); |
| |
| // Reads a proto int64 value from the current cursor. |
| Status ReadInt64(int64_t* out) { |
| return ReadVarint(reinterpret_cast<uint64_t*>(out)); |
| } |
| |
| // Reads a proto uint64 value from the current cursor. |
| Status ReadUint64(uint64_t* out) { return ReadVarint(out); } |
| |
| // Reads a proto sint32 value from the current cursor. |
| Status ReadSint32(int32_t* out); |
| |
| // Reads a proto sint64 value from the current cursor. |
| Status ReadSint64(int64_t* out); |
| |
| // Reads a proto bool value from the current cursor. |
| Status ReadBool(bool* out); |
| |
| // Reads a proto fixed32 value from the current cursor. |
| Status ReadFixed32(uint32_t* out) { return ReadFixed(out); } |
| |
| // Reads a proto fixed64 value from the current cursor. |
| Status ReadFixed64(uint64_t* out) { return ReadFixed(out); } |
| |
| // Reads a proto sfixed32 value from the current cursor. |
| Status ReadSfixed32(int32_t* out) { |
| return ReadFixed32(reinterpret_cast<uint32_t*>(out)); |
| } |
| |
| // Reads a proto sfixed64 value from the current cursor. |
| Status ReadSfixed64(int64_t* out) { |
| return ReadFixed64(reinterpret_cast<uint64_t*>(out)); |
| } |
| |
| // Reads a proto float value from the current cursor. |
| Status ReadFloat(float* out) { |
| static_assert(sizeof(float) == sizeof(uint32_t), |
| "Float and uint32_t must be the same size for protobufs"); |
| return ReadFixed(out); |
| } |
| |
| // Reads a proto double value from the current cursor. |
| Status ReadDouble(double* out) { |
| static_assert(sizeof(double) == sizeof(uint64_t), |
| "Double and uint64_t must be the same size for protobufs"); |
| return ReadFixed(out); |
| } |
| |
| // Reads a proto string value from the current cursor and returns a view of it |
| // in `out`. The raw protobuf data must outlive `out`. If the string field is |
| // invalid, `out` is not modified. |
| Status ReadString(std::string_view* out); |
| |
| // Reads a proto bytes value from the current cursor and returns a view of it |
| // in `out`. The raw protobuf data must outlive the `out` std::span. If the |
| // bytes field is invalid, `out` is not modified. |
| Status ReadBytes(std::span<const std::byte>* out) { |
| return ReadDelimited(out); |
| } |
| |
| // Resets the decoder to start reading a new proto message. |
| void Reset(std::span<const std::byte> proto) { |
| proto_ = proto; |
| previous_field_consumed_ = true; |
| } |
| |
| private: |
| // Advances the cursor to the next field in the proto. |
| Status SkipField(); |
| |
| // Returns the size of the current field, or 0 if the field is invalid. |
| size_t FieldSize() const; |
| |
| Status ConsumeKey(WireType expected_type); |
| |
| // Reads a varint key-value pair from the current cursor position. |
| Status ReadVarint(uint64_t* out); |
| |
| // Reads a fixed-size key-value pair from the current cursor position. |
| Status ReadFixed(std::byte* out, size_t size); |
| |
| template <typename T> |
| Status ReadFixed(T* out) { |
| static_assert( |
| sizeof(T) == sizeof(uint32_t) || sizeof(T) == sizeof(uint64_t), |
| "Protobuf fixed-size fields must be 32- or 64-bit"); |
| return ReadFixed(reinterpret_cast<std::byte*>(out), sizeof(T)); |
| } |
| |
| Status ReadDelimited(std::span<const std::byte>* out); |
| |
| std::span<const std::byte> proto_; |
| bool previous_field_consumed_; |
| }; |
| |
| class DecodeHandler; |
| |
| // A protobuf decoder that iterates over an encoded protobuf, calling a handler |
| // for each field it encounters. |
| // |
| // Example usage: |
| // |
| // class FooProtoHandler : public DecodeHandler { |
| // public: |
| // Status ProcessField(CallbackDecoder& decoder, |
| // uint32_t field_number) override { |
| // switch (field_number) { |
| // case FooFields::kBar: |
| // if (!decoder.ReadSint32(&bar).ok()) { |
| // bar = 0; |
| // } |
| // break; |
| // case FooFields::kBaz: |
| // if (!decoder.ReadUint32(&baz).ok()) { |
| // baz = 0; |
| // } |
| // break; |
| // } |
| // |
| // return OkStatus(); |
| // } |
| // |
| // int bar; |
| // unsigned int baz; |
| // }; |
| // |
| // void DecodeFooProto(std::span<std::byte> raw_proto) { |
| // Decoder decoder; |
| // FooProtoHandler handler; |
| // |
| // decoder.set_handler(&handler); |
| // if (!decoder.Decode(raw_proto).ok()) { |
| // LOG_FATAL("Invalid foo message!"); |
| // } |
| // |
| // LOG_INFO("Read Foo proto message; bar: %d baz: %u", |
| // handler.bar, handler.baz); |
| // } |
| // |
| class CallbackDecoder { |
| public: |
| constexpr CallbackDecoder() |
| : decoder_({}), handler_(nullptr), state_(kReady) {} |
| |
| CallbackDecoder(const CallbackDecoder& other) = delete; |
| CallbackDecoder& operator=(const CallbackDecoder& other) = delete; |
| |
| void set_handler(DecodeHandler* handler) { handler_ = handler; } |
| |
| // Decodes the specified protobuf data. The registered handler's ProcessField |
| // function is called on each field found in the data. |
| Status Decode(std::span<const std::byte> proto); |
| |
| // Reads a proto int32 value from the current cursor. |
| Status ReadInt32(int32_t* out) { return decoder_.ReadInt32(out); } |
| |
| // Reads a proto uint32 value from the current cursor. |
| Status ReadUint32(uint32_t* out) { return decoder_.ReadUint32(out); } |
| |
| // Reads a proto int64 value from the current cursor. |
| Status ReadInt64(int64_t* out) { return decoder_.ReadInt64(out); } |
| |
| // Reads a proto uint64 value from the current cursor. |
| Status ReadUint64(uint64_t* out) { return decoder_.ReadUint64(out); } |
| |
| // Reads a proto sint64 value from the current cursor. |
| Status ReadSint32(int32_t* out) { return decoder_.ReadSint32(out); } |
| |
| // Reads a proto sint64 value from the current cursor. |
| Status ReadSint64(int64_t* out) { return decoder_.ReadSint64(out); } |
| |
| // Reads a proto bool value from the current cursor. |
| Status ReadBool(bool* out) { return decoder_.ReadBool(out); } |
| |
| // Reads a proto fixed32 value from the current cursor. |
| Status ReadFixed32(uint32_t* out) { return decoder_.ReadFixed32(out); } |
| |
| // Reads a proto fixed64 value from the current cursor. |
| Status ReadFixed64(uint64_t* out) { return decoder_.ReadFixed64(out); } |
| |
| // Reads a proto sfixed32 value from the current cursor. |
| Status ReadSfixed32(int32_t* out) { return decoder_.ReadSfixed32(out); } |
| |
| // Reads a proto sfixed64 value from the current cursor. |
| Status ReadSfixed64(int64_t* out) { return decoder_.ReadSfixed64(out); } |
| |
| // Reads a proto float value from the current cursor. |
| Status ReadFloat(float* out) { return decoder_.ReadFloat(out); } |
| |
| // Reads a proto double value from the current cursor. |
| Status ReadDouble(double* out) { return decoder_.ReadDouble(out); } |
| |
| // Reads a proto string value from the current cursor and returns a view of it |
| // in `out`. The raw protobuf data must outlive `out`. If the string field is |
| // invalid, `out` is not modified. |
| Status ReadString(std::string_view* out) { return decoder_.ReadString(out); } |
| |
| // Reads a proto bytes value from the current cursor and returns a view of it |
| // in `out`. The raw protobuf data must outlive the `out` std::span. If the |
| // bytes field is invalid, `out` is not modified. |
| Status ReadBytes(std::span<const std::byte>* out) { |
| return decoder_.ReadBytes(out); |
| } |
| |
| bool cancelled() const { return state_ == kDecodeCancelled; }; |
| |
| private: |
| enum State { |
| kReady, |
| kDecodeInProgress, |
| kDecodeCancelled, |
| kDecodeFailed, |
| }; |
| |
| Decoder decoder_; |
| DecodeHandler* handler_; |
| |
| State state_; |
| }; |
| |
| // The event-handling interface implemented for a proto callback decoding |
| // operation. |
| class DecodeHandler { |
| public: |
| virtual ~DecodeHandler() = default; |
| |
| // Callback called for each field encountered in the decoded proto message. |
| // Receives a pointer to the decoder object, allowing the handler to call |
| // the appropriate method to extract the field's data. |
| // |
| // If the status returned is not OkStatus(), the decode operation is exited |
| // with the provided status. Returning Status::Cancelled() allows a convenient |
| // way of stopping a decode early (for example, if a desired field is found). |
| virtual Status ProcessField(CallbackDecoder& decoder, |
| uint32_t field_number) = 0; |
| }; |
| |
| } // namespace pw::protobuf |