1use crate::ProtocolParser;
18use crate::constants::{error_msg, scheme};
19use crate::error::{ProtocolError, Result};
20use serde::{Deserialize, Serialize};
21
22#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
26pub struct VLessConfig {
27 pub id: String,
29 pub address: String,
31 pub port: u16,
33 #[serde(skip_serializing_if = "Option::is_none")]
35 pub encryption: Option<String>,
36 #[serde(skip_serializing_if = "Option::is_none")]
38 pub flow: Option<String>,
39 #[serde(skip_serializing_if = "Option::is_none")]
41 pub security: Option<String>,
42 #[serde(skip_serializing_if = "Option::is_none")]
44 pub r#type: Option<String>,
45 #[serde(skip_serializing_if = "Option::is_none")]
47 pub host: Option<String>,
48 #[serde(skip_serializing_if = "Option::is_none")]
50 pub path: Option<String>,
51 #[serde(skip_serializing_if = "Option::is_none")]
53 pub sni: Option<String>,
54 #[serde(skip_serializing_if = "Option::is_none")]
56 pub fp: Option<String>,
57 #[serde(skip_serializing_if = "Option::is_none")]
59 pub pbk: Option<String>,
60 #[serde(skip_serializing_if = "Option::is_none")]
62 pub sid: Option<String>,
63 #[serde(skip_serializing_if = "Option::is_none")]
65 pub seed: Option<String>,
66 #[serde(skip_serializing_if = "Option::is_none")]
68 pub header_type: Option<String>,
69 #[serde(skip_serializing_if = "Option::is_none")]
71 pub remark: Option<String>,
72}
73
74#[derive(Debug, Clone, PartialEq, Eq)]
76pub struct VLess {
77 pub config: VLessConfig,
79}
80
81impl ProtocolParser for VLess {
82 fn parse(link: &str) -> Result<Self> {
83 if !link.to_lowercase().starts_with(scheme::VLESS) {
84 return Err(ProtocolError::InvalidFormat(format!(
85 "{} {}",
86 error_msg::MUST_START_WITH,
87 scheme::VLESS
88 )));
89 }
90
91 let link_body = &link[scheme::VLESS.len()..];
92
93 let (main_part, query_part, fragment) = {
95 let hash_pos = link_body.find('#');
96 let (before_hash, fragment) = if let Some(pos) = hash_pos {
97 (&link_body[..pos], Some(&link_body[pos + 1..]))
98 } else {
99 (link_body, None)
100 };
101
102 let query_pos = before_hash.find('?');
103 let (main, query) = if let Some(pos) = query_pos {
104 (&before_hash[..pos], Some(&before_hash[pos + 1..]))
105 } else {
106 (before_hash, None)
107 };
108
109 (main, query, fragment)
110 };
111
112 let at_pos = main_part
114 .find('@')
115 .ok_or_else(|| ProtocolError::InvalidFormat(error_msg::MISSING_AT.to_string()))?;
116
117 let id = &main_part[..at_pos];
118 let host_port = &main_part[at_pos + 1..];
119
120 let colon_pos = host_port.find(':').ok_or_else(|| {
121 ProtocolError::InvalidFormat(error_msg::MISSING_COLON_HOST_PORT.to_string())
122 })?;
123
124 let address = &host_port[..colon_pos];
125 let port_str = &host_port[colon_pos + 1..];
126 let port: u16 = port_str.parse().map_err(|e| {
127 ProtocolError::InvalidField(format!("{}: {}", error_msg::INVALID_PORT, e))
128 })?;
129
130 let mut config = VLessConfig {
132 id: id.to_string(),
133 address: address.to_string(),
134 port,
135 encryption: None,
136 flow: None,
137 security: None,
138 r#type: None,
139 host: None,
140 path: None,
141 sni: None,
142 fp: None,
143 pbk: None,
144 sid: None,
145 seed: None,
146 header_type: None,
147 remark: fragment.map(|s| urlencoding::decode(s).unwrap_or_default().to_string()),
148 };
149
150 if let Some(query) = query_part {
151 let params: std::collections::HashMap<String, String> =
152 url::form_urlencoded::parse(query.as_bytes())
153 .into_owned()
154 .collect();
155
156 config.encryption = params.get("encryption").cloned();
157 config.flow = params.get("flow").cloned();
158 config.security = params.get("security").cloned();
159 config.r#type = params.get("type").cloned();
160 config.host = params.get("host").cloned();
161 config.path = params.get("path").cloned();
162 config.sni = params.get("sni").cloned();
163 config.fp = params.get("fp").cloned();
164 config.pbk = params.get("pbk").cloned();
165 config.sid = params.get("sid").cloned();
166 config.seed = params.get("seed").cloned();
167 config.header_type = params.get("headerType").cloned();
168 }
169
170 Ok(VLess { config })
171 }
172
173 fn to_link(&self) -> Result<String> {
174 let mut parts = vec![format!(
175 "vless://{}@{}:{}",
176 self.config.id, self.config.address, self.config.port
177 )];
178
179 let mut query_params = Vec::new();
181
182 if let Some(ref encryption) = self.config.encryption {
183 query_params.push(format!("encryption={}", urlencoding::encode(encryption)));
184 }
185 if let Some(ref flow) = self.config.flow {
186 query_params.push(format!("flow={}", urlencoding::encode(flow)));
187 }
188 if let Some(ref security) = self.config.security {
189 query_params.push(format!("security={}", urlencoding::encode(security)));
190 }
191 if let Some(ref r#type) = self.config.r#type {
192 query_params.push(format!("type={}", urlencoding::encode(r#type)));
193 }
194 if let Some(ref host) = self.config.host {
195 query_params.push(format!("host={}", urlencoding::encode(host)));
196 }
197 if let Some(ref path) = self.config.path {
198 query_params.push(format!("path={}", urlencoding::encode(path)));
199 }
200 if let Some(ref sni) = self.config.sni {
201 query_params.push(format!("sni={}", urlencoding::encode(sni)));
202 }
203 if let Some(ref fp) = self.config.fp {
204 query_params.push(format!("fp={}", urlencoding::encode(fp)));
205 }
206 if let Some(ref pbk) = self.config.pbk {
207 query_params.push(format!("pbk={}", urlencoding::encode(pbk)));
208 }
209 if let Some(ref sid) = self.config.sid {
210 query_params.push(format!("sid={}", urlencoding::encode(sid)));
211 }
212 if let Some(ref seed) = self.config.seed {
213 query_params.push(format!("seed={}", urlencoding::encode(seed)));
214 }
215 if let Some(ref header_type) = self.config.header_type {
216 query_params.push(format!("headerType={}", urlencoding::encode(header_type)));
217 }
218
219 if !query_params.is_empty() {
220 parts.push(query_params.join("&"));
221 }
222
223 if let Some(ref remark) = self.config.remark {
225 parts.push(format!("#{}", urlencoding::encode(remark)));
226 }
227
228 Ok(parts.join("?"))
229 }
230}