nomad_protocol/transport/
error.rs

1//! Transport layer error types.
2//!
3//! All errors in this module are designed for silent dropping per the spec:
4//! "Silent drops prevent confirmation of session existence to attackers."
5
6use std::io;
7
8use thiserror::Error;
9
10use super::frame::FrameError;
11
12/// Transport layer errors.
13#[derive(Debug, Error)]
14pub enum TransportError {
15    /// Frame parsing error.
16    #[error("frame error: {0}")]
17    Frame(#[from] FrameError),
18
19    /// I/O error (socket operations).
20    #[error("i/o error: {0}")]
21    Io(#[from] io::Error),
22
23    /// Invalid AEAD tag - frame authentication failed.
24    /// Per spec: silently drop, do not respond.
25    #[error("authentication failed")]
26    AuthenticationFailed,
27
28    /// Unknown session ID.
29    /// Per spec: silently drop to prevent session enumeration.
30    #[error("unknown session")]
31    UnknownSession,
32
33    /// Nonce replay detected.
34    /// Per spec: silently drop to prevent replay attacks.
35    #[error("nonce replay detected")]
36    NonceReplay,
37
38    /// Nonce too old (outside anti-replay window).
39    /// Per spec: silently drop.
40    #[error("nonce too old")]
41    NonceTooOld,
42
43    /// Frame too small to be valid.
44    /// Per spec: silently drop to prevent parsing exploits.
45    #[error("frame too small")]
46    FrameTooSmall,
47
48    /// Connection has timed out.
49    #[error("connection timeout")]
50    ConnectionTimeout,
51
52    /// Too many retransmissions, connection failed.
53    #[error("max retransmits exceeded")]
54    MaxRetransmitsExceeded,
55
56    /// Connection is closed.
57    #[error("connection closed")]
58    ConnectionClosed,
59
60    /// Anti-amplification limit reached.
61    #[error("amplification limit reached")]
62    AmplificationLimit,
63
64    /// Migration rate limited.
65    #[error("migration rate limited")]
66    MigrationRateLimited,
67
68    /// Counter exhaustion - nonce counter overflow.
69    /// This is a critical security error requiring session termination.
70    #[error("nonce counter exhaustion - session must be terminated")]
71    CounterExhaustion,
72}
73
74impl TransportError {
75    /// Check if this error should result in silent drop (no response sent).
76    ///
77    /// Per 2-TRANSPORT.md, these errors should not generate any response
78    /// to prevent information leakage to attackers.
79    pub fn is_silent_drop(&self) -> bool {
80        matches!(
81            self,
82            TransportError::AuthenticationFailed
83                | TransportError::UnknownSession
84                | TransportError::NonceReplay
85                | TransportError::NonceTooOld
86                | TransportError::FrameTooSmall
87        )
88    }
89
90    /// Check if this error is fatal to the connection.
91    pub fn is_fatal(&self) -> bool {
92        matches!(
93            self,
94            TransportError::ConnectionTimeout
95                | TransportError::MaxRetransmitsExceeded
96                | TransportError::ConnectionClosed
97                | TransportError::CounterExhaustion
98        )
99    }
100
101    /// Check if this error is a security-related error.
102    pub fn is_security_error(&self) -> bool {
103        matches!(
104            self,
105            TransportError::AuthenticationFailed
106                | TransportError::NonceReplay
107                | TransportError::NonceTooOld
108                | TransportError::CounterExhaustion
109        )
110    }
111}
112
113/// Result type for transport operations.
114pub type TransportResult<T> = Result<T, TransportError>;
115
116#[cfg(test)]
117mod tests {
118    use super::*;
119
120    #[test]
121    fn test_silent_drop_errors() {
122        assert!(TransportError::AuthenticationFailed.is_silent_drop());
123        assert!(TransportError::UnknownSession.is_silent_drop());
124        assert!(TransportError::NonceReplay.is_silent_drop());
125        assert!(TransportError::NonceTooOld.is_silent_drop());
126        assert!(TransportError::FrameTooSmall.is_silent_drop());
127
128        assert!(!TransportError::ConnectionTimeout.is_silent_drop());
129        assert!(!TransportError::Io(io::Error::new(io::ErrorKind::Other, "test")).is_silent_drop());
130    }
131
132    #[test]
133    fn test_fatal_errors() {
134        assert!(TransportError::ConnectionTimeout.is_fatal());
135        assert!(TransportError::MaxRetransmitsExceeded.is_fatal());
136        assert!(TransportError::ConnectionClosed.is_fatal());
137        assert!(TransportError::CounterExhaustion.is_fatal());
138
139        assert!(!TransportError::AuthenticationFailed.is_fatal());
140        assert!(!TransportError::NonceReplay.is_fatal());
141    }
142
143    #[test]
144    fn test_security_errors() {
145        assert!(TransportError::AuthenticationFailed.is_security_error());
146        assert!(TransportError::NonceReplay.is_security_error());
147        assert!(TransportError::NonceTooOld.is_security_error());
148        assert!(TransportError::CounterExhaustion.is_security_error());
149
150        assert!(!TransportError::ConnectionTimeout.is_security_error());
151        assert!(!TransportError::AmplificationLimit.is_security_error());
152    }
153}