Skip to main content

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