1use anyhow::{format_err, Context as _, Error};
6use fidl::endpoints;
7use fidl_fuchsia_wlan_common::{self as fidl_common, WlanMacRole};
8use fidl_fuchsia_wlan_device_service::{
9 self as wlan_service, DeviceMonitorProxy, QueryIfaceResponse,
10};
11use fidl_fuchsia_wlan_sme::ConnectTransactionEvent;
12use futures::prelude::*;
13use ieee80211::{Bssid, MacAddr, MacAddrBytes, Ssid, NULL_ADDR};
14use itertools::Itertools;
15use std::fmt;
16use std::str::FromStr;
17use wlan_common::bss::{BssDescription, Protection};
18use wlan_common::scan::ScanResult;
19use wlan_common::security::wep::WepKey;
20use wlan_common::security::wpa::credential::{Passphrase, Psk};
21use wlan_common::security::SecurityError;
22use {
23 fidl_fuchsia_wlan_common_security as fidl_security,
24 fidl_fuchsia_wlan_ieee80211 as fidl_ieee80211, fidl_fuchsia_wlan_internal as fidl_internal,
25 fidl_fuchsia_wlan_sme as fidl_sme, zx_status, zx_types as zx_sys,
26};
27
28#[cfg(target_os = "fuchsia")]
29use wlan_rsn::psk;
30
31pub mod opts;
32use crate::opts::*;
33
34type DeviceMonitor = DeviceMonitorProxy;
35
36#[derive(Clone, Debug)]
41struct SecurityContext {
42 pub bss: BssDescription,
43 pub unparsed_password_text: Option<String>,
44 pub unparsed_psk_text: Option<String>,
45}
46
47impl TryFrom<SecurityContext> for fidl_security::Authentication {
54 type Error = SecurityError;
55
56 fn try_from(context: SecurityContext) -> Result<Self, SecurityError> {
57 fn parse_wpa_credential_pair(
63 password: Option<String>,
64 psk: Option<String>,
65 ) -> Result<fidl_security::Credentials, SecurityError> {
66 match (password, psk) {
67 (Some(password), None) => Passphrase::try_from(password)
68 .map(|passphrase| {
69 fidl_security::Credentials::Wpa(fidl_security::WpaCredentials::Passphrase(
70 passphrase.into(),
71 ))
72 })
73 .map_err(From::from),
74 (None, Some(psk)) => Psk::parse(psk.as_bytes())
75 .map(|psk| {
76 fidl_security::Credentials::Wpa(fidl_security::WpaCredentials::Psk(
77 psk.into(),
78 ))
79 })
80 .map_err(From::from),
81 _ => Err(SecurityError::Incompatible),
82 }
83 }
84
85 let SecurityContext { bss, unparsed_password_text, unparsed_psk_text } = context;
86 match bss.protection() {
87 Protection::Unknown | Protection::Wpa2Enterprise | Protection::Wpa3Enterprise => {
90 Err(SecurityError::Unsupported)
91 }
92 Protection::Open => match (unparsed_password_text, unparsed_psk_text) {
93 (None, None) => Ok(fidl_security::Authentication {
94 protocol: fidl_security::Protocol::Open,
95 credentials: None,
96 }),
97 _ => Err(SecurityError::Incompatible),
98 },
99 Protection::Wep => unparsed_password_text
100 .ok_or(SecurityError::Incompatible)
101 .and_then(|unparsed_password_text| {
102 WepKey::parse(unparsed_password_text.as_bytes()).map_err(From::from)
103 })
104 .map(|key| fidl_security::Authentication {
105 protocol: fidl_security::Protocol::Wep,
106 credentials: Some(Box::new(fidl_security::Credentials::Wep(
107 fidl_security::WepCredentials { key: key.into() },
108 ))),
109 }),
110 Protection::Wpa1 => {
111 parse_wpa_credential_pair(unparsed_password_text, unparsed_psk_text).map(
112 |credentials| fidl_security::Authentication {
113 protocol: fidl_security::Protocol::Wpa1,
114 credentials: Some(Box::new(credentials)),
115 },
116 )
117 }
118 Protection::Wpa1Wpa2PersonalTkipOnly
119 | Protection::Wpa1Wpa2Personal
120 | Protection::Wpa2PersonalTkipOnly
121 | Protection::Wpa2Personal => {
122 parse_wpa_credential_pair(unparsed_password_text, unparsed_psk_text).map(
123 |credentials| fidl_security::Authentication {
124 protocol: fidl_security::Protocol::Wpa2Personal,
125 credentials: Some(Box::new(credentials)),
126 },
127 )
128 }
129 Protection::Wpa2Wpa3Personal => {
131 parse_wpa_credential_pair(unparsed_password_text, unparsed_psk_text).map(
132 |credentials| match credentials {
133 fidl_security::Credentials::Wpa(
134 fidl_security::WpaCredentials::Passphrase(_),
135 ) => fidl_security::Authentication {
136 protocol: fidl_security::Protocol::Wpa3Personal,
137 credentials: Some(Box::new(credentials)),
138 },
139 fidl_security::Credentials::Wpa(fidl_security::WpaCredentials::Psk(_)) => {
140 fidl_security::Authentication {
141 protocol: fidl_security::Protocol::Wpa2Personal,
142 credentials: Some(Box::new(credentials)),
143 }
144 }
145 _ => unreachable!(),
146 },
147 )
148 }
149 Protection::Wpa3Personal => match (unparsed_password_text, unparsed_psk_text) {
150 (Some(unparsed_password_text), None) => {
151 Passphrase::try_from(unparsed_password_text)
152 .map(|passphrase| fidl_security::Authentication {
153 protocol: fidl_security::Protocol::Wpa3Personal,
154 credentials: Some(Box::new(fidl_security::Credentials::Wpa(
155 fidl_security::WpaCredentials::Passphrase(passphrase.into()),
156 ))),
157 })
158 .map_err(From::from)
159 }
160 _ => Err(SecurityError::Incompatible),
161 },
162 }
163 }
164}
165
166pub async fn handle_wlantool_command(monitor_proxy: DeviceMonitor, opt: Opt) -> Result<(), Error> {
167 match opt {
168 Opt::Phy(cmd) => do_phy(cmd, monitor_proxy).await,
169 Opt::Iface(cmd) => do_iface(cmd, monitor_proxy).await,
170 Opt::Client(opts::ClientCmd::Connect(cmd)) => do_client_connect(cmd, monitor_proxy).await,
171 Opt::Connect(cmd) => do_client_connect(cmd, monitor_proxy).await,
172 Opt::Client(opts::ClientCmd::Disconnect(cmd)) | Opt::Disconnect(cmd) => {
173 do_client_disconnect(cmd, monitor_proxy).await
174 }
175 Opt::Client(opts::ClientCmd::Scan(cmd)) => do_client_scan(cmd, monitor_proxy).await,
176 Opt::Scan(cmd) => do_client_scan(cmd, monitor_proxy).await,
177 Opt::Client(opts::ClientCmd::WmmStatus(cmd)) | Opt::WmmStatus(cmd) => {
178 do_client_wmm_status(cmd, monitor_proxy, &mut std::io::stdout()).await
179 }
180 Opt::Ap(cmd) => do_ap(cmd, monitor_proxy).await,
181 #[cfg(target_os = "fuchsia")]
182 Opt::Rsn(cmd) => do_rsn(cmd).await,
183 Opt::Status(cmd) => do_status(cmd, monitor_proxy).await,
184 }
185}
186
187async fn do_phy(cmd: opts::PhyCmd, monitor_proxy: DeviceMonitor) -> Result<(), Error> {
188 match cmd {
189 opts::PhyCmd::List => {
190 let response = monitor_proxy.list_phys().await.context("error getting response")?;
192 println!("response: {:?}", response);
193 }
194 opts::PhyCmd::Query { phy_id } => {
195 let mac_roles = monitor_proxy
196 .get_supported_mac_roles(phy_id)
197 .await
198 .context("error querying MAC roles")?;
199 let device_path =
200 monitor_proxy.get_dev_path(phy_id).await.context("error querying device path")?;
201 println!("PHY ID: {}", phy_id);
202 println!("Device Path: {:?}", device_path);
203 println!("Supported MAC roles: {:?}", mac_roles);
204 }
205 opts::PhyCmd::GetCountry { phy_id } => {
206 let result =
207 monitor_proxy.get_country(phy_id).await.context("error getting country")?;
208 match result {
209 Ok(country) => {
210 println!("response: \"{}\"", std::str::from_utf8(&country.alpha2[..])?);
211 }
212 Err(status) => {
213 println!(
214 "response: Failed with status {:?}",
215 zx_status::Status::from_raw(status)
216 );
217 }
218 }
219 }
220 opts::PhyCmd::SetCountry { phy_id, country } => {
221 if !is_valid_country_str(&country) {
222 return Err(format_err!(
223 "Country string [{}] looks invalid: Should be 2 ASCII characters",
224 country
225 ));
226 }
227
228 let mut alpha2 = [0u8; 2];
229 alpha2.copy_from_slice(country.as_bytes());
230 let req = wlan_service::SetCountryRequest { phy_id, alpha2 };
231 let response =
232 monitor_proxy.set_country(&req).await.context("error setting country")?;
233 println!("response: {:?}", zx_status::Status::from_raw(response));
234 }
235 opts::PhyCmd::ClearCountry { phy_id } => {
236 let req = wlan_service::ClearCountryRequest { phy_id };
237 let response =
238 monitor_proxy.clear_country(&req).await.context("error clearing country")?;
239 println!("response: {:?}", zx_status::Status::from_raw(response));
240 }
241 }
242 Ok(())
243}
244
245fn is_valid_country_str(country: &String) -> bool {
246 country.len() == 2 && country.chars().all(|x| x.is_ascii())
247}
248
249async fn do_iface(cmd: opts::IfaceCmd, monitor_proxy: DeviceMonitor) -> Result<(), Error> {
250 match cmd {
251 opts::IfaceCmd::New { phy_id, role, sta_addr } => {
252 let sta_addr = match sta_addr {
253 Some(s) => s.parse::<MacAddr>()?,
254 None => NULL_ADDR,
255 };
256
257 let req = wlan_service::DeviceMonitorCreateIfaceRequest {
258 phy_id: Some(phy_id),
259 role: Some(role.into()),
260 sta_address: Some(sta_addr.to_array()),
261 ..Default::default()
262 };
263
264 let response =
265 monitor_proxy.create_iface(&req).await.context("error getting response")?;
266 println!("response: {:?}", response);
267 }
268 opts::IfaceCmd::Delete { iface_id } => {
269 let req = wlan_service::DestroyIfaceRequest { iface_id };
270
271 let response =
272 monitor_proxy.destroy_iface(&req).await.context("error destroying iface")?;
273 match zx_status::Status::ok(response) {
274 Ok(()) => println!("destroyed iface {:?}", iface_id),
275 Err(s) => println!("error destroying iface: {:?}", s),
276 }
277 }
278 opts::IfaceCmd::List => {
279 let response = monitor_proxy.list_ifaces().await.context("error getting response")?;
280 println!("response: {:?}", response);
281 }
282 opts::IfaceCmd::Query { iface_id } => {
283 let result =
284 monitor_proxy.query_iface(iface_id).await.context("error querying iface")?;
285 match result {
286 Ok(response) => println!("response: {}", format_iface_query_response(response)),
287 Err(err) => println!("error querying Iface {}: {}", iface_id, err),
288 }
289 }
290 opts::IfaceCmd::Minstrel(cmd) => match cmd {
291 opts::MinstrelCmd::List { iface_id: _ } => {
292 println!("List minstrel peers is not supported.");
293 }
294 opts::MinstrelCmd::Show { iface_id: _, peer_addr: _ } => {
295 println!("Show minstrel peer is not supported.");
296 }
297 },
298 opts::IfaceCmd::Status(cmd) => do_status(cmd, monitor_proxy).await?,
299 }
300 Ok(())
301}
302
303fn read_scan_result_vmo(_vmo: fidl::Vmo) -> Result<Vec<fidl_sme::ScanResult>, Error> {
304 #[cfg(target_os = "fuchsia")]
305 return wlan_common::scan::read_vmo(_vmo)
306 .map_err(|e| format_err!("failed to read VMO: {:?}", e));
307 #[cfg(not(target_os = "fuchsia"))]
308 return Err(format_err!("cannot read scan result VMO on host"));
309}
310
311async fn do_client_connect(
312 cmd: opts::ClientConnectCmd,
313 monitor_proxy: DeviceMonitorProxy,
314) -> Result<(), Error> {
315 async fn try_get_bss_desc(
316 scan_result: fidl_sme::ClientSmeScanResult,
317 ssid: &Ssid,
318 bssid: Option<&Bssid>,
319 ) -> Result<fidl_common::BssDescription, Error> {
320 let mut bss_description = None;
321 match scan_result {
322 Ok(vmo) => {
323 let scan_result_list = read_scan_result_vmo(vmo)?;
324 if bss_description.is_none() {
325 if let Some(bss_info) = scan_result_list.into_iter().find(|scan_result| {
328 match ScanResult::try_from(scan_result.clone()) {
333 Ok(scan_result) => {
334 scan_result.bss_description.ssid == *ssid
336 && scan_result.bss_description.bssid
337 == *bssid.unwrap_or(&scan_result.bss_description.bssid)
338 }
339 Err(e) => {
340 println!("Failed to convert ScanResult: {:?}", e);
341 println!(" {:?}", scan_result);
342 false
343 }
344 }
345 }) {
346 bss_description = Some(bss_info.bss_description);
347 }
348 }
349 }
350 Err(scan_error_code) => {
351 return Err(format_err!("failed to fetch scan result: {:?}", scan_error_code));
352 }
353 }
354 bss_description.ok_or_else(|| format_err!("failed to find a matching BSS in scan results"))
355 }
356
357 println!(
358 "The `connect` command performs an implicit scan. This behavior is DEPRECATED and in the \
359 future detailed BSS information will be required to connect! Use the `donut` tool to \
360 connect to networks using an SSID."
361 );
362 let opts::ClientConnectCmd { iface_id, ssid, bssid, password, psk, scan_type } = cmd;
363 let ssid = Ssid::try_from(ssid)?;
364 let bssid = bssid.as_deref().map(MacAddr::from_str).transpose().unwrap().map(Bssid::from);
365 let sme = get_client_sme(monitor_proxy, iface_id).await?;
366 let req = match scan_type {
367 ScanTypeArg::Active => fidl_sme::ScanRequest::Active(fidl_sme::ActiveScanRequest {
368 ssids: vec![ssid.to_vec()],
369 channels: vec![],
370 }),
371 ScanTypeArg::Passive => fidl_sme::ScanRequest::Passive(fidl_sme::PassiveScanRequest {}),
372 };
373 let scan_result = sme.scan(&req).await.context("error sending scan request")?;
374 let bss_description = try_get_bss_desc(scan_result, &ssid, bssid.as_ref()).await?;
375 let authentication = match fidl_security::Authentication::try_from(SecurityContext {
376 unparsed_password_text: password,
377 unparsed_psk_text: psk,
378 bss: BssDescription::try_from(bss_description.clone())?,
379 }) {
380 Ok(authentication) => authentication,
381 Err(error) => {
382 println!("authentication error: {}", error);
383 return Ok(());
384 }
385 };
386 let (local, remote) = endpoints::create_proxy();
387 let req = fidl_sme::ConnectRequest {
388 ssid: ssid.to_vec(),
389 bss_description,
390 authentication,
391 deprecated_scan_type: scan_type.into(),
392 multiple_bss_candidates: false, };
394 sme.connect(&req, Some(remote)).context("error sending connect request")?;
395 handle_connect_transaction(local).await
396}
397
398async fn do_client_disconnect(
399 cmd: opts::ClientDisconnectCmd,
400 monitor_proxy: DeviceMonitor,
401) -> Result<(), Error> {
402 let opts::ClientDisconnectCmd { iface_id } = cmd;
403 let sme = get_client_sme(monitor_proxy, iface_id).await?;
404 sme.disconnect(fidl_sme::UserDisconnectReason::WlanDevTool)
405 .await
406 .map_err(|e| format_err!("error sending disconnect request: {}", e))
407}
408
409async fn do_client_scan(
410 cmd: opts::ClientScanCmd,
411 monitor_proxy: DeviceMonitor,
412) -> Result<(), Error> {
413 let opts::ClientScanCmd { iface_id, scan_type } = cmd;
414 let sme = get_client_sme(monitor_proxy, iface_id).await?;
415 let req = match scan_type {
416 ScanTypeArg::Passive => fidl_sme::ScanRequest::Passive(fidl_sme::PassiveScanRequest {}),
417 ScanTypeArg::Active => fidl_sme::ScanRequest::Active(fidl_sme::ActiveScanRequest {
418 ssids: vec![],
419 channels: vec![],
420 }),
421 };
422 let scan_result = sme.scan(&req).await.context("error sending scan request")?;
423 print_scan_result(scan_result);
424 Ok(())
425}
426
427async fn print_iface_status(iface_id: u16, monitor_proxy: DeviceMonitor) -> Result<(), Error> {
428 let result = monitor_proxy
429 .query_iface(iface_id)
430 .await
431 .context("querying iface info")?
432 .map_err(|e| zx_status::Status::from_raw(e))?;
433
434 match result.role {
435 WlanMacRole::Client => {
436 let client_sme = get_client_sme(monitor_proxy, iface_id).await?;
437 let client_status_response = client_sme.status().await?;
438 match client_status_response {
439 fidl_sme::ClientStatusResponse::Connected(serving_ap_info) => {
440 println!(
441 "Iface {}: Connected to '{}' (bssid {}) channel: {:?} rssi: {}dBm snr: {}dB",
442 iface_id,
443 String::from_utf8_lossy(&serving_ap_info.ssid),
444 Bssid::from(serving_ap_info.bssid),
445 serving_ap_info.channel,
446 serving_ap_info.rssi_dbm,
447 serving_ap_info.snr_db,
448 );
449 }
450 fidl_sme::ClientStatusResponse::Connecting(ssid) => {
451 println!("Connecting to '{}'", String::from_utf8_lossy(&ssid));
452 }
453 fidl_sme::ClientStatusResponse::Roaming(bssid) => {
454 println!("Roaming to '{}'", String::from_utf8_lossy(&bssid));
455 }
456 fidl_sme::ClientStatusResponse::Idle(_) => {
457 println!("Iface {}: Not connected to a network", iface_id)
458 }
459 }
460 }
461 WlanMacRole::Ap => {
462 let sme = get_ap_sme(monitor_proxy, iface_id).await?;
463 let status = sme.status().await?;
464 println!(
465 "Iface {}: Running AP: {:?}",
466 iface_id,
467 status.running_ap.map(|ap| {
468 format!(
469 "ssid: {}, channel: {}, clients: {}",
470 String::from_utf8_lossy(&ap.ssid),
471 ap.channel,
472 ap.num_clients
473 )
474 })
475 );
476 }
477 WlanMacRole::Mesh => println!("Iface {}: Mesh not supported", iface_id),
478 fidl_fuchsia_wlan_common::WlanMacRoleUnknown!() => {
479 println!("Iface {}: Unknown WlanMacRole type {:?}", iface_id, result.role);
480 }
481 }
482 Ok(())
483}
484
485async fn do_status(cmd: opts::IfaceStatusCmd, monitor_proxy: DeviceMonitor) -> Result<(), Error> {
486 let ids = get_iface_ids(monitor_proxy.clone(), cmd.iface_id).await?;
487
488 if ids.len() == 0 {
489 return Err(format_err!("No iface found"));
490 }
491 for iface_id in ids {
492 if let Err(e) = print_iface_status(iface_id, monitor_proxy.clone()).await {
493 println!("Iface {}: Error querying status: {}", iface_id, e);
494 continue;
495 }
496 }
497 Ok(())
498}
499
500async fn do_client_wmm_status(
501 cmd: opts::ClientWmmStatusCmd,
502 monitor_proxy: DeviceMonitor,
503 stdout: &mut dyn std::io::Write,
504) -> Result<(), Error> {
505 let sme = get_client_sme(monitor_proxy, cmd.iface_id).await?;
506 let wmm_status = sme
507 .wmm_status()
508 .await
509 .map_err(|e| format_err!("error sending WmmStatus request: {}", e))?;
510 match wmm_status {
511 Ok(wmm_status) => print_wmm_status(&wmm_status, stdout)?,
512 Err(code) => writeln!(stdout, "ClientSme::WmmStatus fails with status code: {}", code)?,
513 }
514 Ok(())
515}
516
517fn print_wmm_status(
518 wmm_status: &fidl_internal::WmmStatusResponse,
519 stdout: &mut dyn std::io::Write,
520) -> Result<(), Error> {
521 writeln!(stdout, "apsd={}", wmm_status.apsd)?;
522 print_wmm_ac_params("ac_be", &wmm_status.ac_be_params, stdout)?;
523 print_wmm_ac_params("ac_bk", &wmm_status.ac_bk_params, stdout)?;
524 print_wmm_ac_params("ac_vi", &wmm_status.ac_vi_params, stdout)?;
525 print_wmm_ac_params("ac_vo", &wmm_status.ac_vo_params, stdout)?;
526 Ok(())
527}
528
529fn print_wmm_ac_params(
530 ac_name: &str,
531 ac_params: &fidl_internal::WmmAcParams,
532 stdout: &mut dyn std::io::Write,
533) -> Result<(), Error> {
534 writeln!(stdout, "{ac_name}: aifsn={aifsn} acm={acm} ecw_min={ecw_min} ecw_max={ecw_max} txop_limit={txop_limit}",
535 ac_name=ac_name,
536 aifsn=ac_params.aifsn,
537 acm=ac_params.acm,
538 ecw_min=ac_params.ecw_min,
539 ecw_max=ac_params.ecw_max,
540 txop_limit=ac_params.txop_limit,
541 )?;
542 Ok(())
543}
544
545async fn do_ap(cmd: opts::ApCmd, monitor_proxy: DeviceMonitor) -> Result<(), Error> {
546 match cmd {
547 opts::ApCmd::Start { iface_id, ssid, password, channel } => {
548 let sme = get_ap_sme(monitor_proxy, iface_id).await?;
549 let config = fidl_sme::ApConfig {
550 ssid: ssid.as_bytes().to_vec(),
551 password: password.map_or(vec![], |p| p.as_bytes().to_vec()),
552 radio_cfg: fidl_sme::RadioConfig {
553 phy: PhyArg::Ht.into(),
554 channel: fidl_common::WlanChannel {
555 primary: channel,
556 cbw: CbwArg::Cbw20.into(),
557 secondary80: 0,
558 },
559 },
560 };
561 println!("{:?}", sme.start(&config).await?);
562 }
563 opts::ApCmd::Stop { iface_id } => {
564 let sme = get_ap_sme(monitor_proxy, iface_id).await?;
565 let r = sme.stop().await;
566 println!("{:?}", r);
567 }
568 }
569 Ok(())
570}
571
572#[cfg(target_os = "fuchsia")]
573async fn do_rsn(cmd: opts::RsnCmd) -> Result<(), Error> {
574 match cmd {
575 opts::RsnCmd::GeneratePsk { passphrase, ssid } => {
576 println!("{}", generate_psk(&passphrase, &ssid)?);
577 }
578 }
579 Ok(())
580}
581
582#[cfg(target_os = "fuchsia")]
583fn generate_psk(passphrase: &str, ssid: &str) -> Result<String, Error> {
584 let psk = psk::compute(passphrase.as_bytes(), &Ssid::try_from(ssid)?)?;
585 Ok(hex::encode(&psk))
586}
587
588fn print_scan_result(scan_result: fidl_sme::ClientSmeScanResult) {
589 match scan_result {
590 Ok(vmo) => {
591 let scan_result_list = match read_scan_result_vmo(vmo) {
592 Ok(list) => list,
593 Err(e) => {
594 eprintln!("Failed to read VMO: {:?}", e);
595 return;
596 }
597 };
598 print_scan_header();
599 scan_result_list
600 .into_iter()
601 .filter_map(
602 |scan_result| match ScanResult::try_from(scan_result.clone()) {
607 Ok(scan_result) => Some(scan_result),
608 Err(e) => {
609 eprintln!("Failed to convert ScanResult: {:?}", e);
610 eprintln!(" {:?}", scan_result);
611 None
612 }
613 },
614 )
615 .sorted_by(|a, b| a.bss_description.ssid.cmp(&b.bss_description.ssid))
616 .by_ref()
617 .for_each(|scan_result| print_one_scan_result(&scan_result));
618 }
619 Err(scan_error_code) => {
620 eprintln!("Error: {:?}", scan_error_code);
621 }
622 }
623}
624
625fn print_scan_line(
626 bssid: impl fmt::Display,
627 dbm: impl fmt::Display,
628 channel: impl fmt::Display,
629 protection: impl fmt::Display,
630 compat: impl fmt::Display,
631 ssid: impl fmt::Display,
632) {
633 println!("{:17} {:>4} {:>6} {:12} {:10} {}", bssid, dbm, channel, protection, compat, ssid)
634}
635
636fn print_scan_header() {
637 print_scan_line("BSSID", "dBm", "Chan", "Protection", "Compatible", "SSID");
638}
639
640fn print_one_scan_result(scan_result: &wlan_common::scan::ScanResult) {
641 print_scan_line(
642 scan_result.bss_description.bssid,
643 scan_result.bss_description.rssi_dbm,
644 wlan_common::channel::Channel::from(scan_result.bss_description.channel),
645 scan_result.bss_description.protection(),
646 if scan_result.is_compatible() { "Y" } else { "N" },
647 scan_result.bss_description.ssid.to_string_not_redactable(),
648 );
649}
650
651async fn handle_connect_transaction(
652 connect_txn: fidl_sme::ConnectTransactionProxy,
653) -> Result<(), Error> {
654 let mut events = connect_txn.take_event_stream();
655 while let Some(evt) = events
656 .try_next()
657 .await
658 .context("failed to receive connect result before the channel was closed")?
659 {
660 match evt {
661 ConnectTransactionEvent::OnConnectResult { result } => {
662 match (result.code, result.is_credential_rejected) {
663 (fidl_ieee80211::StatusCode::Success, _) => println!("Connected successfully"),
664 (fidl_ieee80211::StatusCode::Canceled, _) => {
665 eprintln!("Connecting was canceled or superseded by another command")
666 }
667 (code, true) => eprintln!("Credential rejected, status code: {:?}", code),
668 (code, false) => eprintln!("Failed to connect to network: {:?}", code),
669 }
670 break;
671 }
672 evt => {
673 eprintln!("Expected ConnectTransactionEvent::OnConnectResult event, got {:?}", evt);
674 }
675 }
676 }
677 Ok(())
678}
679
680fn error_from_sme_raw_status(
692 raw_status: zx_sys::zx_status_t,
693 station_mode: WlanMacRole,
694 iface_id: u16,
695) -> Error {
696 match zx_status::Status::from_raw(raw_status) {
697 zx_status::Status::OK => Error::msg("Unexpected OK error"),
698 zx_status::Status::NOT_FOUND => Error::msg("invalid interface id"),
699 zx_status::Status::NOT_SUPPORTED => Error::msg("operation not supported on SME interface"),
700 zx_status::Status::INTERNAL => {
701 Error::msg("internal server error sending endpoint to the SME server future")
702 }
703 _ => Error::msg("unrecognized error associated with SME interface"),
704 }
705 .context(format!(
706 "Failed to access {:?} for interface id {}. \
707 Please ensure the selected iface supports {:?} mode.",
708 station_mode, iface_id, station_mode,
709 ))
710}
711
712async fn get_client_sme(
713 monitor_proxy: DeviceMonitor,
714 iface_id: u16,
715) -> Result<fidl_sme::ClientSmeProxy, Error> {
716 let (proxy, remote) = endpoints::create_proxy();
717 monitor_proxy
718 .get_client_sme(iface_id, remote)
719 .await
720 .context("error sending GetClientSme request")?
721 .map_err(|e| error_from_sme_raw_status(e, WlanMacRole::Client, iface_id))?;
722 Ok(proxy)
723}
724
725async fn get_ap_sme(
726 monitor_proxy: DeviceMonitor,
727 iface_id: u16,
728) -> Result<fidl_sme::ApSmeProxy, Error> {
729 let (proxy, remote) = endpoints::create_proxy();
730 monitor_proxy
731 .get_ap_sme(iface_id, remote)
732 .await
733 .context("error sending GetApSme request")?
734 .map_err(|e| error_from_sme_raw_status(e, WlanMacRole::Ap, iface_id))?;
735 Ok(proxy)
736}
737
738async fn get_iface_ids(
739 monitor_proxy: DeviceMonitor,
740 iface_id: Option<u16>,
741) -> Result<Vec<u16>, Error> {
742 match iface_id {
743 Some(id) => Ok(vec![id]),
744 None => monitor_proxy.list_ifaces().await.context("error listing ifaces"),
745 }
746}
747
748fn format_iface_query_response(resp: QueryIfaceResponse) -> String {
749 format!(
750 "QueryIfaceResponse {{ role: {:?}, id: {}, phy_id: {}, phy_assigned_id: {}, sta_addr: {} }}",
751 resp.role,
752 resp.id,
753 resp.phy_id,
754 resp.phy_assigned_id,
755 MacAddr::from(resp.sta_addr)
756 )
757}
758
759#[cfg(test)]
760mod tests {
761 use super::*;
762 use fidl::endpoints::create_proxy;
763 use fidl_fuchsia_wlan_device_service::DeviceMonitorMarker;
764 use fuchsia_async as fasync;
765 use futures::task::Poll;
766 use ieee80211::SsidError;
767 use std::pin::pin;
768 use wlan_common::{assert_variant, fake_bss_description};
769
770 #[fuchsia::test]
771 fn negotiate_authentication() {
772 let bss = fake_bss_description!(Open);
773 assert_eq!(
774 fidl_security::Authentication::try_from(SecurityContext {
775 unparsed_password_text: None,
776 unparsed_psk_text: None,
777 bss
778 }),
779 Ok(fidl_security::Authentication {
780 protocol: fidl_security::Protocol::Open,
781 credentials: None
782 }),
783 );
784
785 let bss = fake_bss_description!(Wpa1);
786 assert_eq!(
787 fidl_security::Authentication::try_from(SecurityContext {
788 unparsed_password_text: Some(String::from("password")),
789 unparsed_psk_text: None,
790 bss,
791 }),
792 Ok(fidl_security::Authentication {
793 protocol: fidl_security::Protocol::Wpa1,
794 credentials: Some(Box::new(fidl_security::Credentials::Wpa(
795 fidl_security::WpaCredentials::Passphrase(b"password".to_vec())
796 ))),
797 }),
798 );
799
800 let bss = fake_bss_description!(Wpa2);
801 let psk = String::from("f42c6fc52df0ebef9ebb4b90b38a5f902e83fe1b135a70e23aed762e9710a12e");
802 assert_eq!(
803 fidl_security::Authentication::try_from(SecurityContext {
804 unparsed_password_text: None,
805 unparsed_psk_text: Some(psk),
806 bss
807 }),
808 Ok(fidl_security::Authentication {
809 protocol: fidl_security::Protocol::Wpa2Personal,
810 credentials: Some(Box::new(fidl_security::Credentials::Wpa(
811 fidl_security::WpaCredentials::Psk([
812 0xf4, 0x2c, 0x6f, 0xc5, 0x2d, 0xf0, 0xeb, 0xef, 0x9e, 0xbb, 0x4b, 0x90,
813 0xb3, 0x8a, 0x5f, 0x90, 0x2e, 0x83, 0xfe, 0x1b, 0x13, 0x5a, 0x70, 0xe2,
814 0x3a, 0xed, 0x76, 0x2e, 0x97, 0x10, 0xa1, 0x2e,
815 ])
816 ))),
817 }),
818 );
819
820 let bss = fake_bss_description!(Wpa2);
821 let psk = String::from("f42c6fc52df0ebef9ebb4b90b38a5f902e83fe1b135a70e23aed762e9710a12e");
822 assert!(matches!(
823 fidl_security::Authentication::try_from(SecurityContext {
824 unparsed_password_text: Some(String::from("password")),
825 unparsed_psk_text: Some(psk),
826 bss,
827 }),
828 Err(_),
829 ));
830 }
831
832 #[fuchsia::test]
833 fn destroy_iface() {
834 let mut exec = fasync::TestExecutor::new();
835 let (monitor_svc_local, monitor_svc_remote) = create_proxy::<DeviceMonitorMarker>();
836 let mut monitor_svc_stream = monitor_svc_remote.into_stream();
837 let del_fut = do_iface(IfaceCmd::Delete { iface_id: 5 }, monitor_svc_local);
838 let mut del_fut = pin!(del_fut);
839
840 assert_variant!(exec.run_until_stalled(&mut del_fut), Poll::Pending);
841 assert_variant!(
842 exec.run_until_stalled(&mut monitor_svc_stream.next()),
843 Poll::Ready(Some(Ok(wlan_service::DeviceMonitorRequest::DestroyIface {
844 req, responder
845 }))) => {
846 assert_eq!(req.iface_id, 5);
847 responder.send(zx_status::Status::OK.into_raw()).expect("failed to send response");
848 }
849 );
850 }
851
852 #[fuchsia::test]
853 fn test_country_input() {
854 assert!(is_valid_country_str(&"RS".to_string()));
855 assert!(is_valid_country_str(&"00".to_string()));
856 assert!(is_valid_country_str(&"M1".to_string()));
857 assert!(is_valid_country_str(&"-M".to_string()));
858
859 assert!(!is_valid_country_str(&"ABC".to_string()));
860 assert!(!is_valid_country_str(&"X".to_string()));
861 assert!(!is_valid_country_str(&"❤".to_string()));
862 }
863
864 #[fuchsia::test]
865 fn test_get_country() {
866 let mut exec = fasync::TestExecutor::new();
867 let (monitor_svc_local, monitor_svc_remote) = create_proxy::<DeviceMonitorMarker>();
868 let mut monitor_svc_stream = monitor_svc_remote.into_stream();
869 let fut = do_phy(PhyCmd::GetCountry { phy_id: 45 }, monitor_svc_local);
870 let mut fut = pin!(fut);
871
872 assert_variant!(exec.run_until_stalled(&mut fut), Poll::Pending);
873 assert_variant!(
874 exec.run_until_stalled(&mut monitor_svc_stream.next()),
875 Poll::Ready(Some(Ok(wlan_service::DeviceMonitorRequest::GetCountry {
876 phy_id, responder,
877 }))) => {
878 assert_eq!(phy_id, 45);
879 responder.send(
880 Ok(&fidl_fuchsia_wlan_device_service::GetCountryResponse {
881 alpha2: [40u8, 40u8],
882 })).expect("failed to send response");
883 }
884 );
885
886 assert_variant!(exec.run_until_stalled(&mut fut), Poll::Ready(Ok(())));
887 }
888
889 #[fuchsia::test]
890 fn test_set_country() {
891 let mut exec = fasync::TestExecutor::new();
892 let (monitor_svc_local, monitor_svc_remote) = create_proxy::<DeviceMonitorMarker>();
893 let mut monitor_svc_stream = monitor_svc_remote.into_stream();
894 let fut =
895 do_phy(PhyCmd::SetCountry { phy_id: 45, country: "RS".to_string() }, monitor_svc_local);
896 let mut fut = pin!(fut);
897
898 assert_variant!(exec.run_until_stalled(&mut fut), Poll::Pending);
899 assert_variant!(
900 exec.run_until_stalled(&mut monitor_svc_stream.next()),
901 Poll::Ready(Some(Ok(wlan_service::DeviceMonitorRequest::SetCountry {
902 req, responder,
903 }))) => {
904 assert_eq!(req.phy_id, 45);
905 assert_eq!(req.alpha2, "RS".as_bytes());
906 responder.send(zx_status::Status::OK.into_raw()).expect("failed to send response");
907 }
908 );
909 }
910
911 #[fuchsia::test]
912 fn test_clear_country() {
913 let mut exec = fasync::TestExecutor::new();
914 let (monitor_svc_local, monitor_svc_remote) = create_proxy::<DeviceMonitorMarker>();
915 let mut monitor_svc_stream = monitor_svc_remote.into_stream();
916 let fut = do_phy(PhyCmd::ClearCountry { phy_id: 45 }, monitor_svc_local);
917 let mut fut = pin!(fut);
918
919 assert_variant!(exec.run_until_stalled(&mut fut), Poll::Pending);
920 assert_variant!(
921 exec.run_until_stalled(&mut monitor_svc_stream.next()),
922 Poll::Ready(Some(Ok(wlan_service::DeviceMonitorRequest::ClearCountry {
923 req, responder,
924 }))) => {
925 assert_eq!(req.phy_id, 45);
926 responder.send(zx_status::Status::OK.into_raw()).expect("failed to send response");
927 }
928 );
929 }
930
931 #[fuchsia::test]
932 fn test_generate_psk() {
933 assert_eq!(
934 generate_psk("12345678", "coolnet").unwrap(),
935 "1ec9ee30fdff1961a9abd083f571464cc0fe27f62f9f59992bd39f8e625e9f52"
936 );
937 assert!(generate_psk("short", "coolnet").is_err());
938 }
939
940 fn has_expected_cause(error: Error, message: &str) -> bool {
941 error.chain().any(|cause| cause.to_string() == message)
942 }
943
944 #[fuchsia::test]
945 fn test_error_from_sme_raw_status() {
946 let not_found = error_from_sme_raw_status(
947 zx_status::Status::NOT_FOUND.into_raw(),
948 WlanMacRole::Mesh,
949 1,
950 );
951 let not_supported = error_from_sme_raw_status(
952 zx_status::Status::NOT_SUPPORTED.into_raw(),
953 WlanMacRole::Ap,
954 2,
955 );
956 let internal_error = error_from_sme_raw_status(
957 zx_status::Status::INTERNAL.into_raw(),
958 WlanMacRole::Client,
959 3,
960 );
961 let unrecognized_error = error_from_sme_raw_status(
962 zx_status::Status::INTERRUPTED_RETRY.into_raw(),
963 WlanMacRole::Mesh,
964 4,
965 );
966
967 assert!(has_expected_cause(not_found, "invalid interface id"));
968 assert!(has_expected_cause(not_supported, "operation not supported on SME interface"));
969 assert!(has_expected_cause(
970 internal_error,
971 "internal server error sending endpoint to the SME server future"
972 ));
973 assert!(has_expected_cause(
974 unrecognized_error,
975 "unrecognized error associated with SME interface"
976 ));
977 }
978
979 #[fuchsia::test]
980 fn reject_connect_ssid_too_long() {
981 let mut exec = fasync::TestExecutor::new();
982 let (monitor_local, monitor_remote) = create_proxy::<DeviceMonitorMarker>();
983 let mut monitor_stream = monitor_remote.into_stream();
984 let cmd = opts::ClientConnectCmd {
986 iface_id: 0,
987 ssid: String::from_utf8(vec![65; 33]).unwrap(),
988 bssid: None,
989 password: None,
990 psk: None,
991 scan_type: opts::ScanTypeArg::Passive,
992 };
993
994 let connect_fut = do_client_connect(cmd, monitor_local.clone());
995 let mut connect_fut = pin!(connect_fut);
996
997 assert_variant!(exec.run_until_stalled(&mut connect_fut), Poll::Ready(Err(e)) => {
998 assert_eq!(format!("{}", e), format!("{}", SsidError::Size(33)));
999 });
1000 assert_variant!(exec.run_until_stalled(&mut monitor_stream.next()), Poll::Pending);
1002 }
1003
1004 #[fuchsia::test]
1005 fn test_wmm_status() {
1006 let mut exec = fasync::TestExecutor::new();
1007 let (monitor_local, monitor_remote) = create_proxy::<DeviceMonitorMarker>();
1008 let mut monitor_stream = monitor_remote.into_stream();
1009 let mut stdout = Vec::new();
1010 {
1011 let fut = do_client_wmm_status(
1012 ClientWmmStatusCmd { iface_id: 11 },
1013 monitor_local,
1014 &mut stdout,
1015 );
1016 let mut fut = pin!(fut);
1017
1018 assert_variant!(exec.run_until_stalled(&mut fut), Poll::Pending);
1019 let mut fake_sme_server_stream = assert_variant!(
1020 exec.run_until_stalled(&mut monitor_stream.next()),
1021 Poll::Ready(Some(Ok(wlan_service::DeviceMonitorRequest::GetClientSme {
1022 iface_id, sme_server, responder,
1023 }))) => {
1024 assert_eq!(iface_id, 11);
1025 responder.send(Ok(())).expect("failed to send GetClientSme response");
1026 sme_server.into_stream()
1027 }
1028 );
1029
1030 assert_variant!(exec.run_until_stalled(&mut fut), Poll::Pending);
1031 assert_variant!(
1032 exec.run_until_stalled(&mut fake_sme_server_stream.next()),
1033 Poll::Ready(Some(Ok(fidl_sme::ClientSmeRequest::WmmStatus { responder }))) => {
1034 let wmm_status_resp = fidl_internal::WmmStatusResponse {
1035 apsd: true,
1036 ac_be_params: fidl_internal::WmmAcParams {
1037 aifsn: 1,
1038 acm: false,
1039 ecw_min: 2,
1040 ecw_max: 3,
1041 txop_limit: 4,
1042 },
1043 ac_bk_params: fidl_internal::WmmAcParams {
1044 aifsn: 5,
1045 acm: false,
1046 ecw_min: 6,
1047 ecw_max: 7,
1048 txop_limit: 8,
1049 },
1050 ac_vi_params: fidl_internal::WmmAcParams {
1051 aifsn: 9,
1052 acm: true,
1053 ecw_min: 10,
1054 ecw_max: 11,
1055 txop_limit: 12,
1056 },
1057 ac_vo_params: fidl_internal::WmmAcParams {
1058 aifsn: 13,
1059 acm: true,
1060 ecw_min: 14,
1061 ecw_max: 15,
1062 txop_limit: 16,
1063 },
1064 };
1065 responder.send(Ok(&wmm_status_resp)).expect("failed to send WMM status response");
1066 }
1067 );
1068
1069 assert_variant!(exec.run_until_stalled(&mut fut), Poll::Ready(Ok(())));
1070 }
1071 assert_eq!(
1072 String::from_utf8(stdout).expect("expect valid UTF8"),
1073 "apsd=true\n\
1074 ac_be: aifsn=1 acm=false ecw_min=2 ecw_max=3 txop_limit=4\n\
1075 ac_bk: aifsn=5 acm=false ecw_min=6 ecw_max=7 txop_limit=8\n\
1076 ac_vi: aifsn=9 acm=true ecw_min=10 ecw_max=11 txop_limit=12\n\
1077 ac_vo: aifsn=13 acm=true ecw_min=14 ecw_max=15 txop_limit=16\n"
1078 );
1079 }
1080}