iroh_quinn_proto/
transport_parameters.rs

1//! QUIC connection transport parameters
2//!
3//! The `TransportParameters` type is used to represent the transport parameters
4//! negotiated by peers while establishing a QUIC connection. This process
5//! happens as part of the establishment of the TLS session. As such, the types
6//! contained in this modules should generally only be referred to by custom
7//! implementations of the `crypto::Session` trait.
8
9use std::{
10    convert::TryFrom,
11    net::{Ipv4Addr, Ipv6Addr, SocketAddrV4, SocketAddrV6},
12    num::NonZeroU8,
13};
14
15use bytes::{Buf, BufMut};
16use rand::{Rng as _, RngCore, seq::SliceRandom as _};
17use thiserror::Error;
18
19use crate::{
20    LOC_CID_COUNT, MAX_CID_SIZE, MAX_STREAM_COUNT, RESET_TOKEN_SIZE, ResetToken, Side,
21    TIMER_GRANULARITY, TransportError, VarInt, address_discovery,
22    cid_generator::ConnectionIdGenerator,
23    cid_queue::CidQueue,
24    coding::{BufExt, BufMutExt, UnexpectedEnd},
25    config::{EndpointConfig, ServerConfig, TransportConfig},
26    connection::PathId,
27    shared::ConnectionId,
28};
29
30// Apply a given macro to a list of all the transport parameters having integer types, along with
31// their codes and default values. Using this helps us avoid error-prone duplication of the
32// contained information across decoding, encoding, and the `Default` impl. Whenever we want to do
33// something with transport parameters, we'll handle the bulk of cases by writing a macro that
34// takes a list of arguments in this form, then passing it to this macro.
35macro_rules! apply_params {
36    ($macro:ident) => {
37        $macro! {
38            // #[doc] name (id) = default,
39            /// Milliseconds, disabled if zero
40            max_idle_timeout(MaxIdleTimeout) = 0,
41            /// Limits the size of UDP payloads that the endpoint is willing to receive
42            max_udp_payload_size(MaxUdpPayloadSize) = 65527,
43
44            /// Initial value for the maximum amount of data that can be sent on the connection
45            initial_max_data(InitialMaxData) = 0,
46            /// Initial flow control limit for locally-initiated bidirectional streams
47            initial_max_stream_data_bidi_local(InitialMaxStreamDataBidiLocal) = 0,
48            /// Initial flow control limit for peer-initiated bidirectional streams
49            initial_max_stream_data_bidi_remote(InitialMaxStreamDataBidiRemote) = 0,
50            /// Initial flow control limit for unidirectional streams
51            initial_max_stream_data_uni(InitialMaxStreamDataUni) = 0,
52
53            /// Initial maximum number of bidirectional streams the peer may initiate
54            initial_max_streams_bidi(InitialMaxStreamsBidi) = 0,
55            /// Initial maximum number of unidirectional streams the peer may initiate
56            initial_max_streams_uni(InitialMaxStreamsUni) = 0,
57
58            /// Exponent used to decode the ACK Delay field in the ACK frame
59            ack_delay_exponent(AckDelayExponent) = 3,
60            /// Maximum amount of time in milliseconds by which the endpoint will delay sending
61            /// acknowledgments
62            max_ack_delay(MaxAckDelay) = 25,
63            /// Maximum number of connection IDs from the peer that an endpoint is willing to store
64            active_connection_id_limit(ActiveConnectionIdLimit) = 2,
65        }
66    };
67}
68
69macro_rules! make_struct {
70    {$($(#[$doc:meta])* $name:ident ($id:ident) = $default:expr,)*} => {
71        /// Transport parameters used to negotiate connection-level preferences between peers
72        #[derive(Debug, Copy, Clone, Eq, PartialEq)]
73        pub struct TransportParameters {
74            $($(#[$doc])* pub(crate) $name : VarInt,)*
75
76            /// Does the endpoint support active connection migration
77            pub(crate) disable_active_migration: bool,
78            /// Maximum size for datagram frames
79            pub(crate) max_datagram_frame_size: Option<VarInt>,
80            /// The value that the endpoint included in the Source Connection ID field of the first
81            /// Initial packet it sends for the connection
82            pub(crate) initial_src_cid: Option<ConnectionId>,
83            /// The endpoint is willing to receive QUIC packets containing any value for the fixed
84            /// bit
85            pub(crate) grease_quic_bit: bool,
86
87            /// Minimum amount of time in microseconds by which the endpoint is able to delay
88            /// sending acknowledgments
89            ///
90            /// If a value is provided, it implies that the endpoint supports QUIC Acknowledgement
91            /// Frequency
92            pub(crate) min_ack_delay: Option<VarInt>,
93
94            // Server-only
95            /// The value of the Destination Connection ID field from the first Initial packet sent
96            /// by the client
97            pub(crate) original_dst_cid: Option<ConnectionId>,
98            /// The value that the server included in the Source Connection ID field of a Retry
99            /// packet
100            pub(crate) retry_src_cid: Option<ConnectionId>,
101            /// Token used by the client to verify a stateless reset from the server
102            pub(crate) stateless_reset_token: Option<ResetToken>,
103            /// The server's preferred address for communication after handshake completion
104            pub(crate) preferred_address: Option<PreferredAddress>,
105
106            /// The randomly generated reserved transport parameter to sustain future extensibility
107            /// of transport parameter extensions.
108            /// When present, it is included during serialization but ignored during deserialization.
109            pub(crate) grease_transport_parameter: Option<ReservedTransportParameter>,
110
111            /// Defines the order in which transport parameters are serialized.
112            ///
113            /// This field is initialized only for outgoing `TransportParameters` instances and
114            /// is set to `None` for `TransportParameters` received from a peer.
115            pub(crate) write_order: Option<[u8; TransportParameterId::SUPPORTED.len()]>,
116
117            /// The role of this peer in address discovery, if any.
118            pub(crate) address_discovery_role: address_discovery::Role,
119
120            /// Multipath extension
121            pub(crate) initial_max_path_id: Option<PathId>,
122
123            /// Nat traversal draft
124            pub max_remote_nat_traversal_addresses: Option<NonZeroU8>,
125        }
126
127        // We deliberately don't implement the `Default` trait, since that would be public, and
128        // downstream crates should never construct `TransportParameters` except by decoding those
129        // supplied by a peer.
130        impl TransportParameters {
131            /// Standard defaults, used if the peer does not supply a given parameter.
132            pub(crate) fn default() -> Self {
133                Self {
134                    $($name: VarInt::from_u32($default),)*
135
136                    disable_active_migration: false,
137                    max_datagram_frame_size: None,
138                    initial_src_cid: None,
139                    grease_quic_bit: false,
140                    min_ack_delay: None,
141
142                    original_dst_cid: None,
143                    retry_src_cid: None,
144                    stateless_reset_token: None,
145                    preferred_address: None,
146                    grease_transport_parameter: None,
147                    write_order: None,
148                    address_discovery_role: address_discovery::Role::Disabled,
149                    initial_max_path_id: None,
150                    max_remote_nat_traversal_addresses: None,
151                }
152            }
153        }
154    }
155}
156
157apply_params!(make_struct);
158
159impl TransportParameters {
160    pub(crate) fn new(
161        config: &TransportConfig,
162        endpoint_config: &EndpointConfig,
163        cid_gen: &dyn ConnectionIdGenerator,
164        initial_src_cid: ConnectionId,
165        server_config: Option<&ServerConfig>,
166        rng: &mut impl RngCore,
167    ) -> Self {
168        Self {
169            initial_src_cid: Some(initial_src_cid),
170            initial_max_streams_bidi: config.max_concurrent_bidi_streams,
171            initial_max_streams_uni: config.max_concurrent_uni_streams,
172            initial_max_data: config.receive_window,
173            initial_max_stream_data_bidi_local: config.stream_receive_window,
174            initial_max_stream_data_bidi_remote: config.stream_receive_window,
175            initial_max_stream_data_uni: config.stream_receive_window,
176            max_udp_payload_size: endpoint_config.max_udp_payload_size,
177            max_idle_timeout: config.max_idle_timeout.unwrap_or(VarInt(0)),
178            disable_active_migration: server_config.is_some_and(|c| !c.migration),
179            active_connection_id_limit: if cid_gen.cid_len() == 0 {
180                2 // i.e. default, i.e. unsent
181            } else {
182                CidQueue::LEN as u32
183            }
184            .into(),
185            max_datagram_frame_size: config
186                .datagram_receive_buffer_size
187                .map(|x| (x.min(u16::MAX.into()) as u16).into()),
188            grease_quic_bit: endpoint_config.grease_quic_bit,
189            min_ack_delay: Some(
190                VarInt::from_u64(u64::try_from(TIMER_GRANULARITY.as_micros()).unwrap()).unwrap(),
191            ),
192            grease_transport_parameter: Some(ReservedTransportParameter::random(rng)),
193            write_order: Some({
194                let mut order = std::array::from_fn(|i| i as u8);
195                order.shuffle(rng);
196                order
197            }),
198            address_discovery_role: config.address_discovery_role,
199            initial_max_path_id: config.get_initial_max_path_id(),
200            max_remote_nat_traversal_addresses: config.max_remote_nat_traversal_addresses,
201            ..Self::default()
202        }
203    }
204
205    /// Check that these parameters are legal when resuming from
206    /// certain cached parameters
207    pub(crate) fn validate_resumption_from(&self, cached: &Self) -> Result<(), TransportError> {
208        if cached.active_connection_id_limit > self.active_connection_id_limit
209            || cached.initial_max_data > self.initial_max_data
210            || cached.initial_max_stream_data_bidi_local > self.initial_max_stream_data_bidi_local
211            || cached.initial_max_stream_data_bidi_remote > self.initial_max_stream_data_bidi_remote
212            || cached.initial_max_stream_data_uni > self.initial_max_stream_data_uni
213            || cached.initial_max_streams_bidi > self.initial_max_streams_bidi
214            || cached.initial_max_streams_uni > self.initial_max_streams_uni
215            || cached.max_datagram_frame_size > self.max_datagram_frame_size
216            || cached.grease_quic_bit && !self.grease_quic_bit
217            || cached.address_discovery_role != self.address_discovery_role
218            || cached.max_remote_nat_traversal_addresses != self.max_remote_nat_traversal_addresses
219        {
220            return Err(TransportError::PROTOCOL_VIOLATION(
221                "0-RTT accepted with incompatible transport parameters",
222            ));
223        }
224        Ok(())
225    }
226
227    /// Maximum number of CIDs to issue to this peer
228    ///
229    /// Consider both a) the active_connection_id_limit from the other end; and
230    /// b) LOC_CID_COUNT used locally
231    pub(crate) fn issue_cids_limit(&self) -> u64 {
232        self.active_connection_id_limit.0.min(LOC_CID_COUNT)
233    }
234}
235
236/// A server's preferred address
237///
238/// This is communicated as a transport parameter during TLS session establishment.
239#[derive(Debug, Copy, Clone, Eq, PartialEq)]
240pub(crate) struct PreferredAddress {
241    pub(crate) address_v4: Option<SocketAddrV4>,
242    pub(crate) address_v6: Option<SocketAddrV6>,
243    pub(crate) connection_id: ConnectionId,
244    pub(crate) stateless_reset_token: ResetToken,
245}
246
247impl PreferredAddress {
248    fn wire_size(&self) -> u16 {
249        4 + 2 + 16 + 2 + 1 + self.connection_id.len() as u16 + 16
250    }
251
252    fn write<W: BufMut>(&self, w: &mut W) {
253        w.write(self.address_v4.map_or(Ipv4Addr::UNSPECIFIED, |x| *x.ip()));
254        w.write::<u16>(self.address_v4.map_or(0, |x| x.port()));
255        w.write(self.address_v6.map_or(Ipv6Addr::UNSPECIFIED, |x| *x.ip()));
256        w.write::<u16>(self.address_v6.map_or(0, |x| x.port()));
257        w.write::<u8>(self.connection_id.len() as u8);
258        w.put_slice(&self.connection_id);
259        w.put_slice(&self.stateless_reset_token);
260    }
261
262    fn read<R: Buf>(r: &mut R) -> Result<Self, Error> {
263        let ip_v4 = r.get::<Ipv4Addr>()?;
264        let port_v4 = r.get::<u16>()?;
265        let ip_v6 = r.get::<Ipv6Addr>()?;
266        let port_v6 = r.get::<u16>()?;
267        let cid_len = r.get::<u8>()?;
268        if r.remaining() < cid_len as usize || cid_len > MAX_CID_SIZE as u8 {
269            return Err(Error::Malformed);
270        }
271        let mut stage = [0; MAX_CID_SIZE];
272        r.copy_to_slice(&mut stage[0..cid_len as usize]);
273        let cid = ConnectionId::new(&stage[0..cid_len as usize]);
274        if r.remaining() < 16 {
275            return Err(Error::Malformed);
276        }
277        let mut token = [0; RESET_TOKEN_SIZE];
278        r.copy_to_slice(&mut token);
279        let address_v4 = if ip_v4.is_unspecified() && port_v4 == 0 {
280            None
281        } else {
282            Some(SocketAddrV4::new(ip_v4, port_v4))
283        };
284        let address_v6 = if ip_v6.is_unspecified() && port_v6 == 0 {
285            None
286        } else {
287            Some(SocketAddrV6::new(ip_v6, port_v6, 0, 0))
288        };
289        if address_v4.is_none() && address_v6.is_none() {
290            return Err(Error::IllegalValue);
291        }
292        Ok(Self {
293            address_v4,
294            address_v6,
295            connection_id: cid,
296            stateless_reset_token: token.into(),
297        })
298    }
299}
300
301/// Errors encountered while decoding `TransportParameters`
302#[derive(Debug, Copy, Clone, Eq, PartialEq, Error)]
303pub enum Error {
304    /// Parameters that are semantically invalid
305    #[error("parameter had illegal value")]
306    IllegalValue,
307    /// Catch-all error for problems while decoding transport parameters
308    #[error("parameters were malformed")]
309    Malformed,
310}
311
312impl From<Error> for TransportError {
313    fn from(e: Error) -> Self {
314        match e {
315            Error::IllegalValue => Self::TRANSPORT_PARAMETER_ERROR("illegal value"),
316            Error::Malformed => Self::TRANSPORT_PARAMETER_ERROR("malformed"),
317        }
318    }
319}
320
321impl From<UnexpectedEnd> for Error {
322    fn from(_: UnexpectedEnd) -> Self {
323        Self::Malformed
324    }
325}
326
327impl TransportParameters {
328    /// Encode `TransportParameters` into buffer
329    pub fn write<W: BufMut>(&self, w: &mut W) {
330        let ids = match &self.write_order {
331            Some(order) => order,
332            None => &std::array::from_fn(|i| i as u8),
333        };
334
335        for idx in ids {
336            let id = TransportParameterId::SUPPORTED[*idx as usize];
337            match id {
338                TransportParameterId::ReservedTransportParameter => {
339                    if let Some(param) = self.grease_transport_parameter {
340                        param.write(w);
341                    }
342                }
343                TransportParameterId::StatelessResetToken => {
344                    if let Some(ref x) = self.stateless_reset_token {
345                        w.write_var(id as u64);
346                        w.write_var(16);
347                        w.put_slice(x);
348                    }
349                }
350                TransportParameterId::DisableActiveMigration => {
351                    if self.disable_active_migration {
352                        w.write_var(id as u64);
353                        w.write_var(0);
354                    }
355                }
356                TransportParameterId::MaxDatagramFrameSize => {
357                    if let Some(x) = self.max_datagram_frame_size {
358                        w.write_var(id as u64);
359                        w.write_var(x.size() as u64);
360                        w.write(x);
361                    }
362                }
363                TransportParameterId::PreferredAddress => {
364                    if let Some(ref x) = self.preferred_address {
365                        w.write_var(id as u64);
366                        w.write_var(x.wire_size() as u64);
367                        x.write(w);
368                    }
369                }
370                TransportParameterId::OriginalDestinationConnectionId => {
371                    if let Some(ref cid) = self.original_dst_cid {
372                        w.write_var(id as u64);
373                        w.write_var(cid.len() as u64);
374                        w.put_slice(cid);
375                    }
376                }
377                TransportParameterId::InitialSourceConnectionId => {
378                    if let Some(ref cid) = self.initial_src_cid {
379                        w.write_var(id as u64);
380                        w.write_var(cid.len() as u64);
381                        w.put_slice(cid);
382                    }
383                }
384                TransportParameterId::RetrySourceConnectionId => {
385                    if let Some(ref cid) = self.retry_src_cid {
386                        w.write_var(id as u64);
387                        w.write_var(cid.len() as u64);
388                        w.put_slice(cid);
389                    }
390                }
391                TransportParameterId::GreaseQuicBit => {
392                    if self.grease_quic_bit {
393                        w.write_var(id as u64);
394                        w.write_var(0);
395                    }
396                }
397                TransportParameterId::MinAckDelayDraft07 => {
398                    if let Some(x) = self.min_ack_delay {
399                        w.write_var(id as u64);
400                        w.write_var(x.size() as u64);
401                        w.write(x);
402                    }
403                }
404                TransportParameterId::ObservedAddr => {
405                    if let Some(varint_role) = self.address_discovery_role.as_transport_parameter()
406                    {
407                        w.write_var(id as u64);
408                        w.write_var(varint_role.size() as u64);
409                        w.write(varint_role);
410                    }
411                }
412                TransportParameterId::InitialMaxPathId => {
413                    if let Some(val) = self.initial_max_path_id {
414                        w.write_var(id as u64);
415                        w.write_var(val.size() as u64);
416                        w.write(val);
417                    }
418                }
419                TransportParameterId::IrohNatTraversal => {
420                    if let Some(val) = self.max_remote_nat_traversal_addresses {
421                        w.write_var(id as u64);
422                        w.write(VarInt(1));
423                        w.write(val.get());
424                    }
425                }
426                id => {
427                    macro_rules! write_params {
428                        {$($(#[$doc:meta])* $name:ident ($id:ident) = $default:expr,)*} => {
429                            match id {
430                                $(TransportParameterId::$id => {
431                                    if self.$name.0 != $default {
432                                        w.write_var(id as u64);
433                                        w.write(VarInt::try_from(self.$name.size()).unwrap());
434                                        w.write(self.$name);
435                                    }
436                                })*,
437                                _ => {
438                                    unimplemented!("Missing implementation of write for transport parameter with code {id:?}");
439                                }
440                            }
441                        }
442                    }
443                    apply_params!(write_params);
444                }
445            }
446        }
447    }
448
449    /// Decode `TransportParameters` from buffer
450    pub fn read<R: Buf>(side: Side, r: &mut R) -> Result<Self, Error> {
451        // Initialize to protocol-specified defaults
452        let mut params = Self::default();
453
454        // State to check for duplicate transport parameters.
455        macro_rules! param_state {
456            {$($(#[$doc:meta])* $name:ident ($id:ident) = $default:expr,)*} => {{
457                struct ParamState {
458                    $($name: bool,)*
459                }
460
461                ParamState {
462                    $($name: false,)*
463                }
464            }}
465        }
466        let mut got = apply_params!(param_state);
467
468        while r.has_remaining() {
469            let id = r.get_var()?;
470            let len = r.get_var()?;
471            if (r.remaining() as u64) < len {
472                return Err(Error::Malformed);
473            }
474            let len = len as usize;
475            let Ok(id) = TransportParameterId::try_from(id) else {
476                // unknown transport parameters are ignored
477                r.advance(len);
478                continue;
479            };
480
481            match id {
482                TransportParameterId::OriginalDestinationConnectionId => {
483                    decode_cid(len, &mut params.original_dst_cid, r)?
484                }
485                TransportParameterId::StatelessResetToken => {
486                    if len != 16 || params.stateless_reset_token.is_some() {
487                        return Err(Error::Malformed);
488                    }
489                    let mut tok = [0; RESET_TOKEN_SIZE];
490                    r.copy_to_slice(&mut tok);
491                    params.stateless_reset_token = Some(tok.into());
492                }
493                TransportParameterId::DisableActiveMigration => {
494                    if len != 0 || params.disable_active_migration {
495                        return Err(Error::Malformed);
496                    }
497                    params.disable_active_migration = true;
498                }
499                TransportParameterId::PreferredAddress => {
500                    if params.preferred_address.is_some() {
501                        return Err(Error::Malformed);
502                    }
503                    params.preferred_address = Some(PreferredAddress::read(&mut r.take(len))?);
504                }
505                TransportParameterId::InitialSourceConnectionId => {
506                    decode_cid(len, &mut params.initial_src_cid, r)?
507                }
508                TransportParameterId::RetrySourceConnectionId => {
509                    decode_cid(len, &mut params.retry_src_cid, r)?
510                }
511                TransportParameterId::MaxDatagramFrameSize => {
512                    if len > 8 || params.max_datagram_frame_size.is_some() {
513                        return Err(Error::Malformed);
514                    }
515                    params.max_datagram_frame_size = Some(r.get().unwrap());
516                }
517                TransportParameterId::GreaseQuicBit => match len {
518                    0 => params.grease_quic_bit = true,
519                    _ => return Err(Error::Malformed),
520                },
521                TransportParameterId::MinAckDelayDraft07 => {
522                    params.min_ack_delay = Some(r.get().unwrap())
523                }
524                TransportParameterId::ObservedAddr => {
525                    if !params.address_discovery_role.is_disabled() {
526                        // duplicate parameter
527                        return Err(Error::Malformed);
528                    }
529                    let value: VarInt = r.get()?;
530                    if len != value.size() {
531                        return Err(Error::Malformed);
532                    }
533                    params.address_discovery_role = value.try_into()?;
534                    tracing::debug!(
535                        role = ?params.address_discovery_role,
536                        "address discovery enabled for peer"
537                    );
538                }
539                TransportParameterId::InitialMaxPathId => {
540                    if params.initial_max_path_id.is_some() {
541                        return Err(Error::Malformed);
542                    }
543
544                    let value: PathId = r.get()?;
545                    if len != value.size() {
546                        return Err(Error::Malformed);
547                    }
548
549                    params.initial_max_path_id = Some(value);
550                }
551                TransportParameterId::IrohNatTraversal => {
552                    if params.max_remote_nat_traversal_addresses.is_some() {
553                        return Err(Error::Malformed);
554                    }
555                    if len != 1 {
556                        return Err(Error::Malformed);
557                    }
558
559                    let value: u8 = r.get()?;
560                    let value = NonZeroU8::new(value).ok_or(Error::IllegalValue)?;
561
562                    params.max_remote_nat_traversal_addresses = Some(value);
563                }
564                _ => {
565                    macro_rules! parse {
566                        {$($(#[$doc:meta])* $name:ident ($id:ident) = $default:expr,)*} => {
567                            match id {
568                                $(TransportParameterId::$id => {
569                                    let value = r.get::<VarInt>()?;
570                                    if len != value.size() || got.$name { return Err(Error::Malformed); }
571                                    params.$name = value.into();
572                                    got.$name = true;
573                                })*
574                                _ => r.advance(len),
575                            }
576                        }
577                    }
578                    apply_params!(parse);
579                }
580            }
581        }
582
583        // Semantic validation
584
585        // https://www.rfc-editor.org/rfc/rfc9000.html#section-18.2-4.26.1
586        if params.ack_delay_exponent.0 > 20
587            // https://www.rfc-editor.org/rfc/rfc9000.html#section-18.2-4.28.1
588            || params.max_ack_delay.0 >= 1 << 14
589            // https://www.rfc-editor.org/rfc/rfc9000.html#section-18.2-6.2.1
590            || params.active_connection_id_limit.0 < 2
591            // https://www.rfc-editor.org/rfc/rfc9000.html#section-18.2-4.10.1
592            || params.max_udp_payload_size.0 < 1200
593            // https://www.rfc-editor.org/rfc/rfc9000.html#section-4.6-2
594            || params.initial_max_streams_bidi.0 > MAX_STREAM_COUNT
595            || params.initial_max_streams_uni.0 > MAX_STREAM_COUNT
596            // https://www.ietf.org/archive/id/draft-ietf-quic-ack-frequency-08.html#section-3-4
597            || params.min_ack_delay.is_some_and(|min_ack_delay| {
598                // min_ack_delay uses microseconds, whereas max_ack_delay uses milliseconds
599                min_ack_delay.0 > params.max_ack_delay.0 * 1_000
600            })
601            // https://www.rfc-editor.org/rfc/rfc9000.html#section-18.2-8
602            || (side.is_server()
603                && (params.original_dst_cid.is_some()
604                    || params.preferred_address.is_some()
605                    || params.retry_src_cid.is_some()
606                    || params.stateless_reset_token.is_some()))
607            // https://www.rfc-editor.org/rfc/rfc9000.html#section-18.2-4.38.1
608            || params
609                .preferred_address.is_some_and(|x| x.connection_id.is_empty())
610        {
611            return Err(Error::IllegalValue);
612        }
613
614        Ok(params)
615    }
616}
617
618/// A reserved transport parameter.
619///
620/// It has an identifier of the form 31 * N + 27 for the integer value of N.
621/// Such identifiers are reserved to exercise the requirement that unknown transport parameters be ignored.
622/// The reserved transport parameter has no semantics and can carry arbitrary values.
623/// It may be included in transport parameters sent to the peer, and should be ignored when received.
624///
625/// See spec: <https://www.rfc-editor.org/rfc/rfc9000.html#section-18.1>
626#[derive(Debug, Copy, Clone, Eq, PartialEq)]
627pub(crate) struct ReservedTransportParameter {
628    /// The reserved identifier of the transport parameter
629    id: VarInt,
630
631    /// Buffer to store the parameter payload
632    payload: [u8; Self::MAX_PAYLOAD_LEN],
633
634    /// The number of bytes to include in the wire format from the `payload` buffer
635    payload_len: usize,
636}
637
638impl ReservedTransportParameter {
639    /// Generates a transport parameter with a random payload and a reserved ID.
640    ///
641    /// The implementation is inspired by quic-go and quiche:
642    /// 1. <https://github.com/quic-go/quic-go/blob/3e0a67b2476e1819752f04d75968de042b197b56/internal/wire/transport_parameters.go#L338-L344>
643    /// 2. <https://github.com/google/quiche/blob/cb1090b20c40e2f0815107857324e99acf6ec567/quiche/quic/core/crypto/transport_parameters.cc#L843-L860>
644    fn random(rng: &mut impl RngCore) -> Self {
645        let id = Self::generate_reserved_id(rng);
646
647        let payload_len = rng.random_range(0..Self::MAX_PAYLOAD_LEN);
648
649        let payload = {
650            let mut slice = [0u8; Self::MAX_PAYLOAD_LEN];
651            rng.fill_bytes(&mut slice[..payload_len]);
652            slice
653        };
654
655        Self {
656            id,
657            payload,
658            payload_len,
659        }
660    }
661
662    fn write(&self, w: &mut impl BufMut) {
663        w.write_var(self.id.0);
664        w.write_var(self.payload_len as u64);
665        w.put_slice(&self.payload[..self.payload_len]);
666    }
667
668    /// Generates a random reserved identifier of the form `31 * N + 27`, as required by RFC 9000.
669    /// Reserved transport parameter identifiers are used to test compliance with the requirement
670    /// that unknown transport parameters must be ignored by peers.
671    /// See: <https://www.rfc-editor.org/rfc/rfc9000.html#section-18.1> and <https://www.rfc-editor.org/rfc/rfc9000.html#section-22.3>
672    fn generate_reserved_id(rng: &mut impl RngCore) -> VarInt {
673        let id = {
674            let rand = rng.random_range(0u64..(1 << 62) - 27);
675            let n = rand / 31;
676            31 * n + 27
677        };
678        debug_assert!(
679            id % 31 == 27,
680            "generated id does not have the form of 31 * N + 27"
681        );
682        VarInt::from_u64(id).expect(
683            "generated id does fit into range of allowed transport parameter IDs: [0; 2^62)",
684        )
685    }
686
687    /// The maximum length of the payload to include as the parameter payload.
688    /// This value is not a specification-imposed limit but is chosen to match
689    /// the limit used by other implementations of QUIC, e.g., quic-go and quiche.
690    const MAX_PAYLOAD_LEN: usize = 16;
691}
692
693#[repr(u64)]
694#[derive(Debug, Clone, Copy, PartialEq, Eq)]
695pub(crate) enum TransportParameterId {
696    // https://www.rfc-editor.org/rfc/rfc9000.html#iana-tp-table
697    OriginalDestinationConnectionId = 0x00,
698    MaxIdleTimeout = 0x01,
699    StatelessResetToken = 0x02,
700    MaxUdpPayloadSize = 0x03,
701    InitialMaxData = 0x04,
702    InitialMaxStreamDataBidiLocal = 0x05,
703    InitialMaxStreamDataBidiRemote = 0x06,
704    InitialMaxStreamDataUni = 0x07,
705    InitialMaxStreamsBidi = 0x08,
706    InitialMaxStreamsUni = 0x09,
707    AckDelayExponent = 0x0A,
708    MaxAckDelay = 0x0B,
709    DisableActiveMigration = 0x0C,
710    PreferredAddress = 0x0D,
711    ActiveConnectionIdLimit = 0x0E,
712    InitialSourceConnectionId = 0x0F,
713    RetrySourceConnectionId = 0x10,
714
715    // Smallest possible ID of reserved transport parameter https://datatracker.ietf.org/doc/html/rfc9000#section-22.3
716    ReservedTransportParameter = 0x1B,
717
718    // https://www.rfc-editor.org/rfc/rfc9221.html#section-3
719    MaxDatagramFrameSize = 0x20,
720
721    // https://www.rfc-editor.org/rfc/rfc9287.html#section-3
722    GreaseQuicBit = 0x2AB2,
723
724    // https://datatracker.ietf.org/doc/html/draft-ietf-quic-ack-frequency#section-10.1
725    MinAckDelayDraft07 = 0xFF04DE1B,
726
727    // <https://datatracker.ietf.org/doc/draft-seemann-quic-address-discovery/>
728    ObservedAddr = 0x9f81a176,
729
730    // https://datatracker.ietf.org/doc/html/draft-ietf-quic-multipath
731    InitialMaxPathId = 0x3e,
732
733    // inspired by https://www.ietf.org/archive/id/draft-seemann-quic-nat-traversal-02.html,
734    // simplified to iroh's needs
735    IrohNatTraversal = 0x3d7f91120401,
736}
737
738impl TransportParameterId {
739    /// Array with all supported transport parameter IDs
740    const SUPPORTED: [Self; 24] = [
741        Self::MaxIdleTimeout,
742        Self::MaxUdpPayloadSize,
743        Self::InitialMaxData,
744        Self::InitialMaxStreamDataBidiLocal,
745        Self::InitialMaxStreamDataBidiRemote,
746        Self::InitialMaxStreamDataUni,
747        Self::InitialMaxStreamsBidi,
748        Self::InitialMaxStreamsUni,
749        Self::AckDelayExponent,
750        Self::MaxAckDelay,
751        Self::ActiveConnectionIdLimit,
752        Self::ReservedTransportParameter,
753        Self::StatelessResetToken,
754        Self::DisableActiveMigration,
755        Self::MaxDatagramFrameSize,
756        Self::PreferredAddress,
757        Self::OriginalDestinationConnectionId,
758        Self::InitialSourceConnectionId,
759        Self::RetrySourceConnectionId,
760        Self::GreaseQuicBit,
761        Self::MinAckDelayDraft07,
762        Self::ObservedAddr,
763        Self::InitialMaxPathId,
764        Self::IrohNatTraversal,
765    ];
766}
767
768impl std::cmp::PartialEq<u64> for TransportParameterId {
769    fn eq(&self, other: &u64) -> bool {
770        *other == (*self as u64)
771    }
772}
773
774impl TryFrom<u64> for TransportParameterId {
775    type Error = ();
776
777    fn try_from(value: u64) -> Result<Self, Self::Error> {
778        let param = match value {
779            id if Self::MaxIdleTimeout == id => Self::MaxIdleTimeout,
780            id if Self::MaxUdpPayloadSize == id => Self::MaxUdpPayloadSize,
781            id if Self::InitialMaxData == id => Self::InitialMaxData,
782            id if Self::InitialMaxStreamDataBidiLocal == id => Self::InitialMaxStreamDataBidiLocal,
783            id if Self::InitialMaxStreamDataBidiRemote == id => {
784                Self::InitialMaxStreamDataBidiRemote
785            }
786            id if Self::InitialMaxStreamDataUni == id => Self::InitialMaxStreamDataUni,
787            id if Self::InitialMaxStreamsBidi == id => Self::InitialMaxStreamsBidi,
788            id if Self::InitialMaxStreamsUni == id => Self::InitialMaxStreamsUni,
789            id if Self::AckDelayExponent == id => Self::AckDelayExponent,
790            id if Self::MaxAckDelay == id => Self::MaxAckDelay,
791            id if Self::ActiveConnectionIdLimit == id => Self::ActiveConnectionIdLimit,
792            id if Self::ReservedTransportParameter == id => Self::ReservedTransportParameter,
793            id if Self::StatelessResetToken == id => Self::StatelessResetToken,
794            id if Self::DisableActiveMigration == id => Self::DisableActiveMigration,
795            id if Self::MaxDatagramFrameSize == id => Self::MaxDatagramFrameSize,
796            id if Self::PreferredAddress == id => Self::PreferredAddress,
797            id if Self::OriginalDestinationConnectionId == id => {
798                Self::OriginalDestinationConnectionId
799            }
800            id if Self::InitialSourceConnectionId == id => Self::InitialSourceConnectionId,
801            id if Self::RetrySourceConnectionId == id => Self::RetrySourceConnectionId,
802            id if Self::GreaseQuicBit == id => Self::GreaseQuicBit,
803            id if Self::MinAckDelayDraft07 == id => Self::MinAckDelayDraft07,
804            id if Self::ObservedAddr == id => Self::ObservedAddr,
805            id if Self::InitialMaxPathId == id => Self::InitialMaxPathId,
806            id if Self::IrohNatTraversal == id => Self::IrohNatTraversal,
807            _ => return Err(()),
808        };
809        Ok(param)
810    }
811}
812
813fn decode_cid(len: usize, value: &mut Option<ConnectionId>, r: &mut impl Buf) -> Result<(), Error> {
814    if len > MAX_CID_SIZE || value.is_some() || r.remaining() < len {
815        return Err(Error::Malformed);
816    }
817
818    *value = Some(ConnectionId::from_buf(r, len));
819    Ok(())
820}
821
822#[cfg(test)]
823mod test {
824
825    use super::*;
826
827    #[test]
828    fn coding() {
829        let mut buf = Vec::new();
830        let params = TransportParameters {
831            initial_src_cid: Some(ConnectionId::new(&[])),
832            original_dst_cid: Some(ConnectionId::new(&[])),
833            initial_max_streams_bidi: 16u32.into(),
834            initial_max_streams_uni: 16u32.into(),
835            ack_delay_exponent: 2u32.into(),
836            max_udp_payload_size: 1200u32.into(),
837            preferred_address: Some(PreferredAddress {
838                address_v4: Some(SocketAddrV4::new(Ipv4Addr::LOCALHOST, 42)),
839                address_v6: Some(SocketAddrV6::new(Ipv6Addr::LOCALHOST, 24, 0, 0)),
840                connection_id: ConnectionId::new(&[0x42]),
841                stateless_reset_token: [0xab; RESET_TOKEN_SIZE].into(),
842            }),
843            grease_quic_bit: true,
844            min_ack_delay: Some(2_000u32.into()),
845            address_discovery_role: address_discovery::Role::SendOnly,
846            initial_max_path_id: Some(PathId::MAX),
847            max_remote_nat_traversal_addresses: Some(5u8.try_into().unwrap()),
848            ..TransportParameters::default()
849        };
850        params.write(&mut buf);
851        assert_eq!(
852            TransportParameters::read(Side::Client, &mut buf.as_slice()).unwrap(),
853            params
854        );
855    }
856
857    #[test]
858    fn reserved_transport_parameter_generate_reserved_id() {
859        let mut rngs = [
860            StepRng(0),
861            StepRng(1),
862            StepRng(27),
863            StepRng(31),
864            StepRng(u32::MAX as u64),
865            StepRng(u32::MAX as u64 - 1),
866            StepRng(u32::MAX as u64 + 1),
867            StepRng(u32::MAX as u64 - 27),
868            StepRng(u32::MAX as u64 + 27),
869            StepRng(u32::MAX as u64 - 31),
870            StepRng(u32::MAX as u64 + 31),
871            StepRng(u64::MAX),
872            StepRng(u64::MAX - 1),
873            StepRng(u64::MAX - 27),
874            StepRng(u64::MAX - 31),
875            StepRng(1 << 62),
876            StepRng((1 << 62) - 1),
877            StepRng((1 << 62) + 1),
878            StepRng((1 << 62) - 27),
879            StepRng((1 << 62) + 27),
880            StepRng((1 << 62) - 31),
881            StepRng((1 << 62) + 31),
882        ];
883        for rng in &mut rngs {
884            let id = ReservedTransportParameter::generate_reserved_id(rng);
885            assert!(id.0 % 31 == 27)
886        }
887    }
888
889    struct StepRng(u64);
890
891    impl RngCore for StepRng {
892        #[inline]
893        fn next_u32(&mut self) -> u32 {
894            self.next_u64() as u32
895        }
896
897        #[inline]
898        fn next_u64(&mut self) -> u64 {
899            let res = self.0;
900            self.0 = self.0.wrapping_add(1);
901            res
902        }
903
904        #[inline]
905        fn fill_bytes(&mut self, dst: &mut [u8]) {
906            let mut left = dst;
907            while left.len() >= 8 {
908                let (l, r) = left.split_at_mut(8);
909                left = r;
910                l.copy_from_slice(&self.next_u64().to_le_bytes());
911            }
912            let n = left.len();
913            if n > 0 {
914                left.copy_from_slice(&self.next_u32().to_le_bytes()[..n]);
915            }
916        }
917    }
918
919    #[test]
920    fn reserved_transport_parameter_ignored_when_read() {
921        let mut buf = Vec::new();
922        let reserved_parameter = ReservedTransportParameter::random(&mut rand::rng());
923        assert!(reserved_parameter.payload_len < ReservedTransportParameter::MAX_PAYLOAD_LEN);
924        assert!(reserved_parameter.id.0 % 31 == 27);
925
926        reserved_parameter.write(&mut buf);
927        assert!(!buf.is_empty());
928        let read_params = TransportParameters::read(Side::Server, &mut buf.as_slice()).unwrap();
929        assert_eq!(read_params, TransportParameters::default());
930    }
931
932    #[test]
933    fn read_semantic_validation() {
934        #[allow(clippy::type_complexity)]
935        let illegal_params_builders: Vec<Box<dyn FnMut(&mut TransportParameters)>> = vec![
936            Box::new(|t| {
937                // This min_ack_delay is bigger than max_ack_delay!
938                let min_ack_delay = t.max_ack_delay.0 * 1_000 + 1;
939                t.min_ack_delay = Some(VarInt::from_u64(min_ack_delay).unwrap())
940            }),
941            Box::new(|t| {
942                // Preferred address can only be sent by senders (and we are reading the transport
943                // params as a client)
944                t.preferred_address = Some(PreferredAddress {
945                    address_v4: Some(SocketAddrV4::new(Ipv4Addr::LOCALHOST, 42)),
946                    address_v6: None,
947                    connection_id: ConnectionId::new(&[]),
948                    stateless_reset_token: [0xab; RESET_TOKEN_SIZE].into(),
949                })
950            }),
951        ];
952
953        for mut builder in illegal_params_builders {
954            let mut buf = Vec::new();
955            let mut params = TransportParameters::default();
956            builder(&mut params);
957            params.write(&mut buf);
958
959            assert_eq!(
960                TransportParameters::read(Side::Server, &mut buf.as_slice()),
961                Err(Error::IllegalValue)
962            );
963        }
964    }
965
966    #[test]
967    fn resumption_params_validation() {
968        let high_limit = TransportParameters {
969            initial_max_streams_uni: 32u32.into(),
970            ..TransportParameters::default()
971        };
972        let low_limit = TransportParameters {
973            initial_max_streams_uni: 16u32.into(),
974            ..TransportParameters::default()
975        };
976        high_limit.validate_resumption_from(&low_limit).unwrap();
977        low_limit.validate_resumption_from(&high_limit).unwrap_err();
978    }
979}