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