1use thiserror::Error;
4
5#[derive(Debug, Error)]
7pub enum SrxError {
8 #[error("crypto error: {0}")]
9 Crypto(#[from] CryptoError),
10
11 #[error("transport error: {0}")]
12 Transport(#[from] TransportError),
13
14 #[error("frame error: {0}")]
15 Frame(#[from] FrameError),
16
17 #[error("session error: {0}")]
18 Session(#[from] SessionError),
19
20 #[error("routing error: {0}")]
21 Routing(#[from] RoutingError),
22
23 #[error("config error: {0}")]
24 Config(#[from] ConfigError),
25
26 #[error("io error: {0}")]
27 Io(#[from] std::io::Error),
28}
29
30#[derive(Debug, Error)]
32pub enum CryptoError {
33 #[error("key exchange failed: {0}")]
34 KeyExchangeFailed(String),
35
36 #[error("encryption failed: {0}")]
37 EncryptionFailed(String),
38
39 #[error("decryption failed: {0}")]
40 DecryptionFailed(String),
41
42 #[error("MAC verification failed")]
43 MacVerificationFailed,
44
45 #[error("KDF derivation failed: {0}")]
46 KdfFailed(String),
47
48 #[error("PQC operation failed: {0}")]
49 PqcFailed(String),
50}
51
52#[derive(Debug, Error)]
54pub enum TransportError {
55 #[error("connection failed: {0}")]
56 ConnectionFailed(String),
57
58 #[error("channel closed")]
59 ChannelClosed,
60
61 #[error("timeout on transport {transport}: {details}")]
62 Timeout { transport: String, details: String },
63
64 #[error("protocol mismatch: {0}")]
65 ProtocolMismatch(String),
66
67 #[error("all transports exhausted")]
68 AllTransportsExhausted,
69
70 #[error("transport not registered: {0}")]
71 NotRegistered(String),
72}
73
74#[derive(Debug, Error)]
76pub enum FrameError {
77 #[error("invalid frame ID")]
78 InvalidFrameId,
79
80 #[error("fragment reassembly failed: missing {missing} of {total} fragments")]
81 ReassemblyFailed { missing: usize, total: usize },
82
83 #[error("frame too large: {size} bytes (max {max})")]
84 FrameTooLarge { size: usize, max: usize },
85
86 #[error("corrupted frame: {0}")]
87 Corrupted(String),
88}
89
90#[derive(Debug, Error)]
92pub enum SessionError {
93 #[error("handshake failed: {0}")]
94 HandshakeFailed(String),
95
96 #[error("session expired")]
97 SessionExpired,
98
99 #[error("re-key failed: {0}")]
100 ReKeyFailed(String),
101
102 #[error("seed synchronization lost")]
103 SeedDesync,
104}
105
106#[derive(Debug, Error)]
108pub enum RoutingError {
109 #[error("no available routes")]
110 NoAvailableRoutes,
111
112 #[error("routing mask generation failed: {0}")]
113 MaskGenerationFailed(String),
114}
115
116#[derive(Debug, Error)]
118pub enum ConfigError {
119 #[error("invalid configuration: {0}")]
120 Invalid(String),
121
122 #[error("missing required field: {0}")]
123 MissingField(String),
124}
125
126pub type Result<T> = std::result::Result<T, SrxError>;
127
128#[cfg(test)]
129mod tests {
130 use super::*;
131
132 #[test]
133 fn display_messages_are_human_readable() {
134 let e = SrxError::Crypto(CryptoError::EncryptionFailed("boom".into()));
135 assert!(e.to_string().contains("crypto error"));
136 assert!(e.to_string().contains("boom"));
137
138 let t = SrxError::Transport(TransportError::NotRegistered("quic".into()));
139 assert!(t.to_string().contains("transport error"));
140 assert!(t.to_string().contains("quic"));
141 }
142
143 #[test]
144 fn io_error_converts_into_srx_error() {
145 let io = std::io::Error::other("disk");
146 let e: SrxError = io.into();
147 match e {
148 SrxError::Io(inner) => assert_eq!(inner.kind(), std::io::ErrorKind::Other),
149 other => panic!("expected Io variant, got {other:?}"),
150 }
151 }
152
153 #[test]
154 fn from_inner_errors_work() {
155 let e: SrxError = TransportError::ChannelClosed.into();
156 match e {
157 SrxError::Transport(TransportError::ChannelClosed) => {}
158 other => panic!("unexpected variant: {other:?}"),
159 }
160
161 let e: SrxError = FrameError::InvalidFrameId.into();
162 match e {
163 SrxError::Frame(FrameError::InvalidFrameId) => {}
164 other => panic!("unexpected variant: {other:?}"),
165 }
166 }
167
168 #[test]
169 fn timeout_error_format_includes_transport_and_details() {
170 let e = TransportError::Timeout {
171 transport: "tcp".into(),
172 details: "deadline exceeded".into(),
173 };
174 let s = e.to_string();
175 assert!(s.contains("tcp"));
176 assert!(s.contains("deadline exceeded"));
177 }
178}