Skip to main content

wlan_common/
channel.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
5use crate::ie;
6use anyhow::format_err;
7use fidl_fuchsia_wlan_ieee80211 as fidl_ieee80211;
8use std::fmt;
9
10// IEEE Std 802.11-2016, Annex E
11// Note the distinction of index for primary20 and index for center frequency.
12// Fuchsia OS minimizes the use of the notion of center frequency,
13// with following exceptions:
14// - Cbw80P80's secondary frequency segment
15// - Frequency conversion at device drivers
16pub type MHz = u16;
17pub const BASE_FREQ_2GHZ: MHz = 2407;
18pub const BASE_FREQ_5GHZ: MHz = 5000;
19
20pub const INVALID_CHAN_IDX: u8 = 0;
21
22/// Channel bandwidth. Cbw80P80 requires the specification of
23/// channel index corresponding to the center frequency
24/// of the secondary consecutive frequency segment.
25#[derive(Clone, Copy, Debug, Ord, PartialOrd, Eq, PartialEq)]
26pub enum Cbw {
27    Cbw20,
28    Cbw40, // Same as Cbw40Above
29    Cbw40Below,
30    Cbw80,
31    Cbw160,
32    Cbw80P80 { secondary80: u8 },
33}
34
35impl Cbw {
36    // TODO(https://fxbug.dev/42164482): Implement `From `instead.
37    pub fn to_fidl(&self) -> (fidl_ieee80211::ChannelBandwidth, u8) {
38        match self {
39            Cbw::Cbw20 => (fidl_ieee80211::ChannelBandwidth::Cbw20, 0),
40            Cbw::Cbw40 => (fidl_ieee80211::ChannelBandwidth::Cbw40, 0),
41            Cbw::Cbw40Below => (fidl_ieee80211::ChannelBandwidth::Cbw40Below, 0),
42            Cbw::Cbw80 => (fidl_ieee80211::ChannelBandwidth::Cbw80, 0),
43            Cbw::Cbw160 => (fidl_ieee80211::ChannelBandwidth::Cbw160, 0),
44            Cbw::Cbw80P80 { secondary80 } => {
45                (fidl_ieee80211::ChannelBandwidth::Cbw80P80, *secondary80)
46            }
47        }
48    }
49
50    pub fn from_fidl(
51        fidl_cbw: fidl_ieee80211::ChannelBandwidth,
52        fidl_secondary80: u8,
53    ) -> Result<Self, anyhow::Error> {
54        match fidl_cbw {
55            fidl_ieee80211::ChannelBandwidth::Cbw20 => Ok(Cbw::Cbw20),
56            fidl_ieee80211::ChannelBandwidth::Cbw40 => Ok(Cbw::Cbw40),
57            fidl_ieee80211::ChannelBandwidth::Cbw40Below => Ok(Cbw::Cbw40Below),
58            fidl_ieee80211::ChannelBandwidth::Cbw80 => Ok(Cbw::Cbw80),
59            fidl_ieee80211::ChannelBandwidth::Cbw160 => Ok(Cbw::Cbw160),
60            fidl_ieee80211::ChannelBandwidth::Cbw80P80 => {
61                Ok(Cbw::Cbw80P80 { secondary80: fidl_secondary80 })
62            }
63            fidl_ieee80211::ChannelBandwidthUnknown!() => {
64                Err(format_err!("Unknown channel bandwidth from fidl: {:?}", fidl_cbw))
65            }
66        }
67    }
68}
69
70/// A Channel defines the frequency spectrum to be used for radio synchronization.
71/// See for sister definitions in FIDL and C/C++
72///  - //sdk/fidl/fuchsia.wlan.common/wlan_common.fidl |struct wlan_channel_t|
73///  - //sdk/fidl/fuchsia.wlan.mlme/wlan_mlme.fidl |struct WlanChan|
74#[derive(Clone, Copy, Debug, Ord, PartialOrd, Eq, PartialEq)]
75pub struct Channel {
76    // TODO(porce): Augment with country and band
77    pub primary: u8,
78    pub cbw: Cbw,
79}
80
81// Fuchsia's short CBW notation. Not IEEE standard.
82impl fmt::Display for Cbw {
83    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
84        match self {
85            Cbw::Cbw20 => write!(f, ""),       // Vanilla plain 20 MHz bandwidth
86            Cbw::Cbw40 => write!(f, "+"),      // SCA, often denoted by "+1"
87            Cbw::Cbw40Below => write!(f, "-"), // SCB, often denoted by "-1",
88            Cbw::Cbw80 => write!(f, "V"),      // VHT 80 MHz (V from VHT)
89            Cbw::Cbw160 => write!(f, "W"),     // VHT 160 MHz (as Wide as V + V ;) )
90            Cbw::Cbw80P80 { secondary80 } => write!(f, "+{}P", secondary80), // VHT 80Plus80 (not often obvious, but P is the first alphabet)
91        }
92    }
93}
94
95impl fmt::Display for Channel {
96    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
97        write!(f, "{}{}", self.primary, self.cbw)
98    }
99}
100
101impl Channel {
102    pub fn new(primary: u8, cbw: Cbw) -> Self {
103        Channel { primary, cbw }
104    }
105
106    // TODO(https://fxbug.dev/42172557): Using channel number to determine band is incorrect.
107    fn is_primary_2ghz(&self) -> bool {
108        let p = self.primary;
109        p >= 1 && p <= 14
110    }
111
112    // TODO(https://fxbug.dev/42172557): Using channel number to determine band is incorrect.
113    fn is_primary_5ghz(&self) -> bool {
114        let p = self.primary;
115        match p {
116            36..=64 => (p - 36) % 4 == 0,
117            100..=144 => (p - 100) % 4 == 0,
118            149..=165 => (p - 149) % 4 == 0,
119            _ => false,
120        }
121    }
122
123    // TODO(https://fxbug.dev/42172557): Using channel number to determine band is incorrect.
124    pub fn get_band(&self) -> Result<fidl_ieee80211::WlanBand, anyhow::Error> {
125        if self.is_primary_2ghz() {
126            Ok(fidl_ieee80211::WlanBand::TwoGhz)
127        } else if self.is_primary_5ghz() {
128            Ok(fidl_ieee80211::WlanBand::FiveGhz)
129        } else {
130            Err(format_err!("cannot get band for channel {}", self))
131        }
132    }
133
134    fn get_band_start_freq(&self) -> Result<MHz, anyhow::Error> {
135        if self.is_primary_2ghz() {
136            Ok(BASE_FREQ_2GHZ)
137        } else if self.is_primary_5ghz() {
138            Ok(BASE_FREQ_5GHZ)
139        } else {
140            Err(format_err!("cannot get band start freq for channel {}", self))
141        }
142    }
143
144    // Note get_center_chan_idx() is to assist channel validity test.
145    // Return of Ok() does not imply the channel under test is valid.
146    fn get_center_chan_idx(&self) -> Result<u8, anyhow::Error> {
147        if !(self.is_primary_2ghz() || self.is_primary_5ghz()) {
148            return Err(format_err!(
149                "cannot get center channel index for an invalid primary channel {}",
150                self
151            ));
152        }
153
154        let p = self.primary;
155        match self.cbw {
156            Cbw::Cbw20 => Ok(p),
157            Cbw::Cbw40 => Ok(p + 2),
158            Cbw::Cbw40Below => Ok(p - 2),
159            Cbw::Cbw80 | Cbw::Cbw80P80 { .. } => match p {
160                36..=48 => Ok(42),
161                52..=64 => Ok(58),
162                100..=112 => Ok(106),
163                116..=128 => Ok(122),
164                132..=144 => Ok(138),
165                148..=161_ => Ok(155),
166                _ => {
167                    return Err(format_err!(
168                        "cannot get center channel index for invalid channel {}",
169                        self
170                    ));
171                }
172            },
173            Cbw::Cbw160 => {
174                // See IEEE Std 802.11-2016 Table 9-252 and 9-253.
175                // Note CBW160 has only one frequency segment, regardless of
176                // encodings on CCFS0 and CCFS1 in VHT Operation Information IE.
177                match p {
178                    36..=64 => Ok(50),
179                    100..=128 => Ok(114),
180                    _ => {
181                        return Err(format_err!(
182                            "cannot get center channel index for invalid channel {}",
183                            self
184                        ));
185                    }
186                }
187            }
188        }
189    }
190
191    /// Returns the center frequency of the first consecutive frequency segment of the channel
192    /// in MHz if the channel is valid, Err(String) otherwise.
193    pub fn get_center_freq(&self) -> Result<MHz, anyhow::Error> {
194        // IEEE Std 802.11-2016, 21.3.14
195        let start_freq = self.get_band_start_freq()?;
196        let center_chan_idx = self.get_center_chan_idx()?;
197        let spacing: MHz = 5;
198        Ok(start_freq + spacing * center_chan_idx as u16)
199    }
200
201    /// Returns true if the primary channel index, channel bandwidth, and the secondary consecutive
202    /// frequency segment (Cbw80P80 only) are all consistent and meet regulatory requirements of
203    /// the USA. TODO(https://fxbug.dev/42104247): Other countries.
204    pub fn is_valid_in_us(&self) -> bool {
205        if self.is_primary_2ghz() {
206            self.is_valid_2ghz_in_us()
207        } else if self.is_primary_5ghz() {
208            self.is_valid_5ghz_in_us()
209        } else {
210            false
211        }
212    }
213
214    fn is_valid_2ghz_in_us(&self) -> bool {
215        if !self.is_primary_2ghz() {
216            return false;
217        }
218        let p = self.primary;
219        match self.cbw {
220            Cbw::Cbw20 => p <= 11,
221            Cbw::Cbw40 => p <= 7,
222            Cbw::Cbw40Below => p >= 5,
223            _ => false,
224        }
225    }
226
227    fn is_valid_5ghz_in_us(&self) -> bool {
228        if !self.is_primary_5ghz() {
229            return false;
230        }
231        let p = self.primary;
232        match self.cbw {
233            Cbw::Cbw20 => true,
234            Cbw::Cbw40 => p != 165 && (p % 8) == (if p <= 144 { 4 } else { 5 }),
235            Cbw::Cbw40Below => p != 165 && (p % 8) == (if p <= 144 { 0 } else { 1 }),
236            Cbw::Cbw80 => p != 165,
237            Cbw::Cbw160 => p < 132,
238            Cbw::Cbw80P80 { secondary80 } => {
239                if p == 165 {
240                    return false;
241                }
242                let valid_secondary80: [u8; 6] = [42, 58, 106, 122, 138, 155];
243                if !valid_secondary80.contains(&secondary80) {
244                    return false;
245                }
246                let ccfs0 = match self.get_center_chan_idx() {
247                    Ok(v) => v,
248                    Err(_) => return false,
249                };
250                let ccfs1 = secondary80;
251                let gap = (ccfs0 as i16 - ccfs1 as i16).abs();
252                gap > 16
253            }
254        }
255    }
256
257    /// Returns true if the channel is 2GHz. Does not perform validity checks.
258    pub fn is_2ghz(&self) -> bool {
259        self.is_primary_2ghz()
260    }
261
262    /// Returns true if the channel is 5GHz. Does not perform validity checks.
263    pub fn is_5ghz(&self) -> bool {
264        self.is_primary_5ghz()
265    }
266}
267
268impl From<Channel> for fidl_ieee80211::WlanChannel {
269    fn from(channel: Channel) -> fidl_ieee80211::WlanChannel {
270        fidl_ieee80211::WlanChannel::from(&channel)
271    }
272}
273
274impl From<&Channel> for fidl_ieee80211::WlanChannel {
275    fn from(channel: &Channel) -> fidl_ieee80211::WlanChannel {
276        let (cbw, secondary80) = channel.cbw.to_fidl();
277        fidl_ieee80211::WlanChannel { primary: channel.primary, cbw, secondary80 }
278    }
279}
280
281impl TryFrom<fidl_ieee80211::WlanChannel> for Channel {
282    type Error = anyhow::Error;
283    fn try_from(fidl_channel: fidl_ieee80211::WlanChannel) -> Result<Channel, Self::Error> {
284        Channel::try_from(&fidl_channel)
285    }
286}
287
288impl TryFrom<&fidl_ieee80211::WlanChannel> for Channel {
289    type Error = anyhow::Error;
290
291    fn try_from(fidl_channel: &fidl_ieee80211::WlanChannel) -> Result<Channel, Self::Error> {
292        Ok(Channel {
293            primary: fidl_channel.primary,
294            cbw: Cbw::from_fidl(fidl_channel.cbw, fidl_channel.secondary80)?,
295        })
296    }
297}
298
299/// Derive channel given DSSS param set, HT operation, and VHT operation IEs from
300/// beacon or probe response, and the primary channel from which such frame is
301/// received on.
302///
303/// Primary channel is extracted from HT op, DSSS param set, or `rx_primary_channel`,
304/// in descending priority.
305pub fn derive_channel(
306    rx_primary_channel: u8,
307    dsss_channel: Option<u8>,
308    ht_op: Option<ie::HtOperation>,
309    vht_op: Option<ie::VhtOperation>,
310) -> fidl_ieee80211::WlanChannel {
311    let primary = ht_op
312        .as_ref()
313        .map(|ht_op| ht_op.primary_channel)
314        .or(dsss_channel)
315        .unwrap_or(rx_primary_channel);
316
317    let ht_op_cbw = ht_op.map(|ht_op| ht_op.ht_op_info.sta_chan_width());
318    let vht_cbw_and_segs =
319        vht_op.map(|vht_op| (vht_op.vht_cbw, vht_op.center_freq_seg0, vht_op.center_freq_seg1));
320
321    let (cbw, secondary80) = match ht_op_cbw {
322        // Inspect vht/ht op parameters to determine the channel width.
323        Some(ie::StaChanWidth::ANY) => {
324            // Safe to unwrap `ht_op` because `ht_op_cbw` is only Some(_) if `ht_op` has a value.
325            let sec_chan_offset = ht_op.unwrap().ht_op_info.secondary_chan_offset();
326            derive_wide_channel_bandwidth(vht_cbw_and_segs, sec_chan_offset)
327        }
328        // Default to Cbw20 if HT CBW field is set to 0 or not present.
329        _ => Cbw::Cbw20,
330    }
331    .to_fidl();
332
333    fidl_ieee80211::WlanChannel { primary, cbw, secondary80 }
334}
335
336/// Derive a CBW for a primary channel or channel switch.
337/// VHT parameter derivation is defined identically by:
338///     IEEE Std 802.11-2016 9.4.2.159 Table 9-252 for channel switching
339///     IEEE Std 802.11-2016 11.40.1 Table 11-24 for VHT operation
340/// SecChanOffset is defined identially by:
341///     IEEE Std 802.11-2016 9.4.2.20 for channel switching
342///     IEEE Std 802.11-2016 9.4.2.57 Table 9-168 for HT operation
343pub fn derive_wide_channel_bandwidth(
344    vht_cbw_and_segs: Option<(ie::VhtChannelBandwidth, u8, u8)>,
345    sec_chan_offset: ie::SecChanOffset,
346) -> Cbw {
347    use ie::VhtChannelBandwidth as Vcb;
348    match vht_cbw_and_segs {
349        Some((Vcb::CBW_80_160_80P80, _, 0)) => Cbw::Cbw80,
350        Some((Vcb::CBW_80_160_80P80, seg0, seg1)) if abs_sub(seg0, seg1) == 8 => Cbw::Cbw160,
351        Some((Vcb::CBW_80_160_80P80, seg0, seg1)) if abs_sub(seg0, seg1) > 16 => {
352            // See IEEE 802.11-2016, Table 9-252, about channel center frequency segment 1
353            Cbw::Cbw80P80 { secondary80: seg1 }
354        }
355        // Use HT CBW if
356        // - VHT op is not present,
357        // - VHT op has deprecated parameters sets, or
358        // - VHT CBW field is set to 0
359        _ => match sec_chan_offset {
360            ie::SecChanOffset::SECONDARY_ABOVE => Cbw::Cbw40,
361            ie::SecChanOffset::SECONDARY_BELOW => Cbw::Cbw40Below,
362            ie::SecChanOffset::SECONDARY_NONE | _ => Cbw::Cbw20,
363        },
364    }
365}
366
367fn abs_sub(v1: u8, v2: u8) -> u8 {
368    if v2 >= v1 { v2 - v1 } else { v1 - v2 }
369}
370
371/// Converts a 20MHz primary channel center frequency in MHz to a channel number. Returns an error
372/// if the frequency does not correspond to the center frequency of a valid
373/// standard channel in the 2.4GHz or 5GHz bands.
374pub fn primary_channel_from_freq(freq: u32) -> Option<u8> {
375    // 2.4 GHz: Channels 1-13
376    if (2412..=2472).contains(&freq) && (freq - 2412) % 5 == 0 {
377        Some(((freq - 2407) / 5) as u8)
378    // 2.4 GHz: Channel 14
379    } else if freq == 2484 {
380        Some(14)
381    // 5 GHz: Channels 36-144
382    } else if (5180..=5720).contains(&freq) && (freq - 5180) % 20 == 0 {
383        Some(((freq - 5000) / 5) as u8)
384    // 5 GHz: Channels 149-173
385    } else if (5745..=5865).contains(&freq) && (freq - 5745) % 20 == 0 {
386        Some(((freq - 5000) / 5) as u8)
387    } else {
388        None
389    }
390}
391
392#[cfg(test)]
393mod tests {
394    use super::*;
395
396    #[test]
397    fn fmt_display() {
398        let mut c = Channel::new(100, Cbw::Cbw40);
399        assert_eq!(format!("{}", c), "100+");
400        c.cbw = Cbw::Cbw160;
401        assert_eq!(format!("{}", c), "100W");
402        c.cbw = Cbw::Cbw80P80 { secondary80: 200 };
403        assert_eq!(format!("{}", c), "100+200P");
404    }
405
406    #[test]
407    fn test_is_primary_2ghz_or_5ghz() {
408        // Note Cbw is ignored in this test.
409        assert!(Channel::new(1, Cbw::Cbw160).is_primary_2ghz());
410        assert!(!Channel::new(1, Cbw::Cbw160).is_primary_5ghz());
411
412        assert!(Channel::new(12, Cbw::Cbw160).is_primary_2ghz());
413        assert!(!Channel::new(12, Cbw::Cbw160).is_primary_5ghz());
414
415        assert!(!Channel::new(36, Cbw::Cbw160).is_primary_2ghz());
416        assert!(Channel::new(36, Cbw::Cbw160).is_primary_5ghz());
417
418        assert!(!Channel::new(37, Cbw::Cbw160).is_primary_2ghz());
419        assert!(!Channel::new(37, Cbw::Cbw160).is_primary_5ghz());
420
421        assert!(!Channel::new(165, Cbw::Cbw160).is_primary_2ghz());
422        assert!(Channel::new(165, Cbw::Cbw160).is_primary_5ghz());
423
424        assert!(!Channel::new(166, Cbw::Cbw160).is_primary_2ghz());
425        assert!(!Channel::new(166, Cbw::Cbw160).is_primary_5ghz());
426    }
427
428    #[test]
429    fn test_get_band() {
430        assert_eq!(
431            fidl_ieee80211::WlanBand::TwoGhz,
432            Channel::new(1, Cbw::Cbw20).get_band().unwrap()
433        );
434        assert_eq!(
435            fidl_ieee80211::WlanBand::TwoGhz,
436            Channel::new(14, Cbw::Cbw40).get_band().unwrap()
437        );
438        assert_eq!(
439            fidl_ieee80211::WlanBand::FiveGhz,
440            Channel::new(36, Cbw::Cbw80).get_band().unwrap()
441        );
442        assert_eq!(
443            fidl_ieee80211::WlanBand::FiveGhz,
444            Channel::new(165, Cbw::Cbw160).get_band().unwrap()
445        );
446    }
447
448    #[test]
449    fn test_band_start_freq() {
450        assert_eq!(BASE_FREQ_2GHZ, Channel::new(1, Cbw::Cbw20).get_band_start_freq().unwrap());
451        assert_eq!(BASE_FREQ_5GHZ, Channel::new(100, Cbw::Cbw20).get_band_start_freq().unwrap());
452        assert!(Channel::new(15, Cbw::Cbw20).get_band_start_freq().is_err());
453        assert!(Channel::new(200, Cbw::Cbw20).get_band_start_freq().is_err());
454    }
455
456    #[test]
457    fn test_get_center_chan_idx() {
458        assert!(Channel::new(1, Cbw::Cbw80).get_center_chan_idx().is_err());
459        assert_eq!(9, Channel::new(11, Cbw::Cbw40Below).get_center_chan_idx().unwrap());
460        assert_eq!(8, Channel::new(6, Cbw::Cbw40).get_center_chan_idx().unwrap());
461        assert_eq!(36, Channel::new(36, Cbw::Cbw20).get_center_chan_idx().unwrap());
462        assert_eq!(38, Channel::new(36, Cbw::Cbw40).get_center_chan_idx().unwrap());
463        assert_eq!(42, Channel::new(36, Cbw::Cbw80).get_center_chan_idx().unwrap());
464        assert_eq!(50, Channel::new(36, Cbw::Cbw160).get_center_chan_idx().unwrap());
465        assert_eq!(
466            42,
467            Channel::new(36, Cbw::Cbw80P80 { secondary80: 155 }).get_center_chan_idx().unwrap()
468        );
469    }
470
471    #[test]
472    fn test_get_center_freq() {
473        assert_eq!(2412 as MHz, Channel::new(1, Cbw::Cbw20).get_center_freq().unwrap());
474        assert_eq!(2437 as MHz, Channel::new(6, Cbw::Cbw20).get_center_freq().unwrap());
475        assert_eq!(2447 as MHz, Channel::new(6, Cbw::Cbw40).get_center_freq().unwrap());
476        assert_eq!(2427 as MHz, Channel::new(6, Cbw::Cbw40Below).get_center_freq().unwrap());
477        assert_eq!(5180 as MHz, Channel::new(36, Cbw::Cbw20).get_center_freq().unwrap());
478        assert_eq!(5190 as MHz, Channel::new(36, Cbw::Cbw40).get_center_freq().unwrap());
479        assert_eq!(5210 as MHz, Channel::new(36, Cbw::Cbw80).get_center_freq().unwrap());
480        assert_eq!(5250 as MHz, Channel::new(36, Cbw::Cbw160).get_center_freq().unwrap());
481        assert_eq!(
482            5210 as MHz,
483            Channel::new(36, Cbw::Cbw80P80 { secondary80: 155 }).get_center_freq().unwrap()
484        );
485    }
486
487    #[test]
488    fn test_primarychannel_from_freq() {
489        assert_eq!(Some(1), primary_channel_from_freq(2412));
490        // This is between the center frequencies of channel 1 and 2
491        assert_eq!(None, primary_channel_from_freq(2413));
492        assert_eq!(Some(6), primary_channel_from_freq(2437));
493        assert_eq!(Some(13), primary_channel_from_freq(2472));
494        assert_eq!(Some(14), primary_channel_from_freq(2484));
495        // This is below the range of recognized 5GHz channels
496        assert_eq!(None, primary_channel_from_freq(5160));
497        assert_eq!(Some(36), primary_channel_from_freq(5180));
498        // This is between the center frequencies of channel 36 and 40
499        assert_eq!(None, primary_channel_from_freq(5190));
500        assert_eq!(Some(165), primary_channel_from_freq(5825));
501        assert_eq!(Some(173), primary_channel_from_freq(5865));
502        // This is below the range of recognized 2.4GHz channels
503        assert_eq!(None, primary_channel_from_freq(2400));
504        // This is below the range of recognized 5GHz channels
505        assert_eq!(None, primary_channel_from_freq(5000));
506        // This is above the range of recognized channels
507        assert_eq!(None, primary_channel_from_freq(5885));
508    }
509
510    #[test]
511    fn test_valid_us_combo() {
512        assert!(Channel::new(1, Cbw::Cbw20).is_valid_in_us());
513        assert!(Channel::new(1, Cbw::Cbw40).is_valid_in_us());
514        assert!(Channel::new(5, Cbw::Cbw40Below).is_valid_in_us());
515        assert!(Channel::new(6, Cbw::Cbw20).is_valid_in_us());
516        assert!(Channel::new(6, Cbw::Cbw40).is_valid_in_us());
517        assert!(Channel::new(6, Cbw::Cbw40Below).is_valid_in_us());
518        assert!(Channel::new(7, Cbw::Cbw40).is_valid_in_us());
519        assert!(Channel::new(11, Cbw::Cbw20).is_valid_in_us());
520        assert!(Channel::new(11, Cbw::Cbw40Below).is_valid_in_us());
521
522        assert!(Channel::new(36, Cbw::Cbw20).is_valid_in_us());
523        assert!(Channel::new(36, Cbw::Cbw40).is_valid_in_us());
524        assert!(Channel::new(36, Cbw::Cbw160).is_valid_in_us());
525        assert!(Channel::new(40, Cbw::Cbw20).is_valid_in_us());
526        assert!(Channel::new(40, Cbw::Cbw40Below).is_valid_in_us());
527        assert!(Channel::new(40, Cbw::Cbw160).is_valid_in_us());
528        assert!(Channel::new(36, Cbw::Cbw80P80 { secondary80: 155 }).is_valid_in_us());
529        assert!(Channel::new(40, Cbw::Cbw80P80 { secondary80: 155 }).is_valid_in_us());
530        assert!(Channel::new(161, Cbw::Cbw80P80 { secondary80: 42 }).is_valid_in_us());
531    }
532
533    #[test]
534    fn test_invalid_us_combo() {
535        assert!(!Channel::new(1, Cbw::Cbw40Below).is_valid_in_us());
536        assert!(!Channel::new(4, Cbw::Cbw40Below).is_valid_in_us());
537        assert!(!Channel::new(8, Cbw::Cbw40).is_valid_in_us());
538        assert!(!Channel::new(11, Cbw::Cbw40).is_valid_in_us());
539        assert!(!Channel::new(6, Cbw::Cbw80).is_valid_in_us());
540        assert!(!Channel::new(6, Cbw::Cbw160).is_valid_in_us());
541        assert!(!Channel::new(6, Cbw::Cbw80P80 { secondary80: 155 }).is_valid_in_us());
542
543        assert!(!Channel::new(36, Cbw::Cbw40Below).is_valid_in_us());
544        assert!(!Channel::new(36, Cbw::Cbw80P80 { secondary80: 58 }).is_valid_in_us());
545        assert!(!Channel::new(40, Cbw::Cbw40).is_valid_in_us());
546        assert!(!Channel::new(40, Cbw::Cbw80P80 { secondary80: 42 }).is_valid_in_us());
547
548        assert!(!Channel::new(165, Cbw::Cbw80).is_valid_in_us());
549        assert!(!Channel::new(165, Cbw::Cbw80P80 { secondary80: 42 }).is_valid_in_us());
550    }
551
552    #[test]
553    fn test_is_2ghz_or_5ghz() {
554        assert!(Channel::new(1, Cbw::Cbw20).is_2ghz());
555        assert!(!Channel::new(1, Cbw::Cbw20).is_5ghz());
556        assert!(Channel::new(13, Cbw::Cbw20).is_2ghz());
557        assert!(!Channel::new(13, Cbw::Cbw20).is_5ghz());
558        assert!(Channel::new(36, Cbw::Cbw20).is_5ghz());
559        assert!(!Channel::new(36, Cbw::Cbw20).is_2ghz());
560    }
561
562    #[test]
563    fn test_convert_fidl_channel() {
564        let mut f = fidl_ieee80211::WlanChannel::from(Channel::new(1, Cbw::Cbw20));
565        assert!(
566            f.primary == 1
567                && f.cbw == fidl_ieee80211::ChannelBandwidth::Cbw20
568                && f.secondary80 == 0
569        );
570
571        f = Channel::new(36, Cbw::Cbw80P80 { secondary80: 155 }).into();
572        assert!(
573            f.primary == 36
574                && f.cbw == fidl_ieee80211::ChannelBandwidth::Cbw80P80
575                && f.secondary80 == 155
576        );
577
578        let mut c = Channel::try_from(fidl_ieee80211::WlanChannel {
579            primary: 11,
580            cbw: fidl_ieee80211::ChannelBandwidth::Cbw40Below,
581            secondary80: 123,
582        })
583        .unwrap();
584        assert!(c.primary == 11 && c.cbw == Cbw::Cbw40Below);
585        c = fidl_ieee80211::WlanChannel {
586            primary: 149,
587            cbw: fidl_ieee80211::ChannelBandwidth::Cbw80P80,
588            secondary80: 42,
589        }
590        .try_into()
591        .unwrap();
592        assert!(c.primary == 149 && c.cbw == Cbw::Cbw80P80 { secondary80: 42 });
593
594        let r = Channel::try_from(fidl_ieee80211::WlanChannel {
595            primary: 11,
596            cbw: fidl_ieee80211::ChannelBandwidth::unknown(),
597            secondary80: 123,
598        });
599        assert!(r.is_err());
600    }
601
602    const RX_PRIMARY_CHAN: u8 = 11;
603    const HT_PRIMARY_CHAN: u8 = 48;
604
605    #[test]
606    fn test_derive_channel_basic() {
607        let channel = derive_channel(RX_PRIMARY_CHAN, None, None, None);
608        assert_eq!(
609            channel,
610            fidl_ieee80211::WlanChannel {
611                primary: RX_PRIMARY_CHAN,
612                cbw: fidl_ieee80211::ChannelBandwidth::Cbw20,
613                secondary80: 0,
614            }
615        );
616    }
617
618    #[test]
619    fn test_derive_channel_with_dsss_param() {
620        let channel = derive_channel(RX_PRIMARY_CHAN, Some(6), None, None);
621        assert_eq!(
622            channel,
623            fidl_ieee80211::WlanChannel {
624                primary: 6,
625                cbw: fidl_ieee80211::ChannelBandwidth::Cbw20,
626                secondary80: 0
627            }
628        );
629    }
630
631    #[test]
632    fn test_derive_channel_with_ht_20mhz() {
633        let expected_channel = fidl_ieee80211::WlanChannel {
634            primary: HT_PRIMARY_CHAN,
635            cbw: fidl_ieee80211::ChannelBandwidth::Cbw20,
636            secondary80: 0,
637        };
638
639        let test_params = [
640            (ie::StaChanWidth::TWENTY_MHZ, ie::SecChanOffset::SECONDARY_NONE),
641            (ie::StaChanWidth::TWENTY_MHZ, ie::SecChanOffset::SECONDARY_ABOVE),
642            (ie::StaChanWidth::TWENTY_MHZ, ie::SecChanOffset::SECONDARY_BELOW),
643            (ie::StaChanWidth::ANY, ie::SecChanOffset::SECONDARY_NONE),
644        ];
645
646        for (ht_width, sec_chan_offset) in test_params.iter() {
647            let ht_op = ht_op(HT_PRIMARY_CHAN, *ht_width, *sec_chan_offset);
648            let channel = derive_channel(RX_PRIMARY_CHAN, Some(6), Some(ht_op), None);
649            assert_eq!(channel, expected_channel);
650        }
651    }
652
653    #[test]
654    fn test_derive_channel_with_ht_40mhz() {
655        let ht_op =
656            ht_op(HT_PRIMARY_CHAN, ie::StaChanWidth::ANY, ie::SecChanOffset::SECONDARY_ABOVE);
657        let channel = derive_channel(RX_PRIMARY_CHAN, Some(6), Some(ht_op), None);
658        assert_eq!(
659            channel,
660            fidl_ieee80211::WlanChannel {
661                primary: HT_PRIMARY_CHAN,
662                cbw: fidl_ieee80211::ChannelBandwidth::Cbw40,
663                secondary80: 0,
664            }
665        );
666    }
667
668    #[test]
669    fn test_derive_channel_with_ht_40mhz_below() {
670        let ht_op =
671            ht_op(HT_PRIMARY_CHAN, ie::StaChanWidth::ANY, ie::SecChanOffset::SECONDARY_BELOW);
672        let channel = derive_channel(RX_PRIMARY_CHAN, Some(6), Some(ht_op), None);
673        assert_eq!(
674            channel,
675            fidl_ieee80211::WlanChannel {
676                primary: HT_PRIMARY_CHAN,
677                cbw: fidl_ieee80211::ChannelBandwidth::Cbw40Below,
678                secondary80: 0,
679            }
680        );
681    }
682
683    #[test]
684    fn test_derive_channel_with_vht_80mhz() {
685        let ht_op =
686            ht_op(HT_PRIMARY_CHAN, ie::StaChanWidth::ANY, ie::SecChanOffset::SECONDARY_ABOVE);
687        let vht_op = vht_op(ie::VhtChannelBandwidth::CBW_80_160_80P80, 8, 0);
688        let channel = derive_channel(RX_PRIMARY_CHAN, Some(6), Some(ht_op), Some(vht_op));
689        assert_eq!(
690            channel,
691            fidl_ieee80211::WlanChannel {
692                primary: HT_PRIMARY_CHAN,
693                cbw: fidl_ieee80211::ChannelBandwidth::Cbw80,
694                secondary80: 0,
695            }
696        );
697    }
698
699    #[test]
700    fn test_derive_channel_with_vht_160mhz() {
701        let ht_op =
702            ht_op(HT_PRIMARY_CHAN, ie::StaChanWidth::ANY, ie::SecChanOffset::SECONDARY_ABOVE);
703        let vht_op = vht_op(ie::VhtChannelBandwidth::CBW_80_160_80P80, 0, 8);
704        let channel = derive_channel(RX_PRIMARY_CHAN, Some(6), Some(ht_op), Some(vht_op));
705        assert_eq!(
706            channel,
707            fidl_ieee80211::WlanChannel {
708                primary: HT_PRIMARY_CHAN,
709                cbw: fidl_ieee80211::ChannelBandwidth::Cbw160,
710                secondary80: 0,
711            }
712        );
713    }
714
715    #[test]
716    fn test_derive_channel_with_vht_80plus80mhz() {
717        let ht_op =
718            ht_op(HT_PRIMARY_CHAN, ie::StaChanWidth::ANY, ie::SecChanOffset::SECONDARY_ABOVE);
719        let vht_op = vht_op(ie::VhtChannelBandwidth::CBW_80_160_80P80, 18, 1);
720        let channel = derive_channel(RX_PRIMARY_CHAN, Some(6), Some(ht_op), Some(vht_op));
721        assert_eq!(
722            channel,
723            fidl_ieee80211::WlanChannel {
724                primary: HT_PRIMARY_CHAN,
725                cbw: fidl_ieee80211::ChannelBandwidth::Cbw80P80,
726                secondary80: 1,
727            }
728        );
729    }
730
731    #[test]
732    fn test_derive_channel_none() {
733        let channel = derive_channel(8, None, None, None);
734        assert_eq!(
735            channel,
736            fidl_ieee80211::WlanChannel {
737                primary: 8,
738                cbw: fidl_ieee80211::ChannelBandwidth::Cbw20,
739                secondary80: 0,
740            }
741        );
742    }
743
744    #[test]
745    fn test_derive_channel_no_rx_primary() {
746        let channel = derive_channel(8, Some(6), None, None);
747        assert_eq!(
748            channel,
749            fidl_ieee80211::WlanChannel {
750                primary: 6,
751                cbw: fidl_ieee80211::ChannelBandwidth::Cbw20,
752                secondary80: 0,
753            }
754        )
755    }
756
757    fn ht_op(
758        primary_channel: u8,
759        chan_width: ie::StaChanWidth,
760        offset: ie::SecChanOffset,
761    ) -> ie::HtOperation {
762        let ht_op_info =
763            ie::HtOpInfo::new().with_sta_chan_width(chan_width).with_secondary_chan_offset(offset);
764        ie::HtOperation { primary_channel, ht_op_info, basic_ht_mcs_set: ie::SupportedMcsSet(0) }
765    }
766
767    fn vht_op(vht_cbw: ie::VhtChannelBandwidth, seg0: u8, seg1: u8) -> ie::VhtOperation {
768        ie::VhtOperation {
769            vht_cbw,
770            center_freq_seg0: seg0,
771            center_freq_seg1: seg1,
772            basic_mcs_nss: ie::VhtMcsNssMap(0),
773        }
774    }
775}