Skip to main content

net_wire/quic/packet/
header.rs

1//! QUIC packet-header layouts and their version and connection-ID vocabulary.
2
3use super::parse::QuicPacketParseError;
4
5/// An exact borrowed opaque QUIC connection ID.
6///
7/// This scalar deliberately does not impose QUIC versions' 20-byte connection-ID
8/// limit. The invariant long-header layout carries an unsigned-byte length, and
9/// unknown versions and Version Negotiation must remain representable here.
10#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
11pub struct QuicConnectionId<'a>(&'a [u8]);
12
13impl<'a> QuicConnectionId<'a> {
14    /// Creates an opaque connection ID from exact wire bytes.
15    pub const fn new(bytes: &'a [u8]) -> Self {
16        Self(bytes)
17    }
18
19    /// Returns the exact connection-ID bytes.
20    pub const fn as_bytes(self) -> &'a [u8] {
21        self.0
22    }
23
24    /// Returns the connection-ID length in bytes.
25    pub const fn len(self) -> usize {
26        self.0.len()
27    }
28
29    /// Returns whether the connection ID is empty.
30    pub const fn is_empty(self) -> bool {
31        self.0.is_empty()
32    }
33}
34
35/// Identifies a long-header connection-ID field.
36#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
37pub enum QuicConnectionIdField {
38    /// The destination connection ID.
39    Destination,
40    /// The source connection ID.
41    Source,
42}
43
44/// A raw QUIC version value, preserving unknown and reserved versions.
45#[repr(transparent)]
46#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
47pub struct QuicVersion(u32);
48
49impl QuicVersion {
50    /// Version Negotiation (`0x00000000`).
51    pub const NEGOTIATION: Self = Self(0);
52    /// QUIC version 1 (`0x00000001`).
53    pub const V1: Self = Self(1);
54    /// QUIC version 2 (`0x6b3343cf`).
55    pub const V2: Self = Self(0x6b33_43cf);
56
57    /// Creates a version from its raw wire value.
58    pub const fn new(raw: u32) -> Self {
59        Self(raw)
60    }
61
62    /// Returns the raw wire value.
63    pub const fn raw(self) -> u32 {
64        self.0
65    }
66
67    /// Returns whether this is a reserved version with the `0x?a?a?a?a` pattern.
68    pub const fn is_reserved(self) -> bool {
69        self.0 & 0x0f0f_0f0f == 0x0a0a_0a0a
70    }
71}
72
73/// A semantic long-header packet type for a known QUIC version.
74#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
75pub enum QuicLongPacketType {
76    /// Initial packet.
77    Initial,
78    /// 0-RTT packet.
79    ZeroRtt,
80    /// Handshake packet.
81    Handshake,
82    /// Retry packet.
83    Retry,
84}
85
86impl QuicLongPacketType {
87    /// Maps a version and its two raw long-header type bits to a semantic packet type.
88    pub const fn from_raw(version: QuicVersion, raw_type: u8) -> Option<Self> {
89        if raw_type > 3 {
90            return None;
91        }
92        match version {
93            QuicVersion::V1 => match raw_type {
94                0 => Some(Self::Initial),
95                1 => Some(Self::ZeroRtt),
96                2 => Some(Self::Handshake),
97                _ => Some(Self::Retry),
98            },
99            QuicVersion::V2 => match raw_type {
100                0 => Some(Self::Retry),
101                1 => Some(Self::Initial),
102                2 => Some(Self::ZeroRtt),
103                _ => Some(Self::Handshake),
104            },
105            _ => None,
106        }
107    }
108
109    /// Maps this semantic packet type to its two raw long-header type bits for a version.
110    pub const fn raw_type(self, version: QuicVersion) -> Option<u8> {
111        match version {
112            QuicVersion::V1 => match self {
113                Self::Initial => Some(0),
114                Self::ZeroRtt => Some(1),
115                Self::Handshake => Some(2),
116                Self::Retry => Some(3),
117            },
118            QuicVersion::V2 => match self {
119                Self::Initial => Some(1),
120                Self::ZeroRtt => Some(2),
121                Self::Handshake => Some(3),
122                Self::Retry => Some(0),
123            },
124            _ => None,
125        }
126    }
127}
128
129const HEADER_FORM_BIT: u8 = 0x80;
130const FIXED_BIT: u8 = 0x40;
131const LONG_TYPE_SHIFT: u8 = 4;
132const LONG_TYPE_MASK: u8 = 0x03;
133
134/// Caller-supplied information needed to delimit a QUIC short-header destination ID.
135///
136/// Short headers omit their destination connection-ID length on the wire. The context
137/// therefore carries that length as a `usize`, without imposing a long-header wire-field
138/// limit on the caller.
139#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
140pub struct QuicShortHeaderContext {
141    destination_connection_id_len: usize,
142}
143
144impl QuicShortHeaderContext {
145    /// Creates context with the known short-header destination connection-ID length.
146    pub const fn new(destination_connection_id_len: usize) -> Self {
147        Self {
148            destination_connection_id_len,
149        }
150    }
151
152    /// Returns the known short-header destination connection-ID length.
153    pub const fn destination_connection_id_len(self) -> usize {
154        self.destination_connection_id_len
155    }
156}
157
158/// A borrowed RFC 8999 invariant QUIC long-header prefix.
159///
160/// `as_bytes` contains only the invariant prefix structurally owned by this view.
161/// The following bytes are available separately through [`Self::version_specific`] and
162/// are intentionally not interpreted as a payload by this version-independent layer.
163#[derive(Clone, Copy, Debug, Eq, PartialEq)]
164pub struct QuicLongHeader<'a> {
165    bytes: &'a [u8],
166    first_byte: u8,
167    version: QuicVersion,
168    destination_connection_id: QuicConnectionId<'a>,
169    source_connection_id: QuicConnectionId<'a>,
170    version_specific: &'a [u8],
171}
172
173impl<'a> QuicLongHeader<'a> {
174    /// Assembles a long-header view from builder-validated wire components.
175    pub(crate) const fn from_validated(
176        bytes: &'a [u8],
177        first_byte: u8,
178        version: QuicVersion,
179        destination_connection_id: QuicConnectionId<'a>,
180        source_connection_id: QuicConnectionId<'a>,
181        version_specific: &'a [u8],
182    ) -> Self {
183        Self {
184            bytes,
185            first_byte,
186            version,
187            destination_connection_id,
188            source_connection_id,
189            version_specific,
190        }
191    }
192
193    /// Parses exactly the RFC 8999 invariant long-header prefix.
194    pub fn parse(bytes: &'a [u8]) -> Result<Self, QuicPacketParseError> {
195        let first_byte = first_byte(bytes)?;
196        if first_byte & HEADER_FORM_BIT == 0 {
197            return Err(QuicPacketParseError::WrongHeaderForm {
198                expected_long: true,
199                first_byte,
200            });
201        }
202
203        require(bytes, 5)?;
204        let version =
205            QuicVersion::new(u32::from_be_bytes([bytes[1], bytes[2], bytes[3], bytes[4]]));
206        require(bytes, 6)?;
207        let destination_len = usize::from(bytes[5]);
208        let destination_end = field_end(6, destination_len)?;
209        require(bytes, destination_end)?;
210        let destination_connection_id = QuicConnectionId::new(&bytes[6..destination_end]);
211
212        let source_len_offset = destination_end;
213        let source_start = field_end(source_len_offset, 1)?;
214        require(bytes, source_start)?;
215        let source_len = usize::from(bytes[source_len_offset]);
216        let source_end = field_end(source_start, source_len)?;
217        require(bytes, source_end)?;
218
219        Ok(Self {
220            bytes: &bytes[..source_end],
221            first_byte,
222            version,
223            destination_connection_id,
224            source_connection_id: QuicConnectionId::new(&bytes[source_start..source_end]),
225            version_specific: &bytes[source_end..],
226        })
227    }
228
229    /// Returns the raw first byte, including protected and version-specific bits.
230    pub const fn first_byte(self) -> u8 {
231        self.first_byte
232    }
233
234    /// Returns whether the raw fixed bit is set, without validating it.
235    pub const fn has_fixed_bit(self) -> bool {
236        self.first_byte & FIXED_BIT != 0
237    }
238
239    /// Returns the two raw long-header type bits, without assigning unknown-version semantics.
240    pub const fn raw_type_bits(self) -> u8 {
241        (self.first_byte >> LONG_TYPE_SHIFT) & LONG_TYPE_MASK
242    }
243
244    /// Returns the raw QUIC version value.
245    pub const fn version(self) -> QuicVersion {
246        self.version
247    }
248
249    /// Returns the exact destination connection ID.
250    pub const fn destination_connection_id(self) -> QuicConnectionId<'a> {
251        self.destination_connection_id
252    }
253
254    /// Returns the exact source connection ID.
255    pub const fn source_connection_id(self) -> QuicConnectionId<'a> {
256        self.source_connection_id
257    }
258
259    /// Returns the exact invariant-prefix bytes, excluding version-specific bytes.
260    pub const fn as_bytes(self) -> &'a [u8] {
261        self.bytes
262    }
263
264    /// Returns all bytes after the invariant prefix without interpreting them.
265    pub const fn version_specific(self) -> &'a [u8] {
266        self.version_specific
267    }
268
269    /// Returns the version-aware semantic packet type for QUIC v1 or v2 only.
270    pub const fn long_packet_type(self) -> Option<QuicLongPacketType> {
271        QuicLongPacketType::from_raw(self.version, self.raw_type_bits())
272    }
273}
274
275/// A borrowed, context-bounded QUIC short header.
276///
277/// Unlike [`QuicLongHeader::as_bytes`], `as_bytes` returns the complete supplied input.
278/// A short header has no wire length, so this view consumes the datagram remainder after
279/// the caller-context destination connection ID.
280#[derive(Clone, Copy, Debug, Eq, PartialEq)]
281pub struct QuicShortHeader<'a> {
282    bytes: &'a [u8],
283    first_byte: u8,
284    destination_connection_id: QuicConnectionId<'a>,
285    protected_remainder: &'a [u8],
286}
287
288impl<'a> QuicShortHeader<'a> {
289    /// Assembles a short-header view from builder-validated wire components.
290    pub(crate) const fn from_validated(
291        bytes: &'a [u8],
292        first_byte: u8,
293        destination_connection_id: QuicConnectionId<'a>,
294        protected_remainder: &'a [u8],
295    ) -> Self {
296        Self {
297            bytes,
298            first_byte,
299            destination_connection_id,
300            protected_remainder,
301        }
302    }
303
304    /// Parses a short header using caller-supplied destination connection-ID length context.
305    pub fn parse(
306        bytes: &'a [u8],
307        context: QuicShortHeaderContext,
308    ) -> Result<Self, QuicPacketParseError> {
309        let first_byte = first_byte(bytes)?;
310        if first_byte & HEADER_FORM_BIT != 0 {
311            return Err(QuicPacketParseError::WrongHeaderForm {
312                expected_long: false,
313                first_byte,
314            });
315        }
316
317        let destination_end = field_end(1, context.destination_connection_id_len())?;
318        require(bytes, destination_end)?;
319        Ok(Self {
320            bytes,
321            first_byte,
322            destination_connection_id: QuicConnectionId::new(&bytes[1..destination_end]),
323            protected_remainder: &bytes[destination_end..],
324        })
325    }
326
327    /// Returns the raw first byte, whose low bits remain protected and uninterpreted.
328    pub const fn first_byte(self) -> u8 {
329        self.first_byte
330    }
331
332    /// Returns whether the raw fixed bit is set, without validating it.
333    pub const fn has_fixed_bit(self) -> bool {
334        self.first_byte & FIXED_BIT != 0
335    }
336
337    /// Returns the exact destination connection ID delimited by the supplied context.
338    pub const fn destination_connection_id(self) -> QuicConnectionId<'a> {
339        self.destination_connection_id
340    }
341
342    /// Returns all bytes after the context-bounded destination connection ID as opaque bytes.
343    pub const fn protected_remainder(self) -> &'a [u8] {
344        self.protected_remainder
345    }
346
347    /// Returns the complete supplied short-packet slice.
348    pub const fn as_bytes(self) -> &'a [u8] {
349        self.bytes
350    }
351}
352
353fn first_byte(bytes: &[u8]) -> Result<u8, QuicPacketParseError> {
354    bytes
355        .first()
356        .copied()
357        .ok_or(QuicPacketParseError::Incomplete {
358            required: 1,
359            available: 0,
360        })
361}
362
363fn field_end(offset: usize, length: usize) -> Result<usize, QuicPacketParseError> {
364    offset
365        .checked_add(length)
366        .ok_or(QuicPacketParseError::LengthOverflow { offset, length })
367}
368
369fn require(bytes: &[u8], required: usize) -> Result<(), QuicPacketParseError> {
370    if bytes.len() < required {
371        return Err(QuicPacketParseError::Incomplete {
372            required,
373            available: bytes.len(),
374        });
375    }
376    Ok(())
377}