From e999584abd73bb6c0f534941c252759744d5a23a Mon Sep 17 00:00:00 2001 From: Antonio Yang Date: Mon, 20 Apr 2026 17:26:22 +0800 Subject: [PATCH 1/6] client: add x-cfg, format, example in JsonSchema Pass field meta information of json schema to client, such that mobile clients can based on the meta information to show the field really works in mobile client - pass textarea format for larger String field - pass password format for credentials String field - pass x-cfg information - add example description for durations and socket address fields --- lightway-client/src/config.rs | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/lightway-client/src/config.rs b/lightway-client/src/config.rs index 48b18007..15252775 100644 --- a/lightway-client/src/config.rs +++ b/lightway-client/src/config.rs @@ -34,11 +34,13 @@ pub struct Config { doc = "Generate configure for single server in the format to path from `-c, --config-file`" ))] #[serde(skip_serializing)] + #[schemars(skip)] pub generate: ConfigFormat, #[patch(attribute(clap(short, long)))] #[patch(attribute(doc = "Config File to load"))] #[serde(skip_serializing)] + #[schemars(skip)] pub config_file: PathBuf, /// Servers to attempt to connect to. Configuration is only supported in @@ -46,11 +48,13 @@ pub struct Config { #[patch(attribute(clap(skip)))] #[serde(default)] #[serde(skip_serializing)] + #[schemars(skip)] pub servers: Vec, #[patch(attribute(clap(short, long)))] #[patch(attribute(doc = r#"Server to connect to in `:` format Only used if `servers` is empty"#))] + /// socket address, ex: 127.0.0.1:27690 pub server: String, #[patch(attribute(clap(short, long)))] @@ -72,6 +76,7 @@ pub struct Config { #[patch(attribute(doc = r#"Auth token If both token and user/pass are provided, token auth will be used. user/pass will be ignored in this case"#))] + #[schemars(extend("format" = "password"))] pub token: Option, #[patch(attribute(clap(short, long, hide = true)))] @@ -80,12 +85,14 @@ pub struct Config { #[patch(attribute(clap(short, long, hide = true)))] #[patch(attribute(doc = "Password for auth"))] + #[schemars(extend("format" = "password"))] pub password: Option, #[patch(attribute(clap(long)))] #[patch(attribute(doc = r#"CA certificate This can either be a path to the file, or a string starting with "-----BEGIN CERTIFICATE-----""#))] + #[schemars(extend("format" = "textarea"))] pub ca_cert: String, #[patch(attribute(clap(long)))] @@ -99,6 +106,7 @@ pub struct Config { #[cfg(windows)] #[patch(attribute(clap(long)))] #[patch(attribute(doc = "Path to wintun.dll file (Windows only)"))] + #[schemars(extend("x-cfg" = "windows"))] pub wintun_file: Option, #[cfg(windows)] @@ -115,6 +123,7 @@ pub struct Config { #[patch(attribute(clap(long)))] #[patch(attribute(doc = "Peer IP to use in Tun device"))] + #[schemars(extend("x-cfg" = "desktop"))] pub tun_peer_ip: Ipv4Addr, #[patch(attribute(clap(long)))] @@ -123,16 +132,20 @@ pub struct Config { #[cfg(feature = "postquantum")] #[patch(attribute(clap(long, value_enum)))] + #[patch(attribute(doc = "Enable Post Quantum Crypto"))] + #[schemars(extend("x-cfg" = "desktop"))] pub keyshare: KeyShare, #[patch(attribute(clap(long)))] #[patch(attribute(doc = "Interval between keepalives"))] #[schemars(schema_with = "lightway_app_utils::args::nonzero_duration_schema")] + /// ex: 10s pub keepalive_interval: NonZeroDuration, #[patch(attribute(clap(long)))] #[patch(attribute(doc = "Keepalive timeout"))] #[schemars(schema_with = "lightway_app_utils::args::nonzero_duration_schema")] + /// ex: 60s pub keepalive_timeout: NonZeroDuration, #[patch(attribute(clap(long)))] @@ -144,7 +157,9 @@ pub struct Config { #[patch(attribute(clap(long)))] #[patch(attribute(doc = r#"Time it takes to trigger a tracer packet when we haven't received an outside packet"#))] + #[schemars(extend("x-cfg" = "desktop"))] #[schemars(schema_with = "lightway_app_utils::args::nonzero_duration_schema")] + /// ex: 10s pub tracer_packet_timeout: NonZeroDuration, // NOTE: also "Defer timeout" in mobile device @@ -154,16 +169,21 @@ pub struct Config { If the preferred connection connects before the timeout, it will be used immediately."#) )] #[schemars(schema_with = "lightway_app_utils::args::duration_schema")] + /// ex: 2000ms pub preferred_connection_wait_interval: Duration, #[patch(attribute(clap(long)))] #[patch(attribute(doc = "Socket send buffer size"))] + #[schemars(extend("x-cfg" = "desktop"))] #[schemars(schema_with = "byte_size_schema")] + /// ex: 1.5 MiB pub sndbuf: ByteSize, #[patch(attribute(clap(long)))] #[patch(attribute(doc = "Socket receive buffer size"))] + #[schemars(extend("x-cfg" = "desktop"))] #[schemars(schema_with = "byte_size_schema")] + /// ex: 1.5 MiB pub rcvbuf: ByteSize, #[cfg(batch_receive)] @@ -173,6 +193,7 @@ pub struct Config { #[patch(attribute( doc = "Enable batch receive (`recvmsg_x` on macOS, `recvmmsg` on Linux/Android))" ))] + #[schemars(extend("x-cfg" = "batch_receive"))] pub enable_batch_receive: bool, #[cfg(desktop)] @@ -182,6 +203,7 @@ pub struct Config { default: Sets up routes as specified in server, tun_local_ip, tun_peer_ip, tun_dns_ip noexec : Does not setup any routes lan : Sets up default + additional lan routes"#))] + #[schemars(extend("x-cfg" = "desktop"))] pub route_mode: RouteMode, #[cfg(desktop)] @@ -190,6 +212,7 @@ pub struct Config { Modes: default: Sets up DNS Configuration based on target platform noexec : Skips DNS Configuration setup"#))] + #[schemars(extend("x-cfg" = "desktop"))] pub dns_config_mode: DnsConfigMode, #[patch(attribute(clap(long, value_enum)))] @@ -211,29 +234,35 @@ pub struct Config { #[patch(empty_value = false)] #[patch(attribute(serde(default)))] #[patch(attribute(doc = "Enable PMTU discovery for [`ConnectionType::Udp`] connections"))] + #[schemars(extend("x-cfg" = "desktop"))] pub enable_pmtud: bool, #[patch(attribute(clap(long)))] #[patch(attribute(doc = "Base MTU to use for PMTU discovery"))] + #[schemars(extend("x-cfg" = "desktop"))] pub pmtud_base_mtu: Option, #[patch(attribute(clap(long)))] #[patch(empty_value = false)] #[patch(attribute(serde(default)))] #[patch(attribute(doc = "Enable IO-uring interface for Tunnel"))] + #[schemars(extend("x-cfg" = "linux"))] pub enable_tun_iouring: bool, // Any value more than 1024 negatively impact the throughput #[patch(attribute(clap(long)))] #[patch(attribute(doc = r#"IO-uring submission queue count. Only applicable when `enable_tun_iouring` is `true`"#))] + #[schemars(extend("x-cfg" = "linux"))] pub iouring_entry_count: usize, #[patch(attribute(clap(long)))] #[patch(attribute(doc = r#"IO-uring sqpoll idle time. If non-zero use a kernel thread to perform submission queue polling. After the given idle time the thread will go to sleep."#))] + #[schemars(extend("x-cfg" = "linux"))] #[schemars(schema_with = "lightway_app_utils::args::duration_schema")] + /// ex: 100ms pub iouring_sqpoll_idle_time: Duration, #[patch(attribute(clap(short, long)))] @@ -244,11 +273,13 @@ pub struct Config { Only used if a codec is set"#) )] #[serde(alias = "enable_inside_pkt_encoding_at_connect")] + #[schemars(extend("x-cfg" = "desktop"))] pub enable_inside_pkt_encoding: bool, #[cfg(feature = "debug")] #[patch(attribute(clap(long)))] #[patch(attribute(doc = "File path to save wireshark keylog"))] + #[schemars(extend("x-cfg" = "desktop"))] pub keylog: Option, #[cfg(feature = "debug")] @@ -256,6 +287,7 @@ pub struct Config { #[patch(empty_value = false)] #[patch(attribute(serde(default)))] #[patch(attribute(doc = "Enable TLS debug logging"))] + #[schemars(extend("x-cfg" = "desktop"))] pub tls_debug: bool, #[cfg(windows)] @@ -274,11 +306,13 @@ pub struct Config { #[patch(attribute(serde(default)))] #[patch(attribute(doc = r#"Enable DPAPI encryption/decryption for config file Only for Windows platform"#))] + #[schemars(extend("x-cfg" = "windows"))] pub enable_dpapi: bool, /// SNI header for TLS connections #[cfg(feature = "mobile")] #[patch(attribute(clap(skip)))] + #[schemars(extend("x-cfg" = "mobile"))] pub sni_header: String, } From 03d5304a67a3daabbde23f8de1931b6de2646c6b Mon Sep 17 00:00:00 2001 From: Antonio Yang Date: Thu, 30 Apr 2026 10:50:12 +0800 Subject: [PATCH 2/6] client: mobile: apply yaml config to mobile By adding a config_content input, the mobile will easy to apply yaml configs as the same as cli client using, such that we can easy to treat all clients in a unify way, and also the OSS client can directly use dynamic UI, such that the overall maintenance efforts will be less. --- lightway-client/src/lib.rs | 2 ++ lightway-client/src/mobile.rs | 33 +++++++++++++++++++++++++++------ 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/lightway-client/src/lib.rs b/lightway-client/src/lib.rs index 12d72db9..ac3403dd 100644 --- a/lightway-client/src/lib.rs +++ b/lightway-client/src/lib.rs @@ -114,6 +114,8 @@ pub enum LightwayError { Unauthorized, #[error("Config Error: `{0}`")] ConfigError(#[from] crate::config::Error), + #[error("Config Format Error: `{0}`")] + ConfigFormatError(#[from] serde_saphyr::Error), #[cfg(feature = "mobile")] #[error("Logging bridge initialization error: `{0}`")] diff --git a/lightway-client/src/mobile.rs b/lightway-client/src/mobile.rs index 87a9f648..f2c7a685 100644 --- a/lightway-client/src/mobile.rs +++ b/lightway-client/src/mobile.rs @@ -147,15 +147,13 @@ impl RustVpnConnection { endpoints: Vec, event_handler: Arc, raw_tun_fd: i32, - mobile_config: crate::config::MobileConfig, + mobile_config: Option, + config_content: String, ) -> Result { info!("start parallel Lightway connections"); let mut config = crate::config::Config::default(); - config.apply_mobile_config(mobile_config); - - info!("Received {} endpoints", endpoints.len()); - if endpoints.is_empty() { - return Err(LightwayError::EmptyEndpointsError); + if let Some(mobile_config) = mobile_config { + config.apply_mobile_config(mobile_config); } for endpoint in &endpoints { @@ -170,7 +168,30 @@ impl RustVpnConnection { }, ); } + config.apply_mobile_connect_configs(endpoints); + if !config_content.is_empty() { + use struct_patch::Patch; + config.apply(serde_saphyr::from_str(&config_content)?); + config.servers.push(crate::config::ConnectionConfig { + server: config.server.clone(), + mode: config.mode, + server_dn: config.server_dn.take(), + cipher: config.cipher, + outside_mtu: config.outside_mtu, + username: config.user.take(), + password: config.password.take(), + token: config.token.take(), + ca_cert: Some(config.ca_cert.clone()), + }); + } + + info!("Received {} endpoints", config.servers.len()); + if config.servers.is_empty() { + return Err(LightwayError::EmptyEndpointsError); + } + + tracing::debug!("Config:\n{config:#?}"); let mut builder = tokio::runtime::Builder::new_current_thread(); builder From f44fbbccf5546c6925a66b95bc7b35e016b4e19c Mon Sep 17 00:00:00 2001 From: Antonio Yang Date: Thu, 23 Apr 2026 10:23:58 +0800 Subject: [PATCH 3/6] ci: mobile: auto address project root The build and clean command in dev shell will always run on project root. --- nix/modules/devshells.nix | 2 ++ 1 file changed, 2 insertions(+) diff --git a/nix/modules/devshells.nix b/nix/modules/devshells.nix index 54b83ada..3448e77b 100644 --- a/nix/modules/devshells.nix +++ b/nix/modules/devshells.nix @@ -39,9 +39,11 @@ }; androidSdk = androidComposition.androidsdk; buildScript = pkgs.writeShellScriptBin "build" '' + cd "$(git rev-parse --show-toplevel 2>/dev/null)" cargo make build-android ''; cleanScript = pkgs.writeShellScriptBin "clean" '' + cd "$(git rev-parse --show-toplevel 2>/dev/null)" cargo make clean-android ''; pinned-cargo-ndk = pkgs.callPackage ../pkgs/cargo-ndk.nix { }; From 32e3f0b8907758dedd24aacb3bf08672dd2aa1e4 Mon Sep 17 00:00:00 2001 From: Antonio Yang Date: Thu, 30 Apr 2026 11:18:23 +0800 Subject: [PATCH 4/6] client: mobile: rename EventHandlers trait Remove rust prefix, because we do not have benefit from this, and it will still good to use in mobile client without issues. --- lightway-client/src/mobile.rs | 4 ++-- lightway-client/src/mobile/lightway.rs | 27 ++++++++++++-------------- 2 files changed, 14 insertions(+), 17 deletions(-) diff --git a/lightway-client/src/mobile.rs b/lightway-client/src/mobile.rs index f2c7a685..e20d2e49 100644 --- a/lightway-client/src/mobile.rs +++ b/lightway-client/src/mobile.rs @@ -35,7 +35,7 @@ impl TryFrom for ExpresslaneState { #[cfg_attr(not(feature = "mobile-test"), uniffi::export(with_foreign))] #[cfg_attr(test, mockall::automock)] -pub trait RustEventHandlers: Send + Sync { +pub trait EventHandlers: Send + Sync { /// Handles VPN connection status changes from the native Lightway client. /// State values: 2=Connecting, 6=LinkUp, 5=Authenticating, 7=Online, 4=Disconnecting, 1=Disconnected (from lightway-core) /// @@ -145,7 +145,7 @@ impl RustVpnConnection { fn parallel_connect( &self, endpoints: Vec, - event_handler: Arc, + event_handler: Arc, raw_tun_fd: i32, mobile_config: Option, config_content: String, diff --git a/lightway-client/src/mobile/lightway.rs b/lightway-client/src/mobile/lightway.rs index 1904e193..3caf987c 100644 --- a/lightway-client/src/mobile/lightway.rs +++ b/lightway-client/src/mobile/lightway.rs @@ -1,7 +1,7 @@ use crate::config::{Config, ConnectionConfig}; use crate::io::outside::OutsideIO; use crate::keepalive::{Keepalive, KeepaliveResult}; -use crate::mobile::RustEventHandlers; +use crate::mobile::EventHandlers; use crate::mobile::{DeviceNetworkState, ExpresslaneState}; use crate::{ ClientIpConfigCb, ClientResult, ConnectionState, inside_io_task, io, @@ -45,10 +45,7 @@ enum OutsideSocket { } impl OutsideSocket { - fn new( - use_tcp: bool, - event_handler: Option>, - ) -> uniffi::Result { + fn new(use_tcp: bool, event_handler: Option>) -> uniffi::Result { if use_tcp { let socket = TcpSocket::new_v4()?; let fd = socket.as_raw_fd(); @@ -177,7 +174,7 @@ async fn setup_tunnel_interface( pub(crate) async fn async_lightway_start( tun_fd: RawFd, - external_event_handler: Arc, + external_event_handler: Arc, config: Config, connected_index: Arc>, ) -> uniffi::Result { @@ -445,7 +442,7 @@ async fn restartable_outside_io_task( keepalive: Keepalive, notify_keepalive_reply: Arc, mut new_outside_io_receiver: MpscReceiver<()>, - external_event_handler: Arc, + external_event_handler: Arc, ) -> uniffi::Result<()> { let mut current_outside_io = outside_io_config.outside_io; let mut first_run = true; @@ -563,7 +560,7 @@ struct LightwayClientConnectArgs { expresslane_keys_rotation_interval: std::time::Duration, online_signal_sender: tokio::sync::mpsc::Sender, event_stream_handler: EventStreamCallback, - external_event_handler: Arc, + external_event_handler: Arc, } /// Individual connection to a lightway server @@ -728,7 +725,7 @@ async fn lightway_client_connect( async fn handle_global_events( mut stream: EventStream, connection_start_time: Instant, - event_handler: Arc, + event_handler: Arc, ) { let mut current_state = State::Connecting; let mut is_first_packet_received = false; @@ -890,7 +887,7 @@ async fn handle_network_change( #[cfg(test)] mod test { use super::*; - use crate::mobile::MockRustEventHandlers; + use crate::mobile::MockEventHandlers; use mockall::Sequence; use mockall::predicate::eq; @@ -907,7 +904,7 @@ mod test { // Make sure we don't advertise Online state in this function let mut seq = Sequence::new(); - let mut mock_event_handler = MockRustEventHandlers::new(); + let mut mock_event_handler = MockEventHandlers::new(); mock_event_handler .expect_handle_status_change() .times(1) @@ -941,7 +938,7 @@ mod test { sender.event(Event::StateChanged(State::Disconnected)); }); - let mut mock_event_handler = MockRustEventHandlers::new(); + let mut mock_event_handler = MockEventHandlers::new(); mock_event_handler .expect_handle_status_change() .times(0) @@ -961,7 +958,7 @@ mod test { sender.event(Event::FirstPacketReceived); }); - let mut mock_event_handler = MockRustEventHandlers::new(); + let mut mock_event_handler = MockEventHandlers::new(); mock_event_handler .expect_received_first_packet() .with(eq(174u64)) @@ -973,7 +970,7 @@ mod test { #[tokio::test] async fn test_outside_socket_new_calls_created_outside_fd() { // Test TCP socket creation - let mut mock_event_handler = MockRustEventHandlers::new(); + let mut mock_event_handler = MockEventHandlers::new(); mock_event_handler .expect_created_outside_fd() @@ -984,7 +981,7 @@ mod test { assert!(tcp_result.is_ok()); // Test UDP socket creation - let mut mock_event_handler = MockRustEventHandlers::new(); + let mut mock_event_handler = MockEventHandlers::new(); mock_event_handler .expect_created_outside_fd() From e5f57a0156b184184b1058fd90cd114093912bc4 Mon Sep 17 00:00:00 2001 From: Antonio Yang Date: Tue, 12 May 2026 17:36:38 +0800 Subject: [PATCH 5/6] client: mobile: only use config content as input Mobile client now using the same input to start the vpn network, and reduce the code complexity of client side. --- lightway-client/src/config.rs | 171 ---------------------------------- lightway-client/src/mobile.rs | 47 +++------- 2 files changed, 13 insertions(+), 205 deletions(-) diff --git a/lightway-client/src/config.rs b/lightway-client/src/config.rs index 15252775..1937b719 100644 --- a/lightway-client/src/config.rs +++ b/lightway-client/src/config.rs @@ -16,9 +16,6 @@ use std::time::Duration as StdDuration; use std::{net::Ipv4Addr, path::PathBuf}; use struct_patch::Patch; -#[cfg(feature = "mobile")] -use std::net::IpAddr; - // NOTE // The cli argument and options all set to ConfigPatch, which is generated by // Patch derive, do NOT set defualts in the clap macros, and let all default @@ -342,45 +339,6 @@ impl Config { .expect("the path already initialized if it is none"), ) } - - #[cfg(feature = "mobile")] - pub fn apply_mobile_config(&mut self, config: MobileConfig) { - match config.dns_ip.parse::() { - Ok(ip) => self.tun_dns_ip = ip, - Err(e) => tracing::warn!("dns ip is invalid: {e}. Using default."), - } - - match config.local_ip.parse::() { - Ok(ip) => self.tun_local_ip = ip, - Err(e) => tracing::warn!("local ip is invalid: {e}. Using default."), - } - - if let Ok(defer_interval) = u64::try_from(config.defer_timeout) { - self.preferred_connection_wait_interval = - Duration::from_std_duration(StdDuration::from_millis(defer_interval)); - } else { - tracing::warn!( - "Defer timeout value {} is invalid. Using default value.", - config.defer_timeout - ); - } - - let MobileConfig { - enable_heart_beat, - enable_expresslane, - sni_header, - .. - } = config; - - self.keepalive_continuous = enable_heart_beat; - self.enable_expresslane = enable_expresslane; - self.sni_header = sni_header - } - - #[cfg(feature = "mobile")] - pub fn apply_mobile_connect_configs(&mut self, configs: Vec) { - self.servers = configs.into_iter().map(|c| c.into()).collect() - } } impl Default for Config { @@ -544,135 +502,6 @@ pub enum Error { InsufficientAuth, } -#[cfg(any(feature = "mobile", feature = "mobile-test"))] -#[derive(Debug)] -#[cfg_attr( - all(feature = "mobile", not(feature = "mobile-test")), - derive(uniffi::Record) -)] -/// User settings from the mobile app, it is a small set config and easier to use in mobile -pub struct MobileConfig { - /// Enable continuous/NAT keep-alive - pub enable_heart_beat: bool, - /// SNI header for TLS connections - pub sni_header: String, - /// Defer timeout in milliseconds - defer_timeout: i64, - /// Enable Expresslane for UDP connections - pub enable_expresslane: bool, - /// The local ip for TUN interface in ipv4 format - pub local_ip: String, - /// The dns ip in ipv4 format - pub dns_ip: String, -} - -/// Endpoint settings from the mobile app, it is a small set and easier to use in mobile -#[cfg(feature = "mobile")] -#[derive(Clone)] -#[cfg_attr( - all(feature = "mobile", not(feature = "mobile-test")), - derive(uniffi::Record) -)] -pub struct MobileConnectionConfig { - pub server_ip: IpAddress, - pub port: u16, - pub server_dn: String, - pub username: Option, - pub password: Option, - pub auth_token: Option, - pub ca_cert: String, - pub use_tcp: bool, - pub use_cha_cha_20: bool, - pub outside_mtu: u32, -} - -#[cfg(feature = "mobile")] -impl From for ConnectionConfig { - fn from( - MobileConnectionConfig { - server_ip, - port, - server_dn, - username, - password, - auth_token, - ca_cert, - use_tcp, - use_cha_cha_20, - outside_mtu, - }: MobileConnectionConfig, - ) -> ConnectionConfig { - ConnectionConfig { - server: format!("{}:{}", server_ip.ip, port), - mode: if use_tcp { - ConnectionType::Tcp - } else { - ConnectionType::Udp - }, - server_dn: if server_dn.trim().is_empty() { - None - } else { - Some(server_dn) - }, - cipher: if use_cha_cha_20 { - Cipher::Chacha20 - } else { - Cipher::default() - }, - username, - password, - token: auth_token, - outside_mtu: outside_mtu as usize, - ca_cert: if ca_cert.trim().is_empty() { - None - } else { - Some(ca_cert) - }, - } - } -} - -// Ref: https://mozilla.github.io/uniffi-rs/0.27/proc_macro/index.html#the-unifficustom_type-and-unifficustom_newtype-macros -#[cfg(all(feature = "mobile", not(feature = "mobile-test")))] -uniffi::custom_type!(IpAddress, String); - -#[cfg(feature = "mobile")] -#[derive(Debug, Eq, PartialEq, Clone)] -/// Custom type with `String` as the `Builtin` bridge -pub struct IpAddress { - pub ip: IpAddr, -} - -#[cfg(feature = "mobile")] -impl std::fmt::Display for IpAddress { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.ip) - } -} - -#[cfg(all(feature = "mobile", not(feature = "mobile-test")))] -impl crate::UniffiCustomTypeConverter for IpAddress { - type Builtin = String; - - fn into_custom(val: Self::Builtin) -> uniffi::Result { - use std::str::FromStr; - Ok(IpAddress { - ip: IpAddr::from_str(val.as_str())?, - }) - } - - fn from_custom(obj: Self) -> Self::Builtin { - obj.ip.to_string() - } -} - -#[cfg(feature = "mobile")] -impl From for IpAddr { - fn from(val: IpAddress) -> Self { - val.ip - } -} - fn take_auth( token: Option, user: Option, diff --git a/lightway-client/src/mobile.rs b/lightway-client/src/mobile.rs index e20d2e49..0ef40bb4 100644 --- a/lightway-client/src/mobile.rs +++ b/lightway-client/src/mobile.rs @@ -3,6 +3,7 @@ pub(crate) mod lightway; pub(crate) mod tracing_utils; use std::sync::{Arc, OnceLock}; +use struct_patch::Patch; use tracing::info; #[derive(Debug, PartialEq, Eq)] @@ -144,47 +145,25 @@ impl RustVpnConnection { /// `LightwayError` for proper error handling. fn parallel_connect( &self, - endpoints: Vec, event_handler: Arc, raw_tun_fd: i32, - mobile_config: Option, config_content: String, ) -> Result { info!("start parallel Lightway connections"); let mut config = crate::config::Config::default(); - if let Some(mobile_config) = mobile_config { - config.apply_mobile_config(mobile_config); - } - for endpoint in &endpoints { - info!( - "Endpoint {}:{} with {}", - endpoint.server_ip, - endpoint.port, - if endpoint.use_tcp { - "lightway_tcp" - } else { - "lightway_udp" - }, - ); - } - - config.apply_mobile_connect_configs(endpoints); - if !config_content.is_empty() { - use struct_patch::Patch; - config.apply(serde_saphyr::from_str(&config_content)?); - config.servers.push(crate::config::ConnectionConfig { - server: config.server.clone(), - mode: config.mode, - server_dn: config.server_dn.take(), - cipher: config.cipher, - outside_mtu: config.outside_mtu, - username: config.user.take(), - password: config.password.take(), - token: config.token.take(), - ca_cert: Some(config.ca_cert.clone()), - }); - } + config.apply(serde_saphyr::from_str(&config_content)?); + config.servers.push(crate::config::ConnectionConfig { + server: config.server.clone(), + mode: config.mode, + server_dn: config.server_dn.take(), + cipher: config.cipher, + outside_mtu: config.outside_mtu, + username: config.user.take(), + password: config.password.take(), + token: config.token.take(), + ca_cert: Some(config.ca_cert.clone()), + }); info!("Received {} endpoints", config.servers.len()); if config.servers.is_empty() { From 1abe436110cac52f1aa033f21a7cf4b09951371f Mon Sep 17 00:00:00 2001 From: Antonio Yang Date: Tue, 12 May 2026 17:49:44 +0800 Subject: [PATCH 6/6] client: rn user of ConnectionConfig Use the same field name for the user name of auth in global level (Config) and in connection level (ConnectionConfig) --- lightway-client/src/config.rs | 8 ++------ lightway-client/src/mobile.rs | 2 +- lightway-client/src/platform/windows/crypto.rs | 4 ++-- 3 files changed, 5 insertions(+), 9 deletions(-) diff --git a/lightway-client/src/config.rs b/lightway-client/src/config.rs index 1937b719..87b7109f 100644 --- a/lightway-client/src/config.rs +++ b/lightway-client/src/config.rs @@ -429,7 +429,7 @@ pub struct ConnectionConfig { /// Username for User/Pass Auth #[serde(default)] - pub username: Option, + pub user: Option, /// Passwordfor User/Pass Auth #[serde(default)] @@ -452,11 +452,7 @@ impl ConnectionConfig { /// Try build auth from config #[cfg(feature = "mobile")] pub fn take_auth(&mut self) -> Result { - take_auth( - self.token.take(), - self.username.take(), - self.password.take(), - ) + take_auth(self.token.take(), self.user.take(), self.password.take()) } /// Try build CA from ca_crt diff --git a/lightway-client/src/mobile.rs b/lightway-client/src/mobile.rs index 0ef40bb4..33c78e97 100644 --- a/lightway-client/src/mobile.rs +++ b/lightway-client/src/mobile.rs @@ -159,7 +159,7 @@ impl RustVpnConnection { server_dn: config.server_dn.take(), cipher: config.cipher, outside_mtu: config.outside_mtu, - username: config.user.take(), + user: config.user.take(), password: config.password.take(), token: config.token.take(), ca_cert: Some(config.ca_cert.clone()), diff --git a/lightway-client/src/platform/windows/crypto.rs b/lightway-client/src/platform/windows/crypto.rs index 704f4444..82d56b23 100644 --- a/lightway-client/src/platform/windows/crypto.rs +++ b/lightway-client/src/platform/windows/crypto.rs @@ -19,14 +19,14 @@ mod tests { #[derive(Debug, Serialize, PartialEq)] struct TestConfig { server: String, - username: String, + user: String, password: String, } fn generate_mock_config() -> String { let config = TestConfig { server: "vpn.example.com".to_string(), - username: "user1".to_string(), + user: "user1".to_string(), password: "securepassword".to_string(), }; serde_saphyr::to_string(&config).unwrap()