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