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::test_utils::fake_capabilities::fake_5ghz_band_capability_ht_cbw;
234    use crate::{assert_variant, mac};
235    use fidl_fuchsia_wlan_common as fidl_common;
236
237    #[test]
238    fn test_build_cap_info() {
239        let capability_info = CapabilityInfo(0)
240            .with_ess(!OVERRIDE_CAP_INFO_ESS)
241            .with_ibss(!OVERRIDE_CAP_INFO_IBSS)
242            .with_cf_pollable(!OVERRIDE_CAP_INFO_CF_POLLABLE)
243            .with_cf_poll_req(!OVERRIDE_CAP_INFO_CF_POLL_REQUEST)
244            .with_privacy(!OVERRIDE_CAP_INFO_PRIVACY)
245            .with_spectrum_mgmt(!OVERRIDE_CAP_INFO_SPECTRUM_MGMT);
246        let capability_info = override_capability_info(capability_info);
247        assert_eq!(capability_info.ess(), OVERRIDE_CAP_INFO_ESS);
248        assert_eq!(capability_info.ibss(), OVERRIDE_CAP_INFO_IBSS);
249        assert_eq!(capability_info.cf_pollable(), OVERRIDE_CAP_INFO_CF_POLLABLE);
250        assert_eq!(capability_info.cf_poll_req(), OVERRIDE_CAP_INFO_CF_POLL_REQUEST);
251        assert_eq!(capability_info.privacy(), OVERRIDE_CAP_INFO_PRIVACY);
252        assert_eq!(capability_info.spectrum_mgmt(), OVERRIDE_CAP_INFO_SPECTRUM_MGMT);
253    }
254
255    #[test]
256    fn test_override_ht_cap() {
257        let mut ht_cap = ie::fake_ht_capabilities();
258        let ht_cap_info = ht_cap
259            .ht_cap_info
260            .with_tx_stbc(!OVERRIDE_HT_CAP_INFO_TX_STBC)
261            .with_chan_width_set(ie::ChanWidthSet::TWENTY_FORTY);
262        ht_cap.ht_cap_info = ht_cap_info;
263        let mut channel = Channel { primary: 153, cbw: Cbw::Cbw20 };
264
265        let ht_cap_info = override_ht_capabilities(ht_cap, channel.cbw).ht_cap_info;
266        assert_eq!(ht_cap_info.tx_stbc(), OVERRIDE_HT_CAP_INFO_TX_STBC);
267        assert_eq!(ht_cap_info.chan_width_set(), ie::ChanWidthSet::TWENTY_ONLY);
268
269        channel.cbw = Cbw::Cbw40;
270        let ht_cap_info = override_ht_capabilities(ht_cap, channel.cbw).ht_cap_info;
271        assert_eq!(ht_cap_info.chan_width_set(), ie::ChanWidthSet::TWENTY_FORTY);
272    }
273
274    #[test]
275    fn test_override_vht_cap() {
276        let mut vht_cap = ie::fake_vht_capabilities();
277        let vht_cap_info = vht_cap.vht_cap_info.with_supported_cbw_set(2);
278        vht_cap.vht_cap_info = vht_cap_info;
279        let mut channel = Channel { primary: 153, cbw: Cbw::Cbw20 };
280
281        // CBW20, CBW40, CBW80 will set supported_cbw_set to 0
282
283        let vht_cap_info = override_vht_capabilities(vht_cap, channel.cbw).vht_cap_info;
284        assert_eq!(vht_cap_info.supported_cbw_set(), OVERRIDE_VHT_CAP_INFO_SUPPORTED_CBW_SET);
285
286        channel.cbw = Cbw::Cbw40;
287        let vht_cap_info = override_vht_capabilities(vht_cap, channel.cbw).vht_cap_info;
288        assert_eq!(vht_cap_info.supported_cbw_set(), OVERRIDE_VHT_CAP_INFO_SUPPORTED_CBW_SET);
289
290        channel.cbw = Cbw::Cbw80;
291        let vht_cap_info = override_vht_capabilities(vht_cap, channel.cbw).vht_cap_info;
292        assert_eq!(vht_cap_info.supported_cbw_set(), OVERRIDE_VHT_CAP_INFO_SUPPORTED_CBW_SET);
293
294        // CBW160 and CBW80P80 will preserve existing supported_cbw_set value
295
296        channel.cbw = Cbw::Cbw160;
297        let vht_cap_info = override_vht_capabilities(vht_cap, channel.cbw).vht_cap_info;
298        assert_eq!(vht_cap_info.supported_cbw_set(), 2);
299
300        channel.cbw = Cbw::Cbw80P80 { secondary80: 42 };
301        let vht_cap_info = override_vht_capabilities(vht_cap, channel.cbw).vht_cap_info;
302        assert_eq!(vht_cap_info.supported_cbw_set(), 2);
303    }
304
305    #[test]
306    fn band_id() {
307        assert_eq!(fidl_ieee80211::WlanBand::TwoGhz, get_band(1));
308        assert_eq!(fidl_ieee80211::WlanBand::TwoGhz, get_band(14));
309        assert_eq!(fidl_ieee80211::WlanBand::FiveGhz, get_band(36));
310        assert_eq!(fidl_ieee80211::WlanBand::FiveGhz, get_band(165));
311    }
312
313    #[test]
314    fn test_get_band() {
315        assert_eq!(fidl_ieee80211::WlanBand::TwoGhz, get_band(14));
316        assert_eq!(fidl_ieee80211::WlanBand::FiveGhz, get_band(36));
317    }
318
319    #[test]
320    fn test_get_device_band_cap() {
321        let device_info = fidl_mlme::DeviceInfo {
322            sta_addr: [0; 6],
323            role: fidl_common::WlanMacRole::Client,
324            bands: vec![fake_5ghz_band_capability_ht_cbw(ie::ChanWidthSet::TWENTY_FORTY)],
325            softmac_hardware_capability: 0,
326            qos_capable: true,
327        };
328        assert_eq!(
329            fidl_ieee80211::WlanBand::FiveGhz,
330            get_device_band_cap(&device_info, 36).unwrap().band
331        );
332    }
333
334    fn fake_client_join_cap() -> ClientCapabilities {
335        ClientCapabilities(StaCapabilities {
336            capability_info: mac::CapabilityInfo(0x1234),
337            rates: [101, 102, 103, 104].iter().cloned().map(SupportedRate).collect(),
338            ht_cap: Some(HtCapabilities {
339                ht_cap_info: ie::HtCapabilityInfo(0).with_rx_stbc(2).with_tx_stbc(false),
340                ..ie::fake_ht_capabilities()
341            }),
342            vht_cap: Some(ie::fake_vht_capabilities()),
343        })
344    }
345
346    fn fake_ap_join_cap() -> ApCapabilities {
347        ApCapabilities(StaCapabilities {
348            capability_info: mac::CapabilityInfo(0x4321),
349            // 101 + 128 turns it into a basic rate
350            rates: [101 + 128, 102, 9].iter().cloned().map(SupportedRate).collect(),
351            ht_cap: Some(HtCapabilities {
352                ht_cap_info: ie::HtCapabilityInfo(0).with_rx_stbc(1).with_tx_stbc(true),
353                ..ie::fake_ht_capabilities()
354            }),
355            vht_cap: Some(ie::fake_vht_capabilities()),
356        })
357    }
358
359    #[test]
360    fn client_intersect_with_ap() {
361        let caps = assert_variant!(
362            intersect_with_ap_as_client(&fake_client_join_cap(), &fake_ap_join_cap()),
363            Ok(caps) => caps
364        );
365        assert_eq!(
366            caps,
367            StaCapabilities {
368                capability_info: mac::CapabilityInfo(0x0220),
369                rates: [229, 102].iter().cloned().map(SupportedRate).collect(),
370                ht_cap: Some(HtCapabilities {
371                    ht_cap_info: ie::HtCapabilityInfo(0).with_rx_stbc(2).with_tx_stbc(false),
372                    ..ie::fake_ht_capabilities()
373                }),
374                ..fake_client_join_cap().0
375            }
376        )
377    }
378
379    #[test]
380    fn ap_intersect_with_remote_client() {
381        assert_eq!(
382            intersect_with_remote_client_as_ap(&fake_ap_join_cap(), &fake_client_join_cap()),
383            StaCapabilities {
384                capability_info: mac::CapabilityInfo(0x0220),
385                rates: [229, 102].iter().cloned().map(SupportedRate).collect(),
386                ht_cap: Some(HtCapabilities {
387                    ht_cap_info: ie::HtCapabilityInfo(0).with_rx_stbc(0).with_tx_stbc(true),
388                    ..ie::fake_ht_capabilities()
389                }),
390                ..fake_ap_join_cap().0
391            }
392        );
393    }
394}