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