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