stochastic-routing-extended 1.0.2

SRX (Stochastic Routing eXtended) — a next-generation VPN protocol with stochastic routing, DPI evasion, post-quantum cryptography, and multi-transport channel splitting
Documentation
//! Unified error types for the SRX protocol.

use thiserror::Error;

/// Top-level error type for all SRX operations.
#[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),
}

/// Cryptographic operation errors.
#[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),
}

/// Transport-layer errors.
#[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),
}

/// Frame encoding/decoding errors.
#[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),
}

/// Session management errors.
#[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,
}

/// Routing errors.
#[derive(Debug, Error)]
pub enum RoutingError {
    #[error("no available routes")]
    NoAvailableRoutes,

    #[error("routing mask generation failed: {0}")]
    MaskGenerationFailed(String),
}

/// Configuration errors.
#[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"));
    }
}