wlan_common/test_utils/
fake_stas.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::{Cbw, Channel};
6use crate::ie::fake_ies::{fake_owe_transition_ie, fake_wmm_param};
7use crate::ie::{self, IeType, write_rsnxe, write_wmm_param};
8use crate::mac;
9use crate::test_utils::fake_frames::{
10    fake_eap_rsne, fake_owe_rsne, fake_wpa1_ie, fake_wpa2_enterprise_rsne, fake_wpa2_rsne,
11    fake_wpa2_tkip_ccmp_rsne, fake_wpa2_tkip_only_rsne, fake_wpa2_wpa3_rsne,
12    fake_wpa3_enterprise_192_bit_rsne, fake_wpa3_rsne, fake_wpa3_transition_rsne,
13};
14use anyhow::Context;
15use ieee80211::Ssid;
16use num_derive::FromPrimitive;
17use num_traits::FromPrimitive;
18use rand::Rng;
19use rand::distr::{Distribution, StandardUniform};
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#[rustfmt::skip]
26const DEFAULT_MOCK_IES: &'static [u8] = &[
27    // DS parameter set: channel 140
28    0x03, 0x01, 0x8c,
29    // TIM - DTIM count: 0, DTIM period: 1, PVB: 2
30    0x05, 0x04, 0x00, 0x01, 0x00, 0x02,
31    // Country info
32    0x07, 0x10, 0x55, 0x53, 0x20, // US, Any environment
33    0x24, 0x04, 0x24, // 1st channel: 36, # channels: 4, maximum tx power: 36 dBm
34    0x34, 0x04, 0x1e, // 1st channel: 52, # channels: 4, maximum tx power: 30 dBm
35    0x64, 0x0c, 0x1e, // 1st channel: 100, # channels: 12, maximum tx power: 30 dBm
36    0x95, 0x05, 0x24, // 1st channel: 149, # channels: 5, maximum tx power: 36 dBm
37    0x00, // padding
38    // Power constraint: 0
39    0x20, 0x01, 0x00,
40    // TPC Report Transmit Power: 9, Link Margin: 0
41    0x23, 0x02, 0x09, 0x00,
42    // HT Capabilities
43    0x2d, 0x1a, 0xef, 0x09, // HT capabilities info
44    0x17, // A-MPDU parameters
45    0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
46    0x00, // MCS set
47    0x00, 0x00, // HT extended capabilities
48    0x00, 0x00, 0x00, 0x00, // Transmit beamforming
49    0x00, // Antenna selection capabilities
50    // HT Operation
51    0x3d, 0x16, 0x8c, // Primary channel: 140
52    0x0d, // HT info subset - secondary channel above, any channel width, RIFS permitted
53    0x16, 0x00, 0x00, 0x00, // HT info subsets
54    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
55    0x00, // Basic MCS set
56    // Extended Capabilities: extended channel switching, BSS transition, operating mode notification
57    0x7f, 0x08, 0x04, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x40,
58    // VHT Capabilities
59    0xbf, 0x0c, 0x91, 0x59, 0x82, 0x0f, // VHT capabilities info
60    0xea, 0xff, 0x00, 0x00, 0xea, 0xff, 0x00, 0x00, // VHT supported MCS set
61    // VHT Operation
62    0xc0, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00,
63    // VHT Tx Power Envelope
64    0xc3, 0x03, 0x01, 0x24, 0x24,
65    // Aruba, Hewlett Packard vendor-specific IE
66    0xdd, 0x07, 0x00, 0x0b, 0x86, 0x01, 0x04, 0x08, 0x09,
67];
68
69pub struct BssDescriptionCreator {
70    // *** Fields already in fidl_common::BssDescription
71    pub bssid: [u8; 6],
72    pub bss_type: fidl_common::BssType,
73    pub beacon_period: u16,
74    pub channel: Channel,
75    pub rssi_dbm: i8,
76    pub snr_db: i8,
77
78    // *** Custom arguments
79    pub protection_cfg: FakeProtectionCfg,
80    pub ssid: Ssid,
81    pub rates: Vec<u8>,
82    pub wmm_param: Option<ie::WmmParam>,
83    pub sae_hash_to_element: bool,
84
85    // *** Modifiable capability_info bits
86    // The privacy, ess, and ibss bits are reserved for the
87    // macro to set since they are implied by protection_cfg
88    // and bss_type.
89    pub cf_pollable: bool,
90    pub cf_poll_req: bool,
91    pub short_preamble: bool,
92    pub spectrum_mgmt: bool,
93    pub qos: bool,
94    pub short_slot_time: bool,
95    pub apsd: bool,
96    pub radio_measurement: bool,
97    pub delayed_block_ack: bool,
98    pub immediate_block_ack: bool,
99
100    pub ies_overrides: IesOverrides,
101}
102
103impl BssDescriptionCreator {
104    pub fn create_bss_description(self) -> Result<fidl_common::BssDescription, anyhow::Error> {
105        let mut ies_updater = ie::IesUpdater::new(DEFAULT_MOCK_IES.to_vec());
106        ies_updater.set(IeType::SSID, &self.ssid[..]).context("set SSID")?;
107
108        let rates_writer = ie::RatesWriter::try_new(&self.rates[..]).context("set rates")?;
109        let mut rates_buf = vec![];
110        rates_writer.write_supported_rates(&mut rates_buf);
111        ies_updater.set_raw(&rates_buf[..]).context("set rates")?;
112
113        let mut ext_rates_buf = vec![];
114        rates_writer.write_extended_supported_rates(&mut ext_rates_buf);
115        ies_updater.set_raw(&ext_rates_buf[..]).context("set extended rates")?;
116
117        if let Some(rsne) = derive_rsne(self.protection_cfg) {
118            ies_updater.set_raw(&rsne[..]).context("set RSNE")?;
119        }
120        if self.sae_hash_to_element {
121            let mut octet_1 = ie::RsnxeOctet1(0);
122            octet_1.set_sae_hash_to_element(true);
123            let mut rsnxe = vec![];
124            write_rsnxe(&mut rsnxe, octet_1).context("Failed to write RSNXE to IE buffer")?;
125            ies_updater.set_raw(&rsnxe[..]).context("set RSNXE")?;
126        }
127        if let Some(wpa1_vendor_ie) = derive_wpa1_vendor_ies(self.protection_cfg) {
128            ies_updater.set_raw(&wpa1_vendor_ie[..]).context("set WPA1 vendor IE")?;
129        }
130        if let Some(owe_transition_ie) = derive_owe_transition_ie(self.protection_cfg) {
131            ies_updater.set_raw(&owe_transition_ie[..]).context("set OWE Transition IE")?;
132        }
133
134        if let Some(wmm_param) = self.wmm_param {
135            let mut wmm_param_vendor_ie = vec![];
136            write_wmm_param(&mut wmm_param_vendor_ie, &wmm_param)
137                .context("failed to write WmmParam to vendor IE buffer")?;
138            ies_updater.set_raw(&wmm_param_vendor_ie[..]).context("set WMM parameter IE")?;
139        }
140
141        let capability_info = mac::CapabilityInfo(0)
142            .with_cf_pollable(self.cf_pollable)
143            .with_cf_poll_req(self.cf_poll_req)
144            .with_short_preamble(self.short_preamble)
145            .with_spectrum_mgmt(self.spectrum_mgmt)
146            .with_qos(self.qos)
147            .with_short_slot_time(self.short_slot_time)
148            .with_apsd(self.apsd)
149            .with_radio_measurement(self.radio_measurement)
150            .with_delayed_block_ack(self.delayed_block_ack)
151            .with_immediate_block_ack(self.immediate_block_ack);
152
153        // Some values of capability_info are not permitted to be set by
154        // the macro since otherwise the BssDescription will be trivially invalid.
155        let capability_info = match self.protection_cfg {
156            FakeProtectionCfg::Open | FakeProtectionCfg::OpenOweTransition => {
157                capability_info.with_privacy(false)
158            }
159            _ => capability_info.with_privacy(true),
160        };
161        let capability_info = match self.bss_type {
162            fidl_common::BssType::Infrastructure => capability_info.with_ess(true).with_ibss(false),
163            _ => panic!("{:?} is not supported", self.bss_type),
164        };
165        let capability_info = capability_info.0;
166
167        for ovr in self.ies_overrides.overrides {
168            match ovr {
169                IeOverride::Remove(ie_type) => ies_updater.remove(&ie_type),
170                IeOverride::Set(ie_type, bytes) => {
171                    ies_updater
172                        .set(ie_type, &bytes[..])
173                        .with_context(|| format!("set IE type: {:?}", ie_type))?;
174                }
175                IeOverride::SetRaw(bytes) => {
176                    ies_updater.set_raw(&bytes[..]).context("set raw IE")?;
177                }
178            }
179        }
180
181        Ok(fidl_common::BssDescription {
182            bssid: self.bssid,
183            bss_type: self.bss_type,
184            beacon_period: self.beacon_period,
185            capability_info,
186            ies: ies_updater.finalize(),
187            channel: self.channel.into(),
188            rssi_dbm: self.rssi_dbm,
189            snr_db: self.snr_db,
190        })
191    }
192}
193
194pub struct IesOverrides {
195    overrides: Vec<IeOverride>,
196}
197
198impl IesOverrides {
199    pub fn new() -> Self {
200        Self { overrides: vec![] }
201    }
202
203    pub fn remove(mut self, ie_type: IeType) -> Self {
204        self.overrides.push(IeOverride::Remove(ie_type));
205        self
206    }
207
208    pub fn set(mut self, ie_type: IeType, bytes: Vec<u8>) -> Self {
209        self.overrides.push(IeOverride::Set(ie_type, bytes));
210        self
211    }
212
213    pub fn set_raw(mut self, bytes: Vec<u8>) -> Self {
214        self.overrides.push(IeOverride::SetRaw(bytes));
215        self
216    }
217}
218
219enum IeOverride {
220    Remove(IeType),
221    Set(IeType, Vec<u8>),
222    SetRaw(Vec<u8>),
223}
224
225const LAST_FAKE_PROTECTION_CFG_VALUE: isize = 16;
226
227#[derive(Debug, FromPrimitive, Copy, Clone, PartialEq)]
228pub enum FakeProtectionCfg {
229    Open = 0,
230    Owe,
231    OpenOweTransition,
232    Wep,
233    Wpa1,
234    Wpa1Enhanced,
235    Wpa1Wpa2TkipOnly,
236    Wpa2TkipOnly,
237    Wpa1Wpa2,
238    Wpa2TkipCcmp,
239    Wpa2Enterprise,
240    Wpa2,
241    Wpa2Wpa3,
242    Wpa3Transition,
243    Wpa3,
244    Wpa3Enterprise,
245    Eap = LAST_FAKE_PROTECTION_CFG_VALUE,
246}
247
248impl Distribution<FakeProtectionCfg> for StandardUniform {
249    fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> FakeProtectionCfg {
250        // NB: rand does not provide uniform distribution for isize.
251        let r = rng.random_range(0..usize::try_from(LAST_FAKE_PROTECTION_CFG_VALUE + 1).unwrap());
252        FromPrimitive::from_usize(r)
253            .unwrap_or_else(|| panic!("Out of range random value for FakeProtectionCfg: {:?}", r))
254    }
255}
256
257impl From<fidl_sme::Protection> for FakeProtectionCfg {
258    fn from(protection: fidl_sme::Protection) -> Self {
259        match protection {
260            fidl_sme::Protection::Unknown => panic!("unknown protection"),
261            fidl_sme::Protection::Open => FakeProtectionCfg::Open,
262            fidl_sme::Protection::Owe => FakeProtectionCfg::Owe,
263            fidl_sme::Protection::OpenOweTransition => FakeProtectionCfg::OpenOweTransition,
264            fidl_sme::Protection::Wep => FakeProtectionCfg::Wep,
265            fidl_sme::Protection::Wpa1 => FakeProtectionCfg::Wpa1,
266            fidl_sme::Protection::Wpa1Wpa2PersonalTkipOnly => FakeProtectionCfg::Wpa1Wpa2TkipOnly,
267            fidl_sme::Protection::Wpa2PersonalTkipOnly => FakeProtectionCfg::Wpa2TkipOnly,
268            fidl_sme::Protection::Wpa1Wpa2Personal => FakeProtectionCfg::Wpa1Wpa2,
269            fidl_sme::Protection::Wpa2Personal => FakeProtectionCfg::Wpa2,
270            fidl_sme::Protection::Wpa2Wpa3Personal => FakeProtectionCfg::Wpa2Wpa3,
271            fidl_sme::Protection::Wpa3Personal => FakeProtectionCfg::Wpa3,
272            fidl_sme::Protection::Wpa2Enterprise => FakeProtectionCfg::Wpa2Enterprise,
273            fidl_sme::Protection::Wpa3Enterprise => FakeProtectionCfg::Wpa3Enterprise,
274        }
275    }
276}
277
278pub fn build_fake_bss_description_creator__(
279    protection_cfg: FakeProtectionCfg,
280) -> BssDescriptionCreator {
281    BssDescriptionCreator {
282        bssid: [0x07, 0x01, 0x02, 0x4d, 0x35, 0x08],
283        bss_type: fidl_common::BssType::Infrastructure,
284        beacon_period: 100,
285        channel: Channel::new(3, Cbw::Cbw40),
286        rssi_dbm: 0,
287        snr_db: 0,
288
289        protection_cfg,
290        ssid: Ssid::try_from("fake-ssid").unwrap(),
291        rates: vec![0x82, 0x84, 0x8b, 0x96, 0x0c, 0x12, 0x18, 0x24, 0x30, 0x48, 0x60, 0x6c],
292        wmm_param: Some(fake_wmm_param()),
293        sae_hash_to_element: false,
294
295        cf_pollable: false,
296        cf_poll_req: false,
297        short_preamble: false,
298        spectrum_mgmt: false,
299        qos: false,
300        short_slot_time: false,
301        apsd: false,
302        radio_measurement: false,
303        delayed_block_ack: false,
304        immediate_block_ack: false,
305
306        ies_overrides: IesOverrides::new(),
307    }
308}
309
310fn random_ecw_min_max(rng: &mut rand::prelude::ThreadRng) -> ie::EcwMinMax {
311    let min = rng.random_range(0x0..0xf);
312    let max = rng.random_range(min..0xf);
313    ie::EcwMinMax((max << 4) | min)
314}
315
316pub fn build_random_bss_description_creator__(
317    protection_cfg: FakeProtectionCfg,
318) -> BssDescriptionCreator {
319    // Only the Infrastructure BSS type is supported.
320    let bss_type = fidl_common::BssType::Infrastructure;
321
322    let mut rng = rand::rng();
323
324    // Random rates
325    let mut rates: Vec<u8> = vec![];
326    for _ in
327        0..rng.random_range(1..ie::SUPPORTED_RATES_MAX_LEN + ie::EXTENDED_SUPPORTED_RATES_MAX_LEN)
328    {
329        rates.push(rng.random());
330    }
331    let rates = rates; // shadow to make rates immutable
332
333    // Random IE bytes
334    let mut giant_vendor_ies = vec![];
335    for _j in 0..8 {
336        giant_vendor_ies.extend_from_slice(&[221, 250]);
337        giant_vendor_ies.extend((0..250).map(|_| rng.random::<u8>()))
338    }
339    let ies_overrides = IesOverrides::new().set_raw(giant_vendor_ies);
340
341    let qos: bool = rng.random();
342    let apsd: bool = qos && rng.random(); // APSD is a QoS capability, so the AP must also be QoS capable
343
344    BssDescriptionCreator {
345        bssid: (0..6).map(|_| rng.random::<u8>()).collect::<Vec<u8>>().try_into().unwrap(),
346        bss_type,
347        beacon_period: rng.random::<u16>(),
348        // TODO(https://fxbug.dev/42162492): Purely random valid channel values is not implemented.
349        channel: Channel::new(rng.random_range(1..255), Cbw::Cbw20),
350        rssi_dbm: rng.random::<i8>(),
351        snr_db: rng.random::<i8>(),
352
353        protection_cfg,
354        ssid: Ssid::from_bytes_unchecked(
355            (0..fidl_ieee80211::MAX_SSID_BYTE_LEN).map(|_| rng.random::<u8>()).collect::<Vec<u8>>(),
356        ),
357        rates,
358
359        // WMM is independent of 802.11 QoS, so the capabilities randomly indicated
360        // in wmm_param are not related to the QoS and APSD bits in the Capability Information
361        // field indicated. See WMM Specification, Section 1.3.
362        wmm_param: if rng.random() {
363            Some(ie::WmmParam {
364                wmm_info: ie::WmmInfo(0).with_ap_wmm_info(
365                    ie::ApWmmInfo(0)
366                        .with_parameter_set_count(rng.random_range(0x00..0xf))
367                        .with_uapsd(rng.random()),
368                ),
369                _reserved: rng.random(),
370                ac_be_params: ie::WmmAcParams {
371                    aci_aifsn: ie::WmmAciAifsn(0).with_aifsn(rng.random_range(2..0xf)).with_aci(0),
372                    ecw_min_max: random_ecw_min_max(&mut rng),
373                    txop_limit: rng.random(),
374                },
375                ac_bk_params: ie::WmmAcParams {
376                    aci_aifsn: ie::WmmAciAifsn(0).with_aifsn(rng.random_range(2..0xf)).with_aci(1),
377                    ecw_min_max: random_ecw_min_max(&mut rng),
378                    txop_limit: rng.random(),
379                },
380                ac_vi_params: ie::WmmAcParams {
381                    aci_aifsn: ie::WmmAciAifsn(0).with_aifsn(rng.random_range(2..0xf)).with_aci(2),
382                    ecw_min_max: random_ecw_min_max(&mut rng),
383                    txop_limit: rng.random(),
384                },
385                ac_vo_params: ie::WmmAcParams {
386                    aci_aifsn: ie::WmmAciAifsn(0).with_aifsn(rng.random_range(2..0xf)).with_aci(3),
387                    ecw_min_max: random_ecw_min_max(&mut rng),
388                    txop_limit: rng.random(),
389                },
390            })
391        } else {
392            None
393        },
394        sae_hash_to_element: rng.random(),
395
396        cf_pollable: rng.random(),
397        cf_poll_req: rng.random(),
398        short_preamble: rng.random(),
399        spectrum_mgmt: rng.random(),
400        qos,
401        short_slot_time: rng.random(),
402        apsd,
403        radio_measurement: rng.random(),
404        delayed_block_ack: rng.random(),
405        immediate_block_ack: rng.random(),
406
407        // Generating completely random IEs would be chaotic at best, so we
408        // generate some random vendor ies instead.
409        ies_overrides,
410    }
411}
412
413fn derive_rsne(protection_cfg: FakeProtectionCfg) -> Option<Vec<u8>> {
414    match protection_cfg {
415        FakeProtectionCfg::Wpa3Enterprise => Some(fake_wpa3_enterprise_192_bit_rsne()),
416        FakeProtectionCfg::Wpa2Enterprise => Some(fake_wpa2_enterprise_rsne()),
417        FakeProtectionCfg::Wpa3 => Some(fake_wpa3_rsne()),
418        FakeProtectionCfg::Wpa3Transition => Some(fake_wpa3_transition_rsne()),
419        FakeProtectionCfg::Wpa2Wpa3 => Some(fake_wpa2_wpa3_rsne()),
420        FakeProtectionCfg::Wpa2TkipCcmp => Some(fake_wpa2_tkip_ccmp_rsne()),
421        FakeProtectionCfg::Wpa1Wpa2TkipOnly | FakeProtectionCfg::Wpa2TkipOnly => {
422            Some(fake_wpa2_tkip_only_rsne())
423        }
424        FakeProtectionCfg::Wpa1Wpa2 | FakeProtectionCfg::Wpa2 => Some(fake_wpa2_rsne()),
425        FakeProtectionCfg::Eap => Some(fake_eap_rsne()),
426        FakeProtectionCfg::Owe => Some(fake_owe_rsne()),
427        _ => None,
428    }
429}
430
431fn derive_wpa1_vendor_ies(protection_cfg: FakeProtectionCfg) -> Option<Vec<u8>> {
432    match protection_cfg {
433        FakeProtectionCfg::Wpa1
434        | FakeProtectionCfg::Wpa1Wpa2TkipOnly
435        | FakeProtectionCfg::Wpa1Wpa2 => Some(fake_wpa1_ie(false)),
436        FakeProtectionCfg::Wpa1Enhanced => Some(fake_wpa1_ie(true)),
437        _ => None,
438    }
439}
440
441fn derive_owe_transition_ie(protection_cfg: FakeProtectionCfg) -> Option<Vec<u8>> {
442    match protection_cfg {
443        FakeProtectionCfg::OpenOweTransition => Some(fake_owe_transition_ie()),
444        _ => None,
445    }
446}
447
448#[macro_export]
449macro_rules! fake_fidl_bss_description__ {
450    ($build_fake_bss_description_creator__:path, $fake_protection_cfg:expr $(, $bss_key:ident: $bss_value:expr)*) => {{
451        let bss_description_creator = $crate::test_utils::fake_stas::BssDescriptionCreator {
452            $(
453                $bss_key: $bss_value,
454            )*
455            ..$build_fake_bss_description_creator__($fake_protection_cfg.into())
456        };
457        bss_description_creator.create_bss_description().expect("expect creating BSS to succeed")
458    }};
459}
460
461#[macro_export]
462macro_rules! fake_fidl_bss_description {
463    ($protection_name:ident $(, $bss_key:ident: $bss_value:expr)* $(,)?) => {{
464        $crate::fake_fidl_bss_description__!(
465            $crate::test_utils::fake_stas::build_fake_bss_description_creator__,
466            $crate::test_utils::fake_stas::FakeProtectionCfg::$protection_name
467                $(, $bss_key: $bss_value)*)
468    }};
469    (protection => $fake_protection_cfg:expr $(, $bss_key:ident: $bss_value:expr)* $(,)?) => {{
470        $crate::fake_fidl_bss_description__!(
471            $crate::test_utils::fake_stas::build_fake_bss_description_creator__,
472            $fake_protection_cfg
473                $(, $bss_key: $bss_value)*)
474    }};
475}
476
477#[macro_export]
478macro_rules! random_fidl_bss_description {
479    ($($bss_key:ident: $bss_value:expr),* $(,)?) => {{
480        let mut rng = rand::rng();
481        $crate::fake_fidl_bss_description__!(
482            $crate::test_utils::fake_stas::build_random_bss_description_creator__,
483            rng.random::<$crate::test_utils::fake_stas::FakeProtectionCfg>()
484                $(, $bss_key: $bss_value)*)
485    }};
486    ($protection_name:ident $(, $bss_key:ident: $bss_value:expr)* $(,)?) => {{
487        $crate::fake_fidl_bss_description__!(
488            $crate::test_utils::fake_stas::build_random_bss_description_creator__,
489            $crate::test_utils::fake_stas::FakeProtectionCfg::$protection_name
490                $(, $bss_key: $bss_value)*)
491    }};
492    (protection => $fake_protection_cfg:expr $(, $bss_key:ident: $bss_value:expr)* $(,)?) => {{
493        $crate::fake_fidl_bss_description__!(
494            $crate::test_utils::fake_stas::build_random_bss_description_creator__,
495            $fake_protection_cfg
496                $(, $bss_key: $bss_value)*)
497    }};
498}
499
500#[macro_export]
501macro_rules! fake_bss_description__ {
502    ($fidl_bss_description_macro:ident, $fake_protection_cfg:expr $(, $bss_key:ident: $bss_value:expr)* $(,)?) => {{
503        let fidl_bss = $crate::$fidl_bss_description_macro!(protection => $fake_protection_cfg $(, $bss_key: $bss_value)*);
504        let bss_description: $crate::bss::BssDescription = std::convert::TryFrom::try_from(fidl_bss)
505            .expect("expect BSS conversion to succeed");
506        bss_description
507    }}
508}
509
510#[macro_export]
511macro_rules! fake_bss_description {
512    ($protection_name:ident $(, $bss_key:ident: $bss_value:expr)* $(,)?) => {{
513        $crate::fake_bss_description__!(
514            fake_fidl_bss_description,
515            $crate::test_utils::fake_stas::FakeProtectionCfg::$protection_name $(, $bss_key: $bss_value)*)
516    }};
517    (protection => $fake_protection_cfg:expr $(, $bss_key:ident: $bss_value:expr)* $(,)?) => {{
518        $crate::fake_bss_description__!(
519            fake_fidl_bss_description,
520            $fake_protection_cfg $(, $bss_key: $bss_value)*)
521    }};
522}
523
524#[macro_export]
525macro_rules! random_bss_description {
526    ($($bss_key:ident: $bss_value:expr),* $(,)?) => {{
527        let mut rng = rand::rng();
528        $crate::fake_bss_description__!(
529            random_fidl_bss_description,
530            rng.random::<$crate::test_utils::fake_stas::FakeProtectionCfg>()
531                $(, $bss_key: $bss_value)*)
532    }};
533    ($protection_name:ident $(, $bss_key:ident: $bss_value:expr)* $(,)?) => {{
534        $crate::fake_bss_description__!(
535            random_fidl_bss_description,
536            $crate::test_utils::fake_stas::FakeProtectionCfg::$protection_name $(, $bss_key: $bss_value)*)
537    }};
538    (protection => $fake_protection_cfg:expr $(, $bss_key:ident: $bss_value:expr)* $(,)?) => {{
539        $crate::fake_bss_description__!(
540            random_fidl_bss_description,
541            $fake_protection_cfg $(, $bss_key: $bss_value)*)
542    }};
543}
544
545#[cfg(test)]
546mod tests {
547    use super::*;
548    use crate::bss::{BssDescription, Protection};
549    use crate::mac::CapabilityInfo;
550    use crate::test_utils::fake_frames::{fake_wmm_param_body, fake_wmm_param_header};
551
552    #[test]
553    fn check_simplest_macro_use() {
554        let fidl_bss_description = fake_fidl_bss_description!(Open);
555        let bss_description = fake_bss_description!(Open);
556        assert_eq!(
557            BssDescription::try_from(fidl_bss_description)
558                .expect("Failed to convert fake_fidl_bss_description value"),
559            bss_description
560        );
561
562        for i in 1..=11 {
563            if i > 10 {
564                panic!("random_bss_description is always equal to bss_description");
565            }
566
567            let random_fidl_bss_description = random_fidl_bss_description!(Open);
568            let random_bss_description =
569                BssDescription::try_from(random_fidl_bss_description.clone())
570                    .expect("Failed to convert random_fidl_bss_description value");
571            if random_bss_description != bss_description {
572                break;
573            }
574        }
575
576        for i in 1..=11 {
577            if i > 10 {
578                panic!("random_bss_description is always equal to other_random_bss_description");
579            }
580
581            let random_fidl_bss_description = random_fidl_bss_description!(Open);
582            let random_bss_description =
583                BssDescription::try_from(random_fidl_bss_description.clone())
584                    .expect("Failed to convert random_fidl_bss_description value");
585            let other_random_bss_description = random_bss_description!(Open);
586            if random_bss_description != other_random_bss_description {
587                break;
588            }
589        }
590    }
591
592    #[test]
593    fn fake_protection_cfg_from_primitive() {
594        assert!(
595            0 <= LAST_FAKE_PROTECTION_CFG_VALUE,
596            "LAST_FAKE_PROTECTION_CFG_VALUE is not positive: {}",
597            LAST_FAKE_PROTECTION_CFG_VALUE,
598        );
599
600        let too_low: Option<FakeProtectionCfg> = FromPrimitive::from_isize(-1);
601        assert_eq!(
602            too_low, None::<FakeProtectionCfg>,
603            "Successfully converted low out of range FakeProtectionCfg value"
604        );
605
606        for i in 0..(LAST_FAKE_PROTECTION_CFG_VALUE + 1) {
607            let _: FakeProtectionCfg = FromPrimitive::from_isize(i).unwrap_or_else(|| {
608                panic!("Failed to convert {:?} to a FakeProtectionCfg value", i)
609            });
610        }
611
612        let too_high: Option<FakeProtectionCfg> =
613            FromPrimitive::from_isize(LAST_FAKE_PROTECTION_CFG_VALUE + 1);
614        assert_eq!(
615            too_high, None::<FakeProtectionCfg>,
616            "Successfully converted high out of range FakeProtectionCfg value"
617        );
618    }
619
620    #[test]
621    fn fake_protection_cfg_expr_syntax() {
622        let fidl_bss_description =
623            fake_fidl_bss_description!(protection => FakeProtectionCfg::Open);
624        assert_eq!(
625            BssDescription::try_from(fidl_bss_description)
626                .expect("Failed to convert fake_fidl_bss_description value")
627                .protection(),
628            Protection::Open
629        );
630
631        let fidl_bss_description =
632            random_fidl_bss_description!(protection => FakeProtectionCfg::Open);
633        assert_eq!(
634            BssDescription::try_from(fidl_bss_description)
635                .expect("Failed to convert random_fidl_bss_description value")
636                .protection(),
637            Protection::Open
638        );
639
640        let bss_description = fake_bss_description!(protection => FakeProtectionCfg::Open);
641        assert_eq!(bss_description.protection(), Protection::Open);
642
643        let bss_description = random_bss_description!(protection => FakeProtectionCfg::Open);
644        assert_eq!(bss_description.protection(), Protection::Open);
645    }
646
647    #[test]
648    fn fake_protection_cfg_privacy_bit_and_protection() {
649        let bss = fake_bss_description!(Open);
650        assert!(!mac::CapabilityInfo(bss.capability_info).privacy());
651        assert_eq!(bss.protection(), Protection::Open);
652
653        let bss = fake_bss_description!(Wep);
654        assert!(mac::CapabilityInfo(bss.capability_info).privacy());
655        assert_eq!(bss.protection(), Protection::Wep);
656
657        let bss = fake_bss_description!(Wpa1);
658        assert!(mac::CapabilityInfo(bss.capability_info).privacy());
659        assert_eq!(bss.protection(), Protection::Wpa1);
660
661        let bss = fake_bss_description!(Wpa2);
662        assert!(mac::CapabilityInfo(bss.capability_info).privacy());
663        assert_eq!(bss.protection(), Protection::Wpa2Personal);
664    }
665
666    #[test]
667    fn fake_protection_cfg_privacy_bit_and_protection_in_random_bss() {
668        let bss = random_bss_description!(Open);
669        assert!(!mac::CapabilityInfo(bss.capability_info).privacy());
670        assert_eq!(bss.protection(), Protection::Open);
671
672        let bss = random_bss_description!(Wep);
673        assert!(mac::CapabilityInfo(bss.capability_info).privacy());
674        assert_eq!(bss.protection(), Protection::Wep);
675
676        let bss = random_bss_description!(Wpa1);
677        assert!(mac::CapabilityInfo(bss.capability_info).privacy());
678        assert_eq!(bss.protection(), Protection::Wpa1);
679
680        let bss = random_bss_description!(Wpa2);
681        assert!(mac::CapabilityInfo(bss.capability_info).privacy());
682        assert_eq!(bss.protection(), Protection::Wpa2Personal);
683    }
684
685    #[test]
686    fn set_capability_info_bits() {
687        macro_rules! check_bit {
688            ($bit_name:ident) => {{
689                let bss = fake_bss_description!(Open, $bit_name: true);
690                assert!(mac::CapabilityInfo(bss.capability_info).$bit_name());
691                let bss = fake_bss_description!(Open, $bit_name: false);
692                assert!(!mac::CapabilityInfo(bss.capability_info).$bit_name());
693            }}
694        }
695        check_bit!(cf_pollable);
696        check_bit!(cf_poll_req);
697        check_bit!(short_preamble);
698        check_bit!(spectrum_mgmt);
699        check_bit!(qos);
700        check_bit!(short_slot_time);
701        check_bit!(apsd);
702        check_bit!(radio_measurement);
703        check_bit!(delayed_block_ack);
704        check_bit!(immediate_block_ack);
705
706        let bss =
707            fake_bss_description!(Open, cf_pollable: true, apsd: false, immediate_block_ack: true);
708        assert!(mac::CapabilityInfo(bss.capability_info).cf_pollable());
709        assert!(!mac::CapabilityInfo(bss.capability_info).apsd());
710        assert!(mac::CapabilityInfo(bss.capability_info).immediate_block_ack());
711    }
712
713    #[test]
714    fn simple_default_override() {
715        let bss = fake_fidl_bss_description!(Open);
716        assert_eq!(bss.beacon_period, 100);
717
718        let bss = fake_fidl_bss_description!(Open, beacon_period: 50);
719        assert_eq!(bss.beacon_period, 50);
720    }
721
722    #[test]
723    #[should_panic(expected = "Personal is not supported")]
724    // TODO(https://fxbug.dev/42169733): LeakSanitizer flags leaks caused by panic.
725    #[cfg_attr(feature = "variant_asan", ignore)]
726    #[cfg_attr(feature = "variant_hwasan", ignore)]
727    fn unsupported_bss_type() {
728        fake_fidl_bss_description!(Open, bss_type: fidl_common::BssType::Personal);
729    }
730
731    #[test]
732    fn any_protection_syntax() {
733        let _ = random_fidl_bss_description!();
734        let _ = random_bss_description!();
735    }
736
737    #[test]
738    fn random_fidl_bss_decription_override() {
739        let random_bss = random_bss_description!(ssid: Ssid::try_from("foo").unwrap());
740        assert_eq!(random_bss.ssid, Ssid::try_from("foo").unwrap());
741    }
742
743    #[test]
744    fn valid_random_ecw_min_max() {
745        let mut rng = rand::rng();
746        for _ in 0..100 {
747            let ecw_min_max = random_ecw_min_max(&mut rng);
748            assert!(ecw_min_max.ecw_max() >= ecw_min_max.ecw_min());
749        }
750    }
751
752    #[test]
753    fn random_bss_is_not_constant() {
754        for _ in 0..10 {
755            let random_bss_1 = BssDescription::try_from(random_fidl_bss_description!())
756                .expect("Failed to convert random_bss_description value");
757            let random_bss_2 = BssDescription::try_from(random_fidl_bss_description!())
758                .expect("Failed to convert random_bss_description value");
759            if random_bss_1 != random_bss_2 {
760                return;
761            }
762        }
763        panic!("random bss is always the same");
764    }
765
766    #[test]
767    fn random_bss_protection_is_not_constant() {
768        for _ in 0..10 {
769            let random_bss_1 = BssDescription::try_from(random_fidl_bss_description!())
770                .expect("Failed to convert random_bss_description value");
771            let random_bss_2 = BssDescription::try_from(random_fidl_bss_description!())
772                .expect("Failed to convert random_bss_description value");
773            if random_bss_1.protection() != random_bss_2.protection() {
774                return;
775            }
776        }
777        panic!("random bss protection is always the same");
778    }
779
780    #[test]
781    fn some_random_bss_bits_are_fixed() {
782        for _ in 0..5 {
783            let random_bss = random_fidl_bss_description!(Open);
784            assert_eq!(random_bss.bss_type, fidl_common::BssType::Infrastructure);
785            assert!(mac::CapabilityInfo(random_bss.capability_info).ess());
786            assert!(!mac::CapabilityInfo(random_bss.capability_info).ibss());
787            assert!(!mac::CapabilityInfo(random_bss.capability_info).privacy());
788        }
789    }
790
791    // Test random_bss_description generation of random protection since
792    // it doesn't rely on random_fidl_bss_description for it.
793    #[test]
794    fn random_bss_decription_protection_randomness() {
795        for _ in 0..10 {
796            let random_bss_1 = random_bss_description!();
797            let random_bss_2 = random_bss_description!();
798            if random_bss_1.protection() != random_bss_2.protection() {
799                return;
800            }
801        }
802        panic!("random protection is always the same");
803    }
804
805    #[test]
806    fn ies_overrides() {
807        let bss = fake_bss_description!(Wpa1Wpa2,
808            ssid: Ssid::try_from("fuchsia").unwrap(),
809            rates: vec![11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24],
810            ies_overrides: IesOverrides::new()
811                .remove(IeType::new_vendor6([0x00, 0x0b, 0x86, 0x01, 0x04, 0x08]))
812                .set(IeType::DSSS_PARAM_SET, [136].to_vec()),
813        );
814
815        // Things to note:
816        // - SSID "fuchsia" is inserted.
817        // - Rates and extended supported rates are inserted.
818        // - WPA2 RSNE and WPA1 vendor IE are inserted.
819        // - DSSS Param set's value is changed.
820        // - Aruba vendor IE no longer there.
821        #[rustfmt::skip]
822        let mut expected_ies = vec![
823            // SSID
824            0x00, 0x07, b'f', b'u', b'c', b'h', b's', b'i', b'a',
825            // Rates
826            0x01, 0x08, 11, 12, 13, 14, 15, 16, 17, 18,
827            // DS parameter set: channel 136
828            0x03, 0x01, 136,
829            // TIM - DTIM count: 0, DTIM period: 1, PVB: 2
830            0x05, 0x04, 0x00, 0x01, 0x00, 0x02,
831            // Country info
832            0x07, 0x10, 0x55, 0x53, 0x20, // US, Any environment
833            0x24, 0x04, 0x24, // 1st channel: 36, # channels: 4, maximum tx power: 36 dBm
834            0x34, 0x04, 0x1e, // 1st channel: 52, # channels: 4, maximum tx power: 30 dBm
835            0x64, 0x0c, 0x1e, // 1st channel: 100, # channels: 12, maximum tx power: 30 dBm
836            0x95, 0x05, 0x24, // 1st channel: 149, # channels: 5, maximum tx power: 36 dBm
837            0x00, // padding
838            // Power constraint: 0
839            0x20, 0x01, 0x00,
840            // TPC Report Transmit Power: 9, Link Margin: 0
841            0x23, 0x02, 0x09, 0x00,
842            // HT Capabilities
843            0x2d, 0x1a, 0xef, 0x09, // HT capabilities info
844            0x17, // A-MPDU parameters
845            0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
846            0x00, 0x00, // MCS set
847            0x00, 0x00, // HT extended capabilities
848            0x00, 0x00, 0x00, 0x00, // Transmit beamforming
849            0x00, // Antenna selection capabilities
850            // RSNE
851            0x30, 18, // Element header
852            1, 0, // Version
853            0x00, 0x0F, 0xAC, 4, // Group Cipher: CCMP-128
854            1, 0, 0x00, 0x0F, 0xAC, 4, // 1 Pairwise Cipher: CCMP-128
855            1, 0, 0x00, 0x0F, 0xAC, 2, // 1 AKM: PSK
856            // Extended supported rates
857            0x32, 0x06, 19, 20, 21, 22, 23, 24,
858            // HT Operation
859            0x3d, 0x16, 0x8c, // Primary channel: 140
860            0x0d, // HT info subset - secondary channel above, any channel width, RIFS permitted
861            0x16, 0x00, 0x00, 0x00, // HT info subsets
862            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
863            0x00, 0x00, // Basic MCS set
864            // Extended Capabilities: extended channel switching, BSS transition, operating mode notification
865            0x7f, 0x08, 0x04, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x40,
866            // VHT Capabilities
867            0xbf, 0x0c, 0x91, 0x59, 0x82, 0x0f, // VHT capabilities info
868            0xea, 0xff, 0x00, 0x00, 0xea, 0xff, 0x00, 0x00, // VHT supported MCS set
869            // VHT Operation
870            0xc0, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, // VHT Tx Power Envelope
871            0xc3, 0x03, 0x01, 0x24, 0x24,
872            // WPA1 vendor IE
873            0xdd, 0x16, 0x00, 0x50, 0xf2, // IE header
874            0x01, // MSFT specific IE type (WPA)
875            0x01, 0x00, // WPA version
876            0x00, 0x50, 0xf2, 0x02, // multicast cipher: TKIP
877            0x01, 0x00, 0x00, 0x50, 0xf2, 0x02, // 1 unicast cipher
878            0x01, 0x00, 0x00, 0x50, 0xf2, 0x02, // 1 AKM: PSK
879        ];
880        expected_ies.extend(fake_wmm_param_header());
881        expected_ies.extend(fake_wmm_param_body());
882
883        assert_eq!(bss.ies(), &expected_ies[..]);
884    }
885
886    #[test]
887    fn test_bss_open_owe_transition() {
888        let bss: BssDescription = fake_bss_description!(OpenOweTransition);
889        assert!(!CapabilityInfo(bss.capability_info).privacy());
890        let expected = fake_owe_transition_ie();
891        for i in 0..=(bss.ies().len() - expected.len()) {
892            if bss.ies()[i..i + expected.len()] == expected[..] {
893                // Found full OWE Transition IE
894                return;
895            }
896        }
897        panic!("Expected OWE Transition IE not found; ies: {:x?}", bss.ies());
898    }
899}