Skip to main content

trillium_server_common/
quic.rs

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