blob: aabeb8caa4eebf9b9ac94ff5e24f37736a77f6c2 [file] [log] [blame]
/*
* Copyright (C) 2016 The Android Open Source Project
*
* 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
*
* http://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.
*/
// This file implements a simple protobuf encoder and decoder. The high-level
// idea is to use C++ structs as data containers corresponding to protobuf
// messages. A descriptor must be provided for a struct via a
// |nvram::DescriptorForType| specialization that declares the protobuf fields
// to encode and decode.
// * Encoding works by going through the declared fields, and encode the
// corresponding struct members in protobuf wire format.
// * Decoding scans through the binary encoded message. It looks at the wire
// tag decoded form the message to recover the field number as well as
// the protobuf wire type (i.e. kind of encoding). The field number is then
// used to locate the struct field declaration so the appropriate decoding
// logic for the corresponding struct member can be invoked.
// * The main dispatch point that ties member types to decoding and encoding
// logic is the |nvram::proto::detail::Codec| template. The idea is that
// |Codec<Type>| provides encoding and decoding logic for |Type|.
//
// The API for encoding and decoding is straightforward. Consider the following
// Employee struct and its descriptor:
// type:
//
// struct Employee {
// uint32_t id;
// std::string name;
// std::vector<uint32_t> reports;
// };
//
// template <>
// struct DescriptorForType<Employee> {
// static constexpr auto kFields =
// MakeFieldList(MakeField(1, &Employee::id),
// MakeField(2, &Employee::name),
// MakeField(3, &Employee::reports));
// };
//
// Encoding is simple:
//
// Employee employee;
// uint8_t buffer[SIZE];
// nvram::OutputStream stream(buffer, sizeof(buffer));
// if (!nvram::proto::Encode(employee, &stream)) {
// // Handle encoding failure.
// }
//
// Note that |nvram::proto::GetSize()| can be used to determine a sufficient
// buffer size.
//
// Decoding is similar:
//
// Employee employee;
// nvram::InputStreamBuffer stream(buffer_start, buffer_size);
// if (!nvram::proto::Decode(&employee, &stream)) {
// // Handle decoding failure.
// }
//
// Note that this file is not meant as a header to be included by all code that
// needs to encode or decode messages. Rather, this header should only be
// included by a .cpp file which can then instantiate the
// |nvram::proto::Encode()| and |nvram::proto::Decode()| templates to obtain
// encoders and decoders for the relevant message types. This approach results
// in decode and encode logic getting compiled in only one translation unit,
// which other code can link against.
#ifndef NVRAM_MESSAGES_PROTO_HPP_
#define NVRAM_MESSAGES_PROTO_HPP_
extern "C" {
#include <stdint.h>
}
#include <nvram/messages/blob.h>
#include <nvram/messages/compiler.h>
#include <nvram/messages/io.h>
#include <nvram/messages/message_codec.h>
#include <nvram/messages/optional.h>
#include <nvram/messages/struct.h>
#include <nvram/messages/tagged_union.h>
#include <nvram/messages/type_traits.h>
#include <nvram/messages/vector.h>
namespace nvram {
namespace proto {
namespace detail {
// A class template that performs encoding and decoding of a protobuf message
// field of the C++ type |Type|. The base template is left undefined here,
// specific implementations for relevant |Type|s are provided by template
// specializations. Each specialization needs to provide the following members:
// * |static constexpr WireType kWireType| indicates the wire type used for
// encoded field data.
// * |static bool Encode(const Type& object, ProtoWriter* writer)| writes the
// encoded form of |object| to |writer|.
// * |static bool Decode(Type& object, ProtoReader* reader)| decodes a field
// from |reader| and places recovered data in |object|.
//
// |Codec| specializations are provided below for commonly-used types such as
// integral and enum types, as well as structs with corresponding descriptors.
// Additional specializations can be added as needed.
template <typename Type, typename Enable = void>
struct Codec {
// The assert below fails unconditionally, but must depend on the |Type|
// parameter so it only triggers at instantiation time. If this assert fires,
// then you are attempting to encode or decode a struct that contains a field
// of a C++ type for which there exists no code that implements encoding and
// decoding for that type. To add encoding/decoding support for a type, you
// can provide a Codec specialization.
static_assert(sizeof(Type) == 0,
"A Codec specialization must be provided for types "
"that are to be used with the protobuf encoder.");
};
namespace {
// Codec specific message field encoding function. Note that this is marked
// noinline to prevent the compiler from inlining |Codec::Encode| for every
// occurrence of a field of type |Type|.
template <typename Codec, typename Type>
NVRAM_NOINLINE bool EncodeField(const Type& value, ProtoWriter* writer) {
return Codec::Encode(value, writer);
}
// Codec specific message field decoding function. Note that this is marked
// noinline to prevent the compiler from inlining |Codec::Decode| for every
// occurrence of a field of type |Type|.
template <typename Codec, typename Type>
NVRAM_NOINLINE bool DecodeField(Type& value, ProtoReader* reader) {
return Codec::Decode(value, reader);
}
} // namespace
// |Codec| specialization for Blob.
template <>
struct Codec<Blob> {
static constexpr WireType kWireType = WireType::kLengthDelimited;
static bool Encode(const Blob& blob, ProtoWriter* writer) {
return writer->WriteLengthDelimited(blob.data(), blob.size());
}
static bool Decode(Blob& blob, ProtoReader* reader) {
return blob.Resize(reader->field_size()) &&
reader->ReadLengthDelimited(blob.data(), blob.size());
}
};
// A helper to test whether a given |Type| should be handled by the Varint
// |Codec| specialization. The |Type| needs to allow conversion from and to
// |uint64_t|. This checks for static_cast conversion behavior instead of
// implicit conversion in order to also match scoped enums.
template <typename Type>
struct IsVarintCompatible {
template <typename From, typename To>
struct IsCastConvertible {
template <typename T>
static decltype(static_cast<T>(declval<From>()), true_type()) test(int);
template <typename T>
static false_type test(...);
static constexpr bool value = decltype(test<To>(0))::value;
};
static constexpr bool value = IsCastConvertible<Type, uint64_t>::value &&
IsCastConvertible<uint64_t, Type>::value;
};
// |Codec| specialization for varint-encoded numeric fields.
template <typename Type>
struct Codec<Type, typename enable_if<IsVarintCompatible<Type>::value>::Type> {
static constexpr WireType kWireType = WireType::kVarint;
static bool Encode(const Type& value, ProtoWriter* writer) {
return writer->WriteVarint(static_cast<uint64_t>(value));
}
static bool Decode(Type& value, ProtoReader* reader) {
uint64_t raw_value;
if (!reader->ReadVarint(&raw_value)) {
return false;
}
value = static_cast<Type>(raw_value);
return static_cast<uint64_t>(value) == raw_value;
}
};
// |Codec| specialization for |Vector|.
template <typename ElementType>
struct Codec<Vector<ElementType>> {
using ElementCodec = Codec<ElementType>;
static constexpr WireType kWireType = ElementCodec::kWireType;
static bool Encode(const Vector<ElementType>& vector, ProtoWriter* writer) {
for (const ElementType& elem : vector) {
if (!EncodeField<ElementCodec>(elem, writer)) {
return false;
}
}
return true;
}
static bool Decode(Vector<ElementType>& vector, ProtoReader* reader) {
return vector.Resize(vector.size() + 1) &&
DecodeField<ElementCodec>(vector[vector.size() - 1], reader);
}
};
// |Codec| specialization for |Optional|.
template <typename ValueType>
struct Codec<Optional<ValueType>> {
using ValueCodec = Codec<ValueType>;
static constexpr WireType kWireType = ValueCodec::kWireType;
static bool Encode(const Optional<ValueType>& value, ProtoWriter* writer) {
return !value.valid() || EncodeField<ValueCodec>(value.value(), writer);
}
static bool Decode(Optional<ValueType>& value, ProtoReader* reader) {
return DecodeField<ValueCodec>(value.Activate(), reader);
}
};
namespace {
// |StructDescriptor| provides the |FieldDescriptor| table corresponding to
// |StructType|. The table contains information about each field in the protobuf
// encoding, e.g. field number and wire type.
//
// The |IndexSequence| template parameter is present purely for technical
// reasons. It provides a sequence of indices, one for each entry in the field
// declaration list for |StructType|. Having the index available simplifies
// generation of the descriptor table entries.
template <
typename StructType,
typename IndexSequence = decltype(
make_index_sequence<DescriptorForType<StructType>::kFields.kSize>())>
struct StructDescriptor;
template <typename StructType, size_t... indices>
struct StructDescriptor<StructType, index_sequence<indices...>> {
private:
static constexpr auto kFieldSpecList =
DescriptorForType<StructType>::kFields;
using FieldSpecs = typename remove_const<decltype(kFieldSpecList)>::Type;
// A helper function used to preform a compile-time sanity check on the
// declared field numbers to ensure that they're positive, unique and in
// ascending order.
template <typename FieldSpecList>
static constexpr bool CheckFieldNumbersAscending(
FieldSpecList list,
uint32_t previous_field_number) {
return list.kFieldSpec.kFieldNumber > previous_field_number &&
CheckFieldNumbersAscending(list.kTail, list.kFieldSpec.kFieldNumber);
}
static constexpr bool CheckFieldNumbersAscending(FieldSpecList<>, uint32_t) {
return true;
}
// If this fails, check your struct field declarations for the following:
// * Field numbers must be positive.
// * Field numbers must be unique.
// * Fields must be declared in ascending field number order.
static_assert(CheckFieldNumbersAscending(kFieldSpecList, 0),
"Field numbers must be positive, unique and declared in "
"ascending order.");
// Provides the |FieldDescriptor| instance for the field specified by |index|.
// Note that |index| is *not* the proto field number, but the zero-based index
// in the field declaration list.
template <size_t index>
class FieldDescriptorBuilder {
static constexpr auto kFieldSpec = kFieldSpecList.template Get<index>();
using FieldSpecType = typename remove_const<decltype(kFieldSpec)>::Type;
using MemberType = typename FieldSpecType::MemberType;
// Determines the Codec type to use for the field. The default is to use
// |Codec<MemberType>|, which is appropriate for simple fields declared via
// |FieldSpec|.
template <typename FieldSpec>
struct MemberCodecLookup {
using Type = Codec<MemberType>;
};
// |TaggedUnion| members require a special codec implementation that takes
// into account the case, so encoding only takes place if the respective
// union member is active and decoding activates the requested member before
// decoding data.
template <typename Struct, typename TagType, typename... Member>
struct MemberCodecLookup<
OneOfFieldSpec<Struct, TagType, Member...>> {
static constexpr TagType kTag = kFieldSpec.kTag;
struct Type {
using TaggedUnionType = TaggedUnion<TagType, Member...>;
using TaggedUnionMemberType =
typename TaggedUnionType::template MemberLookup<kTag>::Type::Type;
using TaggedUnionMemberCodec = Codec<TaggedUnionMemberType>;
static constexpr WireType kWireType = TaggedUnionMemberCodec::kWireType;
static bool Encode(const TaggedUnionType& object, ProtoWriter* writer) {
const TaggedUnionMemberType* member = object.template get<kTag>();
if (member) {
return EncodeField<TaggedUnionMemberCodec>(*member, writer);
}
return true;
}
static bool Decode(TaggedUnionType& object, ProtoReader* reader) {
return DecodeField<TaggedUnionMemberCodec>(
object.template Activate<kTag>(), reader);
}
};
};
using MemberCodec = typename MemberCodecLookup<FieldSpecType>::Type;
// Encodes a member. Retrieves a reference to the member within |object| and
// calls the appropriate encoder.
static bool EncodeMember(const void* object, ProtoWriter* writer) {
constexpr auto spec = kFieldSpec;
return EncodeField<MemberCodec>(
spec.Get(*static_cast<const StructType*>(object)), writer);
};
// Decodes a member. Retrieves a const reference to the member within
// |object| and calls the appropriate decoder.
static bool DecodeMember(void* object, ProtoReader* reader) {
constexpr auto spec = kFieldSpec;
return DecodeField<MemberCodec>(
spec.Get(*static_cast<StructType*>(object)), reader);
};
public:
// Assemble the actual descriptor for the field. Note that this is still a
// compile-time constant (i.e. has no linkage). However, the constant is
// used below to initialize the entry in the static descriptor table.
static constexpr FieldDescriptor kDescriptor =
FieldDescriptor(kFieldSpec.kFieldNumber,
MemberCodec::kWireType,
&EncodeMember,
&DecodeMember);
};
public:
// Descriptor table size.
static constexpr size_t kNumDescriptors = kFieldSpecList.kSize;
// The actual descriptor table.
static constexpr FieldDescriptor kDescriptors[] = {
FieldDescriptorBuilder<indices>::kDescriptor...};
};
// Provide a definition of the |kDescriptors| array such that the descriptor
// table gets emitted to static data.
template <typename StructType, size_t... index>
constexpr FieldDescriptor
StructDescriptor<StructType, index_sequence<index...>>::kDescriptors[];
// Note that G++ versions before 5.0 have a bug in handling parameter pack
// expansions that result in an empty array initializer. To work around this,
// the following specialization is provided for empty field lists.
template <typename StructType>
struct StructDescriptor<StructType, index_sequence<>> {
static constexpr size_t kNumDescriptors = 0;
static constexpr FieldDescriptor* kDescriptors = nullptr;
};
// A convenience class to initialize |MessageEncoderBase| with the descriptor
// table corresponding to |StructType| as determined by |StructDescriptor|.
template <typename StructType>
class MessageEncoder : public MessageEncoderBase {
public:
MessageEncoder(const StructType& object)
: MessageEncoderBase(&object,
StructDescriptor<StructType>::kDescriptors,
StructDescriptor<StructType>::kNumDescriptors) {}
static bool Encode(const StructType& object, ProtoWriter* writer) {
return MessageEncoderBase::Encode(
&object, writer, StructDescriptor<StructType>::kDescriptors,
StructDescriptor<StructType>::kNumDescriptors);
}
};
// A convenience class to initialize |MessageDecoderBase| with the descriptor
// table corresponding to |StructType| as determined by |StructDescriptor|.
template <typename StructType>
class MessageDecoder : public MessageDecoderBase {
public:
MessageDecoder(StructType& object)
: MessageDecoderBase(&object,
StructDescriptor<StructType>::kDescriptors,
StructDescriptor<StructType>::kNumDescriptors) {}
static bool Decode(StructType& object, ProtoReader* reader) {
return MessageDecoderBase::Decode(
&object, reader, StructDescriptor<StructType>::kDescriptors,
StructDescriptor<StructType>::kNumDescriptors);
}
};
} // namespace
// |Codec| specialization for struct types. The second template parameter
// evaluates to |void| if the appropriate |DescriptorForType| specialization
// exists, enabling the |Codec| specialization for that case.
//
// Note that this template generates code for each struct type that needs to be
// encoded and decoded. To avoid bloating the binary, we keep the type-dependent
// code at the absolute minimum. The |MessageEncoder| and |MessageDecoder|
// templates merely obtain the appropriate descriptor table for the struct type
// and then invoke the type-agnostic encoder and decoder base classes.
template <typename StructType>
struct Codec<StructType,
decltype(
static_cast<void>(DescriptorForType<StructType>::kFields))> {
static constexpr WireType kWireType = WireType::kLengthDelimited;
static bool Encode(const StructType& object, ProtoWriter* writer) {
return MessageEncoder<StructType>::Encode(object, writer);
}
static bool Decode(StructType& object, ProtoReader* reader) {
return MessageDecoder<StructType>::Decode(object, reader);
}
};
} // namespace detail
// Get the encoded size of an object.
template <typename Struct>
size_t GetSize(const Struct& object) {
detail::MessageEncoder<Struct> encoder(object);
return encoder.GetSize();
}
// Encode |object| and write the result to |stream|. Returns true if successful,
// false if encoding fails. Encoding may fail because |stream| doesn't have
// enough room to hold the encoded data.
template <typename Struct>
bool Encode(const Struct& object, OutputStreamBuffer* stream) {
ProtoWriter writer(stream);
detail::MessageEncoder<Struct> encoder(object);
return encoder.EncodeData(&writer);
}
// Decode |stream| and update |object| with the decoded information. Returns
// true if successful, false if encoding fails. Failure conditions include:
// * Binary data isn't valid with respect to the protobuf wire format.
// * |stream| ends prematurely.
// * Memory allocation in |object| to hold decoded data fails.
template <typename Struct>
bool Decode(Struct* object, InputStreamBuffer* stream) {
ProtoReader reader(stream);
detail::MessageDecoder<Struct> decoder(*object);
return decoder.DecodeData(&reader);
}
} // namespace proto
} // namespace nvram
#endif // NVRAM_MESSAGES_PROTO_HPP_