iroh_quinn_proto/connection/
state.rs

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