use anyhow::Context; use jni::JNIEnv; use jni::objects::{JClass, JObject, JString}; use jni::sys::{jboolean, jint, jlong, jstring}; use parking_lot::Mutex; use std::collections::HashMap; use std::net::Ipv4Addr; use std::sync::Arc; use tokio::runtime::Runtime; use vnt_core::api::VntApi; use vnt_core::context::config::{Config, DeviceMode}; use vnt_core::core::{NetworkManager, RegisterResponse}; use vnt_core::nat::NetInput; use vnt_core::port_mapping::PortMapping; use vnt_core::tls::verifier::CertValidationMode; use vnt_core::tunnel_core::server::transport::config::ProtocolAddress; use vnt_core::utils::task_control::{TaskGroupGuard, TaskGroupManager}; /// 全局状态管理 struct GlobalState { /// Tokio运行时(Arc包装以便多线程访问) runtime: Arc, /// 网络管理器实例 network_managers: HashMap>>>, /// API实例 vnt_apis: HashMap, /// 任务组管理器 task_group_managers: HashMap, /// 任务组守卫(drop 时会停止任务组,必须持有到 nativeStop) task_group_guards: HashMap, /// 下一个实例ID next_id: i64, } impl GlobalState { fn new() -> anyhow::Result { Ok(Self { runtime: Arc::new(Runtime::new()?), network_managers: HashMap::new(), vnt_apis: HashMap::new(), task_group_managers: HashMap::new(), task_group_guards: HashMap::new(), next_id: 1, }) } } lazy_static::lazy_static! { static ref GLOBAL_STATE: Mutex> = Mutex::new(None); } /// 从 panic payload 中提取错误消息 fn panic_message(e: Box) -> String { if let Some(s) = e.downcast_ref::<&str>() { s.to_string() } else if let Some(s) = e.downcast_ref::() { s.clone() } else { "unknown panic".to_string() } } /// 捕获闭包内的 panic,转为 Err(消息),防止 panic 跨 FFI unwind 导致宿主 abort fn catch_jni_panic(f: F) -> Result where F: FnOnce() -> T, { std::panic::catch_unwind(std::panic::AssertUnwindSafe(f)).map_err(panic_message) } /// JNI 导出函数的 panic 防护:panic 时向 JVM 抛出异常并返回默认值 macro_rules! jni_guard { ($env:ident, $default_ret:expr, { $($body:tt)* }) => {{ match catch_jni_panic(|| { $($body)* }) { Ok(v) => v, Err(msg) => { let _ = $env.throw(format!("VNT native panic: {}", msg)); $default_ret } } }}; } /// 初始化JNI模块 #[unsafe(no_mangle)] pub extern "system" fn Java_com_vnt_VntManager_nativeInit( mut env: JNIEnv, _class: JClass, ) -> jboolean { jni_guard!(env, 0, { let mut state = GLOBAL_STATE.lock(); if state.is_some() { return 1; // 已经初始化 } match GlobalState::new() { Ok(global_state) => { *state = Some(global_state); 1 } Err(e) => { let _ = env.throw(format!("Failed to initialize VNT: {:?}", e)); 0 } } }) } /// 销毁JNI模块 #[unsafe(no_mangle)] pub extern "system" fn Java_com_vnt_VntManager_nativeDestroy(_env: JNIEnv, _class: JClass) { let _ = catch_jni_panic(|| { let mut state = GLOBAL_STATE.lock(); *state = None; }); } /// 创建网络实例 #[unsafe(no_mangle)] pub extern "system" fn Java_com_vnt_VntManager_nativeCreateNetwork<'local>( mut env: JNIEnv<'local>, _class: JClass<'local>, config_json: JString<'local>, ) -> jlong { jni_guard!(env, -1, { let result: anyhow::Result = (|| { let mut global_state = GLOBAL_STATE.lock(); let state = global_state.as_mut().context("VNT not initialized")?; // 解析配置JSON let config_str: String = env.get_string(&config_json)?.into(); let config = parse_config_from_json(&config_str)?; // 创建任务组 let task_group_manager = TaskGroupManager::new(); let (task_group, task_group_guard) = task_group_manager .create_task() .context("create task group")?; // 获取runtime的clone let runtime = state.runtime.clone(); // 创建网络管理器 let network_manager = runtime.block_on(async { NetworkManager::create_network(Box::new(config), task_group).await })?; // 分配ID let id = state.next_id; state.next_id += 1; // 保存实例(task_group_guard 必须随实例一直持有,drop 会停止整个任务组) state .network_managers .insert(id, Arc::new(Mutex::new(Some(network_manager)))); state.task_group_managers.insert(id, task_group_manager); state.task_group_guards.insert(id, task_group_guard); Ok(id) })(); match result { Ok(id) => id, Err(e) => { let _ = env.throw(format!("Failed to create network: {:?}", e)); -1 } } }) } /// 注册网络 #[unsafe(no_mangle)] pub extern "system" fn Java_com_vnt_VntNetwork_nativeRegister<'local>( mut env: JNIEnv<'local>, _class: JClass<'local>, handle: jlong, ) -> jstring { jni_guard!(env, std::ptr::null_mut(), { let result: anyhow::Result = (|| { let (network_manager_arc, runtime) = { let mut global_state = GLOBAL_STATE.lock(); let state = global_state.as_mut().context("VNT not initialized")?; let network_manager_arc = state .network_managers .get(&handle) .context("Invalid handle")? .clone(); let runtime = state.runtime.clone(); (network_manager_arc, runtime) }; let response = { let mut manager_lock = network_manager_arc.lock(); let manager = manager_lock .as_mut() .context("Network manager already destroyed")?; runtime.block_on(async { manager.register().await })? }; match response { RegisterResponse::Success(network_addr) => { let response_json = serde_json::json!({ "success": true, "ip": network_addr.ip.to_string(), "prefix_len": network_addr.prefix_len, "gateway": network_addr.gateway.to_string(), "broadcast": network_addr.broadcast.to_string(), }); Ok(response_json.to_string()) } RegisterResponse::Failed(error_msg) => { let response_json = serde_json::json!({ "success": false, "error": error_msg.message, }); Ok(response_json.to_string()) } } })(); match result { Ok(json_str) => env .new_string(json_str) .unwrap_or_else(|_| JObject::null().into()) .into_raw(), Err(e) => { let _ = env.throw(format!("Failed to register: {:?}", e)); JObject::null().into_raw() } } }) } /// 启动TUN设备(Android使用,需要传入fd) #[unsafe(no_mangle)] pub extern "system" fn Java_com_vnt_VntNetwork_nativeStartTun( mut env: JNIEnv, _class: JClass, handle: jlong, tun_fd: jint, ) -> jboolean { jni_guard!(env, 0, { let result: anyhow::Result<()> = (|| { let (network_manager_arc, runtime) = { let mut global_state = GLOBAL_STATE.lock(); let state = global_state.as_mut().context("VNT not initialized")?; let network_manager_arc = state .network_managers .get(&handle) .context("Invalid handle")? .clone(); let runtime = state.runtime.clone(); (network_manager_arc, runtime) }; let mut manager_lock = network_manager_arc.lock(); let manager = manager_lock .as_mut() .context("Network manager already destroyed")?; #[cfg(unix)] { let tun_fd = if tun_fd < 0 { None } else { Some(tun_fd) }; runtime.block_on(async { manager.start_device_fd(tun_fd).await })?; } #[cfg(not(unix))] { let _ = tun_fd; // 避免未使用警告 runtime.block_on(async { manager.start_device().await })?; } Ok(()) })(); match result { Ok(_) => 1, Err(e) => { let _ = env.throw(format!("Failed to start TUN: {:?}", e)); 0 } } }) } /// 设置网络IP(非Android系统) #[unsafe(no_mangle)] pub extern "system" fn Java_com_vnt_VntNetwork_nativeSetNetworkIp<'local>( mut env: JNIEnv<'local>, _class: JClass<'local>, handle: jlong, ip: JString<'local>, prefix_len: jint, ) -> jboolean { #[cfg(target_os = "android")] { let _ = (handle, ip, prefix_len); let _ = env.throw("set_network_ip is not supported on Android"); 0 } #[cfg(not(target_os = "android"))] { jni_guard!(env, 0, { let result: anyhow::Result<()> = (|| { let (network_manager_arc, runtime) = { let mut global_state = GLOBAL_STATE.lock(); let state = global_state.as_mut().context("VNT not initialized")?; let network_manager_arc = state .network_managers .get(&handle) .context("Invalid handle")? .clone(); let runtime = state.runtime.clone(); (network_manager_arc, runtime) }; let ip_str: String = env.get_string(&ip)?.into(); let ip_addr: Ipv4Addr = ip_str.parse().context("Invalid IP address")?; let manager_lock = network_manager_arc.lock(); let manager = manager_lock .as_ref() .context("Network manager already destroyed")?; runtime.block_on(async { manager .set_device_network_ip(ip_addr, prefix_len as u8) .await })?; Ok(()) })(); match result { Ok(_) => 1, Err(e) => { let _ = env.throw(format!("Failed to set network IP: {:?}", e)); 0 } } }) } } /// 获取VntApi实例 #[unsafe(no_mangle)] pub extern "system" fn Java_com_vnt_VntNetwork_nativeGetApi( mut env: JNIEnv, _class: JClass, handle: jlong, ) -> jlong { jni_guard!(env, -1, { let result: anyhow::Result = (|| { let mut global_state = GLOBAL_STATE.lock(); let state = global_state.as_mut().context("VNT not initialized")?; let network_manager_arc = state .network_managers .get(&handle) .context("Invalid handle")? .clone(); let api = { let manager_lock = network_manager_arc.lock(); let manager = manager_lock .as_ref() .context("Network manager already destroyed")?; manager.vnt_api() }; state.vnt_apis.insert(handle, api); Ok(handle) })(); match result { Ok(id) => id, Err(e) => { let _ = env.throw(format!("Failed to get API: {:?}", e)); -1 } } }) } /// 检查是否为无TUN模式 #[unsafe(no_mangle)] pub extern "system" fn Java_com_vnt_VntNetwork_nativeIsNoTun( mut env: JNIEnv, _class: JClass, handle: jlong, ) -> jboolean { jni_guard!(env, 0, { let result: anyhow::Result = (|| { let global_state = GLOBAL_STATE.lock(); let state = global_state.as_ref().context("VNT not initialized")?; let network_manager_arc = state .network_managers .get(&handle) .context("Invalid handle")? .clone(); let manager_lock = network_manager_arc.lock(); let manager = manager_lock .as_ref() .context("Network manager already destroyed")?; Ok(manager.device_mode() == DeviceMode::No) })(); match result { Ok(is_no_device) => { if is_no_device { 1 } else { 0 } } Err(e) => { let _ = env.throw(format!("Failed to check device mode: {:?}", e)); 0 } } }) } /// 关闭网络 #[unsafe(no_mangle)] pub extern "system" fn Java_com_vnt_VntNetwork_nativeStop( mut env: JNIEnv, _class: JClass, handle: jlong, ) -> jboolean { jni_guard!(env, 0, { let result: anyhow::Result<()> = (|| { let mut global_state = GLOBAL_STATE.lock(); let state = global_state.as_mut().context("VNT not initialized")?; // 停止任务组 if let Some(task_group_manager) = state.task_group_managers.get(&handle) { task_group_manager.stop(); } // 移除网络管理器 state.network_managers.remove(&handle); state.vnt_apis.remove(&handle); state.task_group_managers.remove(&handle); // 最后释放守卫(drop 时会停止任务组) state.task_group_guards.remove(&handle); Ok(()) })(); match result { Ok(_) => 1, Err(e) => { let _ = env.throw(format!("Failed to stop network: {:?}", e)); 0 } } }) } // ==================== VntApi 接口 ==================== /// 获取客户端列表 #[unsafe(no_mangle)] pub extern "system" fn Java_com_vnt_VntApi_nativeGetClientList<'local>( mut env: JNIEnv<'local>, _class: JClass<'local>, api_handle: jlong, ) -> jstring { jni_guard!(env, std::ptr::null_mut(), { let result: anyhow::Result = (|| { let global_state = GLOBAL_STATE.lock(); let state = global_state.as_ref().context("VNT not initialized")?; let api = state .vnt_apis .get(&api_handle) .context("Invalid API handle")?; let client_ips = api.client_ips(); let json_array: Vec<_> = client_ips .into_iter() .map(|client| { serde_json::json!({ "ip": client.ip.to_string(), "online": client.online, }) }) .collect(); Ok(serde_json::to_string(&json_array)?) })(); match result { Ok(json_str) => env .new_string(json_str) .unwrap_or_else(|_| JObject::null().into()) .into_raw(), Err(e) => { let _ = env.throw(format!("Failed to get client list: {:?}", e)); JObject::null().into_raw() } } }) } /// 获取网络配置信息 #[unsafe(no_mangle)] pub extern "system" fn Java_com_vnt_VntApi_nativeGetNetwork<'local>( mut env: JNIEnv<'local>, _class: JClass<'local>, api_handle: jlong, ) -> jstring { jni_guard!(env, std::ptr::null_mut(), { let result: anyhow::Result = (|| { let global_state = GLOBAL_STATE.lock(); let state = global_state.as_ref().context("VNT not initialized")?; let api = state .vnt_apis .get(&api_handle) .context("Invalid API handle")?; if let Some(network) = api.network() { let json = serde_json::json!({ "ip": network.ip.to_string(), "prefix_len": network.prefix_len, "gateway": network.gateway.to_string(), "broadcast": network.broadcast.to_string(), }); Ok(json.to_string()) } else { Ok("null".to_string()) } })(); match result { Ok(json_str) => env .new_string(json_str) .unwrap_or_else(|_| JObject::null().into()) .into_raw(), Err(e) => { let _ = env.throw(format!("Failed to get network info: {:?}", e)); JObject::null().into_raw() } } }) } /// 获取NAT信息 #[unsafe(no_mangle)] pub extern "system" fn Java_com_vnt_VntApi_nativeGetNatInfo<'local>( mut env: JNIEnv<'local>, _class: JClass<'local>, api_handle: jlong, ) -> jstring { jni_guard!(env, std::ptr::null_mut(), { let result: anyhow::Result = (|| { let global_state = GLOBAL_STATE.lock(); let state = global_state.as_ref().context("VNT not initialized")?; let api = state .vnt_apis .get(&api_handle) .context("Invalid API handle")?; if let Some(nat_info) = api.nat_info() { let json = serde_json::json!({ "nat_type": format!("{:?}", nat_info.nat_type), "public_ips": nat_info.public_ips.iter().map(|ip| ip.to_string()).collect::>(), "ipv6": nat_info.ipv6.map(|ip| ip.to_string()), }); Ok(json.to_string()) } else { Ok("null".to_string()) } })(); match result { Ok(json_str) => env .new_string(json_str) .unwrap_or_else(|_| JObject::null().into()) .into_raw(), Err(e) => { let _ = env.throw(format!("Failed to get NAT info: {:?}", e)); JObject::null().into_raw() } } }) } /// 获取服务器节点列表 #[unsafe(no_mangle)] pub extern "system" fn Java_com_vnt_VntApi_nativeGetServerList<'local>( mut env: JNIEnv<'local>, _class: JClass<'local>, api_handle: jlong, ) -> jstring { jni_guard!(env, std::ptr::null_mut(), { let result: anyhow::Result = (|| { let global_state = GLOBAL_STATE.lock(); let state = global_state.as_ref().context("VNT not initialized")?; let api = state .vnt_apis .get(&api_handle) .context("Invalid API handle")?; let servers = api.server_node_list(); let json_array: Vec<_> = servers .into_iter() .map(|server| { serde_json::json!({ "server_id": server.server_id, "server_addr": server.server_addr.to_string(), "connected": server.connected, "rtt": server.rtt, "data_version": server.data_version, "server_version": server.server_version, }) }) .collect(); Ok(serde_json::to_string(&json_array)?) })(); match result { Ok(json_str) => env .new_string(json_str) .unwrap_or_else(|_| JObject::null().into()) .into_raw(), Err(e) => { let _ = env.throw(format!("Failed to get server list: {:?}", e)); JObject::null().into_raw() } } }) } /// 获取路由表 #[unsafe(no_mangle)] pub extern "system" fn Java_com_vnt_VntApi_nativeGetRouteTable<'local>( mut env: JNIEnv<'local>, _class: JClass<'local>, api_handle: jlong, ) -> jstring { jni_guard!(env, std::ptr::null_mut(), { let result: anyhow::Result = (|| { let global_state = GLOBAL_STATE.lock(); let state = global_state.as_ref().context("VNT not initialized")?; let api = state .vnt_apis .get(&api_handle) .context("Invalid API handle")?; let route_table = api.route_table(); let json_data: Vec<_> = route_table .into_iter() .map(|(ip, routes)| { let routes_json: Vec<_> = routes .into_iter() .map(|route| { serde_json::json!({ "route_key": route.route_key().to_string(), "protocol": route.route_key().protocol().to_string(), "metric": route.metric(), "rtt": route.rtt(), }) }) .collect(); serde_json::json!({ "ip": ip.to_string(), "routes": routes_json, }) }) .collect(); Ok(serde_json::to_string(&json_data)?) })(); match result { Ok(json_str) => env .new_string(json_str) .unwrap_or_else(|_| JObject::null().into()) .into_raw(), Err(e) => { let _ = env.throw(format!("Failed to get route table: {:?}", e)); JObject::null().into_raw() } } }) } /// 检查目标IP是否直连 #[unsafe(no_mangle)] pub extern "system" fn Java_com_vnt_VntApi_nativeIsDirect<'local>( mut env: JNIEnv<'local>, _class: JClass<'local>, api_handle: jlong, ip: JString<'local>, ) -> jboolean { jni_guard!(env, 0, { let result: anyhow::Result = (|| { let global_state = GLOBAL_STATE.lock(); let state = global_state.as_ref().context("VNT not initialized")?; let api = state .vnt_apis .get(&api_handle) .context("Invalid API handle")?; let ip_str: String = env.get_string(&ip)?.into(); let ip_addr: Ipv4Addr = ip_str.parse().context("Invalid IP address")?; Ok(api.is_direct(&ip_addr)) })(); match result { Ok(is_direct) => { if is_direct { 1 } else { 0 } } Err(e) => { let _ = env.throw(format!("Failed to check direct: {:?}", e)); 0 } } }) } /// 获取对端NAT信息 #[unsafe(no_mangle)] pub extern "system" fn Java_com_vnt_VntApi_nativeGetPeerNatInfo<'local>( mut env: JNIEnv<'local>, _class: JClass<'local>, api_handle: jlong, ip: JString<'local>, ) -> jstring { jni_guard!(env, std::ptr::null_mut(), { let result: anyhow::Result = (|| { let global_state = GLOBAL_STATE.lock(); let state = global_state.as_ref().context("VNT not initialized")?; let api = state .vnt_apis .get(&api_handle) .context("Invalid API handle")?; let ip_str: String = env.get_string(&ip)?.into(); let ip_addr: Ipv4Addr = ip_str.parse().context("Invalid IP address")?; if let Some(nat_info) = api.peer_nat_info(&ip_addr) { let json = serde_json::json!({ "nat_type": format!("{:?}", nat_info.nat_type), "public_ips": nat_info.public_ips.iter().map(|ip| ip.to_string()).collect::>(), "ipv6": nat_info.ipv6.map(|ip| ip.to_string()), }); Ok(json.to_string()) } else { Ok("null".to_string()) } })(); match result { Ok(json_str) => env .new_string(json_str) .unwrap_or_else(|_| JObject::null().into()) .into_raw(), Err(e) => { let _ = env.throw(format!("Failed to get peer NAT info: {:?}", e)); JObject::null().into_raw() } } }) } /// 获取丢包信息 #[unsafe(no_mangle)] pub extern "system" fn Java_com_vnt_VntApi_nativeGetPacketLoss<'local>( mut env: JNIEnv<'local>, _class: JClass<'local>, api_handle: jlong, ip: JString<'local>, ) -> jstring { jni_guard!(env, std::ptr::null_mut(), { let result: anyhow::Result = (|| { let global_state = GLOBAL_STATE.lock(); let state = global_state.as_ref().context("VNT not initialized")?; let api = state .vnt_apis .get(&api_handle) .context("Invalid API handle")?; let ip_str: String = env.get_string(&ip)?.into(); let ip_addr: Ipv4Addr = ip_str.parse().context("Invalid IP address")?; if let Some(loss_info) = api.packet_loss_info(&ip_addr) { let json = serde_json::json!({ "ip": loss_info.ip.to_string(), "sent": loss_info.sent, "received": loss_info.received, "loss_rate": loss_info.loss_rate, }); Ok(json.to_string()) } else { Ok("null".to_string()) } })(); match result { Ok(json_str) => env .new_string(json_str) .unwrap_or_else(|_| JObject::null().into()) .into_raw(), Err(e) => { let _ = env.throw(format!("Failed to get packet loss: {:?}", e)); JObject::null().into_raw() } } }) } /// 获取流量信息 #[unsafe(no_mangle)] pub extern "system" fn Java_com_vnt_VntApi_nativeGetTrafficInfo<'local>( mut env: JNIEnv<'local>, _class: JClass<'local>, api_handle: jlong, ip: JString<'local>, ) -> jstring { jni_guard!(env, std::ptr::null_mut(), { let result: anyhow::Result = (|| { let global_state = GLOBAL_STATE.lock(); let state = global_state.as_ref().context("VNT not initialized")?; let api = state .vnt_apis .get(&api_handle) .context("Invalid API handle")?; let ip_str: String = env.get_string(&ip)?.into(); let ip_addr: Ipv4Addr = ip_str.parse().context("Invalid IP address")?; if let Some(traffic_info) = api.traffic_info(&ip_addr) { let json = serde_json::json!({ "ip": traffic_info.ip.to_string(), "tx_bytes": traffic_info.tx_bytes, "rx_bytes": traffic_info.rx_bytes, }); Ok(json.to_string()) } else { Ok("null".to_string()) } })(); match result { Ok(json_str) => env .new_string(json_str) .unwrap_or_else(|_| JObject::null().into()) .into_raw(), Err(e) => { let _ = env.throw(format!("Failed to get traffic info: {:?}", e)); JObject::null().into_raw() } } }) } // ==================== 辅助函数 ==================== /// 从JSON字符串解析配置 fn parse_config_from_json(json_str: &str) -> anyhow::Result { #[derive(serde::Deserialize)] struct ConfigJson { server: Vec, network_code: String, #[serde(default)] device_id: Option, #[serde(default)] device_name: Option, #[serde(default)] tun_name: Option, #[serde(default)] outbound_interface: Option, #[serde(default)] ip: Option, #[serde(default)] password: Option, #[serde(default)] cert_mode: Option, #[serde(default)] no_punch: bool, #[serde(default)] compress: bool, #[serde(default)] rtx: bool, #[serde(default)] fec: bool, #[serde(default)] input: Vec, #[serde(default)] output: Vec, #[serde(default)] no_nat: bool, #[serde(default)] device_mode: DeviceMode, #[serde(default)] mtu: Option, #[serde(default)] port_mapping: Vec, #[serde(default)] allow_mapping: bool, #[serde(default)] udp_stun: Vec, #[serde(default)] tcp_stun: Vec, #[serde(default)] tunnel_port: Option, } let cfg: ConfigJson = serde_json::from_str(json_str)?; let server_addrs: Vec = cfg .server .iter() .map(|s| { s.parse() .map_err(|e| anyhow::anyhow!("invalid server address '{}': {}", s, e)) }) .collect::>()?; let port_mapping: Vec = cfg .port_mapping .iter() .map(|s| { s.parse() .map_err(|e| anyhow::anyhow!("invalid port_mapping '{}': {}", s, e)) }) .collect::>()?; let cert_mode = match cfg.cert_mode.as_deref() { Some(s) => s .parse() .map_err(|e| anyhow::anyhow!("invalid cert_mode '{}': {}", s, e))?, None => CertValidationMode::InsecureSkipVerification, }; let device_id = match cfg.device_id { Some(id) => id, None => vnt_core::utils::device_id::get_device_id() .map_err(|e| anyhow::anyhow!("failed to get device_id: {}", e))?, }; let device_name = cfg.device_name.unwrap_or_else(|| { hostname::get() .ok() .and_then(|v| v.into_string().ok()) .unwrap_or_default() }); let mut udp_stun = cfg.udp_stun; for x in udp_stun.iter_mut() { if !x.contains(':') { x.push_str(":3478"); } } let mut tcp_stun = cfg.tcp_stun; for x in tcp_stun.iter_mut() { if !x.contains(':') { x.push_str(":3478"); } } Ok(Config { server_addr: server_addrs, network_code: cfg.network_code, ip: cfg.ip, no_punch: cfg.no_punch, rtx: cfg.rtx, compress: cfg.compress, device_id, device_name, tun_name: cfg.tun_name, outbound_interface: cfg.outbound_interface, password: cfg.password, cert_mode, input: cfg.input, output: cfg.output, no_nat: cfg.no_nat, device_mode: cfg.device_mode, mtu: cfg.mtu, port_mapping, allow_port_mapping: cfg.allow_mapping, udp_stun, tcp_stun, fec: cfg.fec, tunnel_port: cfg.tunnel_port, }) } #[cfg(test)] mod tests { use super::*; #[test] fn catch_jni_panic_returns_value_unchanged() { let result = catch_jni_panic(|| 42); assert_eq!(result, Ok(42)); } #[test] fn catch_jni_panic_captures_str_message() { let result: Result<(), String> = catch_jni_panic(|| panic!("boom")); let err = result.unwrap_err(); assert!(err.contains("boom"), "unexpected message: {}", err); } #[test] fn catch_jni_panic_captures_string_message() { let result: Result<(), String> = catch_jni_panic(|| panic!("{}", "kaboom")); assert_eq!(result.unwrap_err(), "kaboom"); } }