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
12/// Interface for runtime and tls adapters for the trillium client
13///
14/// See
15/// [`trillium_client`](https://docs.trillium.rs/trillium_client) for more
16/// information on usage.
17pub trait Connector: Send + Sync + 'static {
18    /// the [`Transport`] that [`connect`](Connector::connect) returns
19    type Transport: Transport;
20
21    /// The [`RuntimeTrait`] for this Connector
22    type Runtime: RuntimeTrait;
23
24    /// The async UDP socket type for this connector. Used by QUIC adapters
25    /// for HTTP/3 support. Connectors that do not support UDP should set
26    /// this to `()`.
27    type Udp: UdpTransport;
28
29    /// Initiate a connection to the provided url
30    fn connect(&self, url: &Url) -> impl Future<Output = io::Result<Self::Transport>> + Send;
31
32    /// Returns an object-safe [`ArcedConnector`]. Do not implement this.
33    fn arced(self) -> ArcedConnector
34    where
35        Self: Sized,
36    {
37        ArcedConnector(Arc::new(self))
38    }
39
40    /// Perform a DNS lookup for a given host-and-port
41    fn resolve(
42        &self,
43        host: &str,
44        port: u16,
45    ) -> impl Future<Output = io::Result<Vec<SocketAddr>>> + Send;
46
47    /// Returns the runtime
48    fn runtime(&self) -> Self::Runtime;
49}
50
51/// An Arced and type-erased [`Connector`]
52#[derive(Clone)]
53pub struct ArcedConnector(Arc<dyn ObjectSafeConnector>);
54
55impl Debug for ArcedConnector {
56    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
57        f.debug_tuple("ArcedConnector").finish()
58    }
59}
60
61impl ArcedConnector {
62    /// Constructs a new `ArcedConnector`
63    #[must_use]
64    pub fn new(connector: impl Connector) -> Self {
65        connector.arced()
66    }
67
68    /// Determine if this `ArcedConnector` is the specified type
69    pub fn is<T: Any + 'static>(&self) -> bool {
70        self.as_any().is::<T>()
71    }
72
73    /// Attempt to borrow this `ArcedConnector` as the provided type, returning None if it does not
74    /// contain the type
75    pub fn downcast_ref<T: Any + 'static>(&self) -> Option<&T> {
76        self.0.as_any().downcast_ref()
77    }
78
79    /// Attempt to mutably borrow this `ArcedConnector` as the provided type, returning None if it
80    /// does not contain the type or if there are multiple outstanding clones of this arc
81    pub fn downcast_mut<T: Any + 'static>(&mut self) -> Option<&mut T> {
82        Arc::get_mut(&mut self.0)?.as_mut_any().downcast_mut()
83    }
84
85    /// Returns an object-safe [`Runtime`]
86    pub fn runtime(&self) -> Runtime {
87        self.0.runtime()
88    }
89}
90
91// clippy thinks this is better ¯\(ツ)/¯
92type ConnectResult<'fut> =
93    Pin<Box<dyn Future<Output = io::Result<Box<dyn Transport>>> + Send + 'fut>>;
94
95trait ObjectSafeConnector: Send + Sync + 'static {
96    #[must_use]
97    fn connect<'connector, 'url, 'fut>(&'connector self, url: &'url Url) -> ConnectResult<'fut>
98    where
99        'connector: 'fut,
100        'url: 'fut,
101        Self: 'fut;
102    fn as_any(&self) -> &dyn Any;
103    fn as_mut_any(&mut self) -> &mut dyn Any;
104    fn runtime(&self) -> Runtime;
105
106    fn resolve<'connector, 'host, 'fut>(
107        &'connector self,
108        host: &'host str,
109        port: u16,
110    ) -> Pin<Box<dyn Future<Output = io::Result<Vec<SocketAddr>>> + Send + 'fut>>
111    where
112        'connector: 'fut,
113        'host: 'fut,
114        Self: 'fut;
115}
116
117impl<T: Connector> ObjectSafeConnector for T {
118    fn connect<'connector, 'url, 'fut>(
119        &'connector self,
120        url: &'url Url,
121    ) -> Pin<Box<dyn Future<Output = io::Result<Box<dyn Transport>>> + Send + 'fut>>
122    where
123        'connector: 'fut,
124        'url: 'fut,
125        Self: 'fut,
126    {
127        Box::pin(async move {
128            Connector::connect(self, url)
129                .await
130                .map(|t| Box::new(t) as Box<dyn Transport>)
131        })
132    }
133
134    fn as_any(&self) -> &dyn Any {
135        self
136    }
137
138    fn as_mut_any(&mut self) -> &mut dyn Any {
139        self
140    }
141
142    fn runtime(&self) -> Runtime {
143        Connector::runtime(self).into()
144    }
145
146    fn resolve<'connector, 'host, 'fut>(
147        &'connector self,
148        host: &'host str,
149        port: u16,
150    ) -> Pin<Box<dyn Future<Output = io::Result<Vec<SocketAddr>>> + Send + 'fut>>
151    where
152        'connector: 'fut,
153        'host: 'fut,
154        Self: 'fut,
155    {
156        Box::pin(async move { Connector::resolve(self, host, port).await })
157    }
158}
159
160impl Connector for ArcedConnector {
161    type Runtime = Runtime;
162    type Transport = Box<dyn Transport>;
163    type Udp = ();
164
165    async fn connect(&self, url: &Url) -> io::Result<Box<dyn Transport>> {
166        self.0.connect(url).await
167    }
168
169    fn arced(self) -> ArcedConnector {
170        self
171    }
172
173    fn runtime(&self) -> Self::Runtime {
174        self.0.runtime()
175    }
176
177    async fn resolve(&self, host: &str, port: u16) -> io::Result<Vec<SocketAddr>> {
178        self.0.resolve(host, port).await
179    }
180}
181
182/// Factory for creating client-side QUIC endpoints.
183///
184/// Parameterised over `C: Connector` so that the concrete runtime and UDP socket types
185/// are available to the implementation without coupling the QUIC library to any specific
186/// runtime adapter.
187///
188/// Implementations should produce a [`QuicEndpoint`](crate::QuicEndpoint) bound to the
189/// given local address. TLS configuration is embedded in the implementation.
190pub trait QuicClientConfig<C: Connector>: Send + Sync + 'static {
191    /// The endpoint type produced by [`bind`](QuicClientConfig::bind).
192    type Endpoint: crate::QuicEndpoint;
193
194    /// Bind a QUIC endpoint to the given local address.
195    ///
196    /// `runtime` is the runtime from the paired [`Connector`]; use it for spawning,
197    /// timers, and UDP I/O.
198    fn bind(&self, addr: SocketAddr, runtime: &C::Runtime) -> io::Result<Self::Endpoint>;
199}
200
201// -- Type-erased QuicClientConfig --
202
203trait ObjectSafeQuicClientConfig: Send + Sync + 'static {
204    fn bind(&self, addr: SocketAddr) -> io::Result<crate::ArcedQuicEndpoint>;
205}
206
207/// Binds a [`QuicClientConfig<C>`] together with its runtime before type erasure.
208struct BoundQuicClientConfig<Q, C: Connector> {
209    config: Q,
210    runtime: C::Runtime,
211}
212
213impl<C: Connector, Q: QuicClientConfig<C>> ObjectSafeQuicClientConfig
214    for BoundQuicClientConfig<Q, C>
215{
216    fn bind(&self, addr: SocketAddr) -> io::Result<crate::ArcedQuicEndpoint> {
217        let endpoint = self.config.bind(addr, &self.runtime)?;
218        Ok(crate::ArcedQuicEndpoint::from(endpoint))
219    }
220}
221
222/// An arc-wrapped, type-erased QUIC client config (endpoint factory).
223///
224/// Created via [`Client::new_with_quic`](https://docs.rs/trillium-client/latest/trillium_client/struct.Client.html#method.new_with_quic), which
225/// binds the connector's runtime into the wrapper before erasure.
226#[derive(Clone)]
227pub struct ArcedQuicClientConfig(Arc<dyn ObjectSafeQuicClientConfig>);
228
229impl Debug for ArcedQuicClientConfig {
230    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
231        f.debug_tuple("ArcedQuicClientConfig").finish()
232    }
233}
234
235impl ArcedQuicClientConfig {
236    /// Binds `config` to the runtime from `connector` and wraps the result for type erasure.
237    #[must_use]
238    pub fn new<C: Connector, Q: QuicClientConfig<C>>(connector: &C, config: Q) -> Self {
239        Self(Arc::new(BoundQuicClientConfig {
240            runtime: connector.runtime(),
241            config,
242        }))
243    }
244
245    /// Create a type-erased QUIC endpoint bound to the given local address.
246    pub fn bind(&self, addr: SocketAddr) -> io::Result<crate::ArcedQuicEndpoint> {
247        self.0.bind(addr)
248    }
249}