blob: 6f3c944a6ef2ef2f1364fb4feef411c5954cf7b6 [file] [log] [blame]
// 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 {
// TODO(frolv): Rename this to MemoryDecoder to match the encoder naming.
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.
//
// A return value of 0 indicates that the field number is invalid. An invalid
// field number terminates the decode operation; any subsequent calls to
// Next() or Read*() will return DATA_LOSS.
//
// TODO(frolv): This should be refactored to return a Result<uint32_t>.
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