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