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