// Copyright (c) FIRST and other WPILib contributors. // Open Source Software; you can modify and/or share it under the terms of // the WPILib BSD license file in the root directory of this project. #pragma once #include #include #include #include #include #include #include #include #include "wpi/array.h" #include "wpi/function_ref.h" namespace google::protobuf { class Arena; class Message; template class RepeatedPtrField; template class RepeatedField; } // namespace google::protobuf namespace wpi { template class SmallVectorImpl; /** * Protobuf serialization template. Unspecialized class has no members; only * specializations of this class are useful, and only if they meet the * ProtobufSerializable concept. * * @tparam T type to serialize/deserialize */ template struct Protobuf {}; /** * Specifies that a type is capable of protobuf serialization and * deserialization. * * This is designed for serializing complex flexible data structures using * code generated from a .proto file. Serialization consists of writing * values into a mutable protobuf Message and deserialization consists of * reading values from an immutable protobuf Message. * * Implementations must define a template specialization for wpi::Protobuf with * T being the type that is being serialized/deserialized, with the following * static members (as enforced by this concept): * - google::protobuf::Message* New(google::protobuf::Arena*): create a protobuf * message * - T Unpack(const google::protobuf::Message&): function for deserialization * - void Pack(google::protobuf::Message*, T&& value): function for * serialization * * To avoid pulling in the protobuf headers, these functions use * google::protobuf::Message instead of a more specific type; implementations * will need to static_cast to the correct type as created by New(). * * Additionally: In a static block, call StructRegistry.registerClass() to * register the class */ template concept ProtobufSerializable = requires( google::protobuf::Arena* arena, const google::protobuf::Message& inmsg, google::protobuf::Message* outmsg, const T& value) { typename Protobuf>; { Protobuf>::New(arena) } -> std::same_as; { Protobuf>::Unpack(inmsg) } -> std::same_as>; Protobuf>::Pack(outmsg, value); }; /** * Specifies that a type is capable of in-place protobuf deserialization. * * In addition to meeting ProtobufSerializable, implementations must define a * wpi::Protobuf static member * `void UnpackInto(T*, const google::protobuf::Message&)` to update the * pointed-to T with the contents of the message. */ template concept MutableProtobufSerializable = ProtobufSerializable && requires(T* out, const google::protobuf::Message& msg) { Protobuf>::UnpackInto(out, msg); }; /** * Unpack a serialized protobuf message. * * @tparam T object type * @param msg protobuf message * @return Deserialized object */ template inline T UnpackProtobuf(const google::protobuf::Message& msg) { return Protobuf::Unpack(msg); } /** * Unpack a serialized protobuf array message. * * @tparam Proto element type of the protobuf array * @tparam T object type * @tparam N number of objects * @param msg protobuf array message * @return Deserialized array */ template Proto, ProtobufSerializable T, size_t N> wpi::array UnpackProtobufArray( const google::protobuf::RepeatedPtrField& msg) { if (N != std::dynamic_extent && msg.size() != N) { // TODO } wpi::array arr(wpi::empty_array); for (size_t i = 0; i < N; i++) { arr[i] = wpi::UnpackProtobuf(msg.Get(i)); } return arr; } /** * Unpack a serialized protobuf array message. * * @tparam T element type of the protobuf array * @tparam N number of objects * @param msg protobuf array message * @return Deserialized array */ template wpi::array UnpackProtobufArray( const google::protobuf::RepeatedField& msg) { if (N != std::dynamic_extent && msg.size() != N) { // TODO } wpi::array arr(wpi::empty_array); for (size_t i = 0; i < N; i++) { arr[i] = msg.Get(i); } return arr; } /** * Pack a serialized protobuf message. * * @param msg protobuf message (mutable, output) * @param value object */ template inline void PackProtobuf(google::protobuf::Message* msg, const T& value) { Protobuf>::Pack(msg, value); } /** * Pack a serialized protobuf array message. * * @tparam Proto element type of the protobuf array * @tparam T object type * @tparam N number of objects * @param msg protobuf message (mutable, output) * @param arr array of objects */ template Proto, ProtobufSerializable T, size_t N> void PackProtobufArray(google::protobuf::RepeatedPtrField* msg, const wpi::array& arr) { msg->Clear(); msg->Reserve(N); for (const auto& obj : arr) { PackProtobuf(msg->Add(), obj); } } /** * Pack a serialized protobuf array message. * * @tparam T object type * @tparam N number of objects * @param msg protobuf message (mutable, output) * @param arr array of objects */ template void PackProtobufArray(google::protobuf::RepeatedField* msg, const wpi::array& arr) { msg->Clear(); msg->Reserve(N); msg->Add(arr.begin(), arr.end()); } /** * Unpack a serialized struct into an existing object, overwriting its contents. * * @param out object (output) * @param msg protobuf message */ template inline void UnpackProtobufInto(T* out, const google::protobuf::Message& msg) { if constexpr (MutableProtobufSerializable) { Protobuf::UnpackInto(out, msg); } else { *out = UnpackProtobuf(msg); } } // these detail functions avoid the need to include protobuf headers namespace detail { void DeleteProtobuf(google::protobuf::Message* msg); bool ParseProtobuf(google::protobuf::Message* msg, std::span data); bool SerializeProtobuf(wpi::SmallVectorImpl& out, const google::protobuf::Message& msg); bool SerializeProtobuf(std::vector& out, const google::protobuf::Message& msg); std::string GetTypeString(const google::protobuf::Message& msg); void ForEachProtobufDescriptor( const google::protobuf::Message& msg, function_ref wants, function_ref descriptor)> fn); } // namespace detail /** * Owning wrapper (ala std::unique_ptr) for google::protobuf::Message* that does * not require the protobuf headers be included. Note this object is not thread * safe; users of this object are required to provide any necessary thread * safety. * * @tparam T serialized object type */ template class ProtobufMessage { public: explicit ProtobufMessage(google::protobuf::Arena* arena = nullptr) : m_msg{Protobuf::New(arena)} {} ~ProtobufMessage() { detail::DeleteProtobuf(m_msg); } ProtobufMessage(const ProtobufMessage&) = delete; ProtobufMessage& operator=(const ProtobufMessage&) = delete; ProtobufMessage(ProtobufMessage&& rhs) : m_msg{rhs.m_msg} { rhs.m_msg = nullptr; } ProtobufMessage& operator=(ProtobufMessage&& rhs) { std::swap(m_msg, rhs.m_msg); return *this; } /** * Gets the stored message object. * * @return google::protobuf::Message* */ google::protobuf::Message* GetMessage() { return m_msg; } const google::protobuf::Message* GetMessage() const { return m_msg; } /** * Unpacks from a byte array. * * @param data byte array * @return Optional; empty if parsing failed */ std::optional Unpack(std::span data) { if (!detail::ParseProtobuf(m_msg, data)) { return std::nullopt; } return Protobuf::Unpack(*m_msg); } /** * Unpacks from a byte array into an existing object. * * @param[out] out output object * @param[in] data byte array * @return true if successful */ bool UnpackInto(T* out, std::span data) { if (!detail::ParseProtobuf(m_msg, data)) { return false; } UnpackProtobufInto(out, *m_msg); return true; } /** * Packs object into a SmallVector. * * @param[out] out output bytes * @param[in] value value * @return true if successful */ bool Pack(wpi::SmallVectorImpl& out, const T& value) { Protobuf::Pack(m_msg, value); return detail::SerializeProtobuf(out, *m_msg); } /** * Packs object into a std::vector. * * @param[out] out output bytes * @param[in] value value * @return true if successful */ bool Pack(std::vector& out, const T& value) { Protobuf::Pack(m_msg, value); return detail::SerializeProtobuf(out, *m_msg); } /** * Gets the type string for the message. * * @return type string */ std::string GetTypeString() const { return detail::GetTypeString(*m_msg); } /** * Loops over all protobuf descriptors including nested/referenced * descriptors. * * @param exists function that returns false if fn should be called for the * given type string * @param fn function to call for each descriptor */ void ForEachProtobufDescriptor( function_ref exists, function_ref descriptor)> fn) { detail::ForEachProtobufDescriptor(*m_msg, exists, fn); } private: google::protobuf::Message* m_msg = nullptr; }; } // namespace wpi