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 // Compare the distance without subtracting from an expected number that
117 // can be smaller than half the window (including the first packet).
118 if (candidate_pn + pn_hwin <= expected_pn && candidate_pn < (1ULL << 62) - pn_win)
119 {
120 return candidate_pn + pn_win;
121 }
122 if (candidate_pn > expected_pn + pn_hwin && candidate_pn >= pn_win)
123 {
124 return candidate_pn - pn_win;
125 }
126 return candidate_pn;
127}
128
129auto packet_number::encoded_length(uint64_t full_pn, uint64_t largest_acked) noexcept -> size_t
130{
131 uint64_t num_unacked = (full_pn > largest_acked) ? (full_pn - largest_acked) : 1;
132
133 if (num_unacked < (1ULL << 7))
134 {
135 return 1;
136 }
137 if (num_unacked < (1ULL << 15))
138 {
139 return 2;
140 }
141 if (num_unacked < (1ULL << 23))
142 {
143 return 3;
144 }
145 return 4;
146}
147
148// ============================================================================
149// packet_parser
150// ============================================================================
151
152auto packet_parser::is_version_negotiation(std::span<const uint8_t> data) noexcept -> bool
153{
154 if (data.size() < 5)
155 {
156 return false;
157 }
158 // Long header with version 0
159 if ((data[0] & header_form_long) == 0)
160 {
161 return false;
162 }
163 uint32_t version = (static_cast<uint32_t>(data[1]) << 24) |
164 (static_cast<uint32_t>(data[2]) << 16) |
165 (static_cast<uint32_t>(data[3]) << 8) |
166 static_cast<uint32_t>(data[4]);
167 return version == 0;
168}
169
170auto packet_parser::parse_header(std::span<const uint8_t> data)
172{
173 if (data.empty())
174 {
175 return make_error<std::pair<packet_header, size_t>>(
177 "Empty packet data");
178 }
179
180 if (is_long_header(data[0]))
181 {
182 auto result = parse_long_header(data);
183 if (result.is_err())
184 {
185 return make_error<std::pair<packet_header, size_t>>(
186 result.error().code, result.error().message);
187 }
188 auto& [header, len] = result.value();
189 return ok(std::make_pair(packet_header{std::move(header)}, len));
190 }
191 else
192 {
193 // For short headers, we need to know the connection ID length
194 // Return an error directing caller to use parse_short_header
195 return make_error<std::pair<packet_header, size_t>>(
197 "Short header requires known connection ID length. Use parse_short_header().");
198 }
199}
200
201auto packet_parser::parse_long_header(std::span<const uint8_t> data)
203{
204 // Minimum long header: 1 + 4 + 1 + 1 = 7 bytes (with empty CIDs)
205 if (data.size() < 7)
206 {
207 return make_error<std::pair<long_header, size_t>>(
209 "Insufficient data for long header");
210 }
211
212 long_header header;
213 size_t offset = 0;
214
215 // First byte
216 header.first_byte = data[offset++];
217
218 // Validate header form
219 if (!is_long_header(header.first_byte))
220 {
221 return make_error<std::pair<long_header, size_t>>(
223 "Not a long header packet");
224 }
225
226 // Validate fixed bit
227 if (!has_valid_fixed_bit(header.first_byte))
228 {
229 return make_error<std::pair<long_header, size_t>>(
231 "Invalid fixed bit in long header");
232 }
233
234 // Version (4 bytes, big-endian)
235 if (data.size() < offset + 4)
236 {
237 return make_error<std::pair<long_header, size_t>>(
239 "Insufficient data for version");
240 }
241 header.version = (static_cast<uint32_t>(data[offset]) << 24) |
242 (static_cast<uint32_t>(data[offset + 1]) << 16) |
243 (static_cast<uint32_t>(data[offset + 2]) << 8) |
244 static_cast<uint32_t>(data[offset + 3]);
245 offset += 4;
246
247 // Destination Connection ID Length (1 byte)
248 if (data.size() < offset + 1)
249 {
250 return make_error<std::pair<long_header, size_t>>(
252 "Insufficient data for DCID length");
253 }
254 uint8_t dcid_len = data[offset++];
255 if (dcid_len > connection_id::max_length)
256 {
257 return make_error<std::pair<long_header, size_t>>(
259 "DCID length exceeds maximum");
260 }
261
262 // Destination Connection ID
263 if (data.size() < offset + dcid_len)
264 {
265 return make_error<std::pair<long_header, size_t>>(
267 "Insufficient data for DCID");
268 }
269 header.dest_conn_id = connection_id(data.subspan(offset, dcid_len));
270 offset += dcid_len;
271
272 // Source Connection ID Length (1 byte)
273 if (data.size() < offset + 1)
274 {
275 return make_error<std::pair<long_header, size_t>>(
277 "Insufficient data for SCID length");
278 }
279 uint8_t scid_len = data[offset++];
280 if (scid_len > connection_id::max_length)
281 {
282 return make_error<std::pair<long_header, size_t>>(
284 "SCID length exceeds maximum");
285 }
286
287 // Source Connection ID
288 if (data.size() < offset + scid_len)
289 {
290 return make_error<std::pair<long_header, size_t>>(
292 "Insufficient data for SCID");
293 }
294 header.src_conn_id = connection_id(data.subspan(offset, scid_len));
295 offset += scid_len;
296
297 // Type-specific fields
298 auto ptype = header.type();
299
300 if (ptype == packet_type::initial)
301 {
302 // Token Length (varint)
303 auto token_len_result = varint::decode(data.subspan(offset));
304 if (token_len_result.is_err())
305 {
306 return make_error<std::pair<long_header, size_t>>(
308 "Failed to decode token length");
309 }
310 auto [token_len, token_len_bytes] = token_len_result.value();
311 offset += token_len_bytes;
312
313 // Token
314 if (data.size() < offset + token_len)
315 {
316 return make_error<std::pair<long_header, size_t>>(
318 "Insufficient data for token");
319 }
320 header.token.assign(data.begin() + static_cast<ptrdiff_t>(offset),
321 data.begin() + static_cast<ptrdiff_t>(offset + token_len));
322 offset += token_len;
323
324 // Packet Length (varint)
325 auto pkt_len_result = varint::decode(data.subspan(offset));
326 if (pkt_len_result.is_err())
327 {
328 return make_error<std::pair<long_header, size_t>>(
330 "Failed to decode packet length");
331 }
332 offset += pkt_len_result.value().second;
333
334 // Packet number length from reserved bits
335 header.packet_number_length = (header.first_byte & pn_length_mask) + 1;
336 }
337 else if (ptype == packet_type::handshake || ptype == packet_type::zero_rtt)
338 {
339 // Packet Length (varint)
340 auto pkt_len_result = varint::decode(data.subspan(offset));
341 if (pkt_len_result.is_err())
342 {
343 return make_error<std::pair<long_header, size_t>>(
345 "Failed to decode packet length");
346 }
347 offset += pkt_len_result.value().second;
348
349 // Packet number length from reserved bits
350 header.packet_number_length = (header.first_byte & pn_length_mask) + 1;
351 }
352 else if (ptype == packet_type::retry)
353 {
354 // Retry packets have token (remaining bytes except last 16) and integrity tag
355 // We don't parse the actual payload here, just note that it's a retry
356 // The token is everything between the header and the integrity tag
357 }
358
359 return ok(std::make_pair(std::move(header), offset));
360}
361
362auto packet_parser::parse_short_header(std::span<const uint8_t> data,
363 size_t conn_id_length)
365{
366 // Minimum: 1 byte first + conn_id + 1 byte packet number
367 if (data.size() < 1 + conn_id_length + 1)
368 {
369 return make_error<std::pair<short_header, size_t>>(
371 "Insufficient data for short header");
372 }
373
374 short_header header;
375 size_t offset = 0;
376
377 // First byte
378 header.first_byte = data[offset++];
379
380 // Validate header form (should be 0 for short header)
381 if (is_long_header(header.first_byte))
382 {
383 return make_error<std::pair<short_header, size_t>>(
385 "Not a short header packet");
386 }
387
388 // Validate fixed bit
389 if (!has_valid_fixed_bit(header.first_byte))
390 {
391 return make_error<std::pair<short_header, size_t>>(
393 "Invalid fixed bit in short header");
394 }
395
396 // Destination Connection ID
397 if (conn_id_length > 0)
398 {
399 if (data.size() < offset + conn_id_length)
400 {
401 return make_error<std::pair<short_header, size_t>>(
403 "Insufficient data for DCID");
404 }
405 header.dest_conn_id = connection_id(data.subspan(offset, conn_id_length));
406 offset += conn_id_length;
407 }
408
409 // Packet number length from reserved bits
410 header.packet_number_length = (header.first_byte & pn_length_mask) + 1;
411
412 return ok(std::make_pair(std::move(header), offset));
413}
414
415// ============================================================================
416// packet_builder
417// ============================================================================
418
419void packet_builder::append_bytes(std::vector<uint8_t>& buffer,
420 std::span<const uint8_t> data)
421{
422 buffer.insert(buffer.end(), data.begin(), data.end());
423}
424
426 const connection_id& dest_cid,
427 const connection_id& src_cid,
428 const std::vector<uint8_t>& token,
429 uint64_t packet_num,
430 uint32_t version) -> std::vector<uint8_t>
431{
432 std::vector<uint8_t> buffer;
433
434 // Calculate packet number length
435 size_t pn_len = packet_number::encoded_length(packet_num, 0);
436
437 // First byte: Long header (1) + Fixed bit (1) + Type (00) + Reserved (00) + PN Length
438 uint8_t first_byte = header_form_long | fixed_bit |
439 (static_cast<uint8_t>(packet_type::initial) << long_packet_type_shift) |
440 static_cast<uint8_t>(pn_len - 1);
441 buffer.push_back(first_byte);
442
443 // Version (4 bytes, big-endian)
444 buffer.push_back(static_cast<uint8_t>(version >> 24));
445 buffer.push_back(static_cast<uint8_t>(version >> 16));
446 buffer.push_back(static_cast<uint8_t>(version >> 8));
447 buffer.push_back(static_cast<uint8_t>(version));
448
449 // DCID Length + DCID
450 buffer.push_back(static_cast<uint8_t>(dest_cid.length()));
451 append_bytes(buffer, dest_cid.data());
452
453 // SCID Length + SCID
454 buffer.push_back(static_cast<uint8_t>(src_cid.length()));
455 append_bytes(buffer, src_cid.data());
456
457 // Token Length (varint) + Token
458 auto token_len_encoded = varint::encode(token.size());
459 append_bytes(buffer, token_len_encoded);
460 append_bytes(buffer, token);
461
462 // Packet number (placeholder - actual encoding done separately)
463 auto [pn_bytes, _] = packet_number::encode(packet_num, 0);
464 append_bytes(buffer, pn_bytes);
465
466 return buffer;
467}
468
470 const connection_id& dest_cid,
471 const connection_id& src_cid,
472 uint64_t packet_num,
473 uint32_t version) -> std::vector<uint8_t>
474{
475 std::vector<uint8_t> buffer;
476
477 size_t pn_len = packet_number::encoded_length(packet_num, 0);
478
479 // First byte: Long header (1) + Fixed bit (1) + Type (10) + Reserved (00) + PN Length
480 uint8_t first_byte = header_form_long | fixed_bit |
481 (static_cast<uint8_t>(packet_type::handshake) << long_packet_type_shift) |
482 static_cast<uint8_t>(pn_len - 1);
483 buffer.push_back(first_byte);
484
485 // Version
486 buffer.push_back(static_cast<uint8_t>(version >> 24));
487 buffer.push_back(static_cast<uint8_t>(version >> 16));
488 buffer.push_back(static_cast<uint8_t>(version >> 8));
489 buffer.push_back(static_cast<uint8_t>(version));
490
491 // DCID
492 buffer.push_back(static_cast<uint8_t>(dest_cid.length()));
493 append_bytes(buffer, dest_cid.data());
494
495 // SCID
496 buffer.push_back(static_cast<uint8_t>(src_cid.length()));
497 append_bytes(buffer, src_cid.data());
498
499 // Packet number
500 auto [pn_bytes, _] = packet_number::encode(packet_num, 0);
501 append_bytes(buffer, pn_bytes);
502
503 return buffer;
504}
505
507 const connection_id& dest_cid,
508 const connection_id& src_cid,
509 uint64_t packet_num,
510 uint32_t version) -> std::vector<uint8_t>
511{
512 std::vector<uint8_t> buffer;
513
514 size_t pn_len = packet_number::encoded_length(packet_num, 0);
515
516 // First byte: Long header (1) + Fixed bit (1) + Type (01) + Reserved (00) + PN Length
517 uint8_t first_byte = header_form_long | fixed_bit |
518 (static_cast<uint8_t>(packet_type::zero_rtt) << long_packet_type_shift) |
519 static_cast<uint8_t>(pn_len - 1);
520 buffer.push_back(first_byte);
521
522 // Version
523 buffer.push_back(static_cast<uint8_t>(version >> 24));
524 buffer.push_back(static_cast<uint8_t>(version >> 16));
525 buffer.push_back(static_cast<uint8_t>(version >> 8));
526 buffer.push_back(static_cast<uint8_t>(version));
527
528 // DCID
529 buffer.push_back(static_cast<uint8_t>(dest_cid.length()));
530 append_bytes(buffer, dest_cid.data());
531
532 // SCID
533 buffer.push_back(static_cast<uint8_t>(src_cid.length()));
534 append_bytes(buffer, src_cid.data());
535
536 // Packet number
537 auto [pn_bytes, _] = packet_number::encode(packet_num, 0);
538 append_bytes(buffer, pn_bytes);
539
540 return buffer;
541}
542
544 const connection_id& dest_cid,
545 const connection_id& src_cid,
546 const std::vector<uint8_t>& token,
547 const std::array<uint8_t, 16>& integrity_tag,
548 uint32_t version) -> std::vector<uint8_t>
549{
550 std::vector<uint8_t> buffer;
551
552 // First byte: Long header (1) + Fixed bit (1) + Type (11) + Unused (0000)
553 uint8_t first_byte = header_form_long | fixed_bit |
554 (static_cast<uint8_t>(packet_type::retry) << long_packet_type_shift);
555 buffer.push_back(first_byte);
556
557 // Version
558 buffer.push_back(static_cast<uint8_t>(version >> 24));
559 buffer.push_back(static_cast<uint8_t>(version >> 16));
560 buffer.push_back(static_cast<uint8_t>(version >> 8));
561 buffer.push_back(static_cast<uint8_t>(version));
562
563 // DCID
564 buffer.push_back(static_cast<uint8_t>(dest_cid.length()));
565 append_bytes(buffer, dest_cid.data());
566
567 // SCID
568 buffer.push_back(static_cast<uint8_t>(src_cid.length()));
569 append_bytes(buffer, src_cid.data());
570
571 // Retry Token
572 append_bytes(buffer, token);
573
574 // Retry Integrity Tag
575 buffer.insert(buffer.end(), integrity_tag.begin(), integrity_tag.end());
576
577 return buffer;
578}
579
581 const connection_id& dest_cid,
582 uint64_t packet_num,
583 bool key_phase,
584 bool spin_bit) -> std::vector<uint8_t>
585{
586 std::vector<uint8_t> buffer;
587
588 size_t pn_len = packet_number::encoded_length(packet_num, 0);
589
590 // First byte: Short header (0) + Fixed bit (1) + Spin + Reserved (00) + Key Phase + PN Length
591 uint8_t first_byte = fixed_bit; // Header form = 0
592 if (spin_bit)
593 {
594 first_byte |= spin_bit_mask;
595 }
596 if (key_phase)
597 {
598 first_byte |= key_phase_mask;
599 }
600 first_byte |= static_cast<uint8_t>(pn_len - 1);
601 buffer.push_back(first_byte);
602
603 // DCID (no length field for short header)
604 append_bytes(buffer, dest_cid.data());
605
606 // Packet number
607 auto [pn_bytes, _] = packet_number::encode(packet_num, 0);
608 append_bytes(buffer, pn_bytes);
609
610 return buffer;
611}
612
613auto packet_builder::build(const long_header& header) -> std::vector<uint8_t>
614{
615 auto ptype = header.type();
616 switch (ptype)
617 {
619 return build_initial(header.dest_conn_id, header.src_conn_id,
620 header.token, header.packet_number, header.version);
622 return build_handshake(header.dest_conn_id, header.src_conn_id,
623 header.packet_number, header.version);
625 return build_zero_rtt(header.dest_conn_id, header.src_conn_id,
626 header.packet_number, header.version);
628 return build_retry(header.dest_conn_id, header.src_conn_id,
629 header.token, header.retry_integrity_tag, header.version);
630 default:
631 return {};
632 }
633}
634
635auto packet_builder::build(const short_header& header) -> std::vector<uint8_t>
636{
637 return build_short(header.dest_conn_id, header.packet_number,
638 header.key_phase(), header.spin_bit());
639}
640
641} // 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:543
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:469
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:580
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:506
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:425
static void append_bytes(std::vector< uint8_t > &buffer, std::span< const uint8_t > data)
Helper to append bytes to buffer.
Definition packet.cpp:419
static auto build(const long_header &header) -> std::vector< uint8_t >
Build a long header from a header structure.
Definition packet.cpp:613
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:129
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:170
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:201
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:362
static auto is_version_negotiation(std::span< const uint8_t > data) noexcept -> bool
Check if this is a version negotiation packet.
Definition packet.cpp:152
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
uint32_t code
Definition hpack.cpp:668
@ 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