Skip to main content

eggress_protocol_socks/
error.rs

1/// Error types for SOCKS5 protocol operations.
2#[derive(Debug, thiserror::Error)]
3pub enum Socks5Error {
4    #[error("IO error: {0}")]
5    Io(#[from] std::io::Error),
6
7    #[error("unsupported SOCKS version: {0}")]
8    UnsupportedVersion(u8),
9
10    #[error("unsupported command: {0}")]
11    UnsupportedCommand(u8),
12
13    #[error("unsupported address type: {0}")]
14    UnsupportedAddressType(u8),
15
16    #[error("unsupported authentication method: {0}")]
17    UnsupportedAuthMethod(u8),
18
19    #[error("authentication failed")]
20    AuthFailed,
21
22    #[error("credentials too long (max 255 bytes)")]
23    CredentialsTooLong,
24
25    #[error("connection refused by SOCKS server")]
26    ConnectionRefused,
27
28    #[error("connection failed: {0}")]
29    ConnectionFailed(String),
30
31    #[error("malformed message: {0}")]
32    MalformedMessage(String),
33
34    #[error("method negotiation failed")]
35    MethodNegotiationFailed,
36
37    #[error("unexpected end of stream")]
38    UnexpectedEof,
39
40    #[error("address too long")]
41    AddressTooLong,
42
43    #[error("non-zero reserved byte: {0}")]
44    InvalidReservedByte(u8),
45
46    #[error("domain exceeds SOCKS5 255-byte limit: {0}")]
47    DomainTooLong(usize),
48}
49
50impl From<Socks5Error> for std::io::Error {
51    fn from(e: Socks5Error) -> Self {
52        match e {
53            Socks5Error::Io(io_err) => io_err,
54            other => std::io::Error::other(other),
55        }
56    }
57}
58
59impl Socks5Error {
60    /// Returns the display string for an error with hex formatting.
61    pub fn display_hex(&self) -> String {
62        match self {
63            Socks5Error::UnsupportedVersion(v) => {
64                format!("unsupported SOCKS version: {v:#04x}")
65            }
66            Socks5Error::UnsupportedCommand(c) => {
67                format!("unsupported command: {c:#04x}")
68            }
69            Socks5Error::UnsupportedAddressType(a) => {
70                format!("unsupported address type: {a:#04x}")
71            }
72            Socks5Error::UnsupportedAuthMethod(m) => {
73                format!("unsupported authentication method: {m:#04x}")
74            }
75            _ => self.to_string(),
76        }
77    }
78}
79
80#[cfg(test)]
81mod tests {
82    use super::*;
83
84    #[test]
85    fn test_error_display() {
86        // thiserror displays u8 as decimal, not hex
87        assert_eq!(
88            Socks5Error::UnsupportedVersion(0x04).to_string(),
89            "unsupported SOCKS version: 4"
90        );
91        assert_eq!(
92            Socks5Error::UnsupportedCommand(0x02).to_string(),
93            "unsupported command: 2"
94        );
95        assert_eq!(
96            Socks5Error::UnsupportedAddressType(0x05).to_string(),
97            "unsupported address type: 5"
98        );
99        assert_eq!(
100            Socks5Error::UnsupportedAuthMethod(0x03).to_string(),
101            "unsupported authentication method: 3"
102        );
103        assert_eq!(Socks5Error::AuthFailed.to_string(), "authentication failed");
104        assert_eq!(
105            Socks5Error::CredentialsTooLong.to_string(),
106            "credentials too long (max 255 bytes)"
107        );
108        assert_eq!(
109            Socks5Error::ConnectionRefused.to_string(),
110            "connection refused by SOCKS server"
111        );
112        assert_eq!(
113            Socks5Error::ConnectionFailed("timeout".to_string()).to_string(),
114            "connection failed: timeout"
115        );
116        assert_eq!(
117            Socks5Error::MalformedMessage("bad".to_string()).to_string(),
118            "malformed message: bad"
119        );
120        assert_eq!(
121            Socks5Error::MethodNegotiationFailed.to_string(),
122            "method negotiation failed"
123        );
124        assert_eq!(
125            Socks5Error::UnexpectedEof.to_string(),
126            "unexpected end of stream"
127        );
128        assert_eq!(Socks5Error::AddressTooLong.to_string(), "address too long");
129        assert_eq!(
130            Socks5Error::InvalidReservedByte(0x01).to_string(),
131            "non-zero reserved byte: 1"
132        );
133        assert_eq!(
134            Socks5Error::DomainTooLong(256).to_string(),
135            "domain exceeds SOCKS5 255-byte limit: 256"
136        );
137    }
138
139    #[test]
140    fn test_error_display_hex() {
141        // Test the display_hex method for hex formatting
142        assert_eq!(
143            Socks5Error::UnsupportedVersion(0x04).display_hex(),
144            "unsupported SOCKS version: 0x04"
145        );
146        assert_eq!(
147            Socks5Error::UnsupportedCommand(0x02).display_hex(),
148            "unsupported command: 0x02"
149        );
150        assert_eq!(
151            Socks5Error::UnsupportedAddressType(0x05).display_hex(),
152            "unsupported address type: 0x05"
153        );
154        assert_eq!(
155            Socks5Error::UnsupportedAuthMethod(0x03).display_hex(),
156            "unsupported authentication method: 0x03"
157        );
158        // Non-hex variants fall back to to_string()
159        assert_eq!(
160            Socks5Error::AuthFailed.display_hex(),
161            "authentication failed"
162        );
163    }
164}