Files
OutFleet/src/services/uri_generator/builders/shadowsocks.rs

103 lines
3.3 KiB
Rust
Raw Normal View History

2025-10-24 18:11:34 +03:00
use base64::{engine::general_purpose, Engine as _};
2025-10-24 18:11:34 +03:00
use super::{utils, UriBuilder};
use crate::services::uri_generator::{error::UriGeneratorError, ClientConfigData};
pub struct ShadowsocksUriBuilder;
impl ShadowsocksUriBuilder {
pub fn new() -> Self {
Self
}
2025-10-24 18:11:34 +03:00
/// Map Xray cipher type to Shadowsocks method name
fn map_xray_cipher_to_shadowsocks_method(&self, cipher: &str) -> &str {
match cipher {
// AES GCM variants
"AES_256_GCM" | "aes-256-gcm" => "aes-256-gcm",
"AES_128_GCM" | "aes-128-gcm" => "aes-128-gcm",
2025-10-24 18:11:34 +03:00
// ChaCha20 variants
"CHACHA20_POLY1305" | "chacha20-ietf-poly1305" | "chacha20-poly1305" => {
"chacha20-ietf-poly1305"
}
// AES CFB variants
"AES_256_CFB" | "aes-256-cfb" => "aes-256-cfb",
"AES_128_CFB" | "aes-128-cfb" => "aes-128-cfb",
2025-10-24 18:11:34 +03:00
// Legacy ciphers
"RC4_MD5" | "rc4-md5" => "rc4-md5",
"AES_256_CTR" | "aes-256-ctr" => "aes-256-ctr",
"AES_128_CTR" | "aes-128-ctr" => "aes-128-ctr",
2025-10-24 18:11:34 +03:00
// Default to most secure and widely supported
_ => "aes-256-gcm",
}
}
}
impl UriBuilder for ShadowsocksUriBuilder {
fn build_uri(&self, config: &ClientConfigData) -> Result<String, UriGeneratorError> {
self.validate_config(config)?;
2025-10-24 18:11:34 +03:00
// Get cipher type from base_settings and map to Shadowsocks method
2025-10-24 18:11:34 +03:00
let cipher = config
.base_settings
.get("cipherType")
.and_then(|c| c.as_str())
.or_else(|| config.base_settings.get("method").and_then(|m| m.as_str()))
.unwrap_or("AES_256_GCM");
2025-10-24 18:11:34 +03:00
let method = self.map_xray_cipher_to_shadowsocks_method(cipher);
2025-10-24 18:11:34 +03:00
// Shadowsocks SIP002 format: ss://base64(method:password)@hostname:port#remark
// Use xray_user_id as password (following Marzban approach)
let credentials = format!("{}:{}", method, config.xray_user_id);
let encoded_credentials = general_purpose::STANDARD.encode(credentials.as_bytes());
2025-10-24 18:11:34 +03:00
// Generate alias for the URI
2025-10-19 04:13:36 +03:00
let alias = utils::generate_alias(&config.server_name, &config.template_name);
2025-10-24 18:11:34 +03:00
// Build simple SIP002 URI (no plugin parameters for standard Shadowsocks)
let uri = format!(
"ss://{}@{}:{}#{}",
encoded_credentials,
config.hostname,
config.port,
utils::url_encode(&alias)
);
2025-10-24 18:11:34 +03:00
Ok(uri)
}
2025-10-24 18:11:34 +03:00
fn validate_config(&self, config: &ClientConfigData) -> Result<(), UriGeneratorError> {
// Basic validation
if config.hostname.is_empty() {
2025-10-24 18:11:34 +03:00
return Err(UriGeneratorError::MissingRequiredField(
"hostname".to_string(),
));
}
if config.port <= 0 || config.port > 65535 {
2025-10-24 18:11:34 +03:00
return Err(UriGeneratorError::InvalidConfiguration(
"Invalid port number".to_string(),
));
}
if config.xray_user_id.is_empty() {
2025-10-24 18:11:34 +03:00
return Err(UriGeneratorError::MissingRequiredField(
"xray_user_id".to_string(),
));
}
2025-10-24 18:11:34 +03:00
// Shadowsocks uses xray_user_id as password, already validated above
2025-10-24 18:11:34 +03:00
Ok(())
}
}
impl Default for ShadowsocksUriBuilder {
fn default() -> Self {
Self::new()
}
}