noq_proto/config/
transport.rs

1#[cfg(feature = "qlog")]
2use std::path::Path;
3use std::{
4    fmt,
5    net::SocketAddr,
6    num::{NonZeroU8, NonZeroU32},
7    sync::Arc,
8};
9
10use crate::{
11    ConnectionId, Duration, INITIAL_MTU, Instant, MAX_UDP_PAYLOAD, Side, VarInt,
12    VarIntBoundsExceeded, address_discovery, congestion, connection::qlog::QlogSink,
13};
14#[cfg(feature = "qlog")]
15use crate::{QlogFactory, QlogFileFactory};
16
17/// Parameters governing the core QUIC state machine
18///
19/// Default values should be suitable for most internet applications. Applications protocols which
20/// forbid remotely-initiated streams should set `max_concurrent_bidi_streams` and
21/// `max_concurrent_uni_streams` to zero.
22///
23/// In some cases, performance or resource requirements can be improved by tuning these values to
24/// suit a particular application and/or network connection. In particular, data window sizes can be
25/// tuned for a particular expected round trip time, link capacity, and memory availability. Tuning
26/// for higher bandwidths and latencies increases worst-case memory consumption, but does not impair
27/// performance at lower bandwidths and latencies. The default configuration is tuned for a 100Mbps
28/// link with a 100ms round trip time.
29#[derive(Clone)]
30pub struct TransportConfig {
31    pub(crate) max_concurrent_bidi_streams: VarInt,
32    pub(crate) max_concurrent_uni_streams: VarInt,
33    pub(crate) max_idle_timeout: Option<VarInt>,
34    pub(crate) stream_receive_window: VarInt,
35    pub(crate) receive_window: VarInt,
36    pub(crate) send_window: u64,
37    pub(crate) send_fairness: bool,
38
39    pub(crate) packet_threshold: u32,
40    pub(crate) time_threshold: f32,
41    pub(crate) initial_rtt: Duration,
42    pub(crate) initial_mtu: u16,
43    pub(crate) min_mtu: u16,
44    pub(crate) mtu_discovery_config: Option<MtuDiscoveryConfig>,
45    pub(crate) pad_to_mtu: bool,
46    pub(crate) ack_frequency_config: Option<AckFrequencyConfig>,
47    pub(crate) max_outgoing_bytes_per_second: Option<u64>,
48
49    pub(crate) persistent_congestion_threshold: u32,
50    pub(crate) keep_alive_interval: Option<Duration>,
51    pub(crate) crypto_buffer_size: usize,
52    pub(crate) allow_spin: bool,
53    pub(crate) datagram_receive_buffer_size: Option<usize>,
54    pub(crate) datagram_send_buffer_size: usize,
55    #[cfg(test)]
56    pub(crate) deterministic_packet_numbers: bool,
57
58    pub(crate) congestion_controller_factory: Arc<dyn congestion::ControllerFactory + Send + Sync>,
59
60    pub(crate) enable_segmentation_offload: bool,
61
62    pub(crate) address_discovery_role: address_discovery::Role,
63
64    pub(crate) max_concurrent_multipath_paths: Option<NonZeroU32>,
65
66    pub(crate) default_path_max_idle_timeout: Option<Duration>,
67    pub(crate) default_path_keep_alive_interval: Option<Duration>,
68
69    pub(crate) max_remote_nat_traversal_addresses: Option<NonZeroU8>,
70    pub(crate) server_handshake_migration: bool,
71
72    #[cfg(feature = "qlog")]
73    pub(crate) qlog_factory: Option<Arc<dyn QlogFactory>>,
74}
75
76impl TransportConfig {
77    /// Maximum number of incoming bidirectional streams that may be open concurrently
78    ///
79    /// Must be nonzero for the peer to open any bidirectional streams.
80    ///
81    /// Worst-case memory use is directly proportional to `max_concurrent_bidi_streams *
82    /// stream_receive_window`, with an upper bound proportional to `receive_window`.
83    pub fn max_concurrent_bidi_streams(&mut self, value: VarInt) -> &mut Self {
84        self.max_concurrent_bidi_streams = value;
85        self
86    }
87
88    /// Variant of `max_concurrent_bidi_streams` affecting unidirectional streams
89    pub fn max_concurrent_uni_streams(&mut self, value: VarInt) -> &mut Self {
90        self.max_concurrent_uni_streams = value;
91        self
92    }
93
94    /// Maximum duration of inactivity to accept before timing out the connection.
95    ///
96    /// The true idle timeout is the minimum of this and the peer's own max idle timeout. `None`
97    /// represents an infinite timeout. Defaults to 30 seconds.
98    ///
99    /// **WARNING**: If a peer or its network path malfunctions or acts maliciously, an infinite
100    /// idle timeout can result in permanently hung futures!
101    ///
102    /// ```
103    /// # use std::{convert::TryInto, time::Duration};
104    /// # use noq_proto::{TransportConfig, VarInt, VarIntBoundsExceeded};
105    /// # fn main() -> Result<(), VarIntBoundsExceeded> {
106    /// let mut config = TransportConfig::default();
107    ///
108    /// // Set the idle timeout as `VarInt`-encoded milliseconds
109    /// config.max_idle_timeout(Some(VarInt::from_u32(10_000).into()));
110    ///
111    /// // Set the idle timeout as a `Duration`
112    /// config.max_idle_timeout(Some(Duration::from_secs(10).try_into()?));
113    /// # Ok(())
114    /// # }
115    /// ```
116    pub fn max_idle_timeout(&mut self, value: Option<IdleTimeout>) -> &mut Self {
117        self.max_idle_timeout = value.map(|t| t.0);
118        self
119    }
120
121    /// Maximum number of bytes the peer may transmit without acknowledgement on any one stream
122    /// before becoming blocked.
123    ///
124    /// This should be set to at least the expected connection latency multiplied by the maximum
125    /// desired throughput. Setting this smaller than `receive_window` helps ensure that a single
126    /// stream doesn't monopolize receive buffers, which may otherwise occur if the application
127    /// chooses not to read from a large stream for a time while still requiring data on other
128    /// streams.
129    pub fn stream_receive_window(&mut self, value: VarInt) -> &mut Self {
130        self.stream_receive_window = value;
131        self
132    }
133
134    /// Maximum number of bytes the peer may transmit across all streams of a connection before
135    /// becoming blocked.
136    ///
137    /// This should be set to at least the expected connection latency multiplied by the maximum
138    /// desired throughput. Larger values can be useful to allow maximum throughput within a
139    /// stream while another is blocked.
140    pub fn receive_window(&mut self, value: VarInt) -> &mut Self {
141        self.receive_window = value;
142        self
143    }
144
145    /// Maximum number of bytes to transmit to a peer without acknowledgment
146    ///
147    /// Provides an upper bound on memory when communicating with peers that issue large amounts of
148    /// flow control credit. Endpoints that wish to handle large numbers of connections robustly
149    /// should take care to set this low enough to guarantee memory exhaustion does not occur if
150    /// every connection uses the entire window.
151    pub fn send_window(&mut self, value: u64) -> &mut Self {
152        self.send_window = value;
153        self
154    }
155
156    /// Whether to implement fair queuing for send streams having the same priority.
157    ///
158    /// When enabled, connections schedule data from outgoing streams having the same priority in a
159    /// round-robin fashion. When disabled, streams are scheduled in the order they are written to.
160    ///
161    /// Note that this only affects streams with the same priority. Higher priority streams always
162    /// take precedence over lower priority streams.
163    ///
164    /// Disabling fairness can reduce fragmentation and protocol overhead for workloads that use
165    /// many small streams.
166    pub fn send_fairness(&mut self, value: bool) -> &mut Self {
167        self.send_fairness = value;
168        self
169    }
170
171    /// Maximum reordering in packet number space before FACK style loss detection considers a
172    /// packet lost. Should not be less than 3, per RFC5681.
173    pub fn packet_threshold(&mut self, value: u32) -> &mut Self {
174        self.packet_threshold = value;
175        self
176    }
177
178    /// Maximum reordering in time space before time based loss detection considers a packet lost,
179    /// as a factor of RTT
180    pub fn time_threshold(&mut self, value: f32) -> &mut Self {
181        self.time_threshold = value;
182        self
183    }
184
185    /// The RTT used before an RTT sample is taken
186    pub fn initial_rtt(&mut self, value: Duration) -> &mut Self {
187        self.initial_rtt = value;
188        self
189    }
190
191    /// The initial value to be used as the maximum UDP payload size before running MTU discovery
192    /// (see [`TransportConfig::mtu_discovery_config`]).
193    ///
194    /// Must be at least 1200, which is the default, and known to be safe for typical internet
195    /// applications. Larger values are more efficient, but increase the risk of packet loss due to
196    /// exceeding the network path's IP MTU. If the provided value is higher than what the network
197    /// path actually supports, packet loss will eventually trigger black hole detection and bring
198    /// it down to [`TransportConfig::min_mtu`].
199    pub fn initial_mtu(&mut self, value: u16) -> &mut Self {
200        self.initial_mtu = value.max(INITIAL_MTU);
201        self
202    }
203
204    pub(crate) fn get_initial_mtu(&self) -> u16 {
205        self.initial_mtu.max(self.min_mtu)
206    }
207
208    /// The maximum UDP payload size guaranteed to be supported by the network.
209    ///
210    /// Must be at least 1200, which is the default, and lower than or equal to
211    /// [`TransportConfig::initial_mtu`].
212    ///
213    /// Real-world MTUs can vary according to ISP, VPN, and properties of intermediate network links
214    /// outside of either endpoint's control. Extreme care should be used when raising this value
215    /// outside of private networks where these factors are fully controlled. If the provided value
216    /// is higher than what the network path actually supports, the result will be unpredictable and
217    /// catastrophic packet loss, without a possibility of repair. Prefer
218    /// [`TransportConfig::initial_mtu`] together with
219    /// [`TransportConfig::mtu_discovery_config`] to set a maximum UDP payload size that robustly
220    /// adapts to the network.
221    pub fn min_mtu(&mut self, value: u16) -> &mut Self {
222        self.min_mtu = value.max(INITIAL_MTU);
223        self
224    }
225
226    /// Specifies the MTU discovery config (see [`MtuDiscoveryConfig`] for details).
227    ///
228    /// Enabled by default.
229    pub fn mtu_discovery_config(&mut self, value: Option<MtuDiscoveryConfig>) -> &mut Self {
230        self.mtu_discovery_config = value;
231        self
232    }
233
234    /// Pad UDP datagrams carrying application data to current maximum UDP payload size
235    ///
236    /// Disabled by default. UDP datagrams containing loss probes are exempt from padding.
237    ///
238    /// Enabling this helps mitigate traffic analysis by network observers, but it increases
239    /// bandwidth usage. Without this mitigation precise plain text size of application datagrams as
240    /// well as the total size of stream write bursts can be inferred by observers under certain
241    /// conditions. This analysis requires either an uncongested connection or application datagrams
242    /// too large to be coalesced.
243    pub fn pad_to_mtu(&mut self, value: bool) -> &mut Self {
244        self.pad_to_mtu = value;
245        self
246    }
247
248    /// Specifies the ACK frequency config (see [`AckFrequencyConfig`] for details)
249    ///
250    /// The provided configuration will be ignored if the peer does not support the acknowledgement
251    /// frequency QUIC extension.
252    ///
253    /// Defaults to `None`, which disables controlling the peer's acknowledgement frequency. Even
254    /// if set to `None`, the local side still supports the acknowledgement frequency QUIC
255    /// extension and may use it in other ways.
256    pub fn ack_frequency_config(&mut self, value: Option<AckFrequencyConfig>) -> &mut Self {
257        self.ack_frequency_config = value;
258        self
259    }
260
261    /// Configures an outbound rate limit (in bytes per second) for each connection.
262    ///
263    /// Defaults to `None`, which disables rate limiting.
264    pub fn max_outgoing_bytes_per_second(&mut self, value: Option<u64>) -> &mut Self {
265        self.max_outgoing_bytes_per_second = value;
266        self
267    }
268
269    /// Number of consecutive PTOs after which network is considered to be experiencing persistent congestion.
270    pub fn persistent_congestion_threshold(&mut self, value: u32) -> &mut Self {
271        self.persistent_congestion_threshold = value;
272        self
273    }
274
275    /// Period of inactivity before sending a keep-alive packet
276    ///
277    /// Keep-alive packets prevent an inactive but otherwise healthy connection from timing out.
278    ///
279    /// `None` to disable, which is the default. Only one side of any given connection needs keep-alive
280    /// enabled for the connection to be preserved. Must be set lower than the idle_timeout of both
281    /// peers to be effective.
282    pub fn keep_alive_interval(&mut self, value: Option<Duration>) -> &mut Self {
283        self.keep_alive_interval = value;
284        self
285    }
286
287    /// Maximum quantity of out-of-order crypto layer data to buffer
288    pub fn crypto_buffer_size(&mut self, value: usize) -> &mut Self {
289        self.crypto_buffer_size = value;
290        self
291    }
292
293    /// Whether the implementation is permitted to set the spin bit on this connection
294    ///
295    /// This allows passive observers to easily judge the round trip time of a connection, which can
296    /// be useful for network administration but sacrifices a small amount of privacy.
297    pub fn allow_spin(&mut self, value: bool) -> &mut Self {
298        self.allow_spin = value;
299        self
300    }
301
302    /// Maximum number of incoming application datagram bytes to buffer, or None to disable
303    /// incoming datagrams
304    ///
305    /// The peer is forbidden to send single datagrams larger than this size. If the aggregate size
306    /// of all datagrams that have been received from the peer but not consumed by the application
307    /// exceeds this value, old datagrams are dropped until it is no longer exceeded.
308    pub fn datagram_receive_buffer_size(&mut self, value: Option<usize>) -> &mut Self {
309        self.datagram_receive_buffer_size = value;
310        self
311    }
312
313    /// Maximum number of outgoing application datagram bytes to buffer
314    ///
315    /// While datagrams are sent ASAP, it is possible for an application to generate data faster
316    /// than the link, or even the underlying hardware, can transmit them. This limits the amount of
317    /// memory that may be consumed in that case. When the send buffer is full and a new datagram is
318    /// sent, older datagrams are dropped until sufficient space is available.
319    pub fn datagram_send_buffer_size(&mut self, value: usize) -> &mut Self {
320        self.datagram_send_buffer_size = value;
321        self
322    }
323
324    /// Whether to force every packet number to be used
325    ///
326    /// By default, packet numbers are occasionally skipped to ensure peers aren't ACKing packets
327    /// before they see them.
328    #[cfg(test)]
329    pub(crate) fn deterministic_packet_numbers(&mut self, enabled: bool) -> &mut Self {
330        self.deterministic_packet_numbers = enabled;
331        self
332    }
333
334    /// How to construct new `congestion::Controller`s
335    ///
336    /// Typically the refcounted configuration of a `congestion::Controller`,
337    /// e.g. a `congestion::NewRenoConfig`.
338    ///
339    /// # Example
340    /// ```
341    /// # use noq_proto::*; use std::sync::Arc;
342    /// let mut config = TransportConfig::default();
343    /// config.congestion_controller_factory(Arc::new(congestion::NewRenoConfig::default()));
344    /// ```
345    pub fn congestion_controller_factory(
346        &mut self,
347        factory: Arc<dyn congestion::ControllerFactory + Send + Sync + 'static>,
348    ) -> &mut Self {
349        self.congestion_controller_factory = factory;
350        self
351    }
352
353    /// Whether to use "Generic Segmentation Offload" to accelerate transmits, when supported by the
354    /// environment
355    ///
356    /// Defaults to `true`.
357    ///
358    /// GSO dramatically reduces CPU consumption when sending large numbers of packets with the same
359    /// headers, such as when transmitting bulk data on a connection. However, it is not supported
360    /// by all network interface drivers or packet inspection tools. `noq-udp` will attempt to
361    /// disable GSO automatically when unavailable, but this can lead to spurious packet loss at
362    /// startup, temporarily degrading performance.
363    pub fn enable_segmentation_offload(&mut self, enabled: bool) -> &mut Self {
364        self.enable_segmentation_offload = enabled;
365        self
366    }
367
368    /// Whether to send observed address reports to peers.
369    ///
370    /// This will aid peers in inferring their reachable address, which in most NATd networks
371    /// will not be easily available to them.
372    pub fn send_observed_address_reports(&mut self, enabled: bool) -> &mut Self {
373        self.address_discovery_role.send_reports_to_peers(enabled);
374        self
375    }
376
377    /// Whether to receive observed address reports from other peers.
378    ///
379    /// Peers with the address discovery extension enabled that are willing to provide observed
380    /// address reports will do so if this transport parameter is set. In general, observed address
381    /// reports cannot be trusted. This, however, can aid the current endpoint in inferring its
382    /// reachable address, which in most NATd networks will not be easily available.
383    pub fn receive_observed_address_reports(&mut self, enabled: bool) -> &mut Self {
384        self.address_discovery_role
385            .receive_reports_from_peers(enabled);
386        self
387    }
388
389    /// Enables the Multipath Extension for QUIC.
390    ///
391    /// Setting this to any nonzero value will enable the Multipath Extension for QUIC,
392    /// <https://datatracker.ietf.org/doc/draft-ietf-quic-multipath/>.
393    ///
394    /// The value provided specifies the number maximum number of paths this endpoint may open
395    /// concurrently when multipath is negotiated. For any path to be opened, the remote must
396    /// enable multipath as well.
397    pub fn max_concurrent_multipath_paths(&mut self, max_concurrent: u32) -> &mut Self {
398        self.max_concurrent_multipath_paths = NonZeroU32::new(max_concurrent);
399        self
400    }
401
402    /// Sets a default per-path maximum idle timeout
403    ///
404    /// If the path is idle for this long the path will be abandoned. Bear in mind this will
405    /// interact with the [`TransportConfig::max_idle_timeout`], if the last path is
406    /// abandoned the entire connection will be closed.
407    ///
408    /// You can also change this using [`Connection::set_path_max_idle_timeout`] for
409    /// existing paths.
410    ///
411    /// [`Connection::set_path_max_idle_timeout`]: crate::Connection::set_path_max_idle_timeout
412    pub fn default_path_max_idle_timeout(&mut self, timeout: Option<Duration>) -> &mut Self {
413        self.default_path_max_idle_timeout = timeout;
414        self
415    }
416
417    /// Sets a default per-path keep alive interval
418    ///
419    /// Note that this does not interact with the connection-wide
420    /// [`TransportConfig::keep_alive_interval`].  This setting will keep this path active,
421    /// [`TransportConfig::keep_alive_interval`] will keep the connection active, with no
422    /// control over which path is used for this.
423    ///
424    /// You can also change this using [`Connection::set_path_keep_alive_interval`] for
425    /// existing path.
426    ///
427    /// [`Connection::set_path_keep_alive_interval`]: crate::Connection::set_path_keep_alive_interval
428    pub fn default_path_keep_alive_interval(&mut self, interval: Option<Duration>) -> &mut Self {
429        self.default_path_keep_alive_interval = interval;
430        self
431    }
432
433    /// Get the initial max [`crate::PathId`] this endpoint allows.
434    ///
435    /// Returns `None` if multipath is disabled.
436    pub(crate) fn get_initial_max_path_id(&self) -> Option<crate::PathId> {
437        self.max_concurrent_multipath_paths
438            // a max_concurrent_multipath_paths value of 1 only allows the first path, which
439            // has id 0
440            .map(|nonzero_concurrent| nonzero_concurrent.get() - 1)
441            .map(Into::into)
442    }
443
444    /// Sets the maximum number of nat traversal addresses this endpoint allows the remote to
445    /// advertise
446    ///
447    /// Setting this to any nonzero value will enable n0's nat traversal protocol, loosely based in
448    /// the Nat Traversal Extension for QUIC, see
449    /// <https://www.ietf.org/archive/id/draft-seemann-quic-nat-traversal-02.html>
450    ///
451    /// This implementation expects the multipath extension to be enabled as well. if not yet
452    /// enabled via [`Self::max_concurrent_multipath_paths`], then that setting is set to 8.
453    pub fn max_remote_nat_traversal_addresses(&mut self, max_addresses: u8) -> &mut Self {
454        self.max_remote_nat_traversal_addresses = NonZeroU8::new(max_addresses);
455        if max_addresses != 0 && self.max_concurrent_multipath_paths.is_none() {
456            self.max_concurrent_multipath_paths(8);
457        }
458        self
459    }
460
461    /// Sets whether the server is allowed to migrate once during the handshake.
462    ///
463    /// **Enabling this is not RFC9000 compliant.**
464    ///
465    /// Defaults to `false`.
466    ///
467    /// Enabling this allows the server to migrate once during the handshake: it can send a
468    /// response from a different address than the client's initial packet was sent to. Once
469    /// an authenticated Handshake packet is received the server can no longer migrate
470    /// during the handshake (or after the handshake if not other extension enables this).
471    ///
472    /// This can be used to duplicate the client's initial packet to multiple addresses for
473    /// the server and accept the fastest response. The server will discard all but the
474    /// first such initial, considering any remaining as duplicates.
475    pub fn server_handshake_migration(&mut self, allow_migration: bool) -> &mut Self {
476        self.server_handshake_migration = allow_migration;
477        self
478    }
479
480    /// Configures qlog capturing by setting a [`QlogFactory`].
481    ///
482    /// This assigns a [`QlogFactory`] that produces qlog capture configurations for
483    /// individual connections.
484    #[cfg(feature = "qlog")]
485    pub fn qlog_factory(&mut self, factory: Arc<dyn QlogFactory>) -> &mut Self {
486        self.qlog_factory = Some(factory);
487        self
488    }
489
490    /// Configures qlog capturing through the `QLOGDIR` environment variable.
491    ///
492    /// This uses [`QlogFileFactory::from_env`] to create a factory to write qlog traces
493    /// into the directory set through the `QLOGDIR` environment variable.
494    ///
495    /// If `QLOGDIR` is not set, no traces will be written. If `QLOGDIR` is set to a path
496    /// that does not exist, it will be created.
497    ///
498    /// The files will be prefixed with `prefix`.
499    #[cfg(feature = "qlog")]
500    pub fn qlog_from_env(&mut self, prefix: &str) -> &mut Self {
501        self.qlog_factory(Arc::new(QlogFileFactory::from_env().with_prefix(prefix)))
502    }
503
504    /// Configures qlog capturing into a directory.
505    ///
506    /// This uses [`QlogFileFactory`] to create a factory to write qlog traces into
507    /// the specified directory.  The files will be prefixed with `prefix`.
508    #[cfg(feature = "qlog")]
509    pub fn qlog_from_path(&mut self, path: impl AsRef<Path>, prefix: &str) -> &mut Self {
510        self.qlog_factory(Arc::new(
511            QlogFileFactory::new(path.as_ref().to_owned()).with_prefix(prefix),
512        ))
513    }
514
515    pub(crate) fn create_qlog_sink(
516        &self,
517        side: Side,
518        remote: SocketAddr,
519        initial_dst_cid: ConnectionId,
520        now: Instant,
521    ) -> QlogSink {
522        #[cfg(not(feature = "qlog"))]
523        let sink = {
524            let _ = (side, remote, initial_dst_cid, now);
525            QlogSink::default()
526        };
527
528        #[cfg(feature = "qlog")]
529        let sink = {
530            if let Some(config) = self
531                .qlog_factory
532                .as_ref()
533                .and_then(|factory| factory.for_connection(side, remote, initial_dst_cid, now))
534            {
535                QlogSink::new(config, initial_dst_cid, side, now)
536            } else {
537                QlogSink::default()
538            }
539        };
540
541        sink
542    }
543}
544
545impl Default for TransportConfig {
546    fn default() -> Self {
547        const EXPECTED_RTT: u32 = 100; // ms
548        const MAX_STREAM_BANDWIDTH: u32 = 12500 * 1000; // bytes/s
549        // Window size needed to avoid pipeline
550        // stalls
551        const STREAM_RWND: u32 = MAX_STREAM_BANDWIDTH / 1000 * EXPECTED_RTT;
552
553        Self {
554            max_concurrent_bidi_streams: 100u32.into(),
555            max_concurrent_uni_streams: 100u32.into(),
556            // 30 second default recommended by RFC 9308 ยง 3.2
557            max_idle_timeout: Some(VarInt(30_000)),
558            stream_receive_window: STREAM_RWND.into(),
559            receive_window: VarInt::MAX,
560            send_window: (8 * STREAM_RWND).into(),
561            send_fairness: true,
562
563            packet_threshold: 3,
564            time_threshold: 9.0 / 8.0,
565            initial_rtt: Duration::from_millis(333), // per spec, intentionally distinct from EXPECTED_RTT
566            initial_mtu: INITIAL_MTU,
567            min_mtu: INITIAL_MTU,
568            mtu_discovery_config: Some(MtuDiscoveryConfig::default()),
569            pad_to_mtu: false,
570            ack_frequency_config: None,
571            max_outgoing_bytes_per_second: None,
572
573            persistent_congestion_threshold: 3,
574            keep_alive_interval: None,
575            crypto_buffer_size: 16 * 1024,
576            allow_spin: true,
577            datagram_receive_buffer_size: Some(STREAM_RWND as usize),
578            datagram_send_buffer_size: 1024 * 1024,
579            #[cfg(test)]
580            deterministic_packet_numbers: false,
581
582            congestion_controller_factory: Arc::new(congestion::CubicConfig::default()),
583
584            enable_segmentation_offload: true,
585
586            address_discovery_role: address_discovery::Role::default(),
587
588            // disabled multipath by default
589            max_concurrent_multipath_paths: None,
590            default_path_max_idle_timeout: None,
591            default_path_keep_alive_interval: None,
592
593            // nat traversal disabled by default
594            max_remote_nat_traversal_addresses: None,
595            server_handshake_migration: false,
596
597            #[cfg(feature = "qlog")]
598            qlog_factory: None,
599        }
600    }
601}
602
603impl fmt::Debug for TransportConfig {
604    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
605        let Self {
606            max_concurrent_bidi_streams,
607            max_concurrent_uni_streams,
608            max_idle_timeout,
609            stream_receive_window,
610            receive_window,
611            send_window,
612            send_fairness,
613            packet_threshold,
614            time_threshold,
615            initial_rtt,
616            initial_mtu,
617            min_mtu,
618            mtu_discovery_config,
619            pad_to_mtu,
620            ack_frequency_config,
621            max_outgoing_bytes_per_second,
622            persistent_congestion_threshold,
623            keep_alive_interval,
624            crypto_buffer_size,
625            allow_spin,
626            datagram_receive_buffer_size,
627            datagram_send_buffer_size,
628            #[cfg(test)]
629                deterministic_packet_numbers: _,
630            congestion_controller_factory: _,
631            enable_segmentation_offload,
632            address_discovery_role,
633            max_concurrent_multipath_paths,
634            default_path_max_idle_timeout,
635            default_path_keep_alive_interval,
636            max_remote_nat_traversal_addresses,
637            server_handshake_migration,
638            #[cfg(feature = "qlog")]
639            qlog_factory,
640        } = self;
641        let mut s = fmt.debug_struct("TransportConfig");
642
643        s.field("max_concurrent_bidi_streams", max_concurrent_bidi_streams)
644            .field("max_concurrent_uni_streams", max_concurrent_uni_streams)
645            .field("max_idle_timeout", max_idle_timeout)
646            .field("stream_receive_window", stream_receive_window)
647            .field("receive_window", receive_window)
648            .field("send_window", send_window)
649            .field("send_fairness", send_fairness)
650            .field("packet_threshold", packet_threshold)
651            .field("time_threshold", time_threshold)
652            .field("initial_rtt", initial_rtt)
653            .field("initial_mtu", initial_mtu)
654            .field("min_mtu", min_mtu)
655            .field("mtu_discovery_config", mtu_discovery_config)
656            .field("pad_to_mtu", pad_to_mtu)
657            .field("ack_frequency_config", ack_frequency_config)
658            .field(
659                "max_outgoing_bytes_per_second",
660                max_outgoing_bytes_per_second,
661            )
662            .field(
663                "persistent_congestion_threshold",
664                persistent_congestion_threshold,
665            )
666            .field("keep_alive_interval", keep_alive_interval)
667            .field("crypto_buffer_size", crypto_buffer_size)
668            .field("allow_spin", allow_spin)
669            .field("datagram_receive_buffer_size", datagram_receive_buffer_size)
670            .field("datagram_send_buffer_size", datagram_send_buffer_size)
671            // congestion_controller_factory not debug
672            .field("enable_segmentation_offload", enable_segmentation_offload)
673            .field("address_discovery_role", address_discovery_role)
674            .field(
675                "max_concurrent_multipath_paths",
676                max_concurrent_multipath_paths,
677            )
678            .field(
679                "default_path_max_idle_timeout",
680                default_path_max_idle_timeout,
681            )
682            .field(
683                "default_path_keep_alive_interval",
684                default_path_keep_alive_interval,
685            )
686            .field(
687                "max_remote_nat_traversal_addresses",
688                max_remote_nat_traversal_addresses,
689            )
690            .field("server_handshake_migration", server_handshake_migration);
691        #[cfg(feature = "qlog")]
692        s.field("qlog_factory", &qlog_factory.is_some());
693
694        s.finish_non_exhaustive()
695    }
696}
697
698/// Parameters for controlling the peer's acknowledgement frequency
699///
700/// The parameters provided in this config will be sent to the peer at the beginning of the
701/// connection, so it can take them into account when sending acknowledgements (see each parameter's
702/// description for details on how it influences acknowledgement frequency).
703///
704/// noq's implementation follows the fourth draft of the
705/// [QUIC Acknowledgement Frequency extension](https://datatracker.ietf.org/doc/html/draft-ietf-quic-ack-frequency-04).
706/// The defaults produce behavior slightly different than the behavior without this extension,
707/// because they change the way reordered packets are handled (see
708/// [`AckFrequencyConfig::reordering_threshold`] for details).
709#[derive(Clone, Debug)]
710pub struct AckFrequencyConfig {
711    pub(crate) ack_eliciting_threshold: VarInt,
712    pub(crate) max_ack_delay: Option<Duration>,
713    pub(crate) reordering_threshold: VarInt,
714}
715
716impl AckFrequencyConfig {
717    /// The ack-eliciting threshold we will request the peer to use
718    ///
719    /// This threshold represents the number of ack-eliciting packets an endpoint may receive
720    /// without immediately sending an ACK.
721    ///
722    /// The remote peer should send at least one ACK frame when more than this number of
723    /// ack-eliciting packets have been received. A value of 0 results in a receiver immediately
724    /// acknowledging every ack-eliciting packet.
725    ///
726    /// Defaults to 1, which sends ACK frames for every other ack-eliciting packet.
727    pub fn ack_eliciting_threshold(&mut self, value: VarInt) -> &mut Self {
728        self.ack_eliciting_threshold = value;
729        self
730    }
731
732    /// The `max_ack_delay` we will request the peer to use
733    ///
734    /// This parameter represents the maximum amount of time that an endpoint waits before sending
735    /// an ACK when the ack-eliciting threshold hasn't been reached.
736    ///
737    /// The effective `max_ack_delay` will be clamped to be at least the peer's `min_ack_delay`
738    /// transport parameter, and at most the greater of the current path RTT or 25ms.
739    ///
740    /// Defaults to `None`, in which case the peer's original `max_ack_delay` will be used, as
741    /// obtained from its transport parameters.
742    pub fn max_ack_delay(&mut self, value: Option<Duration>) -> &mut Self {
743        self.max_ack_delay = value;
744        self
745    }
746
747    /// The reordering threshold we will request the peer to use
748    ///
749    /// This threshold represents the amount of out-of-order packets that will trigger an endpoint
750    /// to send an ACK, without waiting for `ack_eliciting_threshold` to be exceeded or for
751    /// `max_ack_delay` to be elapsed.
752    ///
753    /// A value of 0 indicates out-of-order packets do not elicit an immediate ACK. A value of 1
754    /// immediately acknowledges any packets that are received out of order (this is also the
755    /// behavior when the extension is disabled).
756    ///
757    /// It is recommended to set this value to [`TransportConfig::packet_threshold`] minus one.
758    /// Since the default value for [`TransportConfig::packet_threshold`] is 3, this value defaults
759    /// to 2.
760    pub fn reordering_threshold(&mut self, value: VarInt) -> &mut Self {
761        self.reordering_threshold = value;
762        self
763    }
764}
765
766impl Default for AckFrequencyConfig {
767    fn default() -> Self {
768        Self {
769            ack_eliciting_threshold: VarInt(1),
770            max_ack_delay: None,
771            reordering_threshold: VarInt(2),
772        }
773    }
774}
775
776/// Parameters governing MTU discovery.
777///
778/// # The why of MTU discovery
779///
780/// By design, QUIC ensures during the handshake that the network path between the client and the
781/// server is able to transmit unfragmented UDP packets with a body of 1200 bytes. In other words,
782/// once the connection is established, we know that the network path's maximum transmission unit
783/// (MTU) is of at least 1200 bytes (plus IP and UDP headers). Because of this, a QUIC endpoint can
784/// split outgoing data in packets of 1200 bytes, with confidence that the network will be able to
785/// deliver them (if the endpoint were to send bigger packets, they could prove too big and end up
786/// being dropped).
787///
788/// There is, however, a significant overhead associated to sending a packet. If the same
789/// information can be sent in fewer packets, that results in higher throughput. The amount of
790/// packets that need to be sent is inversely proportional to the MTU: the higher the MTU, the
791/// bigger the packets that can be sent, and the fewer packets that are needed to transmit a given
792/// amount of bytes.
793///
794/// Most networks have an MTU higher than 1200. Through MTU discovery, endpoints can detect the
795/// path's MTU and, if it turns out to be higher, start sending bigger packets.
796///
797/// # MTU discovery internals
798///
799/// noq implements MTU discovery through DPLPMTUD (Datagram Packetization Layer Path MTU
800/// Discovery), described in [section 14.3 of RFC
801/// 9000](https://www.rfc-editor.org/rfc/rfc9000.html#section-14.3). This method consists of sending
802/// QUIC packets padded to a particular size (called PMTU probes), and waiting to see if the remote
803/// peer responds with an ACK. If an ACK is received, that means the probe arrived at the remote
804/// peer, which in turn means that the network path's MTU is of at least the packet's size. If the
805/// probe is lost, it is sent another 2 times before concluding that the MTU is lower than the
806/// packet's size.
807///
808/// MTU discovery runs on a schedule (e.g. every 600 seconds) specified through
809/// [`MtuDiscoveryConfig::interval`]. The first run happens right after the handshake, and
810/// subsequent discoveries are scheduled to run when the interval has elapsed, starting from the
811/// last time when MTU discovery completed.
812///
813/// Since the search space for MTUs is quite big (the smallest possible MTU is 1200, and the highest
814/// is 65527), noq performs a binary search to keep the number of probes as low as possible. The
815/// lower bound of the search is equal to [`TransportConfig::initial_mtu`] in the
816/// initial MTU discovery run, and is equal to the currently discovered MTU in subsequent runs. The
817/// upper bound is determined by the minimum of [`MtuDiscoveryConfig::upper_bound`] and the
818/// `max_udp_payload_size` transport parameter received from the peer during the handshake.
819///
820/// # Black hole detection
821///
822/// If, at some point, the network path no longer accepts packets of the detected size, packet loss
823/// will eventually trigger black hole detection and reset the detected MTU to 1200. In that case,
824/// MTU discovery will be triggered after [`MtuDiscoveryConfig::black_hole_cooldown`] (ignoring the
825/// timer that was set based on [`MtuDiscoveryConfig::interval`]).
826///
827/// # Interaction between peers
828///
829/// There is no guarantee that the MTU on the path between A and B is the same as the MTU of the
830/// path between B and A. Therefore, each peer in the connection needs to run MTU discovery
831/// independently in order to discover the path's MTU.
832#[derive(Clone, Debug)]
833pub struct MtuDiscoveryConfig {
834    pub(crate) interval: Duration,
835    pub(crate) upper_bound: u16,
836    pub(crate) minimum_change: u16,
837    pub(crate) black_hole_cooldown: Duration,
838}
839
840impl MtuDiscoveryConfig {
841    /// Specifies the time to wait after completing MTU discovery before starting a new MTU
842    /// discovery run.
843    ///
844    /// Defaults to 600 seconds, as recommended by [RFC
845    /// 8899](https://www.rfc-editor.org/rfc/rfc8899).
846    pub fn interval(&mut self, value: Duration) -> &mut Self {
847        self.interval = value;
848        self
849    }
850
851    /// Specifies the upper bound to the max UDP payload size that MTU discovery will search for.
852    ///
853    /// Defaults to 1452, to stay within Ethernet's MTU when using IPv4 and IPv6. The highest
854    /// allowed value is 65527, which corresponds to the maximum permitted UDP payload on IPv6.
855    ///
856    /// It is safe to use an arbitrarily high upper bound, regardless of the network path's MTU. The
857    /// only drawback is that MTU discovery might take more time to finish.
858    pub fn upper_bound(&mut self, value: u16) -> &mut Self {
859        self.upper_bound = value.min(MAX_UDP_PAYLOAD);
860        self
861    }
862
863    /// Specifies the amount of time that MTU discovery should wait after a black hole was detected
864    /// before running again. Defaults to one minute.
865    ///
866    /// Black hole detection can be spuriously triggered in case of congestion, so it makes sense to
867    /// try MTU discovery again after a short period of time.
868    pub fn black_hole_cooldown(&mut self, value: Duration) -> &mut Self {
869        self.black_hole_cooldown = value;
870        self
871    }
872
873    /// Specifies the minimum MTU change to stop the MTU discovery phase.
874    /// Defaults to 20.
875    pub fn minimum_change(&mut self, value: u16) -> &mut Self {
876        self.minimum_change = value;
877        self
878    }
879}
880
881impl Default for MtuDiscoveryConfig {
882    fn default() -> Self {
883        Self {
884            interval: Duration::from_secs(600),
885            upper_bound: 1452,
886            black_hole_cooldown: Duration::from_secs(60),
887            minimum_change: 20,
888        }
889    }
890}
891
892/// Maximum duration of inactivity to accept before timing out the connection
893///
894/// This wraps an underlying [`VarInt`], representing the duration in milliseconds. Values can be
895/// constructed by converting directly from `VarInt`, or using `TryFrom<Duration>`.
896///
897/// ```
898/// # use std::{convert::TryFrom, time::Duration};
899/// # use noq_proto::{IdleTimeout, VarIntBoundsExceeded, VarInt};
900/// # fn main() -> Result<(), VarIntBoundsExceeded> {
901/// // A `VarInt`-encoded value in milliseconds
902/// let timeout = IdleTimeout::from(VarInt::from_u32(10_000));
903///
904/// // Try to convert a `Duration` into a `VarInt`-encoded timeout
905/// let timeout = IdleTimeout::try_from(Duration::from_secs(10))?;
906/// # Ok(())
907/// # }
908/// ```
909#[derive(Default, Copy, Clone, Eq, Hash, Ord, PartialEq, PartialOrd)]
910pub struct IdleTimeout(VarInt);
911
912impl From<VarInt> for IdleTimeout {
913    fn from(inner: VarInt) -> Self {
914        Self(inner)
915    }
916}
917
918impl std::convert::TryFrom<Duration> for IdleTimeout {
919    type Error = VarIntBoundsExceeded;
920
921    fn try_from(timeout: Duration) -> Result<Self, Self::Error> {
922        let inner = VarInt::try_from(timeout.as_millis())?;
923        Ok(Self(inner))
924    }
925}
926
927impl fmt::Debug for IdleTimeout {
928    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
929        self.0.fmt(f)
930    }
931}