blob: 7e5feed8605bf29f7c9ddfac0040beeb757cc24a [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 <string_view>
#include "pw_protobuf/wire_format.h"
#include "pw_span/span.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 and
// notifying a handler for each field it encounters. The handler receives a
// reference to the decoder object and can extract the field's value from the
// message.
//
// 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:
//
// class FooProtoHandler : public DecodeHandler {
// public:
// Status ProcessField(Decoder* decoder, uint32_t field_number) override {
// switch (field_number) {
// case FooFields::kBar:
// if (!decoder->ReadSint32(field_number, &bar).ok()) {
// bar = 0;
// }
// break;
// case FooFields::kBaz:
// if (!decoder->ReadUint32(field_number, &baz).ok()) {
// baz = 0;
// }
// break;
// }
//
// return Status::OK;
// }
//
// int bar;
// unsigned int baz;
// };
//
// void DecodeFooProto(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);
// }
//
namespace pw::protobuf {
class DecodeHandler;
// A protobuf decoder that iterates over an encoded protobuf, calling a handler
// for each field it encounters.
class Decoder {
public:
constexpr Decoder() : handler_(nullptr), state_(kReady) {}
Decoder(const Decoder& other) = delete;
Decoder& operator=(const Decoder& 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(span<const std::byte> proto);
// Reads a proto int32 value from the current cursor.
Status ReadInt32(uint32_t field_number, int32_t* out) {
return ReadUint32(field_number, reinterpret_cast<uint32_t*>(out));
}
// Reads a proto uint32 value from the current cursor.
Status ReadUint32(uint32_t field_number, uint32_t* out) {
uint64_t value = 0;
Status status = ReadUint64(field_number, &value);
if (!status.ok()) {
return status;
}
if (value > std::numeric_limits<uint32_t>::max()) {
return Status::OUT_OF_RANGE;
}
*out = value;
return Status::OK;
}
// Reads a proto int64 value from the current cursor.
Status ReadInt64(uint32_t field_number, int64_t* out) {
return ReadVarint(field_number, reinterpret_cast<uint64_t*>(out));
}
// Reads a proto uint64 value from the current cursor.
Status ReadUint64(uint32_t field_number, uint64_t* out) {
return ReadVarint(field_number, out);
}
// Reads a proto sint32 value from the current cursor.
Status ReadSint32(uint32_t field_number, int32_t* out) {
int64_t value = 0;
Status status = ReadSint64(field_number, &value);
if (!status.ok()) {
return status;
}
if (value > std::numeric_limits<int32_t>::max()) {
return Status::OUT_OF_RANGE;
}
*out = value;
return Status::OK;
}
// Reads a proto sint64 value from the current cursor.
Status ReadSint64(uint32_t field_number, int64_t* out) {
uint64_t value = 0;
Status status = ReadUint64(field_number, &value);
if (!status.ok()) {
return status;
}
*out = varint::ZigZagDecode(value);
return Status::OK;
}
// Reads a proto bool value from the current cursor.
Status ReadBool(uint32_t field_number, bool* out) {
uint64_t value = 0;
Status status = ReadUint64(field_number, &value);
if (!status.ok()) {
return status;
}
*out = value;
return Status::OK;
}
// Reads a proto fixed32 value from the current cursor.
Status ReadFixed32(uint32_t field_number, uint32_t* out) {
return ReadFixed(field_number, out);
}
// Reads a proto fixed64 value from the current cursor.
Status ReadFixed64(uint32_t field_number, uint64_t* out) {
return ReadFixed(field_number, out);
}
// Reads a proto sfixed32 value from the current cursor.
Status ReadSfixed32(uint32_t field_number, int32_t* out) {
return ReadFixed32(field_number, reinterpret_cast<uint32_t*>(out));
}
// Reads a proto sfixed64 value from the current cursor.
Status ReadSfixed64(uint32_t field_number, int64_t* out) {
return ReadFixed64(field_number, reinterpret_cast<uint64_t*>(out));
}
// Reads a proto float value from the current cursor.
Status ReadFloat(uint32_t field_number, float* out) {
static_assert(sizeof(float) == sizeof(uint32_t),
"Float and uint32_t must be the same size for protobufs");
return ReadFixed(field_number, out);
}
// Reads a proto double value from the current cursor.
Status ReadDouble(uint32_t field_number, double* out) {
static_assert(sizeof(double) == sizeof(uint64_t),
"Double and uint64_t must be the same size for protobufs");
return ReadFixed(field_number, 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(uint32_t field_number, std::string_view* out) {
span<const std::byte> bytes;
Status status = ReadDelimited(field_number, &bytes);
if (!status.ok()) {
return status;
}
*out = std::string_view(reinterpret_cast<const char*>(bytes.data()),
bytes.size());
return Status::OK;
}
// 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` span. If the bytes
// field is invalid, `out` is not modified.
Status ReadBytes(uint32_t field_number, span<const std::byte>* out) {
return ReadDelimited(field_number, out);
}
private:
enum State {
kReady,
kDecodeInProgress,
kDecodeFailed,
};
// Reads a varint key-value pair from the current cursor position.
Status ReadVarint(uint32_t field_number, uint64_t* out);
// Reads a fixed-size key-value pair from the current cursor position.
Status ReadFixed(uint32_t field_number, std::byte* out, size_t size);
template <typename T>
Status ReadFixed(uint32_t field_number, T* out) {
static_assert(
sizeof(T) == sizeof(uint32_t) || sizeof(T) == sizeof(uint64_t),
"Protobuf fixed-size fields must be 32- or 64-bit");
union {
T value;
std::byte bytes[sizeof(T)];
};
Status status = ReadFixed(field_number, bytes, sizeof(bytes));
if (!status.ok()) {
return status;
}
*out = value;
return Status::OK;
}
Status ReadDelimited(uint32_t field_number, span<const std::byte>* out);
Status ConsumeKey(uint32_t field_number, WireType expected_type);
// Advances the cursor to the next field in the proto.
void SkipField();
DecodeHandler* handler_;
State state_;
span<const std::byte> proto_;
};
// The event-handling interface implemented for a proto 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 Status::OK, 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(Decoder* decoder, uint32_t field_number) = 0;
};
} // namespace pw::protobuf