use easytier::proto::{api, common}; use napi_derive_ohos::napi; use serde::Serialize; use std::collections::HashSet; use std::sync::Mutex; static ATTACHED_TUN_INSTANCE_IDS: once_cell::sync::Lazy>> = once_cell::sync::Lazy::new(|| Mutex::new(HashSet::new())); pub fn mark_tun_attached(instance_id: &str) { if let Ok(mut guard) = ATTACHED_TUN_INSTANCE_IDS.lock() { guard.insert(instance_id.to_string()); } } pub fn clear_tun_attached(instance_id: &str) { if let Ok(mut guard) = ATTACHED_TUN_INSTANCE_IDS.lock() { guard.remove(instance_id); } } pub fn is_tun_attached(instance_id: &str) -> bool { ATTACHED_TUN_INSTANCE_IDS .lock() .map(|guard| guard.contains(instance_id)) .unwrap_or(false) } #[derive(Serialize)] #[serde(rename_all = "camelCase")] #[napi(object)] pub struct PeerConnStats { pub rx_bytes: i64, pub tx_bytes: i64, pub rx_packets: i64, pub tx_packets: i64, pub latency_us: i64, } #[derive(Serialize)] #[serde(rename_all = "camelCase")] #[napi(object)] pub struct PeerConnInfo { pub conn_id: String, pub my_peer_id: i64, pub peer_id: i64, pub features: Vec, pub tunnel_type: Option, pub local_addr: Option, pub remote_addr: Option, pub resolved_remote_addr: Option, pub stats: Option, pub loss_rate: Option, pub is_client: bool, pub network_name: Option, pub is_closed: bool, pub secure_auth_level: Option, pub peer_identity_type: Option, } #[derive(Serialize)] #[serde(rename_all = "camelCase")] #[napi(object)] pub struct PeerInfo { pub peer_id: i64, pub default_conn_id: Option, pub directly_connected_conns: Vec, pub conns: Vec, } #[derive(Serialize)] #[serde(rename_all = "camelCase")] #[napi(object)] pub struct RouteView { pub peer_id: i64, pub hostname: Option, pub ipv4: Option, pub ipv4_cidr: Option, pub ipv6_cidr: Option, pub proxy_cidrs: Vec, pub next_hop_peer_id: Option, pub cost: Option, pub path_latency: Option, pub udp_nat_type: Option, pub tcp_nat_type: Option, pub inst_id: Option, pub version: Option, pub is_public_server: Option, } #[derive(Serialize)] #[serde(rename_all = "camelCase")] #[napi(object)] pub struct MyNodeInfo { pub virtual_ipv4: Option, pub virtual_ipv4_cidr: Option, pub hostname: Option, pub version: Option, pub peer_id: Option, pub listeners: Vec, pub vpn_portal_cfg: Option, pub udp_nat_type: Option, pub tcp_nat_type: Option, } #[derive(Serialize)] #[serde(rename_all = "camelCase")] #[napi(object)] pub struct RuntimeInstanceState { pub config_id: String, pub instance_id: String, pub display_name: String, pub running: bool, pub tun_required: bool, pub tun_attached: bool, pub magic_dns_enabled: bool, pub need_exit_node: bool, pub error_message: Option, pub my_node_info: Option, pub events: Vec, pub routes: Vec, pub peers: Vec, } #[derive(Serialize)] #[serde(rename_all = "camelCase")] #[napi(object)] pub struct TunAggregateState { pub active: bool, pub attached_instance_ids: Vec, pub aggregated_routes: Vec, pub dns_servers: Vec, pub need_rebuild: bool, } #[derive(Serialize)] #[serde(rename_all = "camelCase")] #[napi(object)] pub struct RuntimeAggregateState { pub instances: Vec, pub tun: TunAggregateState, pub running_instance_count: i32, } fn stringify_ipv4_inet(value: Option) -> Option { value.map(|v| v.to_string()) } fn stringify_ipv6_inet(value: Option) -> Option { value.map(|v| v.to_string()) } fn stringify_url(value: Option) -> Option { value.map(|v| v.to_string()) } fn stringify_uuid(value: Option) -> Option { value.map(|v| v.to_string()) } fn optional_u32_to_i64(value: Option) -> Option { value.map(|v| v as i64) } fn optional_i32_to_i64(value: Option) -> Option { value.map(|v| v as i64) } fn route_to_view(route: api::instance::Route) -> RouteView { let stun = route.stun_info; let feature_flag = route.feature_flag; RouteView { peer_id: route.peer_id as i64, hostname: (!route.hostname.is_empty()).then_some(route.hostname), ipv4: route .ipv4_addr .as_ref() .and_then(|inet| inet.address.as_ref()) .map(|addr| addr.to_string()), ipv4_cidr: stringify_ipv4_inet(route.ipv4_addr), ipv6_cidr: stringify_ipv6_inet(route.ipv6_addr), proxy_cidrs: route.proxy_cidrs, next_hop_peer_id: optional_u32_to_i64(route.next_hop_peer_id_latency_first) .or_else(|| Some(route.next_hop_peer_id as i64)), cost: Some(route.cost), path_latency: optional_i32_to_i64(route.path_latency_latency_first) .or_else(|| Some(route.path_latency as i64)), udp_nat_type: stun.as_ref().map(|info| info.udp_nat_type), tcp_nat_type: stun.as_ref().map(|info| info.tcp_nat_type), inst_id: (!route.inst_id.is_empty()).then_some(route.inst_id), version: (!route.version.is_empty()).then_some(route.version), is_public_server: feature_flag.map(|flag| flag.is_public_server), } } fn peer_conn_to_view(conn: api::instance::PeerConnInfo) -> PeerConnInfo { let stats = conn.stats.map(|stats| PeerConnStats { rx_bytes: stats.rx_bytes as i64, tx_bytes: stats.tx_bytes as i64, rx_packets: stats.rx_packets as i64, tx_packets: stats.tx_packets as i64, latency_us: stats.latency_us as i64, }); PeerConnInfo { conn_id: conn.conn_id, my_peer_id: conn.my_peer_id as i64, peer_id: conn.peer_id as i64, features: conn.features, tunnel_type: conn.tunnel.as_ref().map(|t| t.tunnel_type.clone()), local_addr: conn .tunnel .as_ref() .and_then(|t| stringify_url(t.local_addr.clone())), remote_addr: conn .tunnel .as_ref() .and_then(|t| stringify_url(t.remote_addr.clone())), resolved_remote_addr: conn .tunnel .as_ref() .and_then(|t| stringify_url(t.resolved_remote_addr.clone())), stats, loss_rate: Some(conn.loss_rate as f64), is_client: conn.is_client, network_name: (!conn.network_name.is_empty()).then_some(conn.network_name), is_closed: conn.is_closed, secure_auth_level: Some(conn.secure_auth_level), peer_identity_type: Some(conn.peer_identity_type), } } fn peer_to_view(peer: api::instance::PeerInfo) -> PeerInfo { PeerInfo { peer_id: peer.peer_id as i64, default_conn_id: stringify_uuid(peer.default_conn_id), directly_connected_conns: peer .directly_connected_conns .into_iter() .map(|id| id.to_string()) .collect(), conns: peer.conns.into_iter().map(peer_conn_to_view).collect(), } } fn my_node_info_to_view(info: api::manage::MyNodeInfo) -> MyNodeInfo { MyNodeInfo { virtual_ipv4: info .virtual_ipv4 .as_ref() .and_then(|inet| inet.address.as_ref()) .map(|addr| addr.to_string()), virtual_ipv4_cidr: stringify_ipv4_inet(info.virtual_ipv4), hostname: (!info.hostname.is_empty()).then_some(info.hostname), version: (!info.version.is_empty()).then_some(info.version), peer_id: Some(info.peer_id as i64), listeners: info .listeners .into_iter() .map(|url| url.to_string()) .collect(), vpn_portal_cfg: info.vpn_portal_cfg, udp_nat_type: info.stun_info.as_ref().map(|stun| stun.udp_nat_type), tcp_nat_type: info.stun_info.as_ref().map(|stun| stun.tcp_nat_type), } } pub fn runtime_instance_from_running_info( config_id: String, display_name: String, magic_dns_enabled: bool, need_exit_node: bool, info: api::manage::NetworkInstanceRunningInfo, ) -> RuntimeInstanceState { let tun_attached = info.running && is_tun_attached(&config_id); let tun_required = info.running && (info.dev_name != "no_tun" || tun_attached); RuntimeInstanceState { config_id: config_id.clone(), instance_id: config_id, display_name, running: info.running, tun_required, tun_attached, magic_dns_enabled, need_exit_node, error_message: info.error_msg, my_node_info: info.my_node_info.map(my_node_info_to_view), events: info.events, routes: info.routes.into_iter().map(route_to_view).collect(), peers: info.peers.into_iter().map(peer_to_view).collect(), } }