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