wlan_common/
capabilities.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
5//! This module tries to check the iface device's capabilities against the BSS it is instructed to
6//! join. The capabilities will be tailored based on the band.
7//! Next, rates will be joined with the AP and HT Capabilities and VHT Capabilities may be modified
8//! based on the user-overridable join channel and bandwidth.
9//! If successful, the capabilities will be extracted and saved.
10
11use crate::channel::{Cbw, Channel};
12use crate::ie::intersect::*;
13use crate::ie::{
14    self, parse_ht_capabilities, parse_vht_capabilities, HtCapabilities, SupportedRate,
15    VhtCapabilities,
16};
17use crate::mac::CapabilityInfo;
18use anyhow::{format_err, Context as _, Error};
19use {fidl_fuchsia_wlan_ieee80211 as fidl_ieee80211, fidl_fuchsia_wlan_mlme as fidl_mlme};
20
21/// Capability Info is defined in IEEE Std 802.11-1026 9.4.1.4.
22/// Figure 9-68 indicates BSS and IBSS bits are reserved for client.
23/// However, some APs will reject association unless they are set to true and false, respectively.
24const OVERRIDE_CAP_INFO_ESS: bool = true;
25const OVERRIDE_CAP_INFO_IBSS: bool = false;
26
27/// IEEE Std 802.11-2016 Table 9-43 defines CF-Pollable and CF-Poll Request, Fuchsia does not
28/// support them.
29const OVERRIDE_CAP_INFO_CF_POLLABLE: bool = false;
30const OVERRIDE_CAP_INFO_CF_POLL_REQUEST: bool = false;
31
32/// In an RSNA non-AP STA, privacy bit is set to false. Otherwise it is reserved (has no meaning and
33/// is not used).
34const OVERRIDE_CAP_INFO_PRIVACY: bool = false;
35
36/// Spectrum Management bit indicates dot11SpectrumManagementRequired. Fuchsia does not support it.
37const OVERRIDE_CAP_INFO_SPECTRUM_MGMT: bool = false;
38
39/// Fuchsia does not support tx_stbc with our existing SoftMAC chips.
40const OVERRIDE_HT_CAP_INFO_TX_STBC: bool = false;
41
42/// Supported channel bandwidth set can only be non-zero if the associating channel is 160 MHz or
43/// 80+80 MHz Channel bandwidth. Otherwise it will be set to 0. 0 is a purely numeric value without
44/// a name. See IEEE Std 802.11-2016 Table 9-250 for more details.
45/// TODO(https://fxbug.dev/42115418): finer control over CBW if necessary.
46const OVERRIDE_VHT_CAP_INFO_SUPPORTED_CBW_SET: u32 = 0;
47
48/// A driver may not properly populate an interface's Capabilities to reflect the selected role.
49/// Override the reported capabilities to ensure compatibility with Client role.
50fn override_capability_info(capability_info: CapabilityInfo) -> CapabilityInfo {
51    capability_info
52        .with_ess(OVERRIDE_CAP_INFO_ESS)
53        .with_ibss(OVERRIDE_CAP_INFO_IBSS)
54        .with_cf_pollable(OVERRIDE_CAP_INFO_CF_POLLABLE)
55        .with_cf_poll_req(OVERRIDE_CAP_INFO_CF_POLL_REQUEST)
56        .with_privacy(OVERRIDE_CAP_INFO_PRIVACY)
57        .with_spectrum_mgmt(OVERRIDE_CAP_INFO_SPECTRUM_MGMT)
58}
59
60/// The entry point of this module.
61/// 1. Extract the band capabilities from the iface device based on BSS channel.
62/// 2. Derive/Override capabilities based on iface capabilities, BSS requirements and
63/// user overridable channel bandwidths.
64pub fn derive_join_capabilities(
65    bss_channel: Channel,
66    bss_rates: &[SupportedRate],
67    device_info: &fidl_mlme::DeviceInfo,
68) -> Result<ClientCapabilities, Error> {
69    // Step 1 - Extract iface capabilities for this particular band we are joining
70    let band_cap = get_device_band_cap(&device_info, bss_channel.primary)
71        .ok_or_else(|| format_err!("iface does not support BSS channel {}", bss_channel.primary))?;
72
73    // Step 2.1 - Override CapabilityInfo
74    // TODO(https://fxbug.dev/42132496): The WlanSoftmacHardwareCapability type is u32 and used here to override
75    // the capability info for joining a BSS. The upper bits are removed but shouldn't have to be.
76    let capability_info =
77        override_capability_info(CapabilityInfo(device_info.softmac_hardware_capability as u16));
78
79    // Step 2.2 - Derive data rates
80    // Both are safe to unwrap because SupportedRate is one byte and will not cause alignment issue.
81    let client_rates = band_cap.basic_rates.iter().map(|&r| SupportedRate(r)).collect::<Vec<_>>();
82    let rates = intersect_rates(ApRates(bss_rates), ClientRates(&client_rates))
83        .map_err(|error| format_err!("could not intersect rates: {:?}", error))
84        .context(format!("deriving rates: {:?} + {:?}", band_cap.basic_rates, bss_rates))?;
85
86    // Step 2.3 - Override HT Capabilities and VHT Capabilities
87    // Here it is assumed that the channel specified by the BSS will never be invalid.
88    let (ht_cap, vht_cap) =
89        override_ht_vht(band_cap.ht_cap.as_ref(), band_cap.vht_cap.as_ref(), bss_channel.cbw)?;
90
91    Ok(ClientCapabilities(StaCapabilities { capability_info, rates, ht_cap, vht_cap }))
92}
93
94/// Wrapper function to convert FIDL {HT,VHT}Capabilities into byte arrays, taking into account the
95/// limitations imposed by the channel bandwidth.
96fn override_ht_vht(
97    fidl_ht_cap: Option<&Box<fidl_ieee80211::HtCapabilities>>,
98    fidl_vht_cap: Option<&Box<fidl_ieee80211::VhtCapabilities>>,
99    cbw: Cbw,
100) -> Result<(Option<HtCapabilities>, Option<VhtCapabilities>), Error> {
101    if fidl_ht_cap.is_none() && fidl_vht_cap.is_some() {
102        return Err(format_err!("VHT Cap without HT Cap is invalid."));
103    }
104
105    let ht_cap = match fidl_ht_cap {
106        Some(h) => {
107            let ht_cap = *parse_ht_capabilities(&h.bytes[..]).context("verifying HT Cap")?;
108            Some(override_ht_capabilities(ht_cap, cbw))
109        }
110        None => None,
111    };
112
113    let vht_cap = match fidl_vht_cap {
114        Some(v) => {
115            let vht_cap = *parse_vht_capabilities(&v.bytes[..]).context("verifying VHT Cap")?;
116            Some(override_vht_capabilities(vht_cap, cbw))
117        }
118        None => None,
119    };
120    Ok((ht_cap, vht_cap))
121}
122
123/// Even though hardware may support higher channel bandwidth, if user specifies a narrower
124/// bandwidth, change the channel bandwidth in ht_cap_info to match user's preference.
125fn override_ht_capabilities(mut ht_cap: HtCapabilities, cbw: Cbw) -> HtCapabilities {
126    let mut ht_cap_info = ht_cap.ht_cap_info.with_tx_stbc(OVERRIDE_HT_CAP_INFO_TX_STBC);
127    match cbw {
128        Cbw::Cbw20 => ht_cap_info.set_chan_width_set(ie::ChanWidthSet::TWENTY_ONLY),
129        _ => (),
130    }
131    ht_cap.ht_cap_info = ht_cap_info;
132    ht_cap
133}
134
135/// Even though hardware may support higher channel bandwidth, if user specifies a narrower
136/// bandwidth, change the channel bandwidth in vht_cap_info to match user's preference.
137fn override_vht_capabilities(mut vht_cap: VhtCapabilities, cbw: Cbw) -> VhtCapabilities {
138    let mut vht_cap_info = vht_cap.vht_cap_info;
139    if vht_cap_info.supported_cbw_set() != OVERRIDE_VHT_CAP_INFO_SUPPORTED_CBW_SET {
140        // Supported channel bandwidth set can only be non-zero if the associating channel is
141        // 160 MHz or 80+80 MHz Channel bandwidth. Otherwise it will be set to 0. 0 is a purely
142        // numeric value without a name. See IEEE Std 802.11-2016 Table 9-250 for more details.
143        // TODO(https://fxbug.dev/42115418): finer control over CBW if necessary.
144        match cbw {
145            Cbw::Cbw160 | Cbw::Cbw80P80 { secondary80: _ } => (),
146            _ => vht_cap_info.set_supported_cbw_set(OVERRIDE_VHT_CAP_INFO_SUPPORTED_CBW_SET),
147        }
148    }
149    vht_cap.vht_cap_info = vht_cap_info;
150    vht_cap
151}
152
153// TODO(https://fxbug.dev/42172557): Using channel number to determine band is incorrect.
154fn get_band(primary_channel: u8) -> fidl_ieee80211::WlanBand {
155    if primary_channel <= 14 {
156        fidl_ieee80211::WlanBand::TwoGhz
157    } else {
158        fidl_ieee80211::WlanBand::FiveGhz
159    }
160}
161
162pub fn get_device_band_cap(
163    device_info: &fidl_mlme::DeviceInfo,
164    channel: u8,
165) -> Option<&fidl_mlme::BandCapability> {
166    let target = get_band(channel);
167    device_info.bands.iter().find(|b| b.band == target)
168}
169
170/// Capabilities that takes the iface device's capabilities based on the channel a client is trying
171/// to join, the PHY parameters that is overridden by user's command line input and the BSS the
172/// client are is trying to join.
173/// They are stored in the form of IEs because at some point they will be transmitted in
174/// (Re)Association Request and (Re)Association Response frames.
175#[derive(Debug, PartialEq)]
176pub struct StaCapabilities {
177    pub capability_info: CapabilityInfo,
178    pub rates: Vec<SupportedRate>,
179    pub ht_cap: Option<HtCapabilities>,
180    pub vht_cap: Option<VhtCapabilities>,
181}
182
183#[derive(Debug, PartialEq)]
184pub struct ClientCapabilities(pub StaCapabilities);
185#[derive(Debug, PartialEq)]
186pub struct ApCapabilities(pub StaCapabilities);
187
188/// Performs capability negotiation with an AP assuming the Fuchsia device is a client.
189pub fn intersect_with_ap_as_client(
190    client: &ClientCapabilities,
191    ap: &ApCapabilities,
192) -> Result<StaCapabilities, Error> {
193    let rates = intersect_rates(ApRates(&ap.0.rates[..]), ClientRates(&client.0.rates[..]))
194        .map_err(|e| format_err!("could not intersect rates: {:?}", e))?;
195    let (capability_info, ht_cap, vht_cap) = intersect(&client.0, &ap.0);
196    Ok(StaCapabilities { rates, capability_info, ht_cap, vht_cap })
197}
198
199/// Performs capability negotiation with a remote client assuming the Fuchsia device is an AP.
200pub fn intersect_with_remote_client_as_ap(
201    ap: &ApCapabilities,
202    remote_client: &ClientCapabilities,
203) -> StaCapabilities {
204    // Safe to unwrap. Otherwise we would have rejected the association from this remote client.
205    let rates = intersect_rates(ApRates(&ap.0.rates[..]), ClientRates(&remote_client.0.rates[..]))
206        .unwrap_or(vec![]);
207    let (capability_info, ht_cap, vht_cap) = intersect(&ap.0, &remote_client.0);
208    StaCapabilities { rates, capability_info, ht_cap, vht_cap }
209}
210
211fn intersect(
212    ours: &StaCapabilities,
213    theirs: &StaCapabilities,
214) -> (CapabilityInfo, Option<HtCapabilities>, Option<VhtCapabilities>) {
215    // Every bit is a boolean so bit-wise and is sufficient
216    let capability_info = CapabilityInfo(ours.capability_info.raw() & theirs.capability_info.raw());
217    let ht_cap = match (ours.ht_cap, theirs.ht_cap) {
218        // Intersect is NOT necessarily symmetrical. Our own capabilities prevails.
219        (Some(ours), Some(theirs)) => Some(ours.intersect(&theirs)),
220        _ => None,
221    };
222    let vht_cap = match (ours.vht_cap, theirs.vht_cap) {
223        // Intersect is NOT necessarily symmetrical. Our own capabilities prevails.
224        (Some(ours), Some(theirs)) => Some(ours.intersect(&theirs)),
225        _ => None,
226    };
227    (capability_info, ht_cap, vht_cap)
228}
229
230#[cfg(test)]
231mod tests {
232    use super::*;
233    use crate::mac;
234    use crate::test_utils::fake_capabilities::fake_5ghz_band_capability_ht_cbw;
235    use assert_matches::assert_matches;
236    use fidl_fuchsia_wlan_common as fidl_common;
237
238    #[test]
239    fn test_build_cap_info() {
240        let capability_info = CapabilityInfo(0)
241            .with_ess(!OVERRIDE_CAP_INFO_ESS)
242            .with_ibss(!OVERRIDE_CAP_INFO_IBSS)
243            .with_cf_pollable(!OVERRIDE_CAP_INFO_CF_POLLABLE)
244            .with_cf_poll_req(!OVERRIDE_CAP_INFO_CF_POLL_REQUEST)
245            .with_privacy(!OVERRIDE_CAP_INFO_PRIVACY)
246            .with_spectrum_mgmt(!OVERRIDE_CAP_INFO_SPECTRUM_MGMT);
247        let capability_info = override_capability_info(capability_info);
248        assert_eq!(capability_info.ess(), OVERRIDE_CAP_INFO_ESS);
249        assert_eq!(capability_info.ibss(), OVERRIDE_CAP_INFO_IBSS);
250        assert_eq!(capability_info.cf_pollable(), OVERRIDE_CAP_INFO_CF_POLLABLE);
251        assert_eq!(capability_info.cf_poll_req(), OVERRIDE_CAP_INFO_CF_POLL_REQUEST);
252        assert_eq!(capability_info.privacy(), OVERRIDE_CAP_INFO_PRIVACY);
253        assert_eq!(capability_info.spectrum_mgmt(), OVERRIDE_CAP_INFO_SPECTRUM_MGMT);
254    }
255
256    #[test]
257    fn test_override_ht_cap() {
258        let mut ht_cap = ie::fake_ht_capabilities();
259        let ht_cap_info = ht_cap
260            .ht_cap_info
261            .with_tx_stbc(!OVERRIDE_HT_CAP_INFO_TX_STBC)
262            .with_chan_width_set(ie::ChanWidthSet::TWENTY_FORTY);
263        ht_cap.ht_cap_info = ht_cap_info;
264        let mut channel = Channel { primary: 153, cbw: Cbw::Cbw20 };
265
266        let ht_cap_info = override_ht_capabilities(ht_cap, channel.cbw).ht_cap_info;
267        assert_eq!(ht_cap_info.tx_stbc(), OVERRIDE_HT_CAP_INFO_TX_STBC);
268        assert_eq!(ht_cap_info.chan_width_set(), ie::ChanWidthSet::TWENTY_ONLY);
269
270        channel.cbw = Cbw::Cbw40;
271        let ht_cap_info = override_ht_capabilities(ht_cap, channel.cbw).ht_cap_info;
272        assert_eq!(ht_cap_info.chan_width_set(), ie::ChanWidthSet::TWENTY_FORTY);
273    }
274
275    #[test]
276    fn test_override_vht_cap() {
277        let mut vht_cap = ie::fake_vht_capabilities();
278        let vht_cap_info = vht_cap.vht_cap_info.with_supported_cbw_set(2);
279        vht_cap.vht_cap_info = vht_cap_info;
280        let mut channel = Channel { primary: 153, cbw: Cbw::Cbw20 };
281
282        // CBW20, CBW40, CBW80 will set supported_cbw_set to 0
283
284        let vht_cap_info = override_vht_capabilities(vht_cap, channel.cbw).vht_cap_info;
285        assert_eq!(vht_cap_info.supported_cbw_set(), OVERRIDE_VHT_CAP_INFO_SUPPORTED_CBW_SET);
286
287        channel.cbw = Cbw::Cbw40;
288        let vht_cap_info = override_vht_capabilities(vht_cap, channel.cbw).vht_cap_info;
289        assert_eq!(vht_cap_info.supported_cbw_set(), OVERRIDE_VHT_CAP_INFO_SUPPORTED_CBW_SET);
290
291        channel.cbw = Cbw::Cbw80;
292        let vht_cap_info = override_vht_capabilities(vht_cap, channel.cbw).vht_cap_info;
293        assert_eq!(vht_cap_info.supported_cbw_set(), OVERRIDE_VHT_CAP_INFO_SUPPORTED_CBW_SET);
294
295        // CBW160 and CBW80P80 will preserve existing supported_cbw_set value
296
297        channel.cbw = Cbw::Cbw160;
298        let vht_cap_info = override_vht_capabilities(vht_cap, channel.cbw).vht_cap_info;
299        assert_eq!(vht_cap_info.supported_cbw_set(), 2);
300
301        channel.cbw = Cbw::Cbw80P80 { secondary80: 42 };
302        let vht_cap_info = override_vht_capabilities(vht_cap, channel.cbw).vht_cap_info;
303        assert_eq!(vht_cap_info.supported_cbw_set(), 2);
304    }
305
306    #[test]
307    fn band_id() {
308        assert_eq!(fidl_ieee80211::WlanBand::TwoGhz, get_band(1));
309        assert_eq!(fidl_ieee80211::WlanBand::TwoGhz, get_band(14));
310        assert_eq!(fidl_ieee80211::WlanBand::FiveGhz, get_band(36));
311        assert_eq!(fidl_ieee80211::WlanBand::FiveGhz, get_band(165));
312    }
313
314    #[test]
315    fn test_get_band() {
316        assert_eq!(fidl_ieee80211::WlanBand::TwoGhz, get_band(14));
317        assert_eq!(fidl_ieee80211::WlanBand::FiveGhz, get_band(36));
318    }
319
320    #[test]
321    fn test_get_device_band_cap() {
322        let device_info = fidl_mlme::DeviceInfo {
323            sta_addr: [0; 6],
324            role: fidl_common::WlanMacRole::Client,
325            bands: vec![fake_5ghz_band_capability_ht_cbw(ie::ChanWidthSet::TWENTY_FORTY)],
326            softmac_hardware_capability: 0,
327            qos_capable: true,
328        };
329        assert_eq!(
330            fidl_ieee80211::WlanBand::FiveGhz,
331            get_device_band_cap(&device_info, 36).unwrap().band
332        );
333    }
334
335    fn fake_client_join_cap() -> ClientCapabilities {
336        ClientCapabilities(StaCapabilities {
337            capability_info: mac::CapabilityInfo(0x1234),
338            rates: [101, 102, 103, 104].iter().cloned().map(SupportedRate).collect(),
339            ht_cap: Some(HtCapabilities {
340                ht_cap_info: ie::HtCapabilityInfo(0).with_rx_stbc(2).with_tx_stbc(false),
341                ..ie::fake_ht_capabilities()
342            }),
343            vht_cap: Some(ie::fake_vht_capabilities()),
344        })
345    }
346
347    fn fake_ap_join_cap() -> ApCapabilities {
348        ApCapabilities(StaCapabilities {
349            capability_info: mac::CapabilityInfo(0x4321),
350            // 101 + 128 turns it into a basic rate
351            rates: [101 + 128, 102, 9].iter().cloned().map(SupportedRate).collect(),
352            ht_cap: Some(HtCapabilities {
353                ht_cap_info: ie::HtCapabilityInfo(0).with_rx_stbc(1).with_tx_stbc(true),
354                ..ie::fake_ht_capabilities()
355            }),
356            vht_cap: Some(ie::fake_vht_capabilities()),
357        })
358    }
359
360    #[test]
361    fn client_intersect_with_ap() {
362        let caps = assert_matches!(
363            intersect_with_ap_as_client(&fake_client_join_cap(), &fake_ap_join_cap()),
364            Ok(caps) => caps
365        );
366        assert_eq!(
367            caps,
368            StaCapabilities {
369                capability_info: mac::CapabilityInfo(0x0220),
370                rates: [229, 102].iter().cloned().map(SupportedRate).collect(),
371                ht_cap: Some(HtCapabilities {
372                    ht_cap_info: ie::HtCapabilityInfo(0).with_rx_stbc(2).with_tx_stbc(false),
373                    ..ie::fake_ht_capabilities()
374                }),
375                ..fake_client_join_cap().0
376            }
377        )
378    }
379
380    #[test]
381    fn ap_intersect_with_remote_client() {
382        assert_eq!(
383            intersect_with_remote_client_as_ap(&fake_ap_join_cap(), &fake_client_join_cap()),
384            StaCapabilities {
385                capability_info: mac::CapabilityInfo(0x0220),
386                rates: [229, 102].iter().cloned().map(SupportedRate).collect(),
387                ht_cap: Some(HtCapabilities {
388                    ht_cap_info: ie::HtCapabilityInfo(0).with_rx_stbc(0).with_tx_stbc(true),
389                    ..ie::fake_ht_capabilities()
390                }),
391                ..fake_ap_join_cap().0
392            }
393        );
394    }
395}