Skip to main content

net_wire/quic/transport_parameters/
semantic.rs

1//! RFC 9000 version-1 present-value transport-parameter validation.
2
3use core::fmt;
4
5use super::{
6    QuicTransportParameter, QuicTransportParameterId, QuicTransportParameterIter,
7    QuicTransportParameterParseError, QuicTransportParameterValueError,
8};
9
10/// The endpoint that sent a QUIC transport-parameter extension.
11#[derive(Clone, Copy, Debug, Eq, PartialEq)]
12pub enum QuicTransportParameterSender {
13    /// Parameters sent by a client.
14    Client,
15    /// Parameters sent by a server.
16    Server,
17}
18
19/// Packet-derived connection IDs used to validate an RFC 9000 handshake transport-parameter set.
20///
21/// Each ID is from the first Initial packet in the direction named by RFC 9000 section 7.3.
22/// For [`Self::Server`], `Some(retry_source_connection_id)` means the client received a Retry;
23/// its value is that Retry packet's source connection ID.
24#[derive(Clone, Copy, Debug, Eq, PartialEq)]
25pub enum QuicTransportParameterHandshakeContext<'a> {
26    /// Context for transport parameters sent by a client.
27    Client {
28        /// Source connection ID of the first Initial packet sent by the client.
29        initial_source_connection_id: &'a [u8],
30    },
31    /// Context for transport parameters sent by a server.
32    Server {
33        /// Source connection ID of the first Initial packet sent by the server.
34        initial_source_connection_id: &'a [u8],
35        /// Destination connection ID of the first Initial packet received by the server before Retry.
36        original_destination_connection_id: &'a [u8],
37        /// Source connection ID of the Retry packet received by the client, when one was received.
38        retry_source_connection_id: Option<&'a [u8]>,
39    },
40}
41
42/// Failure to validate RFC 9000 section 7.3 handshake connection-ID consistency.
43///
44/// Tuple offsets are absolute offsets from the start of the transport-parameter extension payload.
45#[derive(Clone, Copy, Debug, Eq, PartialEq)]
46pub enum QuicTransportParameterHandshakeError {
47    /// The packet-derived context does not describe the validated parameter sender.
48    SenderContextMismatch {
49        /// Sender validated for the transport parameters.
50        sender: QuicTransportParameterSender,
51        /// Sender required by the supplied context.
52        context_sender: QuicTransportParameterSender,
53    },
54    /// A structurally validated parameter no longer parses during handshake validation.
55    Structural {
56        /// Exact structural parsing failure.
57        error: QuicTransportParameterParseError,
58    },
59    /// A required connection-ID parameter was absent.
60    Missing {
61        /// Required parameter identifier.
62        id: QuicTransportParameterId,
63    },
64    /// A connection-ID parameter was present when the packet context prohibits it.
65    Unexpected {
66        /// Unexpected parameter identifier.
67        id: QuicTransportParameterId,
68        /// Absolute tuple offset from the extension-payload start.
69        offset: usize,
70    },
71    /// A connection-ID parameter differs from its corresponding packet connection ID.
72    ConnectionIdMismatch {
73        /// Mismatched parameter identifier.
74        id: QuicTransportParameterId,
75        /// Absolute tuple offset from the extension-payload start.
76        offset: usize,
77    },
78}
79
80impl fmt::Display for QuicTransportParameterHandshakeError {
81    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
82        match self {
83            Self::SenderContextMismatch {
84                sender,
85                context_sender,
86            } => write!(
87                f,
88                "QUIC transport-parameter sender {sender:?} does not match handshake context for {context_sender:?}"
89            ),
90            Self::Structural { error } => {
91                write!(
92                    f,
93                    "QUIC handshake transport parameters are not structural: {error}"
94                )
95            }
96            Self::Missing { id } => write!(
97                f,
98                "QUIC handshake transport parameter ID {} is required but missing",
99                id.raw()
100            ),
101            Self::Unexpected { id, offset } => write!(
102                f,
103                "QUIC handshake transport parameter ID {} at tuple offset {offset} is unexpected",
104                id.raw()
105            ),
106            Self::ConnectionIdMismatch { id, offset } => write!(
107                f,
108                "QUIC handshake transport parameter ID {} at tuple offset {offset} does not match its packet connection ID",
109                id.raw()
110            ),
111        }
112    }
113}
114
115/// Failure to validate present RFC 9000 version-1 transport parameters.
116///
117/// Tuple offsets are absolute offsets from the start of the transport-parameter
118/// extension payload.
119#[derive(Clone, Copy, Debug, Eq, PartialEq)]
120pub enum QuicTransportParameterSemanticError {
121    /// A structurally validated parameter no longer parses during validation.
122    Structural {
123        /// Exact structural parsing failure.
124        error: QuicTransportParameterParseError,
125    },
126    /// A known parameter has an invalid RFC 9000 payload layout.
127    Value {
128        /// Decoded parameter identifier.
129        id: QuicTransportParameterId,
130        /// Absolute tuple offset from the extension-payload start.
131        offset: usize,
132        /// Exact typed payload failure.
133        error: QuicTransportParameterValueError,
134    },
135    /// A client sent a parameter reserved for servers.
136    ForbiddenSender {
137        /// Decoded parameter identifier.
138        id: QuicTransportParameterId,
139        /// Absolute tuple offset from the extension-payload start.
140        offset: usize,
141    },
142    /// An integer parameter is below its RFC 9000 minimum.
143    BelowMinimum {
144        /// Decoded parameter identifier.
145        id: QuicTransportParameterId,
146        /// Absolute tuple offset from the extension-payload start.
147        offset: usize,
148        /// Decoded integer value.
149        value: u64,
150        /// Required inclusive minimum.
151        minimum: u64,
152    },
153    /// An integer parameter exceeds its RFC 9000 maximum.
154    AboveMaximum {
155        /// Decoded parameter identifier.
156        id: QuicTransportParameterId,
157        /// Absolute tuple offset from the extension-payload start.
158        offset: usize,
159        /// Decoded integer value.
160        value: u64,
161        /// Required inclusive maximum.
162        maximum: u64,
163    },
164}
165
166impl fmt::Display for QuicTransportParameterSemanticError {
167    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
168        match self {
169            Self::Structural { error } => {
170                write!(f, "QUIC transport parameters are not structural: {error}")
171            }
172            Self::Value { id, offset, error } => write!(
173                f,
174                "QUIC transport parameter ID {} at tuple offset {offset} has an invalid value: {error}",
175                id.raw()
176            ),
177            Self::ForbiddenSender { id, offset } => write!(
178                f,
179                "QUIC client transport parameter ID {} at tuple offset {offset} is server-only",
180                id.raw()
181            ),
182            Self::BelowMinimum {
183                id,
184                offset,
185                value,
186                minimum,
187            } => write!(
188                f,
189                "QUIC transport parameter ID {} at tuple offset {offset} has value {value}, below minimum {minimum}",
190                id.raw()
191            ),
192            Self::AboveMaximum {
193                id,
194                offset,
195                value,
196                maximum,
197            } => write!(
198                f,
199                "QUIC transport parameter ID {} at tuple offset {offset} has value {value}, above maximum {maximum}",
200                id.raw()
201            ),
202        }
203    }
204}
205
206/// A checked borrowed view of present RFC 9000 version-1 transport parameters.
207#[derive(Clone, Copy, Debug, Eq, PartialEq)]
208pub struct QuicTransportParametersV1<'a> {
209    bytes: &'a [u8],
210    sender: QuicTransportParameterSender,
211}
212
213impl<'a> QuicTransportParametersV1<'a> {
214    /// Returns the exact complete extension-payload bytes.
215    pub const fn as_bytes(self) -> &'a [u8] {
216        self.bytes
217    }
218
219    /// Returns the endpoint role used for validation.
220    pub const fn sender(self) -> QuicTransportParameterSender {
221        self.sender
222    }
223
224    /// Returns a fresh iterator over the original raw parameter tuples.
225    pub const fn iter(self) -> QuicTransportParameterIter<'a> {
226        QuicTransportParameterIter::new(self.bytes)
227    }
228
229    /// Validates RFC 9000 section 7.3 connection-ID parameters against packet-derived context.
230    ///
231    /// This checks only the packet consistency rules that require handshake context; construction
232    /// of this type has already validated sender role and present parameter value layouts.
233    pub fn validate_handshake(
234        self,
235        context: QuicTransportParameterHandshakeContext<'_>,
236    ) -> Result<(), QuicTransportParameterHandshakeError> {
237        let context_sender = match context {
238            QuicTransportParameterHandshakeContext::Client { .. } => {
239                QuicTransportParameterSender::Client
240            }
241            QuicTransportParameterHandshakeContext::Server { .. } => {
242                QuicTransportParameterSender::Server
243            }
244        };
245        if self.sender != context_sender {
246            return Err(
247                QuicTransportParameterHandshakeError::SenderContextMismatch {
248                    sender: self.sender,
249                    context_sender,
250                },
251            );
252        }
253
254        match context {
255            QuicTransportParameterHandshakeContext::Client {
256                initial_source_connection_id,
257            } => validate_client_handshake(self, initial_source_connection_id),
258            QuicTransportParameterHandshakeContext::Server {
259                initial_source_connection_id,
260                original_destination_connection_id,
261                retry_source_connection_id,
262            } => validate_server_handshake(
263                self,
264                initial_source_connection_id,
265                original_destination_connection_id,
266                retry_source_connection_id,
267            ),
268        }
269    }
270}
271
272fn validate_client_handshake(
273    parameters: QuicTransportParametersV1<'_>,
274    initial_source_connection_id: &[u8],
275) -> Result<(), QuicTransportParameterHandshakeError> {
276    let mut initial_present = false;
277    let mut offset = 0;
278    for parameter in parameters.iter() {
279        let parameter = parameter
280            .map_err(|error| QuicTransportParameterHandshakeError::Structural { error })?;
281        let id = parameter.parameter_id();
282        if id == QuicTransportParameterId::INITIAL_SOURCE_CONNECTION_ID {
283            initial_present = true;
284            if parameter.value() != initial_source_connection_id {
285                return Err(QuicTransportParameterHandshakeError::ConnectionIdMismatch {
286                    id,
287                    offset,
288                });
289            }
290        }
291        offset += parameter.as_bytes().len();
292    }
293    if !initial_present {
294        return Err(QuicTransportParameterHandshakeError::Missing {
295            id: QuicTransportParameterId::INITIAL_SOURCE_CONNECTION_ID,
296        });
297    }
298    Ok(())
299}
300
301fn validate_server_handshake(
302    parameters: QuicTransportParametersV1<'_>,
303    initial_source_connection_id: &[u8],
304    original_destination_connection_id: &[u8],
305    retry_source_connection_id: Option<&[u8]>,
306) -> Result<(), QuicTransportParameterHandshakeError> {
307    let mut initial_present = false;
308    let mut original_present = false;
309    let mut retry_present = false;
310    let mut offset = 0;
311    for parameter in parameters.iter() {
312        let parameter = parameter
313            .map_err(|error| QuicTransportParameterHandshakeError::Structural { error })?;
314        let id = parameter.parameter_id();
315        match id {
316            QuicTransportParameterId::INITIAL_SOURCE_CONNECTION_ID => {
317                initial_present = true;
318                if parameter.value() != initial_source_connection_id {
319                    return Err(QuicTransportParameterHandshakeError::ConnectionIdMismatch {
320                        id,
321                        offset,
322                    });
323                }
324            }
325            QuicTransportParameterId::ORIGINAL_DESTINATION_CONNECTION_ID => {
326                original_present = true;
327                if parameter.value() != original_destination_connection_id {
328                    return Err(QuicTransportParameterHandshakeError::ConnectionIdMismatch {
329                        id,
330                        offset,
331                    });
332                }
333            }
334            QuicTransportParameterId::RETRY_SOURCE_CONNECTION_ID => {
335                let Some(expected) = retry_source_connection_id else {
336                    return Err(QuicTransportParameterHandshakeError::Unexpected { id, offset });
337                };
338                retry_present = true;
339                if parameter.value() != expected {
340                    return Err(QuicTransportParameterHandshakeError::ConnectionIdMismatch {
341                        id,
342                        offset,
343                    });
344                }
345            }
346            _ => {}
347        }
348        offset += parameter.as_bytes().len();
349    }
350    if !initial_present {
351        return Err(QuicTransportParameterHandshakeError::Missing {
352            id: QuicTransportParameterId::INITIAL_SOURCE_CONNECTION_ID,
353        });
354    }
355    if !original_present {
356        return Err(QuicTransportParameterHandshakeError::Missing {
357            id: QuicTransportParameterId::ORIGINAL_DESTINATION_CONNECTION_ID,
358        });
359    }
360    if retry_source_connection_id.is_some() && !retry_present {
361        return Err(QuicTransportParameterHandshakeError::Missing {
362            id: QuicTransportParameterId::RETRY_SOURCE_CONNECTION_ID,
363        });
364    }
365    Ok(())
366}
367
368pub(super) fn validate<'a>(
369    bytes: &'a [u8],
370    sender: QuicTransportParameterSender,
371) -> Result<QuicTransportParametersV1<'a>, QuicTransportParameterSemanticError> {
372    let mut offset = 0;
373    let parameters = QuicTransportParameterIter::new(bytes);
374    for parameter in parameters {
375        let parameter =
376            parameter.map_err(|error| QuicTransportParameterSemanticError::Structural { error })?;
377        validate_parameter(parameter, sender, offset)?;
378        offset += parameter.as_bytes().len();
379    }
380    Ok(QuicTransportParametersV1 { bytes, sender })
381}
382
383fn validate_parameter(
384    parameter: QuicTransportParameter<'_>,
385    sender: QuicTransportParameterSender,
386    offset: usize,
387) -> Result<(), QuicTransportParameterSemanticError> {
388    let id = parameter.parameter_id();
389    if sender == QuicTransportParameterSender::Client && is_server_only(id) {
390        return Err(QuicTransportParameterSemanticError::ForbiddenSender { id, offset });
391    }
392
393    match id.raw() {
394        0x00 | 0x0f | 0x10 => parameter
395            .connection_id()
396            .map(|_| ())
397            .map_err(|error| value_error(id, offset, error)),
398        0x01 | 0x04 | 0x05 | 0x06 | 0x07 => parameter
399            .integer()
400            .map(|_| ())
401            .map_err(|error| value_error(id, offset, error)),
402        0x02 => parameter
403            .stateless_reset_token()
404            .map(|_| ())
405            .map_err(|error| value_error(id, offset, error)),
406        0x03 => validate_minimum(parameter, offset, 1200),
407        0x08 | 0x09 => validate_maximum(parameter, offset, 1u64 << 60),
408        0x0a => validate_maximum(parameter, offset, 20),
409        0x0b => validate_maximum(parameter, offset, (1u64 << 14) - 1),
410        0x0c => parameter
411            .disable_active_migration()
412            .map_err(|error| value_error(id, offset, error)),
413        0x0d => parameter
414            .preferred_address()
415            .map(|_| ())
416            .map_err(|error| value_error(id, offset, error)),
417        0x0e => validate_minimum(parameter, offset, 2),
418        _ => Ok(()),
419    }
420}
421
422fn validate_minimum(
423    parameter: QuicTransportParameter<'_>,
424    offset: usize,
425    minimum: u64,
426) -> Result<(), QuicTransportParameterSemanticError> {
427    let id = parameter.parameter_id();
428    let value = parameter
429        .integer()
430        .map_err(|error| value_error(id, offset, error))?
431        .value();
432    if value < minimum {
433        return Err(QuicTransportParameterSemanticError::BelowMinimum {
434            id,
435            offset,
436            value,
437            minimum,
438        });
439    }
440    Ok(())
441}
442
443fn validate_maximum(
444    parameter: QuicTransportParameter<'_>,
445    offset: usize,
446    maximum: u64,
447) -> Result<(), QuicTransportParameterSemanticError> {
448    let id = parameter.parameter_id();
449    let value = parameter
450        .integer()
451        .map_err(|error| value_error(id, offset, error))?
452        .value();
453    if value > maximum {
454        return Err(QuicTransportParameterSemanticError::AboveMaximum {
455            id,
456            offset,
457            value,
458            maximum,
459        });
460    }
461    Ok(())
462}
463
464fn value_error(
465    id: QuicTransportParameterId,
466    offset: usize,
467    error: QuicTransportParameterValueError,
468) -> QuicTransportParameterSemanticError {
469    QuicTransportParameterSemanticError::Value { id, offset, error }
470}
471
472const fn is_server_only(id: QuicTransportParameterId) -> bool {
473    matches!(id.raw(), 0x00 | 0x02 | 0x0d | 0x10)
474}