Skip to main content

net_wire/quic/packet/
terminal.rs

1//! Borrowed terminal QUIC Version Negotiation and Retry packet views.
2
3use core::iter::FusedIterator;
4
5use super::header::{QuicConnectionIdField, QuicLongHeader, QuicLongPacketType, QuicVersion};
6use super::parse::QuicPacketParseError;
7
8const RETRY_INTEGRITY_TAG_LEN: usize = 16;
9
10/// A borrowed QUIC Version Negotiation packet occupying its complete supplied datagram slice.
11///
12/// This view preserves the arbitrary lower seven bits of the first byte, connection-ID lengths
13/// permitted by the invariant header, and every four-byte advertised version without applying
14/// endpoint version-selection policy.
15#[derive(Clone, Copy, Debug, Eq, PartialEq)]
16pub struct QuicVersionNegotiationPacket<'a> {
17    bytes: &'a [u8],
18    header: QuicLongHeader<'a>,
19    versions: &'a [u8],
20}
21
22impl<'a> QuicVersionNegotiationPacket<'a> {
23    /// Assembles a Version Negotiation view from builder-validated wire components.
24    pub(crate) const fn from_validated(
25        bytes: &'a [u8],
26        header: QuicLongHeader<'a>,
27        versions: &'a [u8],
28    ) -> Self {
29        Self {
30            bytes,
31            header,
32            versions,
33        }
34    }
35
36    /// Parses a complete Version Negotiation terminal packet.
37    pub fn parse(bytes: &'a [u8]) -> Result<Self, QuicPacketParseError> {
38        let header = QuicLongHeader::parse(bytes)?;
39        if header.version() != QuicVersion::NEGOTIATION {
40            return Err(QuicPacketParseError::WrongVersion {
41                version: header.version(),
42            });
43        }
44
45        let versions = header.version_specific();
46        if versions.is_empty() {
47            return Err(QuicPacketParseError::EmptyVersionList);
48        }
49        if !versions.len().is_multiple_of(4) {
50            return Err(QuicPacketParseError::MisalignedVersionList {
51                length: versions.len(),
52            });
53        }
54
55        Ok(Self {
56            bytes,
57            header,
58            versions,
59        })
60    }
61
62    /// Returns the accepted invariant long-header prefix.
63    pub const fn header(self) -> QuicLongHeader<'a> {
64        self.header
65    }
66
67    /// Returns the exact raw four-byte version-list bytes.
68    pub const fn version_list_bytes(self) -> &'a [u8] {
69        self.versions
70    }
71
72    /// Returns an exact-size iterator over advertised versions in wire order.
73    pub const fn versions(self) -> QuicVersionIter<'a> {
74        QuicVersionIter {
75            bytes: self.versions,
76        }
77    }
78
79    /// Returns the complete supplied terminal packet bytes.
80    pub const fn as_bytes(self) -> &'a [u8] {
81        self.bytes
82    }
83}
84
85/// An allocation-free iterator over the exact raw versions in a Version Negotiation packet.
86#[derive(Clone, Debug)]
87pub struct QuicVersionIter<'a> {
88    bytes: &'a [u8],
89}
90
91impl Iterator for QuicVersionIter<'_> {
92    type Item = QuicVersion;
93
94    fn next(&mut self) -> Option<Self::Item> {
95        let (version, remainder) = self.bytes.split_first_chunk::<4>()?;
96        self.bytes = remainder;
97        Some(QuicVersion::new(u32::from_be_bytes(*version)))
98    }
99
100    fn size_hint(&self) -> (usize, Option<usize>) {
101        let length = self.len();
102        (length, Some(length))
103    }
104}
105
106impl ExactSizeIterator for QuicVersionIter<'_> {
107    fn len(&self) -> usize {
108        self.bytes.len() / 4
109    }
110}
111
112impl FusedIterator for QuicVersionIter<'_> {}
113
114/// A borrowed QUIC v1 or v2 Retry packet occupying its complete supplied datagram slice.
115///
116/// Retry token bytes and the final Retry Integrity Tag remain opaque. This view does not verify
117/// the integrity tag or interpret the token.
118#[derive(Clone, Copy, Debug, Eq, PartialEq)]
119pub struct QuicRetryPacket<'a> {
120    bytes: &'a [u8],
121    header: QuicLongHeader<'a>,
122    packet_type: QuicLongPacketType,
123    token: &'a [u8],
124    integrity_tag: &'a [u8],
125}
126
127impl<'a> QuicRetryPacket<'a> {
128    /// Assembles a Retry view from builder-validated wire components.
129    pub(crate) const fn from_validated(
130        bytes: &'a [u8],
131        header: QuicLongHeader<'a>,
132        packet_type: QuicLongPacketType,
133        token: &'a [u8],
134        integrity_tag: &'a [u8],
135    ) -> Self {
136        Self {
137            bytes,
138            header,
139            packet_type,
140            token,
141            integrity_tag,
142        }
143    }
144
145    /// Parses a complete QUIC v1 or v2 Retry terminal packet.
146    pub fn parse(bytes: &'a [u8]) -> Result<Self, QuicPacketParseError> {
147        let header = QuicLongHeader::parse(bytes)?;
148        let version = header.version();
149        if version != QuicVersion::V1 && version != QuicVersion::V2 {
150            return Err(QuicPacketParseError::UnsupportedVersion { version });
151        }
152        if !header.has_fixed_bit() {
153            return Err(QuicPacketParseError::FixedBitNotSet {
154                first_byte: header.first_byte(),
155            });
156        }
157        validate_connection_id(
158            QuicConnectionIdField::Destination,
159            header.destination_connection_id().len(),
160        )?;
161        validate_connection_id(
162            QuicConnectionIdField::Source,
163            header.source_connection_id().len(),
164        )?;
165
166        let packet_type = header
167            .long_packet_type()
168            .ok_or(QuicPacketParseError::UnsupportedVersion { version })?;
169        if packet_type != QuicLongPacketType::Retry {
170            return Err(QuicPacketParseError::NotRetry { packet_type });
171        }
172
173        let header_end = header.as_bytes().len();
174        let required = header_end.checked_add(RETRY_INTEGRITY_TAG_LEN).ok_or(
175            QuicPacketParseError::LengthOverflow {
176                offset: header_end,
177                length: RETRY_INTEGRITY_TAG_LEN,
178            },
179        )?;
180        if bytes.len() < required {
181            return Err(QuicPacketParseError::Incomplete {
182                required,
183                available: bytes.len(),
184            });
185        }
186        if bytes.len() == required {
187            return Err(QuicPacketParseError::EmptyRetryToken);
188        }
189
190        let tag_start = bytes.len() - RETRY_INTEGRITY_TAG_LEN;
191        Ok(Self {
192            bytes,
193            header,
194            packet_type,
195            token: &bytes[header_end..tag_start],
196            integrity_tag: &bytes[tag_start..],
197        })
198    }
199
200    /// Returns the accepted invariant long-header prefix.
201    pub const fn header(self) -> QuicLongHeader<'a> {
202        self.header
203    }
204
205    /// Returns the supported QUIC version.
206    pub const fn version(self) -> QuicVersion {
207        self.header.version()
208    }
209
210    /// Returns the Retry semantic packet type.
211    pub const fn packet_type(self) -> QuicLongPacketType {
212        self.packet_type
213    }
214
215    /// Returns the exact opaque Retry Token bytes.
216    pub const fn token(self) -> &'a [u8] {
217        self.token
218    }
219
220    /// Returns the exact final 16-byte opaque Retry Integrity Tag.
221    pub const fn integrity_tag(self) -> &'a [u8] {
222        self.integrity_tag
223    }
224
225    /// Returns the complete supplied terminal packet bytes.
226    pub const fn as_bytes(self) -> &'a [u8] {
227        self.bytes
228    }
229}
230
231fn validate_connection_id(
232    field: QuicConnectionIdField,
233    length: usize,
234) -> Result<(), QuicPacketParseError> {
235    if length > 20 {
236        return Err(QuicPacketParseError::ConnectionIdTooLong { field, length });
237    }
238    Ok(())
239}