blob: 6db612f922bc4de53b3af0cd94f6ab1f102a46f0 [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::{
channel::{Cbw, Channel, Phy},
ie::*,
RadioConfig,
};
fn convert_chanwidth_to_cbw(chan_width_set: ChanWidthSet) -> fidl_common::Cbw {
if chan_width_set == ChanWidthSet::TWENTY_ONLY {
fidl_common::Cbw::Cbw20
} else {
fidl_common::Cbw::Cbw40Below
}
}
fn convert_secchan_offset_to_cbw(secchan_offset: SecChanOffset) -> fidl_common::Cbw {
if secchan_offset == SecChanOffset::SECONDARY_ABOVE {
fidl_common::Cbw::Cbw40
} else if secchan_offset == SecChanOffset::SECONDARY_BELOW {
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: &HtCapabilities, bss_ht_op: &HtOperation) -> fidl_common::Cbw {
let client_ht_cap_info = client_ht_cap.ht_cap_info;
let client_cbw = convert_chanwidth_to_cbw(client_ht_cap_info.chan_width_set());
let ap_cbw =
convert_secchan_offset_to_cbw({ bss_ht_op.ht_op_info_head }.secondary_chan_offset());
std::cmp::min(client_cbw, ap_cbw)
}
fn derive_cbw_vht(
client_ht_cap: &HtCapabilities,
_client_vht_cap: &VhtCapabilities,
bss_ht_op: &HtOperation,
bss_vht_op: &VhtOperation,
) -> fidl_common::Cbw {
// Derive CBW from AP's VHT IEs
let ap_cbw = if bss_vht_op.vht_cbw == VhtChannelBandwidth::CBW_80_160_80P80 {
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(fxbug.dev/29000): 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(
device_info: &fidl_mlme::DeviceInfo,
channel: u8,
) -> Option<&fidl_mlme::BandCapabilities> {
let target = get_band_id(channel);
device_info.bands.iter().find(|b| b.band_id == target)
}
/// Derive PHY and CBW for Client role
pub fn derive_phy_cbw(
bss: &fidl_mlme::BssDescription,
device_info: &fidl_mlme::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),
};
// Safe to unwrap below because phy_to_use guarantees that IEs exist.
// TODO(fxbug.dev/38205): Clean up this part to remove all the expect(...).
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(
&parse_ht_capabilities(&band_cap.ht_cap.as_ref().unwrap().bytes[..])
.expect("band capability needs ht_cap"),
&parse_ht_operation(&bss.ht_op.as_ref().unwrap().bytes[..])
.expect("bss is expected to have ht_op"),
),
fidl_common::Phy::Vht | fidl_common::Phy::Hew => derive_cbw_vht(
&parse_ht_capabilities(&band_cap.ht_cap.as_ref().unwrap().bytes[..])
.expect("band capability needs ht_cap"),
&parse_vht_capabilities(&band_cap.vht_cap.as_ref().unwrap().bytes[..])
.expect("band capability needs vht_cap"),
&parse_ht_operation(&bss.ht_op.as_ref().unwrap().bytes[..]).expect("bss needs ht_op"),
&parse_vht_operation(&bss.vht_op.as_ref().unwrap().bytes[..])
.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)
}
/// Derive PHY to use for AP or Mesh role. Input config_phy and chan are required to be valid.
pub fn derive_phy_cbw_for_ap(
device_info: &fidl_mlme::DeviceInfo,
config_phy: &Phy,
chan: &Channel,
) -> (Phy, Cbw) {
let band_cap = match get_device_band_info(device_info, chan.primary) {
None => {
error!(
"Could not find the device capability corresponding to the \
channel {} Falling back to HT with 20 MHz bandwidth",
chan.primary
);
return (Phy::Ht, Cbw::Cbw20);
}
Some(bc) => bc,
};
let supported_phy = if band_cap.ht_cap.is_none() {
Phy::Erp
} else if band_cap.vht_cap.is_none() {
Phy::Ht
} else {
Phy::Vht
};
let phy_to_use = std::cmp::min(*config_phy, supported_phy);
let best_cbw = match phy_to_use {
Phy::Hr | Phy::Erp => Cbw::Cbw20,
Phy::Ht => {
// Consider the input chan of this function can be Channel
// { primary: 48, cbw: Cbw80 }, which is valid. If phy_to_use is HT, however,
// Cbw80 becomes infeasible, and a next feasible CBW needs to be found.
let ht_cap = parse_ht_capabilities(&band_cap.ht_cap.as_ref().unwrap().bytes[..])
.expect("band capability needs ht_cap");
let ht_cap_info = ht_cap.ht_cap_info;
if ht_cap_info.chan_width_set() == ChanWidthSet::TWENTY_ONLY {
Cbw::Cbw20
} else {
// Only one is feasible.
let c = Channel::new(chan.primary, Cbw::Cbw40);
if c.is_valid() {
Cbw::Cbw40
} else {
Cbw::Cbw40Below
}
}
}
// TODO(porce): CBW160, CBW80P80, HEW support
Phy::Vht | Phy::Hew => Cbw::Cbw80,
};
let cbw_to_use = std::cmp::min(chan.cbw, best_cbw);
(phy_to_use, cbw_to_use)
}
#[cfg(test)]
mod tests {
use {
super::*,
crate::{client::test_utils::fake_vht_bss_description, test_utils::*},
wlan_common::{
channel::{Cbw, Channel, 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(ChanWidthSet::TWENTY_ONLY));
assert_eq!(
fidl_common::Cbw::Cbw40Below,
convert_chanwidth_to_cbw(ChanWidthSet::TWENTY_FORTY)
);
}
#[test]
fn test_convert_secchan_offset_to_cbw() {
assert_eq!(
fidl_common::Cbw::Cbw20,
convert_secchan_offset_to_cbw(SecChanOffset::SECONDARY_NONE)
);
assert_eq!(
fidl_common::Cbw::Cbw40,
convert_secchan_offset_to_cbw(SecChanOffset::SECONDARY_ABOVE)
);
assert_eq!(
fidl_common::Cbw::Cbw40Below,
convert_secchan_offset_to_cbw(SecChanOffset::SECONDARY_BELOW)
);
}
#[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(ChanWidthSet::TWENTY_FORTY),
&fake_ht_op_sec_offset(SecChanOffset::SECONDARY_NONE),
);
assert_eq!(want, got);
}
{
let want = fidl_common::Cbw::Cbw20;
let got = derive_cbw_ht(
&fake_ht_cap_chanwidth(ChanWidthSet::TWENTY_ONLY),
&fake_ht_op_sec_offset(SecChanOffset::SECONDARY_ABOVE),
);
assert_eq!(want, got);
}
{
let want = fidl_common::Cbw::Cbw40;
let got = derive_cbw_ht(
&fake_ht_cap_chanwidth(ChanWidthSet::TWENTY_FORTY),
&fake_ht_op_sec_offset(SecChanOffset::SECONDARY_ABOVE),
);
assert_eq!(want, got);
}
{
let want = fidl_common::Cbw::Cbw40Below;
let got = derive_cbw_ht(
&fake_ht_cap_chanwidth(ChanWidthSet::TWENTY_FORTY),
&fake_ht_op_sec_offset(SecChanOffset::SECONDARY_BELOW),
);
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(ChanWidthSet::TWENTY_FORTY),
&fake_vht_capabilities(),
&fake_ht_op_sec_offset(SecChanOffset::SECONDARY_ABOVE),
&fake_vht_op_cbw(VhtChannelBandwidth::CBW_80_160_80P80),
);
assert_eq!(want, got);
}
{
let want = fidl_common::Cbw::Cbw40;
let got = derive_cbw_vht(
&fake_ht_cap_chanwidth(ChanWidthSet::TWENTY_FORTY),
&fake_vht_capabilities(),
&fake_ht_op_sec_offset(SecChanOffset::SECONDARY_ABOVE),
&fake_vht_op_cbw(VhtChannelBandwidth::CBW_20_40),
);
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(ChanWidthSet::TWENTY_FORTY), 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(ChanWidthSet::TWENTY_FORTY),
&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(ChanWidthSet::TWENTY_FORTY),
&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(ChanWidthSet::TWENTY_ONLY),
&fake_overrider(fidl_common::Phy::Vht, fidl_common::Cbw::Cbw80),
);
assert_eq!(want, got);
}
}
struct UserCfg {
phy: Phy,
chan: Channel,
}
impl UserCfg {
fn new(phy: Phy, primary_chan: u8, cbw: Cbw) -> Self {
UserCfg { phy, chan: Channel::new(primary_chan, cbw) }
}
}
#[test]
fn test_derive_phy_cbw_for_ap() {
// VHT config, VHT device
{
let usr_cfg = UserCfg::new(Phy::Vht, 36, Cbw::Cbw80);
let want = (Phy::Vht, Cbw::Cbw80);
let got = derive_phy_cbw_for_ap(
&fake_device_info_vht(ChanWidthSet::TWENTY_FORTY),
&usr_cfg.phy,
&usr_cfg.chan,
);
assert_eq!(want, got);
}
{
let usr_cfg = UserCfg::new(Phy::Vht, 36, Cbw::Cbw40);
let want = (Phy::Vht, Cbw::Cbw40);
let got = derive_phy_cbw_for_ap(
&fake_device_info_vht(ChanWidthSet::TWENTY_FORTY),
&usr_cfg.phy,
&usr_cfg.chan,
);
assert_eq!(want, got);
}
{
let usr_cfg = UserCfg::new(Phy::Vht, 36, Cbw::Cbw20);
let want = (Phy::Vht, Cbw::Cbw20);
let got = derive_phy_cbw_for_ap(
&fake_device_info_vht(ChanWidthSet::TWENTY_FORTY),
&usr_cfg.phy,
&usr_cfg.chan,
);
assert_eq!(want, got);
}
{
let usr_cfg = UserCfg::new(Phy::Vht, 40, Cbw::Cbw40Below);
let want = (Phy::Vht, Cbw::Cbw40Below);
let got = derive_phy_cbw_for_ap(
&fake_device_info_vht(ChanWidthSet::TWENTY_FORTY),
&usr_cfg.phy,
&usr_cfg.chan,
);
assert_eq!(want, got);
}
// HT config, VHT device
{
let usr_cfg = UserCfg::new(Phy::Ht, 36, Cbw::Cbw40);
let want = (Phy::Ht, Cbw::Cbw40);
let got = derive_phy_cbw_for_ap(
&fake_device_info_vht(ChanWidthSet::TWENTY_FORTY),
&usr_cfg.phy,
&usr_cfg.chan,
);
assert_eq!(want, got);
}
{
let usr_cfg = UserCfg::new(Phy::Ht, 40, Cbw::Cbw40Below);
let want = (Phy::Ht, Cbw::Cbw40Below);
let got = derive_phy_cbw_for_ap(
&fake_device_info_vht(ChanWidthSet::TWENTY_FORTY),
&usr_cfg.phy,
&usr_cfg.chan,
);
assert_eq!(want, got);
}
{
let usr_cfg = UserCfg::new(Phy::Ht, 36, Cbw::Cbw40);
let want = (Phy::Ht, Cbw::Cbw40);
let got = derive_phy_cbw_for_ap(
&fake_device_info_ht(ChanWidthSet::TWENTY_FORTY),
&usr_cfg.phy,
&usr_cfg.chan,
);
assert_eq!(want, got);
}
{
let usr_cfg = UserCfg::new(Phy::Ht, 36, Cbw::Cbw20);
let want = (Phy::Ht, Cbw::Cbw20);
let got = derive_phy_cbw_for_ap(
&fake_device_info_ht(ChanWidthSet::TWENTY_FORTY),
&usr_cfg.phy,
&usr_cfg.chan,
);
assert_eq!(want, got);
}
// VHT config, HT device
{
let usr_cfg = UserCfg::new(Phy::Vht, 36, Cbw::Cbw80);
let want = (Phy::Ht, Cbw::Cbw40);
let got = derive_phy_cbw_for_ap(
&fake_device_info_ht(ChanWidthSet::TWENTY_FORTY),
&usr_cfg.phy,
&usr_cfg.chan,
);
assert_eq!(want, got);
}
{
let usr_cfg = UserCfg::new(Phy::Vht, 40, Cbw::Cbw80);
let want = (Phy::Ht, Cbw::Cbw40Below);
let got = derive_phy_cbw_for_ap(
&fake_device_info_ht(ChanWidthSet::TWENTY_FORTY),
&usr_cfg.phy,
&usr_cfg.chan,
);
assert_eq!(want, got);
}
{
let usr_cfg = UserCfg::new(Phy::Vht, 36, Cbw::Cbw80);
let want = (Phy::Ht, Cbw::Cbw20);
let got = derive_phy_cbw_for_ap(
&fake_device_info_ht(ChanWidthSet::TWENTY_ONLY),
&usr_cfg.phy,
&usr_cfg.chan,
);
assert_eq!(want, got);
}
}
}