Skip to main content

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}