blob: d4ba850675bd4f9b65743233125afc30af2bb2e8 [file] [log] [blame]
// Copyright 2018 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use fidl_fuchsia_wlan_common as fidl_common;
use fidl_fuchsia_wlan_mlme as fidl_mlme;
use log::error;
use wlan_common::RadioConfig;
use crate::DeviceInfo;
fn convert_chanwidth_to_cbw(chan_width_set: u8) -> fidl_common::Cbw {
if chan_width_set == fidl_mlme::ChanWidthSet::TwentyOnly as u8 {
fidl_common::Cbw::Cbw20
} else {
fidl_common::Cbw::Cbw40Below
}
}
fn convert_secchan_offset_to_cbw(secchan_offset: u8) -> fidl_common::Cbw {
if secchan_offset == fidl_mlme::SecChanOffset::SecondaryAbove as u8 {
fidl_common::Cbw::Cbw40
} else if secchan_offset == fidl_mlme::SecChanOffset::SecondaryBelow as u8 {
fidl_common::Cbw::Cbw40Below
} else {
fidl_common::Cbw::Cbw20
}
}
fn convert_vht_segments_to_cbw(seg0: u8, seg1: u8) -> fidl_common::Cbw {
// See IEEE Std 802.11-2016, Table 9-253
let gap = if seg0 >= seg1 { seg0 - seg1 } else { seg1 - seg0 };
if seg1 == 0 {
fidl_common::Cbw::Cbw80
} else if gap == 8 {
fidl_common::Cbw::Cbw160
} else if gap > 16 {
fidl_common::Cbw::Cbw80P80
} else {
fidl_common::Cbw::Cbw80
}
}
fn derive_cbw_ht(
client_ht_cap: &fidl_mlme::HtCapabilities,
bss_ht_op: &fidl_mlme::HtOperation,
) -> fidl_common::Cbw {
let ap_cbw = convert_secchan_offset_to_cbw(bss_ht_op.ht_op_info.secondary_chan_offset);
let client_cbw = convert_chanwidth_to_cbw(client_ht_cap.ht_cap_info.chan_width_set);
std::cmp::min(client_cbw, ap_cbw)
}
fn derive_cbw_vht(
client_ht_cap: &fidl_mlme::HtCapabilities,
_client_vht_cap: &fidl_mlme::VhtCapabilities,
bss_ht_op: &fidl_mlme::HtOperation,
bss_vht_op: &fidl_mlme::VhtOperation,
) -> fidl_common::Cbw {
// Derive CBW from AP's VHT IEs
let ap_cbw = if bss_vht_op.vht_cbw == fidl_mlme::VhtCbw::Cbw8016080P80 as u8 {
convert_vht_segments_to_cbw(bss_vht_op.center_freq_seg0, bss_vht_op.center_freq_seg1)
} else {
derive_cbw_ht(client_ht_cap, bss_ht_op)
};
// TODO(NET-1575): Support CBW160 and CBW80P80
// See IEEE Std 802.11-2016 table 9-250 for full decoding
let client_cbw = fidl_common::Cbw::Cbw80;
std::cmp::min(client_cbw, ap_cbw)
}
fn get_band_id(primary_chan: u8) -> fidl_common::Band {
if primary_chan <= 14 {
fidl_common::Band::WlanBand2Ghz
} else {
fidl_common::Band::WlanBand5Ghz
}
}
pub fn get_device_band_info<'a>(
device_info: &'a DeviceInfo,
channel: u8,
) -> Option<&'a fidl_mlme::BandCapabilities> {
let target = get_band_id(channel);
device_info.bands.iter().find(|b| b.band_id == target)
}
pub fn derive_phy_cbw(
bss: &fidl_mlme::BssDescription,
device_info: &DeviceInfo,
radio_cfg: &RadioConfig,
) -> (fidl_common::Phy, fidl_common::Cbw) {
let band_cap = match get_device_band_info(device_info, bss.chan.primary) {
None => {
error!(
"Could not find the device capability corresponding to the \
channel {} of the selected AP {:?} \
Falling back to ERP with 20 MHz bandwidth",
bss.chan.primary, bss.bssid
);
// Fallback to a common ground of Fuchsia
return (fidl_common::Phy::Erp, fidl_common::Cbw::Cbw20);
}
Some(bc) => bc,
};
let supported_phy = if band_cap.ht_cap.is_none() || bss.ht_cap.is_none() || bss.ht_op.is_none()
{
fidl_common::Phy::Erp
} else if band_cap.vht_cap.is_none() || bss.vht_cap.is_none() || bss.vht_op.is_none() {
fidl_common::Phy::Ht
} else {
fidl_common::Phy::Vht
};
let phy_to_use = match radio_cfg.phy {
None => supported_phy,
Some(override_phy) => std::cmp::min(override_phy.to_fidl(), supported_phy),
};
let best_cbw = match phy_to_use {
fidl_common::Phy::Hr => fidl_common::Cbw::Cbw20,
fidl_common::Phy::Erp => fidl_common::Cbw::Cbw20,
fidl_common::Phy::Ht => derive_cbw_ht(
&band_cap.ht_cap.as_ref().expect("band capability needs ht_cap"),
&bss.ht_op.as_ref().expect("bss is expected to have ht_op"),
),
fidl_common::Phy::Vht | fidl_common::Phy::Hew => derive_cbw_vht(
&band_cap.ht_cap.as_ref().expect("band capability needs ht_cap"),
&band_cap.vht_cap.as_ref().expect("band capability needs vht_cap"),
&bss.ht_op.as_ref().expect("bss needs ht_op"),
&bss.vht_op.as_ref().expect("bss needs vht_op"),
),
};
let cbw_to_use = match radio_cfg.cbw {
None => best_cbw,
Some(override_cbw) => {
let (cbw, _) = override_cbw.to_fidl();
std::cmp::min(best_cbw, cbw)
}
};
(phy_to_use, cbw_to_use)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::client::test_utils::{
fake_5ghz_band_capabilities, fake_ht_capabilities, fake_ht_operation,
fake_vht_bss_description, fake_vht_capabilities, fake_vht_operation,
};
use fidl_fuchsia_wlan_mlme as fidl_mlme;
use wlan_common::{
channel::{Cbw, Phy},
RadioConfig,
};
#[test]
fn band_id() {
assert_eq!(fidl_common::Band::WlanBand2Ghz, get_band_id(1));
assert_eq!(fidl_common::Band::WlanBand2Ghz, get_band_id(14));
assert_eq!(fidl_common::Band::WlanBand5Ghz, get_band_id(36));
assert_eq!(fidl_common::Band::WlanBand5Ghz, get_band_id(165));
}
#[test]
fn test_convert_chanwidth_to_cbw() {
assert_eq!(
fidl_common::Cbw::Cbw20,
convert_chanwidth_to_cbw(fidl_mlme::ChanWidthSet::TwentyOnly as u8)
);
assert_eq!(
fidl_common::Cbw::Cbw40Below,
convert_chanwidth_to_cbw(fidl_mlme::ChanWidthSet::TwentyForty as u8)
);
}
#[test]
fn test_convert_secchan_offset_to_cbw() {
assert_eq!(
fidl_common::Cbw::Cbw20,
convert_secchan_offset_to_cbw(fidl_mlme::SecChanOffset::SecondaryNone as u8)
);
assert_eq!(
fidl_common::Cbw::Cbw40,
convert_secchan_offset_to_cbw(fidl_mlme::SecChanOffset::SecondaryAbove as u8)
);
assert_eq!(
fidl_common::Cbw::Cbw40Below,
convert_secchan_offset_to_cbw(fidl_mlme::SecChanOffset::SecondaryBelow as u8)
);
}
#[test]
fn test_convert_vht_segments_to_cbw() {
assert_eq!(fidl_common::Cbw::Cbw80, convert_vht_segments_to_cbw(255, 0));
assert_eq!(fidl_common::Cbw::Cbw160, convert_vht_segments_to_cbw(255, 247));
assert_eq!(fidl_common::Cbw::Cbw80P80, convert_vht_segments_to_cbw(255, 200));
assert_eq!(fidl_common::Cbw::Cbw80, convert_vht_segments_to_cbw(255, 250));
}
#[test]
fn test_derive_cbw_ht() {
{
let want = fidl_common::Cbw::Cbw20;
let got = derive_cbw_ht(
&fake_ht_cap_chanwidth(fidl_mlme::ChanWidthSet::TwentyForty),
&fake_ht_op_sec_offset(fidl_mlme::SecChanOffset::SecondaryNone),
);
assert_eq!(want, got);
}
{
let want = fidl_common::Cbw::Cbw20;
let got = derive_cbw_ht(
&fake_ht_cap_chanwidth(fidl_mlme::ChanWidthSet::TwentyOnly),
&fake_ht_op_sec_offset(fidl_mlme::SecChanOffset::SecondaryAbove),
);
assert_eq!(want, got);
}
{
let want = fidl_common::Cbw::Cbw40;
let got = derive_cbw_ht(
&fake_ht_cap_chanwidth(fidl_mlme::ChanWidthSet::TwentyForty),
&fake_ht_op_sec_offset(fidl_mlme::SecChanOffset::SecondaryAbove),
);
assert_eq!(want, got);
}
{
let want = fidl_common::Cbw::Cbw40Below;
let got = derive_cbw_ht(
&fake_ht_cap_chanwidth(fidl_mlme::ChanWidthSet::TwentyForty),
&fake_ht_op_sec_offset(fidl_mlme::SecChanOffset::SecondaryBelow),
);
assert_eq!(want, got);
}
}
#[test]
fn test_derive_cbw_vht() {
{
let want = fidl_common::Cbw::Cbw80;
let got = derive_cbw_vht(
&fake_ht_cap_chanwidth(fidl_mlme::ChanWidthSet::TwentyForty),
&fake_vht_capabilities(),
&fake_ht_op_sec_offset(fidl_mlme::SecChanOffset::SecondaryAbove),
&fake_vht_op_cbw(fidl_mlme::VhtCbw::Cbw8016080P80),
);
assert_eq!(want, got);
}
{
let want = fidl_common::Cbw::Cbw40;
let got = derive_cbw_vht(
&fake_ht_cap_chanwidth(fidl_mlme::ChanWidthSet::TwentyForty),
&fake_vht_capabilities(),
&fake_ht_op_sec_offset(fidl_mlme::SecChanOffset::SecondaryAbove),
&fake_vht_op_cbw(fidl_mlme::VhtCbw::Cbw2040),
);
assert_eq!(want, got);
}
}
#[test]
fn test_get_band_id() {
assert_eq!(fidl_common::Band::WlanBand2Ghz, get_band_id(14));
assert_eq!(fidl_common::Band::WlanBand5Ghz, get_band_id(36));
}
#[test]
fn test_get_device_band_info() {
assert_eq!(
fidl_common::Band::WlanBand5Ghz,
get_device_band_info(&fake_device_info_ht(fidl_mlme::ChanWidthSet::TwentyForty), 36)
.unwrap()
.band_id
);
}
#[test]
fn test_derive_phy_cbw() {
{
let want = (fidl_common::Phy::Vht, fidl_common::Cbw::Cbw80);
let got = derive_phy_cbw(
&fake_vht_bss_description(),
&fake_device_info_vht(fidl_mlme::ChanWidthSet::TwentyForty),
&RadioConfig::default(),
);
assert_eq!(want, got);
}
{
let want = (fidl_common::Phy::Ht, fidl_common::Cbw::Cbw40);
let got = derive_phy_cbw(
&fake_vht_bss_description(),
&fake_device_info_vht(fidl_mlme::ChanWidthSet::TwentyForty),
&fake_overrider(fidl_common::Phy::Ht, fidl_common::Cbw::Cbw80),
);
assert_eq!(want, got);
}
{
let want = (fidl_common::Phy::Ht, fidl_common::Cbw::Cbw20);
let got = derive_phy_cbw(
&fake_vht_bss_description(),
&fake_device_info_ht(fidl_mlme::ChanWidthSet::TwentyOnly),
&fake_overrider(fidl_common::Phy::Vht, fidl_common::Cbw::Cbw80),
);
assert_eq!(want, got);
}
}
fn fake_ht_cap_chanwidth(chanwidth: fidl_mlme::ChanWidthSet) -> fidl_mlme::HtCapabilities {
let mut ht_cap = fake_ht_capabilities();
ht_cap.ht_cap_info.chan_width_set = chanwidth as u8;
ht_cap
}
fn fake_ht_op_sec_offset(secondary_offset: fidl_mlme::SecChanOffset) -> fidl_mlme::HtOperation {
let mut ht_op = fake_ht_operation();
ht_op.ht_op_info.secondary_chan_offset = secondary_offset as u8;
ht_op
}
fn fake_vht_op_cbw(cbw: fidl_mlme::VhtCbw) -> fidl_mlme::VhtOperation {
fidl_mlme::VhtOperation { vht_cbw: cbw as u8, ..fake_vht_operation() }
}
pub fn fake_device_info_ht(chanwidth: fidl_mlme::ChanWidthSet) -> DeviceInfo {
DeviceInfo { addr: [0; 6], bands: vec![fake_5ghz_band_capabilities_ht_cbw(chanwidth)] }
}
pub fn fake_device_info_vht(chanwidth: fidl_mlme::ChanWidthSet) -> DeviceInfo {
DeviceInfo { addr: [0; 6], bands: vec![fake_band_capabilities_5ghz_vht(chanwidth)] }
}
fn fake_5ghz_band_capabilities_ht_cbw(
chanwidth: fidl_mlme::ChanWidthSet,
) -> fidl_mlme::BandCapabilities {
let bc = fake_5ghz_band_capabilities();
fidl_mlme::BandCapabilities {
ht_cap: Some(Box::new(fake_ht_capabilities_cbw(chanwidth))),
..bc
}
}
fn fake_band_capabilities_5ghz_vht(
chanwidth: fidl_mlme::ChanWidthSet,
) -> fidl_mlme::BandCapabilities {
let bc = fake_5ghz_band_capabilities();
fidl_mlme::BandCapabilities {
ht_cap: Some(Box::new(fake_ht_capabilities_cbw(chanwidth))),
vht_cap: Some(Box::new(fake_vht_capabilities())),
..bc
}
}
fn fake_overrider(phy: fidl_common::Phy, cbw: fidl_common::Cbw) -> RadioConfig {
RadioConfig {
phy: Some(Phy::from_fidl(phy)),
cbw: Some(Cbw::from_fidl(cbw, 0)),
primary_chan: None,
}
}
fn fake_ht_capabilities_cbw(chanwidth: fidl_mlme::ChanWidthSet) -> fidl_mlme::HtCapabilities {
let mut ht_cap = fake_ht_capabilities();
ht_cap.ht_cap_info.chan_width_set = chanwidth as u8;
ht_cap
}
}