Skip to content

Commit

Permalink
feat: use systemprofiler utility to get list of WiFi
Browse files Browse the repository at this point in the history
This utility does not return the MAC address of routers.
  • Loading branch information
dilawar committed Jul 31, 2024
1 parent 1bceb18 commit ee47a51
Show file tree
Hide file tree
Showing 3 changed files with 37 additions and 17 deletions.
2 changes: 1 addition & 1 deletion src/bin/wifiscanner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ fn main() {
for network in networks {
println!(
"{} {:20} {:10} {:4} {}",
network.mac, network.ssid, network.channel, network.signal_level, network.security
network.mac.unwrap_or("NA".to_string()), network.ssid, network.channel, network.signal_level, network.security
);
}
}
5 changes: 3 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,10 +63,11 @@ pub enum Error {
/// Wifi struct used to return information about wifi hotspots
#[derive(Debug, PartialEq, Eq, Default, Clone)]
pub struct Wifi {
/// mac address
pub mac: String,
/// mac address (may be missing on newer version of macos)
pub mac: Option<String>,
/// hotspot name
pub ssid: String,
/// channel
pub channel: String,
/// wifi signal strength in dBm
pub signal_level: String,
Expand Down
47 changes: 33 additions & 14 deletions src/sys/macos.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
#![allow(non_snake_case)]

use anyhow::Context;
use std::process::Command;

Expand All @@ -9,10 +11,10 @@ pub(crate) fn scan() -> anyhow::Result<Vec<Wifi>> {
.arg("SPAirPortDataType")
.arg("-json")
.output()?;
parse_systemprofiler(String::from_utf8_lossy(output.stdout))
parse_systemprofiler(String::from_utf8_lossy(&output.stdout).into())
}

fn parse_systemprofiler(txt: &str) -> anyhow::Result<Vec<Wifi>> {
fn parse_systemprofiler(text: String) -> anyhow::Result<Vec<Wifi>> {
#[derive(serde::Deserialize, Debug)]
struct SystemProfilerData {
SPAirPortDataType: Vec<Interfaces>,
Expand All @@ -26,26 +28,44 @@ fn parse_systemprofiler(txt: &str) -> anyhow::Result<Vec<Wifi>> {
#[derive(serde::Deserialize, Debug)]
struct Interface {
spairport_airport_other_local_wireless_networks: Option<Vec<WifiPoint>>,
// spairport_wireless_mac_address: String,
}

#[derive(serde::Deserialize, Debug)]
struct WifiPoint {
_name: String,
spairport_network_channel: String,
spairport_network_phymode: String,
// spairport_network_phymode: String,
spairport_security_mode: String,
spairport_signal_noise: String,
}

let text = String::from_utf8_lossy(&output.stdout);
println!("{text}");

let data: SystemProfilerData = serde_json::from_str(&text)?;
println!("{data:?}");

Ok(vec![])
let mut wifis = vec![];
for interface in data.SPAirPortDataType.into_iter().map(|x| x.spairport_airport_interfaces).flatten() {
for wifi in interface.spairport_airport_other_local_wireless_networks.unwrap_or(vec![]) {
let ssid = wifi._name;
let channel = wifi.spairport_network_channel;
let security = wifi.spairport_security_mode;
let security = security.strip_prefix("spairport_security_mode_").unwrap_or(&security).to_string();
let signal_level = wifi.spairport_signal_noise.split('/').nth(0).unwrap_or("").trim().to_string();

wifis.push( crate::Wifi {
mac: None,
ssid,
channel,
security,
signal_level,
})
}
}

Ok(wifis)
}

/// Returns a list of WiFi hotspots in your area - (OSX/MacOS) uses `airport`
#[allow(dead_code)]
pub(crate) fn scan_using_airport() -> anyhow::Result<Vec<Wifi>> {
let output = Command::new(
"/System/Library/PrivateFrameworks/Apple80211.\
Expand Down Expand Up @@ -91,7 +111,7 @@ fn parse_airport(network_list: &str) -> anyhow::Result<Vec<Wifi>> {
let security = &line[col_security..].trim();

wifis.push(Wifi {
mac: mac.to_string(),
mac: Some(mac.to_string()),
ssid: ssid.to_string(),
channel: channel.to_string(),
signal_level: signal_level.to_string(),
Expand All @@ -112,24 +132,23 @@ mod tests {

#[test]
fn should_parse_system_profiler() {
let txt = include_str!("tests/fixtures/systemprofiler/output.txt");
let wifis = parse_systemprofiler(&txt).unwrap();
println!("{wifis:?}");
let txt = include_str!("../../tests/fixtures/systemprofiler/output.txt");
let _wifis = parse_systemprofiler(txt.to_string()).unwrap();
}

#[test]
fn should_parse_airport() {
let mut expected: Vec<Wifi> = Vec::new();
expected.push(Wifi {
mac: "00:35:1a:90:56:03".to_string(),
mac: Some("00:35:1a:90:56:03".to_string()),
ssid: "OurTest".to_string(),
channel: "112".to_string(),
signal_level: "-70".to_string(),
security: "WPA2(PSK/AES/AES)".to_string(),
});

expected.push(Wifi {
mac: "00:35:1a:90:56:00".to_string(),
mac: Some("00:35:1a:90:56:00".to_string()),
ssid: "TEST-Wifi".to_string(),
channel: "1".to_string(),
signal_level: "-67".to_string(),
Expand Down

0 comments on commit ee47a51

Please sign in to comment.