| // 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 |
| } |
| } |