Skip to main content

srx/
error.rs

1//! Unified error types for the SRX protocol.
2
3use thiserror::Error;
4
5/// Top-level error type for all SRX operations.
6#[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/// Cryptographic operation errors.
31#[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/// Transport-layer errors.
53#[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/// Frame encoding/decoding errors.
75#[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/// Session management errors.
91#[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/// Routing errors.
107#[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/// Configuration errors.
117#[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}