1use super::{ParseResult, SocketResult};
2use super::{Result, SocketHandle, config, config::unprintf, warn};
3use crate::error::ClientError;
4
5use serde::Serialize;
6use std::collections::HashMap;
7use std::fmt::Display;
8use std::str::FromStr;
9use std::sync::Arc;
10
11#[derive(Serialize, Debug, Clone)]
12pub struct ScanResult {
14 pub mac: String,
15 pub frequency: String,
16 pub signal: isize,
17 pub flags: String,
18 pub name: String,
19}
20
21impl ScanResult {
22 fn from_line(line: &str) -> Option<Self> {
23 let (mac, rest) = line.split_once('\t')?;
24 let (frequency, rest) = rest.split_once('\t')?;
25 let (signal, rest) = rest.split_once('\t')?;
26 let signal = isize::from_str(signal).ok()?;
27 let (flags, escaped_name) = rest.split_once('\t')?;
28 let name = unprintf(escaped_name).ok()?;
29 Some(ScanResult {
30 mac: mac.to_string(),
31 frequency: frequency.to_string(),
32 signal,
33 flags: flags.to_string(),
34 name,
35 })
36 }
37
38 #[allow(clippy::tabs_in_doc_comments)]
40 pub fn vec_from_str(response: &str) -> ParseResult<Arc<Vec<ScanResult>>> {
53 let mut results = Vec::new();
54 for line in response.lines().skip(1) {
55 if let Some(scan_result) = ScanResult::from_line(line) {
58 results.push(scan_result);
59 } else {
60 warn!("Invalid result from scan: {line}");
61 }
62 }
63 results.sort_by_key(|a| a.signal);
64 Ok(Arc::new(results))
65 }
66}
67
68#[derive(Serialize, Debug, Clone)]
69pub struct NetworkResult {
71 pub network_id: usize,
72 pub ssid: String,
73 pub flags: String,
74}
75
76fn parse_get_network(resp: &str) -> ParseResult<String> {
77 let escaped = resp.trim_matches('\"');
78 Ok(unprintf(escaped)?)
79}
80
81impl NetworkResult {
82 pub(crate) async fn request_results<const N: usize>(
83 socket_handle: &mut SocketHandle<N>,
84 ) -> SocketResult<Result<Vec<NetworkResult>>> {
85 let response: String = match socket_handle
86 .request("LIST_NETWORKS", TryInto::try_into)
87 .await?
88 {
89 Ok(x) => x,
90 Err(e) => return Ok(Err(e)),
91 };
92 let mut results = Vec::new();
93 let split = response.split('\n').skip(1);
94 for line in split {
95 let mut line_split = line.split_whitespace();
96 if let Some(network_id) = line_split.next() {
97 if let Ok(network_id) = usize::from_str(network_id) {
98 let ssid = match socket_handle
99 .request(&format!("GET_NETWORK {network_id} ssid"), parse_get_network)
100 .await?
101 {
102 Ok(x) => x,
103 Err(e) => return Ok(Err(e)),
104 };
105 if let Some(flags) = line_split.last() {
106 results.push(NetworkResult {
107 flags: flags.into(),
108 ssid,
109 network_id,
110 })
111 }
112 } else {
113 warn!("Invalid network_id: {network_id}")
114 }
115 }
116 }
117 Ok(Ok(results))
118 }
119}
120
121#[derive(Serialize, Debug, Clone, Default)]
129pub struct Status {
130 pub wpa_state: Option<String>,
131 pub ssid: Option<String>,
132 pub bssid: Option<String>,
133 pub id: Option<usize>,
134 pub freq: Option<u32>,
135 pub address: Option<String>,
136 pub ip_address: Option<String>,
137 pub key_mgmt: Option<String>,
138 pub mode: Option<String>,
139 pub raw: HashMap<String, String>,
142}
143
144impl Status {
145 pub fn get(&self, key: &str) -> Option<&str> {
147 self.raw.get(key).map(String::as_str)
148 }
149}
150
151pub(crate) fn parse_status(response: &str) -> ParseResult<Status> {
152 let raw: HashMap<String, String> = config::from_str(response)?;
153 Ok(Status {
154 wpa_state: raw.get("wpa_state").cloned(),
155 ssid: raw.get("ssid").cloned(),
156 bssid: raw.get("bssid").cloned(),
157 id: raw.get("id").and_then(|v| v.parse().ok()),
158 freq: raw.get("freq").and_then(|v| v.parse().ok()),
159 address: raw.get("address").cloned(),
160 ip_address: raw.get("ip_address").cloned(),
161 key_mgmt: raw.get("key_mgmt").cloned(),
162 mode: raw.get("mode").cloned(),
163 raw,
164 })
165}
166
167#[derive(Debug)]
168pub enum KeyMgmt {
172 None,
173 WpaPsk,
174 WpaEap,
175 IEEE8021X,
176}
177
178impl Display for KeyMgmt {
179 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
180 let str = match self {
181 KeyMgmt::None => "NONE".to_string(),
182 KeyMgmt::WpaPsk => "WPA-PSK".to_string(),
183 KeyMgmt::WpaEap => "WPA-EAP".to_string(),
184 KeyMgmt::IEEE8021X => "IEEE8021X".to_string(),
185 };
186 write!(f, "{}", str)
187 }
188}
189
190#[derive(Clone)]
224pub struct Psk(PskInner);
225
226#[derive(Clone)]
227enum PskInner {
228 Passphrase(String),
229 Raw([u8; 32]),
230}
231
232impl Psk {
233 pub fn passphrase(passphrase: impl Into<String>) -> Result<Self> {
247 let passphrase = passphrase.into();
248 let quotable = passphrase
249 .bytes()
250 .all(|b| (0x20..=0x7e).contains(&b) && b != b'"');
251 if !quotable || !(8..=63).contains(&passphrase.len()) {
252 return Err(ClientError::InvalidPsk);
253 }
254 Ok(Psk(PskInner::Passphrase(passphrase)))
255 }
256
257 pub fn raw(key: [u8; 32]) -> Self {
260 Psk(PskInner::Raw(key))
261 }
262
263 pub(crate) fn to_field(&self) -> String {
266 match &self.0 {
267 PskInner::Passphrase(passphrase) => format!("\"{passphrase}\""),
268 PskInner::Raw(key) => hex::encode(key),
269 }
270 }
271}
272
273impl FromStr for Psk {
274 type Err = ClientError;
275
276 fn from_str(s: &str) -> Result<Self> {
277 if s.len() == 64 && s.bytes().all(|b| b.is_ascii_hexdigit()) {
278 let mut key = [0u8; 32];
279 hex::decode_to_slice(s, &mut key).expect("64 hex digits");
280 return Ok(Psk::raw(key));
281 }
282 Self::passphrase(s)
283 }
284}
285
286impl std::fmt::Debug for Psk {
289 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
290 write!(f, "Psk(<redacted>)")
291 }
292}
293
294#[derive(Debug, Clone, Copy, PartialEq, Eq)]
313pub struct Bssid([u8; 6]);
314
315impl From<[u8; 6]> for Bssid {
316 fn from(mac: [u8; 6]) -> Self {
317 Bssid(mac)
318 }
319}
320
321impl FromStr for Bssid {
322 type Err = ClientError;
323
324 fn from_str(s: &str) -> Result<Self> {
325 let mut mac = [0u8; 6];
326 let mut octets = s.split(':');
327 for byte in mac.iter_mut() {
328 let octet = octets.next().ok_or(ClientError::InvalidBssid)?;
329 if octet.len() != 2 || !octet.bytes().all(|b| b.is_ascii_hexdigit()) {
331 return Err(ClientError::InvalidBssid);
332 }
333 *byte = u8::from_str_radix(octet, 16).map_err(|_| ClientError::InvalidBssid)?;
334 }
335 if octets.next().is_some() {
336 return Err(ClientError::InvalidBssid);
337 }
338 Ok(Bssid(mac))
339 }
340}
341
342impl Display for Bssid {
343 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
344 let [a, b, c, d, e, g] = self.0;
345 write!(f, "{a:02x}:{b:02x}:{c:02x}:{d:02x}:{e:02x}:{g:02x}")
346 }
347}
348
349#[cfg(test)]
350mod tests {
351 use super::*;
352
353 #[test]
354 fn parse_status_types_known_fields_and_keeps_raw() {
355 let resp = "\
356bssid=cc:7b:5c:1a:d2:21
357freq=2412
358ssid=my-network
359id=3
360mode=station
361wpa_state=COMPLETED
362address=aa:bb:cc:dd:ee:ff
363ip_address=192.168.1.42
364some_future_key=42";
365 let status = parse_status(resp).unwrap();
366 assert_eq!(status.wpa_state.as_deref(), Some("COMPLETED"));
367 assert_eq!(status.ssid.as_deref(), Some("my-network"));
368 assert_eq!(status.id, Some(3));
369 assert_eq!(status.freq, Some(2412));
370 assert_eq!(status.ip_address.as_deref(), Some("192.168.1.42"));
371 assert_eq!(status.key_mgmt, None);
373 assert_eq!(status.get("some_future_key"), Some("42"));
375 }
376
377 #[test]
378 fn parse_status_tolerates_sparse_response() {
379 let status = parse_status("wpa_state=SCANNING").unwrap();
380 assert_eq!(status.wpa_state.as_deref(), Some("SCANNING"));
381 assert_eq!(status.ssid, None);
382 assert_eq!(status.id, None);
383 }
384}