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 | 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_security::Protocol::Owe,
97 _ => fidl_security::Protocol::Open,
98 };
99 Ok(fidl_security::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_security::Authentication {
110 protocol: fidl_security::Protocol::Wep,
111 credentials: Some(Box::new(fidl_security::Credentials::Wep(
112 fidl_security::WepCredentials { key: key.into() },
113 ))),
114 }),
115 Protection::Wpa1 => {
116 parse_wpa_credential_pair(unparsed_password_text, unparsed_psk_text).map(
117 |credentials| fidl_security::Authentication {
118 protocol: fidl_security::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_security::Authentication {
129 protocol: fidl_security::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_security::Credentials::Wpa(
139 fidl_security::WpaCredentials::Passphrase(_),
140 ) => fidl_security::Authentication {
141 protocol: fidl_security::Protocol::Wpa3Personal,
142 credentials: Some(Box::new(credentials)),
143 },
144 fidl_security::Credentials::Wpa(fidl_security::WpaCredentials::Psk(_)) => {
145 fidl_security::Authentication {
146 protocol: fidl_security::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_security::Authentication {
158 protocol: fidl_security::Protocol::Wpa3Personal,
159 credentials: Some(Box::new(fidl_security::Credentials::Wpa(
160 fidl_security::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_common::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 => fidl_sme::ScanRequest::Passive(fidl_sme::PassiveScanRequest {}),
443 };
444 let scan_result = sme.scan(&req).await.context("error sending scan request")?;
445 let bss_description = try_get_bss_desc(scan_result, &ssid, bssid.as_ref()).await?;
446 let authentication = match fidl_security::Authentication::try_from(SecurityContext {
447 unparsed_password_text: password,
448 unparsed_psk_text: psk,
449 bss: BssDescription::try_from(bss_description.clone())?,
450 }) {
451 Ok(authentication) => authentication,
452 Err(error) => {
453 println!("authentication error: {}", error);
454 return Ok(());
455 }
456 };
457 let (local, remote) = endpoints::create_proxy();
458 let req = fidl_sme::ConnectRequest {
459 ssid: ssid.to_vec(),
460 bss_description,
461 authentication,
462 deprecated_scan_type: scan_type.into(),
463 multiple_bss_candidates: false, };
465 sme.connect(&req, Some(remote)).context("error sending connect request")?;
466 handle_connect_transaction(local).await
467}
468
469async fn do_client_disconnect(
470 cmd: opts::ClientDisconnectCmd,
471 monitor_proxy: DeviceMonitor,
472) -> Result<(), Error> {
473 let opts::ClientDisconnectCmd { iface_id } = cmd;
474 let sme = get_client_sme(monitor_proxy, iface_id).await?;
475 sme.disconnect(fidl_sme::UserDisconnectReason::WlanDevTool)
476 .await
477 .map_err(|e| format_err!("error sending disconnect request: {}", e))
478}
479
480async fn do_client_scan(
481 cmd: opts::ClientScanCmd,
482 monitor_proxy: DeviceMonitor,
483) -> Result<(), Error> {
484 let opts::ClientScanCmd { iface_id, scan_type } = cmd;
485 let sme = get_client_sme(monitor_proxy, iface_id).await?;
486 let req = match scan_type {
487 ScanTypeArg::Passive => fidl_sme::ScanRequest::Passive(fidl_sme::PassiveScanRequest {}),
488 ScanTypeArg::Active => fidl_sme::ScanRequest::Active(fidl_sme::ActiveScanRequest {
489 ssids: vec![],
490 channels: vec![],
491 }),
492 };
493 let scan_result = sme.scan(&req).await.context("error sending scan request")?;
494 print_scan_result(scan_result);
495 Ok(())
496}
497
498async fn print_iface_status(iface_id: u16, monitor_proxy: DeviceMonitor) -> Result<(), Error> {
499 let result = monitor_proxy
500 .query_iface(iface_id)
501 .await
502 .context("querying iface info")?
503 .map_err(|e| zx_status::Status::from_raw(e))?;
504
505 match result.role {
506 WlanMacRole::Client => {
507 let client_sme = get_client_sme(monitor_proxy, iface_id).await?;
508 let client_status_response = client_sme.status().await?;
509 match client_status_response {
510 fidl_sme::ClientStatusResponse::Connected(serving_ap_info) => {
511 println!(
512 "Iface {}: Connected to '{}' (bssid {}) channel: {:?} rssi: {}dBm snr: {}dB",
513 iface_id,
514 String::from_utf8_lossy(&serving_ap_info.ssid),
515 Bssid::from(serving_ap_info.bssid),
516 serving_ap_info.channel,
517 serving_ap_info.rssi_dbm,
518 serving_ap_info.snr_db,
519 );
520 }
521 fidl_sme::ClientStatusResponse::Connecting(ssid) => {
522 println!("Connecting to '{}'", String::from_utf8_lossy(&ssid));
523 }
524 fidl_sme::ClientStatusResponse::Roaming(bssid) => {
525 println!("Roaming to '{}'", String::from_utf8_lossy(&bssid));
526 }
527 fidl_sme::ClientStatusResponse::Idle(_) => {
528 println!("Iface {}: Not connected to a network", iface_id)
529 }
530 }
531 }
532 WlanMacRole::Ap => {
533 let sme = get_ap_sme(monitor_proxy, iface_id).await?;
534 let status = sme.status().await?;
535 println!(
536 "Iface {}: Running AP: {:?}",
537 iface_id,
538 status.running_ap.map(|ap| {
539 format!(
540 "ssid: {}, channel: {}, clients: {}",
541 String::from_utf8_lossy(&ap.ssid),
542 ap.channel,
543 ap.num_clients
544 )
545 })
546 );
547 }
548 WlanMacRole::Mesh => println!("Iface {}: Mesh not supported", iface_id),
549 fidl_fuchsia_wlan_common::WlanMacRoleUnknown!() => {
550 println!("Iface {}: Unknown WlanMacRole type {:?}", iface_id, result.role);
551 }
552 }
553 Ok(())
554}
555
556async fn do_status(cmd: opts::IfaceStatusCmd, monitor_proxy: DeviceMonitor) -> Result<(), Error> {
557 let ids = get_iface_ids(monitor_proxy.clone(), cmd.iface_id).await?;
558
559 if ids.len() == 0 {
560 return Err(format_err!("No iface found"));
561 }
562 for iface_id in ids {
563 if let Err(e) = print_iface_status(iface_id, monitor_proxy.clone()).await {
564 println!("Iface {}: Error querying status: {}", iface_id, e);
565 continue;
566 }
567 }
568 Ok(())
569}
570
571async fn do_client_wmm_status(
572 cmd: opts::ClientWmmStatusCmd,
573 monitor_proxy: DeviceMonitor,
574 stdout: &mut dyn std::io::Write,
575) -> Result<(), Error> {
576 let sme = get_client_sme(monitor_proxy, cmd.iface_id).await?;
577 let wmm_status = sme
578 .wmm_status()
579 .await
580 .map_err(|e| format_err!("error sending WmmStatus request: {}", e))?;
581 match wmm_status {
582 Ok(wmm_status) => print_wmm_status(&wmm_status, stdout)?,
583 Err(code) => writeln!(stdout, "ClientSme::WmmStatus fails with status code: {}", code)?,
584 }
585 Ok(())
586}
587
588fn print_wmm_status(
589 wmm_status: &fidl_internal::WmmStatusResponse,
590 stdout: &mut dyn std::io::Write,
591) -> Result<(), Error> {
592 writeln!(stdout, "apsd={}", wmm_status.apsd)?;
593 print_wmm_ac_params("ac_be", &wmm_status.ac_be_params, stdout)?;
594 print_wmm_ac_params("ac_bk", &wmm_status.ac_bk_params, stdout)?;
595 print_wmm_ac_params("ac_vi", &wmm_status.ac_vi_params, stdout)?;
596 print_wmm_ac_params("ac_vo", &wmm_status.ac_vo_params, stdout)?;
597 Ok(())
598}
599
600fn print_wmm_ac_params(
601 ac_name: &str,
602 ac_params: &fidl_internal::WmmAcParams,
603 stdout: &mut dyn std::io::Write,
604) -> Result<(), Error> {
605 writeln!(
606 stdout,
607 "{ac_name}: aifsn={aifsn} acm={acm} ecw_min={ecw_min} ecw_max={ecw_max} txop_limit={txop_limit}",
608 ac_name = ac_name,
609 aifsn = ac_params.aifsn,
610 acm = ac_params.acm,
611 ecw_min = ac_params.ecw_min,
612 ecw_max = ac_params.ecw_max,
613 txop_limit = ac_params.txop_limit,
614 )?;
615 Ok(())
616}
617
618async fn do_ap(cmd: opts::ApCmd, monitor_proxy: DeviceMonitor) -> Result<(), Error> {
619 match cmd {
620 opts::ApCmd::Start { iface_id, ssid, password, channel } => {
621 let sme = get_ap_sme(monitor_proxy, iface_id).await?;
622 let config = fidl_sme::ApConfig {
623 ssid: ssid.as_bytes().to_vec(),
624 password: password.map_or(vec![], |p| p.as_bytes().to_vec()),
625 radio_cfg: fidl_sme::RadioConfig {
626 phy: PhyArg::Ht.into(),
627 channel: fidl_ieee80211::WlanChannel {
628 primary: channel,
629 cbw: CbwArg::Cbw20.into(),
630 secondary80: 0,
631 },
632 },
633 };
634 println!("{:?}", sme.start(&config).await?);
635 }
636 opts::ApCmd::Stop { iface_id } => {
637 let sme = get_ap_sme(monitor_proxy, iface_id).await?;
638 let r = sme.stop().await;
639 println!("{:?}", r);
640 }
641 }
642 Ok(())
643}
644
645#[cfg(target_os = "fuchsia")]
646async fn do_rsn(cmd: opts::RsnCmd) -> Result<(), Error> {
647 match cmd {
648 opts::RsnCmd::GeneratePsk { passphrase, ssid } => {
649 println!("{}", generate_psk(&passphrase, &ssid)?);
650 }
651 }
652 Ok(())
653}
654
655#[cfg(target_os = "fuchsia")]
656fn generate_psk(passphrase: &str, ssid: &str) -> Result<String, Error> {
657 let psk = psk::compute(passphrase.as_bytes(), &Ssid::try_from(ssid)?)?;
658 Ok(hex::encode(&psk))
659}
660
661fn print_scan_result(scan_result: fidl_sme::ClientSmeScanResult) {
662 match scan_result {
663 Ok(vmo) => {
664 let scan_result_list = match read_scan_result_vmo(vmo) {
665 Ok(list) => list,
666 Err(e) => {
667 eprintln!("Failed to read VMO: {:?}", e);
668 return;
669 }
670 };
671 print_scan_header();
672 scan_result_list
673 .into_iter()
674 .filter_map(
675 |scan_result| match ScanResult::try_from(scan_result.clone()) {
680 Ok(scan_result) => Some(scan_result),
681 Err(e) => {
682 eprintln!("Failed to convert ScanResult: {:?}", e);
683 eprintln!(" {:?}", scan_result);
684 None
685 }
686 },
687 )
688 .sorted_by(|a, b| a.bss_description.ssid.cmp(&b.bss_description.ssid))
689 .by_ref()
690 .for_each(|scan_result| print_one_scan_result(&scan_result));
691 }
692 Err(scan_error_code) => {
693 eprintln!("Error: {:?}", scan_error_code);
694 }
695 }
696}
697
698fn print_scan_line(
699 bssid: impl fmt::Display,
700 dbm: impl fmt::Display,
701 channel: impl fmt::Display,
702 protection: impl fmt::Display,
703 compat: impl fmt::Display,
704 ssid: impl fmt::Display,
705) {
706 println!("{:17} {:>4} {:>6} {:12} {:10} {}", bssid, dbm, channel, protection, compat, ssid)
707}
708
709fn print_scan_header() {
710 print_scan_line("BSSID", "dBm", "Chan", "Protection", "Compatible", "SSID");
711}
712
713fn print_one_scan_result(scan_result: &wlan_common::scan::ScanResult) {
714 print_scan_line(
715 scan_result.bss_description.bssid,
716 scan_result.bss_description.rssi_dbm,
717 wlan_common::channel::Channel::from(scan_result.bss_description.channel),
718 scan_result.bss_description.protection(),
719 if scan_result.is_compatible() { "Y" } else { "N" },
720 scan_result.bss_description.ssid.to_string_not_redactable(),
721 );
722}
723
724async fn handle_connect_transaction(
725 connect_txn: fidl_sme::ConnectTransactionProxy,
726) -> Result<(), Error> {
727 let mut events = connect_txn.take_event_stream();
728 while let Some(evt) = events
729 .try_next()
730 .await
731 .context("failed to receive connect result before the channel was closed")?
732 {
733 match evt {
734 ConnectTransactionEvent::OnConnectResult { result } => {
735 match (result.code, result.is_credential_rejected) {
736 (fidl_ieee80211::StatusCode::Success, _) => println!("Connected successfully"),
737 (fidl_ieee80211::StatusCode::Canceled, _) => {
738 eprintln!("Connecting was canceled or superseded by another command")
739 }
740 (code, true) => eprintln!("Credential rejected, status code: {:?}", code),
741 (code, false) => eprintln!("Failed to connect to network: {:?}", code),
742 }
743 break;
744 }
745 evt => {
746 eprintln!("Expected ConnectTransactionEvent::OnConnectResult event, got {:?}", evt);
747 }
748 }
749 }
750 Ok(())
751}
752
753fn error_from_sme_raw_status(
765 raw_status: zx_sys::zx_status_t,
766 station_mode: WlanMacRole,
767 iface_id: u16,
768) -> Error {
769 match zx_status::Status::from_raw(raw_status) {
770 zx_status::Status::OK => Error::msg("Unexpected OK error"),
771 zx_status::Status::NOT_FOUND => Error::msg("invalid interface id"),
772 zx_status::Status::NOT_SUPPORTED => Error::msg("operation not supported on SME interface"),
773 zx_status::Status::INTERNAL => {
774 Error::msg("internal server error sending endpoint to the SME server future")
775 }
776 _ => Error::msg("unrecognized error associated with SME interface"),
777 }
778 .context(format!(
779 "Failed to access {:?} for interface id {}. \
780 Please ensure the selected iface supports {:?} mode.",
781 station_mode, iface_id, station_mode,
782 ))
783}
784
785async fn get_client_sme(
786 monitor_proxy: DeviceMonitor,
787 iface_id: u16,
788) -> Result<fidl_sme::ClientSmeProxy, Error> {
789 let (proxy, remote) = endpoints::create_proxy();
790 monitor_proxy
791 .get_client_sme(iface_id, remote)
792 .await
793 .context("error sending GetClientSme request")?
794 .map_err(|e| error_from_sme_raw_status(e, WlanMacRole::Client, iface_id))?;
795 Ok(proxy)
796}
797
798async fn get_ap_sme(
799 monitor_proxy: DeviceMonitor,
800 iface_id: u16,
801) -> Result<fidl_sme::ApSmeProxy, Error> {
802 let (proxy, remote) = endpoints::create_proxy();
803 monitor_proxy
804 .get_ap_sme(iface_id, remote)
805 .await
806 .context("error sending GetApSme request")?
807 .map_err(|e| error_from_sme_raw_status(e, WlanMacRole::Ap, iface_id))?;
808 Ok(proxy)
809}
810
811async fn get_iface_ids(
812 monitor_proxy: DeviceMonitor,
813 iface_id: Option<u16>,
814) -> Result<Vec<u16>, Error> {
815 match iface_id {
816 Some(id) => Ok(vec![id]),
817 None => monitor_proxy.list_ifaces().await.context("error listing ifaces"),
818 }
819}
820
821fn format_iface_query_response(resp: QueryIfaceResponse) -> String {
822 format!(
823 "QueryIfaceResponse {{ role: {:?}, id: {}, phy_id: {}, phy_assigned_id: {}, sta_addr: {}, factory_addr: {}}}",
824 resp.role,
825 resp.id,
826 resp.phy_id,
827 resp.phy_assigned_id,
828 MacAddr::from(resp.sta_addr),
829 MacAddr::from(resp.factory_addr),
830 )
831}
832
833#[cfg(test)]
834mod tests {
835 use super::*;
836 use assert_matches::assert_matches;
837 use fidl::endpoints::create_proxy;
838 use fidl_fuchsia_wlan_device_service::DeviceMonitorMarker;
839 use fuchsia_async as fasync;
840 use futures::task::Poll;
841 use ieee80211::SsidError;
842 use std::pin::pin;
843 use wlan_common::fake_bss_description;
844
845 #[fuchsia::test]
846 fn negotiate_authentication() {
847 let bss = fake_bss_description!(Open);
848 assert_eq!(
849 fidl_security::Authentication::try_from(SecurityContext {
850 unparsed_password_text: None,
851 unparsed_psk_text: None,
852 bss
853 }),
854 Ok(fidl_security::Authentication {
855 protocol: fidl_security::Protocol::Open,
856 credentials: None
857 }),
858 );
859
860 let bss = fake_bss_description!(Wpa1);
861 assert_eq!(
862 fidl_security::Authentication::try_from(SecurityContext {
863 unparsed_password_text: Some(String::from("password")),
864 unparsed_psk_text: None,
865 bss,
866 }),
867 Ok(fidl_security::Authentication {
868 protocol: fidl_security::Protocol::Wpa1,
869 credentials: Some(Box::new(fidl_security::Credentials::Wpa(
870 fidl_security::WpaCredentials::Passphrase(b"password".to_vec())
871 ))),
872 }),
873 );
874
875 let bss = fake_bss_description!(Wpa2);
876 let psk = String::from("f42c6fc52df0ebef9ebb4b90b38a5f902e83fe1b135a70e23aed762e9710a12e");
877 assert_eq!(
878 fidl_security::Authentication::try_from(SecurityContext {
879 unparsed_password_text: None,
880 unparsed_psk_text: Some(psk),
881 bss
882 }),
883 Ok(fidl_security::Authentication {
884 protocol: fidl_security::Protocol::Wpa2Personal,
885 credentials: Some(Box::new(fidl_security::Credentials::Wpa(
886 fidl_security::WpaCredentials::Psk([
887 0xf4, 0x2c, 0x6f, 0xc5, 0x2d, 0xf0, 0xeb, 0xef, 0x9e, 0xbb, 0x4b, 0x90,
888 0xb3, 0x8a, 0x5f, 0x90, 0x2e, 0x83, 0xfe, 0x1b, 0x13, 0x5a, 0x70, 0xe2,
889 0x3a, 0xed, 0x76, 0x2e, 0x97, 0x10, 0xa1, 0x2e,
890 ])
891 ))),
892 }),
893 );
894
895 let bss = fake_bss_description!(Wpa2);
896 let psk = String::from("f42c6fc52df0ebef9ebb4b90b38a5f902e83fe1b135a70e23aed762e9710a12e");
897 assert!(matches!(
898 fidl_security::Authentication::try_from(SecurityContext {
899 unparsed_password_text: Some(String::from("password")),
900 unparsed_psk_text: Some(psk),
901 bss,
902 }),
903 Err(_),
904 ));
905 }
906
907 #[fuchsia::test]
908 fn destroy_iface() {
909 let mut exec = fasync::TestExecutor::new();
910 let (monitor_svc_local, monitor_svc_remote) = create_proxy::<DeviceMonitorMarker>();
911 let mut monitor_svc_stream = monitor_svc_remote.into_stream();
912 let del_fut = do_iface(IfaceCmd::Delete { iface_id: 5 }, monitor_svc_local);
913 let mut del_fut = pin!(del_fut);
914
915 assert_matches!(exec.run_until_stalled(&mut del_fut), Poll::Pending);
916 assert_matches!(
917 exec.run_until_stalled(&mut monitor_svc_stream.next()),
918 Poll::Ready(Some(Ok(wlan_service::DeviceMonitorRequest::DestroyIface {
919 req, responder
920 }))) => {
921 assert_eq!(req.iface_id, 5);
922 responder.send(zx_status::Status::OK.into_raw()).expect("failed to send response");
923 }
924 );
925 }
926
927 #[fuchsia::test]
928 fn test_country_input() {
929 assert!(is_valid_country_str(&"RS".to_string()));
930 assert!(is_valid_country_str(&"00".to_string()));
931 assert!(is_valid_country_str(&"M1".to_string()));
932 assert!(is_valid_country_str(&"-M".to_string()));
933
934 assert!(!is_valid_country_str(&"ABC".to_string()));
935 assert!(!is_valid_country_str(&"X".to_string()));
936 assert!(!is_valid_country_str(&"❤".to_string()));
937 }
938
939 #[fuchsia::test]
940 fn test_get_country() {
941 let mut exec = fasync::TestExecutor::new();
942 let (monitor_svc_local, monitor_svc_remote) = create_proxy::<DeviceMonitorMarker>();
943 let mut monitor_svc_stream = monitor_svc_remote.into_stream();
944 let fut = do_phy(PhyCmd::GetCountry { phy_id: 45 }, monitor_svc_local);
945 let mut fut = pin!(fut);
946
947 assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
948 assert_matches!(
949 exec.run_until_stalled(&mut monitor_svc_stream.next()),
950 Poll::Ready(Some(Ok(wlan_service::DeviceMonitorRequest::GetCountry {
951 phy_id, responder,
952 }))) => {
953 assert_eq!(phy_id, 45);
954 responder.send(
955 Ok(&fidl_fuchsia_wlan_device_service::GetCountryResponse {
956 alpha2: [40u8, 40u8],
957 })).expect("failed to send response");
958 }
959 );
960
961 assert_matches!(exec.run_until_stalled(&mut fut), Poll::Ready(Ok(())));
962 }
963
964 #[fuchsia::test]
965 fn test_set_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 =
970 do_phy(PhyCmd::SetCountry { phy_id: 45, country: "RS".to_string() }, monitor_svc_local);
971 let mut fut = pin!(fut);
972
973 assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
974 assert_matches!(
975 exec.run_until_stalled(&mut monitor_svc_stream.next()),
976 Poll::Ready(Some(Ok(wlan_service::DeviceMonitorRequest::SetCountry {
977 req, responder,
978 }))) => {
979 assert_eq!(req.phy_id, 45);
980 assert_eq!(req.alpha2, "RS".as_bytes());
981 responder.send(zx_status::Status::OK.into_raw()).expect("failed to send response");
982 }
983 );
984 }
985
986 #[fuchsia::test]
987 fn test_clear_country() {
988 let mut exec = fasync::TestExecutor::new();
989 let (monitor_svc_local, monitor_svc_remote) = create_proxy::<DeviceMonitorMarker>();
990 let mut monitor_svc_stream = monitor_svc_remote.into_stream();
991 let fut = do_phy(PhyCmd::ClearCountry { phy_id: 45 }, monitor_svc_local);
992 let mut fut = pin!(fut);
993
994 assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
995 assert_matches!(
996 exec.run_until_stalled(&mut monitor_svc_stream.next()),
997 Poll::Ready(Some(Ok(wlan_service::DeviceMonitorRequest::ClearCountry {
998 req, responder,
999 }))) => {
1000 assert_eq!(req.phy_id, 45);
1001 responder.send(zx_status::Status::OK.into_raw()).expect("failed to send response");
1002 }
1003 );
1004 }
1005
1006 #[fuchsia::test]
1007 fn test_power_down() {
1008 let mut exec = fasync::TestExecutor::new();
1009 let (monitor_svc_local, monitor_svc_remote) = create_proxy::<DeviceMonitorMarker>();
1010 let mut monitor_svc_stream = monitor_svc_remote.into_stream();
1011 let fut =
1012 do_phy(PhyCmd::SetPowerState { phy_id: 45, state: OnOffArg::Off }, monitor_svc_local);
1013 let mut fut = pin!(fut);
1014
1015 assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
1016 assert_matches!(
1017 exec.run_until_stalled(&mut monitor_svc_stream.next()),
1018 Poll::Ready(Some(Ok(wlan_service::DeviceMonitorRequest::PowerDown {
1019 phy_id, responder,
1020 }))) => {
1021 assert_eq!(phy_id, 45);
1022 responder.send(Err(zx_status::Status::OK.into_raw())).expect("failed to send response");
1023 }
1024 );
1025 }
1026
1027 #[fuchsia::test]
1028 fn test_power_up() {
1029 let mut exec = fasync::TestExecutor::new();
1030 let (monitor_svc_local, monitor_svc_remote) = create_proxy::<DeviceMonitorMarker>();
1031 let mut monitor_svc_stream = monitor_svc_remote.into_stream();
1032 let fut =
1033 do_phy(PhyCmd::SetPowerState { phy_id: 45, state: OnOffArg::On }, monitor_svc_local);
1034 let mut fut = pin!(fut);
1035
1036 assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
1037 assert_matches!(
1038 exec.run_until_stalled(&mut monitor_svc_stream.next()),
1039 Poll::Ready(Some(Ok(wlan_service::DeviceMonitorRequest::PowerUp {
1040 phy_id, responder,
1041 }))) => {
1042 assert_eq!(phy_id, 45);
1043 responder.send(Err(zx_status::Status::OK.into_raw())).expect("failed to send response");
1044 }
1045 );
1046 }
1047
1048 #[fuchsia::test]
1049 fn test_reset() {
1050 let mut exec = fasync::TestExecutor::new();
1051 let (monitor_svc_local, monitor_svc_remote) = create_proxy::<DeviceMonitorMarker>();
1052 let mut monitor_svc_stream = monitor_svc_remote.into_stream();
1053 let fut = do_phy(PhyCmd::Reset { phy_id: 45 }, monitor_svc_local);
1054 let mut fut = pin!(fut);
1055
1056 assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
1057 assert_matches!(
1058 exec.run_until_stalled(&mut monitor_svc_stream.next()),
1059 Poll::Ready(Some(Ok(wlan_service::DeviceMonitorRequest::Reset {
1060 phy_id, responder,
1061 }))) => {
1062 assert_eq!(phy_id, 45);
1063 responder.send(Err(zx_status::Status::OK.into_raw())).expect("failed to send response");
1064 }
1065 );
1066 }
1067
1068 #[fuchsia::test]
1069 fn test_get_power_state() {
1070 let mut exec = fasync::TestExecutor::new();
1071 let (monitor_svc_local, monitor_svc_remote) = create_proxy::<DeviceMonitorMarker>();
1072 let mut monitor_svc_stream = monitor_svc_remote.into_stream();
1073 let fut = do_phy(PhyCmd::GetPowerState { phy_id: 45 }, monitor_svc_local);
1074 let mut fut = pin!(fut);
1075
1076 assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
1077 assert_matches!(
1078 exec.run_until_stalled(&mut monitor_svc_stream.next()),
1079 Poll::Ready(Some(Ok(wlan_service::DeviceMonitorRequest::GetPowerState {
1080 phy_id, responder,
1081 }))) => {
1082 assert_eq!(phy_id, 45);
1083 responder.send(Ok(true)).expect("failed to send response");
1084 }
1085 );
1086
1087 assert_matches!(exec.run_until_stalled(&mut fut), Poll::Ready(Ok(())));
1088 }
1089
1090 #[fuchsia::test]
1091 fn test_set_power_save_mode() {
1092 let mut exec = fasync::TestExecutor::new();
1093 let (monitor_svc_local, monitor_svc_remote) = create_proxy::<DeviceMonitorMarker>();
1094 let mut monitor_svc_stream = monitor_svc_remote.into_stream();
1095 let fut = do_phy(
1096 PhyCmd::SetPowerSaveMode { phy_id: 45, mode: PsModeArg::PsModeBalanced },
1097 monitor_svc_local,
1098 );
1099 let mut fut = pin!(fut);
1100
1101 assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
1102 assert_matches!(
1103 exec.run_until_stalled(&mut monitor_svc_stream.next()),
1104 Poll::Ready(Some(Ok(wlan_service::DeviceMonitorRequest::SetPowerSaveMode {
1105 req, responder,
1106 }))) => {
1107 assert_eq!(req.phy_id, 45);
1108 assert_eq!(
1109 req.ps_mode,
1110 fidl_common::PowerSaveType::PsModeBalanced
1111 );
1112 responder.send(zx_status::Status::OK.into_raw()).expect("failed to send response");
1113 }
1114 );
1115 }
1116
1117 #[fuchsia::test]
1118 fn test_get_power_save_mode() {
1119 let mut exec = fasync::TestExecutor::new();
1120 let (monitor_svc_local, monitor_svc_remote) = create_proxy::<DeviceMonitorMarker>();
1121 let mut monitor_svc_stream = monitor_svc_remote.into_stream();
1122 let fut = do_phy(PhyCmd::GetPowerSaveMode { phy_id: 45 }, monitor_svc_local);
1123 let mut fut = pin!(fut);
1124
1125 assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
1126 assert_matches!(
1127 exec.run_until_stalled(&mut monitor_svc_stream.next()),
1128 Poll::Ready(Some(Ok(wlan_service::DeviceMonitorRequest::GetPowerSaveMode {
1129 phy_id, responder,
1130 }))) => {
1131 assert_eq!(phy_id, 45);
1132 responder
1133 .send(Ok(&fidl_fuchsia_wlan_device_service::GetPowerSaveModeResponse {
1134 ps_mode: fidl_common::PowerSaveType::PsModeBalanced,
1135 }))
1136 .expect("failed to send response");
1137 }
1138 );
1139
1140 assert_matches!(exec.run_until_stalled(&mut fut), Poll::Ready(Ok(())));
1141 }
1142
1143 #[fuchsia::test]
1144 fn test_generate_psk() {
1145 assert_eq!(
1146 generate_psk("12345678", "coolnet").unwrap(),
1147 "1ec9ee30fdff1961a9abd083f571464cc0fe27f62f9f59992bd39f8e625e9f52"
1148 );
1149 assert!(generate_psk("short", "coolnet").is_err());
1150 }
1151
1152 fn has_expected_cause(error: Error, message: &str) -> bool {
1153 error.chain().any(|cause| cause.to_string() == message)
1154 }
1155
1156 #[fuchsia::test]
1157 fn test_error_from_sme_raw_status() {
1158 let not_found = error_from_sme_raw_status(
1159 zx_status::Status::NOT_FOUND.into_raw(),
1160 WlanMacRole::Mesh,
1161 1,
1162 );
1163 let not_supported = error_from_sme_raw_status(
1164 zx_status::Status::NOT_SUPPORTED.into_raw(),
1165 WlanMacRole::Ap,
1166 2,
1167 );
1168 let internal_error = error_from_sme_raw_status(
1169 zx_status::Status::INTERNAL.into_raw(),
1170 WlanMacRole::Client,
1171 3,
1172 );
1173 let unrecognized_error = error_from_sme_raw_status(
1174 zx_status::Status::INTERRUPTED_RETRY.into_raw(),
1175 WlanMacRole::Mesh,
1176 4,
1177 );
1178
1179 assert!(has_expected_cause(not_found, "invalid interface id"));
1180 assert!(has_expected_cause(not_supported, "operation not supported on SME interface"));
1181 assert!(has_expected_cause(
1182 internal_error,
1183 "internal server error sending endpoint to the SME server future"
1184 ));
1185 assert!(has_expected_cause(
1186 unrecognized_error,
1187 "unrecognized error associated with SME interface"
1188 ));
1189 }
1190
1191 #[fuchsia::test]
1192 fn reject_connect_ssid_too_long() {
1193 let mut exec = fasync::TestExecutor::new();
1194 let (monitor_local, monitor_remote) = create_proxy::<DeviceMonitorMarker>();
1195 let mut monitor_stream = monitor_remote.into_stream();
1196 let cmd = opts::ClientConnectCmd {
1198 iface_id: 0,
1199 ssid: String::from_utf8(vec![65; 33]).unwrap(),
1200 bssid: None,
1201 password: None,
1202 psk: None,
1203 scan_type: opts::ScanTypeArg::Passive,
1204 };
1205
1206 let connect_fut = do_client_connect(cmd, monitor_local.clone());
1207 let mut connect_fut = pin!(connect_fut);
1208
1209 assert_matches!(exec.run_until_stalled(&mut connect_fut), Poll::Ready(Err(e)) => {
1210 assert_eq!(format!("{}", e), format!("{}", SsidError::Size(33)));
1211 });
1212 assert_matches!(exec.run_until_stalled(&mut monitor_stream.next()), Poll::Pending);
1214 }
1215
1216 #[fuchsia::test]
1217 fn test_wmm_status() {
1218 let mut exec = fasync::TestExecutor::new();
1219 let (monitor_local, monitor_remote) = create_proxy::<DeviceMonitorMarker>();
1220 let mut monitor_stream = monitor_remote.into_stream();
1221 let mut stdout = Vec::new();
1222 {
1223 let fut = do_client_wmm_status(
1224 ClientWmmStatusCmd { iface_id: 11 },
1225 monitor_local,
1226 &mut stdout,
1227 );
1228 let mut fut = pin!(fut);
1229
1230 assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
1231 let mut fake_sme_server_stream = assert_matches!(
1232 exec.run_until_stalled(&mut monitor_stream.next()),
1233 Poll::Ready(Some(Ok(wlan_service::DeviceMonitorRequest::GetClientSme {
1234 iface_id, sme_server, responder,
1235 }))) => {
1236 assert_eq!(iface_id, 11);
1237 responder.send(Ok(())).expect("failed to send GetClientSme response");
1238 sme_server.into_stream()
1239 }
1240 );
1241
1242 assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
1243 assert_matches!(
1244 exec.run_until_stalled(&mut fake_sme_server_stream.next()),
1245 Poll::Ready(Some(Ok(fidl_sme::ClientSmeRequest::WmmStatus { responder }))) => {
1246 let wmm_status_resp = fidl_internal::WmmStatusResponse {
1247 apsd: true,
1248 ac_be_params: fidl_internal::WmmAcParams {
1249 aifsn: 1,
1250 acm: false,
1251 ecw_min: 2,
1252 ecw_max: 3,
1253 txop_limit: 4,
1254 },
1255 ac_bk_params: fidl_internal::WmmAcParams {
1256 aifsn: 5,
1257 acm: false,
1258 ecw_min: 6,
1259 ecw_max: 7,
1260 txop_limit: 8,
1261 },
1262 ac_vi_params: fidl_internal::WmmAcParams {
1263 aifsn: 9,
1264 acm: true,
1265 ecw_min: 10,
1266 ecw_max: 11,
1267 txop_limit: 12,
1268 },
1269 ac_vo_params: fidl_internal::WmmAcParams {
1270 aifsn: 13,
1271 acm: true,
1272 ecw_min: 14,
1273 ecw_max: 15,
1274 txop_limit: 16,
1275 },
1276 };
1277 responder.send(Ok(&wmm_status_resp)).expect("failed to send WMM status response");
1278 }
1279 );
1280
1281 assert_matches!(exec.run_until_stalled(&mut fut), Poll::Ready(Ok(())));
1282 }
1283 assert_eq!(
1284 String::from_utf8(stdout).expect("expect valid UTF8"),
1285 "apsd=true\n\
1286 ac_be: aifsn=1 acm=false ecw_min=2 ecw_max=3 txop_limit=4\n\
1287 ac_bk: aifsn=5 acm=false ecw_min=6 ecw_max=7 txop_limit=8\n\
1288 ac_vi: aifsn=9 acm=true ecw_min=10 ecw_max=11 txop_limit=12\n\
1289 ac_vo: aifsn=13 acm=true ecw_min=14 ecw_max=15 txop_limit=16\n"
1290 );
1291 }
1292}