Skip to main content

trillium_quinn/
client.rs

1use crate::{
2    config::QuinnEndpoint,
3    runtime::{SocketTransport, TrilliumRuntime},
4};
5use std::{
6    fmt::{self, Debug, Formatter},
7    io,
8    net::SocketAddr,
9    sync::Arc,
10};
11use trillium_server_common::{Connector, QuicClientConfig};
12
13/// Client-side QUIC configuration for HTTP/3, backed by quinn.
14///
15/// This is a thin factory that creates [`QuinnEndpoint`]s bound to local addresses.
16/// The resulting endpoints can both accept and initiate QUIC connections.
17///
18/// # Construction
19///
20/// ```rust,ignore
21/// use trillium_tokio::ClientConfig;
22/// use trillium_quinn::ClientQuicConfig;
23///
24/// let client = trillium_client::Client::new_with_quic(
25///     ClientConfig::default(),
26///     ClientQuicConfig::with_webpki_roots(),
27/// );
28/// ```
29pub struct ClientQuicConfig {
30    client_config: quinn::ClientConfig,
31}
32
33impl Debug for ClientQuicConfig {
34    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
35        f.debug_struct("ClientQuicConfig").finish_non_exhaustive()
36    }
37}
38
39impl ClientQuicConfig {
40    /// Build a `ClientQuicConfig` trusting the [WebPKI](https://github.com/rustls/webpki-roots)
41    /// root certificates.
42    ///
43    /// Suitable for connecting to publicly trusted servers. For custom CA trust or client
44    /// authentication, use [`from_rustls_client_config`](Self::from_rustls_client_config).
45    ///
46    /// Requires the `webpki-roots` crate feature.
47    #[cfg(feature = "webpki-roots")]
48    pub fn with_webpki_roots() -> Self {
49        let root_store =
50            rustls::RootCertStore::from_iter(webpki_roots::TLS_SERVER_ROOTS.iter().cloned());
51        let crypto =
52            rustls::ClientConfig::builder_with_provider(crate::crypto_provider::crypto_provider())
53                .with_safe_default_protocol_versions()
54                .expect("building TLS config with protocol versions")
55                .with_root_certificates(root_store)
56                .with_no_client_auth();
57
58        Self::from_rustls_client_config(crypto)
59    }
60
61    /// Build from a pre-built [`rustls::ClientConfig`].
62    ///
63    /// `h3` ALPN is added automatically if not already present.
64    pub fn from_rustls_client_config(mut tls: rustls::ClientConfig) -> Self {
65        if !tls.alpn_protocols.contains(&b"h3".to_vec()) {
66            tls.alpn_protocols.push(b"h3".to_vec());
67        }
68        let quic_tls = quinn::crypto::rustls::QuicClientConfig::try_from(Arc::new(tls))
69            .expect("building QUIC client TLS config");
70        Self::from_quinn_client_config(quinn::ClientConfig::new(Arc::new(quic_tls)))
71    }
72
73    /// Build from a pre-built [`quinn::ClientConfig`].
74    ///
75    /// Use this when you need full control over transport parameters or TLS. The caller is
76    /// responsible for including `h3` in ALPN protocols.
77    pub fn from_quinn_client_config(config: quinn::ClientConfig) -> Self {
78        Self {
79            client_config: config,
80        }
81    }
82}
83
84impl<C> QuicClientConfig<C> for ClientQuicConfig
85where
86    C: Connector,
87    C::Runtime: Unpin,
88    C::Udp: SocketTransport,
89{
90    type Endpoint = QuinnEndpoint;
91
92    fn bind(&self, addr: SocketAddr, runtime: &C::Runtime) -> io::Result<Self::Endpoint> {
93        let socket = std::net::UdpSocket::bind(addr)?;
94        let quinn_runtime = TrilliumRuntime::<C::Runtime, C::Udp>::new(runtime.clone());
95        let mut endpoint = quinn::Endpoint::new(
96            quinn::EndpointConfig::default(),
97            None, // client-only, no server config
98            socket,
99            quinn_runtime,
100        )?;
101        endpoint.set_default_client_config(self.client_config.clone());
102        Ok(QuinnEndpoint::new(endpoint))
103    }
104}