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