WPILibC++ 2024.3.2
Protobuf.h
Go to the documentation of this file.
1// Copyright (c) FIRST and other WPILib contributors.
2// Open Source Software; you can modify and/or share it under the terms of
3// the WPILib BSD license file in the root directory of this project.
4
5#pragma once
6
7#include <stdint.h>
8
9#include <concepts>
10#include <optional>
11#include <span>
12#include <string>
13#include <string_view>
14#include <utility>
15#include <vector>
16
17#include "wpi/function_ref.h"
18
20class Arena;
21class Message;
22} // namespace google::protobuf
23
24namespace wpi {
25
26template <typename T>
27class SmallVectorImpl;
28
29/**
30 * Protobuf serialization template. Unspecialized class has no members; only
31 * specializations of this class are useful, and only if they meet the
32 * ProtobufSerializable concept.
33 *
34 * @tparam T type to serialize/deserialize
35 */
36template <typename T>
37struct Protobuf {};
38
39/**
40 * Specifies that a type is capable of protobuf serialization and
41 * deserialization.
42 *
43 * This is designed for serializing complex flexible data structures using
44 * code generated from a .proto file. Serialization consists of writing
45 * values into a mutable protobuf Message and deserialization consists of
46 * reading values from an immutable protobuf Message.
47 *
48 * Implementations must define a template specialization for wpi::Protobuf with
49 * T being the type that is being serialized/deserialized, with the following
50 * static members (as enforced by this concept):
51 * - google::protobuf::Message* New(google::protobuf::Arena*): create a protobuf
52 * message
53 * - T Unpack(const google::protobuf::Message&): function for deserialization
54 * - void Pack(google::protobuf::Message*, T&& value): function for
55 * serialization
56 *
57 * To avoid pulling in the protobuf headers, these functions use
58 * google::protobuf::Message instead of a more specific type; implementations
59 * will need to static_cast to the correct type as created by New().
60 *
61 * Additionally: In a static block, call StructRegistry.registerClass() to
62 * register the class
63 */
64template <typename T>
65concept ProtobufSerializable = requires(
66 google::protobuf::Arena* arena, const google::protobuf::Message& inmsg,
67 google::protobuf::Message* outmsg, const T& value) {
68 typename Protobuf<typename std::remove_cvref_t<T>>;
69 {
70 Protobuf<typename std::remove_cvref_t<T>>::New(arena)
71 } -> std::same_as<google::protobuf::Message*>;
72 {
73 Protobuf<typename std::remove_cvref_t<T>>::Unpack(inmsg)
74 } -> std::same_as<typename std::remove_cvref_t<T>>;
75 Protobuf<typename std::remove_cvref_t<T>>::Pack(outmsg, value);
76};
77
78/**
79 * Specifies that a type is capable of in-place protobuf deserialization.
80 *
81 * In addition to meeting ProtobufSerializable, implementations must define a
82 * wpi::Protobuf<T> static member
83 * `void UnpackInto(T*, const google::protobuf::Message&)` to update the
84 * pointed-to T with the contents of the message.
85 */
86template <typename T>
89 requires(T* out, const google::protobuf::Message& msg) {
90 Protobuf<typename std::remove_cvref_t<T>>::UnpackInto(out, msg);
91 };
92
93/**
94 * Unpack a serialized protobuf message.
95 *
96 * @tparam T object type
97 * @param msg protobuf message
98 * @return Deserialized object
99 */
100template <ProtobufSerializable T>
101inline T UnpackProtobuf(const google::protobuf::Message& msg) {
102 return Protobuf<T>::Unpack(msg);
103}
104
105/**
106 * Pack a serialized protobuf message.
107 *
108 * @param msg protobuf message (mutable, output)
109 * @param value object
110 */
111template <ProtobufSerializable T>
112inline void PackProtobuf(google::protobuf::Message* msg, const T& value) {
113 Protobuf<typename std::remove_cvref_t<T>>::Pack(msg, value);
114}
115
116/**
117 * Unpack a serialized struct into an existing object, overwriting its contents.
118 *
119 * @param out object (output)
120 * @param msg protobuf message
121 */
122template <ProtobufSerializable T>
123inline void UnpackProtobufInto(T* out, const google::protobuf::Message& msg) {
124 if constexpr (MutableProtobufSerializable<T>) {
125 Protobuf<T>::UnpackInto(out, msg);
126 } else {
127 *out = UnpackProtobuf<T>(msg);
128 }
129}
130
131// these detail functions avoid the need to include protobuf headers
132namespace detail {
133void DeleteProtobuf(google::protobuf::Message* msg);
134bool ParseProtobuf(google::protobuf::Message* msg,
135 std::span<const uint8_t> data);
137 const google::protobuf::Message& msg);
138bool SerializeProtobuf(std::vector<uint8_t>& out,
139 const google::protobuf::Message& msg);
140std::string GetTypeString(const google::protobuf::Message& msg);
142 const google::protobuf::Message& msg,
143 function_ref<bool(std::string_view filename)> wants,
144 function_ref<void(std::string_view filename,
145 std::span<const uint8_t> descriptor)>
146 fn);
147} // namespace detail
148
149/**
150 * Owning wrapper (ala std::unique_ptr) for google::protobuf::Message* that does
151 * not require the protobuf headers be included. Note this object is not thread
152 * safe; users of this object are required to provide any necessary thread
153 * safety.
154 *
155 * @tparam T serialized object type
156 */
157template <ProtobufSerializable T>
159 public:
160 explicit ProtobufMessage(google::protobuf::Arena* arena = nullptr)
161 : m_msg{Protobuf<T>::New(arena)} {}
165 ProtobufMessage(ProtobufMessage&& rhs) : m_msg{rhs.m_msg} {
166 rhs.m_msg = nullptr;
167 }
169 std::swap(m_msg, rhs.m_msg);
170 return *this;
171 }
172
173 /**
174 * Gets the stored message object.
175 *
176 * @return google::protobuf::Message*
177 */
178 google::protobuf::Message* GetMessage() { return m_msg; }
179 const google::protobuf::Message* GetMessage() const { return m_msg; }
180
181 /**
182 * Unpacks from a byte array.
183 *
184 * @param data byte array
185 * @return Optional; empty if parsing failed
186 */
187 std::optional<T> Unpack(std::span<const uint8_t> data) {
188 if (!detail::ParseProtobuf(m_msg, data)) {
189 return std::nullopt;
190 }
191 return Protobuf<T>::Unpack(*m_msg);
192 }
193
194 /**
195 * Unpacks from a byte array into an existing object.
196 *
197 * @param[out] out output object
198 * @param[in] data byte array
199 * @return true if successful
200 */
201 bool UnpackInto(T* out, std::span<const uint8_t> data) {
202 if (!detail::ParseProtobuf(m_msg, data)) {
203 return false;
204 }
205 UnpackProtobufInto(out, *m_msg);
206 return true;
207 }
208
209 /**
210 * Packs object into a SmallVector.
211 *
212 * @param[out] out output bytes
213 * @param[in] value value
214 * @return true if successful
215 */
216 bool Pack(wpi::SmallVectorImpl<uint8_t>& out, const T& value) {
217 Protobuf<T>::Pack(m_msg, value);
218 return detail::SerializeProtobuf(out, *m_msg);
219 }
220
221 /**
222 * Packs object into a std::vector.
223 *
224 * @param[out] out output bytes
225 * @param[in] value value
226 * @return true if successful
227 */
228 bool Pack(std::vector<uint8_t>& out, const T& value) {
229 Protobuf<T>::Pack(m_msg, value);
230 return detail::SerializeProtobuf(out, *m_msg);
231 }
232
233 /**
234 * Gets the type string for the message.
235 *
236 * @return type string
237 */
238 std::string GetTypeString() const { return detail::GetTypeString(*m_msg); }
239
240 /**
241 * Loops over all protobuf descriptors including nested/referenced
242 * descriptors.
243 *
244 * @param exists function that returns false if fn should be called for the
245 * given type string
246 * @param fn function to call for each descriptor
247 */
249 function_ref<bool(std::string_view filename)> exists,
250 function_ref<void(std::string_view filename,
251 std::span<const uint8_t> descriptor)>
252 fn) {
253 detail::ForEachProtobufDescriptor(*m_msg, exists, fn);
254 }
255
256 private:
257 google::protobuf::Message* m_msg = nullptr;
258};
259
260} // namespace wpi
Owning wrapper (ala std::unique_ptr) for google::protobuf::Message* that does not require the protobu...
Definition: Protobuf.h:158
std::string GetTypeString() const
Gets the type string for the message.
Definition: Protobuf.h:238
std::optional< T > Unpack(std::span< const uint8_t > data)
Unpacks from a byte array.
Definition: Protobuf.h:187
ProtobufMessage & operator=(ProtobufMessage &&rhs)
Definition: Protobuf.h:168
bool UnpackInto(T *out, std::span< const uint8_t > data)
Unpacks from a byte array into an existing object.
Definition: Protobuf.h:201
ProtobufMessage(ProtobufMessage &&rhs)
Definition: Protobuf.h:165
ProtobufMessage(google::protobuf::Arena *arena=nullptr)
Definition: Protobuf.h:160
google::protobuf::Message * GetMessage()
Gets the stored message object.
Definition: Protobuf.h:178
~ProtobufMessage()
Definition: Protobuf.h:162
const google::protobuf::Message * GetMessage() const
Definition: Protobuf.h:179
ProtobufMessage & operator=(const ProtobufMessage &)=delete
bool Pack(std::vector< uint8_t > &out, const T &value)
Packs object into a std::vector.
Definition: Protobuf.h:228
ProtobufMessage(const ProtobufMessage &)=delete
void ForEachProtobufDescriptor(function_ref< bool(std::string_view filename)> exists, function_ref< void(std::string_view filename, std::span< const uint8_t > descriptor)> fn)
Loops over all protobuf descriptors including nested/referenced descriptors.
Definition: Protobuf.h:248
bool Pack(wpi::SmallVectorImpl< uint8_t > &out, const T &value)
Packs object into a SmallVector.
Definition: Protobuf.h:216
An efficient, type-erasing, non-owning reference to a callable.
Definition: function_ref.h:31
Specifies that a type is capable of in-place protobuf deserialization.
Definition: Protobuf.h:87
Specifies that a type is capable of protobuf serialization and deserialization.
Definition: Protobuf.h:65
basic_string_view< char > string_view
Definition: core.h:501
detail namespace with internal helper functions
Definition: xchar.h:20
Definition: Protobuf.h:19
WPI_BASIC_JSON_TPL_DECLARATION void swap(wpi::WPI_BASIC_JSON_TPL &j1, wpi::WPI_BASIC_JSON_TPL &j2) noexcept(//NOLINT(readability-inconsistent-declaration-parameter-name) is_nothrow_move_constructible< wpi::WPI_BASIC_JSON_TPL >::value &&//NOLINT(misc-redundant-expression) is_nothrow_move_assignable< wpi::WPI_BASIC_JSON_TPL >::value)
exchanges the values of two JSON objects
Definition: json.h:5219
void ForEachProtobufDescriptor(const google::protobuf::Message &msg, function_ref< bool(std::string_view filename)> wants, function_ref< void(std::string_view filename, std::span< const uint8_t > descriptor)> fn)
std::string GetTypeString(const google::protobuf::Message &msg)
bool SerializeProtobuf(wpi::SmallVectorImpl< uint8_t > &out, const google::protobuf::Message &msg)
bool ParseProtobuf(google::protobuf::Message *msg, std::span< const uint8_t > data)
void DeleteProtobuf(google::protobuf::Message *msg)
Definition: ntcore_cpp.h:26
void PackProtobuf(google::protobuf::Message *msg, const T &value)
Pack a serialized protobuf message.
Definition: Protobuf.h:112
T UnpackProtobuf(const google::protobuf::Message &msg)
Unpack a serialized protobuf message.
Definition: Protobuf.h:101
void UnpackProtobufInto(T *out, const google::protobuf::Message &msg)
Unpack a serialized struct into an existing object, overwriting its contents.
Definition: Protobuf.h:123
Protobuf serialization template.
Definition: Protobuf.h:37
static void Pack(std::span< uint8_t > data, const frc::DifferentialDriveWheelVoltages &value)
static google::protobuf::Message * New(google::protobuf::Arena *arena)
static constexpr std::string_view GetTypeString()
Definition: DifferentialDriveWheelVoltagesStruct.h:14
static frc::DifferentialDriveWheelVoltages Unpack(std::span< const uint8_t > data)