Skip to main content

net_wire/quic/builder/
terminal.rs

1//! Terminal QUIC packet builders.
2
3use super::super::packet::header::{
4    QuicConnectionId, QuicConnectionIdField, QuicLongHeader, QuicLongPacketType, QuicVersion,
5};
6use super::super::packet::terminal::{QuicRetryPacket, QuicVersionNegotiationPacket};
7use super::QuicPacketBuildError;
8
9const RETRY_INTEGRITY_TAG_LEN: usize = 16;
10
11/// Builds a terminal QUIC Version Negotiation packet in caller-owned storage.
12pub struct QuicVersionNegotiationPacketBuilder<'buffer, 'input> {
13    destination: &'buffer mut [u8],
14    unused_bits: u8,
15    destination_connection_id: QuicConnectionId<'input>,
16    source_connection_id: QuicConnectionId<'input>,
17    versions: &'input [QuicVersion],
18}
19
20impl<'buffer, 'input> QuicVersionNegotiationPacketBuilder<'buffer, 'input> {
21    /// Creates a builder for a Version Negotiation packet with the supplied wire components.
22    pub fn new(
23        destination: &'buffer mut [u8],
24        unused_bits: u8,
25        destination_connection_id: QuicConnectionId<'input>,
26        source_connection_id: QuicConnectionId<'input>,
27        versions: &'input [QuicVersion],
28    ) -> Self {
29        Self {
30            destination,
31            unused_bits,
32            destination_connection_id,
33            source_connection_id,
34            versions,
35        }
36    }
37
38    /// Validates all inputs and capacity before writing a complete Version Negotiation packet.
39    pub fn build(self) -> Result<QuicVersionNegotiationPacket<'buffer>, QuicPacketBuildError> {
40        if self.unused_bits > 0x7f {
41            return Err(QuicPacketBuildError::UnusedBitsOutOfRange {
42                maximum: 0x7f,
43                actual: self.unused_bits,
44            });
45        }
46        validate_connection_id(
47            QuicConnectionIdField::Destination,
48            self.destination_connection_id.len(),
49            255,
50        )?;
51        validate_connection_id(
52            QuicConnectionIdField::Source,
53            self.source_connection_id.len(),
54            255,
55        )?;
56        if self.versions.is_empty() {
57            return Err(QuicPacketBuildError::EmptyVersionList);
58        }
59
60        let version_bytes =
61            self.versions
62                .len()
63                .checked_mul(4)
64                .ok_or(QuicPacketBuildError::LengthOverflow {
65                    component: "version list",
66                    offset: self.versions.len(),
67                    length: 4,
68                })?;
69        let destination_start = 6;
70        let destination_end = checked_add(
71            destination_start,
72            self.destination_connection_id.len(),
73            "destination connection ID",
74        )?;
75        let source_length_offset = destination_end;
76        let source_start = checked_add(source_length_offset, 1, "source connection ID length")?;
77        let source_end = checked_add(
78            source_start,
79            self.source_connection_id.len(),
80            "source connection ID",
81        )?;
82        let required = checked_add(source_end, version_bytes, "version list")?;
83        if self.destination.len() < required {
84            return Err(QuicPacketBuildError::BufferTooShort {
85                required,
86                available: self.destination.len(),
87            });
88        }
89
90        let destination = self.destination;
91        destination[0] = 0x80 | self.unused_bits;
92        destination[1..5].copy_from_slice(&QuicVersion::NEGOTIATION.raw().to_be_bytes());
93        destination[5] = self.destination_connection_id.len() as u8;
94        destination[destination_start..destination_end]
95            .copy_from_slice(self.destination_connection_id.as_bytes());
96        destination[source_length_offset] = self.source_connection_id.len() as u8;
97        destination[source_start..source_end].copy_from_slice(self.source_connection_id.as_bytes());
98        for (slot, version) in destination[source_end..required]
99            .chunks_exact_mut(4)
100            .zip(self.versions)
101        {
102            slot.copy_from_slice(&version.raw().to_be_bytes());
103        }
104
105        let bytes = &destination[..required];
106        let header = QuicLongHeader::from_validated(
107            &bytes[..source_end],
108            bytes[0],
109            QuicVersion::NEGOTIATION,
110            QuicConnectionId::new(&bytes[destination_start..destination_end]),
111            QuicConnectionId::new(&bytes[source_start..source_end]),
112            &bytes[source_end..],
113        );
114        Ok(QuicVersionNegotiationPacket::from_validated(
115            bytes,
116            header,
117            &bytes[source_end..],
118        ))
119    }
120}
121
122/// Builds a terminal QUIC v1 or v2 Retry packet in caller-owned storage.
123pub struct QuicRetryPacketBuilder<'buffer, 'input> {
124    destination: &'buffer mut [u8],
125    version: QuicVersion,
126    unused_bits: u8,
127    destination_connection_id: QuicConnectionId<'input>,
128    source_connection_id: QuicConnectionId<'input>,
129    token: &'input [u8],
130    integrity_tag: &'input [u8; RETRY_INTEGRITY_TAG_LEN],
131}
132
133impl<'buffer, 'input> QuicRetryPacketBuilder<'buffer, 'input> {
134    /// Creates a builder for a Retry packet with supplied opaque token and integrity-tag bytes.
135    pub fn new(
136        destination: &'buffer mut [u8],
137        version: QuicVersion,
138        unused_bits: u8,
139        destination_connection_id: QuicConnectionId<'input>,
140        source_connection_id: QuicConnectionId<'input>,
141        token: &'input [u8],
142        integrity_tag: &'input [u8; RETRY_INTEGRITY_TAG_LEN],
143    ) -> Self {
144        Self {
145            destination,
146            version,
147            unused_bits,
148            destination_connection_id,
149            source_connection_id,
150            token,
151            integrity_tag,
152        }
153    }
154
155    /// Validates all inputs and capacity before writing a complete Retry packet.
156    pub fn build(self) -> Result<QuicRetryPacket<'buffer>, QuicPacketBuildError> {
157        let raw_type = match QuicLongPacketType::Retry.raw_type(self.version) {
158            Some(raw_type) => raw_type,
159            None => {
160                return Err(QuicPacketBuildError::UnsupportedVersion {
161                    version: self.version,
162                });
163            }
164        };
165        if self.unused_bits > 0x0f {
166            return Err(QuicPacketBuildError::UnusedBitsOutOfRange {
167                maximum: 0x0f,
168                actual: self.unused_bits,
169            });
170        }
171        validate_connection_id(
172            QuicConnectionIdField::Destination,
173            self.destination_connection_id.len(),
174            20,
175        )?;
176        validate_connection_id(
177            QuicConnectionIdField::Source,
178            self.source_connection_id.len(),
179            20,
180        )?;
181        if self.token.is_empty() {
182            return Err(QuicPacketBuildError::EmptyRetryToken);
183        }
184
185        let destination_start = 6;
186        let destination_end = checked_add(
187            destination_start,
188            self.destination_connection_id.len(),
189            "destination connection ID",
190        )?;
191        let source_length_offset = destination_end;
192        let source_start = checked_add(source_length_offset, 1, "source connection ID length")?;
193        let source_end = checked_add(
194            source_start,
195            self.source_connection_id.len(),
196            "source connection ID",
197        )?;
198        let token_end = checked_add(source_end, self.token.len(), "Retry Token")?;
199        let required = checked_add(token_end, RETRY_INTEGRITY_TAG_LEN, "Retry Integrity Tag")?;
200        if self.destination.len() < required {
201            return Err(QuicPacketBuildError::BufferTooShort {
202                required,
203                available: self.destination.len(),
204            });
205        }
206
207        let destination = self.destination;
208        destination[0] = 0xc0 | (raw_type << 4) | self.unused_bits;
209        destination[1..5].copy_from_slice(&self.version.raw().to_be_bytes());
210        destination[5] = self.destination_connection_id.len() as u8;
211        destination[destination_start..destination_end]
212            .copy_from_slice(self.destination_connection_id.as_bytes());
213        destination[source_length_offset] = self.source_connection_id.len() as u8;
214        destination[source_start..source_end].copy_from_slice(self.source_connection_id.as_bytes());
215        destination[source_end..token_end].copy_from_slice(self.token);
216        destination[token_end..required].copy_from_slice(self.integrity_tag);
217
218        let bytes = &destination[..required];
219        let header = QuicLongHeader::from_validated(
220            &bytes[..source_end],
221            bytes[0],
222            self.version,
223            QuicConnectionId::new(&bytes[destination_start..destination_end]),
224            QuicConnectionId::new(&bytes[source_start..source_end]),
225            &bytes[source_end..],
226        );
227        Ok(QuicRetryPacket::from_validated(
228            bytes,
229            header,
230            QuicLongPacketType::Retry,
231            &bytes[source_end..token_end],
232            &bytes[token_end..],
233        ))
234    }
235}
236
237fn validate_connection_id(
238    field: QuicConnectionIdField,
239    actual: usize,
240    maximum: usize,
241) -> Result<(), QuicPacketBuildError> {
242    if actual > maximum {
243        return Err(QuicPacketBuildError::ConnectionIdTooLong {
244            field,
245            maximum,
246            actual,
247        });
248    }
249    Ok(())
250}
251
252fn checked_add(
253    offset: usize,
254    length: usize,
255    component: &'static str,
256) -> Result<usize, QuicPacketBuildError> {
257    offset
258        .checked_add(length)
259        .ok_or(QuicPacketBuildError::LengthOverflow {
260            component,
261            offset,
262            length,
263        })
264}