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