Network System 0.1.1
High-performance modular networking library for scalable client-server applications
Loading...
Searching...
No Matches
packet.cpp
Go to the documentation of this file.
1// BSD 3-Clause License
2// Copyright (c) 2024, 🍀☀🌕🌥 🌊
3// See the LICENSE file in the project root for full license information.
4
7
8#include <algorithm>
9
11{
12
13namespace
14{
15 constexpr const char* source = "quic::packet";
16
17 template<typename T>
18 auto make_error(int code, const std::string& message,
19 const std::string& details = "") -> Result<T>
20 {
21 return error<T>(code, message, source, details);
22 }
23
24 // Header form bits
25 constexpr uint8_t header_form_long = 0x80;
26 constexpr uint8_t fixed_bit = 0x40;
27
28 // Long header type mask
29 constexpr uint8_t long_packet_type_mask = 0x30;
30 constexpr uint8_t long_packet_type_shift = 4;
31
32 // Short header bits
33 constexpr uint8_t spin_bit_mask = 0x20;
34 constexpr uint8_t key_phase_mask = 0x04;
35 constexpr uint8_t pn_length_mask = 0x03;
36
37} // namespace
38
39// ============================================================================
40// packet_type_to_string
41// ============================================================================
42
43auto packet_type_to_string(packet_type type) -> std::string
44{
45 switch (type)
46 {
47 case packet_type::initial: return "Initial";
48 case packet_type::zero_rtt: return "0-RTT";
49 case packet_type::handshake: return "Handshake";
50 case packet_type::retry: return "Retry";
51 case packet_type::one_rtt: return "1-RTT";
52 default: return "Unknown";
53 }
54}
55
56// ============================================================================
57// long_header
58// ============================================================================
59
60auto long_header::type() const noexcept -> packet_type
61{
62 return static_cast<packet_type>((first_byte >> 4) & 0x03);
63}
64
65auto long_header::is_retry() const noexcept -> bool
66{
67 return type() == packet_type::retry;
68}
69
70// ============================================================================
71// short_header
72// ============================================================================
73
74auto short_header::spin_bit() const noexcept -> bool
75{
76 return (first_byte & spin_bit_mask) != 0;
77}
78
79auto short_header::key_phase() const noexcept -> bool
80{
81 return (first_byte & key_phase_mask) != 0;
82}
83
84// ============================================================================
85// packet_number
86// ============================================================================
87
88auto packet_number::encode(uint64_t full_pn, uint64_t largest_acked)
89 -> std::pair<std::vector<uint8_t>, size_t>
90{
91 size_t len = encoded_length(full_pn, largest_acked);
92 std::vector<uint8_t> result(len);
93
94 // Encode in big-endian order
95 for (size_t i = 0; i < len; ++i)
96 {
97 result[len - 1 - i] = static_cast<uint8_t>(full_pn >> (i * 8));
98 }
99
100 return {result, len};
101}
102
103auto packet_number::decode(uint64_t truncated_pn, size_t pn_length,
104 uint64_t largest_pn) noexcept -> uint64_t
105{
106 // RFC 9000 Appendix A
107 uint64_t expected_pn = largest_pn + 1;
108 uint64_t pn_win = 1ULL << (pn_length * 8);
109 uint64_t pn_hwin = pn_win / 2;
110 uint64_t pn_mask = pn_win - 1;
111
112 // Reconstruct the full packet number
113 uint64_t candidate_pn = (expected_pn & ~pn_mask) | truncated_pn;
114
115 // Handle wrap-around cases
116 if (candidate_pn <= expected_pn - pn_hwin && candidate_pn < (1ULL << 62) - pn_win)
117 {
118 return candidate_pn + pn_win;
119 }
120 if (candidate_pn > expected_pn + pn_hwin && candidate_pn >= pn_win)
121 {
122 return candidate_pn - pn_win;
123 }
124 return candidate_pn;
125}
126
127auto packet_number::encoded_length(uint64_t full_pn, uint64_t largest_acked) noexcept -> size_t
128{
129 uint64_t num_unacked = (full_pn > largest_acked) ? (full_pn - largest_acked) : 1;
130
131 if (num_unacked < (1ULL << 7))
132 {
133 return 1;
134 }
135 if (num_unacked < (1ULL << 15))
136 {
137 return 2;
138 }
139 if (num_unacked < (1ULL << 23))
140 {
141 return 3;
142 }
143 return 4;
144}
145
146// ============================================================================
147// packet_parser
148// ============================================================================
149
150auto packet_parser::is_version_negotiation(std::span<const uint8_t> data) noexcept -> bool
151{
152 if (data.size() < 5)
153 {
154 return false;
155 }
156 // Long header with version 0
157 if ((data[0] & header_form_long) == 0)
158 {
159 return false;
160 }
161 uint32_t version = (static_cast<uint32_t>(data[1]) << 24) |
162 (static_cast<uint32_t>(data[2]) << 16) |
163 (static_cast<uint32_t>(data[3]) << 8) |
164 static_cast<uint32_t>(data[4]);
165 return version == 0;
166}
167
168auto packet_parser::parse_header(std::span<const uint8_t> data)
170{
171 if (data.empty())
172 {
173 return make_error<std::pair<packet_header, size_t>>(
175 "Empty packet data");
176 }
177
178 if (is_long_header(data[0]))
179 {
180 auto result = parse_long_header(data);
181 if (result.is_err())
182 {
183 return make_error<std::pair<packet_header, size_t>>(
184 result.error().code, result.error().message);
185 }
186 auto& [header, len] = result.value();
187 return ok(std::make_pair(packet_header{std::move(header)}, len));
188 }
189 else
190 {
191 // For short headers, we need to know the connection ID length
192 // Return an error directing caller to use parse_short_header
193 return make_error<std::pair<packet_header, size_t>>(
195 "Short header requires known connection ID length. Use parse_short_header().");
196 }
197}
198
199auto packet_parser::parse_long_header(std::span<const uint8_t> data)
201{
202 // Minimum long header: 1 + 4 + 1 + 1 = 7 bytes (with empty CIDs)
203 if (data.size() < 7)
204 {
205 return make_error<std::pair<long_header, size_t>>(
207 "Insufficient data for long header");
208 }
209
210 long_header header;
211 size_t offset = 0;
212
213 // First byte
214 header.first_byte = data[offset++];
215
216 // Validate header form
217 if (!is_long_header(header.first_byte))
218 {
219 return make_error<std::pair<long_header, size_t>>(
221 "Not a long header packet");
222 }
223
224 // Validate fixed bit
225 if (!has_valid_fixed_bit(header.first_byte))
226 {
227 return make_error<std::pair<long_header, size_t>>(
229 "Invalid fixed bit in long header");
230 }
231
232 // Version (4 bytes, big-endian)
233 if (data.size() < offset + 4)
234 {
235 return make_error<std::pair<long_header, size_t>>(
237 "Insufficient data for version");
238 }
239 header.version = (static_cast<uint32_t>(data[offset]) << 24) |
240 (static_cast<uint32_t>(data[offset + 1]) << 16) |
241 (static_cast<uint32_t>(data[offset + 2]) << 8) |
242 static_cast<uint32_t>(data[offset + 3]);
243 offset += 4;
244
245 // Destination Connection ID Length (1 byte)
246 if (data.size() < offset + 1)
247 {
248 return make_error<std::pair<long_header, size_t>>(
250 "Insufficient data for DCID length");
251 }
252 uint8_t dcid_len = data[offset++];
253 if (dcid_len > connection_id::max_length)
254 {
255 return make_error<std::pair<long_header, size_t>>(
257 "DCID length exceeds maximum");
258 }
259
260 // Destination Connection ID
261 if (data.size() < offset + dcid_len)
262 {
263 return make_error<std::pair<long_header, size_t>>(
265 "Insufficient data for DCID");
266 }
267 header.dest_conn_id = connection_id(data.subspan(offset, dcid_len));
268 offset += dcid_len;
269
270 // Source Connection ID Length (1 byte)
271 if (data.size() < offset + 1)
272 {
273 return make_error<std::pair<long_header, size_t>>(
275 "Insufficient data for SCID length");
276 }
277 uint8_t scid_len = data[offset++];
278 if (scid_len > connection_id::max_length)
279 {
280 return make_error<std::pair<long_header, size_t>>(
282 "SCID length exceeds maximum");
283 }
284
285 // Source Connection ID
286 if (data.size() < offset + scid_len)
287 {
288 return make_error<std::pair<long_header, size_t>>(
290 "Insufficient data for SCID");
291 }
292 header.src_conn_id = connection_id(data.subspan(offset, scid_len));
293 offset += scid_len;
294
295 // Type-specific fields
296 auto ptype = header.type();
297
298 if (ptype == packet_type::initial)
299 {
300 // Token Length (varint)
301 auto token_len_result = varint::decode(data.subspan(offset));
302 if (token_len_result.is_err())
303 {
304 return make_error<std::pair<long_header, size_t>>(
306 "Failed to decode token length");
307 }
308 auto [token_len, token_len_bytes] = token_len_result.value();
309 offset += token_len_bytes;
310
311 // Token
312 if (data.size() < offset + token_len)
313 {
314 return make_error<std::pair<long_header, size_t>>(
316 "Insufficient data for token");
317 }
318 header.token.assign(data.begin() + static_cast<ptrdiff_t>(offset),
319 data.begin() + static_cast<ptrdiff_t>(offset + token_len));
320 offset += token_len;
321
322 // Packet Length (varint)
323 auto pkt_len_result = varint::decode(data.subspan(offset));
324 if (pkt_len_result.is_err())
325 {
326 return make_error<std::pair<long_header, size_t>>(
328 "Failed to decode packet length");
329 }
330 offset += pkt_len_result.value().second;
331
332 // Packet number length from reserved bits
333 header.packet_number_length = (header.first_byte & pn_length_mask) + 1;
334 }
335 else if (ptype == packet_type::handshake || ptype == packet_type::zero_rtt)
336 {
337 // Packet Length (varint)
338 auto pkt_len_result = varint::decode(data.subspan(offset));
339 if (pkt_len_result.is_err())
340 {
341 return make_error<std::pair<long_header, size_t>>(
343 "Failed to decode packet length");
344 }
345 offset += pkt_len_result.value().second;
346
347 // Packet number length from reserved bits
348 header.packet_number_length = (header.first_byte & pn_length_mask) + 1;
349 }
350 else if (ptype == packet_type::retry)
351 {
352 // Retry packets have token (remaining bytes except last 16) and integrity tag
353 // We don't parse the actual payload here, just note that it's a retry
354 // The token is everything between the header and the integrity tag
355 }
356
357 return ok(std::make_pair(std::move(header), offset));
358}
359
360auto packet_parser::parse_short_header(std::span<const uint8_t> data,
361 size_t conn_id_length)
363{
364 // Minimum: 1 byte first + conn_id + 1 byte packet number
365 if (data.size() < 1 + conn_id_length + 1)
366 {
367 return make_error<std::pair<short_header, size_t>>(
369 "Insufficient data for short header");
370 }
371
372 short_header header;
373 size_t offset = 0;
374
375 // First byte
376 header.first_byte = data[offset++];
377
378 // Validate header form (should be 0 for short header)
379 if (is_long_header(header.first_byte))
380 {
381 return make_error<std::pair<short_header, size_t>>(
383 "Not a short header packet");
384 }
385
386 // Validate fixed bit
387 if (!has_valid_fixed_bit(header.first_byte))
388 {
389 return make_error<std::pair<short_header, size_t>>(
391 "Invalid fixed bit in short header");
392 }
393
394 // Destination Connection ID
395 if (conn_id_length > 0)
396 {
397 if (data.size() < offset + conn_id_length)
398 {
399 return make_error<std::pair<short_header, size_t>>(
401 "Insufficient data for DCID");
402 }
403 header.dest_conn_id = connection_id(data.subspan(offset, conn_id_length));
404 offset += conn_id_length;
405 }
406
407 // Packet number length from reserved bits
408 header.packet_number_length = (header.first_byte & pn_length_mask) + 1;
409
410 return ok(std::make_pair(std::move(header), offset));
411}
412
413// ============================================================================
414// packet_builder
415// ============================================================================
416
417void packet_builder::append_bytes(std::vector<uint8_t>& buffer,
418 std::span<const uint8_t> data)
419{
420 buffer.insert(buffer.end(), data.begin(), data.end());
421}
422
424 const connection_id& dest_cid,
425 const connection_id& src_cid,
426 const std::vector<uint8_t>& token,
427 uint64_t packet_num,
428 uint32_t version) -> std::vector<uint8_t>
429{
430 std::vector<uint8_t> buffer;
431
432 // Calculate packet number length
433 size_t pn_len = packet_number::encoded_length(packet_num, 0);
434
435 // First byte: Long header (1) + Fixed bit (1) + Type (00) + Reserved (00) + PN Length
436 uint8_t first_byte = header_form_long | fixed_bit |
437 (static_cast<uint8_t>(packet_type::initial) << long_packet_type_shift) |
438 static_cast<uint8_t>(pn_len - 1);
439 buffer.push_back(first_byte);
440
441 // Version (4 bytes, big-endian)
442 buffer.push_back(static_cast<uint8_t>(version >> 24));
443 buffer.push_back(static_cast<uint8_t>(version >> 16));
444 buffer.push_back(static_cast<uint8_t>(version >> 8));
445 buffer.push_back(static_cast<uint8_t>(version));
446
447 // DCID Length + DCID
448 buffer.push_back(static_cast<uint8_t>(dest_cid.length()));
449 append_bytes(buffer, dest_cid.data());
450
451 // SCID Length + SCID
452 buffer.push_back(static_cast<uint8_t>(src_cid.length()));
453 append_bytes(buffer, src_cid.data());
454
455 // Token Length (varint) + Token
456 auto token_len_encoded = varint::encode(token.size());
457 append_bytes(buffer, token_len_encoded);
458 append_bytes(buffer, token);
459
460 // Packet number (placeholder - actual encoding done separately)
461 auto [pn_bytes, _] = packet_number::encode(packet_num, 0);
462 append_bytes(buffer, pn_bytes);
463
464 return buffer;
465}
466
468 const connection_id& dest_cid,
469 const connection_id& src_cid,
470 uint64_t packet_num,
471 uint32_t version) -> std::vector<uint8_t>
472{
473 std::vector<uint8_t> buffer;
474
475 size_t pn_len = packet_number::encoded_length(packet_num, 0);
476
477 // First byte: Long header (1) + Fixed bit (1) + Type (10) + Reserved (00) + PN Length
478 uint8_t first_byte = header_form_long | fixed_bit |
479 (static_cast<uint8_t>(packet_type::handshake) << long_packet_type_shift) |
480 static_cast<uint8_t>(pn_len - 1);
481 buffer.push_back(first_byte);
482
483 // Version
484 buffer.push_back(static_cast<uint8_t>(version >> 24));
485 buffer.push_back(static_cast<uint8_t>(version >> 16));
486 buffer.push_back(static_cast<uint8_t>(version >> 8));
487 buffer.push_back(static_cast<uint8_t>(version));
488
489 // DCID
490 buffer.push_back(static_cast<uint8_t>(dest_cid.length()));
491 append_bytes(buffer, dest_cid.data());
492
493 // SCID
494 buffer.push_back(static_cast<uint8_t>(src_cid.length()));
495 append_bytes(buffer, src_cid.data());
496
497 // Packet number
498 auto [pn_bytes, _] = packet_number::encode(packet_num, 0);
499 append_bytes(buffer, pn_bytes);
500
501 return buffer;
502}
503
505 const connection_id& dest_cid,
506 const connection_id& src_cid,
507 uint64_t packet_num,
508 uint32_t version) -> std::vector<uint8_t>
509{
510 std::vector<uint8_t> buffer;
511
512 size_t pn_len = packet_number::encoded_length(packet_num, 0);
513
514 // First byte: Long header (1) + Fixed bit (1) + Type (01) + Reserved (00) + PN Length
515 uint8_t first_byte = header_form_long | fixed_bit |
516 (static_cast<uint8_t>(packet_type::zero_rtt) << long_packet_type_shift) |
517 static_cast<uint8_t>(pn_len - 1);
518 buffer.push_back(first_byte);
519
520 // Version
521 buffer.push_back(static_cast<uint8_t>(version >> 24));
522 buffer.push_back(static_cast<uint8_t>(version >> 16));
523 buffer.push_back(static_cast<uint8_t>(version >> 8));
524 buffer.push_back(static_cast<uint8_t>(version));
525
526 // DCID
527 buffer.push_back(static_cast<uint8_t>(dest_cid.length()));
528 append_bytes(buffer, dest_cid.data());
529
530 // SCID
531 buffer.push_back(static_cast<uint8_t>(src_cid.length()));
532 append_bytes(buffer, src_cid.data());
533
534 // Packet number
535 auto [pn_bytes, _] = packet_number::encode(packet_num, 0);
536 append_bytes(buffer, pn_bytes);
537
538 return buffer;
539}
540
542 const connection_id& dest_cid,
543 const connection_id& src_cid,
544 const std::vector<uint8_t>& token,
545 const std::array<uint8_t, 16>& integrity_tag,
546 uint32_t version) -> std::vector<uint8_t>
547{
548 std::vector<uint8_t> buffer;
549
550 // First byte: Long header (1) + Fixed bit (1) + Type (11) + Unused (0000)
551 uint8_t first_byte = header_form_long | fixed_bit |
552 (static_cast<uint8_t>(packet_type::retry) << long_packet_type_shift);
553 buffer.push_back(first_byte);
554
555 // Version
556 buffer.push_back(static_cast<uint8_t>(version >> 24));
557 buffer.push_back(static_cast<uint8_t>(version >> 16));
558 buffer.push_back(static_cast<uint8_t>(version >> 8));
559 buffer.push_back(static_cast<uint8_t>(version));
560
561 // DCID
562 buffer.push_back(static_cast<uint8_t>(dest_cid.length()));
563 append_bytes(buffer, dest_cid.data());
564
565 // SCID
566 buffer.push_back(static_cast<uint8_t>(src_cid.length()));
567 append_bytes(buffer, src_cid.data());
568
569 // Retry Token
570 append_bytes(buffer, token);
571
572 // Retry Integrity Tag
573 buffer.insert(buffer.end(), integrity_tag.begin(), integrity_tag.end());
574
575 return buffer;
576}
577
579 const connection_id& dest_cid,
580 uint64_t packet_num,
581 bool key_phase,
582 bool spin_bit) -> std::vector<uint8_t>
583{
584 std::vector<uint8_t> buffer;
585
586 size_t pn_len = packet_number::encoded_length(packet_num, 0);
587
588 // First byte: Short header (0) + Fixed bit (1) + Spin + Reserved (00) + Key Phase + PN Length
589 uint8_t first_byte = fixed_bit; // Header form = 0
590 if (spin_bit)
591 {
592 first_byte |= spin_bit_mask;
593 }
594 if (key_phase)
595 {
596 first_byte |= key_phase_mask;
597 }
598 first_byte |= static_cast<uint8_t>(pn_len - 1);
599 buffer.push_back(first_byte);
600
601 // DCID (no length field for short header)
602 append_bytes(buffer, dest_cid.data());
603
604 // Packet number
605 auto [pn_bytes, _] = packet_number::encode(packet_num, 0);
606 append_bytes(buffer, pn_bytes);
607
608 return buffer;
609}
610
611auto packet_builder::build(const long_header& header) -> std::vector<uint8_t>
612{
613 auto ptype = header.type();
614 switch (ptype)
615 {
617 return build_initial(header.dest_conn_id, header.src_conn_id,
618 header.token, header.packet_number, header.version);
620 return build_handshake(header.dest_conn_id, header.src_conn_id,
621 header.packet_number, header.version);
623 return build_zero_rtt(header.dest_conn_id, header.src_conn_id,
624 header.packet_number, header.version);
626 return build_retry(header.dest_conn_id, header.src_conn_id,
627 header.token, header.retry_integrity_tag, header.version);
628 default:
629 return {};
630 }
631}
632
633auto packet_builder::build(const short_header& header) -> std::vector<uint8_t>
634{
635 return build_short(header.dest_conn_id, header.packet_number,
636 header.key_phase(), header.spin_bit());
637}
638
639} // namespace kcenon::network::protocols::quic
QUIC Connection ID (RFC 9000 Section 5.1)
static constexpr size_t max_length
Maximum length of a connection ID (RFC 9000)
static auto build_retry(const connection_id &dest_cid, const connection_id &src_cid, const std::vector< uint8_t > &token, const std::array< uint8_t, 16 > &integrity_tag, uint32_t version=quic_version::version_1) -> std::vector< uint8_t >
Build a Retry packet header.
Definition packet.cpp:541
static auto build_handshake(const connection_id &dest_cid, const connection_id &src_cid, uint64_t packet_number, uint32_t version=quic_version::version_1) -> std::vector< uint8_t >
Build a Handshake packet header.
Definition packet.cpp:467
static auto build_short(const connection_id &dest_cid, uint64_t packet_number, bool key_phase=false, bool spin_bit=false) -> std::vector< uint8_t >
Build a Short Header (1-RTT) packet.
Definition packet.cpp:578
static auto build_zero_rtt(const connection_id &dest_cid, const connection_id &src_cid, uint64_t packet_number, uint32_t version=quic_version::version_1) -> std::vector< uint8_t >
Build a 0-RTT packet header.
Definition packet.cpp:504
static auto build_initial(const connection_id &dest_cid, const connection_id &src_cid, const std::vector< uint8_t > &token, uint64_t packet_number, uint32_t version=quic_version::version_1) -> std::vector< uint8_t >
Build an Initial packet header.
Definition packet.cpp:423
static void append_bytes(std::vector< uint8_t > &buffer, std::span< const uint8_t > data)
Helper to append bytes to buffer.
Definition packet.cpp:417
static auto build(const long_header &header) -> std::vector< uint8_t >
Build a long header from a header structure.
Definition packet.cpp:611
static auto encoded_length(uint64_t full_pn, uint64_t largest_acked) noexcept -> size_t
Get the minimum number of bytes needed to encode a packet number.
Definition packet.cpp:127
static auto decode(uint64_t truncated_pn, size_t pn_length, uint64_t largest_pn) noexcept -> uint64_t
Decode a packet number from received data.
Definition packet.cpp:103
static auto encode(uint64_t full_pn, uint64_t largest_acked) -> std::pair< std::vector< uint8_t >, size_t >
Encode a packet number for transmission.
Definition packet.cpp:88
static auto parse_header(std::span< const uint8_t > data) -> Result< std::pair< packet_header, size_t > >
Parse a packet header (without header protection removal)
Definition packet.cpp:168
static auto parse_long_header(std::span< const uint8_t > data) -> Result< std::pair< long_header, size_t > >
Parse a long header packet.
Definition packet.cpp:199
static auto parse_short_header(std::span< const uint8_t > data, size_t conn_id_length) -> Result< std::pair< short_header, size_t > >
Parse a short header packet.
Definition packet.cpp:360
static auto is_version_negotiation(std::span< const uint8_t > data) noexcept -> bool
Check if this is a version negotiation packet.
Definition packet.cpp:150
static auto encode(uint64_t value) -> std::vector< uint8_t >
Encode a value to variable-length format.
Definition varint.cpp:10
static auto decode(std::span< const uint8_t > data) -> Result< std::pair< uint64_t, size_t > >
Decode variable-length integer from buffer.
Definition varint.cpp:146
@ error
Black hole detected, reset to base.
auto packet_type_to_string(packet_type type) -> std::string
Convert packet type to string for debugging.
Definition packet.cpp:43
packet_type
QUIC packet types (RFC 9000 Section 17)
Definition packet.h:55
std::variant< long_header, short_header > packet_header
Variant type for packet headers.
Definition packet.h:160
constexpr const char * version() noexcept
Get the network system version string.
Definition network.cppm:111
VoidResult ok()
QUIC Long Header format (RFC 9000 Section 17.2)
Definition packet.h:95
std::vector< uint8_t > token
Token (Initial and Retry only)
Definition packet.h:102
connection_id dest_conn_id
Destination Connection ID.
Definition packet.h:98
size_t packet_number_length
Packet number length (1-4 bytes)
Definition packet.h:104
auto type() const noexcept -> packet_type
Get the packet type from first byte.
Definition packet.cpp:60
uint8_t first_byte
Header form, fixed bit, type, reserved, PN length.
Definition packet.h:96
auto is_retry() const noexcept -> bool
Check if this is a Retry packet (has integrity tag, no packet number)
Definition packet.cpp:65
connection_id src_conn_id
Source Connection ID.
Definition packet.h:99
QUIC Short Header format (RFC 9000 Section 17.3)
Definition packet.h:138
auto key_phase() const noexcept -> bool
Get the key phase bit.
Definition packet.cpp:79
size_t packet_number_length
Packet number length (1-4 bytes)
Definition packet.h:142
uint8_t first_byte
Header form, fixed bit, spin, reserved, key phase, PN length.
Definition packet.h:139
connection_id dest_conn_id
Destination Connection ID.
Definition packet.h:140
auto spin_bit() const noexcept -> bool
Get the spin bit value.
Definition packet.cpp:74