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