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