use std::collections::HashMap;
use tracing::{debug, instrument};
use zbus::{
Connection,
zvariant::{OwnedObjectPath, OwnedValue, Value},
};
use crate::{
core::{
access_point::types::{SecurityType, Ssid},
settings::Settings,
},
error::Error,
proxy::{access_point::AccessPointProxy, devices::DeviceProxy, manager::NetworkManagerProxy},
types::flags::{NM80211ApFlags, NM80211ApSecurityFlags},
};
type ConnectionSettings = HashMap<String, HashMap<String, OwnedValue>>;
const MANUFACTURER_DEFAULT_SSIDS: &[&str] = &[
"linksys",
"linksys-a",
"linksys-g",
"default",
"belkin54g",
"NETGEAR",
"o2DSL",
"WLAN",
"ALICE-WLAN",
];
pub(super) struct WifiControls;
impl WifiControls {
#[instrument(skip(connection), fields(enabled = enabled), err)]
pub(super) async fn set_enabled(connection: &Connection, enabled: bool) -> Result<(), Error> {
let proxy = NetworkManagerProxy::new(connection).await?;
proxy
.set_wireless_enabled(enabled)
.await
.map_err(|e| Error::OperationFailed {
operation: "set wireless enabled",
source: e.into(),
})?;
Ok(())
}
#[instrument(skip(connection), fields(device = %device_path), err)]
pub(super) async fn disconnect(
connection: &Connection,
device_path: &str,
) -> Result<(), Error> {
let proxy = NetworkManagerProxy::new(connection).await?;
let device_proxy = DeviceProxy::new(connection, device_path)
.await
.map_err(|e| Error::OperationFailed {
operation: "create device proxy",
source: e.into(),
})?;
let active_connection_path =
device_proxy
.active_connection()
.await
.map_err(|e| Error::OperationFailed {
operation: "get active connection",
source: e.into(),
})?;
if active_connection_path.as_str() == "/" || active_connection_path.as_str().is_empty() {
return Ok(());
}
proxy
.deactivate_connection(&active_connection_path)
.await
.map_err(|e| Error::OperationFailed {
operation: "deactivate connection",
source: e.into(),
})?;
Ok(())
}
#[instrument(
skip(connection, password, settings),
fields(device = %device_path, ap = %ap_path),
err
)]
pub(super) async fn connect(
connection: &Connection,
device_path: &str,
ap_path: OwnedObjectPath,
password: Option<String>,
settings: &Settings,
) -> Result<OwnedObjectPath, Error> {
let proxy = NetworkManagerProxy::new(connection).await?;
let ap_proxy = AccessPointProxy::new(connection, ap_path.clone())
.await
.map_err(|err| Error::OperationFailed {
operation: "create access point proxy",
source: err.into(),
})?;
let ssid_bytes = ap_proxy
.ssid()
.await
.map_err(|err| Error::OperationFailed {
operation: "get ssid",
source: err.into(),
})?;
let ssid = Ssid::new(ssid_bytes.clone());
let ssid_string = ssid.to_string_lossy();
let device_path =
OwnedObjectPath::try_from(device_path).map_err(|err| Error::DbusError(err.into()))?;
let existing_profiles = settings.connections_for_ssid(&ssid);
if let Some(profile) = existing_profiles.first() {
debug!(
profile = %profile.object_path,
ssid = %ssid_string,
"reusing saved connection profile"
);
if let Some(pwd) = password {
let security_type = Self::detect_security_type(&ap_proxy).await;
Self::update_profile_password(profile, pwd, security_type).await?;
}
let active_connection_path = proxy
.activate_connection(&profile.object_path, &device_path, &ap_path)
.await
.map_err(|err| Error::OperationFailed {
operation: "activate existing connection",
source: err.into(),
})?;
return Ok(active_connection_path);
}
debug!(ssid = %ssid_string, "no saved profile found, creating new connection");
let bssid = if Self::is_manufacturer_default(&ssid_string) {
ap_proxy.hw_address().await.ok()
} else {
None
};
let security_type = Self::detect_security_type(&ap_proxy).await;
let connection_settings = Self::build_connection_settings(
ssid_string,
ssid_bytes,
bssid,
password,
security_type,
)?;
let (_settings_path, active_connection_path) = proxy
.add_and_activate_connection(connection_settings, &device_path, &ap_path)
.await
.map_err(|err| Error::OperationFailed {
operation: "add and activate connection",
source: err.into(),
})?;
Ok(active_connection_path)
}
async fn update_profile_password(
profile: &crate::core::settings_connection::ConnectionSettings,
password: String,
security_type: SecurityType,
) -> Result<(), Error> {
let mut current_settings = profile.get_settings().await?;
let security = current_settings
.entry(String::from("802-11-wireless-security"))
.or_default();
let to_owned = |value: Value| {
value.try_to_owned().map_err(|err| Error::OperationFailed {
operation: "convert to owned value",
source: err.into(),
})
};
let key_mgmt = Self::key_mgmt_for_security_type(security_type);
security.insert(String::from("key-mgmt"), to_owned(Value::from(key_mgmt))?);
match security_type {
SecurityType::Wep => {
security.insert(String::from("wep-key0"), to_owned(Value::from(password))?);
}
_ => {
security.insert(String::from("psk"), to_owned(Value::from(password))?);
}
}
profile.update(current_settings).await?;
Ok(())
}
fn is_manufacturer_default(ssid: &str) -> bool {
MANUFACTURER_DEFAULT_SSIDS.contains(&ssid)
}
fn build_connection_settings(
ssid_string: String,
ssid_bytes: Vec<u8>,
bssid: Option<String>,
password: Option<String>,
security_type: SecurityType,
) -> Result<ConnectionSettings, Error> {
let to_owned = |value: Value| {
value.try_to_owned().map_err(|e| Error::OperationFailed {
operation: "convert to owned value",
source: e.into(),
})
};
let mut settings = HashMap::new();
let mut connection = HashMap::new();
connection.insert(
String::from("type"),
to_owned(Value::from("802-11-wireless"))?,
);
connection.insert(String::from("id"), to_owned(Value::from(ssid_string))?);
settings.insert(String::from("connection"), connection);
let mut wireless = HashMap::new();
wireless.insert(String::from("ssid"), to_owned(Value::from(ssid_bytes))?);
if let Some(bssid_str) = bssid {
let mac_bytes: Result<Vec<u8>, _> = bssid_str
.split(':')
.map(|part| u8::from_str_radix(part, 16))
.collect();
if let Ok(bytes) = mac_bytes {
wireless.insert(String::from("bssid"), to_owned(Value::from(bytes))?);
}
}
settings.insert(String::from("802-11-wireless"), wireless);
if let Some(pwd) = password {
let mut security = HashMap::new();
let key_mgmt = Self::key_mgmt_for_security_type(security_type);
security.insert(String::from("key-mgmt"), to_owned(Value::from(key_mgmt))?);
match security_type {
SecurityType::Wep => {
security.insert(String::from("wep-key0"), to_owned(Value::from(pwd))?);
}
_ => {
security.insert(String::from("psk"), to_owned(Value::from(pwd))?);
}
}
settings.insert(String::from("802-11-wireless-security"), security);
}
Ok(settings)
}
async fn detect_security_type(ap_proxy: &AccessPointProxy<'_>) -> SecurityType {
let flags = ap_proxy
.flags()
.await
.map(NM80211ApFlags::from_bits_truncate)
.unwrap_or(NM80211ApFlags::NONE);
let wpa_flags = ap_proxy
.wpa_flags()
.await
.map(NM80211ApSecurityFlags::from_bits_truncate)
.unwrap_or(NM80211ApSecurityFlags::NONE);
let rsn_flags = ap_proxy
.rsn_flags()
.await
.map(NM80211ApSecurityFlags::from_bits_truncate)
.unwrap_or(NM80211ApSecurityFlags::NONE);
SecurityType::from_flags(flags, wpa_flags, rsn_flags)
}
fn key_mgmt_for_security_type(security_type: SecurityType) -> &'static str {
match security_type {
SecurityType::None => "none",
SecurityType::Wep => "none",
SecurityType::Wpa | SecurityType::Wpa2 => "wpa-psk",
SecurityType::Wpa3 => "sae",
SecurityType::Enterprise => "wpa-eap",
}
}
}