1use anyhow::{Context as _, Error, format_err};
6use fidl::endpoints;
7use fidl_fuchsia_wlan_common::WlanMacRole;
8use fidl_fuchsia_wlan_device_service::{
9 self as wlan_service, DeviceMonitorProxy, QueryIfaceResponse,
10};
11use fidl_fuchsia_wlan_ieee80211 as fidl_ieee80211;
12use fidl_fuchsia_wlan_internal as fidl_internal;
13use fidl_fuchsia_wlan_sme as fidl_sme;
14use fidl_fuchsia_wlan_sme::ConnectTransactionEvent;
15use futures::prelude::*;
16use ieee80211::{Bssid, MacAddr, MacAddrBytes, NULL_ADDR, Ssid};
17use itertools::Itertools;
18use std::fmt;
19use std::str::FromStr;
20use wlan_common::bss::{BssDescription, Protection};
21use wlan_common::scan::ScanResult;
22use wlan_common::security::SecurityError;
23use wlan_common::security::wep::WepKey;
24use wlan_common::security::wpa::credential::{Passphrase, Psk};
25use zx_status;
26use zx_types as zx_sys;
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_internal::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_internal::Credentials, SecurityError> {
66 match (password, psk) {
67 (Some(password), None) => Passphrase::try_from(password)
68 .map(|passphrase| {
69 fidl_internal::Credentials::Wpa(fidl_internal::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_internal::Credentials::Wpa(fidl_internal::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 | Protection::OpenOweTransition | Protection::Owe => {
93 match (unparsed_password_text, unparsed_psk_text) {
94 (None, None) => {
95 let protocol = match bss.protection() {
96 Protection::Owe => fidl_internal::Protocol::Owe,
97 _ => fidl_internal::Protocol::Open,
98 };
99 Ok(fidl_internal::Authentication { protocol, credentials: None })
100 }
101 _ => Err(SecurityError::Incompatible),
102 }
103 }
104 Protection::Wep => unparsed_password_text
105 .ok_or(SecurityError::Incompatible)
106 .and_then(|unparsed_password_text| {
107 WepKey::parse(unparsed_password_text.as_bytes()).map_err(From::from)
108 })
109 .map(|key| fidl_internal::Authentication {
110 protocol: fidl_internal::Protocol::Wep,
111 credentials: Some(Box::new(fidl_internal::Credentials::Wep(
112 fidl_internal::WepCredentials { key: key.into() },
113 ))),
114 }),
115 Protection::Wpa1 => {
116 parse_wpa_credential_pair(unparsed_password_text, unparsed_psk_text).map(
117 |credentials| fidl_internal::Authentication {
118 protocol: fidl_internal::Protocol::Wpa1,
119 credentials: Some(Box::new(credentials)),
120 },
121 )
122 }
123 Protection::Wpa1Wpa2PersonalTkipOnly
124 | Protection::Wpa1Wpa2Personal
125 | Protection::Wpa2PersonalTkipOnly
126 | Protection::Wpa2Personal => {
127 parse_wpa_credential_pair(unparsed_password_text, unparsed_psk_text).map(
128 |credentials| fidl_internal::Authentication {
129 protocol: fidl_internal::Protocol::Wpa2Personal,
130 credentials: Some(Box::new(credentials)),
131 },
132 )
133 }
134 Protection::Wpa2Wpa3Personal => {
136 parse_wpa_credential_pair(unparsed_password_text, unparsed_psk_text).map(
137 |credentials| match credentials {
138 fidl_internal::Credentials::Wpa(
139 fidl_internal::WpaCredentials::Passphrase(_),
140 ) => fidl_internal::Authentication {
141 protocol: fidl_internal::Protocol::Wpa3Personal,
142 credentials: Some(Box::new(credentials)),
143 },
144 fidl_internal::Credentials::Wpa(fidl_internal::WpaCredentials::Psk(_)) => {
145 fidl_internal::Authentication {
146 protocol: fidl_internal::Protocol::Wpa2Personal,
147 credentials: Some(Box::new(credentials)),
148 }
149 }
150 _ => unreachable!(),
151 },
152 )
153 }
154 Protection::Wpa3Personal => match (unparsed_password_text, unparsed_psk_text) {
155 (Some(unparsed_password_text), None) => {
156 Passphrase::try_from(unparsed_password_text)
157 .map(|passphrase| fidl_internal::Authentication {
158 protocol: fidl_internal::Protocol::Wpa3Personal,
159 credentials: Some(Box::new(fidl_internal::Credentials::Wpa(
160 fidl_internal::WpaCredentials::Passphrase(passphrase.into()),
161 ))),
162 })
163 .map_err(From::from)
164 }
165 _ => Err(SecurityError::Incompatible),
166 },
167 }
168 }
169}
170
171pub async fn handle_wlantool_command(monitor_proxy: DeviceMonitor, opt: Opt) -> Result<(), Error> {
172 match opt {
173 Opt::Phy(cmd) => do_phy(cmd, monitor_proxy).await,
174 Opt::Iface(cmd) => do_iface(cmd, monitor_proxy).await,
175 Opt::Client(opts::ClientCmd::Connect(cmd)) => do_client_connect(cmd, monitor_proxy).await,
176 Opt::Connect(cmd) => do_client_connect(cmd, monitor_proxy).await,
177 Opt::Client(opts::ClientCmd::Disconnect(cmd)) | Opt::Disconnect(cmd) => {
178 do_client_disconnect(cmd, monitor_proxy).await
179 }
180 Opt::Client(opts::ClientCmd::Scan(cmd)) => do_client_scan(cmd, monitor_proxy).await,
181 Opt::Scan(cmd) => do_client_scan(cmd, monitor_proxy).await,
182 Opt::Client(opts::ClientCmd::WmmStatus(cmd)) | Opt::WmmStatus(cmd) => {
183 do_client_wmm_status(cmd, monitor_proxy, &mut std::io::stdout()).await
184 }
185 Opt::Ap(cmd) => do_ap(cmd, monitor_proxy).await,
186 #[cfg(target_os = "fuchsia")]
187 Opt::Rsn(cmd) => do_rsn(cmd).await,
188 Opt::Status(cmd) => do_status(cmd, monitor_proxy).await,
189 }
190}
191
192async fn do_phy(cmd: opts::PhyCmd, monitor_proxy: DeviceMonitor) -> Result<(), Error> {
193 match cmd {
194 opts::PhyCmd::List => {
195 let response = monitor_proxy.list_phys().await.context("error getting response")?;
197 println!("response: {:?}", response);
198 }
199 opts::PhyCmd::Query { phy_id } => {
200 let mac_roles = monitor_proxy
201 .get_supported_mac_roles(phy_id)
202 .await
203 .context("error querying MAC roles")?;
204 println!("PHY ID: {}", phy_id);
205 println!("Supported MAC roles: {:?}", mac_roles);
206 }
207 opts::PhyCmd::GetCountry { phy_id } => {
208 let result =
209 monitor_proxy.get_country(phy_id).await.context("error getting country")?;
210 match result {
211 Ok(country) => {
212 println!("response: \"{}\"", std::str::from_utf8(&country.alpha2[..])?);
213 }
214 Err(status) => {
215 println!(
216 "response: Failed with status {:?}",
217 zx_status::Status::err_from_raw(status)
218 );
219 }
220 }
221 }
222 opts::PhyCmd::SetCountry { phy_id, country } => {
223 if !is_valid_country_str(&country) {
224 return Err(format_err!(
225 "Country string [{}] looks invalid: Should be 2 ASCII characters",
226 country
227 ));
228 }
229
230 let mut alpha2 = [0u8; 2];
231 alpha2.copy_from_slice(country.as_bytes());
232 let req = wlan_service::SetCountryRequest { phy_id, alpha2 };
233 let response =
234 monitor_proxy.set_country(&req).await.context("error setting country")?;
235 println!("response: {:?}", zx_status::Status::ok(response));
236 }
237 opts::PhyCmd::ClearCountry { phy_id } => {
238 let req = wlan_service::ClearCountryRequest { phy_id };
239 let response =
240 monitor_proxy.clear_country(&req).await.context("error clearing country")?;
241 println!("response: {:?}", zx_status::Status::ok(response));
242 }
243 opts::PhyCmd::Reset { phy_id } => {
244 let response = monitor_proxy.reset(phy_id).await.context("error resetting")?;
245 match response {
246 Ok(_) => {
247 println!("response: OK");
248 }
249 Err(status) => {
250 println!("response: Failed {:?}", zx_status::Status::err_from_raw(status));
251 }
252 }
253 }
254 opts::PhyCmd::GetPowerState { phy_id } => {
255 let result =
256 monitor_proxy.get_power_state(phy_id).await.context("error getting power state")?;
257 match result {
258 Ok(power_state) => match power_state {
259 true => println!("Powered ON"),
260 false => println!("Powered OFF"),
261 },
262 Err(status) => {
263 println!("response: Failed {:?}", zx_status::Status::err_from_raw(status));
264 }
265 }
266 }
267 opts::PhyCmd::SetPowerState { phy_id, state } => {
268 let response = match state {
269 OnOffArg::On => {
270 monitor_proxy.power_up(phy_id).await.context("error powering up")?
271 }
272 OnOffArg::Off => {
273 monitor_proxy.power_down(phy_id).await.context("error powering down")?
274 }
275 };
276 match response {
277 Ok(_) => {
278 println!("response: OK");
279 }
280 Err(status) => {
281 println!("response: Failed {:?}", zx_status::Status::err_from_raw(status));
282 }
283 }
284 }
285 opts::PhyCmd::GetPowerSaveMode { phy_id } => {
286 let result = monitor_proxy
287 .get_power_save_mode(phy_id)
288 .await
289 .context("error getting power save mode")?;
290 match result {
291 Ok(wlan_service::GetPowerSaveModeResponse { ps_mode }) => {
292 println!("response: {:?}", ps_mode);
293 }
294 Err(status) => {
295 println!("response: Failed {:?}", zx_status::Status::err_from_raw(status));
296 }
297 }
298 }
299 opts::PhyCmd::SetPowerSaveMode { phy_id, mode } => {
300 let response = monitor_proxy
301 .set_power_save_mode(&fidl_fuchsia_wlan_device_service::SetPowerSaveModeRequest {
302 phy_id,
303 ps_mode: mode.into(),
304 })
305 .await
306 .context("error setting power save mode")?;
307 println!("response: {:?}", zx_status::Status::ok(response));
308 }
309 opts::PhyCmd::SetTxPowerScenario { phy_id, mode } => {
310 let response = monitor_proxy
311 .set_tx_power_scenario(phy_id, mode.into())
312 .await
313 .context("error setting tx power scenario")?;
314 match response {
315 Ok(_) => {
316 println!("response: OK");
317 }
318 Err(status) => {
319 println!("response: Failed {:?}", zx_status::Status::err_from_raw(status));
320 }
321 }
322 }
323 opts::PhyCmd::GetTxPowerScenario { phy_id } => {
324 let result = monitor_proxy
325 .get_tx_power_scenario(phy_id)
326 .await
327 .context("error getting tx power scenario")?;
328 match result {
329 Ok(scenario) => {
330 println!("response: {:?}", scenario);
331 }
332 Err(status) => {
333 println!("response: Failed {:?}", zx_status::Status::err_from_raw(status));
334 }
335 }
336 }
337 opts::PhyCmd::ResetTxPowerScenario { phy_id } => {
338 let response = monitor_proxy
339 .reset_tx_power_scenario(phy_id)
340 .await
341 .context("error resetting tx power scenario")?;
342 match response {
343 Ok(_) => {
344 println!("response: OK");
345 }
346 Err(status) => {
347 println!("response: Failed {:?}", zx_status::Status::err_from_raw(status));
348 }
349 }
350 }
351 }
352 Ok(())
353}
354
355fn is_valid_country_str(country: &String) -> bool {
356 country.len() == 2 && country.chars().all(|x| x.is_ascii())
357}
358
359async fn do_iface(cmd: opts::IfaceCmd, monitor_proxy: DeviceMonitor) -> Result<(), Error> {
360 match cmd {
361 opts::IfaceCmd::New { phy_id, role, sta_addr } => {
362 let sta_addr = match sta_addr {
363 Some(s) => s.parse::<MacAddr>()?,
364 None => NULL_ADDR,
365 };
366
367 let req = wlan_service::DeviceMonitorCreateIfaceRequest {
368 phy_id: Some(phy_id),
369 role: Some(role.into()),
370 sta_address: Some(sta_addr.to_array()),
371 ..Default::default()
372 };
373
374 let response =
375 monitor_proxy.create_iface(&req).await.context("error getting response")?;
376 println!("response: {:?}", response);
377 }
378 opts::IfaceCmd::Delete { iface_id } => {
379 let req = wlan_service::DestroyIfaceRequest { iface_id };
380
381 let response =
382 monitor_proxy.destroy_iface(&req).await.context("error destroying iface")?;
383 match zx_status::Status::ok(response) {
384 Ok(()) => println!("destroyed iface {:?}", iface_id),
385 Err(s) => println!("error destroying iface: {:?}", s),
386 }
387 }
388 opts::IfaceCmd::List => {
389 let response = monitor_proxy.list_ifaces().await.context("error getting response")?;
390 println!("response: {:?}", response);
391 }
392 opts::IfaceCmd::Query { iface_id } => {
393 let result =
394 monitor_proxy.query_iface(iface_id).await.context("error querying iface")?;
395 match result {
396 Ok(response) => println!("response: {}", format_iface_query_response(response)),
397 Err(err) => println!("error querying Iface {}: {}", iface_id, err),
398 }
399 }
400 opts::IfaceCmd::Minstrel(cmd) => match cmd {
401 opts::MinstrelCmd::List { iface_id: _ } => {
402 println!("List minstrel peers is not supported.");
403 }
404 opts::MinstrelCmd::Show { iface_id: _, peer_addr: _ } => {
405 println!("Show minstrel peer is not supported.");
406 }
407 },
408 opts::IfaceCmd::Status(cmd) => do_status(cmd, monitor_proxy).await?,
409 }
410 Ok(())
411}
412
413fn read_scan_result_vmo(_vmo: fidl::Vmo) -> Result<Vec<fidl_sme::ScanResult>, Error> {
414 #[cfg(target_os = "fuchsia")]
415 return wlan_common::scan::read_vmo(_vmo)
416 .map_err(|e| format_err!("failed to read VMO: {:?}", e));
417 #[cfg(not(target_os = "fuchsia"))]
418 return Err(format_err!("cannot read scan result VMO on host"));
419}
420
421async fn do_client_connect(
422 cmd: opts::ClientConnectCmd,
423 monitor_proxy: DeviceMonitorProxy,
424) -> Result<(), Error> {
425 async fn try_get_bss_desc(
426 scan_result: fidl_sme::ClientSmeScanResult,
427 ssid: &Ssid,
428 bssid: Option<&Bssid>,
429 ) -> Result<fidl_ieee80211::BssDescription, Error> {
430 let mut bss_description = None;
431 match scan_result {
432 Ok(vmo) => {
433 let scan_result_list = read_scan_result_vmo(vmo)?;
434 if bss_description.is_none() {
435 if let Some(bss_info) = scan_result_list.into_iter().find(|scan_result| {
438 match ScanResult::try_from(scan_result.clone()) {
443 Ok(scan_result) => {
444 scan_result.bss_description.ssid == *ssid
446 && scan_result.bss_description.bssid
447 == *bssid.unwrap_or(&scan_result.bss_description.bssid)
448 }
449 Err(e) => {
450 println!("Failed to convert ScanResult: {:?}", e);
451 println!(" {:?}", scan_result);
452 false
453 }
454 }
455 }) {
456 bss_description = Some(bss_info.bss_description);
457 }
458 }
459 }
460 Err(scan_error_code) => {
461 return Err(format_err!("failed to fetch scan result: {:?}", scan_error_code));
462 }
463 }
464 bss_description.ok_or_else(|| format_err!("failed to find a matching BSS in scan results"))
465 }
466
467 println!(
468 "The `connect` command performs an implicit scan. This behavior is DEPRECATED and in the \
469 future detailed BSS information will be required to connect! Use the `donut` tool to \
470 connect to networks using an SSID."
471 );
472 let opts::ClientConnectCmd { iface_id, ssid, bssid, password, psk, scan_type } = cmd;
473 let ssid = Ssid::try_from(ssid)?;
474 let bssid = bssid.as_deref().map(MacAddr::from_str).transpose().unwrap().map(Bssid::from);
475 let sme = get_client_sme(monitor_proxy, iface_id).await?;
476 let req = match scan_type {
477 ScanTypeArg::Active => fidl_sme::ScanRequest::Active(fidl_sme::ActiveScanRequest {
478 ssids: vec![ssid.to_vec()],
479 channels: vec![],
480 }),
481 ScanTypeArg::Passive => {
482 fidl_sme::ScanRequest::Passive(fidl_sme::PassiveScanRequest { channels: vec![] })
483 }
484 };
485 let scan_result = sme.scan(&req).await.context("error sending scan request")?;
486 let bss_description = try_get_bss_desc(scan_result, &ssid, bssid.as_ref()).await?;
487 let authentication = match fidl_internal::Authentication::try_from(SecurityContext {
488 unparsed_password_text: password,
489 unparsed_psk_text: psk,
490 bss: BssDescription::try_from(bss_description.clone())?,
491 }) {
492 Ok(authentication) => authentication,
493 Err(error) => {
494 println!("authentication error: {}", error);
495 return Ok(());
496 }
497 };
498 let (local, remote) = endpoints::create_proxy();
499 let req = fidl_sme::ConnectRequest {
500 ssid: ssid.to_vec(),
501 bss_description,
502 authentication,
503 deprecated_scan_type: scan_type.into(),
504 multiple_bss_candidates: false, };
506 sme.connect(&req, Some(remote)).context("error sending connect request")?;
507 handle_connect_transaction(local).await
508}
509
510async fn do_client_disconnect(
511 cmd: opts::ClientDisconnectCmd,
512 monitor_proxy: DeviceMonitor,
513) -> Result<(), Error> {
514 let opts::ClientDisconnectCmd { iface_id } = cmd;
515 let sme = get_client_sme(monitor_proxy, iface_id).await?;
516 sme.disconnect(fidl_sme::UserDisconnectReason::WlanDevTool)
517 .await
518 .map_err(|e| format_err!("error sending disconnect request: {}", e))
519}
520
521async fn do_client_scan(
522 cmd: opts::ClientScanCmd,
523 monitor_proxy: DeviceMonitor,
524) -> Result<(), Error> {
525 let opts::ClientScanCmd { iface_id, scan_type } = cmd;
526 let sme = get_client_sme(monitor_proxy, iface_id).await?;
527 let req = match scan_type {
528 ScanTypeArg::Passive => {
529 fidl_sme::ScanRequest::Passive(fidl_sme::PassiveScanRequest { channels: vec![] })
530 }
531 ScanTypeArg::Active => fidl_sme::ScanRequest::Active(fidl_sme::ActiveScanRequest {
532 ssids: vec![],
533 channels: vec![],
534 }),
535 };
536 let scan_result = sme.scan(&req).await.context("error sending scan request")?;
537 print_scan_result(scan_result);
538 Ok(())
539}
540
541async fn print_iface_status(iface_id: u16, monitor_proxy: DeviceMonitor) -> Result<(), Error> {
542 let result = monitor_proxy
543 .query_iface(iface_id)
544 .await
545 .context("querying iface info")?
546 .map_err(zx_status::Status::err_from_raw)?;
547
548 match result.role {
549 WlanMacRole::Client => {
550 let client_sme = get_client_sme(monitor_proxy, iface_id).await?;
551 let client_status_response = client_sme.status().await?;
552 match client_status_response {
553 fidl_sme::ClientStatusResponse::Connected(serving_ap_info) => {
554 println!(
555 "Iface {}: Connected to '{}' (bssid {}) channel: {:?} rssi: {}dBm snr: {}dB",
556 iface_id,
557 String::from_utf8_lossy(&serving_ap_info.ssid),
558 Bssid::from(serving_ap_info.bssid),
559 serving_ap_info.primary,
560 serving_ap_info.rssi_dbm,
561 serving_ap_info.snr_db,
562 );
563 }
564 fidl_sme::ClientStatusResponse::Connecting(ssid) => {
565 println!("Connecting to '{}'", String::from_utf8_lossy(&ssid));
566 }
567 fidl_sme::ClientStatusResponse::Roaming(bssid) => {
568 println!("Roaming to '{}'", String::from_utf8_lossy(&bssid));
569 }
570 fidl_sme::ClientStatusResponse::Idle(_) => {
571 println!("Iface {}: Not connected to a network", iface_id)
572 }
573 }
574 }
575 WlanMacRole::Ap => {
576 let sme = get_ap_sme(monitor_proxy, iface_id).await?;
577 let status = sme.status().await?;
578 println!(
579 "Iface {}: Running AP: {:?}",
580 iface_id,
581 status.running_ap.map(|ap| {
582 format!(
583 "ssid: {}, channel: {}, clients: {}",
584 String::from_utf8_lossy(&ap.ssid),
585 ap.channel,
586 ap.num_clients
587 )
588 })
589 );
590 }
591 WlanMacRole::Mesh => println!("Iface {}: Mesh not supported", iface_id),
592 fidl_fuchsia_wlan_common::WlanMacRoleUnknown!() => {
593 println!("Iface {}: Unknown WlanMacRole type {:?}", iface_id, result.role);
594 }
595 }
596 Ok(())
597}
598
599async fn do_status(cmd: opts::IfaceStatusCmd, monitor_proxy: DeviceMonitor) -> Result<(), Error> {
600 let ids = get_iface_ids(monitor_proxy.clone(), cmd.iface_id).await?;
601
602 if ids.len() == 0 {
603 return Err(format_err!("No iface found"));
604 }
605 for iface_id in ids {
606 if let Err(e) = print_iface_status(iface_id, monitor_proxy.clone()).await {
607 println!("Iface {}: Error querying status: {}", iface_id, e);
608 continue;
609 }
610 }
611 Ok(())
612}
613
614async fn do_client_wmm_status(
615 cmd: opts::ClientWmmStatusCmd,
616 monitor_proxy: DeviceMonitor,
617 stdout: &mut dyn std::io::Write,
618) -> Result<(), Error> {
619 let sme = get_client_sme(monitor_proxy, cmd.iface_id).await?;
620 let wmm_status = sme
621 .wmm_status()
622 .await
623 .map_err(|e| format_err!("error sending WmmStatus request: {}", e))?;
624 match wmm_status {
625 Ok(wmm_status) => print_wmm_status(&wmm_status, stdout)?,
626 Err(code) => writeln!(stdout, "ClientSme::WmmStatus fails with status code: {}", code)?,
627 }
628 Ok(())
629}
630
631fn print_wmm_status(
632 wmm_status: &fidl_internal::WmmStatusResponse,
633 stdout: &mut dyn std::io::Write,
634) -> Result<(), Error> {
635 writeln!(stdout, "apsd={}", wmm_status.apsd)?;
636 print_wmm_ac_params("ac_be", &wmm_status.ac_be_params, stdout)?;
637 print_wmm_ac_params("ac_bk", &wmm_status.ac_bk_params, stdout)?;
638 print_wmm_ac_params("ac_vi", &wmm_status.ac_vi_params, stdout)?;
639 print_wmm_ac_params("ac_vo", &wmm_status.ac_vo_params, stdout)?;
640 Ok(())
641}
642
643fn print_wmm_ac_params(
644 ac_name: &str,
645 ac_params: &fidl_internal::WmmAcParams,
646 stdout: &mut dyn std::io::Write,
647) -> Result<(), Error> {
648 writeln!(
649 stdout,
650 "{ac_name}: aifsn={aifsn} acm={acm} ecw_min={ecw_min} ecw_max={ecw_max} txop_limit={txop_limit}",
651 ac_name = ac_name,
652 aifsn = ac_params.aifsn,
653 acm = ac_params.acm,
654 ecw_min = ac_params.ecw_min,
655 ecw_max = ac_params.ecw_max,
656 txop_limit = ac_params.txop_limit,
657 )?;
658 Ok(())
659}
660
661async fn do_ap(cmd: opts::ApCmd, monitor_proxy: DeviceMonitor) -> Result<(), Error> {
662 match cmd {
663 opts::ApCmd::Start { iface_id, ssid, password, channel, band } => {
664 let sme = get_ap_sme(monitor_proxy, iface_id).await?;
665 let config = fidl_sme::ApConfig {
666 ssid: ssid.as_bytes().to_vec(),
667 password: password.map_or(vec![], |p| p.as_bytes().to_vec()),
668 radio_cfg: fidl_sme::RadioConfig {
669 phy: PhyArg::Ht.into(),
670 primary: fidl_ieee80211::ChannelNumber { band: band.into(), number: channel },
671 bandwidth: fidl_ieee80211::ChannelBandwidth::Cbw20,
672 },
673 };
674 println!("{:?}", sme.start(&config).await?);
675 }
676 opts::ApCmd::Stop { iface_id } => {
677 let sme = get_ap_sme(monitor_proxy, iface_id).await?;
678 let r = sme.stop().await;
679 println!("{:?}", r);
680 }
681 }
682 Ok(())
683}
684
685#[cfg(target_os = "fuchsia")]
686async fn do_rsn(cmd: opts::RsnCmd) -> Result<(), Error> {
687 match cmd {
688 opts::RsnCmd::GeneratePsk { passphrase, ssid } => {
689 println!("{}", generate_psk(&passphrase, &ssid)?);
690 }
691 }
692 Ok(())
693}
694
695#[cfg(target_os = "fuchsia")]
696fn generate_psk(passphrase: &str, ssid: &str) -> Result<String, Error> {
697 let psk = psk::compute(passphrase.as_bytes(), &Ssid::try_from(ssid)?)?;
698 Ok(hex::encode(&psk))
699}
700
701fn print_scan_result(scan_result: fidl_sme::ClientSmeScanResult) {
702 match scan_result {
703 Ok(vmo) => {
704 let scan_result_list = match read_scan_result_vmo(vmo) {
705 Ok(list) => list,
706 Err(e) => {
707 eprintln!("Failed to read VMO: {:?}", e);
708 return;
709 }
710 };
711 print_scan_header();
712 scan_result_list
713 .into_iter()
714 .filter_map(
715 |scan_result| match ScanResult::try_from(scan_result.clone()) {
720 Ok(scan_result) => Some(scan_result),
721 Err(e) => {
722 eprintln!("Failed to convert ScanResult: {:?}", e);
723 eprintln!(" {:?}", scan_result);
724 None
725 }
726 },
727 )
728 .sorted_by(|a, b| a.bss_description.ssid.cmp(&b.bss_description.ssid))
729 .by_ref()
730 .for_each(|scan_result| print_one_scan_result(&scan_result));
731 }
732 Err(scan_error_code) => {
733 eprintln!("Error: {:?}", scan_error_code);
734 }
735 }
736}
737
738fn print_scan_line(
739 bssid: impl fmt::Display,
740 dbm: impl fmt::Display,
741 channel: impl fmt::Display,
742 protection: impl fmt::Display,
743 compat: impl fmt::Display,
744 ssid: impl fmt::Display,
745) {
746 println!("{:17} {:>4} {:>6} {:12} {:10} {}", bssid, dbm, channel, protection, compat, ssid)
747}
748
749fn print_scan_header() {
750 print_scan_line("BSSID", "dBm", "Chan", "Protection", "Compatible", "SSID");
751}
752
753fn print_one_scan_result(scan_result: &wlan_common::scan::ScanResult) {
754 print_scan_line(
755 scan_result.bss_description.bssid,
756 scan_result.bss_description.rssi_dbm,
757 wlan_common::channel::Channel::from(scan_result.bss_description.channel),
758 scan_result.bss_description.protection(),
759 if scan_result.is_compatible() { "Y" } else { "N" },
760 scan_result.bss_description.ssid.to_string_not_redactable(),
761 );
762}
763
764async fn handle_connect_transaction(
765 connect_txn: fidl_sme::ConnectTransactionProxy,
766) -> Result<(), Error> {
767 let mut events = connect_txn.take_event_stream();
768 while let Some(evt) = events
769 .try_next()
770 .await
771 .context("failed to receive connect result before the channel was closed")?
772 {
773 match evt {
774 ConnectTransactionEvent::OnConnectResult { result } => {
775 match (result.code, result.is_credential_rejected) {
776 (fidl_ieee80211::StatusCode::Success, _) => println!("Connected successfully"),
777 (fidl_ieee80211::StatusCode::Canceled, _) => {
778 eprintln!("Connecting was canceled or superseded by another command")
779 }
780 (code, true) => eprintln!("Credential rejected, status code: {:?}", code),
781 (code, false) => eprintln!("Failed to connect to network: {:?}", code),
782 }
783 break;
784 }
785 evt => {
786 eprintln!("Expected ConnectTransactionEvent::OnConnectResult event, got {:?}", evt);
787 }
788 }
789 }
790 Ok(())
791}
792
793fn error_from_sme_raw_status(
805 raw_status: zx_sys::zx_status_t,
806 station_mode: WlanMacRole,
807 iface_id: u16,
808) -> Error {
809 if zx_status::Status::ok(raw_status).is_ok() {
810 return Error::msg("Unexpected OK error");
811 }
812 match zx_status::Status::err_from_raw(raw_status) {
813 zx_status::Status::NOT_FOUND => Error::msg("invalid interface id"),
814 zx_status::Status::NOT_SUPPORTED => Error::msg("operation not supported on SME interface"),
815 zx_status::Status::INTERNAL => {
816 Error::msg("internal server error sending endpoint to the SME server future")
817 }
818 _ => Error::msg("unrecognized error associated with SME interface"),
819 }
820 .context(format!(
821 "Failed to access {:?} for interface id {}. \
822 Please ensure the selected iface supports {:?} mode.",
823 station_mode, iface_id, station_mode,
824 ))
825}
826
827async fn get_client_sme(
828 monitor_proxy: DeviceMonitor,
829 iface_id: u16,
830) -> Result<fidl_sme::ClientSmeProxy, Error> {
831 let (proxy, remote) = endpoints::create_proxy();
832 monitor_proxy
833 .get_client_sme(iface_id, remote)
834 .await
835 .context("error sending GetClientSme request")?
836 .map_err(|e| error_from_sme_raw_status(e, WlanMacRole::Client, iface_id))?;
837 Ok(proxy)
838}
839
840async fn get_ap_sme(
841 monitor_proxy: DeviceMonitor,
842 iface_id: u16,
843) -> Result<fidl_sme::ApSmeProxy, Error> {
844 let (proxy, remote) = endpoints::create_proxy();
845 monitor_proxy
846 .get_ap_sme(iface_id, remote)
847 .await
848 .context("error sending GetApSme request")?
849 .map_err(|e| error_from_sme_raw_status(e, WlanMacRole::Ap, iface_id))?;
850 Ok(proxy)
851}
852
853async fn get_iface_ids(
854 monitor_proxy: DeviceMonitor,
855 iface_id: Option<u16>,
856) -> Result<Vec<u16>, Error> {
857 match iface_id {
858 Some(id) => Ok(vec![id]),
859 None => monitor_proxy.list_ifaces().await.context("error listing ifaces"),
860 }
861}
862
863fn format_iface_query_response(resp: QueryIfaceResponse) -> String {
864 format!(
865 "QueryIfaceResponse {{ role: {:?}, id: {}, phy_id: {}, phy_assigned_id: {}, sta_addr: {}, factory_addr: {}}}",
866 resp.role,
867 resp.id,
868 resp.phy_id,
869 resp.phy_assigned_id,
870 MacAddr::from(resp.sta_addr),
871 MacAddr::from(resp.factory_addr),
872 )
873}
874
875#[cfg(test)]
876mod tests {
877 use super::*;
878 use assert_matches::assert_matches;
879 use fidl::endpoints::create_proxy;
880 use fidl_fuchsia_wlan_device_service::DeviceMonitorMarker;
881 use fuchsia_async as fasync;
882 use futures::task::Poll;
883 use ieee80211::SsidError;
884 use std::pin::pin;
885 use wlan_common::fake_bss_description;
886
887 #[fuchsia::test]
888 fn negotiate_authentication() {
889 let bss = fake_bss_description!(Open);
890 assert_eq!(
891 fidl_internal::Authentication::try_from(SecurityContext {
892 unparsed_password_text: None,
893 unparsed_psk_text: None,
894 bss
895 }),
896 Ok(fidl_internal::Authentication {
897 protocol: fidl_internal::Protocol::Open,
898 credentials: None
899 }),
900 );
901
902 let bss = fake_bss_description!(Wpa1);
903 assert_eq!(
904 fidl_internal::Authentication::try_from(SecurityContext {
905 unparsed_password_text: Some(String::from("password")),
906 unparsed_psk_text: None,
907 bss,
908 }),
909 Ok(fidl_internal::Authentication {
910 protocol: fidl_internal::Protocol::Wpa1,
911 credentials: Some(Box::new(fidl_internal::Credentials::Wpa(
912 fidl_internal::WpaCredentials::Passphrase(b"password".to_vec())
913 ))),
914 }),
915 );
916
917 let bss = fake_bss_description!(Wpa2);
918 let psk = String::from("f42c6fc52df0ebef9ebb4b90b38a5f902e83fe1b135a70e23aed762e9710a12e");
919 assert_eq!(
920 fidl_internal::Authentication::try_from(SecurityContext {
921 unparsed_password_text: None,
922 unparsed_psk_text: Some(psk),
923 bss
924 }),
925 Ok(fidl_internal::Authentication {
926 protocol: fidl_internal::Protocol::Wpa2Personal,
927 credentials: Some(Box::new(fidl_internal::Credentials::Wpa(
928 fidl_internal::WpaCredentials::Psk([
929 0xf4, 0x2c, 0x6f, 0xc5, 0x2d, 0xf0, 0xeb, 0xef, 0x9e, 0xbb, 0x4b, 0x90,
930 0xb3, 0x8a, 0x5f, 0x90, 0x2e, 0x83, 0xfe, 0x1b, 0x13, 0x5a, 0x70, 0xe2,
931 0x3a, 0xed, 0x76, 0x2e, 0x97, 0x10, 0xa1, 0x2e,
932 ])
933 ))),
934 }),
935 );
936
937 let bss = fake_bss_description!(Wpa2);
938 let psk = String::from("f42c6fc52df0ebef9ebb4b90b38a5f902e83fe1b135a70e23aed762e9710a12e");
939 assert!(matches!(
940 fidl_internal::Authentication::try_from(SecurityContext {
941 unparsed_password_text: Some(String::from("password")),
942 unparsed_psk_text: Some(psk),
943 bss,
944 }),
945 Err(_),
946 ));
947 }
948
949 #[fuchsia::test]
950 fn destroy_iface() {
951 let mut exec = fasync::TestExecutor::new();
952 let (monitor_svc_local, monitor_svc_remote) = create_proxy::<DeviceMonitorMarker>();
953 let mut monitor_svc_stream = monitor_svc_remote.into_stream();
954 let del_fut = do_iface(IfaceCmd::Delete { iface_id: 5 }, monitor_svc_local);
955 let mut del_fut = pin!(del_fut);
956
957 assert_matches!(exec.run_until_stalled(&mut del_fut), Poll::Pending);
958 assert_matches!(
959 exec.run_until_stalled(&mut monitor_svc_stream.next()),
960 Poll::Ready(Some(Ok(wlan_service::DeviceMonitorRequest::DestroyIface {
961 req, responder
962 }))) => {
963 assert_eq!(req.iface_id, 5);
964 responder.send(zx_sys::ZX_OK).expect("failed to send response");
965 }
966 );
967 }
968
969 #[fuchsia::test]
970 fn test_country_input() {
971 assert!(is_valid_country_str(&"RS".to_string()));
972 assert!(is_valid_country_str(&"00".to_string()));
973 assert!(is_valid_country_str(&"M1".to_string()));
974 assert!(is_valid_country_str(&"-M".to_string()));
975
976 assert!(!is_valid_country_str(&"ABC".to_string()));
977 assert!(!is_valid_country_str(&"X".to_string()));
978 assert!(!is_valid_country_str(&"❤".to_string()));
979 }
980
981 #[fuchsia::test]
982 fn test_get_country() {
983 let mut exec = fasync::TestExecutor::new();
984 let (monitor_svc_local, monitor_svc_remote) = create_proxy::<DeviceMonitorMarker>();
985 let mut monitor_svc_stream = monitor_svc_remote.into_stream();
986 let fut = do_phy(PhyCmd::GetCountry { phy_id: 45 }, monitor_svc_local);
987 let mut fut = pin!(fut);
988
989 assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
990 assert_matches!(
991 exec.run_until_stalled(&mut monitor_svc_stream.next()),
992 Poll::Ready(Some(Ok(wlan_service::DeviceMonitorRequest::GetCountry {
993 phy_id, responder,
994 }))) => {
995 assert_eq!(phy_id, 45);
996 responder.send(
997 Ok(&fidl_fuchsia_wlan_device_service::GetCountryResponse {
998 alpha2: [40u8, 40u8],
999 })).expect("failed to send response");
1000 }
1001 );
1002
1003 assert_matches!(exec.run_until_stalled(&mut fut), Poll::Ready(Ok(())));
1004 }
1005
1006 #[fuchsia::test]
1007 fn test_set_country() {
1008 let mut exec = fasync::TestExecutor::new();
1009 let (monitor_svc_local, monitor_svc_remote) = create_proxy::<DeviceMonitorMarker>();
1010 let mut monitor_svc_stream = monitor_svc_remote.into_stream();
1011 let fut =
1012 do_phy(PhyCmd::SetCountry { phy_id: 45, country: "RS".to_string() }, monitor_svc_local);
1013 let mut fut = pin!(fut);
1014
1015 assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
1016 assert_matches!(
1017 exec.run_until_stalled(&mut monitor_svc_stream.next()),
1018 Poll::Ready(Some(Ok(wlan_service::DeviceMonitorRequest::SetCountry {
1019 req, responder,
1020 }))) => {
1021 assert_eq!(req.phy_id, 45);
1022 assert_eq!(req.alpha2, "RS".as_bytes());
1023 responder.send(zx_sys::ZX_OK).expect("failed to send response");
1024 }
1025 );
1026 }
1027
1028 #[fuchsia::test]
1029 fn test_clear_country() {
1030 let mut exec = fasync::TestExecutor::new();
1031 let (monitor_svc_local, monitor_svc_remote) = create_proxy::<DeviceMonitorMarker>();
1032 let mut monitor_svc_stream = monitor_svc_remote.into_stream();
1033 let fut = do_phy(PhyCmd::ClearCountry { phy_id: 45 }, monitor_svc_local);
1034 let mut fut = pin!(fut);
1035
1036 assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
1037 assert_matches!(
1038 exec.run_until_stalled(&mut monitor_svc_stream.next()),
1039 Poll::Ready(Some(Ok(wlan_service::DeviceMonitorRequest::ClearCountry {
1040 req, responder,
1041 }))) => {
1042 assert_eq!(req.phy_id, 45);
1043 responder.send(zx_sys::ZX_OK).expect("failed to send response");
1044 }
1045 );
1046 }
1047
1048 #[fuchsia::test]
1049 fn test_power_down() {
1050 let mut exec = fasync::TestExecutor::new();
1051 let (monitor_svc_local, monitor_svc_remote) = create_proxy::<DeviceMonitorMarker>();
1052 let mut monitor_svc_stream = monitor_svc_remote.into_stream();
1053 let fut =
1054 do_phy(PhyCmd::SetPowerState { phy_id: 45, state: OnOffArg::Off }, monitor_svc_local);
1055 let mut fut = pin!(fut);
1056
1057 assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
1058 assert_matches!(
1059 exec.run_until_stalled(&mut monitor_svc_stream.next()),
1060 Poll::Ready(Some(Ok(wlan_service::DeviceMonitorRequest::PowerDown {
1061 phy_id, responder,
1062 }))) => {
1063 assert_eq!(phy_id, 45);
1064 responder.send(Err(zx_sys::ZX_OK)).expect("failed to send response");
1065 }
1066 );
1067 }
1068
1069 #[fuchsia::test]
1070 fn test_power_up() {
1071 let mut exec = fasync::TestExecutor::new();
1072 let (monitor_svc_local, monitor_svc_remote) = create_proxy::<DeviceMonitorMarker>();
1073 let mut monitor_svc_stream = monitor_svc_remote.into_stream();
1074 let fut =
1075 do_phy(PhyCmd::SetPowerState { phy_id: 45, state: OnOffArg::On }, monitor_svc_local);
1076 let mut fut = pin!(fut);
1077
1078 assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
1079 assert_matches!(
1080 exec.run_until_stalled(&mut monitor_svc_stream.next()),
1081 Poll::Ready(Some(Ok(wlan_service::DeviceMonitorRequest::PowerUp {
1082 phy_id, responder,
1083 }))) => {
1084 assert_eq!(phy_id, 45);
1085 responder.send(Err(zx_sys::ZX_OK)).expect("failed to send response");
1086 }
1087 );
1088 }
1089
1090 #[fuchsia::test]
1091 fn test_reset() {
1092 let mut exec = fasync::TestExecutor::new();
1093 let (monitor_svc_local, monitor_svc_remote) = create_proxy::<DeviceMonitorMarker>();
1094 let mut monitor_svc_stream = monitor_svc_remote.into_stream();
1095 let fut = do_phy(PhyCmd::Reset { phy_id: 45 }, monitor_svc_local);
1096 let mut fut = pin!(fut);
1097
1098 assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
1099 assert_matches!(
1100 exec.run_until_stalled(&mut monitor_svc_stream.next()),
1101 Poll::Ready(Some(Ok(wlan_service::DeviceMonitorRequest::Reset {
1102 phy_id, responder,
1103 }))) => {
1104 assert_eq!(phy_id, 45);
1105 responder.send(Err(zx_sys::ZX_OK)).expect("failed to send response");
1106 }
1107 );
1108 }
1109
1110 #[fuchsia::test]
1111 fn test_get_power_state() {
1112 let mut exec = fasync::TestExecutor::new();
1113 let (monitor_svc_local, monitor_svc_remote) = create_proxy::<DeviceMonitorMarker>();
1114 let mut monitor_svc_stream = monitor_svc_remote.into_stream();
1115 let fut = do_phy(PhyCmd::GetPowerState { phy_id: 45 }, monitor_svc_local);
1116 let mut fut = pin!(fut);
1117
1118 assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
1119 assert_matches!(
1120 exec.run_until_stalled(&mut monitor_svc_stream.next()),
1121 Poll::Ready(Some(Ok(wlan_service::DeviceMonitorRequest::GetPowerState {
1122 phy_id, responder,
1123 }))) => {
1124 assert_eq!(phy_id, 45);
1125 responder.send(Ok(true)).expect("failed to send response");
1126 }
1127 );
1128
1129 assert_matches!(exec.run_until_stalled(&mut fut), Poll::Ready(Ok(())));
1130 }
1131
1132 #[fuchsia::test]
1133 fn test_set_power_save_mode() {
1134 let mut exec = fasync::TestExecutor::new();
1135 let (monitor_svc_local, monitor_svc_remote) = create_proxy::<DeviceMonitorMarker>();
1136 let mut monitor_svc_stream = monitor_svc_remote.into_stream();
1137 let fut = do_phy(
1138 PhyCmd::SetPowerSaveMode { phy_id: 45, mode: PsModeArg::PsModeBalanced },
1139 monitor_svc_local,
1140 );
1141 let mut fut = pin!(fut);
1142
1143 assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
1144 assert_matches!(
1145 exec.run_until_stalled(&mut monitor_svc_stream.next()),
1146 Poll::Ready(Some(Ok(wlan_service::DeviceMonitorRequest::SetPowerSaveMode {
1147 req, responder,
1148 }))) => {
1149 assert_eq!(req.phy_id, 45);
1150 assert_eq!(
1151 req.ps_mode,
1152 fidl_fuchsia_wlan_common::PowerSaveType::PsModeBalanced
1153 );
1154 responder.send(zx_sys::ZX_OK).expect("failed to send response");
1155 }
1156 );
1157 }
1158
1159 #[fuchsia::test]
1160 fn test_get_power_save_mode() {
1161 let mut exec = fasync::TestExecutor::new();
1162 let (monitor_svc_local, monitor_svc_remote) = create_proxy::<DeviceMonitorMarker>();
1163 let mut monitor_svc_stream = monitor_svc_remote.into_stream();
1164 let fut = do_phy(PhyCmd::GetPowerSaveMode { phy_id: 45 }, monitor_svc_local);
1165 let mut fut = pin!(fut);
1166
1167 assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
1168 assert_matches!(
1169 exec.run_until_stalled(&mut monitor_svc_stream.next()),
1170 Poll::Ready(Some(Ok(wlan_service::DeviceMonitorRequest::GetPowerSaveMode {
1171 phy_id, responder,
1172 }))) => {
1173 assert_eq!(phy_id, 45);
1174 responder
1175 .send(Ok(&fidl_fuchsia_wlan_device_service::GetPowerSaveModeResponse {
1176 ps_mode: fidl_fuchsia_wlan_common::PowerSaveType::PsModeBalanced,
1177 }))
1178 .expect("failed to send response");
1179 }
1180 );
1181
1182 assert_matches!(exec.run_until_stalled(&mut fut), Poll::Ready(Ok(())));
1183 }
1184
1185 #[fuchsia::test]
1186 fn test_generate_psk() {
1187 assert_eq!(
1188 generate_psk("12345678", "coolnet").unwrap(),
1189 "1ec9ee30fdff1961a9abd083f571464cc0fe27f62f9f59992bd39f8e625e9f52"
1190 );
1191 assert!(generate_psk("short", "coolnet").is_err());
1192 }
1193
1194 fn has_expected_cause(error: Error, message: &str) -> bool {
1195 error.chain().any(|cause| cause.to_string() == message)
1196 }
1197
1198 #[fuchsia::test]
1199 fn test_error_from_sme_raw_status() {
1200 let not_found = error_from_sme_raw_status(
1201 zx_status::Status::NOT_FOUND.into_raw(),
1202 WlanMacRole::Mesh,
1203 1,
1204 );
1205 let not_supported = error_from_sme_raw_status(
1206 zx_status::Status::NOT_SUPPORTED.into_raw(),
1207 WlanMacRole::Ap,
1208 2,
1209 );
1210 let internal_error = error_from_sme_raw_status(
1211 zx_status::Status::INTERNAL.into_raw(),
1212 WlanMacRole::Client,
1213 3,
1214 );
1215 let unrecognized_error = error_from_sme_raw_status(
1216 zx_status::Status::INTERRUPTED_RETRY.into_raw(),
1217 WlanMacRole::Mesh,
1218 4,
1219 );
1220
1221 assert!(has_expected_cause(not_found, "invalid interface id"));
1222 assert!(has_expected_cause(not_supported, "operation not supported on SME interface"));
1223 assert!(has_expected_cause(
1224 internal_error,
1225 "internal server error sending endpoint to the SME server future"
1226 ));
1227 assert!(has_expected_cause(
1228 unrecognized_error,
1229 "unrecognized error associated with SME interface"
1230 ));
1231 }
1232
1233 #[fuchsia::test]
1234 fn reject_connect_ssid_too_long() {
1235 let mut exec = fasync::TestExecutor::new();
1236 let (monitor_local, monitor_remote) = create_proxy::<DeviceMonitorMarker>();
1237 let mut monitor_stream = monitor_remote.into_stream();
1238 let cmd = opts::ClientConnectCmd {
1240 iface_id: 0,
1241 ssid: String::from_utf8(vec![65; 33]).unwrap(),
1242 bssid: None,
1243 password: None,
1244 psk: None,
1245 scan_type: opts::ScanTypeArg::Passive,
1246 };
1247
1248 let connect_fut = do_client_connect(cmd, monitor_local.clone());
1249 let mut connect_fut = pin!(connect_fut);
1250
1251 assert_matches!(exec.run_until_stalled(&mut connect_fut), Poll::Ready(Err(e)) => {
1252 assert_eq!(format!("{}", e), format!("{}", SsidError::Size(33)));
1253 });
1254 assert_matches!(exec.run_until_stalled(&mut monitor_stream.next()), Poll::Pending);
1256 }
1257
1258 #[fuchsia::test]
1259 fn test_wmm_status() {
1260 let mut exec = fasync::TestExecutor::new();
1261 let (monitor_local, monitor_remote) = create_proxy::<DeviceMonitorMarker>();
1262 let mut monitor_stream = monitor_remote.into_stream();
1263 let mut stdout = Vec::new();
1264 {
1265 let fut = do_client_wmm_status(
1266 ClientWmmStatusCmd { iface_id: 11 },
1267 monitor_local,
1268 &mut stdout,
1269 );
1270 let mut fut = pin!(fut);
1271
1272 assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
1273 let mut fake_sme_server_stream = assert_matches!(
1274 exec.run_until_stalled(&mut monitor_stream.next()),
1275 Poll::Ready(Some(Ok(wlan_service::DeviceMonitorRequest::GetClientSme {
1276 iface_id, sme_server, responder,
1277 }))) => {
1278 assert_eq!(iface_id, 11);
1279 responder.send(Ok(())).expect("failed to send GetClientSme response");
1280 sme_server.into_stream()
1281 }
1282 );
1283
1284 assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
1285 assert_matches!(
1286 exec.run_until_stalled(&mut fake_sme_server_stream.next()),
1287 Poll::Ready(Some(Ok(fidl_sme::ClientSmeRequest::WmmStatus { responder }))) => {
1288 let wmm_status_resp = fidl_internal::WmmStatusResponse {
1289 apsd: true,
1290 ac_be_params: fidl_internal::WmmAcParams {
1291 aifsn: 1,
1292 acm: false,
1293 ecw_min: 2,
1294 ecw_max: 3,
1295 txop_limit: 4,
1296 },
1297 ac_bk_params: fidl_internal::WmmAcParams {
1298 aifsn: 5,
1299 acm: false,
1300 ecw_min: 6,
1301 ecw_max: 7,
1302 txop_limit: 8,
1303 },
1304 ac_vi_params: fidl_internal::WmmAcParams {
1305 aifsn: 9,
1306 acm: true,
1307 ecw_min: 10,
1308 ecw_max: 11,
1309 txop_limit: 12,
1310 },
1311 ac_vo_params: fidl_internal::WmmAcParams {
1312 aifsn: 13,
1313 acm: true,
1314 ecw_min: 14,
1315 ecw_max: 15,
1316 txop_limit: 16,
1317 },
1318 };
1319 responder.send(Ok(&wmm_status_resp)).expect("failed to send WMM status response");
1320 }
1321 );
1322
1323 assert_matches!(exec.run_until_stalled(&mut fut), Poll::Ready(Ok(())));
1324 }
1325 assert_eq!(
1326 String::from_utf8(stdout).expect("expect valid UTF8"),
1327 "apsd=true\n\
1328 ac_be: aifsn=1 acm=false ecw_min=2 ecw_max=3 txop_limit=4\n\
1329 ac_bk: aifsn=5 acm=false ecw_min=6 ecw_max=7 txop_limit=8\n\
1330 ac_vi: aifsn=9 acm=true ecw_min=10 ecw_max=11 txop_limit=12\n\
1331 ac_vo: aifsn=13 acm=true ecw_min=14 ecw_max=15 txop_limit=16\n"
1332 );
1333 }
1334
1335 #[fuchsia::test]
1336 fn test_set_tx_power_scenario() {
1337 let mut exec = fasync::TestExecutor::new();
1338 let (monitor_svc_local, monitor_svc_remote) = create_proxy::<DeviceMonitorMarker>();
1339 let mut monitor_svc_stream = monitor_svc_remote.into_stream();
1340 let fut = do_phy(
1341 PhyCmd::SetTxPowerScenario { phy_id: 45, mode: opts::TxPowerScenarioArg::BodyCellOff },
1342 monitor_svc_local,
1343 );
1344 let mut fut = pin!(fut);
1345
1346 assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
1347 assert_matches!(
1348 exec.run_until_stalled(&mut monitor_svc_stream.next()),
1349 Poll::Ready(Some(Ok(wlan_service::DeviceMonitorRequest::SetTxPowerScenario {
1350 phy_id: 45,
1351 scenario: fidl_internal::TxPowerScenario::BodyCellOff,
1352 responder,
1353 }))) => responder.send(Ok(())).expect("failed to send response")
1354 );
1355 assert_matches!(exec.run_until_stalled(&mut fut), Poll::Ready(Ok(())));
1356 }
1357
1358 #[fuchsia::test]
1359 fn test_get_tx_power_scenario() {
1360 let mut exec = fasync::TestExecutor::new();
1361 let (monitor_svc_local, monitor_svc_remote) = create_proxy::<DeviceMonitorMarker>();
1362 let mut monitor_svc_stream = monitor_svc_remote.into_stream();
1363 let fut = do_phy(PhyCmd::GetTxPowerScenario { phy_id: 45 }, monitor_svc_local);
1364 let mut fut = pin!(fut);
1365
1366 assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
1367 assert_matches!(
1368 exec.run_until_stalled(&mut monitor_svc_stream.next()),
1369 Poll::Ready(Some(Ok(wlan_service::DeviceMonitorRequest::GetTxPowerScenario {
1370 phy_id: 45,
1371 responder,
1372 }))) => responder.send(Ok(fidl_internal::TxPowerScenario::BodyCellOff)).expect("failed to send response")
1373 );
1374 assert_matches!(exec.run_until_stalled(&mut fut), Poll::Ready(Ok(())));
1375 }
1376
1377 #[fuchsia::test]
1378 fn test_reset_tx_power_scenario() {
1379 let mut exec = fasync::TestExecutor::new();
1380 let (monitor_svc_local, monitor_svc_remote) = create_proxy::<DeviceMonitorMarker>();
1381 let mut monitor_svc_stream = monitor_svc_remote.into_stream();
1382 let fut = do_phy(PhyCmd::ResetTxPowerScenario { phy_id: 45 }, monitor_svc_local);
1383 let mut fut = pin!(fut);
1384
1385 assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
1386 assert_matches!(
1387 exec.run_until_stalled(&mut monitor_svc_stream.next()),
1388 Poll::Ready(Some(Ok(wlan_service::DeviceMonitorRequest::ResetTxPowerScenario {
1389 phy_id: 45,
1390 responder,
1391 }))) => responder.send(Ok(())).expect("failed to send response")
1392 );
1393 assert_matches!(exec.run_until_stalled(&mut fut), Poll::Ready(Ok(())));
1394 }
1395}