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
120impl<S: Server> QuicConfig<S> for () {
121    type Endpoint = ();
122
123    fn bind(self, _: SocketAddr, _: S::Runtime, _: &mut Info) -> Option<io::Result<()>> {
124        None
125    }
126}
127
128/// A bound QUIC endpoint that accepts and initiates connections.
129///
130/// Analogous to [`Server`](crate::Server) for TCP. QUIC library adapters implement this to provide
131/// the connection accept loop (server) and outbound connections (client).
132///
133/// The `()` implementation is a no-op (HTTP/3 disabled). Server-only implementations may return
134/// an error from [`connect`](QuicEndpoint::connect); client-only implementations may return
135/// `None` from [`accept`](QuicEndpoint::accept).
136pub trait QuicEndpoint: Send + Sync + 'static {
137    /// The connection type yielded by this endpoint.
138    type Connection: QuicConnectionTrait;
139
140    /// Accept the next inbound QUIC connection, or return `None` if the endpoint is done.
141    fn accept(&self) -> impl Future<Output = Option<Self::Connection>> + Send;
142
143    /// Initiate a QUIC connection to the given address.
144    ///
145    /// `server_name` is the SNI hostname used for TLS verification.
146    fn connect(
147        &self,
148        addr: SocketAddr,
149        server_name: &str,
150    ) -> impl Future<Output = io::Result<Self::Connection>> + Send;
151}
152
153/// Uninhabited type used by the `()` [`QuicBinding`] implementation.
154///
155/// Since `()` never produces connections, this type is never constructed and its trait
156/// implementations are never exercised.
157#[derive(Debug, Clone, Copy)]
158pub enum NoQuic {}
159
160impl QuicTransportSend for NoQuic {
161    fn reset(&mut self, _code: u64) {
162        match *self {}
163    }
164}
165
166impl QuicTransportReceive for NoQuic {
167    fn stop(&mut self, _code: u64) {
168        match *self {}
169    }
170}
171
172impl QuicTransportBidi for NoQuic {}
173
174impl QuicConnectionTrait for NoQuic {
175    type BidiStream = NoQuic;
176    type RecvStream = NoQuic;
177    type SendStream = NoQuic;
178
179    async fn accept_bidi(&self) -> io::Result<(u64, Self::BidiStream)> {
180        match *self {}
181    }
182
183    async fn accept_uni(&self) -> io::Result<(u64, Self::RecvStream)> {
184        match *self {}
185    }
186
187    async fn open_uni(&self) -> io::Result<(u64, Self::SendStream)> {
188        match *self {}
189    }
190
191    async fn open_bidi(&self) -> io::Result<(u64, Self::BidiStream)> {
192        match *self {}
193    }
194
195    fn remote_address(&self) -> SocketAddr {
196        match *self {}
197    }
198
199    fn close(&self, _: u64, _: &[u8]) {
200        match *self {}
201    }
202
203    fn send_datagram(&self, _: &[u8]) -> io::Result<()> {
204        match *self {}
205    }
206
207    async fn recv_datagram<F: FnOnce(&[u8]) + Send>(&self, _: F) -> io::Result<()> {
208        match *self {}
209    }
210
211    fn max_datagram_size(&self) -> Option<usize> {
212        match *self {}
213    }
214}
215
216impl Transport for NoQuic {}
217
218impl AsyncRead for NoQuic {
219    fn poll_read(
220        self: Pin<&mut Self>,
221        _: &mut Context<'_>,
222        _: &mut [u8],
223    ) -> Poll<io::Result<usize>> {
224        match *self.get_mut() {}
225    }
226}
227
228impl AsyncWrite for NoQuic {
229    fn poll_write(self: Pin<&mut Self>, _: &mut Context<'_>, _: &[u8]) -> Poll<io::Result<usize>> {
230        match *self.get_mut() {}
231    }
232
233    fn poll_flush(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<io::Result<()>> {
234        match *self.get_mut() {}
235    }
236
237    fn poll_close(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<io::Result<()>> {
238        match *self.get_mut() {}
239    }
240}
241
242impl QuicEndpoint for () {
243    type Connection = NoQuic;
244
245    async fn accept(&self) -> Option<NoQuic> {
246        None
247    }
248
249    async fn connect(&self, _: SocketAddr, _: &str) -> io::Result<NoQuic> {
250        Err(io::Error::new(
251            io::ErrorKind::Unsupported,
252            "QUIC not configured",
253        ))
254    }
255}
256
257// -- Type-erased QuicConnection --
258
259type BoxedFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;
260pub(crate) type BoxedRecvStream = Box<dyn QuicTransportReceive + Unpin + Send + Sync>;
261pub(crate) type BoxedSendStream = Box<dyn QuicTransportSend + Unpin + Send + Sync>;
262pub(crate) type BoxedBidiStream = Box<dyn QuicTransportBidi + Unpin + Send + Sync>;
263
264type ReceiveDatagramCallback<'a> = Box<dyn FnOnce(&[u8]) + Send + 'a>;
265
266trait ObjectSafeQuicConnection: Send + Sync {
267    fn accept_bidi(&self) -> BoxedFuture<'_, io::Result<(u64, BoxedBidiStream)>>;
268    fn accept_uni(&self) -> BoxedFuture<'_, io::Result<(u64, BoxedRecvStream)>>;
269    fn open_uni(&self) -> BoxedFuture<'_, io::Result<(u64, BoxedSendStream)>>;
270    fn open_bidi(&self) -> BoxedFuture<'_, io::Result<(u64, BoxedBidiStream)>>;
271    fn remote_address(&self) -> SocketAddr;
272    fn close(&self, error_code: u64, reason: &[u8]);
273    fn send_datagram(&self, data: &[u8]) -> io::Result<()>;
274    fn recv_datagram<'a>(
275        &'a self,
276        callback: ReceiveDatagramCallback<'a>,
277    ) -> BoxedFuture<'a, io::Result<()>>;
278    fn max_datagram_size(&self) -> Option<usize>;
279}
280
281impl<T: QuicConnectionTrait> ObjectSafeQuicConnection for T {
282    fn accept_bidi(&self) -> BoxedFuture<'_, io::Result<(u64, BoxedBidiStream)>> {
283        Box::pin(async {
284            let (id, stream) = QuicConnectionTrait::accept_bidi(self).await?;
285            Ok((id, Box::new(stream) as BoxedBidiStream))
286        })
287    }
288
289    fn accept_uni(&self) -> BoxedFuture<'_, io::Result<(u64, BoxedRecvStream)>> {
290        Box::pin(async {
291            let (id, stream) = QuicConnectionTrait::accept_uni(self).await?;
292            Ok((id, Box::new(stream) as BoxedRecvStream))
293        })
294    }
295
296    fn open_uni(&self) -> BoxedFuture<'_, io::Result<(u64, BoxedSendStream)>> {
297        Box::pin(async {
298            let (id, stream) = QuicConnectionTrait::open_uni(self).await?;
299            Ok((id, Box::new(stream) as BoxedSendStream))
300        })
301    }
302
303    fn open_bidi(&self) -> BoxedFuture<'_, io::Result<(u64, BoxedBidiStream)>> {
304        Box::pin(async {
305            let (id, stream) = QuicConnectionTrait::open_bidi(self).await?;
306            Ok((id, Box::new(stream) as BoxedBidiStream))
307        })
308    }
309
310    fn remote_address(&self) -> SocketAddr {
311        QuicConnectionTrait::remote_address(self)
312    }
313
314    fn close(&self, error_code: u64, reason: &[u8]) {
315        QuicConnectionTrait::close(self, error_code, reason)
316    }
317
318    fn send_datagram(&self, data: &[u8]) -> io::Result<()> {
319        QuicConnectionTrait::send_datagram(self, data)
320    }
321
322    fn recv_datagram<'a>(
323        &'a self,
324        callback: Box<dyn FnOnce(&[u8]) + Send + 'a>,
325    ) -> BoxedFuture<'a, io::Result<()>> {
326        Box::pin(QuicConnectionTrait::recv_datagram(self, callback))
327    }
328
329    fn max_datagram_size(&self) -> Option<usize> {
330        QuicConnectionTrait::max_datagram_size(self)
331    }
332}
333
334/// A type-erased QUIC connection handle, equivalent to `Arc<dyn QuicConnectionTrait>`.
335/// Cheaply cloneable.
336///
337/// Handlers retrieve this from conn state to access QUIC features (streams, datagrams)
338/// without depending on the concrete QUIC implementation type.
339#[derive(Clone)]
340pub struct QuicConnection(Arc<dyn ObjectSafeQuicConnection>);
341
342impl Debug for QuicConnection {
343    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
344        f.debug_struct("QuicConnection")
345            .field("peer", &self.remote_address())
346            .finish_non_exhaustive()
347    }
348}
349
350impl<T: QuicConnectionTrait> From<T> for QuicConnection {
351    fn from(connection: T) -> Self {
352        Self(Arc::new(connection))
353    }
354}
355
356impl QuicConnection {
357    /// Accept the next bidirectional stream opened by the peer.
358    pub async fn accept_bidi(&self) -> io::Result<(u64, BoxedBidiStream)> {
359        self.0.accept_bidi().await
360    }
361
362    /// Accept the next unidirectional stream opened by the peer.
363    pub async fn accept_uni(&self) -> io::Result<(u64, BoxedRecvStream)> {
364        self.0.accept_uni().await
365    }
366
367    /// Open a new unidirectional stream to the peer.
368    pub async fn open_uni(&self) -> io::Result<(u64, BoxedSendStream)> {
369        self.0.open_uni().await
370    }
371
372    /// Open a new bidirectional stream to the peer.
373    pub async fn open_bidi(&self) -> io::Result<(u64, BoxedBidiStream)> {
374        self.0.open_bidi().await
375    }
376
377    /// The peer's address.
378    pub fn remote_address(&self) -> SocketAddr {
379        self.0.remote_address()
380    }
381
382    /// Close the entire QUIC connection with an error code and reason.
383    pub fn close(&self, error_code: u64, reason: &[u8]) {
384        self.0.close(error_code, reason)
385    }
386
387    /// Send an unreliable datagram over the QUIC connection.
388    pub fn send_datagram(&self, data: &[u8]) -> io::Result<()> {
389        self.0.send_datagram(data)
390    }
391
392    /// Receive the next unreliable datagram from the peer, passing the raw bytes to `callback`.
393    pub async fn recv_datagram<'a, F: FnOnce(&[u8]) + Send + 'a>(
394        &'a self,
395        callback: F,
396    ) -> io::Result<()> {
397        self.0.recv_datagram(Box::new(callback)).await
398    }
399
400    /// The maximum datagram payload size the peer will accept, if datagrams are supported.
401    pub fn max_datagram_size(&self) -> Option<usize> {
402        self.0.max_datagram_size()
403    }
404}
405
406// -- Type-erased QuicEndpoint --
407
408trait ObjectSafeQuicEndpoint: Send + Sync {
409    fn accept(&self) -> BoxedFuture<'_, Option<QuicConnection>>;
410    fn connect<'a>(
411        &'a self,
412        addr: SocketAddr,
413        server_name: &'a str,
414    ) -> BoxedFuture<'a, io::Result<QuicConnection>>;
415}
416
417impl<T: QuicEndpoint> ObjectSafeQuicEndpoint for T {
418    fn accept(&self) -> BoxedFuture<'_, Option<QuicConnection>> {
419        Box::pin(async { QuicEndpoint::accept(self).await.map(QuicConnection::from) })
420    }
421
422    fn connect<'a>(
423        &'a self,
424        addr: SocketAddr,
425        server_name: &'a str,
426    ) -> BoxedFuture<'a, io::Result<QuicConnection>> {
427        Box::pin(async move {
428            QuicEndpoint::connect(self, addr, server_name)
429                .await
430                .map(QuicConnection::from)
431        })
432    }
433}
434
435/// A type-erased QUIC endpoint, equivalent to `Arc<dyn QuicEndpoint>`.
436/// Cheaply cloneable.
437#[derive(Clone)]
438pub struct ArcedQuicEndpoint(Arc<dyn ObjectSafeQuicEndpoint>);
439
440impl Debug for ArcedQuicEndpoint {
441    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
442        f.debug_tuple("ArcedQuicEndpoint").finish()
443    }
444}
445
446impl<T: QuicEndpoint> From<T> for ArcedQuicEndpoint {
447    fn from(endpoint: T) -> Self {
448        Self(Arc::new(endpoint))
449    }
450}
451
452impl ArcedQuicEndpoint {
453    /// Accept the next inbound QUIC connection.
454    pub async fn accept(&self) -> Option<QuicConnection> {
455        self.0.accept().await
456    }
457
458    /// Initiate a QUIC connection to the given address.
459    pub async fn connect(&self, addr: SocketAddr, server_name: &str) -> io::Result<QuicConnection> {
460        self.0.connect(addr, server_name).await
461    }
462}