iroh_quinn_proto/config/mod.rs
1use std::{
2    fmt,
3    net::{SocketAddrV4, SocketAddrV6},
4    num::TryFromIntError,
5    sync::Arc,
6};
7
8#[cfg(any(feature = "rustls-aws-lc-rs", feature = "rustls-ring"))]
9use rustls::client::WebPkiServerVerifier;
10#[cfg(any(feature = "rustls-aws-lc-rs", feature = "rustls-ring"))]
11use rustls::pki_types::{CertificateDer, PrivateKeyDer};
12use thiserror::Error;
13
14#[cfg(any(feature = "rustls-aws-lc-rs", feature = "rustls-ring"))]
15use crate::crypto::rustls::{configured_provider, QuicServerConfig};
16use crate::{
17    cid_generator::{ConnectionIdGenerator, HashedConnectionIdGenerator},
18    crypto::{self, HandshakeTokenKey, HmacKey},
19    shared::ConnectionId,
20    Duration, NoneTokenLog, NoneTokenStore, RandomConnectionIdGenerator, SystemTime, TokenLog,
21    TokenStore, VarInt, VarIntBoundsExceeded, DEFAULT_SUPPORTED_VERSIONS, MAX_CID_SIZE,
22};
23
24mod transport;
25pub use transport::{AckFrequencyConfig, IdleTimeout, MtuDiscoveryConfig, TransportConfig};
26
27/// Global configuration for the endpoint, affecting all connections
28///
29/// Default values should be suitable for most internet applications.
30#[derive(Clone)]
31pub struct EndpointConfig {
32    pub(crate) reset_key: Arc<dyn HmacKey>,
33    pub(crate) max_udp_payload_size: VarInt,
34    /// CID generator factory
35    ///
36    /// Create a cid generator for local cid in Endpoint struct
37    pub(crate) connection_id_generator_factory:
38        Arc<dyn Fn() -> Box<dyn ConnectionIdGenerator> + Send + Sync>,
39    pub(crate) supported_versions: Vec<u32>,
40    pub(crate) grease_quic_bit: bool,
41    /// Minimum interval between outgoing stateless reset packets
42    pub(crate) min_reset_interval: Duration,
43    /// Optional seed to be used internally for random number generation
44    pub(crate) rng_seed: Option<[u8; 32]>,
45}
46
47impl EndpointConfig {
48    /// Create a default config with a particular `reset_key`
49    pub fn new(reset_key: Arc<dyn HmacKey>) -> Self {
50        let cid_factory =
51            || -> Box<dyn ConnectionIdGenerator> { Box::<HashedConnectionIdGenerator>::default() };
52        Self {
53            reset_key,
54            max_udp_payload_size: (1500u32 - 28).into(), // Ethernet MTU minus IP + UDP headers
55            connection_id_generator_factory: Arc::new(cid_factory),
56            supported_versions: DEFAULT_SUPPORTED_VERSIONS.to_vec(),
57            grease_quic_bit: true,
58            min_reset_interval: Duration::from_millis(20),
59            rng_seed: None,
60        }
61    }
62
63    /// Supply a custom connection ID generator factory
64    ///
65    /// Called once by each `Endpoint` constructed from this configuration to obtain the CID
66    /// generator which will be used to generate the CIDs used for incoming packets on all
67    /// connections involving that  `Endpoint`. A custom CID generator allows applications to embed
68    /// information in local connection IDs, e.g. to support stateless packet-level load balancers.
69    ///
70    /// Defaults to [`HashedConnectionIdGenerator`].
71    pub fn cid_generator<F: Fn() -> Box<dyn ConnectionIdGenerator> + Send + Sync + 'static>(
72        &mut self,
73        factory: F,
74    ) -> &mut Self {
75        self.connection_id_generator_factory = Arc::new(factory);
76        self
77    }
78
79    /// Private key used to send authenticated connection resets to peers who were
80    /// communicating with a previous instance of this endpoint.
81    pub fn reset_key(&mut self, key: Arc<dyn HmacKey>) -> &mut Self {
82        self.reset_key = key;
83        self
84    }
85
86    /// Maximum UDP payload size accepted from peers (excluding UDP and IP overhead).
87    ///
88    /// Must be greater or equal than 1200.
89    ///
90    /// Defaults to 1472, which is the largest UDP payload that can be transmitted in the typical
91    /// 1500 byte Ethernet MTU. Deployments on links with larger MTUs (e.g. loopback or Ethernet
92    /// with jumbo frames) can raise this to improve performance at the cost of a linear increase in
93    /// datagram receive buffer size.
94    pub fn max_udp_payload_size(&mut self, value: u16) -> Result<&mut Self, ConfigError> {
95        if !(1200..=65_527).contains(&value) {
96            return Err(ConfigError::OutOfBounds);
97        }
98
99        self.max_udp_payload_size = value.into();
100        Ok(self)
101    }
102
103    /// Get the current value of [`max_udp_payload_size`](Self::max_udp_payload_size)
104    //
105    // While most parameters don't need to be readable, this must be exposed to allow higher-level
106    // layers, e.g. the `quinn` crate, to determine how large a receive buffer to allocate to
107    // support an externally-defined `EndpointConfig`.
108    //
109    // While `get_` accessors are typically unidiomatic in Rust, we favor concision for setters,
110    // which will be used far more heavily.
111    pub fn get_max_udp_payload_size(&self) -> u64 {
112        self.max_udp_payload_size.into()
113    }
114
115    /// Override supported QUIC versions
116    pub fn supported_versions(&mut self, supported_versions: Vec<u32>) -> &mut Self {
117        self.supported_versions = supported_versions;
118        self
119    }
120
121    /// Whether to accept QUIC packets containing any value for the fixed bit
122    ///
123    /// Enabled by default. Helps protect against protocol ossification and makes traffic less
124    /// identifiable to observers. Disable if helping observers identify this traffic as QUIC is
125    /// desired.
126    pub fn grease_quic_bit(&mut self, value: bool) -> &mut Self {
127        self.grease_quic_bit = value;
128        self
129    }
130
131    /// Minimum interval between outgoing stateless reset packets
132    ///
133    /// Defaults to 20ms. Limits the impact of attacks which flood an endpoint with garbage packets,
134    /// e.g. [ISAKMP/IKE amplification]. Larger values provide a stronger defense, but may delay
135    /// detection of some error conditions by clients. Using a [`ConnectionIdGenerator`] with a low
136    /// rate of false positives in [`validate`](ConnectionIdGenerator::validate) reduces the risk
137    /// incurred by a small minimum reset interval.
138    ///
139    /// [ISAKMP/IKE
140    /// amplification]: https://bughunters.google.com/blog/5960150648750080/preventing-cross-service-udp-loops-in-quic#isakmp-ike-amplification-vs-quic
141    pub fn min_reset_interval(&mut self, value: Duration) -> &mut Self {
142        self.min_reset_interval = value;
143        self
144    }
145
146    /// Optional seed to be used internally for random number generation
147    ///
148    /// By default, quinn will initialize an endpoint's rng using a platform entropy source.
149    /// However, you can seed the rng yourself through this method (e.g. if you need to run quinn
150    /// deterministically or if you are using quinn in an environment that doesn't have a source of
151    /// entropy available).
152    pub fn rng_seed(&mut self, seed: Option<[u8; 32]>) -> &mut Self {
153        self.rng_seed = seed;
154        self
155    }
156}
157
158impl fmt::Debug for EndpointConfig {
159    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
160        fmt.debug_struct("EndpointConfig")
161            // reset_key not debug
162            .field("max_udp_payload_size", &self.max_udp_payload_size)
163            // cid_generator_factory not debug
164            .field("supported_versions", &self.supported_versions)
165            .field("grease_quic_bit", &self.grease_quic_bit)
166            .field("rng_seed", &self.rng_seed)
167            .finish_non_exhaustive()
168    }
169}
170
171#[cfg(any(feature = "aws-lc-rs", feature = "ring"))]
172impl Default for EndpointConfig {
173    fn default() -> Self {
174        #[cfg(all(feature = "aws-lc-rs", not(feature = "ring")))]
175        use aws_lc_rs::hmac;
176        use rand::RngCore;
177        #[cfg(feature = "ring")]
178        use ring::hmac;
179
180        let mut reset_key = [0; 64];
181        rand::thread_rng().fill_bytes(&mut reset_key);
182
183        Self::new(Arc::new(hmac::Key::new(hmac::HMAC_SHA256, &reset_key)))
184    }
185}
186
187/// Parameters governing incoming connections
188///
189/// Default values should be suitable for most internet applications.
190#[derive(Clone)]
191pub struct ServerConfig {
192    /// Transport configuration to use for incoming connections
193    pub transport: Arc<TransportConfig>,
194
195    /// TLS configuration used for incoming connections
196    ///
197    /// Must be set to use TLS 1.3 only.
198    pub crypto: Arc<dyn crypto::ServerConfig>,
199
200    /// Configuration for sending and handling validation tokens
201    pub validation_token: ValidationTokenConfig,
202
203    /// Used to generate one-time AEAD keys to protect handshake tokens
204    pub(crate) token_key: Arc<dyn HandshakeTokenKey>,
205
206    /// Duration after a retry token was issued for which it's considered valid
207    pub(crate) retry_token_lifetime: Duration,
208
209    /// Whether to allow clients to migrate to new addresses
210    ///
211    /// Improves behavior for clients that move between different internet connections or suffer NAT
212    /// rebinding. Enabled by default.
213    pub(crate) migration: bool,
214
215    pub(crate) preferred_address_v4: Option<SocketAddrV4>,
216    pub(crate) preferred_address_v6: Option<SocketAddrV6>,
217
218    pub(crate) max_incoming: usize,
219    pub(crate) incoming_buffer_size: u64,
220    pub(crate) incoming_buffer_size_total: u64,
221
222    pub(crate) time_source: Arc<dyn TimeSource>,
223}
224
225impl ServerConfig {
226    /// Create a default config with a particular handshake token key
227    pub fn new(
228        crypto: Arc<dyn crypto::ServerConfig>,
229        token_key: Arc<dyn HandshakeTokenKey>,
230    ) -> Self {
231        Self {
232            transport: Arc::new(TransportConfig::default()),
233            crypto,
234
235            token_key,
236            retry_token_lifetime: Duration::from_secs(15),
237
238            migration: true,
239
240            validation_token: ValidationTokenConfig::default(),
241
242            preferred_address_v4: None,
243            preferred_address_v6: None,
244
245            max_incoming: 1 << 16,
246            incoming_buffer_size: 10 << 20,
247            incoming_buffer_size_total: 100 << 20,
248
249            time_source: Arc::new(StdSystemTime),
250        }
251    }
252
253    /// Set a custom [`TransportConfig`]
254    pub fn transport_config(&mut self, transport: Arc<TransportConfig>) -> &mut Self {
255        self.transport = transport;
256        self
257    }
258
259    /// Set a custom [`ValidationTokenConfig`]
260    pub fn validation_token_config(
261        &mut self,
262        validation_token: ValidationTokenConfig,
263    ) -> &mut Self {
264        self.validation_token = validation_token;
265        self
266    }
267
268    /// Private key used to authenticate data included in handshake tokens
269    pub fn token_key(&mut self, value: Arc<dyn HandshakeTokenKey>) -> &mut Self {
270        self.token_key = value;
271        self
272    }
273
274    /// Duration after a retry token was issued for which it's considered valid
275    ///
276    /// Defaults to 15 seconds.
277    pub fn retry_token_lifetime(&mut self, value: Duration) -> &mut Self {
278        self.retry_token_lifetime = value;
279        self
280    }
281
282    /// Whether to allow clients to migrate to new addresses
283    ///
284    /// Improves behavior for clients that move between different internet connections or suffer NAT
285    /// rebinding. Enabled by default.
286    pub fn migration(&mut self, value: bool) -> &mut Self {
287        self.migration = value;
288        self
289    }
290
291    /// The preferred IPv4 address that will be communicated to clients during handshaking
292    ///
293    /// If the client is able to reach this address, it will switch to it.
294    pub fn preferred_address_v4(&mut self, address: Option<SocketAddrV4>) -> &mut Self {
295        self.preferred_address_v4 = address;
296        self
297    }
298
299    /// The preferred IPv6 address that will be communicated to clients during handshaking
300    ///
301    /// If the client is able to reach this address, it will switch to it.
302    pub fn preferred_address_v6(&mut self, address: Option<SocketAddrV6>) -> &mut Self {
303        self.preferred_address_v6 = address;
304        self
305    }
306
307    /// Maximum number of [`Incoming`][crate::Incoming] to allow to exist at a time
308    ///
309    /// An [`Incoming`][crate::Incoming] comes into existence when an incoming connection attempt
310    /// is received and stops existing when the application either accepts it or otherwise disposes
311    /// of it. While this limit is reached, new incoming connection attempts are immediately
312    /// refused. Larger values have greater worst-case memory consumption, but accommodate greater
313    /// application latency in handling incoming connection attempts.
314    ///
315    /// The default value is set to 65536. With a typical Ethernet MTU of 1500 bytes, this limits
316    /// memory consumption from this to under 100 MiB--a generous amount that still prevents memory
317    /// exhaustion in most contexts.
318    pub fn max_incoming(&mut self, max_incoming: usize) -> &mut Self {
319        self.max_incoming = max_incoming;
320        self
321    }
322
323    /// Maximum number of received bytes to buffer for each [`Incoming`][crate::Incoming]
324    ///
325    /// An [`Incoming`][crate::Incoming] comes into existence when an incoming connection attempt
326    /// is received and stops existing when the application either accepts it or otherwise disposes
327    /// of it. This limit governs only packets received within that period, and does not include
328    /// the first packet. Packets received in excess of this limit are dropped, which may cause
329    /// 0-RTT or handshake data to have to be retransmitted.
330    ///
331    /// The default value is set to 10 MiB--an amount such that in most situations a client would
332    /// not transmit that much 0-RTT data faster than the server handles the corresponding
333    /// [`Incoming`][crate::Incoming].
334    pub fn incoming_buffer_size(&mut self, incoming_buffer_size: u64) -> &mut Self {
335        self.incoming_buffer_size = incoming_buffer_size;
336        self
337    }
338
339    /// Maximum number of received bytes to buffer for all [`Incoming`][crate::Incoming]
340    /// collectively
341    ///
342    /// An [`Incoming`][crate::Incoming] comes into existence when an incoming connection attempt
343    /// is received and stops existing when the application either accepts it or otherwise disposes
344    /// of it. This limit governs only packets received within that period, and does not include
345    /// the first packet. Packets received in excess of this limit are dropped, which may cause
346    /// 0-RTT or handshake data to have to be retransmitted.
347    ///
348    /// The default value is set to 100 MiB--a generous amount that still prevents memory
349    /// exhaustion in most contexts.
350    pub fn incoming_buffer_size_total(&mut self, incoming_buffer_size_total: u64) -> &mut Self {
351        self.incoming_buffer_size_total = incoming_buffer_size_total;
352        self
353    }
354
355    /// Object to get current [`SystemTime`]
356    ///
357    /// This exists to allow system time to be mocked in tests, or wherever else desired.
358    ///
359    /// Defaults to [`StdSystemTime`], which simply calls [`SystemTime::now()`](SystemTime::now).
360    pub fn time_source(&mut self, time_source: Arc<dyn TimeSource>) -> &mut Self {
361        self.time_source = time_source;
362        self
363    }
364}
365
366#[cfg(any(feature = "rustls-aws-lc-rs", feature = "rustls-ring"))]
367impl ServerConfig {
368    /// Create a server config with the given certificate chain to be presented to clients
369    ///
370    /// Uses a randomized handshake token key.
371    pub fn with_single_cert(
372        cert_chain: Vec<CertificateDer<'static>>,
373        key: PrivateKeyDer<'static>,
374    ) -> Result<Self, rustls::Error> {
375        Ok(Self::with_crypto(Arc::new(QuicServerConfig::new(
376            cert_chain, key,
377        )?)))
378    }
379}
380
381#[cfg(any(feature = "aws-lc-rs", feature = "ring"))]
382impl ServerConfig {
383    /// Create a server config with the given [`crypto::ServerConfig`]
384    ///
385    /// Uses a randomized handshake token key.
386    pub fn with_crypto(crypto: Arc<dyn crypto::ServerConfig>) -> Self {
387        #[cfg(all(feature = "aws-lc-rs", not(feature = "ring")))]
388        use aws_lc_rs::hkdf;
389        use rand::RngCore;
390        #[cfg(feature = "ring")]
391        use ring::hkdf;
392
393        let rng = &mut rand::thread_rng();
394        let mut master_key = [0u8; 64];
395        rng.fill_bytes(&mut master_key);
396        let master_key = hkdf::Salt::new(hkdf::HKDF_SHA256, &[]).extract(&master_key);
397
398        Self::new(crypto, Arc::new(master_key))
399    }
400}
401
402impl fmt::Debug for ServerConfig {
403    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
404        fmt.debug_struct("ServerConfig")
405            .field("transport", &self.transport)
406            // crypto not debug
407            // token not debug
408            .field("retry_token_lifetime", &self.retry_token_lifetime)
409            .field("validation_token", &self.validation_token)
410            .field("migration", &self.migration)
411            .field("preferred_address_v4", &self.preferred_address_v4)
412            .field("preferred_address_v6", &self.preferred_address_v6)
413            .field("max_incoming", &self.max_incoming)
414            .field("incoming_buffer_size", &self.incoming_buffer_size)
415            .field(
416                "incoming_buffer_size_total",
417                &self.incoming_buffer_size_total,
418            )
419            // system_time_clock not debug
420            .finish_non_exhaustive()
421    }
422}
423
424/// Configuration for sending and handling validation tokens in incoming connections
425///
426/// Default values should be suitable for most internet applications.
427///
428/// ## QUIC Tokens
429///
430/// The QUIC protocol defines a concept of "[address validation][1]". Essentially, one side of a
431/// QUIC connection may appear to be receiving QUIC packets from a particular remote UDP address,
432/// but it will only consider that remote address "validated" once it has convincing evidence that
433/// the address is not being [spoofed][2].
434///
435/// Validation is important primarily because of QUIC's "anti-amplification limit." This limit
436/// prevents a QUIC server from sending a client more than three times the number of bytes it has
437/// received from the client on a given address until that address is validated. This is designed
438/// to mitigate the ability of attackers to use QUIC-based servers as reflectors in [amplification
439/// attacks][3].
440///
441/// A path may become validated in several ways. The server is always considered validated by the
442/// client. The client usually begins in an unvalidated state upon first connecting or migrating,
443/// but then becomes validated through various mechanisms that usually take one network round trip.
444/// However, in some cases, a client which has previously attempted to connect to a server may have
445/// been given a one-time use cryptographically secured "token" that it can send in a subsequent
446/// connection attempt to be validated immediately.
447///
448/// There are two ways these tokens can originate:
449///
450/// - If the server responds to an incoming connection with `retry`, a "retry token" is minted and
451///   sent to the client, which the client immediately uses to attempt to connect again. Retry
452///   tokens operate on short timescales, such as 15 seconds.
453/// - If a client's path within an active connection is validated, the server may send the client
454///   one or more "validation tokens," which the client may store for use in later connections to
455///   the same server. Validation tokens may be valid for much longer lifetimes than retry token.
456///
457/// The usage of validation tokens is most impactful in situations where 0-RTT data is also being
458/// used--in particular, in situations where the server sends the client more than three times more
459/// 0.5-RTT data than it has received 0-RTT data. Since the successful completion of a connection
460/// handshake implicitly causes the client's address to be validated, transmission of 0.5-RTT data
461/// is the main situation where a server might be sending application data to an address that could
462/// be validated by token usage earlier than it would become validated without token usage.
463///
464/// [1]: https://www.rfc-editor.org/rfc/rfc9000.html#section-8
465/// [2]: https://en.wikipedia.org/wiki/IP_address_spoofing
466/// [3]: https://en.wikipedia.org/wiki/Denial-of-service_attack#Amplification
467///
468/// These tokens should not be confused with "stateless reset tokens," which are similarly named
469/// but entirely unrelated.
470#[derive(Clone)]
471pub struct ValidationTokenConfig {
472    pub(crate) lifetime: Duration,
473    pub(crate) log: Arc<dyn TokenLog>,
474    pub(crate) sent: u32,
475}
476
477impl ValidationTokenConfig {
478    /// Duration after an address validation token was issued for which it's considered valid
479    ///
480    /// This refers only to tokens sent in NEW_TOKEN frames, in contrast to retry tokens.
481    ///
482    /// Defaults to 2 weeks.
483    pub fn lifetime(&mut self, value: Duration) -> &mut Self {
484        self.lifetime = value;
485        self
486    }
487
488    /// Set a custom [`TokenLog`]
489    ///
490    /// Defaults to [`NoneTokenLog`], which makes the server ignore all address validation tokens
491    /// (that is, tokens originating from NEW_TOKEN frames--retry tokens are not affected).
492    pub fn log(&mut self, log: Arc<dyn TokenLog>) -> &mut Self {
493        self.log = log;
494        self
495    }
496
497    /// Number of address validation tokens sent to a client when its path is validated
498    ///
499    /// This refers only to tokens sent in NEW_TOKEN frames, in contrast to retry tokens.
500    ///
501    /// Defaults to 0.
502    pub fn sent(&mut self, value: u32) -> &mut Self {
503        self.sent = value;
504        self
505    }
506}
507
508impl Default for ValidationTokenConfig {
509    fn default() -> Self {
510        Self {
511            lifetime: Duration::from_secs(2 * 7 * 24 * 60 * 60),
512            log: Arc::new(NoneTokenLog),
513            sent: 0,
514        }
515    }
516}
517
518impl fmt::Debug for ValidationTokenConfig {
519    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
520        fmt.debug_struct("ServerValidationTokenConfig")
521            .field("lifetime", &self.lifetime)
522            // log not debug
523            .field("sent", &self.sent)
524            .finish_non_exhaustive()
525    }
526}
527
528/// Configuration for outgoing connections
529///
530/// Default values should be suitable for most internet applications.
531#[derive(Clone)]
532#[non_exhaustive]
533pub struct ClientConfig {
534    /// Transport configuration to use
535    pub(crate) transport: Arc<TransportConfig>,
536
537    /// Cryptographic configuration to use
538    pub(crate) crypto: Arc<dyn crypto::ClientConfig>,
539
540    /// Validation token store to use
541    pub(crate) token_store: Arc<dyn TokenStore>,
542
543    /// Provider that populates the destination connection ID of Initial Packets
544    pub(crate) initial_dst_cid_provider: Arc<dyn Fn() -> ConnectionId + Send + Sync>,
545
546    /// QUIC protocol version to use
547    pub(crate) version: u32,
548}
549
550impl ClientConfig {
551    /// Create a default config with a particular cryptographic config
552    pub fn new(crypto: Arc<dyn crypto::ClientConfig>) -> Self {
553        Self {
554            transport: Default::default(),
555            crypto,
556            token_store: Arc::new(NoneTokenStore),
557            initial_dst_cid_provider: Arc::new(|| {
558                RandomConnectionIdGenerator::new(MAX_CID_SIZE).generate_cid()
559            }),
560            version: 1,
561        }
562    }
563
564    /// Configure how to populate the destination CID of the initial packet when attempting to
565    /// establish a new connection
566    ///
567    /// By default, it's populated with random bytes with reasonable length, so unless you have
568    /// a good reason, you do not need to change it.
569    ///
570    /// When prefer to override the default, please note that the generated connection ID MUST be
571    /// at least 8 bytes long and unpredictable, as per section 7.2 of RFC 9000.
572    pub fn initial_dst_cid_provider(
573        &mut self,
574        initial_dst_cid_provider: Arc<dyn Fn() -> ConnectionId + Send + Sync>,
575    ) -> &mut Self {
576        self.initial_dst_cid_provider = initial_dst_cid_provider;
577        self
578    }
579
580    /// Set a custom [`TransportConfig`]
581    pub fn transport_config(&mut self, transport: Arc<TransportConfig>) -> &mut Self {
582        self.transport = transport;
583        self
584    }
585
586    /// Set a custom [`TokenStore`]
587    ///
588    /// Defaults to [`NoneTokenStore`], which disables the use of tokens from NEW_TOKEN frames as a
589    /// client.
590    pub fn token_store(&mut self, store: Arc<dyn TokenStore>) -> &mut Self {
591        self.token_store = store;
592        self
593    }
594
595    /// Set the QUIC version to use
596    pub fn version(&mut self, version: u32) -> &mut Self {
597        self.version = version;
598        self
599    }
600}
601
602#[cfg(any(feature = "rustls-aws-lc-rs", feature = "rustls-ring"))]
603impl ClientConfig {
604    /// Create a client configuration that trusts the platform's native roots
605    #[cfg(feature = "platform-verifier")]
606    pub fn with_platform_verifier() -> Self {
607        Self::new(Arc::new(crypto::rustls::QuicClientConfig::new(Arc::new(
608            rustls_platform_verifier::Verifier::new(),
609        ))))
610    }
611
612    /// Create a client configuration that trusts specified trust anchors
613    pub fn with_root_certificates(
614        roots: Arc<rustls::RootCertStore>,
615    ) -> Result<Self, rustls::client::VerifierBuilderError> {
616        Ok(Self::new(Arc::new(crypto::rustls::QuicClientConfig::new(
617            WebPkiServerVerifier::builder_with_provider(roots, configured_provider()).build()?,
618        ))))
619    }
620}
621
622impl fmt::Debug for ClientConfig {
623    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
624        fmt.debug_struct("ClientConfig")
625            .field("transport", &self.transport)
626            // crypto not debug
627            // token_store not debug
628            .field("version", &self.version)
629            .finish_non_exhaustive()
630    }
631}
632
633/// Errors in the configuration of an endpoint
634#[derive(Debug, Error, Clone, PartialEq, Eq)]
635#[non_exhaustive]
636pub enum ConfigError {
637    /// Value exceeds supported bounds
638    #[error("value exceeds supported bounds")]
639    OutOfBounds,
640}
641
642impl From<TryFromIntError> for ConfigError {
643    fn from(_: TryFromIntError) -> Self {
644        Self::OutOfBounds
645    }
646}
647
648impl From<VarIntBoundsExceeded> for ConfigError {
649    fn from(_: VarIntBoundsExceeded) -> Self {
650        Self::OutOfBounds
651    }
652}
653
654/// Object to get current [`SystemTime`]
655///
656/// This exists to allow system time to be mocked in tests, or wherever else desired.
657pub trait TimeSource: Send + Sync {
658    /// Get [`SystemTime::now()`](SystemTime::now) or the mocked equivalent
659    fn now(&self) -> SystemTime;
660}
661
662/// Default implementation of [`TimeSource`]
663///
664/// Implements `now` by calling [`SystemTime::now()`](SystemTime::now).
665pub struct StdSystemTime;
666
667impl TimeSource for StdSystemTime {
668    fn now(&self) -> SystemTime {
669        SystemTime::now()
670    }
671}