Skip to content

Commit d2551d4

Browse files
committed
feat: support hysteria2 to link
1 parent d3523be commit d2551d4

File tree

2 files changed

+77
-8
lines changed

2 files changed

+77
-8
lines changed

proxrs/src/protocol/hysteria2.rs

Lines changed: 60 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ use serde::Serialize;
88
use serde_json::Error;
99

1010
use crate::protocol::deserialize_u16_or_string;
11+
use crate::protocol::deserialize_from_string;
1112
use crate::protocol::ProxyAdapter;
1213
use crate::protocol::UnsupportedLinkError;
1314

@@ -22,9 +23,17 @@ pub struct Hysteria2 {
2223
pub ports: Option<String>,
2324
#[serde(skip_serializing_if = "Option::is_none", rename = "hop-interval")]
2425
pub hop_interval: Option<u16>,
25-
#[serde(skip_serializing_if = "Option::is_none")]
26+
#[serde(
27+
default,
28+
skip_serializing_if = "Option::is_none",
29+
deserialize_with = "deserialize_from_string"
30+
)]
2631
pub up: Option<String>,
27-
#[serde(skip_serializing_if = "Option::is_none")]
32+
#[serde(
33+
default,
34+
skip_serializing_if = "Option::is_none",
35+
deserialize_with = "deserialize_from_string"
36+
)]
2837
pub down: Option<String>,
2938
#[serde(skip_serializing_if = "Option::is_none")]
3039
pub obfs: Option<String>,
@@ -62,7 +71,29 @@ impl ProxyAdapter for Hysteria2 {
6271
}
6372

6473
fn to_link(&self) -> String {
65-
todo!()
74+
let mut params = "insecure=".to_string() + if self.skip_cert_verify.unwrap_or(false) { "1" } else { "0" };
75+
if let Some(sni) = &self.sni {
76+
params += &format!("&sni={}", urlencoding::encode(sni));
77+
}
78+
if let Some(obfs) = &self.obfs {
79+
params += &format!("&obfs={}", obfs);
80+
}
81+
if let Some(obfs_password) = &self.obfs_password {
82+
params += &format!("&obfs-password={}", obfs_password);
83+
}
84+
if let Some(ports) = &self.ports {
85+
params += &format!("&mport={}", ports);
86+
}
87+
if let Some(up) = &self.up {
88+
params += &format!("&up={}", urlencoding::encode(up));
89+
}
90+
if let Some(down) = &self.down {
91+
params += &format!("&down={}", urlencoding::encode(down));
92+
}
93+
if let Some(alpn) = &self.alpn {
94+
params += &format!("&alpn={}", alpn.join(","));
95+
}
96+
format!("hysteria2://{}@{}:{}/?{}#{}", &self.password, &self.server, &self.port, &params, urlencoding::encode(&self.name))
6697
}
6798

6899
/*
@@ -102,6 +133,7 @@ impl ProxyAdapter for Hysteria2 {
102133
let sni = params_map.get("sni").cloned();
103134
let up = params_map.get("up").cloned();
104135
let down = params_map.get("down").cloned();
136+
let mut ports = params_map.get("mport").cloned();
105137
let mut alpn = None;
106138
if let Some(value) = params_map.get("alpn").cloned() {
107139
alpn = Some(
@@ -122,7 +154,6 @@ impl ProxyAdapter for Hysteria2 {
122154
let parts: Vec<&str> = parts[1].split(":").collect();
123155
let server = String::from(parts[0]);
124156
let port;
125-
let mut ports = None;
126157
match parts[1].parse::<u16>() {
127158
Ok(p) => {
128159
port = p;
@@ -185,16 +216,37 @@ mod test {
185216
use super::*;
186217

187218
#[test]
188-
fn test_parse_vless() {
219+
fn test_parse_hysteria2() {
189220
let link = String::from("hysteria2://bfbe4deb-07c8-450b-945e-e3c7676ba5ed@163.123.192.167:50000/?insecure=1&sni=www.microsoft.com&mport=50000-50080#%E5%89%A9%E4%BD%99%E6%B5%81%E9%87%8F%EF%BC%9A163.97%20GB");
190-
let hysteria2 = Hysteria2::from_link(link).unwrap();
221+
let hysteria2 = Hysteria2::from_link(link.clone()).unwrap();
191222
assert_eq!(hysteria2.server, "163.123.192.167");
192223
assert_eq!(hysteria2.port, 50000);
193224
assert_eq!(hysteria2.ports, Some("50000-50080".to_string()));
194225
assert_eq!(hysteria2.password, "bfbe4deb-07c8-450b-945e-e3c7676ba5ed");
195226
assert_eq!(hysteria2.sni, Some("www.microsoft.com".to_string()));
196227
assert_eq!(hysteria2.skip_cert_verify, Some(true));
197-
assert_eq!(hysteria2.fingerprint, Some("chrome".to_string()));
198-
println!("{}", hysteria2.to_json().unwrap());
228+
assert_eq!(hysteria2.client_fingerprint, Some("chrome".to_string()));
229+
assert_eq!(hysteria2.to_link(), link);
230+
}
231+
232+
#[test]
233+
fn test_hysteria2_from_json() {
234+
let json = r#"{
235+
"auth": "836e5ec1-382f-4325-a1dd-e6b5cf2a3632",
236+
"name": "JP_01",
237+
"password": "836e5ec1-382f-4325-a1dd-e6b5cf2a3632",
238+
"port": 23030,
239+
"up": 100,
240+
"down": 100,
241+
"server": "tokyo2-500m.node.xn--l6qx3lcvp58x.com",
242+
"skip-cert-verify": true,
243+
"sni": "2.nodes.yljc.online",
244+
"type": "hysteria2",
245+
"udp": true
246+
}"#;
247+
248+
let hysteria: Hysteria2 = serde_json::from_str(json).unwrap();
249+
assert_eq!(hysteria.name, "JP_01");
250+
assert_eq!(hysteria.up, Some("100".to_string()))
199251
}
200252
}

proxrs/src/protocol/mod.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -293,6 +293,23 @@ where
293293
}
294294
}
295295

296+
pub fn deserialize_from_string<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
297+
where
298+
D: Deserializer<'de>,
299+
{
300+
// 使用 Option<serde_json::Value> 来处理任意类型
301+
let value: Value = Deserialize::deserialize(deserializer)?;
302+
match value {
303+
Value::Number(n) => {
304+
Ok(Some(n.to_string()))
305+
}
306+
Value::String(s) => {
307+
Ok(Some(s))
308+
}
309+
_ => Err(serde::de::Error::custom("Expected a string")),
310+
}
311+
}
312+
296313
#[cfg(test)]
297314
mod test {
298315
use super::*;

0 commit comments

Comments
 (0)