Skip to main content

trillium_server_common/
client.rs

1use crate::{Runtime, RuntimeTrait, Transport, UdpTransport, Url};
2use std::{
3    any::Any,
4    fmt::{self, Debug, Formatter},
5    future::Future,
6    io,
7    net::SocketAddr,
8    pin::Pin,
9    sync::Arc,
10};
11
12mod destination;
13pub use destination::Destination;
14
15#[cfg(test)]
16mod tests;
17
18/// Interface for runtime and tls adapters for the trillium client
19///
20/// See
21/// [`trillium_client`](https://docs.trillium.rs/trillium_client) for more
22/// information on usage.
23pub trait Connector: Send + Sync + 'static {
24    /// the [`Transport`] that [`connect`](Connector::connect) returns
25    type Transport: Transport;
26
27    /// The [`RuntimeTrait`] for this Connector
28    type Runtime: RuntimeTrait;
29
30    /// The async UDP socket type for this connector. Used by QUIC adapters
31    /// for HTTP/3 support. Connectors that do not support UDP should set
32    /// this to `()`.
33    type Udp: UdpTransport;
34
35    /// Initiate a connection to the provided url
36    fn connect(&self, url: &Url) -> impl Future<Output = io::Result<Self::Transport>> + Send;
37
38    /// Open a connection to `destination`: dialing its pre-resolved addresses if present, otherwise
39    /// resolving its host, and advertising any per-connection ALPN it carries.
40    ///
41    /// A domain destination keeps its host as the certificate identity (SNI) regardless of the
42    /// addresses dialed, so pre-resolved addresses may come from any resolver (e.g. a DNS cache)
43    /// without affecting certificate validation.
44    ///
45    /// The default implementation reconstructs a URL via [`Destination::to_url`] and calls
46    /// [`connect`](Connector::connect), which ignores pre-resolved addresses and per-connection
47    /// ALPN; connectors that honor those override this method.
48    ///
49    /// `destination` is taken by value so connectors can adjust it (e.g. clearing `secure` before
50    /// delegating the TCP dial to an inner connector) without copying. A caller that needs to
51    /// retain it should clone before calling.
52    fn connect_to(
53        &self,
54        destination: Destination,
55    ) -> impl Future<Output = io::Result<Self::Transport>> + Send {
56        async move { self.connect(&destination.to_url()?).await }
57    }
58
59    /// Establish this connector's transport security for `destination` over an already-open
60    /// byte stream — e.g. an HTTP CONNECT tunnel — instead of dialing.
61    ///
62    /// `destination` describes the far end of `transport`. When `destination.secure()` is true, a
63    /// TLS connector handshakes over `transport`: SNI and certificate validation use the
64    /// destination's host, or for a host-less destination the first of `destination.addrs()` —
65    /// never `transport`'s peer address, which belongs to whatever the stream was opened to (e.g.
66    /// the proxy). The per-connection ALPN override applies as in
67    /// [`connect_to`](Connector::connect_to). Nothing is dialed; pre-resolved addresses serve only
68    /// as the bare-IP identity.
69    ///
70    /// # Errors
71    ///
72    /// The default returns `transport` unchanged for a non-secure destination and
73    /// [`io::ErrorKind::Unsupported`] for a secure one, so a connector that can't layer TLS never
74    /// silently downgrades.
75    fn connect_over(
76        &self,
77        transport: Box<dyn Transport>,
78        destination: Destination,
79    ) -> impl Future<Output = io::Result<Box<dyn Transport>>> + Send {
80        async move {
81            if destination.secure() {
82                Err(io::Error::new(
83                    io::ErrorKind::Unsupported,
84                    "connector does not support TLS over an existing transport",
85                ))
86            } else {
87                Ok(transport)
88            }
89        }
90    }
91    /// Returns an object-safe [`ArcedConnector`]. Do not implement this.
92    fn arced(self) -> ArcedConnector
93    where
94        Self: Sized,
95    {
96        ArcedConnector(Arc::new(self))
97    }
98
99    /// Perform a DNS lookup for a given host-and-port
100    fn resolve(
101        &self,
102        host: &str,
103        port: u16,
104    ) -> impl Future<Output = io::Result<Vec<SocketAddr>>> + Send;
105
106    /// Returns the runtime
107    fn runtime(&self) -> Self::Runtime;
108}
109
110/// An Arced and type-erased [`Connector`]
111#[derive(Clone)]
112pub struct ArcedConnector(Arc<dyn ObjectSafeConnector>);
113
114impl Debug for ArcedConnector {
115    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
116        f.debug_tuple("ArcedConnector").finish()
117    }
118}
119
120impl ArcedConnector {
121    /// Constructs a new `ArcedConnector`
122    #[must_use]
123    pub fn new(connector: impl Connector) -> Self {
124        connector.arced()
125    }
126
127    /// Determine if this `ArcedConnector` is the specified type
128    pub fn is<T: Any + 'static>(&self) -> bool {
129        self.as_any().is::<T>()
130    }
131
132    /// Attempt to borrow this `ArcedConnector` as the provided type, returning None if it does not
133    /// contain the type
134    pub fn downcast_ref<T: Any + 'static>(&self) -> Option<&T> {
135        self.0.as_any().downcast_ref()
136    }
137
138    /// Attempt to mutably borrow this `ArcedConnector` as the provided type, returning None if it
139    /// does not contain the type or if there are multiple outstanding clones of this arc
140    pub fn downcast_mut<T: Any + 'static>(&mut self) -> Option<&mut T> {
141        Arc::get_mut(&mut self.0)?.as_mut_any().downcast_mut()
142    }
143
144    /// Returns an object-safe [`Runtime`]
145    pub fn runtime(&self) -> Runtime {
146        self.0.runtime()
147    }
148}
149
150// clippy thinks this is better ¯\(ツ)/¯
151type ConnectResult<'fut> =
152    Pin<Box<dyn Future<Output = io::Result<Box<dyn Transport>>> + Send + 'fut>>;
153
154trait ObjectSafeConnector: Send + Sync + 'static {
155    fn connect<'connector, 'url, 'fut>(&'connector self, url: &'url Url) -> ConnectResult<'fut>
156    where
157        'connector: 'fut,
158        'url: 'fut,
159        Self: 'fut;
160    fn as_any(&self) -> &dyn Any;
161    fn as_mut_any(&mut self) -> &mut dyn Any;
162    fn runtime(&self) -> Runtime;
163
164    fn resolve<'connector, 'host, 'fut>(
165        &'connector self,
166        host: &'host str,
167        port: u16,
168    ) -> Pin<Box<dyn Future<Output = io::Result<Vec<SocketAddr>>> + Send + 'fut>>
169    where
170        'connector: 'fut,
171        'host: 'fut,
172        Self: 'fut;
173
174    fn connect_to<'connector, 'fut>(
175        &'connector self,
176        destination: Destination,
177    ) -> ConnectResult<'fut>
178    where
179        'connector: 'fut,
180        Self: 'fut;
181
182    fn connect_over<'connector, 'fut>(
183        &'connector self,
184        transport: Box<dyn Transport>,
185        destination: Destination,
186    ) -> ConnectResult<'fut>
187    where
188        'connector: 'fut,
189        Self: 'fut;
190}
191
192impl<T: Connector> ObjectSafeConnector for T {
193    fn connect<'connector, 'url, 'fut>(
194        &'connector self,
195        url: &'url Url,
196    ) -> Pin<Box<dyn Future<Output = io::Result<Box<dyn Transport>>> + Send + 'fut>>
197    where
198        'connector: 'fut,
199        'url: 'fut,
200        Self: 'fut,
201    {
202        Box::pin(async move {
203            Connector::connect(self, url)
204                .await
205                .map(|t| Box::new(t) as Box<dyn Transport>)
206        })
207    }
208
209    fn as_any(&self) -> &dyn Any {
210        self
211    }
212
213    fn as_mut_any(&mut self) -> &mut dyn Any {
214        self
215    }
216
217    fn runtime(&self) -> Runtime {
218        Connector::runtime(self).into()
219    }
220
221    fn resolve<'connector, 'host, 'fut>(
222        &'connector self,
223        host: &'host str,
224        port: u16,
225    ) -> Pin<Box<dyn Future<Output = io::Result<Vec<SocketAddr>>> + Send + 'fut>>
226    where
227        'connector: 'fut,
228        'host: 'fut,
229        Self: 'fut,
230    {
231        Box::pin(async move { Connector::resolve(self, host, port).await })
232    }
233
234    fn connect_to<'connector, 'fut>(
235        &'connector self,
236        destination: Destination,
237    ) -> ConnectResult<'fut>
238    where
239        'connector: 'fut,
240        Self: 'fut,
241    {
242        Box::pin(async move {
243            Connector::connect_to(self, destination)
244                .await
245                .map(|t| Box::new(t) as Box<dyn Transport>)
246        })
247    }
248
249    fn connect_over<'connector, 'fut>(
250        &'connector self,
251        transport: Box<dyn Transport>,
252        destination: Destination,
253    ) -> ConnectResult<'fut>
254    where
255        'connector: 'fut,
256        Self: 'fut,
257    {
258        Box::pin(async move { Connector::connect_over(self, transport, destination).await })
259    }
260}
261
262impl Connector for ArcedConnector {
263    type Runtime = Runtime;
264    type Transport = Box<dyn Transport>;
265    type Udp = ();
266
267    async fn connect(&self, url: &Url) -> io::Result<Box<dyn Transport>> {
268        self.0.connect(url).await
269    }
270
271    fn arced(self) -> ArcedConnector {
272        self
273    }
274
275    fn runtime(&self) -> Self::Runtime {
276        self.0.runtime()
277    }
278
279    async fn resolve(&self, host: &str, port: u16) -> io::Result<Vec<SocketAddr>> {
280        self.0.resolve(host, port).await
281    }
282
283    async fn connect_to(&self, destination: Destination) -> io::Result<Box<dyn Transport>> {
284        self.0.connect_to(destination).await
285    }
286
287    async fn connect_over(
288        &self,
289        transport: Box<dyn Transport>,
290        destination: Destination,
291    ) -> io::Result<Box<dyn Transport>> {
292        self.0.connect_over(transport, destination).await
293    }
294}
295
296/// Factory for creating client-side QUIC endpoints.
297///
298/// Parameterised over `C: Connector` so that the concrete runtime and UDP socket types
299/// are available to the implementation without coupling the QUIC library to any specific
300/// runtime adapter.
301///
302/// Implementations should produce a [`QuicEndpoint`](crate::QuicEndpoint) bound to the
303/// given local address. TLS configuration is embedded in the implementation.
304pub trait QuicClientConfig<C: Connector>: Send + Sync + 'static {
305    /// The endpoint type produced by [`bind`](QuicClientConfig::bind).
306    type Endpoint: crate::QuicEndpoint;
307
308    /// Bind a QUIC endpoint to the given local address.
309    ///
310    /// `runtime` is the runtime from the paired [`Connector`]; use it for spawning,
311    /// timers, and UDP I/O.
312    fn bind(&self, addr: SocketAddr, runtime: &C::Runtime) -> io::Result<Self::Endpoint>;
313}
314
315// -- Type-erased QuicClientConfig --
316
317trait ObjectSafeQuicClientConfig: Send + Sync + 'static {
318    fn bind(&self, addr: SocketAddr) -> io::Result<crate::ArcedQuicEndpoint>;
319}
320
321/// Binds a [`QuicClientConfig<C>`] together with its runtime before type erasure.
322struct BoundQuicClientConfig<Q, C: Connector> {
323    config: Q,
324    runtime: C::Runtime,
325}
326
327impl<C: Connector, Q: QuicClientConfig<C>> ObjectSafeQuicClientConfig
328    for BoundQuicClientConfig<Q, C>
329{
330    fn bind(&self, addr: SocketAddr) -> io::Result<crate::ArcedQuicEndpoint> {
331        let endpoint = self.config.bind(addr, &self.runtime)?;
332        Ok(crate::ArcedQuicEndpoint::from(endpoint))
333    }
334}
335
336/// An arc-wrapped, type-erased QUIC client config (endpoint factory).
337///
338/// Created via [`Client::new_with_quic`](https://docs.rs/trillium-client/latest/trillium_client/struct.Client.html#method.new_with_quic), which
339/// binds the connector's runtime into the wrapper before erasure.
340#[derive(Clone)]
341pub struct ArcedQuicClientConfig(Arc<dyn ObjectSafeQuicClientConfig>);
342
343impl Debug for ArcedQuicClientConfig {
344    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
345        f.debug_tuple("ArcedQuicClientConfig").finish()
346    }
347}
348
349impl ArcedQuicClientConfig {
350    /// Binds `config` to the runtime from `connector` and wraps the result for type erasure.
351    #[must_use]
352    pub fn new<C: Connector, Q: QuicClientConfig<C>>(connector: &C, config: Q) -> Self {
353        Self(Arc::new(BoundQuicClientConfig {
354            runtime: connector.runtime(),
355            config,
356        }))
357    }
358
359    /// Create a type-erased QUIC endpoint bound to the given local address.
360    pub fn bind(&self, addr: SocketAddr) -> io::Result<crate::ArcedQuicEndpoint> {
361        self.0.bind(addr)
362    }
363}