WPILibC++ 2025.2.1
Loading...
Searching...
No Matches
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 "pb.h"
18#include "pb_decode.h"
19#include "pb_encode.h"
20#include "wpi/array.h"
21#include "wpi/function_ref.h"
22
23namespace wpi {
24
25template <typename T>
26class SmallVectorImpl;
27
28/**
29 * Protobuf serialization template. Unspecialized class has no members; only
30 * specializations of this class are useful, and only if they meet the
31 * ProtobufSerializable concept.
32 *
33 * @tparam T type to serialize/deserialize
34 */
35template <typename T>
36struct Protobuf {};
37
38namespace detail {
40using StdVectorType = std::vector<uint8_t>;
42 size_t count);
43
44bool WriteFromStdVector(pb_ostream_t* stream, const pb_byte_t* buf,
45 size_t count);
46
47bool WriteSubmessage(pb_ostream_t* stream, const pb_msgdesc_t* desc,
48 const void* msg);
49} // namespace detail
50
51/**
52 * Class for wrapping a nanopb istream.
53 */
54template <typename T>
56 public:
57 /**
58 * Constructs a nanopb istream from an existing istream object.
59 * Generally used internally for decoding submessages
60 *
61 * @param[in] stream the nanopb istream
62 */
64 : m_streamMsg{stream},
65 m_msgDesc{
66 Protobuf<std::remove_cvref_t<T>>::MessageStruct::msg_descriptor()} {
67 }
68
69 /**
70 * Constructs a nanopb istream from a buffer.
71 *
72 * @param[in] stream the stream buffer
73 */
74 explicit ProtoInputStream(std::span<const uint8_t> stream)
75 : m_streamLocal{pb_istream_from_buffer(
76 reinterpret_cast<const pb_byte_t*>(stream.data()), stream.size())},
77 m_msgDesc{
78 Protobuf<std::remove_cvref_t<T>>::MessageStruct::msg_descriptor()} {
79 }
80
81 /**
82 * Gets the backing nanopb stream object.
83 *
84 * @return nanopb stream
85 */
86 pb_istream_t* Stream() noexcept {
87 return m_streamMsg ? m_streamMsg : &m_streamLocal;
88 }
89
90 /**
91 * Gets the nanopb message descriptor
92 *
93 * @return the nanopb message descriptor
94 */
95 const pb_msgdesc_t* MsgDesc() const noexcept { return m_msgDesc; }
96
97 /**
98 * Decodes a protobuf. Flags are the same flags passed to pb_decode_ex.
99 *
100 * @param[in] msg The message to decode into
101 * @param[in] flags Flags to pass
102 * @return true if decoding was successful, false otherwise
103 */
104 bool Decode(typename Protobuf<std::remove_cvref_t<T>>::MessageStruct& msg,
105 unsigned int flags = 0) {
106 return pb_decode_ex(Stream(), m_msgDesc, &msg, flags);
107 }
108
109 private:
110 pb_istream_t m_streamLocal;
111 pb_istream_t* m_streamMsg{nullptr};
112 const pb_msgdesc_t* m_msgDesc;
113};
114
115/**
116 * Class for wrapping a nanopb ostream
117 */
118template <typename T>
120 public:
121 /**
122 * Constructs a nanopb ostream from an existing ostream object
123 * Generally used internally for encoding messages.
124 *
125 * This constructor will cause `Encode` to call pb_encode_submessage
126 * instead of `pb_encode_ex`
127 *
128 * @param[in] stream the nanopb ostream
129 */
131 : m_streamMsg{stream},
132 m_msgDesc{
133 Protobuf<std::remove_cvref_t<T>>::MessageStruct::msg_descriptor()} {
134 }
135
136 /**
137 * Constructs a nanopb ostream from a buffer.
138 *
139 * This constructor will cause `Encode` to call pb_encode_ex`
140 *
141 * @param[in] out the stream buffer
142 */
144 : m_msgDesc{
145 Protobuf<std::remove_cvref_t<T>>::MessageStruct::msg_descriptor()} {
147 m_streamLocal.state = &out;
148 m_streamLocal.max_size = SIZE_MAX;
149 m_streamLocal.bytes_written = 0;
150 m_streamLocal.errmsg = nullptr;
151 }
152
153 /**
154 * Constructs a nanopb ostream from a buffer.
155 *
156 * This constructor will cause `Encode` to call pb_encode_ex`
157 *
158 * @param[in] out the stream buffer
159 */
161 : m_msgDesc{
162 Protobuf<std::remove_cvref_t<T>>::MessageStruct::msg_descriptor()} {
163 m_streamLocal.callback = detail::WriteFromStdVector;
164 m_streamLocal.state = &out;
165 m_streamLocal.max_size = SIZE_MAX;
166 m_streamLocal.bytes_written = 0;
167 m_streamLocal.errmsg = nullptr;
168 }
169
170 /**
171 * Constructs a empty nanopb stream. You must fill out the stream
172 * returned from `Stream` before calling Encode.
173 *
174 * This constructor exists to cause `Encode` to call pb_encode_ex`,
175 * but allow manipulating the stream manually.
176 */
178 : m_msgDesc{Protobuf<
179 std::remove_cvref_t<T>>::MessageStruct::msg_descriptor()} {}
180
181 /**
182 * Gets the backing nanopb stream object.
183 *
184 * @return nanopb stream
185 */
186 pb_ostream_t* Stream() noexcept {
187 return m_streamMsg ? m_streamMsg : &m_streamLocal;
188 }
189
190 /**
191 * Gets if this stream points to a submessage, and will call
192 * pb_encode_submessage instead of pb_encode
193 *
194 * @return true if submessage, false otherwise
195 */
196 bool IsSubmessage() const noexcept { return m_streamMsg; }
197
198 /**
199 * Gets the nanopb message descriptor
200 *
201 * @return the nanopb message descriptor
202 */
203 const pb_msgdesc_t* MsgDesc() const noexcept { return m_msgDesc; }
204
205 /**
206 * Decodes a protobuf. Flags are the same flags passed to pb_decode_ex.
207 *
208 * @param[in] msg The message to encode from
209 * @return true if encoding was successful, false otherwise
210 */
211 bool Encode(
212 const typename Protobuf<std::remove_cvref_t<T>>::MessageStruct& msg) {
213 if (m_streamMsg) {
214 return detail::WriteSubmessage(m_streamMsg, m_msgDesc, &msg);
215 // return pb_encode_submessage(m_streamMsg, m_msgDesc, &msg);
216 }
217 return pb_encode(&m_streamLocal, m_msgDesc, &msg);
218 }
219
220 private:
221 pb_ostream_t m_streamLocal;
222 pb_ostream_t* m_streamMsg{nullptr};
223 const pb_msgdesc_t* m_msgDesc;
224};
225
226/**
227 * Specifies that a type is capable of protobuf serialization and
228 * deserialization.
229 *
230 * This is designed for serializing complex flexible data structures using
231 * code generated from a .proto file. Serialization consists of writing
232 * values into a nanopb Stream and deserialization consists of
233 * reading values from nanopb Stream.
234 *
235 * Implementations must define a template specialization for wpi::Protobuf with
236 * T being the type that is being serialized/deserialized, with the following
237 * static members (as enforced by this concept):
238 * - using MessageStruct = nanopb_message_struct_here: typedef to the wpilib
239 * modified nanopb message struct
240 * - std::optional<T> Unpack(wpi::ProtoInputStream<T>&): function
241 * for deserialization
242 * - bool Pack(wpi::ProtoOutputStream<T>&, T&& value): function
243 * for serialization
244 *
245 * As a suggestion, 2 extra type usings can be added to simplify the stream
246 * definitions, however these are not required.
247 * - using InputStream = wpi::ProtoInputStream<T>;
248 * - using OutputStream = wpi::ProtoOutputStream<T>;
249 */
250template <typename T>
251concept ProtobufSerializable = requires(
253 wpi::ProtoInputStream<std::remove_cvref_t<T>>& istream, const T& value) {
254 typename Protobuf<typename std::remove_cvref_t<T>>;
255 {
256 Protobuf<typename std::remove_cvref_t<T>>::Unpack(istream)
257 } -> std::same_as<std::optional<typename std::remove_cvref_t<T>>>;
258 {
259 Protobuf<typename std::remove_cvref_t<T>>::Pack(ostream, value)
260 } -> std::same_as<bool>;
261 typename Protobuf<typename std::remove_cvref_t<T>>::MessageStruct;
262 {
263 Protobuf<typename std::remove_cvref_t<T>>::MessageStruct::msg_descriptor()
264 } -> std::same_as<const pb_msgdesc_t*>;
265 {
266 Protobuf<typename std::remove_cvref_t<T>>::MessageStruct::msg_name()
267 } -> std::same_as<std::string_view>;
268 {
269 Protobuf<typename std::remove_cvref_t<T>>::MessageStruct::file_descriptor()
270 } -> std::same_as<pb_filedesc_t>;
271};
272
273/**
274 * Specifies that a type is capable of in-place protobuf deserialization.
275 *
276 * In addition to meeting ProtobufSerializable, implementations must define a
277 * wpi::Protobuf<T> static member
278 * - bool UnpackInto(T*, wpi::ProtoInputStream<T>&)` to update the
279 * pointed-to T with the contents of the message.
280 */
281template <typename T>
284 requires(T* out, wpi::ProtoInputStream<T>& istream) {
285 {
286 Protobuf<typename std::remove_cvref_t<T>>::UnpackInto(out, istream)
287 } -> std::same_as<bool>;
288 };
289
290namespace detail {
291std::string GetTypeString(const pb_msgdesc_t* msg);
293 const pb_msgdesc_t* msg,
294 function_ref<bool(std::string_view filename)> wants,
295 function_ref<void(std::string_view filename,
296 std::span<const uint8_t> descriptor)>
297 fn);
298} // namespace detail
299
300/**
301 * Ease of use wrapper to make nanopb streams more opaque to the user.
302 * This class is stateless and thread safe.
303 *
304 * @tparam T serialized object type
305 */
306template <ProtobufSerializable T>
308 public:
309 /**
310 * Unpacks from a byte array.
311 *
312 * @param data byte array
313 * @return Optional; empty if parsing failed
314 */
315 std::optional<std::remove_cvref_t<T>> Unpack(std::span<const uint8_t> data) {
318 }
319
320 /**
321 * Unpacks from a byte array into an existing object.
322 *
323 * @param[out] out output object
324 * @param[in] data byte array
325 * @return true if successful
326 */
327 bool UnpackInto(T* out, std::span<const uint8_t> data) {
328 if constexpr (MutableProtobufSerializable<T>) {
331 } else {
332 auto unpacked = Unpack(data);
333 if (!unpacked) {
334 return false;
335 }
336 *out = std::move(unpacked.value());
337 return true;
338 }
339 }
340
341 /**
342 * Packs object into a SmallVector.
343 *
344 * @param[out] out output bytes
345 * @param[in] value value
346 * @return true if successful
347 */
348 bool Pack(wpi::SmallVectorImpl<uint8_t>& out, const T& value) {
350 return Protobuf<std::remove_cvref_t<T>>::Pack(stream, value);
351 }
352
353 /**
354 * Packs object into a std::vector.
355 *
356 * @param[out] out output bytes
357 * @param[in] value value
358 * @return true if successful
359 */
360 bool Pack(std::vector<uint8_t>& out, const T& value) {
362 return Protobuf<std::remove_cvref_t<T>>::Pack(stream, value);
363 }
364
365 /**
366 * Gets the type string for the message.
367 *
368 * @return type string
369 */
370 std::string GetTypeString() const {
372 Protobuf<std::remove_cvref_t<T>>::MessageStruct::msg_descriptor());
373 }
374
375 /**
376 * Loops over all protobuf descriptors including nested/referenced
377 * descriptors.
378 *
379 * @param exists function that returns false if fn should be called for the
380 * given type string
381 * @param fn function to call for each descriptor
382 */
384 function_ref<bool(std::string_view filename)> exists,
385 function_ref<void(std::string_view filename,
386 std::span<const uint8_t> descriptor)>
387 fn) {
389 Protobuf<std::remove_cvref_t<T>>::MessageStruct::msg_descriptor(),
390 exists, fn);
391 }
392};
393
394} // namespace wpi
Class for wrapping a nanopb istream.
Definition Protobuf.h:55
bool Decode(typename Protobuf< std::remove_cvref_t< T > >::MessageStruct &msg, unsigned int flags=0)
Decodes a protobuf.
Definition Protobuf.h:104
ProtoInputStream(pb_istream_t *stream)
Constructs a nanopb istream from an existing istream object.
Definition Protobuf.h:63
pb_istream_t * Stream() noexcept
Gets the backing nanopb stream object.
Definition Protobuf.h:86
const pb_msgdesc_t * MsgDesc() const noexcept
Gets the nanopb message descriptor.
Definition Protobuf.h:95
ProtoInputStream(std::span< const uint8_t > stream)
Constructs a nanopb istream from a buffer.
Definition Protobuf.h:74
Class for wrapping a nanopb ostream.
Definition Protobuf.h:119
ProtoOutputStream(detail::StdVectorType &out)
Constructs a nanopb ostream from a buffer.
Definition Protobuf.h:160
const pb_msgdesc_t * MsgDesc() const noexcept
Gets the nanopb message descriptor.
Definition Protobuf.h:203
ProtoOutputStream()
Constructs a empty nanopb stream.
Definition Protobuf.h:177
bool IsSubmessage() const noexcept
Gets if this stream points to a submessage, and will call pb_encode_submessage instead of pb_encode.
Definition Protobuf.h:196
pb_ostream_t * Stream() noexcept
Gets the backing nanopb stream object.
Definition Protobuf.h:186
ProtoOutputStream(pb_ostream_t *stream)
Constructs a nanopb ostream from an existing ostream object Generally used internally for encoding me...
Definition Protobuf.h:130
bool Encode(const typename Protobuf< std::remove_cvref_t< T > >::MessageStruct &msg)
Decodes a protobuf.
Definition Protobuf.h:211
ProtoOutputStream(detail::SmallVectorType &out)
Constructs a nanopb ostream from a buffer.
Definition Protobuf.h:143
Ease of use wrapper to make nanopb streams more opaque to the user.
Definition Protobuf.h:307
std::string GetTypeString() const
Gets the type string for the message.
Definition Protobuf.h:370
bool UnpackInto(T *out, std::span< const uint8_t > data)
Unpacks from a byte array into an existing object.
Definition Protobuf.h:327
std::optional< std::remove_cvref_t< T > > Unpack(std::span< const uint8_t > data)
Unpacks from a byte array.
Definition Protobuf.h:315
bool Pack(std::vector< uint8_t > &out, const T &value)
Packs object into a std::vector.
Definition Protobuf.h:360
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:383
bool Pack(wpi::SmallVectorImpl< uint8_t > &out, const T &value)
Packs object into a SmallVector.
Definition Protobuf.h:348
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:282
Specifies that a type is capable of protobuf serialization and deserialization.
Definition Protobuf.h:251
detail namespace with internal helper functions
Definition input_adapters.h:32
Implement std::hash so that hash_code can be used in STL containers.
Definition PointerIntPair.h:280
bool WriteFromStdVector(pb_ostream_t *stream, const pb_byte_t *buf, size_t count)
std::string GetTypeString(const pb_msgdesc_t *msg)
bool WriteSubmessage(pb_ostream_t *stream, const pb_msgdesc_t *desc, const void *msg)
bool WriteFromSmallVector(pb_ostream_t *stream, const pb_byte_t *buf, size_t count)
std::vector< uint8_t > StdVectorType
Definition Protobuf.h:40
void ForEachProtobufDescriptor(const pb_msgdesc_t *msg, function_ref< bool(std::string_view filename)> wants, function_ref< void(std::string_view filename, std::span< const uint8_t > descriptor)> fn)
Foonathan namespace.
Definition ntcore_cpp.h:26
flags
Definition http_parser.h:206
uint_least8_t pb_byte_t
Definition pb.h:228
pb_istream_t pb_istream_from_buffer(const pb_byte_t *buf, size_t msglen)
bool pb_decode_ex(pb_istream_t *stream, const pb_msgdesc_t *fields, void *dest_struct, unsigned int flags)
bool pb_encode(pb_ostream_t *stream, const pb_msgdesc_t *fields, const void *src_struct)
Definition pb_decode.h:27
Definition pb.h:331
Definition pb_encode.h:26
size_t bytes_written
Definition pb_encode.h:49
bool(* callback)(pb_ostream_t *stream, const pb_byte_t *buf, size_t count)
Definition pb_encode.h:36
const char * errmsg
Definition pb_encode.h:53
void * state
Definition pb_encode.h:43
size_t max_size
Definition pb_encode.h:46
Protobuf serialization template.
Definition Protobuf.h:36
typename std::remove_cv< remove_reference_t< T > >::type remove_cvref_t
Definition base.h:306