use std::fmt;
use serde::{Deserialize, Serialize};
use zvariant::OwnedObjectPath;
use super::DeviceState;
#[non_exhaustive]
#[derive(Debug, Clone, PartialEq)]
pub struct AccessPoint {
pub path: OwnedObjectPath,
pub device_path: OwnedObjectPath,
pub interface: String,
pub ssid: String,
pub ssid_bytes: Vec<u8>,
pub bssid: String,
pub frequency_mhz: u32,
pub max_bitrate_kbps: u32,
pub strength: u8,
pub mode: ApMode,
pub security: SecurityFeatures,
pub last_seen_secs: Option<i64>,
pub is_active: bool,
pub device_state: DeviceState,
}
#[non_exhaustive]
#[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize, Deserialize)]
pub enum ApMode {
Adhoc,
Infrastructure,
Ap,
Mesh,
Unknown(u32),
}
impl From<u32> for ApMode {
fn from(value: u32) -> Self {
match value {
1 => Self::Adhoc,
2 => Self::Infrastructure,
3 => Self::Ap,
4 => Self::Mesh,
other => Self::Unknown(other),
}
}
}
impl fmt::Display for ApMode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Adhoc => write!(f, "Ad-Hoc"),
Self::Infrastructure => write!(f, "Infrastructure"),
Self::Ap => write!(f, "AP"),
Self::Mesh => write!(f, "Mesh"),
Self::Unknown(v) => write!(f, "Unknown({v})"),
}
}
}
#[non_exhaustive]
#[derive(Debug, Clone, Copy, Eq, PartialEq, Default, Serialize, Deserialize)]
pub struct SecurityFeatures {
pub privacy: bool,
pub wps: bool,
pub psk: bool,
pub eap: bool,
pub sae: bool,
pub owe: bool,
pub owe_transition_mode: bool,
pub eap_suite_b_192: bool,
pub wep40: bool,
pub wep104: bool,
pub tkip: bool,
pub ccmp: bool,
}
impl SecurityFeatures {
#[must_use]
pub fn is_open(&self) -> bool {
!self.privacy
&& !self.psk
&& !self.eap
&& !self.sae
&& !self.owe
&& !self.owe_transition_mode
&& !self.eap_suite_b_192
&& !self.wep40
&& !self.wep104
}
#[must_use]
pub fn is_enterprise(&self) -> bool {
self.eap || self.eap_suite_b_192
}
#[must_use]
pub fn is_wpa3(&self) -> bool {
self.sae || self.owe
}
#[must_use]
pub fn preferred_connect_type(&self) -> ConnectType {
if self.eap || self.eap_suite_b_192 {
ConnectType::Eap
} else if self.sae {
ConnectType::Sae
} else if self.owe || self.owe_transition_mode {
ConnectType::Owe
} else if self.psk {
ConnectType::Psk
} else {
ConnectType::Open
}
}
}
#[non_exhaustive]
#[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize, Deserialize)]
pub enum ConnectType {
Open,
Psk,
Sae,
Eap,
Owe,
}
impl fmt::Display for ConnectType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Open => write!(f, "Open"),
Self::Psk => write!(f, "PSK"),
Self::Sae => write!(f, "SAE"),
Self::Eap => write!(f, "EAP"),
Self::Owe => write!(f, "OWE"),
}
}
}
const AP_FLAGS_PRIVACY: u32 = 0x1;
const AP_FLAGS_WPS: u32 = 0x2;
const SEC_PAIR_WEP40: u32 = 0x1;
const SEC_PAIR_WEP104: u32 = 0x2;
const SEC_PAIR_TKIP: u32 = 0x4;
const SEC_PAIR_CCMP: u32 = 0x8;
const SEC_KEY_MGMT_PSK: u32 = 0x100;
const SEC_KEY_MGMT_802_1X: u32 = 0x200;
const SEC_KEY_MGMT_SAE: u32 = 0x400;
const SEC_KEY_MGMT_OWE: u32 = 0x800;
const SEC_KEY_MGMT_OWE_TM: u32 = 0x1000;
const SEC_KEY_MGMT_EAP_SUITE_B_192: u32 = 0x2000;
pub(crate) fn decode_security(flags: u32, wpa: u32, rsn: u32) -> SecurityFeatures {
let combined = wpa | rsn;
SecurityFeatures {
privacy: (flags & AP_FLAGS_PRIVACY) != 0,
wps: (flags & AP_FLAGS_WPS) != 0,
psk: (combined & SEC_KEY_MGMT_PSK) != 0,
eap: (combined & SEC_KEY_MGMT_802_1X) != 0,
sae: (combined & SEC_KEY_MGMT_SAE) != 0,
owe: (combined & SEC_KEY_MGMT_OWE) != 0,
owe_transition_mode: (combined & SEC_KEY_MGMT_OWE_TM) != 0,
eap_suite_b_192: (combined & SEC_KEY_MGMT_EAP_SUITE_B_192) != 0,
wep40: (combined & SEC_PAIR_WEP40) != 0,
wep104: (combined & SEC_PAIR_WEP104) != 0,
tkip: (combined & SEC_PAIR_TKIP) != 0,
ccmp: (combined & SEC_PAIR_CCMP) != 0,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn decode_open_network() {
let sec = decode_security(0, 0, 0);
assert!(sec.is_open());
assert!(!sec.is_enterprise());
assert!(!sec.is_wpa3());
assert_eq!(sec.preferred_connect_type(), ConnectType::Open);
}
#[test]
fn decode_wep40() {
let sec = decode_security(AP_FLAGS_PRIVACY, SEC_PAIR_WEP40, 0);
assert!(!sec.is_open());
assert!(sec.privacy);
assert!(sec.wep40);
assert_eq!(sec.preferred_connect_type(), ConnectType::Open);
}
#[test]
fn decode_wep104() {
let sec = decode_security(AP_FLAGS_PRIVACY, SEC_PAIR_WEP104, 0);
assert!(sec.wep104);
assert!(sec.privacy);
}
#[test]
fn decode_wpa_tkip_psk() {
let sec = decode_security(AP_FLAGS_PRIVACY, SEC_PAIR_TKIP | SEC_KEY_MGMT_PSK, 0);
assert!(sec.psk);
assert!(sec.tkip);
assert!(!sec.ccmp);
assert_eq!(sec.preferred_connect_type(), ConnectType::Psk);
}
#[test]
fn decode_wpa2_ccmp_psk() {
let sec = decode_security(AP_FLAGS_PRIVACY, 0, SEC_PAIR_CCMP | SEC_KEY_MGMT_PSK);
assert!(sec.psk);
assert!(sec.ccmp);
assert!(!sec.tkip);
assert_eq!(sec.preferred_connect_type(), ConnectType::Psk);
}
#[test]
fn decode_wpa2_enterprise() {
let sec = decode_security(AP_FLAGS_PRIVACY, 0, SEC_PAIR_CCMP | SEC_KEY_MGMT_802_1X);
assert!(sec.eap);
assert!(sec.ccmp);
assert!(sec.is_enterprise());
assert_eq!(sec.preferred_connect_type(), ConnectType::Eap);
}
#[test]
fn decode_wpa3_sae() {
let sec = decode_security(AP_FLAGS_PRIVACY, 0, SEC_PAIR_CCMP | SEC_KEY_MGMT_SAE);
assert!(sec.sae);
assert!(sec.ccmp);
assert!(sec.is_wpa3());
assert_eq!(sec.preferred_connect_type(), ConnectType::Sae);
}
#[test]
fn decode_owe() {
let sec = decode_security(0, 0, SEC_PAIR_CCMP | SEC_KEY_MGMT_OWE);
assert!(sec.owe);
assert!(sec.is_wpa3());
assert_eq!(sec.preferred_connect_type(), ConnectType::Owe);
}
#[test]
fn decode_owe_transition() {
let sec = decode_security(0, 0, SEC_KEY_MGMT_OWE_TM);
assert!(sec.owe_transition_mode);
assert_eq!(sec.preferred_connect_type(), ConnectType::Owe);
}
#[test]
fn decode_eap_suite_b_192() {
let sec = decode_security(
AP_FLAGS_PRIVACY,
0,
SEC_PAIR_CCMP | SEC_KEY_MGMT_EAP_SUITE_B_192,
);
assert!(sec.eap_suite_b_192);
assert!(sec.is_enterprise());
assert_eq!(sec.preferred_connect_type(), ConnectType::Eap);
}
#[test]
fn decode_wps_flag() {
let sec = decode_security(AP_FLAGS_WPS, 0, 0);
assert!(sec.wps);
}
#[test]
fn decode_mixed_wpa_wpa2() {
let sec = decode_security(
AP_FLAGS_PRIVACY,
SEC_PAIR_TKIP | SEC_KEY_MGMT_PSK,
SEC_PAIR_CCMP | SEC_KEY_MGMT_PSK,
);
assert!(sec.psk);
assert!(sec.tkip);
assert!(sec.ccmp);
}
#[test]
fn ap_mode_from_u32() {
assert_eq!(ApMode::from(1), ApMode::Adhoc);
assert_eq!(ApMode::from(2), ApMode::Infrastructure);
assert_eq!(ApMode::from(3), ApMode::Ap);
assert_eq!(ApMode::from(4), ApMode::Mesh);
assert_eq!(ApMode::from(99), ApMode::Unknown(99));
}
#[test]
fn connect_type_display() {
assert_eq!(ConnectType::Open.to_string(), "Open");
assert_eq!(ConnectType::Psk.to_string(), "PSK");
assert_eq!(ConnectType::Sae.to_string(), "SAE");
assert_eq!(ConnectType::Eap.to_string(), "EAP");
assert_eq!(ConnectType::Owe.to_string(), "OWE");
}
#[test]
fn security_features_default_is_open() {
let sec = SecurityFeatures::default();
assert!(sec.is_open());
}
#[test]
fn eap_prioritized_over_psk() {
let sec = SecurityFeatures {
psk: true,
eap: true,
ccmp: true,
privacy: true,
..Default::default()
};
assert_eq!(sec.preferred_connect_type(), ConnectType::Eap);
}
#[test]
fn sae_prioritized_over_psk() {
let sec = SecurityFeatures {
psk: true,
sae: true,
ccmp: true,
privacy: true,
..Default::default()
};
assert_eq!(sec.preferred_connect_type(), ConnectType::Sae);
}
}