use thiserror::Error;
#[derive(Debug, Error)]
pub enum SrxError {
#[error("crypto error: {0}")]
Crypto(#[from] CryptoError),
#[error("transport error: {0}")]
Transport(#[from] TransportError),
#[error("frame error: {0}")]
Frame(#[from] FrameError),
#[error("session error: {0}")]
Session(#[from] SessionError),
#[error("routing error: {0}")]
Routing(#[from] RoutingError),
#[error("config error: {0}")]
Config(#[from] ConfigError),
#[error("io error: {0}")]
Io(#[from] std::io::Error),
}
#[derive(Debug, Error)]
pub enum CryptoError {
#[error("key exchange failed: {0}")]
KeyExchangeFailed(String),
#[error("encryption failed: {0}")]
EncryptionFailed(String),
#[error("decryption failed: {0}")]
DecryptionFailed(String),
#[error("MAC verification failed")]
MacVerificationFailed,
#[error("KDF derivation failed: {0}")]
KdfFailed(String),
#[error("PQC operation failed: {0}")]
PqcFailed(String),
}
#[derive(Debug, Error)]
pub enum TransportError {
#[error("connection failed: {0}")]
ConnectionFailed(String),
#[error("channel closed")]
ChannelClosed,
#[error("timeout on transport {transport}: {details}")]
Timeout { transport: String, details: String },
#[error("protocol mismatch: {0}")]
ProtocolMismatch(String),
#[error("all transports exhausted")]
AllTransportsExhausted,
#[error("transport not registered: {0}")]
NotRegistered(String),
}
#[derive(Debug, Error)]
pub enum FrameError {
#[error("invalid frame ID")]
InvalidFrameId,
#[error("fragment reassembly failed: missing {missing} of {total} fragments")]
ReassemblyFailed { missing: usize, total: usize },
#[error("frame too large: {size} bytes (max {max})")]
FrameTooLarge { size: usize, max: usize },
#[error("corrupted frame: {0}")]
Corrupted(String),
}
#[derive(Debug, Error)]
pub enum SessionError {
#[error("handshake failed: {0}")]
HandshakeFailed(String),
#[error("session expired")]
SessionExpired,
#[error("re-key failed: {0}")]
ReKeyFailed(String),
#[error("seed synchronization lost")]
SeedDesync,
}
#[derive(Debug, Error)]
pub enum RoutingError {
#[error("no available routes")]
NoAvailableRoutes,
#[error("routing mask generation failed: {0}")]
MaskGenerationFailed(String),
}
#[derive(Debug, Error)]
pub enum ConfigError {
#[error("invalid configuration: {0}")]
Invalid(String),
#[error("missing required field: {0}")]
MissingField(String),
}
pub type Result<T> = std::result::Result<T, SrxError>;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn display_messages_are_human_readable() {
let e = SrxError::Crypto(CryptoError::EncryptionFailed("boom".into()));
assert!(e.to_string().contains("crypto error"));
assert!(e.to_string().contains("boom"));
let t = SrxError::Transport(TransportError::NotRegistered("quic".into()));
assert!(t.to_string().contains("transport error"));
assert!(t.to_string().contains("quic"));
}
#[test]
fn io_error_converts_into_srx_error() {
let io = std::io::Error::other("disk");
let e: SrxError = io.into();
match e {
SrxError::Io(inner) => assert_eq!(inner.kind(), std::io::ErrorKind::Other),
other => panic!("expected Io variant, got {other:?}"),
}
}
#[test]
fn from_inner_errors_work() {
let e: SrxError = TransportError::ChannelClosed.into();
match e {
SrxError::Transport(TransportError::ChannelClosed) => {}
other => panic!("unexpected variant: {other:?}"),
}
let e: SrxError = FrameError::InvalidFrameId.into();
match e {
SrxError::Frame(FrameError::InvalidFrameId) => {}
other => panic!("unexpected variant: {other:?}"),
}
}
#[test]
fn timeout_error_format_includes_transport_and_details() {
let e = TransportError::Timeout {
transport: "tcp".into(),
details: "deadline exceeded".into(),
};
let s = e.to_string();
assert!(s.contains("tcp"));
assert!(s.contains("deadline exceeded"));
}
}