xref: /aosp_15_r20/external/cronet/net/third_party/quiche/src/quiche/common/wire_serialization.h (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
1 // Copyright (c) 2022 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 // wire_serialization.h -- absl::StrCat()-like interface for QUICHE wire format.
6 //
7 // When serializing a data structure, there are two common approaches:
8 //   (1) Allocate into a dynamically sized buffer and incur the costs of memory
9 //       allocations.
10 //   (2) Precompute the length of the structure, allocate a buffer of the
11 //       exact required size and then write into the said buffer.
12 //  QUICHE generally takes the second approach, but as a result, a lot of
13 //  serialization code is written twice. This API avoids this issue by letting
14 //  the caller declaratively describe the wire format; the description provided
15 //  is used both for the size computation and for the serialization.
16 //
17 // Consider the following struct in RFC 9000 language:
18 //   Test Struct {
19 //     Magic Value (32),
20 //     Some Number (i),
21 //     [Optional Number (i)],
22 //     Magical String Length (i),
23 //     Magical String (..),
24 //   }
25 //
26 // Using the functions in this header, it can be serialized as follows:
27 //   absl::StatusOr<quiche::QuicheBuffer> test_struct = SerializeIntoBuffer(
28 //     WireUint32(magic_value),
29 //     WireVarInt62(some_number),
30 //     WireOptional<WireVarint62>(optional_number),
31 //     WireStringWithVarInt62Length(magical_string)
32 //   );
33 //
34 // This header provides three main functions with fairly self-explanatory names:
35 //  - size_t ComputeLengthOnWire(d1, d2, ... dN)
36 //  - absl::Status SerializeIntoWriter(writer, d1, d2, ... dN)
37 //  - absl::StatusOr<QuicheBuffer> SerializeIntoBuffer(allocator, d1, ... dN)
38 //
39 // It is possible to define a custom serializer for individual structs. Those
40 // would normally look like this:
41 //
42 //     struct AwesomeStruct { ... }
43 //     class WireAwesomeStruct {
44 //      public:
45 //       using DataType = AwesomeStruct;
46 //       WireAwesomeStruct(const AwesomeStruct& awesome) : awesome_(awesome) {}
47 //       size_t GetLengthOnWire() { ... }
48 //       absl::Status SerializeIntoWriter(QuicheDataWriter& writer) { ... }
49 //     };
50 //
51 // See the unit test for the full version of the example above.
52 
53 #ifndef QUICHE_COMMON_WIRE_SERIALIZATION_H_
54 #define QUICHE_COMMON_WIRE_SERIALIZATION_H_
55 
56 #include <cstddef>
57 #include <cstdint>
58 #include <optional>
59 #include <tuple>
60 #include <type_traits>
61 #include <utility>
62 
63 #include "absl/base/attributes.h"
64 #include "absl/status/status.h"
65 #include "absl/status/statusor.h"
66 #include "absl/strings/string_view.h"
67 #include "quiche/common/platform/api/quiche_logging.h"
68 #include "quiche/common/quiche_buffer_allocator.h"
69 #include "quiche/common/quiche_data_writer.h"
70 #include "quiche/common/quiche_status_utils.h"
71 
72 namespace quiche {
73 
74 // T::SerializeIntoWriter() is allowed to return both a bool and an
75 // absl::Status.  There are two reasons for that:
76 //   1. Most QuicheDataWriter methods return a bool.
77 //   2. While cheap, absl::Status has a non-trivial destructor and thus is not
78 //      as free as a bool is.
79 // To accomodate this, SerializeIntoWriterStatus<T> provides a way to deduce
80 // what is the status type returned by the SerializeIntoWriter method.
81 template <typename T>
82 class QUICHE_NO_EXPORT SerializeIntoWriterStatus {
83  public:
84   static_assert(std::is_trivially_copyable_v<T> && sizeof(T) <= 32,
85                 "The types passed into SerializeInto() APIs are passed by "
86                 "value; if your type has non-trivial copy costs, it should be "
87                 "wrapped into a type that carries a pointer");
88 
89   using Type = decltype(std::declval<T>().SerializeIntoWriter(
90       std::declval<QuicheDataWriter&>()));
91   static constexpr bool kIsBool = std::is_same_v<Type, bool>;
92   static constexpr bool kIsStatus = std::is_same_v<Type, absl::Status>;
93   static_assert(
94       kIsBool || kIsStatus,
95       "SerializeIntoWriter() has to return either a bool or an absl::Status");
96 
OkValue()97   static ABSL_ATTRIBUTE_ALWAYS_INLINE Type OkValue() {
98     if constexpr (kIsStatus) {
99       return absl::OkStatus();
100     } else {
101       return true;
102     }
103   }
104 };
105 
IsWriterStatusOk(bool status)106 inline ABSL_ATTRIBUTE_ALWAYS_INLINE bool IsWriterStatusOk(bool status) {
107   return status;
108 }
IsWriterStatusOk(const absl::Status & status)109 inline ABSL_ATTRIBUTE_ALWAYS_INLINE bool IsWriterStatusOk(
110     const absl::Status& status) {
111   return status.ok();
112 }
113 
114 // ------------------- WireType() wrapper definitions -------------------
115 
116 // Base class for WireUint8/16/32/64.
117 template <typename T>
118 class QUICHE_EXPORT WireFixedSizeIntBase {
119  public:
120   using DataType = T;
121   static_assert(std::is_integral_v<DataType>,
122                 "WireFixedSizeIntBase is only usable with integral types");
123 
WireFixedSizeIntBase(T value)124   explicit WireFixedSizeIntBase(T value) { value_ = value; }
GetLengthOnWire()125   size_t GetLengthOnWire() const { return sizeof(T); }
value()126   T value() const { return value_; }
127 
128  private:
129   T value_;
130 };
131 
132 // Fixed-size integer fields.  Correspond to (8), (16), (32) and (64) fields in
133 // RFC 9000 language.
134 class QUICHE_EXPORT WireUint8 : public WireFixedSizeIntBase<uint8_t> {
135  public:
136   using WireFixedSizeIntBase::WireFixedSizeIntBase;
SerializeIntoWriter(QuicheDataWriter & writer)137   bool SerializeIntoWriter(QuicheDataWriter& writer) const {
138     return writer.WriteUInt8(value());
139   }
140 };
141 class QUICHE_EXPORT WireUint16 : public WireFixedSizeIntBase<uint16_t> {
142  public:
143   using WireFixedSizeIntBase::WireFixedSizeIntBase;
SerializeIntoWriter(QuicheDataWriter & writer)144   bool SerializeIntoWriter(QuicheDataWriter& writer) const {
145     return writer.WriteUInt16(value());
146   }
147 };
148 class QUICHE_EXPORT WireUint32 : public WireFixedSizeIntBase<uint32_t> {
149  public:
150   using WireFixedSizeIntBase::WireFixedSizeIntBase;
SerializeIntoWriter(QuicheDataWriter & writer)151   bool SerializeIntoWriter(QuicheDataWriter& writer) const {
152     return writer.WriteUInt32(value());
153   }
154 };
155 class QUICHE_EXPORT WireUint64 : public WireFixedSizeIntBase<uint64_t> {
156  public:
157   using WireFixedSizeIntBase::WireFixedSizeIntBase;
SerializeIntoWriter(QuicheDataWriter & writer)158   bool SerializeIntoWriter(QuicheDataWriter& writer) const {
159     return writer.WriteUInt64(value());
160   }
161 };
162 
163 // Represents a 62-bit variable-length non-negative integer.  Those are
164 // described in the Section 16 of RFC 9000, and are denoted as (i) in type
165 // descriptions.
166 class QUICHE_EXPORT WireVarInt62 {
167  public:
168   using DataType = uint64_t;
169 
WireVarInt62(uint64_t value)170   explicit WireVarInt62(uint64_t value) { value_ = value; }
171   // Convenience wrapper. This is safe, since it is clear from the context that
172   // the enum is being treated as an integer.
173   template <typename T>
WireVarInt62(T value)174   explicit WireVarInt62(T value) {
175     static_assert(std::is_enum_v<T> || std::is_convertible_v<T, uint64_t>);
176     value_ = static_cast<uint64_t>(value);
177   }
178 
GetLengthOnWire()179   size_t GetLengthOnWire() const {
180     return QuicheDataWriter::GetVarInt62Len(value_);
181   }
SerializeIntoWriter(QuicheDataWriter & writer)182   bool SerializeIntoWriter(QuicheDataWriter& writer) const {
183     return writer.WriteVarInt62(value_);
184   }
185 
186  private:
187   uint64_t value_;
188 };
189 
190 // Represents unframed raw string.
191 class QUICHE_EXPORT WireBytes {
192  public:
193   using DataType = absl::string_view;
194 
WireBytes(absl::string_view value)195   explicit WireBytes(absl::string_view value) { value_ = value; }
GetLengthOnWire()196   size_t GetLengthOnWire() { return value_.size(); }
SerializeIntoWriter(QuicheDataWriter & writer)197   bool SerializeIntoWriter(QuicheDataWriter& writer) {
198     return writer.WriteStringPiece(value_);
199   }
200 
201  private:
202   absl::string_view value_;
203 };
204 
205 // Represents a string where another wire type is used as a length prefix.
206 template <class LengthWireType>
207 class QUICHE_EXPORT WireStringWithLengthPrefix {
208  public:
209   using DataType = absl::string_view;
210 
WireStringWithLengthPrefix(absl::string_view value)211   explicit WireStringWithLengthPrefix(absl::string_view value) {
212     value_ = value;
213   }
GetLengthOnWire()214   size_t GetLengthOnWire() {
215     return LengthWireType(value_.size()).GetLengthOnWire() + value_.size();
216   }
SerializeIntoWriter(QuicheDataWriter & writer)217   absl::Status SerializeIntoWriter(QuicheDataWriter& writer) {
218     if (!LengthWireType(value_.size()).SerializeIntoWriter(writer)) {
219       return absl::InternalError("Failed to serialize the length prefix");
220     }
221     if (!writer.WriteStringPiece(value_)) {
222       return absl::InternalError("Failed to serialize the string proper");
223     }
224     return absl::OkStatus();
225   }
226 
227  private:
228   absl::string_view value_;
229 };
230 
231 // Represents varint62-prefixed strings.
232 using WireStringWithVarInt62Length = WireStringWithLengthPrefix<WireVarInt62>;
233 
234 // Allows std::optional to be used with this API. For instance, if the spec
235 // defines
236 //   [Context ID (i)]
237 // and the value is stored as std::optional<uint64> context_id, this can be
238 // recorded as
239 //   WireOptional<WireVarInt62>(context_id)
240 // When optional is absent, nothing is written onto the wire.
241 template <typename WireType, typename InnerType = typename WireType::DataType>
242 class QUICHE_EXPORT WireOptional {
243  public:
244   using DataType = std::optional<InnerType>;
245   using Status = SerializeIntoWriterStatus<WireType>;
246 
WireOptional(DataType value)247   explicit WireOptional(DataType value) { value_ = value; }
GetLengthOnWire()248   size_t GetLengthOnWire() const {
249     return value_.has_value() ? WireType(*value_).GetLengthOnWire() : 0;
250   }
SerializeIntoWriter(QuicheDataWriter & writer)251   typename Status::Type SerializeIntoWriter(QuicheDataWriter& writer) const {
252     if (value_.has_value()) {
253       return WireType(*value_).SerializeIntoWriter(writer);
254     }
255     return Status::OkValue();
256   }
257 
258  private:
259   DataType value_;
260 };
261 
262 // Allows multiple entries of the same type to be serialized in a single call.
263 template <typename WireType,
264           typename SpanElementType = typename WireType::DataType>
265 class QUICHE_EXPORT WireSpan {
266  public:
267   using DataType = absl::Span<const SpanElementType>;
268 
WireSpan(DataType value)269   explicit WireSpan(DataType value) { value_ = value; }
GetLengthOnWire()270   size_t GetLengthOnWire() const {
271     size_t total = 0;
272     for (const SpanElementType& value : value_) {
273       total += WireType(value).GetLengthOnWire();
274     }
275     return total;
276   }
SerializeIntoWriter(QuicheDataWriter & writer)277   absl::Status SerializeIntoWriter(QuicheDataWriter& writer) const {
278     for (size_t i = 0; i < value_.size(); i++) {
279       // `status` here can be either a bool or an absl::Status.
280       auto status = WireType(value_[i]).SerializeIntoWriter(writer);
281       if (IsWriterStatusOk(status)) {
282         continue;
283       }
284       if constexpr (SerializeIntoWriterStatus<WireType>::kIsStatus) {
285         return AppendToStatus(std::move(status),
286                               " while serializing the value #", i);
287       } else {
288         return absl::InternalError(
289             absl::StrCat("Failed to serialize vector value #", i));
290       }
291     }
292     return absl::OkStatus();
293   }
294 
295  private:
296   DataType value_;
297 };
298 
299 // ------------------- Top-level serialization API -------------------
300 
301 namespace wire_serialization_internal {
302 template <typename T>
SerializeIntoWriterWrapper(QuicheDataWriter & writer,int argno,T data)303 auto SerializeIntoWriterWrapper(QuicheDataWriter& writer, int argno, T data) {
304 #if defined(NDEBUG)
305   (void)argno;
306   (void)data;
307   return data.SerializeIntoWriter(writer);
308 #else
309   // When running in the debug build, we check that the length reported by
310   // GetLengthOnWire() matches what is actually being written onto the wire.
311   // While any mismatch will most likely lead to an error further down the line,
312   // this simplifies the debugging process.
313   const size_t initial_offset = writer.length();
314   const size_t expected_size = data.GetLengthOnWire();
315   auto result = data.SerializeIntoWriter(writer);
316   const size_t final_offset = writer.length();
317   if (IsWriterStatusOk(result)) {
318     QUICHE_DCHECK_EQ(initial_offset + expected_size, final_offset)
319         << "while serializing field #" << argno;
320   }
321   return result;
322 #endif
323 }
324 
325 template <typename T>
326 std::enable_if_t<SerializeIntoWriterStatus<T>::kIsBool, absl::Status>
SerializeIntoWriterCore(QuicheDataWriter & writer,int argno,T data)327 SerializeIntoWriterCore(QuicheDataWriter& writer, int argno, T data) {
328   const bool success = SerializeIntoWriterWrapper(writer, argno, data);
329   if (!success) {
330     return absl::InternalError(
331         absl::StrCat("Failed to serialize field #", argno));
332   }
333   return absl::OkStatus();
334 }
335 
336 template <typename T>
337 std::enable_if_t<SerializeIntoWriterStatus<T>::kIsStatus, absl::Status>
SerializeIntoWriterCore(QuicheDataWriter & writer,int argno,T data)338 SerializeIntoWriterCore(QuicheDataWriter& writer, int argno, T data) {
339   return AppendToStatus(SerializeIntoWriterWrapper(writer, argno, data),
340                         " while serializing field #", argno);
341 }
342 
343 template <typename T1, typename... Ts>
SerializeIntoWriterCore(QuicheDataWriter & writer,int argno,T1 data1,Ts...rest)344 absl::Status SerializeIntoWriterCore(QuicheDataWriter& writer, int argno,
345                                      T1 data1, Ts... rest) {
346   QUICHE_RETURN_IF_ERROR(SerializeIntoWriterCore(writer, argno, data1));
347   return SerializeIntoWriterCore(writer, argno + 1, rest...);
348 }
349 
SerializeIntoWriterCore(QuicheDataWriter &,int)350 inline absl::Status SerializeIntoWriterCore(QuicheDataWriter&, int) {
351   return absl::OkStatus();
352 }
353 }  // namespace wire_serialization_internal
354 
355 // SerializeIntoWriter(writer, d1, d2, ... dN) serializes all of supplied data
356 // into the writer |writer|.  True is returned on success, and false is returned
357 // if serialization fails (typically because the writer ran out of buffer). This
358 // is conceptually similar to absl::StrAppend().
359 template <typename... Ts>
SerializeIntoWriter(QuicheDataWriter & writer,Ts...data)360 absl::Status SerializeIntoWriter(QuicheDataWriter& writer, Ts... data) {
361   return wire_serialization_internal::SerializeIntoWriterCore(
362       writer, /*argno=*/0, data...);
363 }
364 
365 // ComputeLengthOnWire(writer, d1, d2, ... dN) calculates the number of bytes
366 // necessary to serialize the supplied data.
367 template <typename T>
ComputeLengthOnWire(T data)368 size_t ComputeLengthOnWire(T data) {
369   return data.GetLengthOnWire();
370 }
371 template <typename T1, typename... Ts>
ComputeLengthOnWire(T1 data1,Ts...rest)372 size_t ComputeLengthOnWire(T1 data1, Ts... rest) {
373   return data1.GetLengthOnWire() + ComputeLengthOnWire(rest...);
374 }
ComputeLengthOnWire()375 inline size_t ComputeLengthOnWire() { return 0; }
376 
377 // SerializeIntoBuffer(allocator, d1, d2, ... dN) computes the length required
378 // to store the supplied data, allocates the buffer of appropriate size using
379 // |allocator|, and serializes the result into it.  In a rare event that the
380 // serialization fails (e.g. due to invalid varint62 value), an empty buffer is
381 // returned.
382 template <typename... Ts>
SerializeIntoBuffer(QuicheBufferAllocator * allocator,Ts...data)383 absl::StatusOr<QuicheBuffer> SerializeIntoBuffer(
384     QuicheBufferAllocator* allocator, Ts... data) {
385   size_t buffer_size = ComputeLengthOnWire(data...);
386   if (buffer_size == 0) {
387     return QuicheBuffer();
388   }
389 
390   QuicheBuffer buffer(allocator, buffer_size);
391   QuicheDataWriter writer(buffer.size(), buffer.data());
392   QUICHE_RETURN_IF_ERROR(SerializeIntoWriter(writer, data...));
393   if (writer.remaining() != 0) {
394     return absl::InternalError(absl::StrCat(
395         "Excess ", writer.remaining(), " bytes allocated while serializing"));
396   }
397   return buffer;
398 }
399 
400 // SerializeIntoBuffer() that returns std::string instead of QuicheBuffer.
401 template <typename... Ts>
SerializeIntoString(Ts...data)402 absl::StatusOr<std::string> SerializeIntoString(Ts... data) {
403   size_t buffer_size = ComputeLengthOnWire(data...);
404   if (buffer_size == 0) {
405     return std::string();
406   }
407 
408   std::string buffer;
409   buffer.resize(buffer_size);
410   QuicheDataWriter writer(buffer.size(), buffer.data());
411   QUICHE_RETURN_IF_ERROR(SerializeIntoWriter(writer, data...));
412   if (writer.remaining() != 0) {
413     return absl::InternalError(absl::StrCat(
414         "Excess ", writer.remaining(), " bytes allocated while serializing"));
415   }
416   return buffer;
417 }
418 
419 }  // namespace quiche
420 
421 #endif  // QUICHE_COMMON_WIRE_SERIALIZATION_H_
422