Skip to main content

tokio_quiche/quic/connection/
mod.rs

1// Copyright (C) 2025, Cloudflare, Inc.
2// All rights reserved.
3//
4// Redistribution and use in source and binary forms, with or without
5// modification, are permitted provided that the following conditions are
6// met:
7//
8//     * Redistributions of source code must retain the above copyright notice,
9//       this list of conditions and the following disclaimer.
10//
11//     * Redistributions in binary form must reproduce the above copyright
12//       notice, this list of conditions and the following disclaimer in the
13//       documentation and/or other materials provided with the distribution.
14//
15// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
16// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
17// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
18// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
19// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
20// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
22// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
23// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
24// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26
27mod error;
28mod id;
29mod map;
30
31pub use self::error::HandshakeError;
32pub use self::id::ConnectionIdGenerator;
33pub use self::id::SharedConnectionIdGenerator;
34pub use self::id::SimpleConnectionIdGenerator;
35pub(crate) use self::map::ConnectionMap;
36
37use boring::ssl::SslRef;
38use datagram_socket::AsSocketStats;
39use datagram_socket::DatagramSocketSend;
40use datagram_socket::MaybeConnectedSocket;
41use datagram_socket::QuicAuditStats;
42use datagram_socket::ShutdownConnection;
43use datagram_socket::SocketStats;
44use foundations::telemetry::log;
45use futures::Future;
46use quiche::ConnectionId;
47use std::fmt;
48use std::io;
49use std::net::SocketAddr;
50use std::sync::Arc;
51use std::sync::Mutex;
52use std::task::Poll;
53use std::time::Duration;
54use std::time::Instant;
55use std::time::SystemTime;
56use tokio::sync::mpsc;
57use tokio_util::task::AbortOnDropHandle;
58
59use self::error::make_handshake_result;
60use super::hooks::ConnectionHook;
61use super::io::connection_stage::Close;
62use super::io::connection_stage::ConnectionStageContext;
63use super::io::connection_stage::Handshake;
64use super::io::connection_stage::RunningApplication;
65use super::io::worker::Closing;
66use super::io::worker::IoWorkerParams;
67use super::io::worker::Running;
68use super::io::worker::RunningOrClosing;
69use super::io::worker::WriteState;
70use super::DscpHandle;
71use super::QuicheConnection;
72use crate::metrics::Metrics;
73use crate::quic::io::worker::IoWorker;
74use crate::quic::io::worker::WriterConfig;
75use crate::quic::io::worker::INCOMING_QUEUE_SIZE;
76use crate::quic::router::ConnectionMapCommand;
77use crate::QuicResult;
78
79/// Wrapper for connection statistics recorded by [quiche].
80#[derive(Debug)]
81pub struct QuicConnectionStats {
82    /// Aggregate connection statistics across all paths.
83    pub stats: quiche::Stats,
84    /// Specific statistics about the connection's active path.
85    pub path_stats: Option<quiche::PathStats>,
86}
87pub(crate) type QuicConnectionStatsShared = Arc<Mutex<QuicConnectionStats>>;
88
89impl QuicConnectionStats {
90    pub(crate) fn from_conn(qconn: &QuicheConnection) -> Self {
91        Self {
92            stats: qconn.stats(),
93            path_stats: qconn.path_stats().find(|stats| stats.active),
94        }
95    }
96
97    fn startup_exit_to_socket_stats(
98        value: quiche::StartupExit,
99    ) -> datagram_socket::StartupExit {
100        let reason = match value.reason {
101            quiche::StartupExitReason::Loss =>
102                datagram_socket::StartupExitReason::Loss,
103            quiche::StartupExitReason::BandwidthPlateau =>
104                datagram_socket::StartupExitReason::BandwidthPlateau,
105            quiche::StartupExitReason::PersistentQueue =>
106                datagram_socket::StartupExitReason::PersistentQueue,
107            quiche::StartupExitReason::ConservativeSlowStartRounds =>
108                datagram_socket::StartupExitReason::ConservativeSlowStartRounds,
109        };
110
111        datagram_socket::StartupExit {
112            cwnd: value.cwnd,
113            bandwidth: value.bandwidth,
114            reason,
115        }
116    }
117}
118
119impl AsSocketStats for QuicConnectionStats {
120    fn as_socket_stats(&self) -> SocketStats {
121        SocketStats {
122            pmtu: self
123                .path_stats
124                .as_ref()
125                .map(|p| p.pmtu as u16)
126                .unwrap_or_default(),
127            rtt_us: self
128                .path_stats
129                .as_ref()
130                .map(|p| p.rtt.as_micros() as i64)
131                .unwrap_or_default(),
132            min_rtt_us: self
133                .path_stats
134                .as_ref()
135                .and_then(|p| p.min_rtt.map(|x| x.as_micros() as i64))
136                .unwrap_or_default(),
137            max_rtt_us: self
138                .path_stats
139                .as_ref()
140                .and_then(|p| p.max_rtt.map(|x| x.as_micros() as i64))
141                .unwrap_or_default(),
142            rtt_var_us: self
143                .path_stats
144                .as_ref()
145                .map(|p| p.rttvar.as_micros() as i64)
146                .unwrap_or_default(),
147            cwnd: self
148                .path_stats
149                .as_ref()
150                .map(|p| p.cwnd as u64)
151                .unwrap_or_default(),
152            total_pto_count: self
153                .path_stats
154                .as_ref()
155                .map(|p| p.total_pto_count as u64)
156                .unwrap_or_default(),
157            packets_sent: self.stats.sent as u64,
158            packets_recvd: self.stats.recv as u64,
159            packets_lost: self.stats.lost as u64,
160            packets_lost_spurious: self.stats.spurious_lost as u64,
161            packets_retrans: self.stats.retrans as u64,
162            bytes_sent: self.stats.sent_bytes,
163            bytes_recvd: self.stats.recv_bytes,
164            bytes_lost: self.stats.lost_bytes,
165            bytes_retrans: self.stats.stream_retrans_bytes,
166            bytes_unsent: 0, /* not implemented yet, kept for compatibility
167                              * with TCP */
168            delivery_rate: self
169                .path_stats
170                .as_ref()
171                .map(|p| p.delivery_rate)
172                .unwrap_or_default(),
173            max_bandwidth: self.path_stats.as_ref().and_then(|p| p.max_bandwidth),
174            startup_exit: self
175                .path_stats
176                .as_ref()
177                .and_then(|p| p.startup_exit)
178                .map(QuicConnectionStats::startup_exit_to_socket_stats),
179            bytes_in_flight_duration_us: self
180                .stats
181                .bytes_in_flight_duration
182                .as_micros() as u64,
183        }
184    }
185}
186
187/// A received network packet with additional metadata.
188#[derive(Debug)]
189pub struct Incoming {
190    /// The address that sent the inbound packet.
191    pub peer_addr: SocketAddr,
192    /// The address on which we received the inbound packet.
193    pub local_addr: SocketAddr,
194    /// The receive timestamp of the packet.
195    ///
196    /// Used for the `perf-quic-listener-metrics` feature.
197    pub rx_time: Option<SystemTime>,
198    /// The packet's contents.
199    pub buf: Vec<u8>,
200    /// If set, then `buf` is a GRO buffer containing multiple packets.
201    /// Each individual packet has a size of `gso` (except for the last one).
202    pub gro: Option<i32>,
203    /// [SO_MARK] control message value received from the socket.
204    ///
205    /// This will always be `None` after the connection has been spawned as
206    /// the message is `take()`d before spawning.
207    ///
208    /// [SO_MARK]: https://man7.org/linux/man-pages/man7/socket.7.html
209    #[cfg(target_os = "linux")]
210    pub so_mark_data: Option<[u8; 4]>,
211}
212
213/// A QUIC connection that has not performed a handshake yet.
214///
215/// This type is currently only used for server-side connections. It is created
216/// and added to the listener's connection stream after an initial packet from
217/// a client has been received and (optionally) the client's IP address has been
218/// validated.
219///
220/// To turn the initial connection into a fully established one, a QUIC
221/// handshake must be performed. Users have multiple options to facilitate this:
222/// - `start` is a simple entrypoint which spawns a task to handle the entire
223///   lifetime of the QUIC connection. The caller can then only communicate with
224///   the connection via their [`ApplicationOverQuic`].
225/// - `handshake` spawns a task for the handshake and awaits its completion.
226///   Afterwards, it pauses the connection and allows the caller to resume it
227///   later via an opaque struct. We spawn a separate task to allow the tokio
228///   scheduler free choice in where to run the handshake.
229/// - `handshake_fut` returns a future to drive the handshake for maximum
230///   flexibility.
231#[must_use = "call InitialQuicConnection::start to establish the connection"]
232pub struct InitialQuicConnection<Tx, M>
233where
234    Tx: DatagramSocketSend + Send + 'static + ?Sized,
235    M: Metrics,
236{
237    params: QuicConnectionParams<Tx, M>,
238    pub(crate) audit_log_stats: Arc<QuicAuditStats>,
239    stats: QuicConnectionStatsShared,
240    pub(crate) incoming_ev_sender: mpsc::Sender<Incoming>,
241    incoming_ev_receiver: mpsc::Receiver<Incoming>,
242}
243
244impl<Tx, M> InitialQuicConnection<Tx, M>
245where
246    Tx: DatagramSocketSend + Send + 'static + ?Sized,
247    M: Metrics,
248{
249    #[inline]
250    pub(crate) fn new(params: QuicConnectionParams<Tx, M>) -> Self {
251        let (incoming_ev_sender, incoming_ev_receiver) =
252            mpsc::channel(INCOMING_QUEUE_SIZE);
253        let audit_log_stats = Arc::new(QuicAuditStats::new(params.scid.to_vec()));
254
255        let stats = Arc::new(Mutex::new(QuicConnectionStats::from_conn(
256            &params.quiche_conn,
257        )));
258
259        Self {
260            params,
261            audit_log_stats,
262            stats,
263            incoming_ev_sender,
264            incoming_ev_receiver,
265        }
266    }
267
268    /// The local address this connection listens on.
269    pub fn local_addr(&self) -> SocketAddr {
270        self.params.local_addr
271    }
272
273    /// The remote address for this connection.
274    pub fn peer_addr(&self) -> SocketAddr {
275        self.params.peer_addr
276    }
277
278    /// [boring]'s SSL object for this connection.
279    #[doc(hidden)]
280    pub fn ssl_mut(&mut self) -> &mut SslRef {
281        // Deref to pick `Connection::as_mut` over `Box::as_mut`.
282        (*self.params.quiche_conn).as_mut()
283    }
284
285    /// Returns the outgoing DSCP handle when server marking is configured.
286    ///
287    /// Clone it before starting the handshake to update the worker's marking
288    /// from a certificate-selection callback. Until then the handle has no
289    /// per-packet override, so packets inherit the UDP socket's TOS/TClass.
290    pub fn dscp_handle(&self) -> Option<&DscpHandle> {
291        self.params.dscp_handle.as_ref()
292    }
293
294    /// A handle to the [`QuicAuditStats`] for this connection.
295    ///
296    /// # Note
297    /// These stats are updated during the lifetime of the connection.
298    /// The getter exists to grab a handle early on, which can then
299    /// be stowed away and read out after the connection has closed.
300    #[inline]
301    pub fn audit_log_stats(&self) -> Arc<QuicAuditStats> {
302        Arc::clone(&self.audit_log_stats)
303    }
304
305    /// A handle to the [`QuicConnectionStats`] for this connection.
306    ///
307    /// # Note
308    /// Initially, these stats represent the state when the [quiche::Connection]
309    /// was created. They are updated when the connection is closed, so this
310    /// getter exists primarily to grab a handle early on.
311    #[inline]
312    pub fn stats(&self) -> &QuicConnectionStatsShared {
313        &self.stats
314    }
315
316    /// Creates a future to drive the connection's handshake.
317    ///
318    /// This is a lower-level alternative to the `handshake` function which
319    /// gives the caller more control over execution of the future. See
320    /// `handshake` for details on the return values.
321    pub fn handshake_fut<A: ApplicationOverQuic>(
322        self, app: A,
323    ) -> (
324        QuicConnection,
325        impl Future<Output = io::Result<Running<Arc<Tx>, M, A>>> + Send + 'static,
326    ) {
327        self.params.metrics.connections_in_memory().inc();
328
329        let conn = QuicConnection {
330            local_addr: self.params.local_addr,
331            peer_addr: self.params.peer_addr,
332            audit_log_stats: Arc::clone(&self.audit_log_stats),
333            stats: Arc::clone(&self.stats),
334            scid: self.params.scid,
335        };
336        let context = ConnectionStageContext {
337            in_pkt: self.params.initial_pkt,
338            incoming_pkt_receiver: self.incoming_ev_receiver,
339            application: app,
340            stats: Arc::clone(&self.stats),
341            connection_hook: self.params.connection_hook,
342        };
343        let conn_stage = Handshake;
344        let params = IoWorkerParams {
345            socket: MaybeConnectedSocket::new(self.params.socket),
346            shutdown_tx: self.params.shutdown_tx,
347            cfg: self.params.writer_cfg,
348            audit_log_stats: self.audit_log_stats,
349            write_state: WriteState::default(),
350            dscp_handle: self.params.dscp_handle,
351            conn_map_cmd_tx: self.params.conn_map_cmd_tx,
352            cid_generator: self.params.cid_generator,
353            #[cfg(feature = "perf-quic-listener-metrics")]
354            init_rx_time: self.params.init_rx_time,
355            metrics: self.params.metrics.clone(),
356            handshake_info: self.params.handshake_info,
357        };
358
359        let handshake_fut = async move {
360            let qconn = self.params.quiche_conn;
361            let handshake_done =
362                IoWorker::new(params, conn_stage).run(qconn, context).await;
363
364            match handshake_done {
365                RunningOrClosing::Running(r) => Ok(r),
366                RunningOrClosing::Closing(Closing {
367                    params,
368                    work_loop_result,
369                    mut context,
370                    mut qconn,
371                }) => {
372                    let hs_result = make_handshake_result(&work_loop_result);
373                    IoWorker::new(params, Close { work_loop_result })
374                        .close(&mut qconn, &mut context)
375                        .await;
376                    hs_result
377                },
378            }
379        };
380
381        (conn, handshake_fut)
382    }
383
384    /// Performs the QUIC handshake in a separate tokio task and awaits its
385    /// completion.
386    ///
387    /// The returned [`QuicConnection`] holds metadata about the established
388    /// connection. The connection itself is paused after `handshake`
389    /// returns and must be resumed by passing the opaque `Running` value to
390    /// [`InitialQuicConnection::resume`]. This two-step process
391    /// allows callers to collect telemetry and run code before serving their
392    /// [`ApplicationOverQuic`].
393    pub async fn handshake<A: ApplicationOverQuic>(
394        self, app: A,
395    ) -> io::Result<(QuicConnection, Running<Arc<Tx>, M, A>)> {
396        let task_metrics = self.params.metrics.clone();
397        let (conn, handshake_fut) = Self::handshake_fut(self, app);
398
399        let handshake_handle = crate::metrics::tokio_task::spawn(
400            "quic_handshake_worker",
401            task_metrics,
402            handshake_fut,
403        );
404
405        // `AbortOnDropHandle` simulates task-killswitch behavior without
406        // needing to give up ownership of the `JoinHandle`.
407        let handshake_abort_handle = AbortOnDropHandle::new(handshake_handle);
408
409        let worker = handshake_abort_handle.await??;
410
411        Ok((conn, worker))
412    }
413
414    /// Resumes a QUIC connection which was paused after a successful handshake.
415    pub fn resume<A: ApplicationOverQuic>(pre_running: Running<Arc<Tx>, M, A>) {
416        let task_metrics = pre_running.params.metrics.clone();
417        let fut = async move {
418            let Running {
419                params,
420                context,
421                qconn,
422            } = pre_running;
423            let running_worker = IoWorker::new(params, RunningApplication);
424
425            let Closing {
426                params,
427                mut context,
428                work_loop_result,
429                mut qconn,
430            } = running_worker.run(qconn, context).await;
431
432            IoWorker::new(params, Close { work_loop_result })
433                .close(&mut qconn, &mut context)
434                .await;
435        };
436
437        crate::metrics::tokio_task::spawn_with_killswitch(
438            "quic_io_worker",
439            task_metrics,
440            fut,
441        );
442    }
443
444    /// Drives a QUIC connection from handshake to close in separate tokio
445    /// tasks.
446    ///
447    /// It combines [`InitialQuicConnection::handshake`] and
448    /// [`InitialQuicConnection::resume`] into a single call.
449    pub fn start<A: ApplicationOverQuic>(self, app: A) -> QuicConnection {
450        let task_metrics = self.params.metrics.clone();
451        let (conn, handshake_fut) = Self::handshake_fut(self, app);
452        // Pin to the heap so the spawned task only carries a pointer to it
453        // instead of inlining the full future state across the await.
454        let handshake_fut = Box::pin(handshake_fut);
455
456        let fut = async move {
457            match handshake_fut.await {
458                Ok(running) => Self::resume(running),
459                Err(e) => {
460                    log::error!("QUIC handshake failed in IQC::start"; "error" => e);
461                },
462            }
463        };
464
465        crate::metrics::tokio_task::spawn_with_killswitch(
466            "quic_handshake_worker",
467            task_metrics,
468            fut,
469        );
470
471        conn
472    }
473}
474
475pub(crate) struct QuicConnectionParams<Tx, M>
476where
477    Tx: DatagramSocketSend + Send + 'static + ?Sized,
478    M: Metrics,
479{
480    pub writer_cfg: WriterConfig,
481    pub initial_pkt: Option<Incoming>,
482    pub dscp_handle: Option<DscpHandle>,
483    pub shutdown_tx: mpsc::Sender<()>,
484    pub conn_map_cmd_tx: mpsc::UnboundedSender<ConnectionMapCommand>, /* channel that signals connection map changes */
485    pub scid: ConnectionId<'static>,
486    pub cid_generator: Option<SharedConnectionIdGenerator>,
487    pub metrics: M,
488    pub connection_hook: Option<Arc<dyn ConnectionHook + Send + Sync + 'static>>,
489    #[cfg(feature = "perf-quic-listener-metrics")]
490    pub init_rx_time: Option<SystemTime>,
491    pub handshake_info: HandshakeInfo,
492    /// Boxed because this value is moved by-value through several nested
493    /// async state machines. Inlining a [`QuicheConnection`] here would
494    /// duplicate its payload across the future state slots that hold it
495    /// across an `.await`.
496    pub quiche_conn: Box<QuicheConnection>,
497    pub socket: Arc<Tx>,
498    pub local_addr: SocketAddr,
499    pub peer_addr: SocketAddr,
500}
501
502/// Metadata about an established QUIC connection.
503///
504/// While this struct allows access to some facets of a QUIC connection, it
505/// notably does not represent the [quiche::Connection] itself. The crate
506/// handles most interactions with [quiche] internally in a worker task. Users
507/// can only access the connection directly via their [`ApplicationOverQuic`]
508/// implementation.
509///
510/// See the [module-level docs](crate::quic) for an overview of how a QUIC
511/// connection is handled internally.
512pub struct QuicConnection {
513    local_addr: SocketAddr,
514    peer_addr: SocketAddr,
515    audit_log_stats: Arc<QuicAuditStats>,
516    stats: QuicConnectionStatsShared,
517    scid: ConnectionId<'static>,
518}
519
520impl QuicConnection {
521    /// The local address this connection listens on.
522    #[inline]
523    pub fn local_addr(&self) -> SocketAddr {
524        self.local_addr
525    }
526
527    /// The remote address for this connection.
528    #[inline]
529    pub fn peer_addr(&self) -> SocketAddr {
530        self.peer_addr
531    }
532
533    /// A handle to the [`QuicAuditStats`] for this connection.
534    ///
535    /// # Note
536    /// These stats are updated during the lifetime of the connection.
537    /// The getter exists to grab a handle early on, which can then
538    /// be stowed away and read out after the connection has closed.
539    #[inline]
540    pub fn audit_log_stats(&self) -> &Arc<QuicAuditStats> {
541        &self.audit_log_stats
542    }
543
544    /// A handle to the [`QuicConnectionStats`] for this connection.
545    ///
546    /// # Note
547    /// Initially, these stats represent the state when the [quiche::Connection]
548    /// was created. They are updated when the connection is closed, so this
549    /// getter exists primarily to grab a handle early on.
550    #[inline]
551    pub fn stats(&self) -> &QuicConnectionStatsShared {
552        &self.stats
553    }
554
555    /// The QUIC source connection ID used by this connection.
556    #[inline]
557    pub fn scid(&self) -> &ConnectionId<'static> {
558        &self.scid
559    }
560}
561
562impl AsSocketStats for QuicConnection {
563    #[inline]
564    fn as_socket_stats(&self) -> SocketStats {
565        // It is important to note that those stats are only updated when
566        // the connection stops, which is fine, since this is only used to
567        // log after the connection is finished.
568        self.stats.lock().unwrap().as_socket_stats()
569    }
570
571    #[inline]
572    fn as_quic_stats(&self) -> Option<&Arc<QuicAuditStats>> {
573        Some(&self.audit_log_stats)
574    }
575}
576
577impl<Tx, M> AsSocketStats for InitialQuicConnection<Tx, M>
578where
579    Tx: DatagramSocketSend + Send + 'static + ?Sized,
580    M: Metrics,
581{
582    #[inline]
583    fn as_socket_stats(&self) -> SocketStats {
584        // It is important to note that those stats are only updated when
585        // the connection stops, which is fine, since this is only used to
586        // log after the connection is finished.
587        self.stats.lock().unwrap().as_socket_stats()
588    }
589
590    #[inline]
591    fn as_quic_stats(&self) -> Option<&Arc<QuicAuditStats>> {
592        Some(&self.audit_log_stats)
593    }
594}
595
596impl<Tx, M> ShutdownConnection for InitialQuicConnection<Tx, M>
597where
598    Tx: DatagramSocketSend + Send + 'static + ?Sized,
599    M: Metrics,
600{
601    #[inline]
602    fn poll_shutdown(
603        &mut self, _cx: &mut std::task::Context,
604    ) -> std::task::Poll<io::Result<()>> {
605        // TODO: Does nothing at the moment. We always call Self::start
606        // anyway so it's not really important at this moment.
607        Poll::Ready(Ok(()))
608    }
609}
610
611impl ShutdownConnection for QuicConnection {
612    #[inline]
613    fn poll_shutdown(
614        &mut self, _cx: &mut std::task::Context,
615    ) -> std::task::Poll<io::Result<()>> {
616        // TODO: does nothing at the moment
617        Poll::Ready(Ok(()))
618    }
619}
620
621/// Details about a connection's QUIC handshake.
622#[derive(Debug, Clone)]
623pub struct HandshakeInfo {
624    /// The time at which the connection was created.
625    start_time: Instant,
626    /// The timeout before which the handshake must complete.
627    timeout: Option<Duration>,
628    /// The real duration that the handshake took to complete.
629    time_handshake: Option<Duration>,
630}
631
632impl HandshakeInfo {
633    pub(crate) fn new(start_time: Instant, timeout: Option<Duration>) -> Self {
634        Self {
635            start_time,
636            timeout,
637            time_handshake: None,
638        }
639    }
640
641    /// The time at which the connection was created.
642    #[inline]
643    pub fn start_time(&self) -> Instant {
644        self.start_time
645    }
646
647    /// How long the handshake took to complete.
648    #[inline]
649    pub fn elapsed(&self) -> Duration {
650        self.time_handshake.unwrap_or_default()
651    }
652
653    pub(crate) fn set_elapsed(&mut self) {
654        let elapsed = self.start_time.elapsed();
655        self.time_handshake = Some(elapsed)
656    }
657
658    pub(crate) fn deadline(&self) -> Option<Instant> {
659        self.timeout.map(|timeout| self.start_time + timeout)
660    }
661
662    pub(crate) fn is_expired(&self) -> bool {
663        self.timeout
664            .is_some_and(|timeout| self.start_time.elapsed() >= timeout)
665    }
666}
667
668/// A trait to implement an application served over QUIC.
669///
670/// The application is driven by an internal worker task, which also handles I/O
671/// for the connection. The worker feeds inbound packets into the
672/// [quiche::Connection], calls [`ApplicationOverQuic::process_reads`] followed
673/// by [`ApplicationOverQuic::process_writes`], and then flushes any pending
674/// outbound packets to the network. This repeats in a loop until either the
675/// connection is closed or the [`ApplicationOverQuic`] returns an error.
676///
677/// In between loop iterations, the worker yields until a new packet arrives, a
678/// timer expires, or [`ApplicationOverQuic::wait_for_data`] resolves.
679/// Implementors can interact with the underlying connection via the mutable
680/// reference passed to trait methods.
681#[allow(unused_variables)] // for default functions
682pub trait ApplicationOverQuic: Send + 'static {
683    /// Callback to customize the [`ApplicationOverQuic`] after the QUIC
684    /// handshake completed successfully.
685    ///
686    /// # Errors
687    /// Returning an error from this method immediately stops the worker loop
688    /// and transitions to the connection closing stage.
689    fn on_conn_established(
690        &mut self, qconn: &mut QuicheConnection, handshake_info: &HandshakeInfo,
691    ) -> QuicResult<()>;
692
693    /// Determines whether the application's methods will be called by the
694    /// worker.
695    ///
696    /// The function is checked in each iteration of the worker loop. Only
697    /// `on_conn_established()` bypasses this check.
698    fn should_act(&self) -> bool;
699
700    /// Waits for an event to trigger the next iteration of the worker loop.
701    ///
702    /// The returned future is awaited in parallel to inbound packets and the
703    /// connection's timers. Any one of those futures resolving triggers the
704    /// next loop iteration, so implementations should not rely on
705    /// `wait_for_data` for the bulk of their processing. Instead, after
706    /// `wait_for_data` resolves, `process_writes` should be used to pull all
707    /// available data out of the event source (for example, a channel).
708    ///
709    /// As for any future, it is **very important** that this method does not
710    /// block the runtime. If it does, the other concurrent futures will be
711    /// starved.
712    ///
713    /// # Cancel safety
714    /// This method MUST be cancel safe.
715    /// It gets called inside select! and could be (repeatedly) cancelled
716    ///
717    /// # Errors
718    /// Returning an error from this method immediately stops the worker loop
719    /// and transitions to the connection closing stage.
720    fn wait_for_data(
721        &mut self, qconn: &mut QuicheConnection,
722    ) -> impl Future<Output = QuicResult<()>> + Send;
723
724    /// Processes data received on the connection.
725    ///
726    /// This method is only called if `should_act()` returns `true` and any
727    /// packets were received since the last worker loop iteration. It
728    /// should be used to read from the connection's open streams.
729    ///
730    /// # Errors
731    /// Returning an error from this method immediately stops the worker loop
732    /// and transitions to the connection closing stage.
733    fn process_reads(&mut self, qconn: &mut QuicheConnection) -> QuicResult<()>;
734
735    /// Adds data to be sent on the connection.
736    ///
737    /// Unlike `process_reads`, this method is called on every iteration of the
738    /// worker loop (provided `should_act()` returns true). It is called
739    /// after `process_reads` and immediately before packets are pushed to
740    /// the socket. The main use case is providing already-buffered data to
741    /// the [quiche::Connection].
742    ///
743    /// # Errors
744    /// Returning an error from this method immediately stops the worker loop
745    /// and transitions to the connection closing stage.
746    fn process_writes(&mut self, qconn: &mut QuicheConnection) -> QuicResult<()>;
747
748    /// Callback to inspect the result of the worker task, before a final packet
749    /// with a `CONNECTION_CLOSE` frame is flushed to the network.
750    ///
751    /// `connection_result` is [`Ok`] only if the connection was closed without
752    /// any local error. Otherwise, the state of `qconn` depends on the
753    /// error type and application behavior.
754    fn on_conn_close<M: Metrics>(
755        &mut self, qconn: &mut QuicheConnection, metrics: &M,
756        connection_result: &QuicResult<()>,
757    ) {
758    }
759}
760
761/// A command to execute on a [quiche::Connection] in the context of an
762/// [`ApplicationOverQuic`].
763///
764/// We expect most [`ApplicationOverQuic`] implementations (such as
765/// [H3Driver](crate::http3::driver::H3Driver)) will provide some way to submit
766/// actions for them to take, for example via a channel. This enum may be
767/// accepted as part of those actions to inspect or alter the state of the
768/// underlying connection.
769pub enum QuicCommand {
770    /// Close the connection with the given parameters.
771    ///
772    /// Some packets may still be sent after this command has been executed, so
773    /// the worker task may continue running for a bit. See
774    /// [`quiche::Connection::close`] for details.
775    ConnectionClose(ConnectionShutdownBehaviour),
776    /// Execute a custom callback on the connection.
777    Custom(Box<dyn FnOnce(&mut QuicheConnection) + Send + 'static>),
778    /// Collect the current [`SocketStats`] from the connection.
779    ///
780    /// Unlike [`QuicConnection::stats()`], these statistics are not cached and
781    /// instead are retrieved right before the command is executed.
782    Stats(Box<dyn FnOnce(datagram_socket::SocketStats) + Send + 'static>),
783    /// Collect the current [`QuicConnectionStats`] from the connection.
784    ///
785    /// These statistics are not cached and instead are retrieved right before
786    /// the command is executed.
787    ConnectionStats(Box<dyn FnOnce(QuicConnectionStats) + Send + 'static>),
788}
789
790impl QuicCommand {
791    /// Consume the command and perform its operation on `qconn`.
792    ///
793    /// This method should be called by [`ApplicationOverQuic`] implementations
794    /// when they receive a [`QuicCommand`] to execute.
795    pub fn execute(self, qconn: &mut QuicheConnection) {
796        match self {
797            Self::ConnectionClose(behavior) => {
798                let ConnectionShutdownBehaviour {
799                    send_application_close,
800                    error_code,
801                    reason,
802                } = behavior;
803
804                let _ = qconn.close(send_application_close, error_code, &reason);
805            },
806            Self::Custom(f) => {
807                (f)(qconn);
808            },
809            Self::Stats(callback) => {
810                let stats_pair = QuicConnectionStats::from_conn(qconn);
811                (callback)(stats_pair.as_socket_stats());
812            },
813            Self::ConnectionStats(callback) => {
814                let stats_pair = QuicConnectionStats::from_conn(qconn);
815                (callback)(stats_pair);
816            },
817        }
818    }
819}
820
821impl fmt::Debug for QuicCommand {
822    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
823        match self {
824            Self::ConnectionClose(b) =>
825                f.debug_tuple("ConnectionClose").field(b).finish(),
826            Self::Custom(_) => f.debug_tuple("Custom").finish_non_exhaustive(),
827            Self::Stats(_) => f.debug_tuple("Stats").finish_non_exhaustive(),
828            Self::ConnectionStats(_) =>
829                f.debug_tuple("ConnectionStats").finish_non_exhaustive(),
830        }
831    }
832}
833
834/// Parameters to close a [quiche::Connection].
835///
836/// The connection will use these parameters for the `CONNECTION_CLOSE` frame
837/// it sends to its peer.
838#[derive(Debug, Clone)]
839pub struct ConnectionShutdownBehaviour {
840    /// Whether to send an application close or a regular close to the peer.
841    ///
842    /// If this is true but the connection is not in a state where it is safe to
843    /// send an application error (not established nor in early data), in
844    /// accordance with [RFC 9000](https://www.rfc-editor.org/rfc/rfc9000.html#section-10.2.3-3), the
845    /// error code is changed to `APPLICATION_ERROR` and the reason phrase is
846    /// cleared.
847    pub send_application_close: bool,
848    /// The [QUIC][proto-err] or [application-level][app-err] error code to send
849    /// to the peer.
850    ///
851    /// [proto-err]: https://www.rfc-editor.org/rfc/rfc9000.html#section-20.1
852    /// [app-err]: https://www.rfc-editor.org/rfc/rfc9000.html#section-20.2
853    pub error_code: u64,
854    /// The reason phrase to send to the peer.
855    pub reason: Vec<u8>,
856}