Skip to main content

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