Skip to main content

trillium_server_common/
quic.rs

1use crate::{Server, Transport};
2use futures_lite::{AsyncRead, AsyncWrite};
3use std::{
4    fmt::Debug,
5    future::Future,
6    io,
7    net::SocketAddr,
8    pin::Pin,
9    sync::Arc,
10    task::{Context, Poll},
11};
12use trillium::Info;
13
14/// Abstraction over the inbound half of a QUIC stream (both bidi and inbound uni)
15pub trait QuicTransportReceive: AsyncRead {
16    /// Stop a receive stream, signaling an error code to the peer.
17    fn stop(&mut self, code: u64);
18}
19
20/// Abstraction over the outbound half of a QUIC stream (both bidi and outbound uni)
21pub trait QuicTransportSend: AsyncWrite {
22    /// Close the send stream immediately with the provided error code.
23    fn reset(&mut self, code: u64);
24}
25
26/// Abstraction over a QUIC bidirectional stream
27pub trait QuicTransportBidi: QuicTransportReceive + QuicTransportSend + Transport {}
28
29/// Abstraction over a single QUIC connection.
30///
31/// QUIC library adapters (e.g. trillium-quinn) implement this trait. The generic HTTP/3 connection
32/// handler in server-common consumes it to manage streams without knowing about the underlying QUIC
33/// implementation.
34///
35/// Implementations should be cheaply cloneable (typically wrapping an `Arc`-based connection
36/// handle) since the connection handler clones this into spawned tasks.
37pub trait QuicConnectionTrait: Clone + Send + Sync + 'static {
38    /// A bidirectional stream
39    type BidiStream: QuicTransportBidi + Unpin + Send + Sync + 'static;
40
41    /// A unidirectional receive stream from the peer
42    type RecvStream: QuicTransportReceive + Unpin + Send + Sync + 'static;
43
44    /// A unidirectional send stream to the peer
45    type SendStream: QuicTransportSend + Unpin + Send + Sync + 'static;
46
47    /// Accept the next bidirectional stream opened by the peer.
48    ///
49    /// Returns the QUIC stream ID and a combined read/write transport.
50    fn accept_bidi(&self) -> impl Future<Output = io::Result<(u64, Self::BidiStream)>> + Send;
51
52    /// Accept the next unidirectional stream opened by the peer.
53    ///
54    /// Returns the QUIC stream ID and a receive-only stream.
55    fn accept_uni(&self) -> impl Future<Output = io::Result<(u64, Self::RecvStream)>> + Send;
56
57    /// Open a new unidirectional stream to the peer.
58    ///
59    /// Returns the QUIC stream ID and a send-only stream.
60    fn open_uni(&self) -> impl Future<Output = io::Result<(u64, Self::SendStream)>> + Send;
61
62    /// Open a new bidirectional stream to the peer.
63    ///
64    /// Returns the QUIC stream ID and a combined read/write transport.
65    fn open_bidi(&self) -> impl Future<Output = io::Result<(u64, Self::BidiStream)>> + Send;
66
67    /// The peer's address.
68    fn remote_address(&self) -> SocketAddr;
69
70    /// Close the entire QUIC connection with an error code and reason.
71    fn close(&self, error_code: u64, reason: &[u8]);
72
73    /// Send an unreliable datagram over the QUIC connection.
74    ///
75    /// Datagrams are atomic and unordered. The data must fit in a single QUIC packet
76    /// (typically ~1200 bytes). Returns an error if datagrams are not supported by the
77    /// peer or the data is too large.
78    fn send_datagram(&self, data: &[u8]) -> io::Result<()>;
79
80    /// Receive the next unreliable datagram from the peer, passing the raw bytes to `callback`.
81    fn recv_datagram<F: FnOnce(&[u8]) + Send>(
82        &self,
83        callback: F,
84    ) -> impl Future<Output = io::Result<()>> + Send;
85
86    /// The maximum datagram payload size the peer will accept, if datagrams are supported.
87    ///
88    /// Returns `None` if the peer does not support datagrams.
89    fn max_datagram_size(&self) -> Option<usize>;
90}
91
92/// Configuration for a QUIC endpoint, provided by the user at server setup time.
93///
94/// QUIC library adapters implement this (e.g. `trillium_quinn::QuicConfig`). The `()`
95/// implementation produces no binding (HTTP/3 disabled).
96///
97/// The generic flow is:
98/// 1. User provides a `QuicConfig` via [`Config::with_quic`](crate::Config)
99/// 2. During server startup, `bind` is called with the TCP listener's address and runtime
100/// 3. The resulting [`QuicEndpoint`] is stored on `RunningConfig` and drives the H3 accept loop
101pub trait QuicConfig<S: Server>: Send + 'static {
102    /// The bound endpoint type produced by [`bind`](QuicConfig::bind).
103    type Endpoint: QuicEndpoint;
104
105    /// Bind a QUIC endpoint to the given address.
106    ///
107    /// The runtime is provided so that QUIC library adapters can bridge
108    /// to the active async runtime for timers, spawning, and UDP I/O.
109    ///
110    /// Returns `None` if QUIC is not configured (the `()` case), `Some(Ok(binding))` on success,
111    /// or `Some(Err(..))` if binding fails.
112    fn bind(
113        self,
114        addr: SocketAddr,
115        runtime: S::Runtime,
116        info: &mut Info,
117    ) -> Option<io::Result<Self::Endpoint>>;
118
119    /// Whether this is a real QUIC configuration (`true`) rather than the no-op `()` (`false`).
120    ///
121    /// Lets a caller decide whether to set up a QUIC listener at all without consuming `self` by
122    /// calling [`bind`](Self::bind). The default returns `true`; only the `()` implementation
123    /// overrides it.
124    fn is_configured(&self) -> bool {
125        true
126    }
127
128    /// Bind a QUIC endpoint over a pre-claimed [`std::net::UdpSocket`].
129    ///
130    /// The multi-listener server builder claims the UDP socket eagerly (fail-fast at
131    /// `bind_quic` time) and hands it through to the adapter when the runtime is available.
132    /// The default implementation reads the socket's local address and delegates to
133    /// [`bind`](Self::bind), which is correct but rebinds the address; adapters should override
134    /// to consume the pre-claimed socket directly (e.g. quinn accepts a `std::net::UdpSocket`
135    /// in `Endpoint::new`).
136    fn bind_with_socket(
137        self,
138        socket: std::net::UdpSocket,
139        runtime: S::Runtime,
140        info: &mut Info,
141    ) -> io::Result<Self::Endpoint>
142    where
143        Self: Sized,
144    {
145        let addr = socket.local_addr()?;
146        drop(socket);
147        self.bind(addr, runtime, info).unwrap_or_else(|| {
148            Err(io::Error::new(
149                io::ErrorKind::Unsupported,
150                "QuicConfig::bind returned None; this QuicConfig is a no-op and cannot be bound \
151                 with bind_with_socket",
152            ))
153        })
154    }
155}
156
157impl<S: Server> QuicConfig<S> for () {
158    type Endpoint = ();
159
160    fn bind(self, _: SocketAddr, _: S::Runtime, _: &mut Info) -> Option<io::Result<()>> {
161        None
162    }
163
164    fn is_configured(&self) -> bool {
165        false
166    }
167}
168
169/// A bound QUIC endpoint that accepts and initiates connections.
170///
171/// Analogous to [`Server`](crate::Server) for TCP. QUIC library adapters implement this to provide
172/// the connection accept loop (server) and outbound connections (client).
173///
174/// The `()` implementation is a no-op (HTTP/3 disabled). Server-only implementations may return
175/// an error from [`connect`](QuicEndpoint::connect); client-only implementations may return
176/// `None` from [`accept`](QuicEndpoint::accept).
177pub trait QuicEndpoint: Send + Sync + 'static {
178    /// The connection type yielded by this endpoint.
179    type Connection: QuicConnectionTrait;
180
181    /// Accept the next inbound QUIC connection, or return `None` if the endpoint is done.
182    fn accept(&self) -> impl Future<Output = Option<Self::Connection>> + Send;
183
184    /// Initiate a QUIC connection to the given address.
185    ///
186    /// `server_name` is the SNI hostname used for TLS verification.
187    fn connect(
188        &self,
189        addr: SocketAddr,
190        server_name: &str,
191    ) -> impl Future<Output = io::Result<Self::Connection>> + Send;
192
193    /// The local address this endpoint is bound to. The default impl returns
194    /// `Unsupported`; adapters override when a bound UDP socket is available.
195    fn local_addr(&self) -> io::Result<SocketAddr> {
196        Err(io::Error::new(
197            io::ErrorKind::Unsupported,
198            "QuicEndpoint::local_addr not implemented for this adapter",
199        ))
200    }
201}
202
203/// Uninhabited type used by the `()` [`QuicEndpoint`] implementation.
204///
205/// Since `()` never produces connections, this type is never constructed and its trait
206/// implementations are never exercised.
207#[derive(Debug, Clone, Copy)]
208pub enum NoQuic {}
209
210impl QuicTransportSend for NoQuic {
211    fn reset(&mut self, _code: u64) {
212        match *self {}
213    }
214}
215
216impl QuicTransportReceive for NoQuic {
217    fn stop(&mut self, _code: u64) {
218        match *self {}
219    }
220}
221
222impl QuicTransportBidi for NoQuic {}
223
224impl QuicConnectionTrait for NoQuic {
225    type BidiStream = NoQuic;
226    type RecvStream = NoQuic;
227    type SendStream = NoQuic;
228
229    async fn accept_bidi(&self) -> io::Result<(u64, Self::BidiStream)> {
230        match *self {}
231    }
232
233    async fn accept_uni(&self) -> io::Result<(u64, Self::RecvStream)> {
234        match *self {}
235    }
236
237    async fn open_uni(&self) -> io::Result<(u64, Self::SendStream)> {
238        match *self {}
239    }
240
241    async fn open_bidi(&self) -> io::Result<(u64, Self::BidiStream)> {
242        match *self {}
243    }
244
245    fn remote_address(&self) -> SocketAddr {
246        match *self {}
247    }
248
249    fn close(&self, _: u64, _: &[u8]) {
250        match *self {}
251    }
252
253    fn send_datagram(&self, _: &[u8]) -> io::Result<()> {
254        match *self {}
255    }
256
257    async fn recv_datagram<F: FnOnce(&[u8]) + Send>(&self, _: F) -> io::Result<()> {
258        match *self {}
259    }
260
261    fn max_datagram_size(&self) -> Option<usize> {
262        match *self {}
263    }
264}
265
266impl Transport for NoQuic {}
267
268impl AsyncRead for NoQuic {
269    fn poll_read(
270        self: Pin<&mut Self>,
271        _: &mut Context<'_>,
272        _: &mut [u8],
273    ) -> Poll<io::Result<usize>> {
274        match *self.get_mut() {}
275    }
276}
277
278impl AsyncWrite for NoQuic {
279    fn poll_write(self: Pin<&mut Self>, _: &mut Context<'_>, _: &[u8]) -> Poll<io::Result<usize>> {
280        match *self.get_mut() {}
281    }
282
283    fn poll_flush(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<io::Result<()>> {
284        match *self.get_mut() {}
285    }
286
287    fn poll_close(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<io::Result<()>> {
288        match *self.get_mut() {}
289    }
290}
291
292impl QuicEndpoint for () {
293    type Connection = NoQuic;
294
295    async fn accept(&self) -> Option<NoQuic> {
296        None
297    }
298
299    async fn connect(&self, _: SocketAddr, _: &str) -> io::Result<NoQuic> {
300        Err(io::Error::new(
301            io::ErrorKind::Unsupported,
302            "QUIC not configured",
303        ))
304    }
305}
306
307// -- Type-erased QuicConnection --
308
309type BoxedFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;
310
311/// A type-erased [`QuicTransportReceive`] stream — `Box<dyn QuicTransportReceive + Unpin + Send +
312/// Sync>`.
313pub type BoxedRecvStream = Box<dyn QuicTransportReceive + Unpin + Send + Sync>;
314
315/// A type-erased [`QuicTransportSend`] stream — `Box<dyn QuicTransportSend + Unpin + Send + Sync>`.
316pub type BoxedSendStream = Box<dyn QuicTransportSend + Unpin + Send + Sync>;
317
318/// A type-erased [`QuicTransportBidi`] stream — `Box<dyn QuicTransportBidi + Unpin + Send + Sync>`.
319pub type BoxedBidiStream = Box<dyn QuicTransportBidi + Unpin + Send + Sync>;
320
321impl QuicTransportReceive for BoxedRecvStream {
322    fn stop(&mut self, code: u64) {
323        (**self).stop(code);
324    }
325}
326
327impl QuicTransportSend for BoxedSendStream {
328    fn reset(&mut self, code: u64) {
329        (**self).reset(code);
330    }
331}
332
333impl QuicTransportReceive for BoxedBidiStream {
334    fn stop(&mut self, code: u64) {
335        (**self).stop(code);
336    }
337}
338
339impl QuicTransportSend for BoxedBidiStream {
340    fn reset(&mut self, code: u64) {
341        (**self).reset(code);
342    }
343}
344
345impl QuicTransportBidi for BoxedBidiStream {}
346
347impl Transport for BoxedBidiStream {
348    fn set_linger(&mut self, linger: Option<std::time::Duration>) -> io::Result<()> {
349        (**self).set_linger(linger)
350    }
351
352    fn set_nodelay(&mut self, nodelay: bool) -> io::Result<()> {
353        (**self).set_nodelay(nodelay)
354    }
355
356    fn set_ip_ttl(&mut self, ttl: u32) -> io::Result<()> {
357        (**self).set_ip_ttl(ttl)
358    }
359
360    fn peer_addr(&self) -> io::Result<Option<SocketAddr>> {
361        (**self).peer_addr()
362    }
363
364    fn negotiated_alpn(&self) -> Option<std::borrow::Cow<'_, [u8]>> {
365        (**self).negotiated_alpn()
366    }
367}
368
369type ReceiveDatagramCallback<'a> = Box<dyn FnOnce(&[u8]) + Send + 'a>;
370
371trait ObjectSafeQuicConnection: Send + Sync {
372    fn accept_bidi(&self) -> BoxedFuture<'_, io::Result<(u64, BoxedBidiStream)>>;
373    fn accept_uni(&self) -> BoxedFuture<'_, io::Result<(u64, BoxedRecvStream)>>;
374    fn open_uni(&self) -> BoxedFuture<'_, io::Result<(u64, BoxedSendStream)>>;
375    fn open_bidi(&self) -> BoxedFuture<'_, io::Result<(u64, BoxedBidiStream)>>;
376    fn remote_address(&self) -> SocketAddr;
377    fn close(&self, error_code: u64, reason: &[u8]);
378    fn send_datagram(&self, data: &[u8]) -> io::Result<()>;
379    fn recv_datagram<'a>(
380        &'a self,
381        callback: ReceiveDatagramCallback<'a>,
382    ) -> BoxedFuture<'a, io::Result<()>>;
383    fn max_datagram_size(&self) -> Option<usize>;
384}
385
386impl<T: QuicConnectionTrait> ObjectSafeQuicConnection for T {
387    fn accept_bidi(&self) -> BoxedFuture<'_, io::Result<(u64, BoxedBidiStream)>> {
388        Box::pin(async {
389            let (id, stream) = QuicConnectionTrait::accept_bidi(self).await?;
390            Ok((id, Box::new(stream) as BoxedBidiStream))
391        })
392    }
393
394    fn accept_uni(&self) -> BoxedFuture<'_, io::Result<(u64, BoxedRecvStream)>> {
395        Box::pin(async {
396            let (id, stream) = QuicConnectionTrait::accept_uni(self).await?;
397            Ok((id, Box::new(stream) as BoxedRecvStream))
398        })
399    }
400
401    fn open_uni(&self) -> BoxedFuture<'_, io::Result<(u64, BoxedSendStream)>> {
402        Box::pin(async {
403            let (id, stream) = QuicConnectionTrait::open_uni(self).await?;
404            Ok((id, Box::new(stream) as BoxedSendStream))
405        })
406    }
407
408    fn open_bidi(&self) -> BoxedFuture<'_, io::Result<(u64, BoxedBidiStream)>> {
409        Box::pin(async {
410            let (id, stream) = QuicConnectionTrait::open_bidi(self).await?;
411            Ok((id, Box::new(stream) as BoxedBidiStream))
412        })
413    }
414
415    fn remote_address(&self) -> SocketAddr {
416        QuicConnectionTrait::remote_address(self)
417    }
418
419    fn close(&self, error_code: u64, reason: &[u8]) {
420        QuicConnectionTrait::close(self, error_code, reason)
421    }
422
423    fn send_datagram(&self, data: &[u8]) -> io::Result<()> {
424        QuicConnectionTrait::send_datagram(self, data)
425    }
426
427    fn recv_datagram<'a>(
428        &'a self,
429        callback: Box<dyn FnOnce(&[u8]) + Send + 'a>,
430    ) -> BoxedFuture<'a, io::Result<()>> {
431        Box::pin(QuicConnectionTrait::recv_datagram(self, callback))
432    }
433
434    fn max_datagram_size(&self) -> Option<usize> {
435        QuicConnectionTrait::max_datagram_size(self)
436    }
437}
438
439/// A type-erased QUIC connection handle, equivalent to `Arc<dyn QuicConnectionTrait>`.
440/// Cheaply cloneable.
441///
442/// Handlers retrieve this from conn state to access QUIC features (streams, datagrams)
443/// without depending on the concrete QUIC implementation type.
444#[derive(Clone)]
445pub struct QuicConnection(Arc<dyn ObjectSafeQuicConnection>);
446
447impl Debug for QuicConnection {
448    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
449        f.debug_struct("QuicConnection")
450            .field("peer", &self.remote_address())
451            .finish_non_exhaustive()
452    }
453}
454
455impl<T: QuicConnectionTrait> From<T> for QuicConnection {
456    fn from(connection: T) -> Self {
457        Self(Arc::new(connection))
458    }
459}
460
461impl QuicConnection {
462    /// Accept the next bidirectional stream opened by the peer.
463    pub async fn accept_bidi(&self) -> io::Result<(u64, BoxedBidiStream)> {
464        self.0.accept_bidi().await
465    }
466
467    /// Accept the next unidirectional stream opened by the peer.
468    pub async fn accept_uni(&self) -> io::Result<(u64, BoxedRecvStream)> {
469        self.0.accept_uni().await
470    }
471
472    /// Open a new unidirectional stream to the peer.
473    pub async fn open_uni(&self) -> io::Result<(u64, BoxedSendStream)> {
474        self.0.open_uni().await
475    }
476
477    /// Open a new bidirectional stream to the peer.
478    pub async fn open_bidi(&self) -> io::Result<(u64, BoxedBidiStream)> {
479        self.0.open_bidi().await
480    }
481
482    /// The peer's address.
483    pub fn remote_address(&self) -> SocketAddr {
484        self.0.remote_address()
485    }
486
487    /// Close the entire QUIC connection with an error code and reason.
488    pub fn close(&self, error_code: u64, reason: &[u8]) {
489        self.0.close(error_code, reason)
490    }
491
492    /// Send an unreliable datagram over the QUIC connection.
493    pub fn send_datagram(&self, data: &[u8]) -> io::Result<()> {
494        self.0.send_datagram(data)
495    }
496
497    /// Receive the next unreliable datagram from the peer, passing the raw bytes to `callback`.
498    pub async fn recv_datagram<'a, F: FnOnce(&[u8]) + Send + 'a>(
499        &'a self,
500        callback: F,
501    ) -> io::Result<()> {
502        self.0.recv_datagram(Box::new(callback)).await
503    }
504
505    /// The maximum datagram payload size the peer will accept, if datagrams are supported.
506    pub fn max_datagram_size(&self) -> Option<usize> {
507        self.0.max_datagram_size()
508    }
509}
510
511// -- Type-erased QuicEndpoint --
512
513trait ObjectSafeQuicEndpoint: Send + Sync {
514    fn accept(&self) -> BoxedFuture<'_, Option<QuicConnection>>;
515    fn connect<'a>(
516        &'a self,
517        addr: SocketAddr,
518        server_name: &'a str,
519    ) -> BoxedFuture<'a, io::Result<QuicConnection>>;
520    fn local_addr(&self) -> io::Result<SocketAddr>;
521}
522
523impl<T: QuicEndpoint> ObjectSafeQuicEndpoint for T {
524    fn accept(&self) -> BoxedFuture<'_, Option<QuicConnection>> {
525        Box::pin(async { QuicEndpoint::accept(self).await.map(QuicConnection::from) })
526    }
527
528    fn connect<'a>(
529        &'a self,
530        addr: SocketAddr,
531        server_name: &'a str,
532    ) -> BoxedFuture<'a, io::Result<QuicConnection>> {
533        Box::pin(async move {
534            QuicEndpoint::connect(self, addr, server_name)
535                .await
536                .map(QuicConnection::from)
537        })
538    }
539
540    fn local_addr(&self) -> io::Result<SocketAddr> {
541        QuicEndpoint::local_addr(self)
542    }
543}
544
545/// A type-erased QUIC endpoint, equivalent to `Arc<dyn QuicEndpoint>`.
546/// Cheaply cloneable.
547#[derive(Clone)]
548pub struct ArcedQuicEndpoint(Arc<dyn ObjectSafeQuicEndpoint>);
549
550impl Debug for ArcedQuicEndpoint {
551    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
552        f.debug_tuple("ArcedQuicEndpoint").finish()
553    }
554}
555
556impl<T: QuicEndpoint> From<T> for ArcedQuicEndpoint {
557    fn from(endpoint: T) -> Self {
558        Self(Arc::new(endpoint))
559    }
560}
561
562impl ArcedQuicEndpoint {
563    /// Accept the next inbound QUIC connection.
564    pub async fn accept(&self) -> Option<QuicConnection> {
565        self.0.accept().await
566    }
567
568    /// Initiate a QUIC connection to the given address.
569    pub async fn connect(&self, addr: SocketAddr, server_name: &str) -> io::Result<QuicConnection> {
570        self.0.connect(addr, server_name).await
571    }
572
573    /// The local address this endpoint is bound to, if the adapter supports reporting it.
574    pub fn local_addr(&self) -> io::Result<SocketAddr> {
575        self.0.local_addr()
576    }
577}