libiw_async/
ap_info.rs

1use anyhow::Result;
2use encoding_rs::*;
3use macaddr::MacAddr6;
4use std::{collections::HashMap, fmt::Display, str::FromStr};
5
6#[repr(C)]
7#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
8pub enum BandWidth {
9    HT20,
10    HT40,
11    MHz80,
12    MHz160,
13}
14
15impl Default for BandWidth {
16    fn default() -> Self {
17        Self::HT20
18    }
19}
20
21impl Display for BandWidth {
22    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
23        write!(f, "{}", self.cmd())
24    }
25}
26
27impl BandWidth {
28    pub fn cmd(&self) -> &'static str {
29        match self {
30            BandWidth::HT20 => "HT20",
31            BandWidth::HT40 => "HT40",
32            BandWidth::MHz80 => "80MHz",
33            BandWidth::MHz160 => "160MHz",
34        }
35    }
36}
37
38#[repr(C)]
39#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
40pub enum SecondChannel {
41    Below,
42    Above,
43}
44
45#[derive(Debug, Clone)]
46pub struct BSS {
47    pub channel: i32,
48    pub freq_mhz: i32,
49    pub rssi: i32,
50    pub ssid: String,
51    pub width: BandWidth,
52    pub second: Option<SecondChannel>,
53    pub addr: MacAddr6,
54    pub std: Standard,
55    pub security: Vec<Security>,
56}
57
58#[repr(C)]
59#[derive(Debug, Clone, Copy, Hash)]
60pub enum Standard {
61    B,
62    A,
63    G,
64    N,
65    AC,
66    AX,
67}
68
69#[repr(C)]
70#[derive(Debug, Clone, Copy, Hash)]
71pub enum Security {
72    WEP,
73    WPA,
74    WPA2,
75    WPA3,
76}
77
78impl Display for BSS {
79    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
80        write!(
81            f,
82            r"BSS
83    ssid: {}
84    mac: {}
85    channel: {}
86    freq: {} MHz
87    rssi: {}
88    width: {}
89    second: {:?}
90    std: 802.11{:?}
91    security: {:?}",
92            self.ssid,
93            self.addr,
94            self.channel,
95            self.freq_mhz,
96            self.rssi,
97            self.width,
98            self.second,
99            self.std,
100            self.security,
101        )
102    }
103}
104
105impl BSS {
106    pub(crate) fn parse_all(result: &str) -> Result<Vec<Self>> {
107        let mut out = Vec::new();
108        let mut iter = 0;
109
110        while let Some(_n) = result[iter..].find("\nBSS") {
111            let mut end = result.len() - 1;
112            if let Some(n) = result[iter + 5..].find("\nBSS ") {
113                end = iter + n + 5;
114            }
115            let bss_str = &result[iter..end];
116
117            if let Some(s) = Self::parse2(bss_str) {
118                out.push(s);
119            }
120
121            iter = end;
122        }
123
124        Ok(out)
125    }
126
127    fn parse2(src: &str) -> Option<Self> {
128        let addr = {
129            let mut line = src.trim();
130            if let Some(n) = line.find("BSS") {
131                if n < 10 {
132                    line = &line[n + 4..];
133                }
134            }
135            line = line.trim();
136            if let Some(n) = line.find("(") {
137                line = &line[..n];
138            }
139            MacAddr6::from_str(line).unwrap_or_default()
140        };
141
142        let sections = prase_section(src, 1);
143
144        let freq_mhz = sections
145            .get("freq")
146            .map_or(0, |s| s.trim().parse::<i32>().unwrap());
147
148        let rssi = sections.get("signal").map_or(-127, |src| {
149            let src = src.trim_end_matches("dBm").trim();
150            let rssi = src.parse::<f32>().unwrap();
151            rssi as i32
152        });
153
154        let ssid = sections
155            .get("SSID")
156            .map_or(String::new(), |s| parse_ssid(s.trim()));
157
158        let mut channel = sections.get("DS Parameter set").map_or(0, |src| {
159            let c = src.trim().trim_start_matches("channel").trim();
160            c.parse().unwrap()
161        });
162        let mut second = None;
163
164        if let Some(src) = sections.get("HT operation") {
165            let sub = prase_section(src, 2);
166
167            if let Some(pri) = sub.get("* primary channel") {
168                let c = pri.trim();
169                if let Ok(ch) = c.parse() {
170                    channel = ch;
171                }
172            }
173
174            if let Some(s) = sub.get("* secondary channel offset") {
175                second = match s.trim() {
176                    "above" => Some(SecondChannel::Above),
177                    "below" => Some(SecondChannel::Below),
178                    _ => None,
179                }
180            }
181        }
182
183        let mut width = BandWidth::HT20;
184
185        if freq_mhz >= 5000 {
186            if src.contains("short GI (80 MHz)") {
187                width = BandWidth::MHz80;
188            }
189            if src.contains("short GI (160 MHz)") {
190                width = BandWidth::MHz80;
191            }
192        } else if second.is_some() {
193            width = BandWidth::HT40;
194        }
195
196        let mut standard = Standard::B;
197        let mut security = Vec::new();
198
199        if let Some(rate) = sections.get("Supported rates") {
200            if rate.contains("11.0") {
201                standard = Standard::A;
202            }
203        }
204
205        if sections.contains_key("HT capabilities") {
206            standard = Standard::G;
207        }
208
209        if freq_mhz >= 5000 {
210            standard = Standard::N
211        }
212
213        if sections.contains_key("VHT capabilities") {
214            standard = Standard::AC;
215        }
216        if sections.contains_key("HE capabilities") {
217            standard = Standard::AX;
218        }
219
220        if sections.contains_key("WEP") {
221            security.push(Security::WEP);
222        }
223
224        if sections.contains_key("WPA") {
225            security.push(Security::WPA);
226        }
227
228        if sections.contains_key("RSN") {
229            security.push(Security::WPA2);
230        }
231
232        Some(Self {
233            channel,
234            freq_mhz,
235            rssi,
236            ssid,
237            width,
238            second,
239            addr,
240            std: standard,
241            security,
242        })
243    }
244}
245
246// fn search_key_value<'a>(src: &'a str, key: &str) -> Option<&'a str> {
247//     let i_key_b = src.find(key)?;
248//     let key_and_after = &src[i_key_b..];
249//     let i_value_e = key_and_after.find("\n")?;
250//     let value = &key_and_after[..i_value_e];
251//     Some(value.trim_start_matches(key).trim())
252// }
253
254fn parse_ssid(src: &str) -> String {
255    let mut s = Vec::with_capacity(src.len());
256    let mut i = 0;
257    let bytes = src.as_bytes();
258    while i < src.len() {
259        let b = bytes[i];
260        if b == b'\\' {
261            let elem = &bytes[i + 2..i + 4];
262            let elem_str = match std::str::from_utf8(elem) {
263                Ok(s) => s,
264                Err(_) => {
265                    break;
266                }
267            };
268
269            if let Ok(c) = u8::from_str_radix(elem_str, 16) {
270                s.push(c);
271            }
272            i += 4;
273        } else {
274            s.push(b);
275            i += 1;
276        }
277    }
278    let encodings_to_try = vec![UTF_8, GBK, BIG5, EUC_KR];
279
280    for encoding in encodings_to_try {
281        let (out, _, has_err) = encoding.decode(&s);
282        if !has_err {
283            return out.to_string();
284        }
285    }
286
287    String::new()
288}
289
290fn prase_section(src: &str, level: usize) -> HashMap<String, String> {
291    let mut out = HashMap::new();
292    let mut value = String::new();
293    let mut key = String::new();
294
295    let pattern = "\t".repeat(level);
296
297    for line in src.lines() {
298        if line.starts_with(pattern.as_str()) && line.chars().count() > level {
299            let b = line.as_bytes()[level];
300            if b != b'\t' {
301                if !key.is_empty() {
302                    out.insert(key.clone(), value.clone());
303                    value = String::new();
304                }
305                let sp = line.split(":").collect::<Vec<_>>();
306                key = sp[0].trim().to_string();
307                value += pattern.as_str();
308                value += sp[1].trim();
309                continue;
310            }
311        }
312        value += "\r\n";
313        value += line;
314    }
315
316    out
317}
318
319#[cfg(test)]
320mod test {
321    use super::*;
322
323    const SCAN_RESULT: &str = include_str!("../data/scan.txt");
324
325    #[test]
326    fn test_bss() {
327        let out = BSS::parse_all(SCAN_RESULT).unwrap();
328        assert_eq!(out[0].ssid, "IOT2_4G2");
329        for one in out {
330            println!("{}", one);
331        }
332    }
333
334    // #[test]
335    // fn test_section() {
336    //     let src = r"BSS 04:f9:f8:aa:54:98(on wlxc43cb064be06)
337    //     last seen: 7177304.561s [boottime]
338    //     TSF: 11505523040 usec (0d, 03:11:45)
339    //     RSN:     * Version: 1
340    //              * Group cipher: CCMP
341    //              * Pairwise ciphers: CCMP
342    //              * Authentication suites: PSK
343    //              * Capabilities: 1-PTKSA-RC 1-GTKSA-RC (0x0000)
344    //     RM enabled capabilities:
345    //             Capabilities: 0x73 0xd0 0x00 0x00 0x0c
346    //                     Link Measurement
347    //                     Neighbor Report
348    //                     Beacon Passive Measurement
349    //                     Beacon Active Measurement
350    //                     Beacon Table Measurement
351    //                     LCI Measurement
352    //                     Transmit Stream/Category Measurement
353    //                     Triggered Transmit Stream/Category
354    //                     FTM Range Report
355    //                     Civic Location Measurement
356    //             Nonoperating Channel Max Measurement Duration: 0
357    //     ";
358
359    //     let out = prase_section(src, 1);
360    //     assert_eq!(out["TSF"], "11505523040 usec (0d, 03:11:45)");
361    // }
362
363    #[test]
364    fn test_ssid_parse() {
365        let s = "\\xce\\xc4\\xc1\\xd8_5G\\xce\\xc4\\xc1\\xd8";
366        let ssid = parse_ssid(s);
367
368        assert_eq!(ssid, "文霖_5G文霖");
369    }
370}