Skip to main content

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