zenith-net 0.1.0

Zenith 网络地址与传输层抽象:L2-L4 协议解析、TCP/UDP/QUIC 状态机、来源准入引擎、单队列 Worker 数据面循环
//! zenith-net 错误类型
//!
//! 严格遵循 AGENT.md 错误处理规范:
//! - 携带上下文
//! - 使用 thiserror 派生
//! - 禁止裸错误

use thiserror::Error;

/// 网络协议错误
#[derive(Debug, Error)]
pub enum NetError {
    /// 数据包太短,无法解析
    #[error("packet too short: need {need} bytes, got {got}")]
    PacketTooShort {
        /// 所需最小字节数
        need: usize,
        /// 实际字节数
        got: usize,
    },

    /// 无效的以太网类型
    #[error("invalid ethertype: {0:#06x}")]
    InvalidEtherType(u16),

    /// 无效的 IP 版本
    #[error("invalid IP version: {0}")]
    InvalidIpVersion(u8),

    /// 不支持的协议
    #[error("unsupported protocol: {0}")]
    UnsupportedProtocol(&'static str),

    /// 无效的数据包
    #[error("invalid packet: {reason}")]
    InvalidPacket {
        /// 错误原因
        reason: &'static str,
    },

    /// 描述符操作失败
    #[cfg(all(feature = "linux", target_os = "linux"))]
    #[error("descriptor error: {0}")]
    Descriptor(#[from] zenith_linux::error::DescriptorError),

    /// Ring 操作失败
    #[cfg(all(feature = "linux", target_os = "linux"))]
    #[error("ring error: {0}")]
    Ring(#[from] zenith_linux::error::RingError),

    /// XSK Socket 错误
    #[cfg(all(feature = "linux", target_os = "linux"))]
    #[error("xsk socket error: {0}")]
    XskSocket(#[from] zenith_linux::error::XskError),

    /// UMEM 错误
    #[cfg(all(feature = "linux", target_os = "linux"))]
    #[error("umem error: {0}")]
    Umem(#[from] zenith_linux::error::UmemError),

    /// 核心层错误(帧池、账本等)
    #[error("core error: {0}")]
    Core(#[from] zenith_foundation::CoreError),

    /// 准入拒绝
    #[error("admission rejected: {reason}")]
    AdmissionRejected {
        /// 拒绝原因
        reason: &'static str,
    },

    /// Worker 状态错误
    #[error("worker state error: {reason}")]
    WorkerState {
        /// 状态错误原因
        reason: &'static str,
    },

    /// Linux 平台错误(顶层封装)
    #[cfg(all(feature = "linux", target_os = "linux"))]
    #[error("linux error: {0}")]
    Linux(#[from] zenith_linux::error::LinuxError),

    /// eBPF Map 操作错误(XSKMAP 注册/更新等)
    #[cfg(all(feature = "linux", target_os = "linux"))]
    #[error("ebpf map error: {0}")]
    Ebpf(#[from] zenith_ebpf::MapError),

    /// 内部错误
    #[error("internal error: {0}")]
    Internal(&'static str),

    /// 连接表已满
    #[error("connection table full: capacity={capacity}")]
    ConnectionTableFull {
        /// 表容量
        capacity: usize,
    },

    /// 连接已存在
    #[error("connection already exists: {key}")]
    ConnectionExists {
        /// 连接键描述
        key: String,
    },

    /// 哈希表已满
    #[error("hash table full")]
    HashTableFull,

    /// 无效操作
    #[error("invalid operation: {reason}")]
    InvalidOperation {
        /// 错误原因
        reason: String,
    },

    /// 资源限额(硬上限)
    #[error("resource limit exceeded: {0}")]
    ResourceLimit(String),

    /// QUIC 协议错误
    #[error("quic protocol error: {reason}")]
    QuicError {
        /// 错误原因
        reason: String,
    },
}

impl NetError {
    /// 是否为可恢复错误
    #[inline]
    pub fn is_recoverable(&self) -> bool {
        matches!(
            self,
            Self::PacketTooShort { .. }
                | Self::InvalidEtherType(_)
                | Self::InvalidIpVersion(_)
                | Self::UnsupportedProtocol(_)
                | Self::InvalidPacket { .. }
                | Self::AdmissionRejected { .. }
        )
    }

    /// 是否为需要 Worker 停止的致命错误
    #[inline]
    pub fn is_fatal(&self) -> bool {
        #[cfg(all(feature = "linux", target_os = "linux"))]
        {
            matches!(
                self,
                Self::Descriptor(_)
                    | Self::Ring(_)
                    | Self::XskSocket(_)
                    | Self::Umem(_)
                    | Self::Core(_)
                    | Self::WorkerState { .. }
                    | Self::Linux(_)
                    | Self::Internal(_)
            )
        }
        #[cfg(not(all(feature = "linux", target_os = "linux")))]
        {
            matches!(
                self,
                Self::Core(_) | Self::WorkerState { .. } | Self::Internal(_)
            )
        }
    }
}

/// 结果类型别名
pub type Result<T> = std::result::Result<T, NetError>;

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_packet_too_short_error() {
        let err = NetError::PacketTooShort { need: 20, got: 10 };
        let msg = format!("{}", err);
        assert!(msg.contains("packet too short"));
        assert!(msg.contains("20"));
        assert!(msg.contains("10"));
        assert!(err.is_recoverable());
        assert!(!err.is_fatal());
    }

    #[test]
    fn test_invalid_ethertype_error() {
        let err = NetError::InvalidEtherType(0x1234);
        let msg = format!("{}", err);
        assert!(msg.contains("invalid ethertype"));
        assert!(msg.contains("0x1234"));
        assert!(err.is_recoverable());
        assert!(!err.is_fatal());
    }

    #[test]
    fn test_invalid_ip_version_error() {
        let err = NetError::InvalidIpVersion(6);
        let msg = format!("{}", err);
        assert!(msg.contains("invalid IP version"));
        assert!(msg.contains("6"));
        assert!(err.is_recoverable());
        assert!(!err.is_fatal());
    }

    #[test]
    fn test_unsupported_protocol_error() {
        let err = NetError::UnsupportedProtocol("SCTP");
        let msg = format!("{}", err);
        assert!(msg.contains("unsupported protocol"));
        assert!(msg.contains("SCTP"));
        assert!(err.is_recoverable());
        assert!(!err.is_fatal());
    }

    #[test]
    fn test_invalid_packet_error() {
        let err = NetError::InvalidPacket {
            reason: "bad checksum",
        };
        let msg = format!("{}", err);
        assert!(msg.contains("invalid packet"));
        assert!(msg.contains("bad checksum"));
        assert!(err.is_recoverable());
        assert!(!err.is_fatal());
    }

    #[test]
    fn test_admission_rejected_error() {
        let err = NetError::AdmissionRejected {
            reason: "blacklisted IP",
        };
        let msg = format!("{}", err);
        assert!(msg.contains("admission rejected"));
        assert!(msg.contains("blacklisted IP"));
        assert!(err.is_recoverable());
        assert!(!err.is_fatal());
    }

    #[test]
    fn test_worker_state_error() {
        let err = NetError::WorkerState {
            reason: "already running",
        };
        let msg = format!("{}", err);
        assert!(msg.contains("worker state error"));
        assert!(msg.contains("already running"));
        assert!(!err.is_recoverable());
        assert!(err.is_fatal());
    }

    #[test]
    fn test_internal_error() {
        let err = NetError::Internal("out of memory");
        let msg = format!("{}", err);
        assert!(msg.contains("internal error"));
        assert!(msg.contains("out of memory"));
        assert!(!err.is_recoverable());
        assert!(err.is_fatal());
    }

    #[test]
    fn test_connection_table_full_error() {
        let err = NetError::ConnectionTableFull { capacity: 1024 };
        let msg = format!("{}", err);
        assert!(msg.contains("connection table full"));
        assert!(msg.contains("1024"));
        assert!(!err.is_recoverable());
        assert!(!err.is_fatal());
    }

    #[test]
    fn test_connection_exists_error() {
        let err = NetError::ConnectionExists {
            key: "10.0.0.1:8080".to_string(),
        };
        let msg = format!("{}", err);
        assert!(msg.contains("connection already exists"));
        assert!(msg.contains("10.0.0.1:8080"));
        assert!(!err.is_recoverable());
        assert!(!err.is_fatal());
    }

    #[test]
    fn test_hash_table_full_error() {
        let err = NetError::HashTableFull;
        let msg = format!("{}", err);
        assert!(msg.contains("hash table full"));
        assert!(!err.is_recoverable());
        assert!(!err.is_fatal());
    }

    #[test]
    fn test_invalid_operation_error() {
        let err = NetError::InvalidOperation {
            reason: "cannot close closed connection".to_string(),
        };
        let msg = format!("{}", err);
        assert!(msg.contains("invalid operation"));
        assert!(msg.contains("cannot close closed connection"));
        assert!(!err.is_recoverable());
        assert!(!err.is_fatal());
    }

    #[test]
    fn test_resource_limit_error() {
        let err = NetError::ResourceLimit("too many open files".to_string());
        let msg = format!("{}", err);
        assert!(msg.contains("resource limit exceeded"));
        assert!(msg.contains("too many open files"));
        assert!(!err.is_recoverable());
        assert!(!err.is_fatal());
    }

    #[test]
    fn test_quic_error() {
        let err = NetError::QuicError {
            reason: "invalid packet".to_string(),
        };
        let msg = format!("{}", err);
        assert!(msg.contains("quic protocol error"));
        assert!(msg.contains("invalid packet"));
        assert!(!err.is_recoverable());
        assert!(!err.is_fatal());
    }

    #[test]
    fn test_error_classification_recoverable() {
        let recoverable_errors = vec![
            NetError::PacketTooShort { need: 10, got: 5 },
            NetError::InvalidEtherType(0x0000),
            NetError::InvalidIpVersion(0),
            NetError::UnsupportedProtocol("test"),
            NetError::InvalidPacket { reason: "test" },
            NetError::AdmissionRejected { reason: "test" },
        ];
        for err in &recoverable_errors {
            assert!(err.is_recoverable(), "should be recoverable: {:?}", err);
            assert!(!err.is_fatal(), "should not be fatal: {:?}", err);
        }
    }

    #[test]
    fn test_error_classification_fatal() {
        let fatal_errors = vec![
            NetError::WorkerState { reason: "test" },
            NetError::Internal("test"),
        ];
        for err in &fatal_errors {
            assert!(err.is_fatal(), "should be fatal: {:?}", err);
            assert!(!err.is_recoverable(), "should not be recoverable: {:?}", err);
        }
    }

    #[test]
    fn test_error_debug_format() {
        let err = NetError::PacketTooShort { need: 20, got: 10 };
        let debug = format!("{:?}", err);
        assert!(debug.contains("PacketTooShort"));
        assert!(debug.contains("need: 20"));
        assert!(debug.contains("got: 10"));
    }

    #[test]
    fn test_result_type_alias() {
        let ok: usize = 42;
        assert_eq!(ok, 42);

        let err: Result<usize> = Err(NetError::Internal("test"));
        assert!(err.is_err());
    }
}