wlan_mlme/
minstrel.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::common::mac::WlanGi;
6use crate::probe_sequence::{ProbeEntry, ProbeSequence};
7use ieee80211::{MacAddr, MacAddrBytes};
8use log::{debug, error};
9use std::collections::{hash_map, HashMap, HashSet};
10use std::time::Duration;
11use wlan_common::ie::{HtCapabilities, RxMcsBitmask, SupportedRate};
12use wlan_common::mac::FrameControl;
13use wlan_common::tx_vector::{
14    TxVecIdx, TxVector, ERP_NUM_TX_VECTOR, ERP_START_IDX, HT_NUM_MCS, HT_NUM_UNIQUE_MCS,
15};
16use {
17    fidl_fuchsia_wlan_common as fidl_common, fidl_fuchsia_wlan_minstrel as fidl_minstrel,
18    fidl_fuchsia_wlan_softmac as fidl_softmac,
19};
20
21// TODO(https://fxbug.dev/42103418): Enable CBW40 support once its information is available from AssocCtx.
22const ASSOC_CHAN_WIDTH: fidl_common::ChannelBandwidth = fidl_common::ChannelBandwidth::Cbw20;
23
24const MCS_MASK_0_31: u128 = 0xFFFFFFFF;
25const MINSTREL_FRAME_LENGTH: u32 = 1400; // bytes
26const MINSTREL_EXP_WEIGHT: f32 = 0.75; // Used to calculate moving average throughput
27
28// TODO(https://fxbug.dev/42163096): Determine if we should use a separate variable for (1.0 - MINSTREL_PROABILITY_THRESHOLD).
29const MINSTREL_PROBABILITY_THRESHOLD: f32 = 0.9; // If probability is past this level,
30                                                 // only consider throughput
31const PROBE_INTERVAL: u8 = 16; // Number of normal packets to send between two probe packets.
32const MAX_SLOW_PROBE: u64 = 2; // If the data rate is low, don't probe more than twice per update interval.
33const DEAD_PROBE_CYCLE_COUNT: u8 = 32; // If the success rate is under (1 - MINSTREL_PROBABILITY_THRESHOLD)
34                                       // only probe once every DEAD_PROBE_CYCLE_COUNT cycles.
35
36type TxStatsMap = HashMap<TxVecIdx, TxStats>;
37
38struct TxStats {
39    tx_vector_idx: TxVecIdx,
40    perfect_tx_time: Duration, // Minimum possible time to transmit a MINSTREL_FRAME_LENGTH frame at this rate.
41    success_cur: u64,          // Successful transmissions since last update.
42    attempts_cur: u64,         // Transmission attempts since last update.
43    moving_avg_probability: f32, // Exponentially weighted moving average probability of success.
44    expected_throughput: f32,  // Expected average throughput.
45    success_total: u64,        // Cumulative success counts. u64 to avoid overflow issues.
46    attempts_total: u64,       // Cumulative attempts. u64 to avoid overflow issues.
47    probe_cycles_skipped: u8, // Reduce probe frequency if probability < (1 - MINSTREL_PROBABILITY_THRESHOLD)
48    probes_total: u64,
49}
50
51impl TxStats {
52    fn new(tx_vector_idx: TxVecIdx) -> Self {
53        Self {
54            tx_vector_idx,
55            perfect_tx_time: Duration::ZERO,
56            success_cur: 0,
57            attempts_cur: 0,
58            // A low initial probability of success, but high enough to avoid marking the rate dead.
59            moving_avg_probability: 1.0 - MINSTREL_PROBABILITY_THRESHOLD,
60            expected_throughput: 0.0,
61            success_total: 0,
62            attempts_total: 0,
63            probe_cycles_skipped: 0,
64            probes_total: 0,
65        }
66    }
67
68    fn phy_type_strictly_preferred_over(&self, other: &TxStats) -> bool {
69        // Based on experiments, if HT is supported, it is better not to use ERP for
70        // data frames. With ralink RT5592 and Netgear Nighthawk X10, approximately 80
71        // feet away, HT/ERP tx throughput < 1 Mbps, HT only tx 4-8 Mbps
72        // TODO(https://fxbug.dev/42104244): Revisit with VHT support.
73        self.tx_vector_idx.is_ht() && !other.tx_vector_idx.is_ht()
74    }
75
76    fn better_for_expected_throughput_than(&self, other: &TxStats) -> bool {
77        (self.expected_throughput, self.moving_avg_probability)
78            > (other.expected_throughput, other.moving_avg_probability)
79    }
80
81    fn better_for_reliable_transmission_than(&self, other: &TxStats) -> bool {
82        if self.moving_avg_probability > MINSTREL_PROBABILITY_THRESHOLD
83            && other.moving_avg_probability > MINSTREL_PROBABILITY_THRESHOLD
84        {
85            // When probability is high enough, consider throughput instead.
86            self.expected_throughput > other.expected_throughput
87        } else {
88            self.moving_avg_probability > other.moving_avg_probability
89        }
90    }
91}
92
93impl From<&TxStats> for fidl_minstrel::StatsEntry {
94    fn from(stats: &TxStats) -> fidl_minstrel::StatsEntry {
95        fidl_minstrel::StatsEntry {
96            tx_vector_idx: *stats.tx_vector_idx,
97            tx_vec_desc: format!("{}", stats.tx_vector_idx),
98            success_cur: stats.success_cur,
99            attempts_cur: stats.attempts_cur,
100            probability: stats.moving_avg_probability,
101            cur_tp: stats.expected_throughput,
102            success_total: stats.success_total,
103            attempts_total: stats.attempts_total,
104            probes_total: stats.probes_total,
105            probe_cycles_skipped: stats.probe_cycles_skipped,
106        }
107    }
108}
109
110struct Peer {
111    addr: MacAddr,
112    tx_stats_map: TxStatsMap,
113    erp_rates: HashSet<TxVecIdx>,
114    highest_erp_rate: Option<TxVecIdx>,         // Set by AssocCtx
115    best_erp_for_reliability: Option<TxVecIdx>, // Based on transmission sucess probability for erp rates
116    best_expected_throughput: Option<TxVecIdx>, // Based on expected throughput
117    best_for_reliability: Option<TxVecIdx>,     // Based on transmission success probability
118
119    // Probe parameters
120    num_probe_cycles_done: u64,
121    num_pkt_until_next_probe: u8,
122    probes_total: u64,
123    probe_entry: ProbeEntry,
124}
125
126impl Peer {
127    fn from_assoc_cfg(assoc_cfg: &fidl_softmac::WlanAssociationConfig) -> Result<Self, zx::Status> {
128        let mut peer = Self {
129            addr: match assoc_cfg.bssid {
130                None => {
131                    error!("Cannot create Peer without a BSSID.");
132                    return Err(zx::Status::INTERNAL);
133                }
134                Some(bssid) => bssid.into(),
135            },
136            num_pkt_until_next_probe: PROBE_INTERVAL - 1,
137            tx_stats_map: Default::default(),
138            erp_rates: Default::default(),
139            highest_erp_rate: Default::default(),
140            best_erp_for_reliability: Default::default(),
141            best_expected_throughput: Default::default(),
142            best_for_reliability: Default::default(),
143            num_probe_cycles_done: Default::default(),
144            probes_total: Default::default(),
145            probe_entry: Default::default(),
146        };
147
148        if let Some(ht_cap) = assoc_cfg.ht_cap {
149            let mut ht_cap = HtCapabilities::from(ht_cap.clone());
150
151            // TODO(https://fxbug.dev/42104244): SGI support suppressed. Remove these once they are supported.
152            let mut cap_info = ht_cap.ht_cap_info;
153            cap_info.set_short_gi_20(false);
154            cap_info.set_short_gi_40(false);
155            ht_cap.ht_cap_info = cap_info;
156
157            let mcs_set = ht_cap.mcs_set;
158            if (mcs_set.rx_mcs().0 & MCS_MASK_0_31) == 0 {
159                error!("Invalid AssocCtx. HT supported but no valid MCS: {:?}", mcs_set);
160                return Err(zx::Status::INTERNAL);
161            } else {
162                peer.add_ht(&ht_cap);
163            }
164        }
165
166        if let Some(rates) = &assoc_cfg.rates {
167            peer.erp_rates = peer.add_supported_erp(&rates[..]);
168            peer.highest_erp_rate = peer.erp_rates.iter().cloned().max();
169        }
170        debug!("tx_stats_map populated. size: {}", peer.tx_stats_map.len());
171        if peer.tx_stats_map.is_empty() {
172            error!("No usable rates for peer {}", &peer.addr);
173            return Err(zx::Status::INTERNAL);
174        }
175
176        Ok(peer)
177    }
178
179    fn handle_tx_result_report(&mut self, tx_result: &fidl_common::WlanTxResult) {
180        let mut last_attempted_idx = None;
181        for status_entry in &tx_result.tx_result_entry[..] {
182            let idx = match TxVecIdx::new(status_entry.tx_vector_idx) {
183                Some(idx) => idx,
184                None => break,
185            };
186            last_attempted_idx.replace(idx);
187            // Get the stats for this rate, or attempt to add stats if none exist.
188            let stats = match self.tx_stats_map.entry(idx) {
189                hash_map::Entry::Occupied(val) => Some(val.into_mut()),
190                hash_map::Entry::Vacant(vacant) => {
191                    idx.to_erp_rate().map(|rate| vacant.insert(erp_idx_stats(idx, rate)))
192                }
193            };
194            match stats {
195                Some(stats) => stats.attempts_cur += status_entry.attempts as u64,
196                None => {
197                    last_attempted_idx.take();
198                    debug!("error: Invalid TxVecIdx: {:?}", idx)
199                }
200            }
201        }
202        if let Some(idx) = last_attempted_idx {
203            if tx_result.result_code == fidl_common::WlanTxResultCode::Success {
204                // last_attempted_idx will always have a corresponding tx_stats_map entry.
205                self.tx_stats_map.get_mut(&idx).unwrap().success_cur += 1;
206            }
207        }
208    }
209
210    fn add_ht(&mut self, ht_cap: &HtCapabilities) {
211        let mut max_size = HT_NUM_MCS + ERP_NUM_TX_VECTOR; // Account for ERP rates.
212        let cap_info = ht_cap.ht_cap_info;
213        let sgi_20 = cap_info.short_gi_20();
214        let sgi_40 = cap_info.short_gi_40();
215        if sgi_20 {
216            max_size += HT_NUM_MCS;
217        }
218        if ASSOC_CHAN_WIDTH == fidl_common::ChannelBandwidth::Cbw40 {
219            max_size += HT_NUM_MCS;
220            if sgi_40 {
221                max_size += HT_NUM_MCS;
222            }
223        }
224
225        debug!("max_size is {}", max_size);
226        self.tx_stats_map.reserve(max_size as usize);
227        let mcs_set = ht_cap.mcs_set;
228        self.add_supported_ht(
229            fidl_common::ChannelBandwidth::Cbw20,
230            WlanGi::G_800NS,
231            mcs_set.rx_mcs(),
232        );
233        if sgi_20 {
234            self.add_supported_ht(
235                fidl_common::ChannelBandwidth::Cbw20,
236                WlanGi::G_400NS,
237                mcs_set.rx_mcs(),
238            );
239        }
240        if ASSOC_CHAN_WIDTH == fidl_common::ChannelBandwidth::Cbw40 {
241            self.add_supported_ht(
242                fidl_common::ChannelBandwidth::Cbw40,
243                WlanGi::G_800NS,
244                mcs_set.rx_mcs(),
245            );
246            if sgi_40 {
247                self.add_supported_ht(
248                    fidl_common::ChannelBandwidth::Cbw40,
249                    WlanGi::G_400NS,
250                    mcs_set.rx_mcs(),
251                );
252            }
253        }
254
255        debug!("TxStatsMap size: {}", self.tx_stats_map.len());
256    }
257
258    // SupportedMcsRx is 78 bits long in IEEE802.11-2016, Figure 9-334
259    // In reality, devices implement MCS 0-31, sometimes 32, almost never beyond 32.
260    fn add_supported_ht(
261        &mut self,
262        channel_bandwidth: fidl_common::ChannelBandwidth,
263        gi: WlanGi,
264        mcs_set: RxMcsBitmask,
265    ) {
266        let mut tx_stats_added = 0;
267        for mcs_idx in 0..HT_NUM_MCS {
268            if mcs_set.support(mcs_idx) {
269                let tx_vector =
270                    TxVector::new(fidl_common::WlanPhyType::Ht, gi, channel_bandwidth, mcs_idx)
271                        .expect("Should be a valid TxVector");
272                let tx_vector_idx = tx_vector.to_idx();
273                let perfect_tx_time = tx_time_ht(channel_bandwidth, gi, mcs_idx);
274                let tx_stats = TxStats { perfect_tx_time, ..TxStats::new(tx_vector_idx) };
275                self.tx_stats_map.insert(tx_vector_idx, tx_stats);
276                tx_stats_added += 1;
277            }
278        }
279        debug!(
280            "{} HTs added with channel_bandwidth={:?}, gi={:?}",
281            tx_stats_added, channel_bandwidth, gi
282        );
283    }
284
285    fn add_supported_erp(&mut self, rates: &[u8]) -> HashSet<TxVecIdx> {
286        let mut tx_stats_added = 0;
287        let basic_rates: HashSet<TxVecIdx> = rates
288            .iter()
289            .filter_map(|rate| {
290                let rate = SupportedRate(*rate);
291                let tx_vector = match TxVector::from_supported_rate(&rate) {
292                    Ok(tx_vector) => Some(tx_vector),
293                    Err(e) => {
294                        error!("Could not create tx vector from supported rate: {}", e);
295                        None
296                    }
297                }?;
298                if tx_vector.phy() != fidl_common::WlanPhyType::Erp {
299                    return None;
300                }
301                let tx_vector_idx = tx_vector.to_idx();
302                self.tx_stats_map.insert(tx_vector_idx, erp_idx_stats(tx_vector_idx, rate));
303                tx_stats_added += 1;
304                if rate.basic() {
305                    Some(tx_vector_idx)
306                } else {
307                    None
308                }
309            })
310            .collect();
311        debug!("{} ERP added.", tx_stats_added);
312        if basic_rates.is_empty() {
313            vec![TxVecIdx::new(ERP_START_IDX).unwrap()].into_iter().collect()
314        } else {
315            basic_rates
316        }
317    }
318
319    fn update_stats(&mut self) {
320        // Update all TxStats for known TxVecIdx.
321        for tx_stats in self.tx_stats_map.values_mut() {
322            if tx_stats.attempts_cur != 0 {
323                let probability = tx_stats.success_cur as f32 / tx_stats.attempts_cur as f32;
324                if tx_stats.attempts_total == 0 {
325                    tx_stats.moving_avg_probability = probability;
326                } else {
327                    tx_stats.moving_avg_probability = tx_stats.moving_avg_probability
328                        * MINSTREL_EXP_WEIGHT
329                        + probability * (1.0 - MINSTREL_EXP_WEIGHT);
330                }
331                tx_stats.attempts_total += tx_stats.attempts_cur;
332                tx_stats.success_total += tx_stats.success_cur;
333                tx_stats.attempts_cur = 0;
334                tx_stats.success_cur = 0;
335                tx_stats.probe_cycles_skipped = 0;
336            } else {
337                tx_stats.probe_cycles_skipped = tx_stats.probe_cycles_skipped.saturating_add(1);
338            }
339            const NANOS_PER_SECOND: f32 = 1e9;
340            // perfect_tx_time is always non-zero as guaranteed by add_supported_ht and add_supported_erp.
341            tx_stats.expected_throughput = NANOS_PER_SECOND
342                / tx_stats.perfect_tx_time.as_nanos() as f32
343                * tx_stats.moving_avg_probability;
344        }
345
346        // Pick a random rate to start comparisons.
347        let arbitrary_rate = match self.tx_stats_map.iter().next() {
348            Some((tx_vec_idx, _)) => *tx_vec_idx,
349            None => return, // There are no supported rates, so everything past here is pointless.
350        };
351
352        // Determine optimal data rates for throughput and reliability based on current network conditions.
353        let mut best_expected_throughput = arbitrary_rate;
354        let mut best_for_reliability = arbitrary_rate;
355        let mut best_erp_for_reliability = self.highest_erp_rate;
356        for (tx_vector_idx, tx_stats) in &self.tx_stats_map {
357            // Unwraps are safe since max_tp and max_probability are taken from
358            // iterating through tx_stats_map keys.
359            let best_throughput_stats = self.tx_stats_map.get(&best_expected_throughput).unwrap();
360            let best_reliability_stats = self.tx_stats_map.get(&best_for_reliability).unwrap();
361
362            // Pick the data rate with the highest throughput. Prefer a better phy type (i.e. HT > ERP),
363            // unless the better phy type has extremely poor performance.
364            if (!tx_unlikely(tx_stats)
365                && tx_stats.phy_type_strictly_preferred_over(best_throughput_stats))
366                || (tx_stats.better_for_expected_throughput_than(best_throughput_stats)
367                    && !(!tx_unlikely(best_throughput_stats)
368                        && best_throughput_stats.phy_type_strictly_preferred_over(tx_stats)))
369            {
370                best_expected_throughput = *tx_vector_idx;
371            }
372            // Pick the data rate with the highest probability of transmission success. Prefer a better
373            // phy type (i.e. HT > ERP), unless the better phy type has extremely poor performance.
374            if (!tx_unlikely(tx_stats)
375                && tx_stats.phy_type_strictly_preferred_over(best_reliability_stats))
376                || (tx_stats.better_for_reliable_transmission_than(best_reliability_stats)
377                    && !(!tx_unlikely(best_reliability_stats)
378                        && best_reliability_stats.phy_type_strictly_preferred_over(tx_stats)))
379            {
380                best_for_reliability = *tx_vector_idx;
381            }
382            if let Some(best_erp_for_reliability) = best_erp_for_reliability.as_mut() {
383                let best_erp_reliability_stats =
384                    self.tx_stats_map.get(best_erp_for_reliability).unwrap();
385                if self.erp_rates.contains(tx_vector_idx)
386                    && tx_stats.better_for_reliable_transmission_than(best_erp_reliability_stats)
387                {
388                    *best_erp_for_reliability = *tx_vector_idx;
389                }
390            }
391        }
392        self.best_expected_throughput = Some(best_expected_throughput);
393        self.best_for_reliability = Some(best_for_reliability);
394        self.best_erp_for_reliability = best_erp_for_reliability;
395    }
396
397    fn get_tx_vector_idx(
398        &mut self,
399        needs_reliability: bool,
400        probe_sequence: &ProbeSequence,
401    ) -> Option<TxVecIdx> {
402        if needs_reliability {
403            self.best_for_reliability
404        } else if self.num_pkt_until_next_probe > 0 {
405            self.num_pkt_until_next_probe -= 1;
406            self.best_expected_throughput
407        } else {
408            self.num_pkt_until_next_probe = PROBE_INTERVAL - 1;
409            self.get_next_probe(probe_sequence)
410        }
411    }
412
413    fn get_next_probe(&mut self, probe_sequence: &ProbeSequence) -> Option<TxVecIdx> {
414        // We generally don't have any reason to switch to a rate with a lower throughput than
415        // our most reliable rate. Limit the number of probes for any rate below this threshold.
416        let slow_probe_cutoff = self.tx_stats_map.get(&self.best_for_reliability?)?.perfect_tx_time;
417        if self.tx_stats_map.len() == 1 {
418            return self.best_expected_throughput;
419        }
420        // Check each entry in the map once.
421        for _ in 0..self.tx_stats_map.len() {
422            let probe_idx = self.next_supported_probe_idx(probe_sequence);
423            let tx_stats = self.tx_stats_map.get_mut(&probe_idx).unwrap();
424            // Don't bother probing our current default indices, since we get data on them with every
425            // non-probe frame.
426            if Some(probe_idx) == self.best_erp_for_reliability.or(self.highest_erp_rate)
427                || Some(probe_idx) == self.best_expected_throughput.or(self.best_for_reliability)
428                // Don't probe the most-probed index.
429                || tx_stats.attempts_cur > self.num_probe_cycles_done
430                // Low throughput index, probe at most MAX_SLOW_PROBE times per update interval.
431                || (tx_stats.perfect_tx_time > slow_probe_cutoff
432                    && tx_stats.attempts_cur >= MAX_SLOW_PROBE)
433                // Low probability of success, only probe occasionally.
434                || (tx_unlikely(tx_stats)
435                    && (tx_stats.probe_cycles_skipped < DEAD_PROBE_CYCLE_COUNT
436                        || tx_stats.attempts_cur > 0))
437            {
438                continue;
439            }
440            self.probes_total += 1;
441            tx_stats.probes_total += 1;
442            return Some(probe_idx);
443        }
444        return self.best_expected_throughput;
445    }
446
447    // This is not safe to call unless tx_stats_map.len() > 0
448    fn next_supported_probe_idx(&mut self, probe_sequence: &ProbeSequence) -> TxVecIdx {
449        assert!(
450            !self.tx_stats_map.is_empty(),
451            "Cannot call next_supported_probe_idx with empty tx_stats_map"
452        );
453
454        loop {
455            let idx = probe_sequence.next(&mut self.probe_entry);
456            if self.probe_entry.cycle_complete() {
457                self.num_probe_cycles_done += 1;
458            }
459            if self.tx_stats_map.contains_key(&idx) {
460                return idx;
461            }
462        }
463    }
464}
465
466fn tx_unlikely(tx_stats: &TxStats) -> bool {
467    tx_stats.moving_avg_probability < 1.0 - MINSTREL_PROBABILITY_THRESHOLD
468}
469
470pub trait TimerManager {
471    fn schedule(&mut self, from_now: Duration);
472    fn cancel(&mut self);
473}
474
475/// MinstrelRateSelector is responsible for handling data rate selection on frame transmission for
476/// SoftMAC WLAN interfaces. It stores observed behavior for various compatible data rates,
477/// and uses intermittent probes to ensure that we continue to use the data rates with
478/// highest throughput and transmission success rate.
479///
480/// Some SoftMAC devices may provide their own rate selection implementations. In these cases,
481/// Minstrel may be used to collect and forward data rate statistics to upper stack layers.
482pub struct MinstrelRateSelector<T: TimerManager> {
483    timer_manager: T,
484    update_interval: Duration,
485    probe_sequence: ProbeSequence,
486    peer_map: HashMap<MacAddr, Peer>,
487    outdated_peers: HashSet<MacAddr>,
488}
489
490impl<T: TimerManager> MinstrelRateSelector<T> {
491    pub fn new(timer_manager: T, update_interval: Duration, probe_sequence: ProbeSequence) -> Self {
492        Self {
493            timer_manager,
494            update_interval,
495            probe_sequence,
496            peer_map: Default::default(),
497            outdated_peers: Default::default(),
498        }
499    }
500
501    pub fn add_peer(
502        &mut self,
503        assoc_cfg: &fidl_softmac::WlanAssociationConfig,
504    ) -> Result<(), zx::Status> {
505        let bssid: MacAddr = match assoc_cfg.bssid {
506            None => {
507                error!("Attempted to add peer with no BSSID.");
508                return Err(zx::Status::INTERNAL);
509            }
510            Some(bssid) => bssid.into(),
511        };
512        if self.peer_map.contains_key(&bssid) {
513            error!("Attempted to add peer {} twice.", &bssid);
514        } else {
515            let mut peer = Peer::from_assoc_cfg(assoc_cfg)?;
516            if self.peer_map.is_empty() {
517                self.timer_manager.schedule(self.update_interval);
518            }
519            peer.update_stats();
520            self.peer_map.insert(bssid, peer);
521        }
522        Ok(())
523    }
524
525    pub fn remove_peer(&mut self, addr: &MacAddr) {
526        self.outdated_peers.remove(addr);
527        match self.peer_map.remove(addr) {
528            Some(_) => debug!("Peer {} removed.", addr),
529            None => debug!("Cannot remove peer {}, not found.", addr),
530        }
531        if self.peer_map.is_empty() {
532            self.timer_manager.cancel();
533        }
534    }
535
536    pub fn handle_tx_result_report(&mut self, tx_result: &fidl_common::WlanTxResult) {
537        let peer_addr: MacAddr = tx_result.peer_addr.into();
538        match self.peer_map.get_mut(&peer_addr) {
539            Some(peer) => {
540                peer.handle_tx_result_report(tx_result);
541                self.outdated_peers.insert(peer_addr);
542            }
543            None => {
544                debug!("Peer {} received tx status report after it was removed.", peer_addr);
545            }
546        }
547    }
548
549    fn update_stats(&mut self) {
550        for outdated_peer in self.outdated_peers.drain() {
551            self.peer_map.get_mut(&outdated_peer).map(|peer| peer.update_stats());
552        }
553    }
554
555    pub fn handle_timeout(&mut self) {
556        // Reschedule our timer so we keep updating in a loop.
557        self.timer_manager.schedule(self.update_interval);
558        self.update_stats();
559    }
560
561    pub fn get_tx_vector_idx(
562        &mut self,
563        frame_control: &FrameControl,
564        peer_addr: &MacAddr,
565        flags: fidl_softmac::WlanTxInfoFlags,
566    ) -> Option<TxVecIdx> {
567        match self.peer_map.get_mut(peer_addr) {
568            None => TxVecIdx::new(ERP_START_IDX + ERP_NUM_TX_VECTOR as u16 - 1),
569            Some(peer) => {
570                if frame_control.is_data() {
571                    let needs_reliability =
572                        flags.contains(fidl_softmac::WlanTxInfoFlags::FAVOR_RELIABILITY);
573                    peer.get_tx_vector_idx(needs_reliability, &self.probe_sequence)
574                } else {
575                    peer.best_erp_for_reliability
576                }
577            }
578        }
579    }
580
581    pub fn get_fidl_peers(&self) -> fidl_minstrel::Peers {
582        fidl_minstrel::Peers {
583            addrs: self.peer_map.iter().map(|(peer, _)| peer.to_array()).collect(),
584        }
585    }
586
587    pub fn get_fidl_peer_stats(
588        &self,
589        peer_addr: &MacAddr,
590    ) -> Result<fidl_minstrel::Peer, zx::Status> {
591        let peer = self.peer_map.get(peer_addr).ok_or(zx::Status::NOT_FOUND)?;
592        Ok(fidl_minstrel::Peer {
593            addr: peer_addr.to_array(),
594            max_tp: tx_vec_idx_opt_to_u16(&peer.best_expected_throughput),
595            max_probability: tx_vec_idx_opt_to_u16(&peer.best_for_reliability),
596            basic_highest: tx_vec_idx_opt_to_u16(&peer.highest_erp_rate),
597            basic_max_probability: tx_vec_idx_opt_to_u16(&peer.best_erp_for_reliability),
598            probes: peer.probes_total,
599            entries: peer.tx_stats_map.iter().map(|(_, entry)| entry.into()).collect(),
600        })
601    }
602
603    pub fn is_active(&self) -> bool {
604        !self.peer_map.is_empty()
605    }
606}
607
608fn tx_vec_idx_opt_to_u16(tx_vec_idx: &Option<TxVecIdx>) -> u16 {
609    match tx_vec_idx {
610        Some(idx) => **idx,
611        None => 0,
612    }
613}
614
615fn tx_time_ht(
616    channel_bandwidth: fidl_common::ChannelBandwidth,
617    gi: WlanGi,
618    relative_mcs_idx: u8,
619) -> Duration {
620    header_tx_time_ht() + payload_tx_time_ht(channel_bandwidth, gi, relative_mcs_idx)
621}
622
623fn header_tx_time_ht() -> Duration {
624    // TODO(https://fxbug.dev/42162502): Implement Plcp preamble and header
625    Duration::ZERO
626}
627
628// relative_mcs_idx is the index for combination of (modulation, coding rate)
629// tuple when listed in the same order as MCS Index, without nss. i.e. 0: BPSK,
630// 1/2 1: QPSK, 1/2 2: QPSK, 3/4 3: 16-QAM, 1/2 4: 16-QAM, 3/4 5: 64-QAM, 2/3 6:
631// 64-QAM, 3/4 7: 64-QAM, 5/6 8: 256-QAM, 3/4 (since VHT) 9: 256-QAM, 5/6 (since
632// VHT)
633fn payload_tx_time_ht(
634    channel_bandwidth: fidl_common::ChannelBandwidth,
635    gi: WlanGi,
636    mcs_idx: u8,
637) -> Duration {
638    // N_{dbps} as defined in IEEE 802.11-2016 Table 19-26
639    // Unit: Number of data bits per OFDM symbol (20 MHz channel width)
640    const BITS_PER_SYMBOL_LIST: [u16; HT_NUM_UNIQUE_MCS as usize + /* since VHT */ 2] =
641        [26, 52, 78, 104, 156, 208, 234, 260, /* since VHT */ 312, 347];
642    // N_{sd} as defined in IEEE 802.11-2016 Table 19-26
643    // Unit: Number of complex data numbers per spatial stream per OFDM symbol (20 MHz)
644    const DATA_SUB_CARRIERS_20: u16 = 52;
645    // Unit: Number of complex data numbers per spatial stream per OFDM symbol (40 MHz)
646    const DATA_SUB_CARRIERS_40: u16 = 108;
647    // TODO(https://fxbug.dev/42104244): VHT would have kDataSubCarriers80 = 234 and kDataSubCarriers160 = 468
648
649    let nss = 1 + mcs_idx / HT_NUM_UNIQUE_MCS;
650    let relative_mcs_idx = mcs_idx % HT_NUM_UNIQUE_MCS;
651    let bits_per_symbol = if channel_bandwidth == fidl_common::ChannelBandwidth::Cbw40 {
652        BITS_PER_SYMBOL_LIST[relative_mcs_idx as usize] * DATA_SUB_CARRIERS_40
653            / DATA_SUB_CARRIERS_20
654    } else {
655        BITS_PER_SYMBOL_LIST[relative_mcs_idx as usize]
656    };
657
658    const TX_TIME_PER_SYMBOL_GI_800: Duration = Duration::from_nanos(4000);
659    const TX_TIME_PER_SYMBOL_GI_400: Duration = Duration::from_nanos(3600);
660    const TX_TIME_PADDING_GI_400: Duration = Duration::from_nanos(800);
661
662    // Perform multiplication before division to prevent precision loss
663    match gi {
664        WlanGi::G_400NS => {
665            TX_TIME_PADDING_GI_400
666                + (TX_TIME_PER_SYMBOL_GI_400 * 8 * MINSTREL_FRAME_LENGTH)
667                    / (nss as u32 * bits_per_symbol as u32)
668        }
669        WlanGi::G_800NS => {
670            (TX_TIME_PER_SYMBOL_GI_800 * 8 * MINSTREL_FRAME_LENGTH)
671                / (nss as u32 * bits_per_symbol as u32)
672        }
673        _ => panic!("payload_tx_time_ht is invalid for non-ht phy"),
674    }
675}
676
677fn tx_time_erp(rate: &SupportedRate) -> Duration {
678    header_tx_time_erp() + payload_tx_time_erp(rate)
679}
680
681fn header_tx_time_erp() -> Duration {
682    // TODO(https://fxbug.dev/42162502): Implement Plcp preamble and header
683    Duration::ZERO
684}
685
686fn payload_tx_time_erp(rate: &SupportedRate) -> Duration {
687    // D_{bps} as defined in IEEE 802.11-2016 Table 17-4
688    // Unit: Number of data bits per OFDM symbol
689    let bits_per_symbol = rate.rate() * 2;
690    const TX_TIME_PER_SYMBOL: Duration = Duration::from_nanos(4000);
691    TX_TIME_PER_SYMBOL * 8 * MINSTREL_FRAME_LENGTH / bits_per_symbol as u32
692}
693
694fn erp_idx_stats(tx_vector_idx: TxVecIdx, rate: SupportedRate) -> TxStats {
695    let perfect_tx_time = tx_time_erp(&rate);
696    TxStats { perfect_tx_time, ..TxStats::new(tx_vector_idx) }
697}
698
699#[cfg(test)]
700mod tests {
701    use super::*;
702    use fidl_fuchsia_wlan_common as fidl_common;
703    use lazy_static::lazy_static;
704    use std::sync::{Arc, Mutex};
705    use wlan_common::ie::{ChanWidthSet, HtCapabilityInfo};
706    use wlan_common::mac::FrameType;
707    use wlan_common::tx_vector::HT_START_IDX;
708
709    struct MockTimerManager {
710        scheduled: Arc<Mutex<Option<Duration>>>,
711    }
712
713    impl TimerManager for MockTimerManager {
714        fn schedule(&mut self, from_now: Duration) {
715            let mut scheduled = self.scheduled.lock().unwrap();
716            scheduled.replace(from_now);
717        }
718        fn cancel(&mut self) {
719            let mut scheduled = self.scheduled.lock().unwrap();
720            scheduled.take();
721        }
722    }
723
724    fn mock_minstrel() -> (MinstrelRateSelector<MockTimerManager>, Arc<Mutex<Option<Duration>>>) {
725        let timer = Arc::new(Mutex::new(None));
726        let timer_manager = MockTimerManager { scheduled: timer.clone() };
727        let update_interval = Duration::from_micros(100);
728        let probe_sequence = ProbeSequence::sequential();
729        (MinstrelRateSelector::new(timer_manager, update_interval, probe_sequence), timer)
730    }
731
732    lazy_static! {
733        static ref TEST_MAC_ADDR: MacAddr = MacAddr::from([50, 53, 51, 56, 55, 52]);
734    }
735
736    const BASIC_RATE_BIT: u8 = 0b10000000;
737
738    fn ht_assoc_cfg() -> fidl_softmac::WlanAssociationConfig {
739        let mut ht_cap = wlan_common::ie::fake_ht_capabilities();
740        let mut ht_cap_info = HtCapabilityInfo(0);
741        ht_cap_info.set_short_gi_40(true);
742        ht_cap_info.set_short_gi_20(true);
743        ht_cap_info.set_chan_width_set(ChanWidthSet::TWENTY_FORTY);
744        ht_cap.ht_cap_info = ht_cap_info;
745        ht_cap.mcs_set.0 = 0xffff; // Enable MCS 0-15
746
747        fidl_softmac::WlanAssociationConfig {
748            bssid: Some(TEST_MAC_ADDR.to_array()),
749            aid: Some(42),
750            listen_interval: Some(0),
751            channel: Some(fidl_common::WlanChannel {
752                primary: 149,
753                cbw: fidl_common::ChannelBandwidth::Cbw40,
754                secondary80: 0,
755            }),
756            qos: Some(true),
757            wmm_params: None,
758            rates: Some(vec![
759                2,
760                4,
761                11,
762                22,
763                12 | BASIC_RATE_BIT,
764                18,
765                24,
766                36,
767                48,
768                72,
769                96,
770                108 | BASIC_RATE_BIT,
771            ]),
772            capability_info: Some(0),
773            ht_cap: Some(ht_cap.into()),
774            ht_op: None,
775            vht_cap: None,
776            vht_op: None,
777            ..Default::default()
778        }
779    }
780
781    #[test]
782    fn peer_from_assoc_cfg() {
783        let assoc_cfg = ht_assoc_cfg();
784        let peer = Peer::from_assoc_cfg(&assoc_cfg)
785            .expect("Failed to convert WlanAssociationConfig into Peer.");
786        assert_eq!(peer.addr, assoc_cfg.bssid.unwrap().into());
787        assert_eq!(peer.tx_stats_map.len(), 24);
788        let mut peer_rates = peer
789            .tx_stats_map
790            .keys()
791            .into_iter()
792            .map(|tx_vector_idx| **tx_vector_idx)
793            .collect::<Vec<u16>>();
794        peer_rates.sort();
795        assert_eq!(
796            peer_rates,
797            vec![
798                1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, // HT rates
799                129, 130, 131, 132, 133, 134, 135, 136, // ERP rates
800            ]
801        );
802        let mut peer_erp_rates =
803            peer.erp_rates.iter().map(|tx_vector_idx| **tx_vector_idx).collect::<Vec<u16>>();
804        peer_erp_rates.sort();
805        let expected_basic_rate_1 =
806            TxVector::from_supported_rate(&SupportedRate(12 | BASIC_RATE_BIT)).unwrap().to_idx();
807        let expected_basic_rate_2 =
808            TxVector::from_supported_rate(&SupportedRate(108 | BASIC_RATE_BIT)).unwrap().to_idx();
809        assert_eq!(peer_erp_rates, vec![*expected_basic_rate_1, *expected_basic_rate_2]);
810        assert_eq!(peer.highest_erp_rate, TxVecIdx::new(136));
811    }
812
813    #[test]
814    fn add_peer() {
815        let (mut minstrel, timer) = mock_minstrel();
816        assert!(timer.lock().unwrap().is_none()); // No timer is scheduled.
817        minstrel.add_peer(&ht_assoc_cfg()).expect("Failed to add peer.");
818        assert!(timer.lock().unwrap().is_some()); // A timer is scheduled.
819
820        let peers = minstrel.get_fidl_peers();
821        assert_eq!(peers.addrs.len(), 1);
822
823        let peer_addr: MacAddr = {
824            let mut peer_addr = [0u8; 6];
825            peer_addr.copy_from_slice(&peers.addrs[0][..]);
826            peer_addr.into()
827        };
828        let peer_stats =
829            minstrel.get_fidl_peer_stats(&peer_addr).expect("Failed to get peer stats");
830        assert_eq!(&peer_stats.addr, TEST_MAC_ADDR.as_array());
831        // TODO(https://fxbug.dev/42103418): Size would be 40 if 40 MHz is supported and 72 if 40 MHz + SGI are supported.
832        assert_eq!(peer_stats.entries.len(), 24);
833        assert_eq!(peer_stats.max_tp, 16); // In the absence of data, our highest supported rate is max throughput.
834        assert_eq!(peer_stats.basic_highest, ERP_START_IDX + ERP_NUM_TX_VECTOR as u16 - 1);
835        assert_eq!(peer_stats.basic_max_probability, ERP_START_IDX + ERP_NUM_TX_VECTOR as u16 - 1);
836    }
837
838    #[test]
839    fn remove_peer() {
840        let (mut minstrel, timer) = mock_minstrel();
841        minstrel.add_peer(&ht_assoc_cfg()).expect("Failed to add peer.");
842        assert_eq!(minstrel.get_fidl_peers().addrs.len(), 1);
843        assert!(timer.lock().unwrap().is_some()); // A timer is scheduled.
844
845        minstrel.remove_peer(&TEST_MAC_ADDR);
846        assert!(timer.lock().unwrap().is_none()); // No more peers -- timer cancelled.
847
848        assert!(minstrel.get_fidl_peers().addrs.is_empty());
849        assert_eq!(minstrel.get_fidl_peer_stats(&TEST_MAC_ADDR), Err(zx::Status::NOT_FOUND));
850    }
851
852    #[test]
853    fn remove_second_peer() {
854        let (mut minstrel, timer) = mock_minstrel();
855        minstrel.add_peer(&ht_assoc_cfg()).expect("Failed to add peer.");
856        let mut peer2 = ht_assoc_cfg();
857        peer2.bssid = Some([11, 12, 13, 14, 15, 16]);
858        minstrel.add_peer(&peer2).expect("Failed to add peer.");
859        assert_eq!(minstrel.get_fidl_peers().addrs.len(), 2);
860        assert!(timer.lock().unwrap().is_some()); // A timer is scheduled.
861
862        minstrel.remove_peer(&TEST_MAC_ADDR);
863        assert_eq!(minstrel.get_fidl_peers().addrs.len(), 1);
864        assert!(timer.lock().unwrap().is_some()); // A timer is still scheduled.
865
866        assert_eq!(minstrel.get_fidl_peer_stats(&TEST_MAC_ADDR), Err(zx::Status::NOT_FOUND));
867        assert!(minstrel.get_fidl_peer_stats(&peer2.bssid.unwrap().into()).is_ok());
868    }
869
870    /// Helper fn to easily create tx result reports.
871    fn make_tx_result(entries: Vec<(u16, u8)>, success: bool) -> fidl_common::WlanTxResult {
872        assert!(entries.len() <= 8);
873        let mut tx_result_entry =
874            [fidl_common::WlanTxResultEntry { tx_vector_idx: 0, attempts: 0 }; 8];
875        tx_result_entry[0..entries.len()].copy_from_slice(
876            &entries
877                .into_iter()
878                .map(|(tx_vector_idx, attempts)| fidl_common::WlanTxResultEntry {
879                    tx_vector_idx,
880                    attempts,
881                })
882                .collect::<Vec<fidl_common::WlanTxResultEntry>>()[..],
883        );
884        let result_code = if success {
885            fidl_common::WlanTxResultCode::Success
886        } else {
887            fidl_common::WlanTxResultCode::Failed
888        };
889        fidl_common::WlanTxResult {
890            tx_result_entry,
891            peer_addr: TEST_MAC_ADDR.to_array(),
892            result_code,
893        }
894    }
895
896    #[test]
897    fn handle_tx_result_reports() {
898        // Indicate that we failed to transmit on rates 16-14 and succeeded on 13.
899        let tx_result = make_tx_result(vec![(16, 1), (15, 1), (14, 1), (13, 1)], true);
900
901        let (mut minstrel, _timer) = mock_minstrel();
902        minstrel.add_peer(&ht_assoc_cfg()).expect("Failed to add peer.");
903        minstrel.handle_tx_result_report(&tx_result);
904
905        // Stats are not updated until after the timer fires.
906        let peer_stats =
907            minstrel.get_fidl_peer_stats(&TEST_MAC_ADDR).expect("Failed to get peer stats");
908        assert_eq!(peer_stats.max_tp, 16);
909
910        minstrel.handle_timeout();
911        let peer_stats =
912            minstrel.get_fidl_peer_stats(&TEST_MAC_ADDR).expect("Failed to get peer stats");
913        assert_eq!(peer_stats.max_tp, 13);
914        assert_eq!(peer_stats.max_probability, 13);
915
916        let tx_result = make_tx_result(vec![(13, 1), (9, 1)], true);
917
918        for _ in 0..10 {
919            minstrel.handle_tx_result_report(&tx_result);
920            minstrel.handle_timeout();
921            let peer_stats =
922                minstrel.get_fidl_peer_stats(&TEST_MAC_ADDR).expect("Failed to get peer stats");
923            assert_eq!(peer_stats.max_probability, 9);
924        }
925        // We switch both max_probability and max_tp to rate 9 after enough observed failures on rate 13.
926        let peer_stats =
927            minstrel.get_fidl_peer_stats(&TEST_MAC_ADDR).expect("Failed to get peer stats");
928        assert_eq!(peer_stats.max_probability, 9);
929        assert_eq!(peer_stats.max_tp, 9);
930    }
931
932    #[test]
933    fn ht_rates_preferred() {
934        let ht_tx_result_failed = make_tx_result(
935            vec![
936                (HT_START_IDX + 15, 1),
937                (HT_START_IDX + 14, 1),
938                (HT_START_IDX + 13, 1),
939                (HT_START_IDX + 12, 1),
940                (HT_START_IDX + 11, 1),
941                (HT_START_IDX + 10, 1),
942                (HT_START_IDX + 9, 1),
943                (HT_START_IDX + 8, 1),
944            ],
945            false,
946        );
947        let ht_tx_result_success = make_tx_result(
948            vec![
949                (HT_START_IDX + 7, 1),
950                (HT_START_IDX + 6, 1),
951                (HT_START_IDX + 5, 1),
952                (HT_START_IDX + 4, 1),
953                (HT_START_IDX + 3, 1),
954                (HT_START_IDX + 2, 1),
955                (HT_START_IDX + 1, 1),
956                // MCS 0 succeeds with success probability 11% (1/9).
957                // This is greater than the cutoff of 1.0 - MINSTREL_PROBABILITY_THRESHOLD == 0.1.
958                (HT_START_IDX + 0, 9),
959            ],
960            true,
961        );
962        // Highest ERP rate succeeds with 100% probability.
963        let erp_tx_result_success =
964            make_tx_result(vec![(ERP_START_IDX + ERP_NUM_TX_VECTOR as u16 - 1, 1)], true);
965
966        let (mut minstrel, _timer) = mock_minstrel();
967        minstrel.add_peer(&ht_assoc_cfg()).expect("Failed to add peer.");
968        minstrel.handle_tx_result_report(&ht_tx_result_failed);
969        minstrel.handle_tx_result_report(&ht_tx_result_success);
970        minstrel.handle_tx_result_report(&erp_tx_result_success);
971        minstrel.handle_timeout();
972
973        let peer_stats =
974            minstrel.get_fidl_peer_stats(&TEST_MAC_ADDR).expect("Failed to get peer stats");
975        // HT should be selected over ERP for max_tp and max_probability despite lower performance.
976        assert_eq!(peer_stats.max_probability, HT_START_IDX);
977        assert_eq!(peer_stats.max_tp, HT_START_IDX);
978    }
979
980    #[test]
981    fn add_missing_rates() {
982        let (mut minstrel, _timer) = mock_minstrel();
983        let mut assoc_cfg = ht_assoc_cfg();
984        // Remove top rates 96 and 108 from the supported list.
985        let reduced_supported_rates = vec![2, 4, 11, 22, 12, 18, 24, 36, 48, 72];
986        assoc_cfg.rates = Some(reduced_supported_rates);
987        minstrel.add_peer(&assoc_cfg).expect("Failed to add peer.");
988
989        let rate_108 = ERP_START_IDX + ERP_NUM_TX_VECTOR as u16 - 1; // ERP, CBW20, GI 800 ns
990        let rate_72 = ERP_START_IDX + ERP_NUM_TX_VECTOR as u16 - 3;
991
992        // We should not have any stats for rate 108.
993        let peer_stats =
994            minstrel.get_fidl_peer_stats(&TEST_MAC_ADDR).expect("Failed to get peer stats");
995        assert!(!peer_stats.entries.iter().any(|entry| entry.tx_vector_idx == rate_108));
996
997        // Fail transmission at unsupported rate 108, then succeed at 72.
998        let tx_result = make_tx_result(vec![(rate_108, 1), (rate_72, 1)], true);
999        minstrel.handle_tx_result_report(&tx_result);
1000        // Despite failure, we should now have stats for rate 108.
1001        let peer_stats =
1002            minstrel.get_fidl_peer_stats(&TEST_MAC_ADDR).expect("Failed to get peer stats");
1003        assert!(peer_stats.entries.iter().any(|entry| entry.tx_vector_idx == rate_108));
1004    }
1005
1006    #[track_caller]
1007    fn expect_probe_order(
1008        minstrel: &mut MinstrelRateSelector<MockTimerManager>,
1009        expected_probes: &[u16],
1010    ) {
1011        let mut probes_iter = expected_probes.iter();
1012        let max_tp =
1013            minstrel.get_fidl_peer_stats(&TEST_MAC_ADDR).expect("Failed to get peer stats").max_tp;
1014        let mut fc = FrameControl(0);
1015        fc.set_frame_type(FrameType::DATA);
1016        let flags = fidl_softmac::WlanTxInfoFlags::empty();
1017
1018        for i in 0..(PROBE_INTERVAL as usize * expected_probes.len()) {
1019            let tx_vec_idx = minstrel.get_tx_vector_idx(&fc, &TEST_MAC_ADDR, flags);
1020            if i % PROBE_INTERVAL as usize == PROBE_INTERVAL as usize - 1 {
1021                // We expect a probe now.
1022                assert_eq!(*tx_vec_idx.unwrap(), *probes_iter.next().unwrap());
1023            } else {
1024                assert_eq!(*tx_vec_idx.unwrap(), max_tp);
1025            }
1026        }
1027    }
1028
1029    #[test]
1030    fn expected_probe_order() {
1031        let (mut minstrel, _timer) = mock_minstrel();
1032        minstrel.add_peer(&ht_assoc_cfg()).expect("Failed to add peer.");
1033
1034        // We do not expect to probe rate 16 since it's max throughput,
1035        // or probe rate 136 since it's the highest basic rate.
1036        const EXPECTED_PROBES: [u16; 22] = [
1037            1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, /* 16 ,*/
1038            129, 130, 131, 132, 133, 134, 135, /* 136 */
1039        ];
1040
1041        expect_probe_order(&mut minstrel, &EXPECTED_PROBES[..]);
1042    }
1043
1044    #[test]
1045    fn skip_seen_probes() {
1046        let (mut minstrel, _timer) = mock_minstrel();
1047        minstrel.add_peer(&ht_assoc_cfg()).expect("Failed to add peer.");
1048        let tx_result = make_tx_result(vec![(16, 1), (15, 1), (14, 1)], true);
1049        minstrel.handle_tx_result_report(&tx_result);
1050        let tx_result = make_tx_result(vec![(13, 1)], true);
1051        minstrel.handle_tx_result_report(&tx_result);
1052
1053        // We skip rates 13-16 since we've recently attempted tx on them.
1054        const UPDATED_PROBES: [u16; 19] = [
1055            1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, /* 13, 14, 15, 16, */ 129, 130, 131, 132,
1056            133, 134, 135, /* 136 */
1057        ];
1058
1059        expect_probe_order(&mut minstrel, &UPDATED_PROBES[..]);
1060
1061        // Increment the probe cycle.
1062        minstrel.handle_timeout();
1063
1064        // We skip 14 since it's now our max_tp and 15-16 since they're low probability.
1065        // We probe 13 since we haven't attempted tx in the last probe cycle.
1066        const UPDATED_PROBES_2: [u16; 20] = [
1067            1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, /* 14, 15, 16, */ 129, 130, 131, 132,
1068            133, 134, 135, /* 136 */
1069        ];
1070
1071        expect_probe_order(&mut minstrel, &UPDATED_PROBES_2[..]);
1072    }
1073
1074    #[test]
1075    fn dead_probe_cycle_count() {
1076        let (mut minstrel, _timer) = mock_minstrel();
1077        minstrel.add_peer(&ht_assoc_cfg()).expect("Failed to add peer.");
1078        let tx_result = make_tx_result(vec![(16, 1), (15, 1), (14, 1)], true);
1079        minstrel.handle_tx_result_report(&tx_result);
1080        minstrel.handle_timeout();
1081
1082        // Probe rates 15 and 16 now have low probability and will not be probed.
1083        const EXPECTED_PROBES: [u16; 20] = [
1084            1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, /* 14, 15, 16, */ 129, 130, 131, 132,
1085            133, 134, 135, /* 136 */
1086        ];
1087        expect_probe_order(&mut minstrel, &EXPECTED_PROBES[..]);
1088
1089        for _ in 0..DEAD_PROBE_CYCLE_COUNT as usize {
1090            // Repeatedly increment probe_cycles_skipped for all rates.
1091            minstrel.outdated_peers.insert(*TEST_MAC_ADDR);
1092            minstrel.handle_timeout();
1093        }
1094
1095        // We've passed enough cycles to probe rates 15 and 16 despite a 0% observed success rate.
1096        const EXPECT_DEAD_PROBES: [u16; 22] = [
1097            1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, /* 14, */ 15, 16, 129, 130, 131, 132,
1098            133, 134, 135, /* 136 */
1099        ];
1100        expect_probe_order(&mut minstrel, &EXPECT_DEAD_PROBES[..]);
1101    }
1102
1103    #[test]
1104    fn max_slow_probe() {
1105        let (mut minstrel, _timer) = mock_minstrel();
1106        minstrel.add_peer(&ht_assoc_cfg()).expect("Failed to add peer.");
1107        // Rate 16 is max_tp, max_probability, and 100% success rate.
1108        minstrel.handle_tx_result_report(&make_tx_result(vec![(16, 1)], true));
1109        minstrel.handle_timeout();
1110
1111        const EXPECTED_PROBES: [u16; 22] = [
1112            1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, /* 16, */ 129, 130, 131, 132,
1113            133, 134, 135, /* 136 */
1114        ];
1115        for _ in 0..MAX_SLOW_PROBE {
1116            // Probe rate 1 MAX_SLOW_PROBE times, incrementing num_probe_cycles_done as we go.
1117            expect_probe_order(&mut minstrel, &EXPECTED_PROBES[..]);
1118            minstrel.handle_tx_result_report(&make_tx_result(vec![(1, 1)], true));
1119        }
1120
1121        // We should no longer probe rate 1 since it is slow compared to our selected rate 16.
1122        const NEW_EXPECTED_PROBES: [u16; 21] = [
1123            /*1, */ 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, /* 16, */ 129, 130,
1124            131, 132, 133, 134, 135, /* 136 */
1125        ];
1126        expect_probe_order(&mut minstrel, &NEW_EXPECTED_PROBES[..]);
1127
1128        // After a timeout we probe rate 1 again.
1129        minstrel.handle_timeout();
1130        expect_probe_order(&mut minstrel, &EXPECTED_PROBES[..]);
1131    }
1132
1133    #[track_caller]
1134    fn assert_data_rate(
1135        channel_bandwidth: fidl_common::ChannelBandwidth,
1136        gi: WlanGi,
1137        relative_mcs_idx: u8,
1138        expected_mbit_per_second: f64,
1139    ) {
1140        let tx_time = tx_time_ht(channel_bandwidth, gi, relative_mcs_idx);
1141        const BYTES_PER_MBIT: f64 = 125000.0;
1142        let mut expected_tx_time =
1143            (MINSTREL_FRAME_LENGTH as f64 / BYTES_PER_MBIT) / expected_mbit_per_second;
1144        if gi == WlanGi::G_400NS {
1145            // Add 800ns test interval for short gap tx times. This becomes significant at high data rates.
1146            expected_tx_time += Duration::from_nanos(800).as_secs_f64();
1147        }
1148        let actual_tx_time = tx_time.as_secs_f64();
1149        let ratio = expected_tx_time / actual_tx_time;
1150        assert!(ratio < 1.01 && ratio > 0.99);
1151    }
1152
1153    #[test]
1154    fn tx_time_ht_approx_values_cbw20() {
1155        // IEEE 802.11-2016 Tables 19-27 through 19-30 list data rates for CBW20. We test a sample here.
1156        assert_data_rate(fidl_common::ChannelBandwidth::Cbw20, WlanGi::G_800NS, 0, 6.5);
1157        assert_data_rate(fidl_common::ChannelBandwidth::Cbw20, WlanGi::G_400NS, 0, 7.2);
1158        assert_data_rate(fidl_common::ChannelBandwidth::Cbw20, WlanGi::G_800NS, 8, 13.0);
1159        assert_data_rate(fidl_common::ChannelBandwidth::Cbw20, WlanGi::G_400NS, 8, 14.4);
1160        assert_data_rate(fidl_common::ChannelBandwidth::Cbw20, WlanGi::G_800NS, 31, 260.0);
1161        assert_data_rate(fidl_common::ChannelBandwidth::Cbw20, WlanGi::G_400NS, 31, 288.9);
1162    }
1163
1164    #[test]
1165    fn tx_time_ht_approx_values_cbw40() {
1166        // IEEE 802.11-2016 Tables 19-32 through 19-34 list data rates for CBW40. We test a sample here.
1167        assert_data_rate(fidl_common::ChannelBandwidth::Cbw40, WlanGi::G_800NS, 0, 13.5);
1168        assert_data_rate(fidl_common::ChannelBandwidth::Cbw40, WlanGi::G_400NS, 0, 15.0);
1169        assert_data_rate(fidl_common::ChannelBandwidth::Cbw40, WlanGi::G_800NS, 8, 27.0);
1170        assert_data_rate(fidl_common::ChannelBandwidth::Cbw40, WlanGi::G_400NS, 8, 30.0);
1171        assert_data_rate(fidl_common::ChannelBandwidth::Cbw40, WlanGi::G_800NS, 31, 540.0);
1172        assert_data_rate(fidl_common::ChannelBandwidth::Cbw40, WlanGi::G_400NS, 31, 600.0);
1173    }
1174}