Skip to main content

net_wire/quic/packet/
unprotected.rs

1//! Checked overlays for caller-unprotected QUIC packet headers.
2//!
3//! These views only associate externally unmasked header material with accepted protected packet
4//! structure. They do not apply header protection, authenticate packets, decrypt payloads, or
5//! reconstruct full packet numbers.
6
7use core::fmt;
8
9use super::header::QuicShortHeader;
10use super::long::QuicProtectedLongPacket;
11
12const LONG_PRESERVED_MASK: u8 = 0xf0;
13const SHORT_PRESERVED_MASK: u8 = 0xe0;
14
15/// Failure to associate caller-unprotected bytes with a protected QUIC packet view.
16#[derive(Clone, Copy, Debug, Eq, PartialEq)]
17pub enum QuicUnprotectedHeaderError {
18    /// Header-protection-preserved first-byte bits differ from the protected packet.
19    PreservedBitsMismatch {
20        /// The first byte from the accepted protected packet.
21        protected_first_byte: u8,
22        /// The caller-supplied unprotected first byte.
23        unprotected_first_byte: u8,
24        /// Mask identifying bits that header protection must preserve.
25        preserved_mask: u8,
26    },
27    /// The supplied truncated packet-number byte count disagrees with the unprotected first byte.
28    PacketNumberLengthMismatch {
29        /// Packet-number byte count required by the unprotected first byte.
30        expected: usize,
31        /// Caller-supplied packet-number byte count.
32        supplied: usize,
33    },
34    /// The protected packet remainder cannot physically contain the indicated packet number.
35    ProtectedRemainderTooShort {
36        /// Packet-number byte count required by the unprotected first byte.
37        required: usize,
38        /// Bytes physically available in the protected packet remainder.
39        available: usize,
40    },
41}
42
43impl fmt::Display for QuicUnprotectedHeaderError {
44    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
45        match self {
46            Self::PreservedBitsMismatch {
47                protected_first_byte,
48                unprotected_first_byte,
49                preserved_mask,
50            } => write!(
51                f,
52                "QUIC protected first byte {protected_first_byte:#04x} and unprotected first byte {unprotected_first_byte:#04x} differ under preserved-bit mask {preserved_mask:#04x}"
53            ),
54            Self::PacketNumberLengthMismatch { expected, supplied } => write!(
55                f,
56                "QUIC unprotected packet-number width mismatch: need {expected} bytes, have {supplied}"
57            ),
58            Self::ProtectedRemainderTooShort {
59                required,
60                available,
61            } => write!(
62                f,
63                "QUIC protected packet remainder is too short for its unprotected packet number: need {required} bytes, have {available}"
64            ),
65        }
66    }
67}
68
69/// The encoded width of a QUIC truncated packet number.
70#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
71pub enum QuicPacketNumberLen {
72    /// A one-byte truncated packet number.
73    One,
74    /// A two-byte truncated packet number.
75    Two,
76    /// A three-byte truncated packet number.
77    Three,
78    /// A four-byte truncated packet number.
79    Four,
80}
81
82impl QuicPacketNumberLen {
83    /// Returns the encoded packet-number width in bytes.
84    pub const fn byte_len(self) -> usize {
85        match self {
86            Self::One => 1,
87            Self::Two => 2,
88            Self::Three => 3,
89            Self::Four => 4,
90        }
91    }
92
93    const fn from_first_byte(first_byte: u8) -> Self {
94        match first_byte & 0x03 {
95            0 => Self::One,
96            1 => Self::Two,
97            2 => Self::Three,
98            _ => Self::Four,
99        }
100    }
101}
102
103/// Exact caller-supplied bytes of a truncated QUIC packet number.
104///
105/// This does not reconstruct a full packet number from an expected packet number.
106#[derive(Clone, Copy, Debug, Eq, PartialEq)]
107pub struct QuicTruncatedPacketNumber<'a> {
108    bytes: &'a [u8],
109    encoded_len: QuicPacketNumberLen,
110}
111
112impl<'a> QuicTruncatedPacketNumber<'a> {
113    const fn new(bytes: &'a [u8], encoded_len: QuicPacketNumberLen) -> Self {
114        Self { bytes, encoded_len }
115    }
116
117    /// Returns the exact caller-supplied unprotected packet-number bytes.
118    pub const fn as_bytes(self) -> &'a [u8] {
119        self.bytes
120    }
121
122    /// Returns the packet number's encoded width.
123    pub const fn encoded_len(self) -> QuicPacketNumberLen {
124        self.encoded_len
125    }
126
127    /// Returns the big-endian truncated packet-number value.
128    pub fn value(self) -> u32 {
129        self.bytes
130            .iter()
131            .fold(0, |value, &byte| (value << 8) | u32::from(byte))
132    }
133}
134
135/// A checked caller-unprotected overlay of an accepted QUIC v1 or v2 protected long packet.
136///
137/// The supplied packet-number bytes may be external unmasking output in separate storage. This
138/// view verifies only their width and physical capacity in the protected packet; it neither
139/// authenticates the bytes nor requires them to alias the protected packet.
140#[derive(Clone, Copy, Debug, Eq, PartialEq)]
141pub struct QuicUnprotectedLongHeader<'packet, 'unprotected> {
142    protected_packet: QuicProtectedLongPacket<'packet>,
143    unprotected_first_byte: u8,
144    packet_number: QuicTruncatedPacketNumber<'unprotected>,
145}
146
147impl<'packet, 'unprotected> QuicUnprotectedLongHeader<'packet, 'unprotected> {
148    /// Associates externally unmasked long-header material with an accepted protected packet.
149    ///
150    /// This validates preserved first-byte bits, supplied packet-number width, and physical
151    /// packet-number capacity in that order. It performs no cryptographic operation.
152    pub fn new(
153        protected_packet: QuicProtectedLongPacket<'packet>,
154        unprotected_first_byte: u8,
155        unprotected_packet_number: &'unprotected [u8],
156    ) -> Result<Self, QuicUnprotectedHeaderError> {
157        validate(
158            protected_packet.header().first_byte(),
159            unprotected_first_byte,
160            LONG_PRESERVED_MASK,
161            unprotected_packet_number,
162            protected_packet.protected_remainder().len(),
163        )?;
164        Ok(Self {
165            protected_packet,
166            unprotected_first_byte,
167            packet_number: QuicTruncatedPacketNumber::new(
168                unprotected_packet_number,
169                QuicPacketNumberLen::from_first_byte(unprotected_first_byte),
170            ),
171        })
172    }
173
174    /// Returns the accepted protected long packet.
175    pub const fn protected_packet(self) -> QuicProtectedLongPacket<'packet> {
176        self.protected_packet
177    }
178
179    /// Returns the caller-supplied unprotected first byte.
180    pub const fn unprotected_first_byte(self) -> u8 {
181        self.unprotected_first_byte
182    }
183
184    /// Returns the exact caller-supplied truncated packet number.
185    pub const fn packet_number(self) -> QuicTruncatedPacketNumber<'unprotected> {
186        self.packet_number
187    }
188
189    /// Returns the raw unprotected long-header reserved bits without validating them.
190    pub const fn raw_reserved_bits(self) -> u8 {
191        (self.unprotected_first_byte >> 2) & 0x03
192    }
193}
194
195/// A checked caller-unprotected overlay of an accepted QUIC short header.
196///
197/// This is caller-unprotected structure, not authentication or semantic validation. The supplied
198/// packet-number bytes may be external unmasking output in separate storage and need not alias
199/// the protected packet.
200#[derive(Clone, Copy, Debug, Eq, PartialEq)]
201pub struct QuicUnprotectedShortHeader<'packet, 'unprotected> {
202    protected_packet: QuicShortHeader<'packet>,
203    unprotected_first_byte: u8,
204    packet_number: QuicTruncatedPacketNumber<'unprotected>,
205}
206
207impl<'packet, 'unprotected> QuicUnprotectedShortHeader<'packet, 'unprotected> {
208    /// Associates externally unmasked short-header material with an accepted protected packet.
209    ///
210    /// This validates preserved first-byte bits, supplied packet-number width, and physical
211    /// packet-number capacity in that order. It performs no cryptographic operation.
212    pub fn new(
213        protected_packet: QuicShortHeader<'packet>,
214        unprotected_first_byte: u8,
215        unprotected_packet_number: &'unprotected [u8],
216    ) -> Result<Self, QuicUnprotectedHeaderError> {
217        validate(
218            protected_packet.first_byte(),
219            unprotected_first_byte,
220            SHORT_PRESERVED_MASK,
221            unprotected_packet_number,
222            protected_packet.protected_remainder().len(),
223        )?;
224        Ok(Self {
225            protected_packet,
226            unprotected_first_byte,
227            packet_number: QuicTruncatedPacketNumber::new(
228                unprotected_packet_number,
229                QuicPacketNumberLen::from_first_byte(unprotected_first_byte),
230            ),
231        })
232    }
233
234    /// Returns the accepted protected short packet.
235    pub const fn protected_packet(self) -> QuicShortHeader<'packet> {
236        self.protected_packet
237    }
238
239    /// Returns the caller-supplied unprotected first byte.
240    pub const fn unprotected_first_byte(self) -> u8 {
241        self.unprotected_first_byte
242    }
243
244    /// Returns the exact caller-supplied truncated packet number.
245    pub const fn packet_number(self) -> QuicTruncatedPacketNumber<'unprotected> {
246        self.packet_number
247    }
248
249    /// Returns the raw unprotected short-header reserved bits without validating them.
250    pub const fn raw_reserved_bits(self) -> u8 {
251        (self.unprotected_first_byte >> 3) & 0x03
252    }
253
254    /// Returns the raw unprotected short-header spin bit without validating it.
255    pub const fn spin_bit(self) -> bool {
256        self.unprotected_first_byte & 0x20 != 0
257    }
258
259    /// Returns the raw unprotected short-header key phase bit without validating it.
260    pub const fn key_phase(self) -> bool {
261        self.unprotected_first_byte & 0x04 != 0
262    }
263}
264
265fn validate(
266    protected_first_byte: u8,
267    unprotected_first_byte: u8,
268    preserved_mask: u8,
269    unprotected_packet_number: &[u8],
270    protected_remainder_len: usize,
271) -> Result<(), QuicUnprotectedHeaderError> {
272    if protected_first_byte & preserved_mask != unprotected_first_byte & preserved_mask {
273        return Err(QuicUnprotectedHeaderError::PreservedBitsMismatch {
274            protected_first_byte,
275            unprotected_first_byte,
276            preserved_mask,
277        });
278    }
279
280    let expected = QuicPacketNumberLen::from_first_byte(unprotected_first_byte).byte_len();
281    let supplied = unprotected_packet_number.len();
282    if supplied != expected {
283        return Err(QuicUnprotectedHeaderError::PacketNumberLengthMismatch { expected, supplied });
284    }
285
286    if protected_remainder_len < expected {
287        return Err(QuicUnprotectedHeaderError::ProtectedRemainderTooShort {
288            required: expected,
289            available: protected_remainder_len,
290        });
291    }
292    Ok(())
293}