Skip to main content

wlan_common/
bss.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 crate::channel::Channel;
6use crate::ie::owe_transition::{OweTransition, parse_owe_transition};
7use crate::ie::rsn::suite_filter;
8use crate::ie::wsc::{ProbeRespWsc, parse_probe_resp_wsc};
9use crate::ie::{self, IeType};
10use crate::mac::CapabilityInfo;
11use anyhow::format_err;
12use fidl_fuchsia_wlan_ieee80211 as fidl_ieee80211;
13use fidl_fuchsia_wlan_sme as fidl_sme;
14use ieee80211::{Bssid, MacAddrBytes, Ssid};
15use static_assertions::assert_eq_size;
16use std::cmp::Ordering;
17use std::collections::HashMap;
18use std::fmt;
19use std::hash::Hash;
20use std::ops::Range;
21use zerocopy::{IntoBytes, Ref};
22
23#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord)]
24pub enum Protection {
25    Unknown,
26    Open,
27    OpenOweTransition,
28    Owe,
29    Wep,
30    Wpa1,
31    Wpa1Wpa2PersonalTkipOnly,
32    Wpa2PersonalTkipOnly,
33    Wpa1Wpa2Personal,
34    Wpa2Personal,
35    Wpa2Wpa3Personal,
36    Wpa3Personal,
37    Wpa2Enterprise,
38    /// WPA3 Enterprise 192-bit mode. WPA3 spec specifies an optional 192-bit mode but says nothing
39    /// about a non 192-bit version. Thus, colloquially, it's likely that the term WPA3 Enterprise
40    /// will be used to refer to WPA3 Enterprise 192-bit mode.
41    Wpa3Enterprise,
42}
43
44impl From<Protection> for fidl_sme::Protection {
45    fn from(protection: Protection) -> fidl_sme::Protection {
46        match protection {
47            Protection::Unknown => fidl_sme::Protection::Unknown,
48            Protection::Open => fidl_sme::Protection::Open,
49            Protection::OpenOweTransition => fidl_sme::Protection::OpenOweTransition,
50            Protection::Owe => fidl_sme::Protection::Owe,
51            Protection::Wep => fidl_sme::Protection::Wep,
52            Protection::Wpa1 => fidl_sme::Protection::Wpa1,
53            Protection::Wpa1Wpa2PersonalTkipOnly => fidl_sme::Protection::Wpa1Wpa2PersonalTkipOnly,
54            Protection::Wpa2PersonalTkipOnly => fidl_sme::Protection::Wpa2PersonalTkipOnly,
55            Protection::Wpa1Wpa2Personal => fidl_sme::Protection::Wpa1Wpa2Personal,
56            Protection::Wpa2Personal => fidl_sme::Protection::Wpa2Personal,
57            Protection::Wpa2Wpa3Personal => fidl_sme::Protection::Wpa2Wpa3Personal,
58            Protection::Wpa3Personal => fidl_sme::Protection::Wpa3Personal,
59            Protection::Wpa2Enterprise => fidl_sme::Protection::Wpa2Enterprise,
60            Protection::Wpa3Enterprise => fidl_sme::Protection::Wpa3Enterprise,
61        }
62    }
63}
64
65impl From<fidl_sme::Protection> for Protection {
66    fn from(protection: fidl_sme::Protection) -> Self {
67        match protection {
68            fidl_sme::Protection::Unknown => Protection::Unknown,
69            fidl_sme::Protection::Open => Protection::Open,
70            fidl_sme::Protection::OpenOweTransition => Protection::OpenOweTransition,
71            fidl_sme::Protection::Owe => Protection::Owe,
72            fidl_sme::Protection::Wep => Protection::Wep,
73            fidl_sme::Protection::Wpa1 => Protection::Wpa1,
74            fidl_sme::Protection::Wpa1Wpa2PersonalTkipOnly => Protection::Wpa1Wpa2PersonalTkipOnly,
75            fidl_sme::Protection::Wpa2PersonalTkipOnly => Protection::Wpa2PersonalTkipOnly,
76            fidl_sme::Protection::Wpa1Wpa2Personal => Protection::Wpa1Wpa2Personal,
77            fidl_sme::Protection::Wpa2Personal => Protection::Wpa2Personal,
78            fidl_sme::Protection::Wpa2Wpa3Personal => Protection::Wpa2Wpa3Personal,
79            fidl_sme::Protection::Wpa3Personal => Protection::Wpa3Personal,
80            fidl_sme::Protection::Wpa2Enterprise => Protection::Wpa2Enterprise,
81            fidl_sme::Protection::Wpa3Enterprise => Protection::Wpa3Enterprise,
82        }
83    }
84}
85
86impl fmt::Display for Protection {
87    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
88        match self {
89            Protection::Unknown => write!(f, "{}", "Unknown"),
90            Protection::Open => write!(f, "{}", "Open"),
91            Protection::OpenOweTransition => write!(f, "{}", "Open OWE Transition"),
92            Protection::Owe => write!(f, "{}", "OWE"),
93            Protection::Wep => write!(f, "{}", "WEP"),
94            Protection::Wpa1 => write!(f, "{}", "WPA1"),
95            Protection::Wpa1Wpa2PersonalTkipOnly => write!(f, "{}", "WPA1/2 PSK TKIP"),
96            Protection::Wpa2PersonalTkipOnly => write!(f, "{}", "WPA2 PSK TKIP"),
97            Protection::Wpa1Wpa2Personal => write!(f, "{}", "WPA1/2 PSK"),
98            Protection::Wpa2Personal => write!(f, "{}", "WPA2 PSK"),
99            Protection::Wpa2Wpa3Personal => write!(f, "{}", "WPA2/3 PSK"),
100            Protection::Wpa3Personal => write!(f, "{}", "WPA3 PSK"),
101            Protection::Wpa2Enterprise => write!(f, "{}", "WPA2 802.1X"),
102            Protection::Wpa3Enterprise => write!(f, "{}", "WPA3 802.1X"),
103        }
104    }
105}
106
107#[derive(Clone, Debug, Eq, Hash, PartialEq)]
108pub enum Standard {
109    Dot11A,
110    Dot11B,
111    Dot11G,
112    Dot11N,
113    Dot11Ac,
114}
115
116#[derive(Debug, Clone, PartialEq)]
117pub struct BssDescription {
118    // *** Fields originally in fidl_common::BssDescription
119    pub ssid: Ssid,
120    pub bssid: Bssid,
121    pub bss_type: fidl_ieee80211::BssType,
122    pub beacon_period: u16,
123    pub capability_info: u16,
124    pub channel: Channel,
125    pub rssi_dbm: i8,
126    pub snr_db: i8,
127    // Private because the parsed information reference the IEs
128    ies: Vec<u8>,
129
130    // *** Fields parsed out of fidl_common::BssDescription IEs
131    // IEEE Std 802.11-2016 9.4.2.3
132    // in 0.5 Mbps, with MSB indicating basic rate. See Table 9-78 for 126, 127.
133    // The rates here may include both the basic rates and extended rates, which are not
134    // continuous slices, hence we cannot use `Range`.
135    rates: Vec<ie::SupportedRate>,
136    tim_range: Option<Range<usize>>,
137    country_range: Option<Range<usize>>,
138    rsne_range: Option<Range<usize>>,
139    ht_cap_range: Option<Range<usize>>,
140    ht_op_range: Option<Range<usize>>,
141    rm_enabled_cap_range: Option<Range<usize>>,
142    ext_cap_range: Option<Range<usize>>,
143    vht_cap_range: Option<Range<usize>>,
144    vht_op_range: Option<Range<usize>>,
145    rsnxe_range: Option<Range<usize>>,
146    owe_transition_range: Option<Range<usize>>,
147}
148
149impl BssDescription {
150    pub fn rates(&self) -> &[ie::SupportedRate] {
151        &self.rates[..]
152    }
153
154    pub fn dtim_period(&self) -> u8 {
155        self.tim_range
156            .as_ref()
157            .map(|range|
158            // Safe to unwrap because we made sure TIM is parseable in `from_fidl`
159            ie::parse_tim(&self.ies[range.clone()]).unwrap().header.dtim_period)
160            .unwrap_or(0)
161    }
162
163    pub fn country(&self) -> Option<&[u8]> {
164        self.country_range.as_ref().map(|range| &self.ies[range.clone()])
165    }
166
167    pub fn rsne(&self) -> Option<&[u8]> {
168        self.rsne_range.as_ref().map(|range| &self.ies[range.clone()])
169    }
170
171    pub fn ht_cap(&self) -> Option<Ref<&[u8], ie::HtCapabilities>> {
172        self.ht_cap_range.clone().map(|range| {
173            // Safe to unwrap because we already verified HT caps is parseable in `from_fidl`
174            ie::parse_ht_capabilities(&self.ies[range]).unwrap()
175        })
176    }
177
178    pub fn raw_ht_cap(&self) -> Option<fidl_ieee80211::HtCapabilities> {
179        type HtCapArray = [u8; fidl_ieee80211::HT_CAP_LEN as usize];
180        self.ht_cap().map(|ht_cap| {
181            assert_eq_size!(ie::HtCapabilities, HtCapArray);
182            let bytes: HtCapArray = ht_cap.as_bytes().try_into().unwrap();
183            fidl_ieee80211::HtCapabilities { bytes }
184        })
185    }
186
187    pub fn ht_op(&self) -> Option<Ref<&[u8], ie::HtOperation>> {
188        self.ht_op_range.clone().map(|range| {
189            // Safe to unwrap because we already verified HT op is parseable in `from_fidl`
190            ie::parse_ht_operation(&self.ies[range]).unwrap()
191        })
192    }
193
194    pub fn rm_enabled_cap(&self) -> Option<Ref<&[u8], ie::RmEnabledCapabilities>> {
195        self.rm_enabled_cap_range.clone().map(|range| {
196            // Safe to unwrap because we already verified RM enabled cap is parseable in `from_fidl`
197            ie::parse_rm_enabled_capabilities(&self.ies[range]).unwrap()
198        })
199    }
200
201    pub fn ext_cap(&self) -> Option<ie::ExtCapabilitiesView<&[u8]>> {
202        self.ext_cap_range.clone().map(|range| ie::parse_ext_capabilities(&self.ies[range]))
203    }
204
205    pub fn raw_ht_op(&self) -> Option<fidl_ieee80211::HtOperation> {
206        type HtOpArray = [u8; fidl_ieee80211::HT_OP_LEN as usize];
207        self.ht_op().map(|ht_op| {
208            assert_eq_size!(ie::HtOperation, HtOpArray);
209            let bytes: HtOpArray = ht_op.as_bytes().try_into().unwrap();
210            fidl_ieee80211::HtOperation { bytes }
211        })
212    }
213
214    pub fn vht_cap(&self) -> Option<Ref<&[u8], ie::VhtCapabilities>> {
215        self.vht_cap_range.clone().map(|range| {
216            // Safe to unwrap because we already verified VHT caps is parseable in `from_fidl`
217            ie::parse_vht_capabilities(&self.ies[range]).unwrap()
218        })
219    }
220
221    pub fn raw_vht_cap(&self) -> Option<fidl_ieee80211::VhtCapabilities> {
222        type VhtCapArray = [u8; fidl_ieee80211::VHT_CAP_LEN as usize];
223        self.vht_cap().map(|vht_cap| {
224            assert_eq_size!(ie::VhtCapabilities, VhtCapArray);
225            let bytes: VhtCapArray = vht_cap.as_bytes().try_into().unwrap();
226            fidl_ieee80211::VhtCapabilities { bytes }
227        })
228    }
229
230    pub fn vht_op(&self) -> Option<Ref<&[u8], ie::VhtOperation>> {
231        self.vht_op_range.clone().map(|range| {
232            // Safe to unwrap because we already verified VHT op is parseable in `from_fidl`
233            ie::parse_vht_operation(&self.ies[range]).unwrap()
234        })
235    }
236
237    pub fn raw_vht_op(&self) -> Option<fidl_ieee80211::VhtOperation> {
238        type VhtOpArray = [u8; fidl_ieee80211::VHT_OP_LEN as usize];
239        self.vht_op().map(|vht_op| {
240            assert_eq_size!(ie::VhtOperation, VhtOpArray);
241            let bytes: VhtOpArray = vht_op.as_bytes().try_into().unwrap();
242            fidl_ieee80211::VhtOperation { bytes }
243        })
244    }
245
246    pub fn rsnxe(&self) -> Option<ie::RsnxeView<&[u8]>> {
247        self.rsnxe_range.clone().map(|range| ie::parse_rsnxe(&self.ies[range]))
248    }
249
250    pub fn ies(&self) -> &[u8] {
251        &self.ies[..]
252    }
253
254    /// Return bool on whether BSS is protected.
255    pub fn is_protected(&self) -> bool {
256        self.protection() != Protection::Open && self.protection() != Protection::OpenOweTransition
257    }
258
259    /// Return bool on whether BSS has security type that would require exchanging EAPOL frames.
260    pub fn needs_eapol_exchange(&self) -> bool {
261        match self.protection() {
262            Protection::Unknown
263            | Protection::Open
264            | Protection::OpenOweTransition
265            | Protection::Wep => false,
266            _ => true,
267        }
268    }
269
270    /// Categorize BSS on what protection it supports.
271    pub fn protection(&self) -> Protection {
272        if !CapabilityInfo(self.capability_info).privacy() {
273            if self.owe_transition_range.is_some() {
274                return Protection::OpenOweTransition;
275            } else {
276                return Protection::Open;
277            }
278        }
279
280        let supports_wpa_1 = self
281            .wpa_ie()
282            .map(|wpa_ie| {
283                let rsne = ie::rsn::rsne::Rsne {
284                    group_data_cipher_suite: Some(wpa_ie.multicast_cipher),
285                    pairwise_cipher_suites: wpa_ie.unicast_cipher_list,
286                    akm_suites: wpa_ie.akm_list,
287                    ..Default::default()
288                };
289                suite_filter::WPA1_PERSONAL.is_satisfied(&rsne)
290            })
291            .unwrap_or(false);
292
293        let rsne = match self.rsne() {
294            Some(rsne) => match ie::rsn::rsne::from_bytes(rsne) {
295                Ok((_, rsne)) => rsne,
296                Err(_e) => {
297                    return Protection::Unknown;
298                }
299            },
300            None if self.find_wpa_ie().is_some() => {
301                if supports_wpa_1 {
302                    return Protection::Wpa1;
303                } else {
304                    return Protection::Unknown;
305                }
306            }
307            None => return Protection::Wep,
308        };
309
310        let rsn_caps = rsne.rsn_capabilities.as_ref().unwrap_or(&ie::rsn::rsne::RsnCapabilities(0));
311        let mfp_req = rsn_caps.mgmt_frame_protection_req();
312        let mfp_cap = rsn_caps.mgmt_frame_protection_cap();
313
314        if suite_filter::WPA3_PERSONAL.is_satisfied(&rsne) {
315            if suite_filter::WPA2_PERSONAL.is_satisfied(&rsne) {
316                if mfp_cap {
317                    return Protection::Wpa2Wpa3Personal;
318                }
319            } else if mfp_cap && mfp_req {
320                return Protection::Wpa3Personal;
321            }
322        }
323        if suite_filter::WPA2_PERSONAL.is_satisfied(&rsne) {
324            if supports_wpa_1 {
325                return Protection::Wpa1Wpa2Personal;
326            } else {
327                return Protection::Wpa2Personal;
328            }
329        }
330        if suite_filter::WPA2_PERSONAL_TKIP_ONLY.is_satisfied(&rsne) {
331            if supports_wpa_1 {
332                return Protection::Wpa1Wpa2PersonalTkipOnly;
333            } else {
334                return Protection::Wpa2PersonalTkipOnly;
335            }
336        }
337        if supports_wpa_1 {
338            return Protection::Wpa1;
339        }
340        if suite_filter::WPA3_ENTERPRISE_192_BIT.is_satisfied(&rsne) {
341            if mfp_cap && mfp_req {
342                return Protection::Wpa3Enterprise;
343            }
344        }
345        if suite_filter::WPA2_ENTERPRISE.is_satisfied(&rsne) {
346            return Protection::Wpa2Enterprise;
347        }
348        if suite_filter::OWE.is_satisfied(&rsne) {
349            return Protection::Owe;
350        }
351        Protection::Unknown
352    }
353
354    /// Get the latest WLAN standard that the BSS supports.
355    pub fn latest_standard(&self) -> Standard {
356        if self.vht_cap().is_some() && self.vht_op().is_some() {
357            Standard::Dot11Ac
358        } else if self.ht_cap().is_some() && self.ht_op().is_some() {
359            Standard::Dot11N
360        } else if self.channel.band == fidl_ieee80211::WlanBand::TwoGhz {
361            if self.rates.iter().any(|r| match r.rate() {
362                12 | 18 | 24 | 36 | 48 | 72 | 96 | 108 => true,
363                _ => false,
364            }) {
365                Standard::Dot11G
366            } else {
367                Standard::Dot11B
368            }
369        } else {
370            Standard::Dot11A
371        }
372    }
373
374    /// Search for vendor-specific Info Element for WPA. If found, return the body.
375    pub fn find_wpa_ie(&self) -> Option<&[u8]> {
376        ie::Reader::new(&self.ies[..])
377            .filter_map(|(id, ie)| match id {
378                ie::Id::VENDOR_SPECIFIC => match ie::parse_vendor_ie(ie) {
379                    Ok(ie::VendorIe::MsftLegacyWpa(body)) => Some(&body[..]),
380                    _ => None,
381                },
382                _ => None,
383            })
384            .next()
385    }
386
387    /// Search for WPA Info Element and parse it. If no WPA Info Element is found, or a WPA Info
388    /// Element is found but is not valid, return an error.
389    pub fn wpa_ie(&self) -> Result<ie::wpa::WpaIe, anyhow::Error> {
390        ie::parse_wpa_ie(self.find_wpa_ie().ok_or_else(|| format_err!("no wpa ie found"))?)
391            .map_err(|e| e.into())
392    }
393
394    /// Search for vendor-specific Info Element for WMM Parameter. If found, return the body.
395    pub fn find_wmm_param(&self) -> Option<&[u8]> {
396        ie::Reader::new(&self.ies[..])
397            .filter_map(|(id, ie)| match id {
398                ie::Id::VENDOR_SPECIFIC => match ie::parse_vendor_ie(ie) {
399                    Ok(ie::VendorIe::WmmParam(body)) => Some(&body[..]),
400                    _ => None,
401                },
402                _ => None,
403            })
404            .next()
405    }
406
407    /// Search for WMM Parameter Element and parse it. If no WMM Parameter Element is found,
408    /// return an error.
409    pub fn wmm_param(&self) -> Result<Ref<&[u8], ie::WmmParam>, anyhow::Error> {
410        ie::parse_wmm_param(
411            self.find_wmm_param().ok_or_else(|| format_err!("no wmm parameter found"))?,
412        )
413        .map_err(|e| e.into())
414    }
415
416    /// Search for the WiFi Simple Configuration Info Element. If found, return the body.
417    pub fn find_wsc_ie(&self) -> Option<&[u8]> {
418        ie::Reader::new(&self.ies[..])
419            .filter_map(|(id, ie)| match id {
420                ie::Id::VENDOR_SPECIFIC => match ie::parse_vendor_ie(ie) {
421                    Ok(ie::VendorIe::Wsc(body)) => Some(&body[..]),
422                    _ => None,
423                },
424                _ => None,
425            })
426            .next()
427    }
428
429    pub fn probe_resp_wsc(&self) -> Option<ProbeRespWsc> {
430        match self.find_wsc_ie() {
431            Some(ie) => match parse_probe_resp_wsc(ie) {
432                Ok(wsc) => Some(wsc),
433                // Parsing could fail because the WSC IE comes from a beacon, which does
434                // not contain all the information that a probe response WSC is expected
435                // to have. We don't have the information to distinguish between a beacon
436                // and a probe response, so we let this case fail silently.
437                Err(_) => None,
438            },
439            None => None,
440        }
441    }
442
443    pub fn owe_transition(&self) -> Option<OweTransition> {
444        self.owe_transition_range.clone().map(|range| {
445            // Safe to unwrap because we already verified OWE Transition is parseable in TryFrom
446            parse_owe_transition(&self.ies[range]).unwrap()
447        })
448    }
449
450    pub fn supports_uapsd(&self) -> bool {
451        let wmm_info = ie::Reader::new(&self.ies[..])
452            .filter_map(|(id, ie)| match id {
453                ie::Id::VENDOR_SPECIFIC => match ie::parse_vendor_ie(ie) {
454                    Ok(ie::VendorIe::WmmInfo(body)) => {
455                        ie::parse_wmm_info(body).map(|wmm_info| *wmm_info).ok()
456                    }
457                    Ok(ie::VendorIe::WmmParam(body)) => {
458                        ie::parse_wmm_param(body).map(|wmm_param| wmm_param.wmm_info).ok()
459                    }
460                    _ => None,
461                },
462                _ => None,
463            })
464            .next();
465        wmm_info.map(|wmm_info| wmm_info.ap_wmm_info().uapsd()).unwrap_or(false)
466    }
467
468    /// IEEE 802.11-2016 4.5.4.8
469    pub fn supports_ft(&self) -> bool {
470        ie::Reader::new(&self.ies[..]).any(|(id, _ie)| id == ie::Id::MOBILITY_DOMAIN)
471    }
472
473    /// Returns a simplified BssCandidacy which implements PartialOrd.
474    pub fn candidacy(&self) -> BssCandidacy {
475        let rssi_dbm = self.rssi_dbm;
476        match rssi_dbm {
477            // The value 0 is considered a marker for an invalid RSSI and is therefore
478            // transformed to the minimum RSSI value.
479            0 => BssCandidacy { protection: self.protection(), rssi_dbm: i8::MIN },
480            _ => BssCandidacy { protection: self.protection(), rssi_dbm },
481        }
482    }
483
484    /// Returns a string representation of the BssDescriptionExt. This representation
485    /// is not suitable for protecting the privacy of an SSID and BSSID.
486    pub fn to_non_obfuscated_string(&self) -> String {
487        format!(
488            "SSID: {}, BSSID: {}, Protection: {}, Pri Chan: {}, Rx dBm: {}",
489            self.ssid.to_string_not_redactable(),
490            self.bssid,
491            self.protection(),
492            self.channel.primary,
493            self.rssi_dbm,
494        )
495    }
496
497    pub fn is_open(&self) -> bool {
498        matches!(self.protection(), Protection::Open | Protection::OpenOweTransition)
499    }
500
501    pub fn has_owe_configured(&self) -> bool {
502        matches!(self.protection(), Protection::Owe)
503    }
504
505    pub fn has_wep_configured(&self) -> bool {
506        matches!(self.protection(), Protection::Wep)
507    }
508
509    pub fn has_wpa1_configured(&self) -> bool {
510        matches!(
511            self.protection(),
512            Protection::Wpa1 | Protection::Wpa1Wpa2PersonalTkipOnly | Protection::Wpa1Wpa2Personal
513        )
514    }
515
516    pub fn has_wpa2_personal_configured(&self) -> bool {
517        matches!(
518            self.protection(),
519            Protection::Wpa1Wpa2PersonalTkipOnly
520                | Protection::Wpa1Wpa2Personal
521                | Protection::Wpa2PersonalTkipOnly
522                | Protection::Wpa2Personal
523                | Protection::Wpa2Wpa3Personal
524        )
525    }
526
527    pub fn has_wpa3_personal_configured(&self) -> bool {
528        matches!(self.protection(), Protection::Wpa2Wpa3Personal | Protection::Wpa3Personal)
529    }
530}
531
532impl From<BssDescription> for fidl_ieee80211::BssDescription {
533    fn from(bss: BssDescription) -> fidl_ieee80211::BssDescription {
534        let (bandwidth, vht_secondary_80_channel_num) = bss.channel.bandwidth.to_fidl();
535        let vht_secondary_80_channel = fidl_ieee80211::ChannelNumber {
536            band: bss.channel.band,
537            number: vht_secondary_80_channel_num,
538        };
539        fidl_ieee80211::BssDescription {
540            bssid: bss.bssid.to_array(),
541            bss_type: bss.bss_type,
542            beacon_period: bss.beacon_period,
543            capability_info: bss.capability_info,
544            primary: bss.channel.into(),
545            bandwidth,
546            vht_secondary_80_channel: vht_secondary_80_channel,
547            rssi_dbm: bss.rssi_dbm,
548            snr_db: bss.snr_db,
549            ies: bss.ies,
550        }
551    }
552}
553
554impl fmt::Display for BssDescription {
555    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
556        write!(
557            f,
558            "SSID: {}, BSSID: {}, Protection: {}, Pri Chan: {}, Rx dBm: {}",
559            self.ssid,
560            self.bssid,
561            self.protection(),
562            self.channel.primary,
563            self.rssi_dbm,
564        )
565    }
566}
567// TODO(https://fxbug.dev/42164415): The error printed should include a minimal amount of information
568// about the BSS Description that could not be converted to aid debugging.
569impl TryFrom<fidl_ieee80211::BssDescription> for BssDescription {
570    type Error = anyhow::Error;
571
572    fn try_from(bss: fidl_ieee80211::BssDescription) -> Result<BssDescription, Self::Error> {
573        let mut ssid_range = None;
574        let mut rates = None;
575        let mut tim_range = None;
576        let mut country_range = None;
577        let mut rsne_range = None;
578        let mut ht_cap_range = None;
579        let mut ht_op_range = None;
580        let mut rm_enabled_cap_range = None;
581        let mut ext_cap_range = None;
582        let mut vht_cap_range = None;
583        let mut vht_op_range = None;
584        let mut rsnxe_range = None;
585        let mut owe_transition_range = None;
586
587        for (ie_type, range) in ie::IeSummaryIter::new(&bss.ies[..]) {
588            let body = &bss.ies[range.clone()];
589            match ie_type {
590                IeType::SSID => {
591                    ie::parse_ssid(body)?;
592                    ssid_range = Some(range);
593                }
594                IeType::SUPPORTED_RATES => {
595                    rates.get_or_insert(vec![]).extend(&*ie::parse_supported_rates(body)?);
596                }
597                IeType::EXTENDED_SUPPORTED_RATES => {
598                    rates.get_or_insert(vec![]).extend(&*ie::parse_extended_supported_rates(body)?);
599                }
600                IeType::TIM => {
601                    ie::parse_tim(body)?;
602                    tim_range = Some(range);
603                }
604                IeType::COUNTRY => country_range = Some(range),
605                // Decrement start of range by two to include the IE header.
606                IeType::RSNE => rsne_range = Some(range.start - 2..range.end),
607                IeType::HT_CAPABILITIES => {
608                    ie::parse_ht_capabilities(body)?;
609                    ht_cap_range = Some(range);
610                }
611                IeType::HT_OPERATION => {
612                    ie::parse_ht_operation(body)?;
613                    ht_op_range = Some(range);
614                }
615                IeType::RM_ENABLED_CAPABILITIES => {
616                    if let Ok(_) = ie::parse_rm_enabled_capabilities(body) {
617                        rm_enabled_cap_range = Some(range);
618                    }
619                }
620                IeType::EXT_CAPABILITIES => {
621                    // Parsing ExtCapabilities always succeeds, so no need to test parsing it here
622                    ext_cap_range = Some(range);
623                }
624                IeType::VHT_CAPABILITIES => {
625                    ie::parse_vht_capabilities(body)?;
626                    vht_cap_range = Some(range);
627                }
628                IeType::VHT_OPERATION => {
629                    ie::parse_vht_operation(body)?;
630                    vht_op_range = Some(range);
631                }
632                IeType::RSNXE => {
633                    rsnxe_range = Some(range);
634                }
635                IeType::OWE_TRANSITION => {
636                    if let Ok(_) = parse_owe_transition(body) {
637                        owe_transition_range = Some(range);
638                    }
639                }
640                _ => (),
641            }
642        }
643
644        let ssid_range = ssid_range.ok_or_else(|| format_err!("Missing SSID IE"))?;
645        let rates = rates.ok_or_else(|| format_err!("Missing rates IE"))?;
646
647        Ok(Self {
648            ssid: Ssid::from_bytes_unchecked(bss.ies[ssid_range].to_vec()),
649            bssid: Bssid::from(bss.bssid),
650            bss_type: bss.bss_type,
651            beacon_period: bss.beacon_period,
652            capability_info: bss.capability_info,
653            channel: crate::channel::Channel::from_fidl(
654                bss.primary,
655                bss.bandwidth,
656                bss.vht_secondary_80_channel,
657            )?,
658            rssi_dbm: bss.rssi_dbm,
659            snr_db: bss.snr_db,
660            ies: bss.ies,
661
662            rates,
663            tim_range,
664            country_range,
665            rsne_range,
666            ht_cap_range,
667            ht_op_range,
668            rm_enabled_cap_range,
669            ext_cap_range,
670            vht_cap_range,
671            vht_op_range,
672            rsnxe_range,
673            owe_transition_range,
674        })
675    }
676}
677
678/// The BssCandidacy type is used to rank fidl_common::BssDescription values. It is ordered
679/// first by Protection and then by Dbm.
680#[derive(Debug, Eq, PartialEq)]
681pub struct BssCandidacy {
682    protection: Protection,
683    rssi_dbm: i8,
684}
685
686impl PartialOrd for BssCandidacy {
687    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
688        Some(self.cmp(other))
689    }
690}
691
692impl Ord for BssCandidacy {
693    fn cmp(&self, other: &Self) -> Ordering {
694        self.protection.cmp(&other.protection).then(self.rssi_dbm.cmp(&other.rssi_dbm))
695    }
696}
697
698/// Given a list of BssDescription, categorize each one based on the latest PHY standard it
699/// supports and return a mapping from Standard to number of BSS.
700pub fn phy_standard_map(bss_list: &Vec<BssDescription>) -> HashMap<Standard, usize> {
701    info_map(bss_list, |bss| bss.latest_standard())
702}
703
704/// Given a list of BssDescription, return a mapping from channel to the number of BSS using
705/// that channel.
706pub fn channel_map(bss_list: &Vec<BssDescription>) -> HashMap<u8, usize> {
707    info_map(bss_list, |bss| bss.channel.primary)
708}
709
710fn info_map<F, T>(bss_list: &Vec<BssDescription>, f: F) -> HashMap<T, usize>
711where
712    T: Eq + Hash,
713    F: Fn(&BssDescription) -> T,
714{
715    let mut info_map: HashMap<T, usize> = HashMap::new();
716    for bss in bss_list {
717        *info_map.entry(f(&bss)).or_insert(0) += 1
718    }
719    info_map
720}
721
722#[cfg(test)]
723mod tests {
724    use super::*;
725    use crate::channel::Bandwidth;
726    use crate::fake_bss_description;
727    use crate::ie::IeType;
728    use crate::ie::fake_ies::{fake_owe_transition, fake_wmm_param};
729    use crate::test_utils::fake_frames::{
730        fake_unknown_rsne, fake_wmm_param_body, fake_wpa1_ie_body, fake_wpa2_mfpc_rsne,
731        fake_wpa2_mfpr_rsne, fake_wpa2_rsne, fake_wpa2_wpa3_mfpr_rsne, fake_wpa2_wpa3_no_mfp_rsne,
732        invalid_wpa3_enterprise_192_bit_rsne, invalid_wpa3_rsne,
733    };
734    use crate::test_utils::fake_stas::IesOverrides;
735    use assert_matches::assert_matches;
736    use test_case::test_case;
737
738    #[test_case(fake_bss_description!(
739        Wpa1Wpa2,
740        channel: Channel::new(36, Bandwidth::Cbw80P80{ vht_secondary_80_channel: 106 }, fidl_ieee80211::WlanBand::FiveGhz),
741        rssi_dbm: -20,
742        short_preamble: true,
743        ies_overrides: IesOverrides::new()
744            .set(IeType::DSSS_PARAM_SET, [136].to_vec())
745    ))]
746    #[test_case(fake_bss_description!(
747        Open,
748        channel: Channel::new(1, Bandwidth::Cbw20, fidl_ieee80211::WlanBand::TwoGhz),
749        beacon_period: 110,
750        short_preamble: true,
751        radio_measurement: true,
752        rates: vec![0x02, 0x04, 0x0c],
753    ))]
754    fn test_bss_lossless_conversion(bss: BssDescription) {
755        let fidl_bss = fidl_ieee80211::BssDescription::from(bss.clone());
756        assert_eq!(bss, BssDescription::try_from(fidl_bss.clone()).unwrap());
757        assert_eq!(
758            fidl_bss,
759            fidl_ieee80211::BssDescription::from(
760                BssDescription::try_from(fidl_bss.clone()).unwrap()
761            )
762        );
763    }
764
765    #[test]
766    fn test_known_protection() {
767        assert_eq!(Protection::Open, fake_bss_description!(Open).protection());
768        assert_eq!(
769            Protection::OpenOweTransition,
770            fake_bss_description!(OpenOweTransition).protection()
771        );
772        assert_eq!(Protection::Owe, fake_bss_description!(Owe).protection());
773        assert_eq!(Protection::Wep, fake_bss_description!(Wep).protection());
774        assert_eq!(Protection::Wpa1, fake_bss_description!(Wpa1).protection());
775        assert_eq!(Protection::Wpa1, fake_bss_description!(Wpa1Enhanced).protection());
776        assert_eq!(
777            Protection::Wpa1Wpa2PersonalTkipOnly,
778            fake_bss_description!(Wpa1Wpa2TkipOnly).protection()
779        );
780        assert_eq!(
781            Protection::Wpa2PersonalTkipOnly,
782            fake_bss_description!(Wpa2TkipOnly).protection()
783        );
784        assert_eq!(Protection::Wpa1Wpa2Personal, fake_bss_description!(Wpa1Wpa2).protection());
785        assert_eq!(Protection::Wpa2Personal, fake_bss_description!(Wpa2TkipCcmp).protection());
786        assert_eq!(Protection::Wpa2Personal, fake_bss_description!(Wpa2).protection());
787        assert_eq!(Protection::Wpa2Wpa3Personal, fake_bss_description!(Wpa2Wpa3).protection());
788        assert_eq!(Protection::Wpa3Personal, fake_bss_description!(Wpa3).protection());
789        assert_eq!(Protection::Wpa2Enterprise, fake_bss_description!(Wpa2Enterprise).protection());
790        assert_eq!(Protection::Wpa3Enterprise, fake_bss_description!(Wpa3Enterprise).protection());
791    }
792
793    #[test]
794    fn test_pmf_configs_supported() {
795        let bss = fake_bss_description!(Wpa2,
796            ies_overrides: IesOverrides::new()
797                .set(IeType::RSNE, fake_wpa2_mfpc_rsne()[2..].to_vec())
798        );
799        assert_eq!(Protection::Wpa2Personal, bss.protection());
800
801        let bss = fake_bss_description!(Wpa2,
802            ies_overrides: IesOverrides::new()
803                .set(IeType::RSNE, fake_wpa2_mfpr_rsne()[2..].to_vec())
804        );
805        assert_eq!(Protection::Wpa2Personal, bss.protection());
806
807        let bss = fake_bss_description!(Wpa2,
808            ies_overrides: IesOverrides::new()
809                .set(IeType::RSNE, fake_wpa2_wpa3_mfpr_rsne()[2..].to_vec())
810        );
811        assert_eq!(Protection::Wpa2Wpa3Personal, bss.protection());
812    }
813
814    #[test]
815    fn test_downgrade() {
816        // If Wpa3 doesn't use MFP, ignore it and use Wpa2 instead.
817        let bss = fake_bss_description!(Wpa2,
818            ies_overrides: IesOverrides::new()
819                .set(IeType::RSNE, fake_wpa2_wpa3_no_mfp_rsne()[2..].to_vec())
820        );
821        assert_eq!(Protection::Wpa2Personal, bss.protection());
822
823        // Downgrade to Wpa1 as well.
824        let bss = fake_bss_description!(Wpa1,
825            ies_overrides: IesOverrides::new()
826                .set(IeType::RSNE, invalid_wpa3_rsne()[2..].to_vec())
827        );
828        assert_eq!(Protection::Wpa1, bss.protection());
829    }
830
831    #[test]
832    fn test_unknown_protection() {
833        let bss = fake_bss_description!(Wpa2,
834            ies_overrides: IesOverrides::new()
835                .set(IeType::RSNE, fake_unknown_rsne()[2..].to_vec())
836        );
837        assert_eq!(Protection::Unknown, bss.protection());
838
839        let bss = fake_bss_description!(Wpa2,
840            ies_overrides: IesOverrides::new()
841                .set(IeType::RSNE, invalid_wpa3_rsne()[2..].to_vec())
842        );
843        assert_eq!(Protection::Unknown, bss.protection());
844
845        let bss = fake_bss_description!(Wpa2,
846            ies_overrides: IesOverrides::new()
847                .set(IeType::RSNE, invalid_wpa3_enterprise_192_bit_rsne()[2..].to_vec())
848        );
849        assert_eq!(Protection::Unknown, bss.protection());
850    }
851
852    #[test]
853    fn test_needs_eapol_exchange() {
854        assert!(fake_bss_description!(Owe).needs_eapol_exchange());
855        assert!(fake_bss_description!(Wpa1).needs_eapol_exchange());
856        assert!(fake_bss_description!(Wpa2).needs_eapol_exchange());
857
858        assert!(!fake_bss_description!(Open).needs_eapol_exchange());
859        assert!(!fake_bss_description!(OpenOweTransition).needs_eapol_exchange());
860        assert!(!fake_bss_description!(Wep).needs_eapol_exchange());
861    }
862
863    #[test]
864    fn test_rm_enabled_cap_ie() {
865        let bss = fake_bss_description!(Wpa2,
866            ies_overrides: IesOverrides::new()
867                .remove(IeType::RM_ENABLED_CAPABILITIES)
868        );
869        assert!(bss.rm_enabled_cap().is_none());
870
871        #[rustfmt::skip]
872        let rm_enabled_capabilities = vec![
873            0x03, // link measurement and neighbor report enabled
874            0x00, 0x00, 0x00, 0x00,
875        ];
876        let bss = fake_bss_description!(Wpa2,
877            ies_overrides: IesOverrides::new()
878                .remove(IeType::RM_ENABLED_CAPABILITIES)
879                .set(IeType::RM_ENABLED_CAPABILITIES, rm_enabled_capabilities.clone())
880        );
881        assert_matches!(bss.rm_enabled_cap(), Some(cap) => {
882            assert_eq!(cap.as_bytes(), &rm_enabled_capabilities[..]);
883        });
884    }
885
886    #[test]
887    fn test_ext_cap_ie() {
888        let bss = fake_bss_description!(Wpa2,
889            ies_overrides: IesOverrides::new()
890                .remove(IeType::EXT_CAPABILITIES)
891        );
892        assert!(bss.ext_cap().is_none());
893
894        #[rustfmt::skip]
895        let ext_capabilities = vec![
896            0x04, 0x00,
897            0x08, // BSS transition supported
898            0x00, 0x00, 0x00, 0x00, 0x40
899        ];
900        let bss = fake_bss_description!(Wpa2,
901            ies_overrides: IesOverrides::new()
902                .remove(IeType::EXT_CAPABILITIES)
903                .set(IeType::EXT_CAPABILITIES, ext_capabilities.clone())
904        );
905        let ext_cap = bss.ext_cap().expect("expect bss.ext_cap() to be Some");
906        assert_eq!(ext_cap.ext_caps_octet_1.map(|o| o.0), Some(0x04));
907        assert_eq!(ext_cap.ext_caps_octet_2.map(|o| o.0), Some(0x00));
908        assert_eq!(ext_cap.ext_caps_octet_3.map(|o| o.0), Some(0x08));
909        assert_eq!(ext_cap.remaining, &[0x00, 0x00, 0x00, 0x00, 0x40]);
910    }
911
912    #[test]
913    fn test_wpa_ie() {
914        let buf =
915            fake_bss_description!(Wpa1).wpa_ie().expect("failed to find WPA1 IE").into_bytes();
916        assert_eq!(&fake_wpa1_ie_body(false)[..], &buf[..]);
917        fake_bss_description!(Wpa2).wpa_ie().expect_err("found unexpected WPA1 IE");
918    }
919
920    #[test]
921    fn test_wmm_param() {
922        let bss = fake_bss_description!(Wpa2, qos: true, wmm_param: Some(fake_wmm_param()));
923        let wmm_param = bss.wmm_param().expect("failed to find wmm param");
924        assert_eq!(fake_wmm_param_body(), wmm_param.as_bytes());
925    }
926
927    #[test]
928    fn test_owe_transition() {
929        let bss = fake_bss_description!(OpenOweTransition);
930        let owe_transition = bss.owe_transition().expect("failed to find owe transition ie");
931        assert_eq!(owe_transition, fake_owe_transition());
932    }
933
934    #[test]
935    fn test_latest_standard_ac() {
936        let bss = fake_bss_description!(Open,
937            ies_overrides: IesOverrides::new()
938                .set(IeType::VHT_CAPABILITIES, vec![0; fidl_ieee80211::VHT_CAP_LEN as usize])
939                .set(IeType::VHT_OPERATION, vec![0; fidl_ieee80211::VHT_OP_LEN as usize]),
940        );
941        assert_eq!(Standard::Dot11Ac, bss.latest_standard());
942    }
943
944    #[test]
945    fn test_latest_standard_n() {
946        let bss = fake_bss_description!(Open,
947            ies_overrides: IesOverrides::new()
948                .set(IeType::HT_CAPABILITIES, vec![0; fidl_ieee80211::HT_CAP_LEN as usize])
949                .set(IeType::HT_OPERATION, vec![0; fidl_ieee80211::HT_OP_LEN as usize])
950                .remove(IeType::VHT_CAPABILITIES)
951                .remove(IeType::VHT_OPERATION),
952        );
953        assert_eq!(Standard::Dot11N, bss.latest_standard());
954    }
955
956    #[test]
957    fn test_latest_standard_g() {
958        let bss = fake_bss_description!(Open,
959            channel: Channel::new(1, Bandwidth::Cbw20, fidl_ieee80211::WlanBand::TwoGhz),
960            rates: vec![12],
961            ies_overrides: IesOverrides::new()
962                .remove(IeType::HT_CAPABILITIES)
963                .remove(IeType::HT_OPERATION)
964                .remove(IeType::VHT_CAPABILITIES)
965                .remove(IeType::VHT_OPERATION),
966        );
967        assert_eq!(Standard::Dot11G, bss.latest_standard());
968    }
969
970    #[test]
971    fn test_latest_standard_b() {
972        let bss = fake_bss_description!(Open,
973            channel: Channel::new(1, Bandwidth::Cbw20, fidl_ieee80211::WlanBand::TwoGhz),
974            rates: vec![2],
975            ies_overrides: IesOverrides::new()
976                .remove(IeType::HT_CAPABILITIES)
977                .remove(IeType::HT_OPERATION)
978                .remove(IeType::VHT_CAPABILITIES)
979                .remove(IeType::VHT_OPERATION),
980        );
981        assert_eq!(Standard::Dot11B, bss.latest_standard());
982    }
983
984    #[test]
985    fn test_latest_standard_b_with_basic() {
986        let bss = fake_bss_description!(Open,
987            channel: Channel::new(1, Bandwidth::Cbw20, fidl_ieee80211::WlanBand::TwoGhz),
988            rates: vec![ie::SupportedRate(2).with_basic(true).0],
989            ies_overrides: IesOverrides::new()
990                .remove(IeType::HT_CAPABILITIES)
991                .remove(IeType::HT_OPERATION)
992                .remove(IeType::VHT_CAPABILITIES)
993                .remove(IeType::VHT_OPERATION),
994        );
995        assert_eq!(Standard::Dot11B, bss.latest_standard());
996    }
997
998    #[test]
999    fn test_latest_standard_a() {
1000        let bss = fake_bss_description!(Open,
1001            channel: Channel::new(36, Bandwidth::Cbw20, fidl_ieee80211::WlanBand::FiveGhz),
1002            rates: vec![48],
1003            ies_overrides: IesOverrides::new()
1004                .remove(IeType::HT_CAPABILITIES)
1005                .remove(IeType::HT_OPERATION)
1006                .remove(IeType::VHT_CAPABILITIES)
1007                .remove(IeType::VHT_OPERATION),
1008        );
1009        assert_eq!(Standard::Dot11A, bss.latest_standard());
1010    }
1011
1012    #[test]
1013    fn test_supports_uapsd() {
1014        let bss = fake_bss_description!(Wpa2,
1015            ies_overrides: IesOverrides::new()
1016                .remove(IeType::WMM_INFO)
1017                .remove(IeType::WMM_PARAM)
1018        );
1019        assert!(!bss.supports_uapsd());
1020
1021        let mut wmm_info = vec![0x80]; // U-APSD enabled
1022        let bss = fake_bss_description!(Wpa2,
1023            ies_overrides: IesOverrides::new()
1024                .remove(IeType::WMM_INFO)
1025                .remove(IeType::WMM_PARAM)
1026                .set(IeType::WMM_INFO, wmm_info.clone())
1027        );
1028        assert!(bss.supports_uapsd());
1029
1030        wmm_info = vec![0x00]; // U-APSD not enabled
1031        let bss = fake_bss_description!(Wpa2,
1032            ies_overrides: IesOverrides::new()
1033                .remove(IeType::WMM_INFO)
1034                .remove(IeType::WMM_PARAM)
1035                .set(IeType::WMM_INFO, wmm_info)
1036        );
1037        assert!(!bss.supports_uapsd());
1038
1039        #[rustfmt::skip]
1040        let mut wmm_param = vec![
1041            0x80, // U-APSD enabled
1042            0x00, // reserved
1043            0x03, 0xa4, 0x00, 0x00, // AC_BE parameters
1044            0x27, 0xa4, 0x00, 0x00, // AC_BK parameters
1045            0x42, 0x43, 0x5e, 0x00, // AC_VI parameters
1046            0x62, 0x32, 0x2f, 0x00, // AC_VO parameters
1047        ];
1048        let bss = fake_bss_description!(Wpa2,
1049            ies_overrides: IesOverrides::new()
1050                .remove(IeType::WMM_INFO)
1051                .remove(IeType::WMM_PARAM)
1052                .set(IeType::WMM_PARAM, wmm_param.clone())
1053        );
1054        assert!(bss.supports_uapsd());
1055
1056        wmm_param[0] = 0x00; // U-APSD not enabled
1057        let bss = fake_bss_description!(Wpa2,
1058            ies_overrides: IesOverrides::new()
1059                .remove(IeType::WMM_INFO)
1060                .remove(IeType::WMM_PARAM)
1061                .set(IeType::WMM_PARAM, wmm_param)
1062        );
1063        assert!(!bss.supports_uapsd());
1064    }
1065
1066    #[test]
1067    fn test_supports_ft() {
1068        let bss = fake_bss_description!(Wpa2,
1069            ies_overrides: IesOverrides::new()
1070                .remove(IeType::MOBILITY_DOMAIN)
1071        );
1072        assert!(!bss.supports_ft());
1073
1074        let bss = fake_bss_description!(Wpa2,
1075            ies_overrides: IesOverrides::new()
1076                .remove(IeType::MOBILITY_DOMAIN)
1077                // We only check that the IE exists, so just set the content to bytes 0's.
1078                .set(IeType::MOBILITY_DOMAIN, vec![0x00; 3])
1079        );
1080        assert!(bss.supports_ft());
1081    }
1082
1083    #[test]
1084    fn test_candidacy() {
1085        let bss_candidacy = fake_bss_description!(Wpa2, rssi_dbm: -10).candidacy();
1086        assert_eq!(
1087            bss_candidacy,
1088            BssCandidacy { protection: Protection::Wpa2Personal, rssi_dbm: -10 }
1089        );
1090
1091        let bss_candidacy = fake_bss_description!(Open, rssi_dbm: -10).candidacy();
1092        assert_eq!(bss_candidacy, BssCandidacy { protection: Protection::Open, rssi_dbm: -10 });
1093
1094        let bss_candidacy = fake_bss_description!(Wpa2, rssi_dbm: -20).candidacy();
1095        assert_eq!(
1096            bss_candidacy,
1097            BssCandidacy { protection: Protection::Wpa2Personal, rssi_dbm: -20 }
1098        );
1099
1100        let bss_candidacy = fake_bss_description!(Wpa2, rssi_dbm: 0).candidacy();
1101        assert_eq!(
1102            bss_candidacy,
1103            BssCandidacy { protection: Protection::Wpa2Personal, rssi_dbm: i8::MIN }
1104        );
1105    }
1106
1107    fn assert_bss_comparison(worse: &BssDescription, better: &BssDescription) {
1108        assert_eq!(Ordering::Less, worse.candidacy().cmp(&better.candidacy()));
1109        assert_eq!(Ordering::Greater, better.candidacy().cmp(&worse.candidacy()));
1110    }
1111
1112    #[test]
1113    fn test_bss_comparison() {
1114        //  Two BSSDescription values with the same protection and RSSI are equivalent.
1115        assert_eq!(
1116            Ordering::Equal,
1117            fake_bss_description!(Wpa2, rssi_dbm: -10)
1118                .candidacy()
1119                .cmp(&fake_bss_description!(Wpa2, rssi_dbm: -10).candidacy())
1120        );
1121
1122        // Higher security is better.
1123        assert_bss_comparison(
1124            &fake_bss_description!(Wpa1, rssi_dbm: -10),
1125            &fake_bss_description!(Wpa2, rssi_dbm: -50),
1126        );
1127        assert_bss_comparison(
1128            &fake_bss_description!(Open, rssi_dbm: -10),
1129            &fake_bss_description!(Wpa2, rssi_dbm: -50),
1130        );
1131        // Higher RSSI is better if security is equivalent.
1132        assert_bss_comparison(
1133            &fake_bss_description!(Wpa2, rssi_dbm: -50),
1134            &fake_bss_description!(Wpa2, rssi_dbm: -10),
1135        );
1136        // Having an RSSI measurement is always better than not having any measurement
1137        assert_bss_comparison(
1138            &fake_bss_description!(Wpa2, rssi_dbm: 0),
1139            &fake_bss_description!(Wpa2, rssi_dbm: -100),
1140        );
1141    }
1142
1143    #[test]
1144    fn test_bss_ie_fields() {
1145        #[rustfmt::skip]
1146        let ht_cap = vec![
1147            0xef, 0x09, // HT Capabilities Info
1148            0x1b, // A-MPDU Parameters: 0x1b
1149            0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, // MCS Set
1150            0x00, 0x00, // HT Extended Capabilities
1151            0x00, 0x00, 0x00, 0x00, // Transmit Beamforming Capabilities
1152            0x00
1153        ];
1154        #[rustfmt::skip]
1155        let ht_op = vec![
1156            0x9d, // Primary Channel: 157
1157            0x0d, // HT Info Subset - secondary channel above, any channel width, RIFS permitted
1158            0x00, 0x00, 0x00, 0x00, // HT Info Subsets
1159            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Basic MCS Set
1160        ];
1161        #[rustfmt::skip]
1162        let vht_cap = vec![
1163            0xb2, 0x01, 0x80, 0x33, // VHT Capabilities Info
1164            0xea, 0xff, 0x00, 0x00, 0xea, 0xff, 0x00, 0x00, // VHT Supported MCS Set
1165        ];
1166        let vht_op = vec![0x01, 0x9b, 0x00, 0xfc, 0xff];
1167        let rsnxe = vec![0b00100001];
1168
1169        let bss = fake_bss_description!(Wpa2,
1170            ies_overrides: IesOverrides::new()
1171                .set(IeType::SSID, b"ssidie".to_vec())
1172                .set(IeType::SUPPORTED_RATES, vec![0x81, 0x82, 0x83])
1173                .set(IeType::EXTENDED_SUPPORTED_RATES, vec![4, 5, 6])
1174                .set(IeType::COUNTRY, vec![1, 2, 3])
1175                .set(IeType::HT_CAPABILITIES, ht_cap.clone())
1176                .set(IeType::HT_OPERATION, ht_op.clone())
1177                .set(IeType::VHT_CAPABILITIES, vht_cap.clone())
1178                .set(IeType::VHT_OPERATION, vht_op.clone())
1179                .set(IeType::RSNXE, rsnxe.clone())
1180        );
1181        assert_eq!(bss.ssid, Ssid::try_from("ssidie").unwrap());
1182        assert_eq!(
1183            bss.rates(),
1184            &[
1185                ie::SupportedRate(0x81),
1186                ie::SupportedRate(0x82),
1187                ie::SupportedRate(0x83),
1188                ie::SupportedRate(4),
1189                ie::SupportedRate(5),
1190                ie::SupportedRate(6)
1191            ]
1192        );
1193        assert_eq!(bss.country(), Some(&[1, 2, 3][..]));
1194        assert_eq!(bss.rsne(), Some(&fake_wpa2_rsne()[..]));
1195        assert_matches!(bss.ht_cap(), Some(capability_info) => {
1196            assert_eq!(Ref::bytes(&capability_info), &ht_cap[..]);
1197        });
1198        assert_eq!(
1199            bss.raw_ht_cap().map(|capability_info| capability_info.bytes.to_vec()),
1200            Some(ht_cap)
1201        );
1202        assert_matches!(bss.ht_op(), Some(op) => {
1203            assert_eq!(Ref::bytes(&op), &ht_op[..]);
1204        });
1205        assert_eq!(bss.raw_ht_op().map(|op| op.bytes.to_vec()), Some(ht_op));
1206        assert_matches!(bss.vht_cap(), Some(capability_info) => {
1207            assert_eq!(Ref::bytes(&capability_info), &vht_cap[..]);
1208        });
1209        assert_eq!(
1210            bss.raw_vht_cap().map(|capability_info| capability_info.bytes.to_vec()),
1211            Some(vht_cap)
1212        );
1213        assert_matches!(bss.vht_op(), Some(op) => {
1214            assert_eq!(Ref::bytes(&op), &vht_op[..]);
1215        });
1216        assert_eq!(bss.raw_vht_op().map(|op| op.bytes.to_vec()), Some(vht_op));
1217        assert_matches!(bss.rsnxe(), Some(r) => {
1218            assert_eq!(Ref::bytes(&r.rsnxe_octet_1.expect("no octet 1")), &rsnxe[..]);
1219        });
1220    }
1221
1222    #[test]
1223    fn test_protection_conversions() {
1224        assert_eq!(
1225            Protection::Unknown,
1226            Protection::from(fidl_sme::Protection::from(Protection::Unknown))
1227        );
1228        assert_eq!(
1229            Protection::Open,
1230            Protection::from(fidl_sme::Protection::from(Protection::Open))
1231        );
1232        assert_eq!(
1233            Protection::OpenOweTransition,
1234            Protection::from(fidl_sme::Protection::from(Protection::OpenOweTransition))
1235        );
1236        assert_eq!(Protection::Owe, Protection::from(fidl_sme::Protection::from(Protection::Owe)));
1237        assert_eq!(Protection::Wep, Protection::from(fidl_sme::Protection::from(Protection::Wep)));
1238        assert_eq!(
1239            Protection::Wpa1,
1240            Protection::from(fidl_sme::Protection::from(Protection::Wpa1))
1241        );
1242        assert_eq!(
1243            Protection::Wpa1Wpa2PersonalTkipOnly,
1244            Protection::from(fidl_sme::Protection::from(Protection::Wpa1Wpa2PersonalTkipOnly))
1245        );
1246        assert_eq!(
1247            Protection::Wpa2PersonalTkipOnly,
1248            Protection::from(fidl_sme::Protection::from(Protection::Wpa2PersonalTkipOnly))
1249        );
1250        assert_eq!(
1251            Protection::Wpa1Wpa2Personal,
1252            Protection::from(fidl_sme::Protection::from(Protection::Wpa1Wpa2Personal))
1253        );
1254        assert_eq!(
1255            Protection::Wpa2Personal,
1256            Protection::from(fidl_sme::Protection::from(Protection::Wpa2Personal))
1257        );
1258        assert_eq!(
1259            Protection::Wpa2Wpa3Personal,
1260            Protection::from(fidl_sme::Protection::from(Protection::Wpa2Wpa3Personal))
1261        );
1262        assert_eq!(
1263            Protection::Wpa3Personal,
1264            Protection::from(fidl_sme::Protection::from(Protection::Wpa3Personal))
1265        );
1266        assert_eq!(
1267            Protection::Wpa2Enterprise,
1268            Protection::from(fidl_sme::Protection::from(Protection::Wpa2Enterprise))
1269        );
1270        assert_eq!(
1271            Protection::Wpa3Enterprise,
1272            Protection::from(fidl_sme::Protection::from(Protection::Wpa3Enterprise))
1273        );
1274    }
1275}