dstar_gateway_core/error/
timeout.rs

1//! `TimeoutError` typed deadlines.
2
3use std::net::SocketAddr;
4use std::time::Duration;
5
6use crate::types::StreamId;
7
8/// Typed deadline-exceeded errors.
9///
10/// Each variant carries enough context to write a useful log line
11/// and to drive recovery logic.
12#[derive(Debug, Clone, thiserror::Error)]
13#[non_exhaustive]
14pub enum TimeoutError {
15    /// Reflector did not acknowledge LINK1/LINK2/DCS connect within deadline.
16    #[error("connect timeout: deadline {deadline:?}, elapsed {elapsed:?}, last state {last_state}")]
17    Connect {
18        /// Configured deadline.
19        deadline: Duration,
20        /// Wall time elapsed before tripping.
21        elapsed: Duration,
22        /// Last state name observed before the trip (string tag;
23        /// see [`crate::session::client::ClientStateKind`] for the typed form).
24        last_state: &'static str,
25    },
26
27    /// Reflector stopped responding to keepalives.
28    #[error("keepalive inactivity: deadline {deadline:?}, elapsed {elapsed:?}, peer {peer}")]
29    KeepaliveInactivity {
30        /// Configured deadline.
31        deadline: Duration,
32        /// Wall time elapsed.
33        elapsed: Duration,
34        /// Affected peer.
35        peer: SocketAddr,
36    },
37
38    /// Disconnect was requested but the reflector never `ACKed` the unlink.
39    #[error("disconnect ack timeout: deadline {deadline:?}, elapsed {elapsed:?}")]
40    Disconnect {
41        /// Configured deadline.
42        deadline: Duration,
43        /// Wall time elapsed.
44        elapsed: Duration,
45    },
46
47    /// Voice stream stopped mid-transmission with no EOT for >2s.
48    #[error("voice inactivity on stream {stream_id}, elapsed {elapsed:?}")]
49    VoiceInactivity {
50        /// Affected stream.
51        stream_id: StreamId,
52        /// Time since last frame on this stream.
53        elapsed: Duration,
54    },
55
56    /// `DPlus` auth TCP connect took too long.
57    #[error("auth connect timeout: deadline {deadline:?}, elapsed {elapsed:?}")]
58    AuthConnect {
59        /// Configured deadline.
60        deadline: Duration,
61        /// Wall time elapsed.
62        elapsed: Duration,
63    },
64
65    /// `DPlus` auth read stalled waiting for the host list.
66    #[error(
67        "auth read stalled: deadline {deadline:?}, elapsed {elapsed:?}, {bytes_so_far} bytes received"
68    )]
69    AuthRead {
70        /// Configured deadline.
71        deadline: Duration,
72        /// Wall time elapsed.
73        elapsed: Duration,
74        /// Bytes received before the stall.
75        bytes_so_far: usize,
76    },
77}
78
79#[cfg(test)]
80mod tests {
81    use std::net::{IpAddr, Ipv4Addr};
82
83    use super::*;
84
85    const CAFE: StreamId = {
86        // SAFETY: Option::unwrap is const since 1.83; 0xCAFE != 0 so this
87        // is a compile-time assertion, never a runtime panic.
88        StreamId::new(0xCAFE).unwrap()
89    };
90
91    #[test]
92    fn timeout_connect_display_includes_state() {
93        let err = TimeoutError::Connect {
94            deadline: Duration::from_secs(5),
95            elapsed: Duration::from_secs(5),
96            last_state: "Connecting",
97        };
98        let s = err.to_string();
99        assert!(s.contains("Connecting"));
100    }
101
102    #[test]
103    fn timeout_keepalive_includes_peer() {
104        let peer = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 20001);
105        let err = TimeoutError::KeepaliveInactivity {
106            deadline: Duration::from_secs(30),
107            elapsed: Duration::from_secs(31),
108            peer,
109        };
110        let s = err.to_string();
111        assert!(s.contains("127.0.0.1:20001"));
112    }
113
114    #[test]
115    fn timeout_voice_inactivity_includes_stream_id() {
116        let err = TimeoutError::VoiceInactivity {
117            stream_id: CAFE,
118            elapsed: Duration::from_secs(2),
119        };
120        let s = err.to_string();
121        assert!(s.contains("0xCAFE"));
122    }
123}