use crate::common::mac::WlanGi;
use crate::probe_sequence::{ProbeEntry, ProbeSequence};
use ieee80211::{MacAddr, MacAddrBytes};
use std::collections::{hash_map, HashMap, HashSet};
use std::time::Duration;
use tracing::{debug, error};
use wlan_common::ie::{HtCapabilities, RxMcsBitmask, SupportedRate};
use wlan_common::mac::FrameControl;
use wlan_common::tx_vector::{
TxVecIdx, TxVector, ERP_NUM_TX_VECTOR, ERP_START_IDX, HT_NUM_MCS, HT_NUM_UNIQUE_MCS,
};
use {
fidl_fuchsia_wlan_common as fidl_common, fidl_fuchsia_wlan_minstrel as fidl_minstrel,
fidl_fuchsia_wlan_softmac as fidl_softmac,
};
const ASSOC_CHAN_WIDTH: fidl_common::ChannelBandwidth = fidl_common::ChannelBandwidth::Cbw20;
const MCS_MASK_0_31: u128 = 0xFFFFFFFF;
const MINSTREL_FRAME_LENGTH: u32 = 1400; const MINSTREL_EXP_WEIGHT: f32 = 0.75; const MINSTREL_PROBABILITY_THRESHOLD: f32 = 0.9; const PROBE_INTERVAL: u8 = 16; const MAX_SLOW_PROBE: u64 = 2; const DEAD_PROBE_CYCLE_COUNT: u8 = 32; type TxStatsMap = HashMap<TxVecIdx, TxStats>;
struct TxStats {
tx_vector_idx: TxVecIdx,
perfect_tx_time: Duration, success_cur: u64, attempts_cur: u64, moving_avg_probability: f32, expected_throughput: f32, success_total: u64, attempts_total: u64, probe_cycles_skipped: u8, probes_total: u64,
}
impl TxStats {
fn new(tx_vector_idx: TxVecIdx) -> Self {
Self {
tx_vector_idx,
perfect_tx_time: Duration::ZERO,
success_cur: 0,
attempts_cur: 0,
moving_avg_probability: 1.0 - MINSTREL_PROBABILITY_THRESHOLD,
expected_throughput: 0.0,
success_total: 0,
attempts_total: 0,
probe_cycles_skipped: 0,
probes_total: 0,
}
}
fn phy_type_strictly_preferred_over(&self, other: &TxStats) -> bool {
self.tx_vector_idx.is_ht() && !other.tx_vector_idx.is_ht()
}
fn better_for_expected_throughput_than(&self, other: &TxStats) -> bool {
(self.expected_throughput, self.moving_avg_probability)
> (other.expected_throughput, other.moving_avg_probability)
}
fn better_for_reliable_transmission_than(&self, other: &TxStats) -> bool {
if self.moving_avg_probability > MINSTREL_PROBABILITY_THRESHOLD
&& other.moving_avg_probability > MINSTREL_PROBABILITY_THRESHOLD
{
self.expected_throughput > other.expected_throughput
} else {
self.moving_avg_probability > other.moving_avg_probability
}
}
}
impl From<&TxStats> for fidl_minstrel::StatsEntry {
fn from(stats: &TxStats) -> fidl_minstrel::StatsEntry {
fidl_minstrel::StatsEntry {
tx_vector_idx: *stats.tx_vector_idx,
tx_vec_desc: format!("{}", stats.tx_vector_idx),
success_cur: stats.success_cur,
attempts_cur: stats.attempts_cur,
probability: stats.moving_avg_probability,
cur_tp: stats.expected_throughput,
success_total: stats.success_total,
attempts_total: stats.attempts_total,
probes_total: stats.probes_total,
probe_cycles_skipped: stats.probe_cycles_skipped,
}
}
}
struct Peer {
addr: MacAddr,
tx_stats_map: TxStatsMap,
erp_rates: HashSet<TxVecIdx>,
highest_erp_rate: Option<TxVecIdx>, best_erp_for_reliability: Option<TxVecIdx>, best_expected_throughput: Option<TxVecIdx>, best_for_reliability: Option<TxVecIdx>, num_probe_cycles_done: u64,
num_pkt_until_next_probe: u8,
probes_total: u64,
probe_entry: ProbeEntry,
}
impl Peer {
fn from_assoc_cfg(assoc_cfg: &fidl_softmac::WlanAssociationConfig) -> Result<Self, zx::Status> {
let mut peer = Self {
addr: match assoc_cfg.bssid {
None => {
error!("Cannot create Peer without a BSSID.");
return Err(zx::Status::INTERNAL);
}
Some(bssid) => bssid.into(),
},
num_pkt_until_next_probe: PROBE_INTERVAL - 1,
tx_stats_map: Default::default(),
erp_rates: Default::default(),
highest_erp_rate: Default::default(),
best_erp_for_reliability: Default::default(),
best_expected_throughput: Default::default(),
best_for_reliability: Default::default(),
num_probe_cycles_done: Default::default(),
probes_total: Default::default(),
probe_entry: Default::default(),
};
if let Some(ht_cap) = assoc_cfg.ht_cap {
let mut ht_cap = HtCapabilities::from(ht_cap.clone());
let mut cap_info = ht_cap.ht_cap_info;
cap_info.set_short_gi_20(false);
cap_info.set_short_gi_40(false);
ht_cap.ht_cap_info = cap_info;
let mcs_set = ht_cap.mcs_set;
if (mcs_set.rx_mcs().0 & MCS_MASK_0_31) == 0 {
error!("Invalid AssocCtx. HT supported but no valid MCS: {:?}", mcs_set);
return Err(zx::Status::INTERNAL);
} else {
peer.add_ht(&ht_cap);
}
}
if let Some(rates) = &assoc_cfg.rates {
peer.erp_rates = peer.add_supported_erp(&rates[..]);
peer.highest_erp_rate = peer.erp_rates.iter().cloned().max();
}
debug!("tx_stats_map populated. size: {}", peer.tx_stats_map.len());
if peer.tx_stats_map.is_empty() {
error!("No usable rates for peer {}", &peer.addr);
return Err(zx::Status::INTERNAL);
}
Ok(peer)
}
fn handle_tx_result_report(&mut self, tx_result: &fidl_common::WlanTxResult) {
let mut last_attempted_idx = None;
for status_entry in &tx_result.tx_result_entry[..] {
let idx = match TxVecIdx::new(status_entry.tx_vector_idx) {
Some(idx) => idx,
None => break,
};
last_attempted_idx.replace(idx);
let stats = match self.tx_stats_map.entry(idx) {
hash_map::Entry::Occupied(val) => Some(val.into_mut()),
hash_map::Entry::Vacant(vacant) => {
idx.to_erp_rate().map(|rate| vacant.insert(erp_idx_stats(idx, rate)))
}
};
match stats {
Some(stats) => stats.attempts_cur += status_entry.attempts as u64,
None => {
last_attempted_idx.take();
debug!("error: Invalid TxVecIdx: {:?}", idx)
}
}
}
if let Some(idx) = last_attempted_idx {
if tx_result.result_code == fidl_common::WlanTxResultCode::Success {
self.tx_stats_map.get_mut(&idx).unwrap().success_cur += 1;
}
}
}
fn add_ht(&mut self, ht_cap: &HtCapabilities) {
let mut max_size = HT_NUM_MCS + ERP_NUM_TX_VECTOR; let cap_info = ht_cap.ht_cap_info;
let sgi_20 = cap_info.short_gi_20();
let sgi_40 = cap_info.short_gi_40();
if sgi_20 {
max_size += HT_NUM_MCS;
}
if ASSOC_CHAN_WIDTH == fidl_common::ChannelBandwidth::Cbw40 {
max_size += HT_NUM_MCS;
if sgi_40 {
max_size += HT_NUM_MCS;
}
}
debug!("max_size is {}", max_size);
self.tx_stats_map.reserve(max_size as usize);
let mcs_set = ht_cap.mcs_set;
self.add_supported_ht(
fidl_common::ChannelBandwidth::Cbw20,
WlanGi::G_800NS,
mcs_set.rx_mcs(),
);
if sgi_20 {
self.add_supported_ht(
fidl_common::ChannelBandwidth::Cbw20,
WlanGi::G_400NS,
mcs_set.rx_mcs(),
);
}
if ASSOC_CHAN_WIDTH == fidl_common::ChannelBandwidth::Cbw40 {
self.add_supported_ht(
fidl_common::ChannelBandwidth::Cbw40,
WlanGi::G_800NS,
mcs_set.rx_mcs(),
);
if sgi_40 {
self.add_supported_ht(
fidl_common::ChannelBandwidth::Cbw40,
WlanGi::G_400NS,
mcs_set.rx_mcs(),
);
}
}
debug!("TxStatsMap size: {}", self.tx_stats_map.len());
}
fn add_supported_ht(
&mut self,
channel_bandwidth: fidl_common::ChannelBandwidth,
gi: WlanGi,
mcs_set: RxMcsBitmask,
) {
let mut tx_stats_added = 0;
for mcs_idx in 0..HT_NUM_MCS {
if mcs_set.support(mcs_idx) {
let tx_vector =
TxVector::new(fidl_common::WlanPhyType::Ht, gi, channel_bandwidth, mcs_idx)
.expect("Should be a valid TxVector");
let tx_vector_idx = tx_vector.to_idx();
let perfect_tx_time = tx_time_ht(channel_bandwidth, gi, mcs_idx);
let tx_stats = TxStats { perfect_tx_time, ..TxStats::new(tx_vector_idx) };
self.tx_stats_map.insert(tx_vector_idx, tx_stats);
tx_stats_added += 1;
}
}
debug!(
"{} HTs added with channel_bandwidth={:?}, gi={:?}",
tx_stats_added, channel_bandwidth, gi
);
}
fn add_supported_erp(&mut self, rates: &[u8]) -> HashSet<TxVecIdx> {
let mut tx_stats_added = 0;
let basic_rates: HashSet<TxVecIdx> = rates
.iter()
.filter_map(|rate| {
let rate = SupportedRate(*rate);
let tx_vector = match TxVector::from_supported_rate(&rate) {
Ok(tx_vector) => Some(tx_vector),
Err(e) => {
error!("Could not create tx vector from supported rate: {}", e);
None
}
}?;
if tx_vector.phy() != fidl_common::WlanPhyType::Erp {
return None;
}
let tx_vector_idx = tx_vector.to_idx();
self.tx_stats_map.insert(tx_vector_idx, erp_idx_stats(tx_vector_idx, rate));
tx_stats_added += 1;
if rate.basic() {
Some(tx_vector_idx)
} else {
None
}
})
.collect();
debug!("{} ERP added.", tx_stats_added);
if basic_rates.is_empty() {
vec![TxVecIdx::new(ERP_START_IDX).unwrap()].into_iter().collect()
} else {
basic_rates
}
}
fn update_stats(&mut self) {
for tx_stats in self.tx_stats_map.values_mut() {
if tx_stats.attempts_cur != 0 {
let probability = tx_stats.success_cur as f32 / tx_stats.attempts_cur as f32;
if tx_stats.attempts_total == 0 {
tx_stats.moving_avg_probability = probability;
} else {
tx_stats.moving_avg_probability = tx_stats.moving_avg_probability
* MINSTREL_EXP_WEIGHT
+ probability * (1.0 - MINSTREL_EXP_WEIGHT);
}
tx_stats.attempts_total += tx_stats.attempts_cur;
tx_stats.success_total += tx_stats.success_cur;
tx_stats.attempts_cur = 0;
tx_stats.success_cur = 0;
tx_stats.probe_cycles_skipped = 0;
} else {
tx_stats.probe_cycles_skipped = tx_stats.probe_cycles_skipped.saturating_add(1);
}
const NANOS_PER_SECOND: f32 = 1e9;
tx_stats.expected_throughput = NANOS_PER_SECOND
/ tx_stats.perfect_tx_time.as_nanos() as f32
* tx_stats.moving_avg_probability;
}
let arbitrary_rate = match self.tx_stats_map.iter().next() {
Some((tx_vec_idx, _)) => *tx_vec_idx,
None => return, };
let mut best_expected_throughput = arbitrary_rate;
let mut best_for_reliability = arbitrary_rate;
let mut best_erp_for_reliability = self.highest_erp_rate;
for (tx_vector_idx, tx_stats) in &self.tx_stats_map {
let best_throughput_stats = self.tx_stats_map.get(&best_expected_throughput).unwrap();
let best_reliability_stats = self.tx_stats_map.get(&best_for_reliability).unwrap();
if (!tx_unlikely(tx_stats)
&& tx_stats.phy_type_strictly_preferred_over(best_throughput_stats))
|| (tx_stats.better_for_expected_throughput_than(best_throughput_stats)
&& !(!tx_unlikely(best_throughput_stats)
&& best_throughput_stats.phy_type_strictly_preferred_over(tx_stats)))
{
best_expected_throughput = *tx_vector_idx;
}
if (!tx_unlikely(tx_stats)
&& tx_stats.phy_type_strictly_preferred_over(best_reliability_stats))
|| (tx_stats.better_for_reliable_transmission_than(best_reliability_stats)
&& !(!tx_unlikely(best_reliability_stats)
&& best_reliability_stats.phy_type_strictly_preferred_over(tx_stats)))
{
best_for_reliability = *tx_vector_idx;
}
if let Some(best_erp_for_reliability) = best_erp_for_reliability.as_mut() {
let best_erp_reliability_stats =
self.tx_stats_map.get(best_erp_for_reliability).unwrap();
if self.erp_rates.contains(tx_vector_idx)
&& tx_stats.better_for_reliable_transmission_than(best_erp_reliability_stats)
{
*best_erp_for_reliability = *tx_vector_idx;
}
}
}
self.best_expected_throughput = Some(best_expected_throughput);
self.best_for_reliability = Some(best_for_reliability);
self.best_erp_for_reliability = best_erp_for_reliability;
}
fn get_tx_vector_idx(
&mut self,
needs_reliability: bool,
probe_sequence: &ProbeSequence,
) -> Option<TxVecIdx> {
if needs_reliability {
self.best_for_reliability
} else if self.num_pkt_until_next_probe > 0 {
self.num_pkt_until_next_probe -= 1;
self.best_expected_throughput
} else {
self.num_pkt_until_next_probe = PROBE_INTERVAL - 1;
self.get_next_probe(probe_sequence)
}
}
fn get_next_probe(&mut self, probe_sequence: &ProbeSequence) -> Option<TxVecIdx> {
let slow_probe_cutoff = self.tx_stats_map.get(&self.best_for_reliability?)?.perfect_tx_time;
if self.tx_stats_map.len() == 1 {
return self.best_expected_throughput;
}
for _ in 0..self.tx_stats_map.len() {
let probe_idx = self.next_supported_probe_idx(probe_sequence);
let tx_stats = self.tx_stats_map.get_mut(&probe_idx).unwrap();
if Some(probe_idx) == self.best_erp_for_reliability.or(self.highest_erp_rate)
|| Some(probe_idx) == self.best_expected_throughput.or(self.best_for_reliability)
|| tx_stats.attempts_cur > self.num_probe_cycles_done
|| (tx_stats.perfect_tx_time > slow_probe_cutoff
&& tx_stats.attempts_cur >= MAX_SLOW_PROBE)
|| (tx_unlikely(tx_stats)
&& (tx_stats.probe_cycles_skipped < DEAD_PROBE_CYCLE_COUNT
|| tx_stats.attempts_cur > 0))
{
continue;
}
self.probes_total += 1;
tx_stats.probes_total += 1;
return Some(probe_idx);
}
return self.best_expected_throughput;
}
fn next_supported_probe_idx(&mut self, probe_sequence: &ProbeSequence) -> TxVecIdx {
assert!(
!self.tx_stats_map.is_empty(),
"Cannot call next_supported_probe_idx with empty tx_stats_map"
);
loop {
let idx = probe_sequence.next(&mut self.probe_entry);
if self.probe_entry.cycle_complete() {
self.num_probe_cycles_done += 1;
}
if self.tx_stats_map.contains_key(&idx) {
return idx;
}
}
}
}
fn tx_unlikely(tx_stats: &TxStats) -> bool {
tx_stats.moving_avg_probability < 1.0 - MINSTREL_PROBABILITY_THRESHOLD
}
pub trait TimerManager {
fn schedule(&mut self, from_now: Duration);
fn cancel(&mut self);
}
pub struct MinstrelRateSelector<T: TimerManager> {
timer_manager: T,
update_interval: Duration,
probe_sequence: ProbeSequence,
peer_map: HashMap<MacAddr, Peer>,
outdated_peers: HashSet<MacAddr>,
}
impl<T: TimerManager> MinstrelRateSelector<T> {
pub fn new(timer_manager: T, update_interval: Duration, probe_sequence: ProbeSequence) -> Self {
Self {
timer_manager,
update_interval,
probe_sequence,
peer_map: Default::default(),
outdated_peers: Default::default(),
}
}
pub fn add_peer(
&mut self,
assoc_cfg: &fidl_softmac::WlanAssociationConfig,
) -> Result<(), zx::Status> {
let bssid: MacAddr = match assoc_cfg.bssid {
None => {
error!("Attempted to add peer with no BSSID.");
return Err(zx::Status::INTERNAL);
}
Some(bssid) => bssid.into(),
};
if self.peer_map.contains_key(&bssid) {
error!("Attempted to add peer {} twice.", &bssid);
} else {
let mut peer = Peer::from_assoc_cfg(assoc_cfg)?;
if self.peer_map.is_empty() {
self.timer_manager.schedule(self.update_interval);
}
peer.update_stats();
self.peer_map.insert(bssid, peer);
}
Ok(())
}
pub fn remove_peer(&mut self, addr: &MacAddr) {
self.outdated_peers.remove(addr);
match self.peer_map.remove(addr) {
Some(_) => debug!("Peer {} removed.", addr),
None => debug!("Cannot remove peer {}, not found.", addr),
}
if self.peer_map.is_empty() {
self.timer_manager.cancel();
}
}
pub fn handle_tx_result_report(&mut self, tx_result: &fidl_common::WlanTxResult) {
let peer_addr: MacAddr = tx_result.peer_addr.into();
match self.peer_map.get_mut(&peer_addr) {
Some(peer) => {
peer.handle_tx_result_report(tx_result);
self.outdated_peers.insert(peer_addr);
}
None => {
debug!("Peer {} received tx status report after it was removed.", peer_addr);
}
}
}
fn update_stats(&mut self) {
for outdated_peer in self.outdated_peers.drain() {
self.peer_map.get_mut(&outdated_peer).map(|peer| peer.update_stats());
}
}
pub fn handle_timeout(&mut self) {
self.timer_manager.schedule(self.update_interval);
self.update_stats();
}
pub fn get_tx_vector_idx(
&mut self,
frame_control: &FrameControl,
peer_addr: &MacAddr,
flags: fidl_softmac::WlanTxInfoFlags,
) -> Option<TxVecIdx> {
match self.peer_map.get_mut(peer_addr) {
None => TxVecIdx::new(ERP_START_IDX + ERP_NUM_TX_VECTOR as u16 - 1),
Some(peer) => {
if frame_control.is_data() {
let needs_reliability =
flags.contains(fidl_softmac::WlanTxInfoFlags::FAVOR_RELIABILITY);
peer.get_tx_vector_idx(needs_reliability, &self.probe_sequence)
} else {
peer.best_erp_for_reliability
}
}
}
}
pub fn get_fidl_peers(&self) -> fidl_minstrel::Peers {
fidl_minstrel::Peers {
addrs: self.peer_map.iter().map(|(peer, _)| peer.to_array()).collect(),
}
}
pub fn get_fidl_peer_stats(
&self,
peer_addr: &MacAddr,
) -> Result<fidl_minstrel::Peer, zx::Status> {
let peer = self.peer_map.get(peer_addr).ok_or(zx::Status::NOT_FOUND)?;
Ok(fidl_minstrel::Peer {
addr: peer_addr.to_array(),
max_tp: tx_vec_idx_opt_to_u16(&peer.best_expected_throughput),
max_probability: tx_vec_idx_opt_to_u16(&peer.best_for_reliability),
basic_highest: tx_vec_idx_opt_to_u16(&peer.highest_erp_rate),
basic_max_probability: tx_vec_idx_opt_to_u16(&peer.best_erp_for_reliability),
probes: peer.probes_total,
entries: peer.tx_stats_map.iter().map(|(_, entry)| entry.into()).collect(),
})
}
pub fn is_active(&self) -> bool {
!self.peer_map.is_empty()
}
}
fn tx_vec_idx_opt_to_u16(tx_vec_idx: &Option<TxVecIdx>) -> u16 {
match tx_vec_idx {
Some(idx) => **idx,
None => 0,
}
}
fn tx_time_ht(
channel_bandwidth: fidl_common::ChannelBandwidth,
gi: WlanGi,
relative_mcs_idx: u8,
) -> Duration {
header_tx_time_ht() + payload_tx_time_ht(channel_bandwidth, gi, relative_mcs_idx)
}
fn header_tx_time_ht() -> Duration {
Duration::ZERO
}
fn payload_tx_time_ht(
channel_bandwidth: fidl_common::ChannelBandwidth,
gi: WlanGi,
mcs_idx: u8,
) -> Duration {
const BITS_PER_SYMBOL_LIST: [u16; HT_NUM_UNIQUE_MCS as usize + 2] =
[26, 52, 78, 104, 156, 208, 234, 260, 312, 347];
const DATA_SUB_CARRIERS_20: u16 = 52;
const DATA_SUB_CARRIERS_40: u16 = 108;
let nss = 1 + mcs_idx / HT_NUM_UNIQUE_MCS;
let relative_mcs_idx = mcs_idx % HT_NUM_UNIQUE_MCS;
let bits_per_symbol = if channel_bandwidth == fidl_common::ChannelBandwidth::Cbw40 {
BITS_PER_SYMBOL_LIST[relative_mcs_idx as usize] * DATA_SUB_CARRIERS_40
/ DATA_SUB_CARRIERS_20
} else {
BITS_PER_SYMBOL_LIST[relative_mcs_idx as usize]
};
const TX_TIME_PER_SYMBOL_GI_800: Duration = Duration::from_nanos(4000);
const TX_TIME_PER_SYMBOL_GI_400: Duration = Duration::from_nanos(3600);
const TX_TIME_PADDING_GI_400: Duration = Duration::from_nanos(800);
match gi {
WlanGi::G_400NS => {
TX_TIME_PADDING_GI_400
+ (TX_TIME_PER_SYMBOL_GI_400 * 8 * MINSTREL_FRAME_LENGTH)
/ (nss as u32 * bits_per_symbol as u32)
}
WlanGi::G_800NS => {
(TX_TIME_PER_SYMBOL_GI_800 * 8 * MINSTREL_FRAME_LENGTH)
/ (nss as u32 * bits_per_symbol as u32)
}
_ => panic!("payload_tx_time_ht is invalid for non-ht phy"),
}
}
fn tx_time_erp(rate: &SupportedRate) -> Duration {
header_tx_time_erp() + payload_tx_time_erp(rate)
}
fn header_tx_time_erp() -> Duration {
Duration::ZERO
}
fn payload_tx_time_erp(rate: &SupportedRate) -> Duration {
let bits_per_symbol = rate.rate() * 2;
const TX_TIME_PER_SYMBOL: Duration = Duration::from_nanos(4000);
TX_TIME_PER_SYMBOL * 8 * MINSTREL_FRAME_LENGTH / bits_per_symbol as u32
}
fn erp_idx_stats(tx_vector_idx: TxVecIdx, rate: SupportedRate) -> TxStats {
let perfect_tx_time = tx_time_erp(&rate);
TxStats { perfect_tx_time, ..TxStats::new(tx_vector_idx) }
}
#[cfg(test)]
mod tests {
use super::*;
use fidl_fuchsia_wlan_common as fidl_common;
use lazy_static::lazy_static;
use std::sync::{Arc, Mutex};
use wlan_common::ie::{ChanWidthSet, HtCapabilityInfo};
use wlan_common::mac::FrameType;
use wlan_common::tx_vector::HT_START_IDX;
struct MockTimerManager {
scheduled: Arc<Mutex<Option<Duration>>>,
}
impl TimerManager for MockTimerManager {
fn schedule(&mut self, from_now: Duration) {
let mut scheduled = self.scheduled.lock().unwrap();
scheduled.replace(from_now);
}
fn cancel(&mut self) {
let mut scheduled = self.scheduled.lock().unwrap();
scheduled.take();
}
}
fn mock_minstrel() -> (MinstrelRateSelector<MockTimerManager>, Arc<Mutex<Option<Duration>>>) {
let timer = Arc::new(Mutex::new(None));
let timer_manager = MockTimerManager { scheduled: timer.clone() };
let update_interval = Duration::from_micros(100);
let probe_sequence = ProbeSequence::sequential();
(MinstrelRateSelector::new(timer_manager, update_interval, probe_sequence), timer)
}
lazy_static! {
static ref TEST_MAC_ADDR: MacAddr = MacAddr::from([50, 53, 51, 56, 55, 52]);
}
const BASIC_RATE_BIT: u8 = 0b10000000;
fn ht_assoc_cfg() -> fidl_softmac::WlanAssociationConfig {
let mut ht_cap = wlan_common::ie::fake_ht_capabilities();
let mut ht_cap_info = HtCapabilityInfo(0);
ht_cap_info.set_short_gi_40(true);
ht_cap_info.set_short_gi_20(true);
ht_cap_info.set_chan_width_set(ChanWidthSet::TWENTY_FORTY);
ht_cap.ht_cap_info = ht_cap_info;
ht_cap.mcs_set.0 = 0xffff; fidl_softmac::WlanAssociationConfig {
bssid: Some(TEST_MAC_ADDR.to_array()),
aid: Some(42),
listen_interval: Some(0),
channel: Some(fidl_common::WlanChannel {
primary: 149,
cbw: fidl_common::ChannelBandwidth::Cbw40,
secondary80: 0,
}),
qos: Some(true),
wmm_params: None,
rates: Some(vec![
2,
4,
11,
22,
12 | BASIC_RATE_BIT,
18,
24,
36,
48,
72,
96,
108 | BASIC_RATE_BIT,
]),
capability_info: Some(0),
ht_cap: Some(ht_cap.into()),
ht_op: None,
vht_cap: None,
vht_op: None,
..Default::default()
}
}
#[test]
fn peer_from_assoc_cfg() {
let assoc_cfg = ht_assoc_cfg();
let peer = Peer::from_assoc_cfg(&assoc_cfg)
.expect("Failed to convert WlanAssociationConfig into Peer.");
assert_eq!(peer.addr, assoc_cfg.bssid.unwrap().into());
assert_eq!(peer.tx_stats_map.len(), 24);
let mut peer_rates = peer
.tx_stats_map
.keys()
.into_iter()
.map(|tx_vector_idx| **tx_vector_idx)
.collect::<Vec<u16>>();
peer_rates.sort();
assert_eq!(
peer_rates,
vec![
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 129, 130, 131, 132, 133, 134, 135, 136, ]
);
let mut peer_erp_rates =
peer.erp_rates.iter().map(|tx_vector_idx| **tx_vector_idx).collect::<Vec<u16>>();
peer_erp_rates.sort();
let expected_basic_rate_1 =
TxVector::from_supported_rate(&SupportedRate(12 | BASIC_RATE_BIT)).unwrap().to_idx();
let expected_basic_rate_2 =
TxVector::from_supported_rate(&SupportedRate(108 | BASIC_RATE_BIT)).unwrap().to_idx();
assert_eq!(peer_erp_rates, vec![*expected_basic_rate_1, *expected_basic_rate_2]);
assert_eq!(peer.highest_erp_rate, TxVecIdx::new(136));
}
#[test]
fn add_peer() {
let (mut minstrel, timer) = mock_minstrel();
assert!(timer.lock().unwrap().is_none()); minstrel.add_peer(&ht_assoc_cfg()).expect("Failed to add peer.");
assert!(timer.lock().unwrap().is_some()); let peers = minstrel.get_fidl_peers();
assert_eq!(peers.addrs.len(), 1);
let peer_addr: MacAddr = {
let mut peer_addr = [0u8; 6];
peer_addr.copy_from_slice(&peers.addrs[0][..]);
peer_addr.into()
};
let peer_stats =
minstrel.get_fidl_peer_stats(&peer_addr).expect("Failed to get peer stats");
assert_eq!(&peer_stats.addr, TEST_MAC_ADDR.as_array());
assert_eq!(peer_stats.entries.len(), 24);
assert_eq!(peer_stats.max_tp, 16); assert_eq!(peer_stats.basic_highest, ERP_START_IDX + ERP_NUM_TX_VECTOR as u16 - 1);
assert_eq!(peer_stats.basic_max_probability, ERP_START_IDX + ERP_NUM_TX_VECTOR as u16 - 1);
}
#[test]
fn remove_peer() {
let (mut minstrel, timer) = mock_minstrel();
minstrel.add_peer(&ht_assoc_cfg()).expect("Failed to add peer.");
assert_eq!(minstrel.get_fidl_peers().addrs.len(), 1);
assert!(timer.lock().unwrap().is_some()); minstrel.remove_peer(&TEST_MAC_ADDR);
assert!(timer.lock().unwrap().is_none()); assert!(minstrel.get_fidl_peers().addrs.is_empty());
assert_eq!(minstrel.get_fidl_peer_stats(&TEST_MAC_ADDR), Err(zx::Status::NOT_FOUND));
}
#[test]
fn remove_second_peer() {
let (mut minstrel, timer) = mock_minstrel();
minstrel.add_peer(&ht_assoc_cfg()).expect("Failed to add peer.");
let mut peer2 = ht_assoc_cfg();
peer2.bssid = Some([11, 12, 13, 14, 15, 16]);
minstrel.add_peer(&peer2).expect("Failed to add peer.");
assert_eq!(minstrel.get_fidl_peers().addrs.len(), 2);
assert!(timer.lock().unwrap().is_some()); minstrel.remove_peer(&TEST_MAC_ADDR);
assert_eq!(minstrel.get_fidl_peers().addrs.len(), 1);
assert!(timer.lock().unwrap().is_some()); assert_eq!(minstrel.get_fidl_peer_stats(&TEST_MAC_ADDR), Err(zx::Status::NOT_FOUND));
assert!(minstrel.get_fidl_peer_stats(&peer2.bssid.unwrap().into()).is_ok());
}
fn make_tx_result(entries: Vec<(u16, u8)>, success: bool) -> fidl_common::WlanTxResult {
assert!(entries.len() <= 8);
let mut tx_result_entry =
[fidl_common::WlanTxResultEntry { tx_vector_idx: 0, attempts: 0 }; 8];
tx_result_entry[0..entries.len()].copy_from_slice(
&entries
.into_iter()
.map(|(tx_vector_idx, attempts)| fidl_common::WlanTxResultEntry {
tx_vector_idx,
attempts,
})
.collect::<Vec<fidl_common::WlanTxResultEntry>>()[..],
);
let result_code = if success {
fidl_common::WlanTxResultCode::Success
} else {
fidl_common::WlanTxResultCode::Failed
};
fidl_common::WlanTxResult {
tx_result_entry,
peer_addr: TEST_MAC_ADDR.to_array(),
result_code,
}
}
#[test]
fn handle_tx_result_reports() {
let tx_result = make_tx_result(vec![(16, 1), (15, 1), (14, 1), (13, 1)], true);
let (mut minstrel, _timer) = mock_minstrel();
minstrel.add_peer(&ht_assoc_cfg()).expect("Failed to add peer.");
minstrel.handle_tx_result_report(&tx_result);
let peer_stats =
minstrel.get_fidl_peer_stats(&TEST_MAC_ADDR).expect("Failed to get peer stats");
assert_eq!(peer_stats.max_tp, 16);
minstrel.handle_timeout();
let peer_stats =
minstrel.get_fidl_peer_stats(&TEST_MAC_ADDR).expect("Failed to get peer stats");
assert_eq!(peer_stats.max_tp, 13);
assert_eq!(peer_stats.max_probability, 13);
let tx_result = make_tx_result(vec![(13, 1), (9, 1)], true);
for _ in 0..10 {
minstrel.handle_tx_result_report(&tx_result);
minstrel.handle_timeout();
let peer_stats =
minstrel.get_fidl_peer_stats(&TEST_MAC_ADDR).expect("Failed to get peer stats");
assert_eq!(peer_stats.max_probability, 9);
}
let peer_stats =
minstrel.get_fidl_peer_stats(&TEST_MAC_ADDR).expect("Failed to get peer stats");
assert_eq!(peer_stats.max_probability, 9);
assert_eq!(peer_stats.max_tp, 9);
}
#[test]
fn ht_rates_preferred() {
let ht_tx_result_failed = make_tx_result(
vec![
(HT_START_IDX + 15, 1),
(HT_START_IDX + 14, 1),
(HT_START_IDX + 13, 1),
(HT_START_IDX + 12, 1),
(HT_START_IDX + 11, 1),
(HT_START_IDX + 10, 1),
(HT_START_IDX + 9, 1),
(HT_START_IDX + 8, 1),
],
false,
);
let ht_tx_result_success = make_tx_result(
vec![
(HT_START_IDX + 7, 1),
(HT_START_IDX + 6, 1),
(HT_START_IDX + 5, 1),
(HT_START_IDX + 4, 1),
(HT_START_IDX + 3, 1),
(HT_START_IDX + 2, 1),
(HT_START_IDX + 1, 1),
(HT_START_IDX + 0, 9),
],
true,
);
let erp_tx_result_success =
make_tx_result(vec![(ERP_START_IDX + ERP_NUM_TX_VECTOR as u16 - 1, 1)], true);
let (mut minstrel, _timer) = mock_minstrel();
minstrel.add_peer(&ht_assoc_cfg()).expect("Failed to add peer.");
minstrel.handle_tx_result_report(&ht_tx_result_failed);
minstrel.handle_tx_result_report(&ht_tx_result_success);
minstrel.handle_tx_result_report(&erp_tx_result_success);
minstrel.handle_timeout();
let peer_stats =
minstrel.get_fidl_peer_stats(&TEST_MAC_ADDR).expect("Failed to get peer stats");
assert_eq!(peer_stats.max_probability, HT_START_IDX);
assert_eq!(peer_stats.max_tp, HT_START_IDX);
}
#[test]
fn add_missing_rates() {
let (mut minstrel, _timer) = mock_minstrel();
let mut assoc_cfg = ht_assoc_cfg();
let reduced_supported_rates = vec![2, 4, 11, 22, 12, 18, 24, 36, 48, 72];
assoc_cfg.rates = Some(reduced_supported_rates);
minstrel.add_peer(&assoc_cfg).expect("Failed to add peer.");
let rate_108 = ERP_START_IDX + ERP_NUM_TX_VECTOR as u16 - 1; let rate_72 = ERP_START_IDX + ERP_NUM_TX_VECTOR as u16 - 3;
let peer_stats =
minstrel.get_fidl_peer_stats(&TEST_MAC_ADDR).expect("Failed to get peer stats");
assert!(!peer_stats.entries.iter().any(|entry| entry.tx_vector_idx == rate_108));
let tx_result = make_tx_result(vec![(rate_108, 1), (rate_72, 1)], true);
minstrel.handle_tx_result_report(&tx_result);
let peer_stats =
minstrel.get_fidl_peer_stats(&TEST_MAC_ADDR).expect("Failed to get peer stats");
assert!(peer_stats.entries.iter().any(|entry| entry.tx_vector_idx == rate_108));
}
#[track_caller]
fn expect_probe_order(
minstrel: &mut MinstrelRateSelector<MockTimerManager>,
expected_probes: &[u16],
) {
let mut probes_iter = expected_probes.iter();
let max_tp =
minstrel.get_fidl_peer_stats(&TEST_MAC_ADDR).expect("Failed to get peer stats").max_tp;
let mut fc = FrameControl(0);
fc.set_frame_type(FrameType::DATA);
let flags = fidl_softmac::WlanTxInfoFlags::empty();
for i in 0..(PROBE_INTERVAL as usize * expected_probes.len()) {
let tx_vec_idx = minstrel.get_tx_vector_idx(&fc, &TEST_MAC_ADDR, flags);
if i % PROBE_INTERVAL as usize == PROBE_INTERVAL as usize - 1 {
assert_eq!(*tx_vec_idx.unwrap(), *probes_iter.next().unwrap());
} else {
assert_eq!(*tx_vec_idx.unwrap(), max_tp);
}
}
}
#[test]
fn expected_probe_order() {
let (mut minstrel, _timer) = mock_minstrel();
minstrel.add_peer(&ht_assoc_cfg()).expect("Failed to add peer.");
const EXPECTED_PROBES: [u16; 22] = [
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 129, 130, 131, 132, 133, 134, 135, ];
expect_probe_order(&mut minstrel, &EXPECTED_PROBES[..]);
}
#[test]
fn skip_seen_probes() {
let (mut minstrel, _timer) = mock_minstrel();
minstrel.add_peer(&ht_assoc_cfg()).expect("Failed to add peer.");
let tx_result = make_tx_result(vec![(16, 1), (15, 1), (14, 1)], true);
minstrel.handle_tx_result_report(&tx_result);
let tx_result = make_tx_result(vec![(13, 1)], true);
minstrel.handle_tx_result_report(&tx_result);
const UPDATED_PROBES: [u16; 19] = [
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 129, 130, 131, 132,
133, 134, 135, ];
expect_probe_order(&mut minstrel, &UPDATED_PROBES[..]);
minstrel.handle_timeout();
const UPDATED_PROBES_2: [u16; 20] = [
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 129, 130, 131, 132,
133, 134, 135, ];
expect_probe_order(&mut minstrel, &UPDATED_PROBES_2[..]);
}
#[test]
fn dead_probe_cycle_count() {
let (mut minstrel, _timer) = mock_minstrel();
minstrel.add_peer(&ht_assoc_cfg()).expect("Failed to add peer.");
let tx_result = make_tx_result(vec![(16, 1), (15, 1), (14, 1)], true);
minstrel.handle_tx_result_report(&tx_result);
minstrel.handle_timeout();
const EXPECTED_PROBES: [u16; 20] = [
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 129, 130, 131, 132,
133, 134, 135, ];
expect_probe_order(&mut minstrel, &EXPECTED_PROBES[..]);
for _ in 0..DEAD_PROBE_CYCLE_COUNT as usize {
minstrel.outdated_peers.insert(*TEST_MAC_ADDR);
minstrel.handle_timeout();
}
const EXPECT_DEAD_PROBES: [u16; 22] = [
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 15, 16, 129, 130, 131, 132,
133, 134, 135, ];
expect_probe_order(&mut minstrel, &EXPECT_DEAD_PROBES[..]);
}
#[test]
fn max_slow_probe() {
let (mut minstrel, _timer) = mock_minstrel();
minstrel.add_peer(&ht_assoc_cfg()).expect("Failed to add peer.");
minstrel.handle_tx_result_report(&make_tx_result(vec![(16, 1)], true));
minstrel.handle_timeout();
const EXPECTED_PROBES: [u16; 22] = [
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 129, 130, 131, 132,
133, 134, 135, ];
for _ in 0..MAX_SLOW_PROBE {
expect_probe_order(&mut minstrel, &EXPECTED_PROBES[..]);
minstrel.handle_tx_result_report(&make_tx_result(vec![(1, 1)], true));
}
const NEW_EXPECTED_PROBES: [u16; 21] = [
2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 129, 130,
131, 132, 133, 134, 135, ];
expect_probe_order(&mut minstrel, &NEW_EXPECTED_PROBES[..]);
minstrel.handle_timeout();
expect_probe_order(&mut minstrel, &EXPECTED_PROBES[..]);
}
#[track_caller]
fn assert_data_rate(
channel_bandwidth: fidl_common::ChannelBandwidth,
gi: WlanGi,
relative_mcs_idx: u8,
expected_mbit_per_second: f64,
) {
let tx_time = tx_time_ht(channel_bandwidth, gi, relative_mcs_idx);
const BYTES_PER_MBIT: f64 = 125000.0;
let mut expected_tx_time =
(MINSTREL_FRAME_LENGTH as f64 / BYTES_PER_MBIT) / expected_mbit_per_second;
if gi == WlanGi::G_400NS {
expected_tx_time += Duration::from_nanos(800).as_secs_f64();
}
let actual_tx_time = tx_time.as_secs_f64();
let ratio = expected_tx_time / actual_tx_time;
assert!(ratio < 1.01 && ratio > 0.99);
}
#[test]
fn tx_time_ht_approx_values_cbw20() {
assert_data_rate(fidl_common::ChannelBandwidth::Cbw20, WlanGi::G_800NS, 0, 6.5);
assert_data_rate(fidl_common::ChannelBandwidth::Cbw20, WlanGi::G_400NS, 0, 7.2);
assert_data_rate(fidl_common::ChannelBandwidth::Cbw20, WlanGi::G_800NS, 8, 13.0);
assert_data_rate(fidl_common::ChannelBandwidth::Cbw20, WlanGi::G_400NS, 8, 14.4);
assert_data_rate(fidl_common::ChannelBandwidth::Cbw20, WlanGi::G_800NS, 31, 260.0);
assert_data_rate(fidl_common::ChannelBandwidth::Cbw20, WlanGi::G_400NS, 31, 288.9);
}
#[test]
fn tx_time_ht_approx_values_cbw40() {
assert_data_rate(fidl_common::ChannelBandwidth::Cbw40, WlanGi::G_800NS, 0, 13.5);
assert_data_rate(fidl_common::ChannelBandwidth::Cbw40, WlanGi::G_400NS, 0, 15.0);
assert_data_rate(fidl_common::ChannelBandwidth::Cbw40, WlanGi::G_800NS, 8, 27.0);
assert_data_rate(fidl_common::ChannelBandwidth::Cbw40, WlanGi::G_400NS, 8, 30.0);
assert_data_rate(fidl_common::ChannelBandwidth::Cbw40, WlanGi::G_800NS, 31, 540.0);
assert_data_rate(fidl_common::ChannelBandwidth::Cbw40, WlanGi::G_400NS, 31, 600.0);
}
}