wlan_dev/
lib.rs

1// Copyright 2021 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use 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/// Context for negotiating an `Authentication` (security protocol and credentials).
37///
38/// This ephemeral type joins a BSS description with credential data to negotiate an
39/// `Authentication`. See the `TryFrom` implementation below.
40#[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
47/// Negotiates an `Authentication` from security information (given credentials and a BSS
48/// description). The security protocol is based on the protection information described by the BSS
49/// description. This is used to parse and validate the given credentials.
50///
51/// This is necessary, because `wlandev` communicates directly with SME, which requires more
52/// detailed information than the Policy layer.
53impl TryFrom<SecurityContext> for fidl_security::Authentication {
54    type Error = SecurityError;
55
56    fn try_from(context: SecurityContext) -> Result<Self, SecurityError> {
57        /// Interprets the given password and PSK both as WPA credentials and attempts to parse the
58        /// pair.
59        ///
60        /// Note that the given password can also represent a WEP key, so this function should only
61        /// be used in WPA contexts.
62        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            // Unsupported.
88            // TODO(https://fxbug.dev/42174395): Implement conversions for WPA Enterprise.
89            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            // Use WPA2 for transitional networks when a PSK is supplied.
130            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            // TODO(tkilbourn): add timeouts to prevent hanging commands
191            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                    // Write the first matching `BssDescription`. Any additional information is
392                    // ignored.
393                    if let Some(bss_info) = scan_result_list.into_iter().find(|scan_result| {
394                        // TODO(https://fxbug.dev/42164415): Until the error produced by
395                        // `ScanResult::try_from` includes some details about the scan result
396                        // which failed conversion, `scan_result` must be cloned for debug
397                        // logging if conversion fails.
398                        match ScanResult::try_from(scan_result.clone()) {
399                            Ok(scan_result) => {
400                                // Find a matching SSID and (if provided) BSSID
401                                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, // only used for metrics, select arbitrary value
459    };
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                    // TODO(https://fxbug.dev/42164415): Until the error produced by
671                    // ScanResult::TryFrom includes some details about the
672                    // scan result which failed conversion, scan_result must
673                    // be cloned for debug logging if conversion fails.
674                    |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
748/// Constructs a `Result<(), Error>` from a `zx::sys::zx_status_t` returned
749/// from one of the `get_client_sme` or `get_ap_sme`
750/// functions. In particular, when `zx_status::Status::from_raw(raw_status)` does
751/// not match `zx_status::Status::OK`, this function will attach the appropriate
752/// error message to the returned `Result`. When `zx_status::Status::from_raw(raw_status)`
753/// does match `zx_status::Status::OK`, this function returns `Ok()`.
754///
755/// If this function returns an `Err`, it includes both a cause and a context.
756/// The cause is a readable conversion of `raw_status` based on `station_mode`
757/// and `iface_id`. The context notes the failed operation and suggests the
758/// interface be checked for support of the given `station_mode`.
759fn 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        // SSID is one byte too long.
1191        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        // No connect request is sent to SME because the command is invalid and rejected.
1207        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}