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