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