• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 "absl/types/optional.h"
68 #include "quiche/common/platform/api/quiche_logging.h"
69 #include "quiche/common/quiche_buffer_allocator.h"
70 #include "quiche/common/quiche_data_writer.h"
71 #include "quiche/common/quiche_status_utils.h"
72 
73 namespace quiche {
74 
75 // T::SerializeIntoWriter() is allowed to return both a bool and an
76 // absl::Status.  There are two reasons for that:
77 //   1. Most QuicheDataWriter methods return a bool.
78 //   2. While cheap, absl::Status has a non-trivial destructor and thus is not
79 //      as free as a bool is.
80 // To accomodate this, SerializeIntoWriterStatus<T> provides a way to deduce
81 // what is the status type returned by the SerializeIntoWriter method.
82 template <typename T>
83 class QUICHE_NO_EXPORT SerializeIntoWriterStatus {
84  public:
85   static_assert(std::is_trivially_copyable_v<T> && sizeof(T) <= 32,
86                 "The types passed into SerializeInto() APIs are passed by "
87                 "value; if your type has non-trivial copy costs, it should be "
88                 "wrapped into a type that carries a pointer");
89 
90   using Type = decltype(std::declval<T>().SerializeIntoWriter(
91       std::declval<QuicheDataWriter&>()));
92   static constexpr bool kIsBool = std::is_same_v<Type, bool>;
93   static constexpr bool kIsStatus = std::is_same_v<Type, absl::Status>;
94   static_assert(
95       kIsBool || kIsStatus,
96       "SerializeIntoWriter() has to return either a bool or an absl::Status");
97 
OkValue()98   static ABSL_ATTRIBUTE_ALWAYS_INLINE Type OkValue() {
99     if constexpr (kIsStatus) {
100       return absl::OkStatus();
101     } else {
102       return true;
103     }
104   }
105 };
106 
IsWriterStatusOk(bool status)107 inline ABSL_ATTRIBUTE_ALWAYS_INLINE bool IsWriterStatusOk(bool status) {
108   return status;
109 }
IsWriterStatusOk(const absl::Status & status)110 inline ABSL_ATTRIBUTE_ALWAYS_INLINE bool IsWriterStatusOk(
111     const absl::Status& status) {
112   return status.ok();
113 }
114 
115 // ------------------- WireType() wrapper definitions -------------------
116 
117 // Base class for WireUint8/16/32/64.
118 template <typename T>
119 class QUICHE_EXPORT WireFixedSizeIntBase {
120  public:
121   using DataType = T;
122   static_assert(std::is_integral_v<DataType>,
123                 "WireFixedSizeIntBase is only usable with integral types");
124 
WireFixedSizeIntBase(T value)125   explicit WireFixedSizeIntBase(T value) { value_ = value; }
GetLengthOnWire()126   size_t GetLengthOnWire() const { return sizeof(T); }
value()127   T value() const { return value_; }
128 
129  private:
130   T value_;
131 };
132 
133 // Fixed-size integer fields.  Correspond to (8), (16), (32) and (64) fields in
134 // RFC 9000 language.
135 class QUICHE_EXPORT WireUint8 : public WireFixedSizeIntBase<uint8_t> {
136  public:
137   using WireFixedSizeIntBase::WireFixedSizeIntBase;
SerializeIntoWriter(QuicheDataWriter & writer)138   bool SerializeIntoWriter(QuicheDataWriter& writer) const {
139     return writer.WriteUInt8(value());
140   }
141 };
142 class QUICHE_EXPORT WireUint16 : public WireFixedSizeIntBase<uint16_t> {
143  public:
144   using WireFixedSizeIntBase::WireFixedSizeIntBase;
SerializeIntoWriter(QuicheDataWriter & writer)145   bool SerializeIntoWriter(QuicheDataWriter& writer) const {
146     return writer.WriteUInt16(value());
147   }
148 };
149 class QUICHE_EXPORT WireUint32 : public WireFixedSizeIntBase<uint32_t> {
150  public:
151   using WireFixedSizeIntBase::WireFixedSizeIntBase;
SerializeIntoWriter(QuicheDataWriter & writer)152   bool SerializeIntoWriter(QuicheDataWriter& writer) const {
153     return writer.WriteUInt32(value());
154   }
155 };
156 class QUICHE_EXPORT WireUint64 : public WireFixedSizeIntBase<uint64_t> {
157  public:
158   using WireFixedSizeIntBase::WireFixedSizeIntBase;
SerializeIntoWriter(QuicheDataWriter & writer)159   bool SerializeIntoWriter(QuicheDataWriter& writer) const {
160     return writer.WriteUInt64(value());
161   }
162 };
163 
164 // Represents a 62-bit variable-length non-negative integer.  Those are
165 // described in the Section 16 of RFC 9000, and are denoted as (i) in type
166 // descriptions.
167 class QUICHE_EXPORT WireVarInt62 {
168  public:
169   using DataType = uint64_t;
170 
WireVarInt62(uint64_t value)171   explicit WireVarInt62(uint64_t value) { value_ = value; }
172   // Convenience wrapper. This is safe, since it is clear from the context that
173   // the enum is being treated as an integer.
174   template <typename T>
WireVarInt62(T value)175   explicit WireVarInt62(T value) {
176     static_assert(std::is_enum_v<T> || std::is_convertible_v<T, uint64_t>);
177     value_ = static_cast<uint64_t>(value);
178   }
179 
GetLengthOnWire()180   size_t GetLengthOnWire() const {
181     return QuicheDataWriter::GetVarInt62Len(value_);
182   }
SerializeIntoWriter(QuicheDataWriter & writer)183   bool SerializeIntoWriter(QuicheDataWriter& writer) const {
184     return writer.WriteVarInt62(value_);
185   }
186 
187  private:
188   uint64_t value_;
189 };
190 
191 // Represents unframed raw string.
192 class QUICHE_EXPORT WireBytes {
193  public:
194   using DataType = absl::string_view;
195 
WireBytes(absl::string_view value)196   explicit WireBytes(absl::string_view value) { value_ = value; }
GetLengthOnWire()197   size_t GetLengthOnWire() { return value_.size(); }
SerializeIntoWriter(QuicheDataWriter & writer)198   bool SerializeIntoWriter(QuicheDataWriter& writer) {
199     return writer.WriteStringPiece(value_);
200   }
201 
202  private:
203   absl::string_view value_;
204 };
205 
206 // Represents a string where another wire type is used as a length prefix.
207 template <class LengthWireType>
208 class QUICHE_EXPORT WireStringWithLengthPrefix {
209  public:
210   using DataType = absl::string_view;
211 
WireStringWithLengthPrefix(absl::string_view value)212   explicit WireStringWithLengthPrefix(absl::string_view value) {
213     value_ = value;
214   }
GetLengthOnWire()215   size_t GetLengthOnWire() {
216     return LengthWireType(value_.size()).GetLengthOnWire() + value_.size();
217   }
SerializeIntoWriter(QuicheDataWriter & writer)218   absl::Status SerializeIntoWriter(QuicheDataWriter& writer) {
219     if (!LengthWireType(value_.size()).SerializeIntoWriter(writer)) {
220       return absl::InternalError("Failed to serialize the length prefix");
221     }
222     if (!writer.WriteStringPiece(value_)) {
223       return absl::InternalError("Failed to serialize the string proper");
224     }
225     return absl::OkStatus();
226   }
227 
228  private:
229   absl::string_view value_;
230 };
231 
232 // Represents varint62-prefixed strings.
233 using WireStringWithVarInt62Length = WireStringWithLengthPrefix<WireVarInt62>;
234 
235 // Allows absl::optional to be used with this API. For instance, if the spec
236 // defines
237 //   [Context ID (i)]
238 // and the value is stored as absl::optional<uint64> context_id, this can be
239 // recorded as
240 //   WireOptional<WireVarInt62>(context_id)
241 // When optional is absent, nothing is written onto the wire.
242 template <typename WireType, typename InnerType = typename WireType::DataType>
243 class QUICHE_EXPORT WireOptional {
244  public:
245   using DataType = absl::optional<InnerType>;
246   using Status = SerializeIntoWriterStatus<WireType>;
247 
WireOptional(DataType value)248   explicit WireOptional(DataType value) { value_ = value; }
GetLengthOnWire()249   size_t GetLengthOnWire() const {
250     return value_.has_value() ? WireType(*value_).GetLengthOnWire() : 0;
251   }
SerializeIntoWriter(QuicheDataWriter & writer)252   typename Status::Type SerializeIntoWriter(QuicheDataWriter& writer) const {
253     if (value_.has_value()) {
254       return WireType(*value_).SerializeIntoWriter(writer);
255     }
256     return Status::OkValue();
257   }
258 
259  private:
260   DataType value_;
261 };
262 
263 // Allows multiple entries of the same type to be serialized in a single call.
264 template <typename WireType,
265           typename SpanElementType = typename WireType::DataType>
266 class QUICHE_EXPORT WireSpan {
267  public:
268   using DataType = absl::Span<const SpanElementType>;
269 
WireSpan(DataType value)270   explicit WireSpan(DataType value) { value_ = value; }
GetLengthOnWire()271   size_t GetLengthOnWire() const {
272     size_t total = 0;
273     for (const SpanElementType& value : value_) {
274       total += WireType(value).GetLengthOnWire();
275     }
276     return total;
277   }
SerializeIntoWriter(QuicheDataWriter & writer)278   absl::Status SerializeIntoWriter(QuicheDataWriter& writer) const {
279     for (size_t i = 0; i < value_.size(); i++) {
280       // `status` here can be either a bool or an absl::Status.
281       auto status = WireType(value_[i]).SerializeIntoWriter(writer);
282       if (IsWriterStatusOk(status)) {
283         continue;
284       }
285       if constexpr (SerializeIntoWriterStatus<WireType>::kIsStatus) {
286         return AppendToStatus(std::move(status),
287                               " while serializing the value #", i);
288       } else {
289         return absl::InternalError(
290             absl::StrCat("Failed to serialize vector value #", i));
291       }
292     }
293     return absl::OkStatus();
294   }
295 
296  private:
297   DataType value_;
298 };
299 
300 // ------------------- Top-level serialization API -------------------
301 
302 namespace wire_serialization_internal {
303 template <typename T>
SerializeIntoWriterWrapper(QuicheDataWriter & writer,int argno,T data)304 auto SerializeIntoWriterWrapper(QuicheDataWriter& writer, int argno, T data) {
305 #if defined(NDEBUG)
306   (void)argno;
307   (void)data;
308   return data.SerializeIntoWriter(writer);
309 #else
310   // When running in the debug build, we check that the length reported by
311   // GetLengthOnWire() matches what is actually being written onto the wire.
312   // While any mismatch will most likely lead to an error further down the line,
313   // this simplifies the debugging process.
314   const size_t initial_offset = writer.length();
315   const size_t expected_size = data.GetLengthOnWire();
316   auto result = data.SerializeIntoWriter(writer);
317   const size_t final_offset = writer.length();
318   if (IsWriterStatusOk(result)) {
319     QUICHE_DCHECK_EQ(initial_offset + expected_size, final_offset)
320         << "while serializing field #" << argno;
321   }
322   return result;
323 #endif
324 }
325 
326 template <typename T>
327 std::enable_if_t<SerializeIntoWriterStatus<T>::kIsBool, absl::Status>
SerializeIntoWriterCore(QuicheDataWriter & writer,int argno,T data)328 SerializeIntoWriterCore(QuicheDataWriter& writer, int argno, T data) {
329   const bool success = SerializeIntoWriterWrapper(writer, argno, data);
330   if (!success) {
331     return absl::InternalError(
332         absl::StrCat("Failed to serialize field #", argno));
333   }
334   return absl::OkStatus();
335 }
336 
337 template <typename T>
338 std::enable_if_t<SerializeIntoWriterStatus<T>::kIsStatus, absl::Status>
SerializeIntoWriterCore(QuicheDataWriter & writer,int argno,T data)339 SerializeIntoWriterCore(QuicheDataWriter& writer, int argno, T data) {
340   return AppendToStatus(SerializeIntoWriterWrapper(writer, argno, data),
341                         " while serializing field #", argno);
342 }
343 
344 template <typename T1, typename... Ts>
SerializeIntoWriterCore(QuicheDataWriter & writer,int argno,T1 data1,Ts...rest)345 absl::Status SerializeIntoWriterCore(QuicheDataWriter& writer, int argno,
346                                      T1 data1, Ts... rest) {
347   QUICHE_RETURN_IF_ERROR(SerializeIntoWriterCore(writer, argno, data1));
348   return SerializeIntoWriterCore(writer, argno + 1, rest...);
349 }
350 }  // namespace wire_serialization_internal
351 
352 // SerializeIntoWriter(writer, d1, d2, ... dN) serializes all of supplied data
353 // into the writer |writer|.  True is returned on success, and false is returned
354 // if serialization fails (typically because the writer ran out of buffer). This
355 // is conceptually similar to absl::StrAppend().
356 template <typename... Ts>
SerializeIntoWriter(QuicheDataWriter & writer,Ts...data)357 absl::Status SerializeIntoWriter(QuicheDataWriter& writer, Ts... data) {
358   return wire_serialization_internal::SerializeIntoWriterCore(
359       writer, /*argno=*/0, data...);
360 }
361 
362 // ComputeLengthOnWire(writer, d1, d2, ... dN) calculates the number of bytes
363 // necessary to serialize the supplied data.
364 template <typename T>
ComputeLengthOnWire(T data)365 size_t ComputeLengthOnWire(T data) {
366   return data.GetLengthOnWire();
367 }
368 template <typename T1, typename... Ts>
ComputeLengthOnWire(T1 data1,Ts...rest)369 size_t ComputeLengthOnWire(T1 data1, Ts... rest) {
370   return data1.GetLengthOnWire() + ComputeLengthOnWire(rest...);
371 }
372 
373 // SerializeIntoBuffer(allocator, d1, d2, ... dN) computes the length required
374 // to store the supplied data, allocates the buffer of appropriate size using
375 // |allocator|, and serializes the result into it.  In a rare event that the
376 // serialization fails (e.g. due to invalid varint62 value), an empty buffer is
377 // returned.
378 template <typename... Ts>
SerializeIntoBuffer(QuicheBufferAllocator * allocator,Ts...data)379 absl::StatusOr<QuicheBuffer> SerializeIntoBuffer(
380     QuicheBufferAllocator* allocator, Ts... data) {
381   size_t buffer_size = ComputeLengthOnWire(data...);
382   if (buffer_size == 0) {
383     return QuicheBuffer();
384   }
385 
386   QuicheBuffer buffer(allocator, buffer_size);
387   QuicheDataWriter writer(buffer.size(), buffer.data());
388   QUICHE_RETURN_IF_ERROR(SerializeIntoWriter(writer, data...));
389   if (writer.remaining() != 0) {
390     return absl::InternalError(absl::StrCat(
391         "Excess ", writer.remaining(), " bytes allocated while serializing"));
392   }
393   return buffer;
394 }
395 
396 }  // namespace quiche
397 
398 #endif  // QUICHE_COMMON_WIRE_SERIALIZATION_H_
399