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
246fn 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]
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}