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