io_proxy/socks/v5/
auth.rs1use alloc::{
6 string::{String, ToString},
7 vec::Vec,
8};
9use core::fmt;
10
11use thiserror::Error;
12
13use crate::socks::v5::AUTH_VERSION;
14
15#[derive(Clone, Debug, Error, PartialEq, Eq)]
17pub enum Socks5CredentialsError {
18 #[error("SOCKS5 username too long: {0} bytes (max 255)")]
20 UsernameTooLong(usize),
21 #[error("SOCKS5 password too long: {0} bytes (max 255)")]
23 PasswordTooLong(usize),
24}
25
26#[derive(Clone)]
30pub struct Socks5Credentials {
31 username: String,
32 password: String,
33}
34
35impl Socks5Credentials {
36 pub fn new(
39 username: &str,
40 password: &str,
41 ) -> Result<Socks5Credentials, Socks5CredentialsError> {
42 if username.len() > 255 {
43 return Err(Socks5CredentialsError::UsernameTooLong(username.len()));
44 }
45 if password.len() > 255 {
46 return Err(Socks5CredentialsError::PasswordTooLong(password.len()));
47 }
48 Ok(Socks5Credentials {
49 username: username.to_string(),
50 password: password.to_string(),
51 })
52 }
53
54 pub(crate) fn encode(&self) -> Vec<u8> {
57 let user = self.username.as_bytes();
58 let pass = self.password.as_bytes();
59
60 let mut out = Vec::with_capacity(3 + user.len() + pass.len());
61 out.push(AUTH_VERSION);
62 out.push(user.len() as u8);
63 out.extend_from_slice(user);
64 out.push(pass.len() as u8);
65 out.extend_from_slice(pass);
66 out
67 }
68}
69
70impl fmt::Debug for Socks5Credentials {
71 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
72 f.debug_struct("Socks5Credentials")
73 .field("username", &self.username)
74 .field("password", &"***")
75 .finish()
76 }
77}
78
79#[cfg(test)]
80mod tests {
81 use super::*;
82
83 #[test]
84 fn encode_matches_rfc1929() {
85 let creds = Socks5Credentials::new("user", "pass").unwrap();
86 assert_eq!(
88 creds.encode(),
89 [
90 0x01, 0x04, b'u', b's', b'e', b'r', 0x04, b'p', b'a', b's', b's'
91 ]
92 );
93 }
94
95 #[test]
96 fn rejects_overlong_fields() {
97 let long = "x".repeat(256);
98 assert!(matches!(
99 Socks5Credentials::new(&long, "p"),
100 Err(Socks5CredentialsError::UsernameTooLong(256))
101 ));
102 assert!(matches!(
103 Socks5Credentials::new("u", &long),
104 Err(Socks5CredentialsError::PasswordTooLong(256))
105 ));
106 }
107
108 #[test]
109 fn debug_redacts_password() {
110 let creds = Socks5Credentials::new("alice", "secret").unwrap();
111 let rendered = format!("{creds:?}");
112 assert!(rendered.contains("alice"));
113 assert!(!rendered.contains("secret"));
114 }
115}