use std::collections::HashMap;
use zbus::Connection;
use crate::Result;
use crate::api::models::access_point::{AccessPoint, ApMode, decode_security};
use crate::api::models::{ConnectionError, DeviceState, Network};
use crate::core::connection_settings::has_saved_connection;
use crate::dbus::{NMAccessPointProxy, NMDeviceProxy, NMProxy, NMWirelessProxy};
use crate::monitoring::info::current_ssid;
use crate::types::constants::{device_type, security_flags, wifi_mode};
use crate::util::utils::{
decode_ssid_or_empty, decode_ssid_or_hidden, get_ip_addresses_from_active_connection,
};
pub(crate) async fn scan_networks(conn: &Connection, interface: Option<&str>) -> Result<()> {
let nm = NMProxy::new(conn).await?;
let devices = nm.get_devices().await?;
let mut scanned_any = false;
for dp in devices {
let d_proxy = NMDeviceProxy::builder(conn)
.path(dp.clone())?
.build()
.await?;
let dev_type = d_proxy
.device_type()
.await
.map_err(|e| ConnectionError::DbusOperation {
context: format!(
"failed to get device type for {} during Wi-Fi scan",
dp.as_str()
),
source: e,
})?;
if dev_type != device_type::WIFI {
continue;
}
if let Some(want) = interface {
let iface = d_proxy.interface().await.unwrap_or_default();
if iface != want {
continue;
}
}
let wifi = NMWirelessProxy::builder(conn)
.path(dp.clone())?
.build()
.await?;
let opts = std::collections::HashMap::new();
wifi.request_scan(opts)
.await
.map_err(|e| ConnectionError::DbusOperation {
context: format!("failed to request Wi-Fi scan on device {}", dp.as_str()),
source: e,
})?;
scanned_any = true;
}
if let Some(want) = interface
&& !scanned_any
{
return Err(ConnectionError::WifiInterfaceNotFound {
interface: want.to_string(),
});
}
Ok(())
}
pub(crate) async fn list_access_points(
conn: &Connection,
interface: Option<&str>,
) -> Result<Vec<AccessPoint>> {
let nm = NMProxy::new(conn).await?;
let devices = nm.get_devices().await?;
let mut results = Vec::new();
for dp in devices {
let dev = NMDeviceProxy::builder(conn)
.path(dp.clone())?
.build()
.await?;
if dev.device_type().await? != device_type::WIFI {
continue;
}
let iface = dev.interface().await.unwrap_or_default();
if let Some(target) = interface
&& iface != target
{
continue;
}
let raw_state = dev.state().await?;
let device_state: DeviceState = raw_state.into();
let wifi = NMWirelessProxy::builder(conn)
.path(dp.clone())?
.build()
.await?;
let active_ap = wifi.active_access_point().await?;
let is_active_ap = |path: &zvariant::OwnedObjectPath| -> bool {
active_ap.as_str() != "/" && &active_ap == path
};
for ap_path in wifi.access_points().await? {
let ap = NMAccessPointProxy::builder(conn)
.path(ap_path.clone())?
.build()
.await?;
let ssid_bytes = ap.ssid().await?;
let ssid = decode_ssid_or_hidden(&ssid_bytes);
let bssid = ap.hw_address().await?;
let flags = ap.flags().await?;
let wpa = ap.wpa_flags().await?;
let rsn = ap.rsn_flags().await?;
let frequency_mhz = ap.frequency().await?;
let max_bitrate_kbps = ap.max_bitrate().await.unwrap_or(0);
let strength = ap.strength().await?;
let mode_raw = ap.mode().await.unwrap_or(0);
let last_seen_raw = ap.last_seen().await.unwrap_or(-1);
let last_seen_secs = if last_seen_raw < 0 {
None
} else {
Some(i64::from(last_seen_raw))
};
results.push(AccessPoint {
path: ap_path.clone(),
device_path: dp.clone(),
interface: iface.clone(),
ssid: ssid.to_string(),
ssid_bytes: ssid_bytes.clone(),
bssid,
frequency_mhz,
max_bitrate_kbps,
strength,
mode: ApMode::from(mode_raw),
security: decode_security(flags, wpa, rsn),
last_seen_secs,
is_active: is_active_ap(&ap_path),
device_state: device_state.clone(),
});
}
}
Ok(results)
}
pub(crate) async fn list_networks(
conn: &Connection,
interface: Option<&str>,
) -> Result<Vec<Network>> {
let aps = list_access_points(conn, interface).await?;
let mut groups: HashMap<(String, String), Network> = HashMap::new();
for ap in &aps {
let key = (ap.interface.clone(), ap.ssid.clone());
let sec_flags = ap.security;
let secured = !sec_flags.is_open();
let is_psk = sec_flags.psk;
let is_eap = sec_flags.eap || sec_flags.eap_suite_b_192;
let is_hotspot = ap.mode == ApMode::Ap;
let (ip4_address, ip6_address) = if ap.is_active {
active_ip_addresses(conn, &ap.device_path).await
} else {
(None, None)
};
let net = Network {
device: if ap.is_active {
ap.interface.clone()
} else {
String::new()
},
ssid: ap.ssid.clone(),
bssid: Some(ap.bssid.clone()),
strength: Some(ap.strength),
frequency: Some(ap.frequency_mhz),
secured,
is_psk,
is_eap,
is_hotspot,
ip4_address,
ip6_address,
best_bssid: ap.bssid.clone(),
bssids: vec![ap.bssid.clone()],
is_active: ap.is_active,
known: false,
security_features: sec_flags,
};
groups
.entry(key)
.and_modify(|n| n.merge_ap(&net))
.or_insert(net);
}
for net in groups.values_mut() {
net.known = has_saved_connection(conn, &net.ssid).await.unwrap_or(false);
if net.device.is_empty()
&& net.is_active
&& let Some(ap) = aps.iter().find(|a| a.ssid == net.ssid && a.is_active)
{
net.device.clone_from(&ap.interface);
}
}
Ok(groups.into_values().collect())
}
async fn active_ip_addresses(
conn: &Connection,
device_path: &zvariant::OwnedObjectPath,
) -> (Option<String>, Option<String>) {
let builder = match NMDeviceProxy::builder(conn).path(device_path.clone()) {
Ok(b) => b,
Err(_) => return (None, None),
};
let dev = match builder.build().await {
Ok(d) => d,
Err(_) => return (None, None),
};
match dev.active_connection().await {
Ok(ac) if ac.as_str() != "/" => get_ip_addresses_from_active_connection(conn, &ac).await,
_ => (None, None),
}
}
pub(crate) async fn current_network(conn: &Connection) -> Result<Option<Network>> {
let current_ssid = match current_ssid(conn).await {
Some(ssid) => ssid,
None => return Ok(None),
};
let nm = NMProxy::new(conn).await?;
let devices = nm.get_devices().await?;
for dev_path in devices {
let dev = NMDeviceProxy::builder(conn)
.path(dev_path.clone())?
.build()
.await?;
if dev.device_type().await? != device_type::WIFI {
continue;
}
let wifi = NMWirelessProxy::builder(conn)
.path(dev_path.clone())?
.build()
.await?;
let ap_path = wifi.active_access_point().await?;
if ap_path.as_str() == "/" {
continue;
}
let ap = NMAccessPointProxy::builder(conn)
.path(ap_path)?
.build()
.await?;
let ssid_bytes = ap.ssid().await?;
let ssid = decode_ssid_or_empty(&ssid_bytes);
if ssid != current_ssid {
continue;
}
let strength = ap.strength().await?;
let bssid = ap.hw_address().await?;
let flags = ap.flags().await?;
let wpa = ap.wpa_flags().await?;
let rsn = ap.rsn_flags().await?;
let frequency = ap.frequency().await?;
let secured = (flags & security_flags::WEP) != 0 || wpa != 0 || rsn != 0;
let is_psk = (wpa & security_flags::PSK) != 0 || (rsn & security_flags::PSK) != 0;
let is_eap = (wpa & security_flags::EAP) != 0 || (rsn & security_flags::EAP) != 0;
let is_hotspot = ap.mode().await.unwrap_or(0) == wifi_mode::AP;
let interface = dev.interface().await.unwrap_or_default();
let (ip4_address, ip6_address) = if let Ok(active_conn_path) = dev.active_connection().await
{
if active_conn_path.as_str() != "/" {
get_ip_addresses_from_active_connection(conn, &active_conn_path).await
} else {
(None, None)
}
} else {
(None, None)
};
let sec_features = decode_security(flags, wpa, rsn);
return Ok(Some(Network {
device: interface,
ssid: ssid.to_string(),
bssid: Some(bssid.clone()),
strength: Some(strength),
frequency: Some(frequency),
secured,
is_psk,
is_eap,
is_hotspot,
ip4_address,
ip6_address,
best_bssid: bssid.clone(),
bssids: vec![bssid],
is_active: true,
known: true,
security_features: sec_features,
}));
}
Ok(None)
}