iroh_quinn_proto/connection/
state.rs

1use bytes::Bytes;
2use tracing::trace;
3
4use crate::frame::Close;
5use crate::{ApplicationClose, ConnectionClose, ConnectionError, TransportError, TransportErrorCode};
6
7#[allow(unreachable_pub)] // fuzzing only
8#[derive(Debug, Clone)]
9pub struct State {
10    /// Nested [`InnerState`] to enforce all state transitions are done in this module.
11    inner: InnerState,
12}
13
14impl State {
15    pub(super) fn as_handshake_mut(&mut self) -> Option<&mut Handshake> {
16        if let InnerState::Handshake(ref mut hs) = self.inner {
17            Some(hs)
18        } else {
19            None
20        }
21    }
22
23    pub(super) fn as_handshake(&self) -> Option<&Handshake> {
24        if let InnerState::Handshake(ref hs) = self.inner {
25            Some(hs)
26        } else {
27            None
28        }
29    }
30
31    pub(super) fn as_closed(&self) -> Option<&CloseReason> {
32        if let InnerState::Closed {
33            ref remote_reason, ..
34        } = self.inner
35        {
36            Some(remote_reason)
37        } else {
38            None
39        }
40    }
41
42    #[allow(unreachable_pub)] // fuzzing only
43    #[cfg(any(test, fuzzing))]
44    pub fn established() -> Self {
45        Self {
46            inner: InnerState::Established,
47        }
48    }
49
50    pub(super) fn handshake(hs: Handshake) -> Self {
51        Self {
52            inner: InnerState::Handshake(hs),
53        }
54    }
55
56    pub(super) fn move_to_handshake(&mut self, hs: Handshake) {
57        self.inner = InnerState::Handshake(hs);
58        trace!("connection state: handshake");
59    }
60
61    pub(super) fn move_to_established(&mut self) {
62        self.inner = InnerState::Established;
63        trace!("connection state: established");
64    }
65
66    /// Moves to the drained state.
67    ///
68    /// Panics if the state was already drained.
69    pub(super) fn move_to_drained(&mut self, error: Option<ConnectionError>) {
70        let (error, is_local) = if let Some(error) = error {
71            (Some(error), false)
72        } else {
73            let error = match &mut self.inner {
74                InnerState::Draining { error, .. } => error.take(),
75                InnerState::Drained { .. } => panic!("invalid state transition drained -> drained"),
76                InnerState::Closed { error_read, .. } if *error_read => None,
77                InnerState::Closed { remote_reason, .. } => {
78                    let error = match remote_reason.clone().into() {
79                        ConnectionError::ConnectionClosed(close) => {
80                            if close.error_code == TransportErrorCode::PROTOCOL_VIOLATION {
81                                ConnectionError::TransportError(TransportError::new(
82                                    close.error_code,
83                                    std::string::String::from_utf8_lossy(&close.reason[..])
84                                        .to_string(),
85                                ))
86                            } else {
87                                ConnectionError::ConnectionClosed(close)
88                            }
89                        }
90                        e => e,
91                    };
92                    Some(error)
93                }
94                InnerState::Handshake(_) | InnerState::Established => None,
95            };
96            (error, self.is_local_close())
97        };
98        self.inner = InnerState::Drained { error, is_local };
99        trace!("connection state: drained");
100    }
101
102    /// Moves to a draining state.
103    ///
104    /// Panics if the state is already draining or drained.
105    pub(super) fn move_to_draining(&mut self, error: Option<ConnectionError>) {
106        assert!(
107            matches!(
108                self.inner,
109                InnerState::Handshake(_) | InnerState::Established | InnerState::Closed { .. }
110            ),
111            "invalid state transition {:?} -> draining",
112            self.as_type()
113        );
114        let is_local = self.is_local_close();
115        self.inner = InnerState::Draining { error, is_local };
116        trace!("connection state: draining");
117    }
118
119    fn is_local_close(&self) -> bool {
120        match self.inner {
121            InnerState::Handshake(_) => false,
122            InnerState::Established => false,
123            InnerState::Closed { is_local, .. } => is_local,
124            InnerState::Draining { is_local, .. } => is_local,
125            InnerState::Drained { is_local, .. } => is_local,
126        }
127    }
128
129    /// Moves to a closed state after a remote error is received.
130    ///
131    /// Panics if the state is later than established.
132    pub(super) fn move_to_closed<R: Into<CloseReason>>(&mut self, reason: R) {
133        assert!(
134            matches!(
135                self.inner,
136                InnerState::Handshake(_) | InnerState::Established | InnerState::Closed { .. }
137            ),
138            "invalid state transition {:?} -> closed",
139            self.as_type()
140        );
141        let remote_reason = reason.into();
142        let is_local = false;
143        trace!(?remote_reason, ?is_local, "connection state: closed");
144        self.inner = InnerState::Closed {
145            error_read: false,
146            remote_reason,
147            is_local,
148        };
149    }
150
151    /// Moves to a closed state after a local error.
152    ///
153    /// Panics if the state is later than established.
154    pub(super) fn move_to_closed_local<R: Into<CloseReason>>(&mut self, reason: R) {
155        assert!(
156            matches!(
157                self.inner,
158                InnerState::Handshake(_) | InnerState::Established | InnerState::Closed { .. }
159            ),
160            "invalid state transition {:?} -> closed (local)",
161            self.as_type()
162        );
163        let remote_reason = reason.into();
164        let is_local = true;
165        trace!(?remote_reason, ?is_local, "connection state: closed");
166        self.inner = InnerState::Closed {
167            error_read: false,
168            remote_reason,
169            is_local,
170        };
171    }
172
173    pub(super) fn is_handshake(&self) -> bool {
174        matches!(self.inner, InnerState::Handshake(_))
175    }
176
177    pub(super) fn is_established(&self) -> bool {
178        matches!(self.inner, InnerState::Established)
179    }
180
181    pub(super) fn is_closed(&self) -> bool {
182        matches!(
183            self.inner,
184            InnerState::Closed { .. } | InnerState::Draining { .. } | InnerState::Drained { .. }
185        )
186    }
187
188    pub(super) fn is_drained(&self) -> bool {
189        matches!(self.inner, InnerState::Drained { .. })
190    }
191
192    pub(super) fn take_error(&mut self) -> Option<ConnectionError> {
193        match &mut self.inner {
194            InnerState::Draining { error, is_local } => {
195                if !*is_local {
196                    error.take()
197                } else {
198                    None
199                }
200            }
201            InnerState::Drained { error, is_local } => {
202                if !*is_local {
203                    error.take()
204                } else {
205                    None
206                }
207            }
208            InnerState::Closed {
209                remote_reason,
210                is_local: local_reason,
211                error_read,
212            } => {
213                if *error_read {
214                    None
215                } else {
216                    *error_read = true;
217                    if *local_reason {
218                        None
219                    } else {
220                        Some(remote_reason.clone().into())
221                    }
222                }
223            }
224            InnerState::Handshake(_) | InnerState::Established => None,
225        }
226    }
227
228    pub(super) fn as_type(&self) -> StateType {
229        match self.inner {
230            InnerState::Handshake(_) => StateType::Handshake,
231            InnerState::Established => StateType::Established,
232            InnerState::Closed { .. } => StateType::Closed,
233            InnerState::Draining { .. } => StateType::Draining,
234            InnerState::Drained { .. } => StateType::Drained,
235        }
236    }
237}
238
239#[derive(Debug, Clone)]
240pub(super) enum StateType {
241    Handshake,
242    Established,
243    Closed,
244    Draining,
245    Drained,
246}
247
248#[derive(Debug, Clone)]
249pub(super) enum CloseReason {
250    TransportError(TransportError),
251    Connection(ConnectionClose),
252    Application(ApplicationClose),
253}
254
255impl From<TransportError> for CloseReason {
256    fn from(x: TransportError) -> Self {
257        Self::TransportError(x)
258    }
259}
260impl From<ConnectionClose> for CloseReason {
261    fn from(x: ConnectionClose) -> Self {
262        Self::Connection(x)
263    }
264}
265impl From<ApplicationClose> for CloseReason {
266    fn from(x: ApplicationClose) -> Self {
267        Self::Application(x)
268    }
269}
270
271impl From<Close> for CloseReason {
272    fn from(value: Close) -> Self {
273        match value {
274            Close::Application(reason) => Self::Application(reason),
275            Close::Connection(reason) => Self::Connection(reason),
276        }
277    }
278}
279
280impl From<CloseReason> for ConnectionError {
281    fn from(value: CloseReason) -> Self {
282        match value {
283            CloseReason::TransportError(err) => Self::TransportError(err),
284            CloseReason::Connection(reason) => Self::ConnectionClosed(reason),
285            CloseReason::Application(reason) => Self::ApplicationClosed(reason),
286        }
287    }
288}
289
290impl From<CloseReason> for Close {
291    fn from(value: CloseReason) -> Self {
292        match value {
293            CloseReason::TransportError(err) => Self::Connection(err.into()),
294            CloseReason::Connection(reason) => Self::Connection(reason),
295            CloseReason::Application(reason) => Self::Application(reason),
296        }
297    }
298}
299
300#[derive(Debug, Clone)]
301enum InnerState {
302    Handshake(Handshake),
303    Established,
304    Closed {
305        /// The reason the remote closed the connection, or the reason we are sending to the remote.
306        remote_reason: CloseReason,
307        /// Set to true if we closed the connection locally.
308        is_local: bool,
309        /// Did we read this as error already?
310        error_read: bool,
311    },
312    Draining {
313        /// Why the connection was lost, if it has been.
314        error: Option<ConnectionError>,
315        /// Set to true if we closed the connection locally.
316        is_local: bool,
317    },
318    /// Waiting for application to call close so we can dispose of the resources.
319    Drained {
320        /// Why the connection was lost, if it has been.
321        error: Option<ConnectionError>,
322        /// Set to true if we closed the connection locally.
323        is_local: bool,
324    },
325}
326
327#[allow(unreachable_pub)] // fuzzing only
328#[derive(Debug, Clone)]
329pub struct Handshake {
330    /// Whether the remote CID has been set by the peer yet.
331    ///
332    /// Always set for servers.
333    pub(super) rem_cid_set: bool,
334    /// Stateless retry token received in the first Initial by a server.
335    ///
336    /// Must be present in every Initial. Always empty for clients.
337    pub(super) expected_token: Bytes,
338    /// First cryptographic message.
339    ///
340    /// Only set for clients.
341    pub(super) client_hello: Option<Bytes>,
342    /// Whether the server address is allowed to migrate.
343    ///
344    /// We allow the server to migrate during the handshake as long as we have not
345    /// received an authenticated handshake packet: it can send a response from a
346    /// different address than we sent the initial to.  This allows us to send the
347    /// initial packet over multiple paths - by means of an IPv6 ULA address that copies
348    /// the packets sent to it to multiple destinations - and accept one response.
349    ///
350    /// This is only ever set to true if for a client which hasn't yet received an
351    /// authenticated handshake packet.  It is set back to false in
352    /// [`super::Connection::on_packet_authenticated`].
353    ///
354    /// THIS IS NOT RFC 9000 COMPLIANT!  A server is not allowed to migrate addresses,
355    /// other than using the preferred-address transport parameter.
356    pub(super) allow_server_migration: bool,
357}