Skip to main content

tokio_quiche/quic/io/
connection_stage.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
27use std::fmt::Debug;
28use std::ops::ControlFlow;
29use std::sync::Arc;
30use std::time::Instant;
31
32use tokio::sync::mpsc;
33
34use crate::quic::connection::ApplicationOverQuic;
35use crate::quic::connection::HandshakeError;
36use crate::quic::connection::HandshakeInfo;
37use crate::quic::connection::Incoming;
38use crate::quic::connection::QuicConnectionStatsShared;
39use crate::quic::hooks::ConnectionHook;
40use crate::quic::QuicheConnection;
41use crate::QuicResult;
42
43/// Represents the current lifecycle stage of a [quiche::Connection].
44/// Implementors of this trait inform the underlying I/O loop as to how to
45/// behave.
46///
47/// The I/O loop will always handle sending/receiving packets - this trait
48/// simply serves to augment its functionality. For example, an established
49/// HTTP/3 connection may want its `on_read` to include handing packets off to
50/// an [ApplicationOverQuic].
51///
52/// To prevent borrow checker conflicts, we inject a `qconn` into all methods.
53/// This also simplifies state transitions, since the `IoWorker` must maintain
54/// ownership over the connection in order to read, gather, and flush from it.
55pub trait ConnectionStage: Send + Debug {
56    fn on_read<A: ApplicationOverQuic>(
57        &mut self, _received_packets: bool, _qconn: &mut QuicheConnection,
58        _ctx: &mut ConnectionStageContext<A>,
59    ) -> QuicResult<()> {
60        Ok(())
61    }
62
63    fn on_flush<A: ApplicationOverQuic>(
64        &mut self, _qconn: &mut QuicheConnection,
65        _ctx: &mut ConnectionStageContext<A>,
66    ) -> ControlFlow<QuicResult<()>> {
67        ControlFlow::Continue(())
68    }
69
70    fn wait_deadline(&mut self) -> Option<Instant> {
71        None
72    }
73
74    fn post_wait(
75        &self, _qconn: &mut QuicheConnection,
76    ) -> ControlFlow<QuicResult<()>> {
77        ControlFlow::Continue(())
78    }
79}
80
81/// Global context shared across all [ConnectionStage]s for a given connection
82pub struct ConnectionStageContext<A> {
83    pub in_pkt: Option<Incoming>,
84    pub application: A,
85    pub incoming_pkt_receiver: mpsc::Receiver<Incoming>,
86    pub stats: QuicConnectionStatsShared,
87    pub connection_hook: Option<Arc<dyn ConnectionHook + Send + Sync + 'static>>,
88}
89
90#[derive(Debug)]
91pub struct Handshake {
92    pub handshake_info: HandshakeInfo,
93}
94
95impl Handshake {
96    fn check_handshake_timeout_expired(
97        &self, conn: &mut QuicheConnection,
98    ) -> QuicResult<()> {
99        if self.handshake_info.is_expired() {
100            let _ = conn.close(
101                false,
102                quiche::WireErrorCode::ApplicationError as u64,
103                &[],
104            );
105            return Err(HandshakeError::Timeout.into());
106        }
107
108        Ok(())
109    }
110}
111
112impl ConnectionStage for Handshake {
113    fn on_flush<A: ApplicationOverQuic>(
114        &mut self, qconn: &mut QuicheConnection,
115        _ctx: &mut ConnectionStageContext<A>,
116    ) -> ControlFlow<QuicResult<()>> {
117        // Transition to RunningApplication if we have 1-RTT keys (handshake is
118        // complete) or if we have 0-RTT keys (in early data).
119        if qconn.is_established() || qconn.is_in_early_data() {
120            ControlFlow::Break(Ok(()))
121        } else {
122            ControlFlow::Continue(())
123        }
124    }
125
126    fn wait_deadline(&mut self) -> Option<Instant> {
127        self.handshake_info.deadline()
128    }
129
130    fn post_wait(
131        &self, qconn: &mut QuicheConnection,
132    ) -> ControlFlow<QuicResult<()>> {
133        match self.check_handshake_timeout_expired(qconn) {
134            Ok(_) => ControlFlow::Continue(()),
135            Err(e) => ControlFlow::Break(Err(e)),
136        }
137    }
138}
139
140#[derive(Debug)]
141pub struct RunningApplication;
142
143impl ConnectionStage for RunningApplication {
144    fn on_read<A: ApplicationOverQuic>(
145        &mut self, received_packets: bool, qconn: &mut QuicheConnection,
146        ctx: &mut ConnectionStageContext<A>,
147    ) -> QuicResult<()> {
148        if ctx.application.should_act() {
149            if received_packets {
150                ctx.application.process_reads(qconn)?;
151            }
152
153            if qconn.is_established() {
154                ctx.application.process_writes(qconn)?;
155            }
156        }
157
158        Ok(())
159    }
160}
161
162#[derive(Debug)]
163pub struct Close {
164    pub work_loop_result: QuicResult<()>,
165}
166
167impl ConnectionStage for Close {}