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