wlan_common/
bss.rs

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