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