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::{Cbw, 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) =
90        override_ht_vht(band_cap.ht_cap.as_ref(), band_cap.vht_cap.as_ref(), bss_channel.cbw)?;
91
92    Ok(ClientCapabilities(StaCapabilities { capability_info, rates, ht_cap, vht_cap }))
93}
94
95/// Wrapper function to convert FIDL {HT,VHT}Capabilities into byte arrays, taking into account the
96/// limitations imposed by the channel bandwidth.
97fn override_ht_vht(
98    fidl_ht_cap: Option<&Box<fidl_ieee80211::HtCapabilities>>,
99    fidl_vht_cap: Option<&Box<fidl_ieee80211::VhtCapabilities>>,
100    cbw: Cbw,
101) -> Result<(Option<HtCapabilities>, Option<VhtCapabilities>), Error> {
102    if fidl_ht_cap.is_none() && fidl_vht_cap.is_some() {
103        return Err(format_err!("VHT Cap without HT Cap is invalid."));
104    }
105
106    let ht_cap = match fidl_ht_cap {
107        Some(h) => {
108            let ht_cap = *parse_ht_capabilities(&h.bytes[..]).context("verifying HT Cap")?;
109            Some(override_ht_capabilities(ht_cap, cbw))
110        }
111        None => None,
112    };
113
114    let vht_cap = match fidl_vht_cap {
115        Some(v) => {
116            let vht_cap = *parse_vht_capabilities(&v.bytes[..]).context("verifying VHT Cap")?;
117            Some(override_vht_capabilities(vht_cap, cbw))
118        }
119        None => None,
120    };
121    Ok((ht_cap, vht_cap))
122}
123
124/// Even though hardware may support higher channel bandwidth, if user specifies a narrower
125/// bandwidth, change the channel bandwidth in ht_cap_info to match user's preference.
126fn override_ht_capabilities(mut ht_cap: HtCapabilities, cbw: Cbw) -> HtCapabilities {
127    let mut ht_cap_info = ht_cap.ht_cap_info.with_tx_stbc(OVERRIDE_HT_CAP_INFO_TX_STBC);
128    match cbw {
129        Cbw::Cbw20 => ht_cap_info.set_chan_width_set(ie::ChanWidthSet::TWENTY_ONLY),
130        _ => (),
131    }
132    ht_cap.ht_cap_info = ht_cap_info;
133    ht_cap
134}
135
136/// Even though hardware may support higher channel bandwidth, if user specifies a narrower
137/// bandwidth, change the channel bandwidth in vht_cap_info to match user's preference.
138fn override_vht_capabilities(mut vht_cap: VhtCapabilities, cbw: Cbw) -> VhtCapabilities {
139    let mut vht_cap_info = vht_cap.vht_cap_info;
140    if vht_cap_info.supported_cbw_set() != OVERRIDE_VHT_CAP_INFO_SUPPORTED_CBW_SET {
141        // Supported channel bandwidth set can only be non-zero if the associating channel is
142        // 160 MHz or 80+80 MHz Channel bandwidth. Otherwise it will be set to 0. 0 is a purely
143        // numeric value without a name. See IEEE Std 802.11-2016 Table 9-250 for more details.
144        // TODO(https://fxbug.dev/42115418): finer control over CBW if necessary.
145        match cbw {
146            Cbw::Cbw160 | Cbw::Cbw80P80 { secondary80: _ } => (),
147            _ => vht_cap_info.set_supported_cbw_set(OVERRIDE_VHT_CAP_INFO_SUPPORTED_CBW_SET),
148        }
149    }
150    vht_cap.vht_cap_info = vht_cap_info;
151    vht_cap
152}
153
154pub fn get_band_cap_for_channel(
155    bands: &[fidl_mlme::BandCapability],
156    channel: Channel,
157) -> Result<&fidl_mlme::BandCapability, anyhow::Error> {
158    let target = channel.get_band().context("Failed to retrieve band capabilities")?;
159    bands
160        .iter()
161        .find(|b| {
162            b.band == target && b.primary_channels.iter().any(|c| c.number == channel.primary)
163        })
164        .ok_or_else(|| format_err!("No band capability for channel {channel:?}: {bands:?}"))
165}
166
167/// Capabilities that takes the iface device's capabilities based on the channel a client is trying
168/// to join, the PHY parameters that is overridden by user's command line input and the BSS the
169/// client are is trying to join.
170/// They are stored in the form of IEs because at some point they will be transmitted in
171/// (Re)Association Request and (Re)Association Response frames.
172#[derive(Debug, PartialEq)]
173pub struct StaCapabilities {
174    pub capability_info: CapabilityInfo,
175    pub rates: Vec<SupportedRate>,
176    pub ht_cap: Option<HtCapabilities>,
177    pub vht_cap: Option<VhtCapabilities>,
178}
179
180#[derive(Debug, PartialEq)]
181pub struct ClientCapabilities(pub StaCapabilities);
182#[derive(Debug, PartialEq)]
183pub struct ApCapabilities(pub StaCapabilities);
184
185/// Performs capability negotiation with an AP assuming the Fuchsia device is a client.
186pub fn intersect_with_ap_as_client(
187    client: &ClientCapabilities,
188    ap: &ApCapabilities,
189) -> Result<StaCapabilities, Error> {
190    let rates = intersect_rates(ApRates(&ap.0.rates[..]), ClientRates(&client.0.rates[..]))
191        .map_err(|e| format_err!("could not intersect rates: {:?}", e))?;
192    let (capability_info, ht_cap, vht_cap) = intersect(&client.0, &ap.0);
193    Ok(StaCapabilities { rates, capability_info, ht_cap, vht_cap })
194}
195
196/// Performs capability negotiation with a remote client assuming the Fuchsia device is an AP.
197pub fn intersect_with_remote_client_as_ap(
198    ap: &ApCapabilities,
199    remote_client: &ClientCapabilities,
200) -> StaCapabilities {
201    // Safe to unwrap. Otherwise we would have rejected the association from this remote client.
202    let rates = intersect_rates(ApRates(&ap.0.rates[..]), ClientRates(&remote_client.0.rates[..]))
203        .unwrap_or(vec![]);
204    let (capability_info, ht_cap, vht_cap) = intersect(&ap.0, &remote_client.0);
205    StaCapabilities { rates, capability_info, ht_cap, vht_cap }
206}
207
208fn intersect(
209    ours: &StaCapabilities,
210    theirs: &StaCapabilities,
211) -> (CapabilityInfo, Option<HtCapabilities>, Option<VhtCapabilities>) {
212    // Every bit is a boolean so bit-wise and is sufficient
213    let capability_info = CapabilityInfo(ours.capability_info.raw() & theirs.capability_info.raw());
214    let ht_cap = match (ours.ht_cap, theirs.ht_cap) {
215        // Intersect is NOT necessarily symmetrical. Our own capabilities prevails.
216        (Some(ours), Some(theirs)) => Some(ours.intersect(&theirs)),
217        _ => None,
218    };
219    let vht_cap = match (ours.vht_cap, theirs.vht_cap) {
220        // Intersect is NOT necessarily symmetrical. Our own capabilities prevails.
221        (Some(ours), Some(theirs)) => Some(ours.intersect(&theirs)),
222        _ => None,
223    };
224    (capability_info, ht_cap, vht_cap)
225}
226
227#[cfg(test)]
228mod tests {
229    use super::*;
230    use crate::mac;
231    use crate::test_utils::fake_capabilities::fake_5ghz_band_capability_ht;
232    use assert_matches::assert_matches;
233    use fidl_fuchsia_wlan_common as fidl_common;
234
235    #[test]
236    fn test_build_cap_info() {
237        let capability_info = CapabilityInfo(0)
238            .with_ess(!OVERRIDE_CAP_INFO_ESS)
239            .with_ibss(!OVERRIDE_CAP_INFO_IBSS)
240            .with_cf_pollable(!OVERRIDE_CAP_INFO_CF_POLLABLE)
241            .with_cf_poll_req(!OVERRIDE_CAP_INFO_CF_POLL_REQUEST)
242            .with_privacy(!OVERRIDE_CAP_INFO_PRIVACY)
243            .with_spectrum_mgmt(!OVERRIDE_CAP_INFO_SPECTRUM_MGMT);
244        let capability_info = override_capability_info(capability_info);
245        assert_eq!(capability_info.ess(), OVERRIDE_CAP_INFO_ESS);
246        assert_eq!(capability_info.ibss(), OVERRIDE_CAP_INFO_IBSS);
247        assert_eq!(capability_info.cf_pollable(), OVERRIDE_CAP_INFO_CF_POLLABLE);
248        assert_eq!(capability_info.cf_poll_req(), OVERRIDE_CAP_INFO_CF_POLL_REQUEST);
249        assert_eq!(capability_info.privacy(), OVERRIDE_CAP_INFO_PRIVACY);
250        assert_eq!(capability_info.spectrum_mgmt(), OVERRIDE_CAP_INFO_SPECTRUM_MGMT);
251    }
252
253    #[test]
254    fn test_override_ht_cap() {
255        let mut ht_cap = ie::fake_ht_capabilities();
256        let ht_cap_info = ht_cap
257            .ht_cap_info
258            .with_tx_stbc(!OVERRIDE_HT_CAP_INFO_TX_STBC)
259            .with_chan_width_set(ie::ChanWidthSet::TWENTY_FORTY);
260        ht_cap.ht_cap_info = ht_cap_info;
261        let mut channel = Channel::new(153, Cbw::Cbw20, fidl_ieee80211::WlanBand::FiveGhz);
262
263        let ht_cap_info = override_ht_capabilities(ht_cap, channel.cbw).ht_cap_info;
264        assert_eq!(ht_cap_info.tx_stbc(), OVERRIDE_HT_CAP_INFO_TX_STBC);
265        assert_eq!(ht_cap_info.chan_width_set(), ie::ChanWidthSet::TWENTY_ONLY);
266
267        channel.cbw = Cbw::Cbw40;
268        let ht_cap_info = override_ht_capabilities(ht_cap, channel.cbw).ht_cap_info;
269        assert_eq!(ht_cap_info.chan_width_set(), ie::ChanWidthSet::TWENTY_FORTY);
270    }
271
272    #[test]
273    fn test_override_vht_cap() {
274        let mut vht_cap = ie::fake_vht_capabilities();
275        let vht_cap_info = vht_cap.vht_cap_info.with_supported_cbw_set(2);
276        vht_cap.vht_cap_info = vht_cap_info;
277        let mut channel = Channel::new(153, Cbw::Cbw20, fidl_ieee80211::WlanBand::FiveGhz);
278
279        // CBW20, CBW40, CBW80 will set supported_cbw_set to 0
280
281        let vht_cap_info = override_vht_capabilities(vht_cap, channel.cbw).vht_cap_info;
282        assert_eq!(vht_cap_info.supported_cbw_set(), OVERRIDE_VHT_CAP_INFO_SUPPORTED_CBW_SET);
283
284        channel.cbw = Cbw::Cbw40;
285        let vht_cap_info = override_vht_capabilities(vht_cap, channel.cbw).vht_cap_info;
286        assert_eq!(vht_cap_info.supported_cbw_set(), OVERRIDE_VHT_CAP_INFO_SUPPORTED_CBW_SET);
287
288        channel.cbw = Cbw::Cbw80;
289        let vht_cap_info = override_vht_capabilities(vht_cap, channel.cbw).vht_cap_info;
290        assert_eq!(vht_cap_info.supported_cbw_set(), OVERRIDE_VHT_CAP_INFO_SUPPORTED_CBW_SET);
291
292        // CBW160 and CBW80P80 will preserve existing supported_cbw_set value
293
294        channel.cbw = Cbw::Cbw160;
295        let vht_cap_info = override_vht_capabilities(vht_cap, channel.cbw).vht_cap_info;
296        assert_eq!(vht_cap_info.supported_cbw_set(), 2);
297
298        channel.cbw = Cbw::Cbw80P80 { secondary80: 42 };
299        let vht_cap_info = override_vht_capabilities(vht_cap, channel.cbw).vht_cap_info;
300        assert_eq!(vht_cap_info.supported_cbw_set(), 2);
301    }
302
303    #[test]
304    fn test_get_device_band_cap() {
305        let device_info = fidl_mlme::DeviceInfo {
306            sta_addr: [0; 6],
307            factory_addr: [0; 6],
308            role: fidl_common::WlanMacRole::Client,
309            bands: vec![fake_5ghz_band_capability_ht(ie::ChanWidthSet::TWENTY_FORTY)],
310            softmac_hardware_capability: 0,
311            qos_capable: true,
312        };
313        assert_eq!(
314            fidl_ieee80211::WlanBand::FiveGhz,
315            get_band_cap_for_channel(
316                &device_info.bands[..],
317                Channel::new(36, Cbw::Cbw20, fidl_ieee80211::WlanBand::FiveGhz)
318            )
319            .unwrap()
320            .band
321        );
322    }
323
324    fn fake_client_join_cap() -> ClientCapabilities {
325        ClientCapabilities(StaCapabilities {
326            capability_info: mac::CapabilityInfo(0x1234),
327            rates: [101, 102, 103, 104].iter().cloned().map(SupportedRate).collect(),
328            ht_cap: Some(HtCapabilities {
329                ht_cap_info: ie::HtCapabilityInfo(0).with_rx_stbc(2).with_tx_stbc(false),
330                ..ie::fake_ht_capabilities()
331            }),
332            vht_cap: Some(ie::fake_vht_capabilities()),
333        })
334    }
335
336    fn fake_ap_join_cap() -> ApCapabilities {
337        ApCapabilities(StaCapabilities {
338            capability_info: mac::CapabilityInfo(0x4321),
339            // 101 + 128 turns it into a basic rate
340            rates: [101 + 128, 102, 9].iter().cloned().map(SupportedRate).collect(),
341            ht_cap: Some(HtCapabilities {
342                ht_cap_info: ie::HtCapabilityInfo(0).with_rx_stbc(1).with_tx_stbc(true),
343                ..ie::fake_ht_capabilities()
344            }),
345            vht_cap: Some(ie::fake_vht_capabilities()),
346        })
347    }
348
349    #[test]
350    fn client_intersect_with_ap() {
351        let caps = assert_matches!(
352            intersect_with_ap_as_client(&fake_client_join_cap(), &fake_ap_join_cap()),
353            Ok(caps) => caps
354        );
355        assert_eq!(
356            caps,
357            StaCapabilities {
358                capability_info: mac::CapabilityInfo(0x0220),
359                rates: [229, 102].iter().cloned().map(SupportedRate).collect(),
360                ht_cap: Some(HtCapabilities {
361                    ht_cap_info: ie::HtCapabilityInfo(0).with_rx_stbc(2).with_tx_stbc(false),
362                    ..ie::fake_ht_capabilities()
363                }),
364                ..fake_client_join_cap().0
365            }
366        )
367    }
368
369    #[test]
370    fn ap_intersect_with_remote_client() {
371        assert_eq!(
372            intersect_with_remote_client_as_ap(&fake_ap_join_cap(), &fake_client_join_cap()),
373            StaCapabilities {
374                capability_info: mac::CapabilityInfo(0x0220),
375                rates: [229, 102].iter().cloned().map(SupportedRate).collect(),
376                ht_cap: Some(HtCapabilities {
377                    ht_cap_info: ie::HtCapabilityInfo(0).with_rx_stbc(0).with_tx_stbc(true),
378                    ..ie::fake_ht_capabilities()
379                }),
380                ..fake_ap_join_cap().0
381            }
382        );
383    }
384}