Skip to content

Commit

Permalink
Add support for unicode in SSIDs and PSKs
Browse files Browse the repository at this point in the history
The corresponding c code for encoding and decoding them can be found in
wpa_supplicant/src/utils/common.c

printf_decode escapes a SSID for returning in scan results

wpa_config_parse_string reads a string from the config or the socket. It
might be nicer to use the printf encoded P"I \xf0\x9f\xa6\x80 rust"
style but I could not find a decent encoder.
  • Loading branch information
aj-bagwell committed Jul 30, 2024
1 parent 50641e8 commit 6b7de2a
Show file tree
Hide file tree
Showing 3 changed files with 68 additions and 37 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ keywords = ["hostapd", "wpa-supplicant", "wpa_supplicant", "wpa-cli", "wifi"]

[dependencies]
config = {version="0", default-features = false, features = ["ini"]}
hex = "0.4"
log = { version = "0" }
serde = {version = "1", features = ["derive"] }
thiserror = "1"
Expand Down
14 changes: 12 additions & 2 deletions src/sta/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -227,9 +227,9 @@ impl WifiStation {
let cmd = format!(
"SET_NETWORK {id} {}",
match param {
SetNetwork::Ssid(ssid) => format!("ssid \"{ssid}\""),
SetNetwork::Ssid(ssid) => format!("ssid {}", conf_escape(&ssid)),
SetNetwork::Bssid(bssid) => format!("bssid \"{bssid}\""),
SetNetwork::Psk(psk) => format!("psk \"{psk}\""),
SetNetwork::Psk(psk) => format!("psk {}", conf_escape(&psk)),
SetNetwork::KeyMgmt(mgmt) => format!("key_mgmt {}", mgmt),
}
);
Expand Down Expand Up @@ -306,6 +306,16 @@ impl WifiStation {
}
}

/// convert to wpa config format idealy a "quoted string"
/// in case of new-lines, quotes or emoji fall back to hex encoding the whole thing
fn conf_escape(raw: &str) -> String {
if raw.bytes().all(|b| b.is_ascii_graphic() && b != b'"') {
format!("\"{raw}\"")
} else {
hex::encode(raw)
}
}

struct SelectRequest {
response: oneshot::Sender<Result<SelectResult>>,
timeout: tokio::task::JoinHandle<()>,
Expand Down
90 changes: 55 additions & 35 deletions src/sta/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,43 +16,63 @@ pub struct ScanResult {
}

impl ScanResult {
fn from_line(line: &str) -> Option<Self> {
let (mac, rest) = line.split_once('\t')?;
let (frequency, rest) = rest.split_once('\t')?;
let (signal, rest) = rest.split_once('\t')?;
let signal = isize::from_str(signal).ok()?;
let (flags, escaped_name) = rest.split_once('\t')?;
let mut bytes = escaped_name.as_bytes().iter().copied();
let mut name = vec![];
// undo "printf_encode"
loop {
name.push(match bytes.next() {
Some(b'\\') => match bytes.next()? {
b'n' => b'\n',
b'r' => b'\r',
b't' => b'\t',
b'e' => b'\x1b',
b'x' => {
let hex = [bytes.next()?, bytes.next()?];
u8::from_str_radix(std::str::from_utf8(&hex).ok()?, 16).ok()?
}
c => c,
},
Some(c) => c,
None => break,
})
}
let name = String::from_utf8(name).ok()?;
Some(ScanResult {
mac: mac.to_string(),
frequency: frequency.to_string(),
signal,
flags: flags.to_string(),
name,
})
}

// Overide to allow tabs in the raw string to avoid double escaping everything
#[allow(clippy::tabs_in_doc_comments)]
/// Parses lines from a scan result
///```
///use wifi_ctrl::sta::ScanResult;
///let results = ScanResult::vec_from_str(r#"bssid / frequency / signal level / flags / ssid
///00:5f:67:90:da:64 2417 -35 [WPA-PSK-CCMP][WPA2-PSK-CCMP][ESS] TP-Link DA64
///e0:91:f5:7d:11:c0 2462 -33 [WPA2-PSK-CCMP][WPS][ESS] ¯\\_(\xe3\x83\x84)_/¯
///"#).unwrap();
///assert_eq!(results[0].mac, "00:5f:67:90:da:64");
///assert_eq!(results[0].name, "TP-Link DA64");
///assert_eq!(results[1].signal, -33);
///assert_eq!(results[1].name, r#"¯\_(ツ)_/¯"#);
///```
pub fn vec_from_str(response: &str) -> Result<Vec<ScanResult>> {
let mut results = Vec::new();
let split = response.split('\n').skip(1);
for line in split {
let mut line_split = line.split_whitespace();
if let (Some(mac), Some(frequency), Some(signal), Some(flags)) = (
line_split.next(),
line_split.next(),
line_split.next(),
line_split.next(),
) {
let mut name: Option<String> = None;
for text in line_split {
match &mut name {
Some(started) => {
started.push(' ');
started.push_str(text);
}
None => {
name = Some(text.to_string());
}
}
}
if let Some(name) = name {
if let Ok(signal) = isize::from_str(signal) {
let scan_result = ScanResult {
mac: mac.to_string(),
frequency: frequency.to_string(),
signal,
flags: flags.to_string(),
name,
};
results.push(scan_result);
} else {
warn!("Invalid string for signal: {signal}");
}
}
for line in response.lines().skip(1) {
if let Some(scan_result) = ScanResult::from_line(line) {
results.push(scan_result);
} else {
warn!("Invalid result from scan: {line}");
}
}
Ok(results)
Expand Down

0 comments on commit 6b7de2a

Please sign in to comment.