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