mirror of
https://github.com/house-of-vanity/OutFleet.git
synced 2025-10-26 02:09:07 +00:00
103 lines
3.3 KiB
Rust
103 lines
3.3 KiB
Rust
use base64::{engine::general_purpose, Engine as _};
|
|
|
|
use super::{utils, UriBuilder};
|
|
use crate::services::uri_generator::{error::UriGeneratorError, ClientConfigData};
|
|
|
|
pub struct ShadowsocksUriBuilder;
|
|
|
|
impl ShadowsocksUriBuilder {
|
|
pub fn new() -> Self {
|
|
Self
|
|
}
|
|
|
|
/// 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",
|
|
|
|
// 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",
|
|
|
|
// 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",
|
|
|
|
// 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)?;
|
|
|
|
// Get cipher type from base_settings and map to Shadowsocks method
|
|
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");
|
|
|
|
let method = self.map_xray_cipher_to_shadowsocks_method(cipher);
|
|
|
|
// 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());
|
|
|
|
// Generate alias for the URI
|
|
let alias = utils::generate_alias(&config.server_name, &config.template_name);
|
|
|
|
// Build simple SIP002 URI (no plugin parameters for standard Shadowsocks)
|
|
let uri = format!(
|
|
"ss://{}@{}:{}#{}",
|
|
encoded_credentials,
|
|
config.hostname,
|
|
config.port,
|
|
utils::url_encode(&alias)
|
|
);
|
|
|
|
Ok(uri)
|
|
}
|
|
|
|
fn validate_config(&self, config: &ClientConfigData) -> Result<(), UriGeneratorError> {
|
|
// Basic validation
|
|
if config.hostname.is_empty() {
|
|
return Err(UriGeneratorError::MissingRequiredField(
|
|
"hostname".to_string(),
|
|
));
|
|
}
|
|
if config.port <= 0 || config.port > 65535 {
|
|
return Err(UriGeneratorError::InvalidConfiguration(
|
|
"Invalid port number".to_string(),
|
|
));
|
|
}
|
|
if config.xray_user_id.is_empty() {
|
|
return Err(UriGeneratorError::MissingRequiredField(
|
|
"xray_user_id".to_string(),
|
|
));
|
|
}
|
|
|
|
// Shadowsocks uses xray_user_id as password, already validated above
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl Default for ShadowsocksUriBuilder {
|
|
fn default() -> Self {
|
|
Self::new()
|
|
}
|
|
}
|