vpn_link_serde/lib.rs
1//! # VPN Link Serde
2//!
3//! A comprehensive Rust library for parsing and serializing VPN proxy protocol links.
4//! Supports VMess, VLess, Shadowsocks, Trojan, and Hysteria2 protocols.
5//!
6//! ## Features
7//!
8//! - Parse protocol links into structured Rust types
9//! - Serialize structured types back into protocol links
10//! - Full support for all protocol variants and parameters
11//! - Comprehensive error handling
12//! - Serde support for serialization/deserialization
13//!
14//! ## Supported Protocols
15//!
16//! - **[VMess]** (`vmess://`) — V1 and V2 formats; serialization always outputs V2
17//! - **[VLess]** (`vless://`) — Full parameter support including Reality, XTLS
18//! - **[Shadowsocks]** (`ss://`) — SIP002 (Base64 userinfo), plugin and tag
19//! - **[Trojan]** (`trojan://`) — TLS, XTLS, query and fragment (remark)
20//! - **[Hysteria2]** (`hysteria2://`) — Optional auth, official and extended query params
21//!
22//! ## Link format and parsing rules (unified)
23//!
24//! - **Scheme prefix**: Case-insensitive (e.g. `VMESS://` is valid).
25//! - **Port**: 1–65535; VMess allows port as number or string in JSON; others require a valid u16.
26//! - **Query string**: Parsed as `application/x-www-form-urlencoded`; parameter names are case-sensitive.
27//! - **Fragment (`#`)**: Decoded as remark/tag; non-ASCII and spaces must be URL-encoded.
28//! - **Errors**: Invalid format → `InvalidFormat`; invalid or missing required field → `InvalidField`;
29//! unknown scheme → `UnsupportedProtocol`.
30//!
31//! See each type's module (e.g. [VMess], [VLess]) for format details, required/optional fields, and serialization rules.
32//!
33//! ## References
34//!
35//! Link formats and parsing rules follow the specifications and community conventions:
36//! - VMess: [Project V VMess](https://www.v2ray.com/en/configuration/protocols/vmess.html), [V2Fly Guide](https://guide.v2fly.org/en_US/basics/vmess.html)
37//! - VLESS: [XTLS VLESS](https://xtls.github.io/en/config/inbounds/vless.html)
38//! - Shadowsocks: [SIP002 URI Scheme](https://shadowsocks.org/doc/sip002.html) (RFC 3986)
39//! - Trojan: [Trojan Protocol](https://trojan-gfw.github.io/trojan/protocol.html), [Trojan-Go URL](https://azadzadeh.github.io/trojan-go/en/developer/url/)
40//! - Hysteria2: [Hysteria 2 URI Scheme](https://v2.hysteria.network/docs/developers/URI-Scheme)
41//!
42//! A detailed specification (link format, parsing rules, and serialization) is available in the repository at `doc/protocols.md`.
43//!
44//! ## Example
45//!
46//! ```rust
47//! use vpn_link_serde::Protocol;
48//!
49//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
50//! // Parse any protocol link
51//! let protocol = Protocol::parse("vmess://eyJ2IjoiMiIsImFkZCI6IjEyNy4wLjAuMSIsInBvcnQiOjQ0MywiaWQiOiJ1dWlkLTEyMyJ9")?;
52//!
53//! // Generate link from parsed protocol
54//! let link = protocol.to_link()?;
55//! # Ok(())
56//! # }
57//! ```
58//!
59//! ## License
60//!
61//! Licensed under either of
62//! - MIT license ([LICENSE-MIT](LICENSE-MIT) or <http://opensource.org/licenses/MIT>)
63//!
64
65#![warn(missing_docs)]
66#![warn(rustdoc::missing_crate_level_docs)]
67
68mod constants;
69mod error;
70mod hysteria2;
71mod shadowsocks;
72mod trojan;
73mod vless;
74mod vmess;
75
76#[cfg(test)]
77mod protocols_comprehensive;
78
79pub use error::{ProtocolError, Result};
80pub use hysteria2::{Hysteria2, Hysteria2Config};
81pub use shadowsocks::{Shadowsocks, ShadowsocksConfig};
82pub use trojan::{Trojan, TrojanConfig};
83pub use vless::{VLess, VLessConfig};
84pub use vmess::{VMess, VMessV2};
85
86/// Trait for protocol parsers that can parse links and generate links
87pub trait ProtocolParser: Sized {
88 /// Parse a protocol link string into a structured configuration
89 ///
90 /// # Errors
91 ///
92 /// Returns `ProtocolError` if the link format is invalid or unsupported.
93 ///
94 /// # Example
95 ///
96 /// ```rust
97 /// use vpn_link_serde::{VMess, ProtocolParser};
98 ///
99 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
100 /// let vmess = VMess::parse("vmess://eyJ2IjoiMiIsImFkZCI6IjEyNy4wLjAuMSIsInBvcnQiOjQ0MywiaWQiOiJ1dWlkLTEyMyJ9")?;
101 /// # Ok(())
102 /// # }
103 /// ```
104 fn parse(link: &str) -> Result<Self>;
105
106 /// Generate a protocol link string from the structured configuration
107 ///
108 /// # Errors
109 ///
110 /// Returns `ProtocolError` if the configuration is invalid or cannot be serialized.
111 ///
112 /// # Example
113 ///
114 /// ```rust
115 /// use vpn_link_serde::{VMess, ProtocolParser};
116 ///
117 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
118 /// let vmess = VMess::parse("vmess://eyJ2IjoiMiIsImFkZCI6IjEyNy4wLjAuMSIsInBvcnQiOjQ0MywiaWQiOiJ1dWlkLTEyMyJ9")?;
119 /// let link = vmess.to_link()?;
120 /// # Ok(())
121 /// # }
122 /// ```
123 fn to_link(&self) -> Result<String>;
124}
125
126/// Enum representing different protocol types
127///
128/// This enum provides a unified interface for working with different VPN protocols.
129/// Use `Protocol::parse()` to automatically detect and parse any supported protocol link.
130///
131/// # Example
132///
133/// ```rust
134/// use vpn_link_serde::Protocol;
135///
136/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
137/// // Automatically detect protocol type
138/// let protocol = Protocol::parse("vmess://eyJ2IjoiMiIsImFkZCI6IjEyNy4wLjAuMSIsInBvcnQiOjQ0MywiaWQiOiJ1dWlkLTEyMyJ9")?;
139///
140/// match &protocol {
141/// Protocol::VMess(v) => println!("VMess: {}", v.config.add),
142/// Protocol::VLess(v) => println!("VLess: {}", v.config.address),
143/// Protocol::Shadowsocks(s) => println!("Shadowsocks: {}", s.config.address),
144/// Protocol::Trojan(t) => println!("Trojan: {}", t.config.address),
145/// Protocol::Hysteria2(h) => println!("Hysteria2: {}", h.config.host),
146/// }
147///
148/// // Generate link
149/// let link = protocol.to_link()?;
150/// # Ok(())
151/// # }
152/// ```
153#[derive(Debug, Clone, PartialEq)]
154pub enum Protocol {
155 /// VMess protocol
156 VMess(VMess),
157 /// VLess protocol
158 VLess(VLess),
159 /// Shadowsocks protocol
160 Shadowsocks(Shadowsocks),
161 /// Trojan protocol
162 Trojan(Trojan),
163 /// Hysteria2 protocol
164 Hysteria2(Hysteria2),
165}
166
167impl Protocol {
168 /// Parse any protocol link and return the appropriate protocol variant
169 ///
170 /// Automatically detects the protocol type based on the link prefix and parses it accordingly.
171 ///
172 /// # Arguments
173 ///
174 /// * `link` - The protocol link string (e.g., "vmess://...", "vless://...")
175 ///
176 /// # Returns
177 ///
178 /// Returns `Ok(Protocol)` with the appropriate variant if parsing succeeds,
179 /// or `Err(ProtocolError)` if the link is invalid or unsupported.
180 ///
181 /// # Example
182 ///
183 /// ```rust
184 /// use vpn_link_serde::Protocol;
185 ///
186 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
187 /// let protocol = Protocol::parse("vmess://eyJ2IjoiMiIsImFkZCI6IjEyNy4wLjAuMSIsInBvcnQiOjQ0MywiaWQiOiJ1dWlkLTEyMyJ9")?;
188 /// # Ok(())
189 /// # }
190 /// ```
191 pub fn parse(link: &str) -> Result<Self> {
192 use crate::constants::scheme;
193
194 let link_lower = link.to_lowercase();
195
196 if link_lower.starts_with(scheme::VMESS) {
197 Ok(Protocol::VMess(VMess::parse(link)?))
198 } else if link_lower.starts_with(scheme::VLESS) {
199 Ok(Protocol::VLess(VLess::parse(link)?))
200 } else if link_lower.starts_with(scheme::SHADOWSOCKS) {
201 Ok(Protocol::Shadowsocks(Shadowsocks::parse(link)?))
202 } else if link_lower.starts_with(scheme::TROJAN) {
203 Ok(Protocol::Trojan(Trojan::parse(link)?))
204 } else if link_lower.starts_with(scheme::HYSTERIA2) {
205 Ok(Protocol::Hysteria2(Hysteria2::parse(link)?))
206 } else {
207 let scheme_name = link.split("://").next().unwrap_or("unknown");
208 Err(ProtocolError::UnsupportedProtocol(format!(
209 "Unsupported protocol: {}",
210 scheme_name
211 )))
212 }
213 }
214
215 /// Generate a link string from the protocol
216 ///
217 /// Converts the parsed protocol configuration back into a link string.
218 ///
219 /// # Returns
220 ///
221 /// Returns `Ok(String)` with the generated link if successful,
222 /// or `Err(ProtocolError)` if the configuration cannot be serialized.
223 ///
224 /// # Example
225 ///
226 /// ```rust
227 /// use vpn_link_serde::Protocol;
228 ///
229 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
230 /// let protocol = Protocol::parse("vmess://eyJ2IjoiMiIsImFkZCI6IjEyNy4wLjAuMSIsInBvcnQiOjQ0MywiaWQiOiJ1dWlkLTEyMyJ9")?;
231 /// let link = protocol.to_link()?;
232 /// # Ok(())
233 /// # }
234 /// ```
235 pub fn to_link(&self) -> Result<String> {
236 match self {
237 Protocol::VMess(v) => v.to_link(),
238 Protocol::VLess(v) => v.to_link(),
239 Protocol::Shadowsocks(s) => s.to_link(),
240 Protocol::Trojan(t) => t.to_link(),
241 Protocol::Hysteria2(h) => h.to_link(),
242 }
243 }
244}