Skip to main content

ant_quic/
connection_lifecycle.rs

1use std::fmt;
2
3use crate::{ConnectionError, VarInt};
4
5/// Reserved application close-code range for ant-quic lifecycle signaling.
6///
7/// `0x4E5B00..=0x4E5BFF` encodes ASCII `N[` in the upper bytes.
8pub const ANT_QUIC_CLOSE_CODE_BASE: u32 = 0x4E5B00;
9const CLOSE_CODE_SUPERSEDED: u32 = ANT_QUIC_CLOSE_CODE_BASE;
10const CLOSE_CODE_READER_EXIT: u32 = ANT_QUIC_CLOSE_CODE_BASE + 0x01;
11const CLOSE_CODE_PEER_SHUTDOWN: u32 = ANT_QUIC_CLOSE_CODE_BASE + 0x02;
12const CLOSE_CODE_BANNED: u32 = ANT_QUIC_CLOSE_CODE_BASE + 0x03;
13const CLOSE_CODE_LIFECYCLE_CLEANUP: u32 = ANT_QUIC_CLOSE_CODE_BASE + 0x04;
14
15/// ant-quic lifecycle-aware connection close reasons.
16#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
17pub enum ConnectionCloseReason {
18    /// A newer connection superseded this one.
19    Superseded,
20    /// The reader task exited and the endpoint actively closed the connection.
21    ReaderExit,
22    /// The remote endpoint is shutting down.
23    PeerShutdown,
24    /// Trust or policy enforcement rejected the peer.
25    Banned,
26    /// Generic lifecycle cleanup.
27    LifecycleCleanup,
28    /// The peer sent a non-lifecycle application close.
29    ApplicationClosed,
30    /// The peer or transport closed the connection without an application code.
31    ConnectionClosed,
32    /// The connection timed out.
33    TimedOut,
34    /// The peer reset the connection.
35    Reset,
36    /// A transport error closed the connection.
37    TransportError,
38    /// The local side closed the connection.
39    LocallyClosed,
40    /// Version or capability mismatch closed the connection.
41    VersionMismatch,
42    /// CID exhaustion closed the connection.
43    CidsExhausted,
44    /// Unknown or unmapped close reason.
45    Unknown,
46}
47
48impl ConnectionCloseReason {
49    /// Return the reserved QUIC application error code, if this reason has one.
50    pub fn app_error_code(self) -> Option<VarInt> {
51        let code = match self {
52            Self::Superseded => CLOSE_CODE_SUPERSEDED,
53            Self::ReaderExit => CLOSE_CODE_READER_EXIT,
54            Self::PeerShutdown => CLOSE_CODE_PEER_SHUTDOWN,
55            Self::Banned => CLOSE_CODE_BANNED,
56            Self::LifecycleCleanup => CLOSE_CODE_LIFECYCLE_CLEANUP,
57            Self::ApplicationClosed
58            | Self::ConnectionClosed
59            | Self::TimedOut
60            | Self::Reset
61            | Self::TransportError
62            | Self::LocallyClosed
63            | Self::VersionMismatch
64            | Self::CidsExhausted
65            | Self::Unknown => return None,
66        };
67        Some(VarInt::from_u32(code))
68    }
69
70    /// Human-readable identifier for logs and diagnostics.
71    pub fn as_str(self) -> &'static str {
72        match self {
73            Self::Superseded => "Superseded",
74            Self::ReaderExit => "ReaderExit",
75            Self::PeerShutdown => "PeerShutdown",
76            Self::Banned => "Banned",
77            Self::LifecycleCleanup => "LifecycleCleanup",
78            Self::ApplicationClosed => "ApplicationClosed",
79            Self::ConnectionClosed => "ConnectionClosed",
80            Self::TimedOut => "TimedOut",
81            Self::Reset => "Reset",
82            Self::TransportError => "TransportError",
83            Self::LocallyClosed => "LocallyClosed",
84            Self::VersionMismatch => "VersionMismatch",
85            Self::CidsExhausted => "CidsExhausted",
86            Self::Unknown => "Unknown",
87        }
88    }
89
90    /// Static reason bytes used in CONNECTION_CLOSE frames.
91    pub fn reason_bytes(self) -> &'static [u8] {
92        self.as_str().as_bytes()
93    }
94
95    /// Map a QUIC application close code into a lifecycle reason.
96    pub fn from_app_error_code(code: VarInt) -> Option<Self> {
97        match code.into_inner() as u32 {
98            CLOSE_CODE_SUPERSEDED => Some(Self::Superseded),
99            CLOSE_CODE_READER_EXIT => Some(Self::ReaderExit),
100            CLOSE_CODE_PEER_SHUTDOWN => Some(Self::PeerShutdown),
101            CLOSE_CODE_BANNED => Some(Self::Banned),
102            CLOSE_CODE_LIFECYCLE_CLEANUP => Some(Self::LifecycleCleanup),
103            _ => None,
104        }
105    }
106
107    /// Map a transport connection error into a lifecycle reason.
108    pub fn from_connection_error(error: &ConnectionError) -> Self {
109        match error {
110            ConnectionError::ApplicationClosed(frame) => {
111                Self::from_app_error_code(frame.error_code).unwrap_or(Self::ApplicationClosed)
112            }
113            ConnectionError::ConnectionClosed(_) => Self::ConnectionClosed,
114            ConnectionError::TransportError(_) => Self::TransportError,
115            ConnectionError::VersionMismatch => Self::VersionMismatch,
116            ConnectionError::Reset => Self::Reset,
117            ConnectionError::TimedOut => Self::TimedOut,
118            ConnectionError::LocallyClosed => Self::LocallyClosed,
119            ConnectionError::CidsExhausted => Self::CidsExhausted,
120        }
121    }
122}
123
124impl fmt::Display for ConnectionCloseReason {
125    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
126        f.write_str(self.as_str())
127    }
128}
129
130#[derive(Debug, Clone, Copy, PartialEq, Eq)]
131pub(crate) enum ConnectionLifecycleState {
132    Live,
133    Superseded {
134        replaced_by_generation: u64,
135    },
136    Closing {
137        reason: ConnectionCloseReason,
138    },
139    Closed {
140        reason: ConnectionCloseReason,
141        closed_at_unix_ms: u64,
142    },
143}
144
145impl ConnectionLifecycleState {
146    pub(crate) fn name(self) -> &'static str {
147        match self {
148            Self::Live => "Live",
149            Self::Superseded { .. } => "Superseded",
150            Self::Closing { .. } => "Closing",
151            Self::Closed { .. } => "Closed",
152        }
153    }
154}