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