use anyhow::Result;
use encoding_rs::*;
use macaddr::MacAddr6;
use std::{collections::HashMap, fmt::Display, str::FromStr};
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum BandWidth {
HT20,
HT40,
MHz80,
MHz160,
}
impl Display for BandWidth {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.cmd())
}
}
impl BandWidth {
pub fn cmd(&self) -> &'static str {
match self {
BandWidth::HT20 => "HT20",
BandWidth::HT40 => "HT40",
BandWidth::MHz80 => "80MHz",
BandWidth::MHz160 => "160MHz",
}
}
}
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum SecondChannel {
Below,
Above,
}
#[derive(Debug, Clone)]
pub struct BSS {
pub channel: i32,
pub freq_mhz: i32,
pub rssi: i32,
pub ssid: String,
pub width: BandWidth,
pub second: Option<SecondChannel>,
pub addr: MacAddr6,
pub std: Standard,
pub security: Vec<Security>,
}
#[repr(C)]
#[derive(Debug, Clone, Copy, Hash)]
pub enum Standard {
B,
A,
G,
N,
AC,
AX,
}
#[repr(C)]
#[derive(Debug, Clone, Copy, Hash)]
pub enum Security {
WEP,
WPA,
WPA2,
WPA3,
}
impl Display for BSS {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
r"BSS
ssid: {}
mac: {}
channel: {}
freq: {} MHz
rssi: {}
width: {}
second: {:?}
std: 802.11{:?}
security: {:?}",
self.ssid,
self.addr,
self.channel,
self.freq_mhz,
self.rssi,
self.width,
self.second,
self.std,
self.security,
)
}
}
impl BSS {
pub(crate) fn parse_all(result: &str) -> Result<Vec<Self>> {
let mut out = Vec::new();
let mut iter = 0;
while let Some(_n) = result[iter..].find("\nBSS") {
let mut end = result.len() - 1;
if let Some(n) = result[iter + 5..].find("\nBSS ") {
end = iter + n + 5;
}
let bss_str = &result[iter..end];
if let Some(s) = Self::parse2(bss_str) {
out.push(s);
}
iter = end;
}
Ok(out)
}
fn parse2(src: &str) -> Option<Self> {
let addr = {
let mut line = src.trim();
if let Some(n) = line.find("BSS") {
if n < 10 {
line = &line[n + 4..];
}
}
line = line.trim();
if let Some(n) = line.find("(") {
line = &line[..n];
}
MacAddr6::from_str(line).unwrap_or_default()
};
let sections = prase_section(src, 1);
let freq_mhz = sections
.get("freq")
.map_or(0, |s| s.trim().parse::<i32>().unwrap());
let rssi = sections.get("signal").map_or(-127, |src| {
let src = src.trim_end_matches("dBm").trim();
let rssi = src.parse::<f32>().unwrap();
rssi as i32
});
let ssid = sections
.get("SSID")
.map_or(String::new(), |s| parse_ssid(s.trim()));
let mut channel = sections.get("DS Parameter set").map_or(0, |src| {
let c = src.trim().trim_start_matches("channel").trim();
c.parse().unwrap()
});
let mut second = None;
if let Some(src) = sections.get("HT operation") {
let sub = prase_section(src, 2);
if let Some(pri) = sub.get("* primary channel") {
let c = pri.trim();
if let Ok(ch) = c.parse() {
channel = ch;
}
}
if let Some(s) = sub.get("* secondary channel offset") {
second = match s.trim() {
"above" => Some(SecondChannel::Above),
"below" => Some(SecondChannel::Below),
_ => None,
}
}
}
let mut width = BandWidth::HT20;
if freq_mhz >= 5000 {
if src.contains("short GI (80 MHz)") {
width = BandWidth::MHz80;
}
if src.contains("short GI (160 MHz)") {
width = BandWidth::MHz80;
}
} else if second.is_some() {
width = BandWidth::HT40;
}
let mut standard = Standard::B;
let mut security = Vec::new();
if let Some(rate) = sections.get("Supported rates") {
if rate.contains("11.0") {
standard = Standard::A;
}
}
if sections.contains_key("HT capabilities") {
standard = Standard::G;
}
if freq_mhz >= 5000 {
standard = Standard::N
}
if sections.contains_key("VHT capabilities") {
standard = Standard::AC;
}
if sections.contains_key("HE capabilities") {
standard = Standard::AX;
}
if sections.contains_key("WEP") {
security.push(Security::WEP);
}
if sections.contains_key("WPA") {
security.push(Security::WPA);
}
if sections.contains_key("RSN") {
security.push(Security::WPA2);
}
Some(Self {
channel,
freq_mhz,
rssi,
ssid,
width,
second,
addr,
std: standard,
security,
})
}
}
fn parse_ssid(src: &str) -> String {
let mut s = Vec::with_capacity(src.len());
let mut i = 0;
let bytes = src.as_bytes();
while i < src.len() {
let b = bytes[i];
if b == b'\\' {
let elem = &bytes[i + 2..i + 4];
let elem_str = match std::str::from_utf8(elem) {
Ok(s) => s,
Err(_) => {
break;
}
};
if let Ok(c) = u8::from_str_radix(elem_str, 16) {
s.push(c);
}
i += 4;
} else {
s.push(b);
i += 1;
}
}
let encodings_to_try = vec![UTF_8, GBK, BIG5, EUC_KR];
for encoding in encodings_to_try {
let (out, _, has_err) = encoding.decode(&s);
if !has_err {
return out.to_string();
}
}
String::new()
}
fn prase_section(src: &str, level: usize) -> HashMap<String, String> {
let mut out = HashMap::new();
let mut value = String::new();
let mut key = String::new();
let pattern = "\t".repeat(level);
for line in src.lines() {
if line.starts_with(pattern.as_str()) && line.chars().count() > level {
let b = line.as_bytes()[level];
if b != b'\t' {
if !key.is_empty() {
out.insert(key.clone(), value.clone());
value = String::new();
}
let sp = line.split(":").collect::<Vec<_>>();
key = sp[0].trim().to_string();
value += pattern.as_str();
value += sp[1].trim();
continue;
}
}
value += "\r\n";
value += line;
}
out
}
#[cfg(test)]
mod test {
use super::*;
const SCAN_RESULT: &str = include_str!("../data/scan.txt");
#[test]
fn test_bss() {
let out = BSS::parse_all(SCAN_RESULT).unwrap();
assert_eq!(out[0].ssid, "IOT2_4G2");
for one in out {
println!("{}", one);
}
}
#[test]
fn test_ssid_parse() {
let s = "\\xce\\xc4\\xc1\\xd8_5G\\xce\\xc4\\xc1\\xd8";
let ssid = parse_ssid(s);
assert_eq!(ssid, "æé_5Gæé");
}
}