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    // Weak validity test w.r.t the 2 GHz band primary channel only
107    fn is_primary_2ghz(&self) -> bool {
108        let p = self.primary;
109        p >= 1 && p <= 14
110    }
111
112    // Weak validity test w.r.t the 5 GHz band primary channel only
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    fn get_band_start_freq(&self) -> Result<MHz, anyhow::Error> {
124        if self.is_primary_2ghz() {
125            Ok(BASE_FREQ_2GHZ)
126        } else if self.is_primary_5ghz() {
127            Ok(BASE_FREQ_5GHZ)
128        } else {
129            return Err(format_err!("cannot get band start freq for channel {}", self));
130        }
131    }
132
133    // Note get_center_chan_idx() is to assist channel validity test.
134    // Return of Ok() does not imply the channel under test is valid.
135    fn get_center_chan_idx(&self) -> Result<u8, anyhow::Error> {
136        if !(self.is_primary_2ghz() || self.is_primary_5ghz()) {
137            return Err(format_err!(
138                "cannot get center channel index for an invalid primary channel {}",
139                self
140            ));
141        }
142
143        let p = self.primary;
144        match self.cbw {
145            Cbw::Cbw20 => Ok(p),
146            Cbw::Cbw40 => Ok(p + 2),
147            Cbw::Cbw40Below => Ok(p - 2),
148            Cbw::Cbw80 | Cbw::Cbw80P80 { .. } => match p {
149                36..=48 => Ok(42),
150                52..=64 => Ok(58),
151                100..=112 => Ok(106),
152                116..=128 => Ok(122),
153                132..=144 => Ok(138),
154                148..=161_ => Ok(155),
155                _ => {
156                    return Err(format_err!(
157                        "cannot get center channel index for invalid channel {}",
158                        self
159                    ))
160                }
161            },
162            Cbw::Cbw160 => {
163                // See IEEE Std 802.11-2016 Table 9-252 and 9-253.
164                // Note CBW160 has only one frequency segment, regardless of
165                // encodings on CCFS0 and CCFS1 in VHT Operation Information IE.
166                match p {
167                    36..=64 => Ok(50),
168                    100..=128 => Ok(114),
169                    _ => {
170                        return Err(format_err!(
171                            "cannot get center channel index for invalid channel {}",
172                            self
173                        ))
174                    }
175                }
176            }
177        }
178    }
179
180    /// Returns the center frequency of the first consecutive frequency segment of the channel
181    /// in MHz if the channel is valid, Err(String) otherwise.
182    pub fn get_center_freq(&self) -> Result<MHz, anyhow::Error> {
183        // IEEE Std 802.11-2016, 21.3.14
184        let start_freq = self.get_band_start_freq()?;
185        let center_chan_idx = self.get_center_chan_idx()?;
186        let spacing: MHz = 5;
187        Ok(start_freq + spacing * center_chan_idx as u16)
188    }
189
190    /// Returns true if the primary channel index, channel bandwidth, and the secondary consecutive
191    /// frequency segment (Cbw80P80 only) are all consistent and meet regulatory requirements of
192    /// the USA. TODO(https://fxbug.dev/42104247): Other countries.
193    pub fn is_valid_in_us(&self) -> bool {
194        if self.is_primary_2ghz() {
195            self.is_valid_2ghz_in_us()
196        } else if self.is_primary_5ghz() {
197            self.is_valid_5ghz_in_us()
198        } else {
199            false
200        }
201    }
202
203    fn is_valid_2ghz_in_us(&self) -> bool {
204        if !self.is_primary_2ghz() {
205            return false;
206        }
207        let p = self.primary;
208        match self.cbw {
209            Cbw::Cbw20 => p <= 11,
210            Cbw::Cbw40 => p <= 7,
211            Cbw::Cbw40Below => p >= 5,
212            _ => false,
213        }
214    }
215
216    fn is_valid_5ghz_in_us(&self) -> bool {
217        if !self.is_primary_5ghz() {
218            return false;
219        }
220        let p = self.primary;
221        match self.cbw {
222            Cbw::Cbw20 => true,
223            Cbw::Cbw40 => p != 165 && (p % 8) == (if p <= 144 { 4 } else { 5 }),
224            Cbw::Cbw40Below => p != 165 && (p % 8) == (if p <= 144 { 0 } else { 1 }),
225            Cbw::Cbw80 => p != 165,
226            Cbw::Cbw160 => p < 132,
227            Cbw::Cbw80P80 { secondary80 } => {
228                if p == 165 {
229                    return false;
230                }
231                let valid_secondary80: [u8; 6] = [42, 58, 106, 122, 138, 155];
232                if !valid_secondary80.contains(&secondary80) {
233                    return false;
234                }
235                let ccfs0 = match self.get_center_chan_idx() {
236                    Ok(v) => v,
237                    Err(_) => return false,
238                };
239                let ccfs1 = secondary80;
240                let gap = (ccfs0 as i16 - ccfs1 as i16).abs();
241                gap > 16
242            }
243        }
244    }
245
246    /// Returns true if the channel is 2GHz. Does not perform validity checks.
247    pub fn is_2ghz(&self) -> bool {
248        self.is_primary_2ghz()
249    }
250
251    /// Returns true if the channel is 5GHz. Does not perform validity checks.
252    pub fn is_5ghz(&self) -> bool {
253        self.is_primary_5ghz()
254    }
255
256    // TODO(https://fxbug.dev/335283785): Remove or explain unused code.
257    #[allow(dead_code)]
258    fn is_unii1(&self) -> bool {
259        let p = self.primary;
260        p >= 32 && p <= 50
261    }
262
263    fn is_unii2a(&self) -> bool {
264        let p = self.primary;
265        // Note the overlap with U-NII-1
266        p >= 50 && p <= 68
267    }
268
269    fn is_unii2c(&self) -> bool {
270        let p = self.primary;
271        p >= 96 && p <= 144
272    }
273
274    // TODO(https://fxbug.dev/335283785): Remove or explain unused code.
275    #[allow(dead_code)]
276    fn is_unii3(&self) -> bool {
277        let p = self.primary;
278        // Note the overlap with U-NII-2C
279        p >= 138 && p <= 165
280    }
281
282    pub fn is_dfs(&self) -> bool {
283        self.is_unii2a() || self.is_unii2c()
284    }
285}
286
287impl From<Channel> for fidl_ieee80211::WlanChannel {
288    fn from(channel: Channel) -> fidl_ieee80211::WlanChannel {
289        fidl_ieee80211::WlanChannel::from(&channel)
290    }
291}
292
293impl From<&Channel> for fidl_ieee80211::WlanChannel {
294    fn from(channel: &Channel) -> fidl_ieee80211::WlanChannel {
295        let (cbw, secondary80) = channel.cbw.to_fidl();
296        fidl_ieee80211::WlanChannel { primary: channel.primary, cbw, secondary80 }
297    }
298}
299
300impl TryFrom<fidl_ieee80211::WlanChannel> for Channel {
301    type Error = anyhow::Error;
302    fn try_from(fidl_channel: fidl_ieee80211::WlanChannel) -> Result<Channel, Self::Error> {
303        Channel::try_from(&fidl_channel)
304    }
305}
306
307impl TryFrom<&fidl_ieee80211::WlanChannel> for Channel {
308    type Error = anyhow::Error;
309
310    fn try_from(fidl_channel: &fidl_ieee80211::WlanChannel) -> Result<Channel, Self::Error> {
311        Ok(Channel {
312            primary: fidl_channel.primary,
313            cbw: Cbw::from_fidl(fidl_channel.cbw, fidl_channel.secondary80)?,
314        })
315    }
316}
317
318/// Derive channel given DSSS param set, HT operation, and VHT operation IEs from
319/// beacon or probe response, and the primary channel from which such frame is
320/// received on.
321///
322/// Primary channel is extracted from HT op, DSSS param set, or `rx_primary_channel`,
323/// in descending priority.
324pub fn derive_channel(
325    rx_primary_channel: u8,
326    dsss_channel: Option<u8>,
327    ht_op: Option<ie::HtOperation>,
328    vht_op: Option<ie::VhtOperation>,
329) -> fidl_ieee80211::WlanChannel {
330    let primary = ht_op
331        .as_ref()
332        .map(|ht_op| ht_op.primary_channel)
333        .or(dsss_channel)
334        .unwrap_or(rx_primary_channel);
335
336    let ht_op_cbw = ht_op.map(|ht_op| ht_op.ht_op_info.sta_chan_width());
337    let vht_cbw_and_segs =
338        vht_op.map(|vht_op| (vht_op.vht_cbw, vht_op.center_freq_seg0, vht_op.center_freq_seg1));
339
340    let (cbw, secondary80) = match ht_op_cbw {
341        // Inspect vht/ht op parameters to determine the channel width.
342        Some(ie::StaChanWidth::ANY) => {
343            // Safe to unwrap `ht_op` because `ht_op_cbw` is only Some(_) if `ht_op` has a value.
344            let sec_chan_offset = ht_op.unwrap().ht_op_info.secondary_chan_offset();
345            derive_wide_channel_bandwidth(vht_cbw_and_segs, sec_chan_offset)
346        }
347        // Default to Cbw20 if HT CBW field is set to 0 or not present.
348        _ => Cbw::Cbw20,
349    }
350    .to_fidl();
351
352    fidl_ieee80211::WlanChannel { primary, cbw, secondary80 }
353}
354
355/// Derive a CBW for a primary channel or channel switch.
356/// VHT parameter derivation is defined identically by:
357///     IEEE Std 802.11-2016 9.4.2.159 Table 9-252 for channel switching
358///     IEEE Std 802.11-2016 11.40.1 Table 11-24 for VHT operation
359/// SecChanOffset is defined identially by:
360///     IEEE Std 802.11-2016 9.4.2.20 for channel switching
361///     IEEE Std 802.11-2016 9.4.2.57 Table 9-168 for HT operation
362pub fn derive_wide_channel_bandwidth(
363    vht_cbw_and_segs: Option<(ie::VhtChannelBandwidth, u8, u8)>,
364    sec_chan_offset: ie::SecChanOffset,
365) -> Cbw {
366    use ie::VhtChannelBandwidth as Vcb;
367    match vht_cbw_and_segs {
368        Some((Vcb::CBW_80_160_80P80, _, 0)) => Cbw::Cbw80,
369        Some((Vcb::CBW_80_160_80P80, seg0, seg1)) if abs_sub(seg0, seg1) == 8 => Cbw::Cbw160,
370        Some((Vcb::CBW_80_160_80P80, seg0, seg1)) if abs_sub(seg0, seg1) > 16 => {
371            // See IEEE 802.11-2016, Table 9-252, about channel center frequency segment 1
372            Cbw::Cbw80P80 { secondary80: seg1 }
373        }
374        // Use HT CBW if
375        // - VHT op is not present,
376        // - VHT op has deprecated parameters sets, or
377        // - VHT CBW field is set to 0
378        _ => match sec_chan_offset {
379            ie::SecChanOffset::SECONDARY_ABOVE => Cbw::Cbw40,
380            ie::SecChanOffset::SECONDARY_BELOW => Cbw::Cbw40Below,
381            ie::SecChanOffset::SECONDARY_NONE | _ => Cbw::Cbw20,
382        },
383    }
384}
385
386fn abs_sub(v1: u8, v2: u8) -> u8 {
387    if v2 >= v1 {
388        v2 - v1
389    } else {
390        v1 - v2
391    }
392}
393
394#[cfg(test)]
395mod tests {
396    use super::*;
397
398    #[test]
399    fn fmt_display() {
400        let mut c = Channel::new(100, Cbw::Cbw40);
401        assert_eq!(format!("{}", c), "100+");
402        c.cbw = Cbw::Cbw160;
403        assert_eq!(format!("{}", c), "100W");
404        c.cbw = Cbw::Cbw80P80 { secondary80: 200 };
405        assert_eq!(format!("{}", c), "100+200P");
406    }
407
408    #[test]
409    fn test_is_primary_2ghz_or_5ghz() {
410        // Note Cbw is ignored in this test.
411        assert!(Channel::new(1, Cbw::Cbw160).is_primary_2ghz());
412        assert!(!Channel::new(1, Cbw::Cbw160).is_primary_5ghz());
413
414        assert!(Channel::new(12, Cbw::Cbw160).is_primary_2ghz());
415        assert!(!Channel::new(12, Cbw::Cbw160).is_primary_5ghz());
416
417        assert!(!Channel::new(36, Cbw::Cbw160).is_primary_2ghz());
418        assert!(Channel::new(36, Cbw::Cbw160).is_primary_5ghz());
419
420        assert!(!Channel::new(37, Cbw::Cbw160).is_primary_2ghz());
421        assert!(!Channel::new(37, Cbw::Cbw160).is_primary_5ghz());
422
423        assert!(!Channel::new(165, Cbw::Cbw160).is_primary_2ghz());
424        assert!(Channel::new(165, Cbw::Cbw160).is_primary_5ghz());
425
426        assert!(!Channel::new(166, Cbw::Cbw160).is_primary_2ghz());
427        assert!(!Channel::new(166, Cbw::Cbw160).is_primary_5ghz());
428    }
429
430    #[test]
431    fn test_band_start_freq() {
432        assert_eq!(BASE_FREQ_2GHZ, Channel::new(1, Cbw::Cbw20).get_band_start_freq().unwrap());
433        assert_eq!(BASE_FREQ_5GHZ, Channel::new(100, Cbw::Cbw20).get_band_start_freq().unwrap());
434        assert!(Channel::new(15, Cbw::Cbw20).get_band_start_freq().is_err());
435        assert!(Channel::new(200, Cbw::Cbw20).get_band_start_freq().is_err());
436    }
437
438    #[test]
439    fn test_get_center_chan_idx() {
440        assert!(Channel::new(1, Cbw::Cbw80).get_center_chan_idx().is_err());
441        assert_eq!(9, Channel::new(11, Cbw::Cbw40Below).get_center_chan_idx().unwrap());
442        assert_eq!(8, Channel::new(6, Cbw::Cbw40).get_center_chan_idx().unwrap());
443        assert_eq!(36, Channel::new(36, Cbw::Cbw20).get_center_chan_idx().unwrap());
444        assert_eq!(38, Channel::new(36, Cbw::Cbw40).get_center_chan_idx().unwrap());
445        assert_eq!(42, Channel::new(36, Cbw::Cbw80).get_center_chan_idx().unwrap());
446        assert_eq!(50, Channel::new(36, Cbw::Cbw160).get_center_chan_idx().unwrap());
447        assert_eq!(
448            42,
449            Channel::new(36, Cbw::Cbw80P80 { secondary80: 155 }).get_center_chan_idx().unwrap()
450        );
451    }
452
453    #[test]
454    fn test_get_center_freq() {
455        assert_eq!(2412 as MHz, Channel::new(1, Cbw::Cbw20).get_center_freq().unwrap());
456        assert_eq!(2437 as MHz, Channel::new(6, Cbw::Cbw20).get_center_freq().unwrap());
457        assert_eq!(2447 as MHz, Channel::new(6, Cbw::Cbw40).get_center_freq().unwrap());
458        assert_eq!(2427 as MHz, Channel::new(6, Cbw::Cbw40Below).get_center_freq().unwrap());
459        assert_eq!(5180 as MHz, Channel::new(36, Cbw::Cbw20).get_center_freq().unwrap());
460        assert_eq!(5190 as MHz, Channel::new(36, Cbw::Cbw40).get_center_freq().unwrap());
461        assert_eq!(5210 as MHz, Channel::new(36, Cbw::Cbw80).get_center_freq().unwrap());
462        assert_eq!(5250 as MHz, Channel::new(36, Cbw::Cbw160).get_center_freq().unwrap());
463        assert_eq!(
464            5210 as MHz,
465            Channel::new(36, Cbw::Cbw80P80 { secondary80: 155 }).get_center_freq().unwrap()
466        );
467    }
468
469    #[test]
470    fn test_valid_us_combo() {
471        assert!(Channel::new(1, Cbw::Cbw20).is_valid_in_us());
472        assert!(Channel::new(1, Cbw::Cbw40).is_valid_in_us());
473        assert!(Channel::new(5, Cbw::Cbw40Below).is_valid_in_us());
474        assert!(Channel::new(6, Cbw::Cbw20).is_valid_in_us());
475        assert!(Channel::new(6, Cbw::Cbw40).is_valid_in_us());
476        assert!(Channel::new(6, Cbw::Cbw40Below).is_valid_in_us());
477        assert!(Channel::new(7, Cbw::Cbw40).is_valid_in_us());
478        assert!(Channel::new(11, Cbw::Cbw20).is_valid_in_us());
479        assert!(Channel::new(11, Cbw::Cbw40Below).is_valid_in_us());
480
481        assert!(Channel::new(36, Cbw::Cbw20).is_valid_in_us());
482        assert!(Channel::new(36, Cbw::Cbw40).is_valid_in_us());
483        assert!(Channel::new(36, Cbw::Cbw160).is_valid_in_us());
484        assert!(Channel::new(40, Cbw::Cbw20).is_valid_in_us());
485        assert!(Channel::new(40, Cbw::Cbw40Below).is_valid_in_us());
486        assert!(Channel::new(40, Cbw::Cbw160).is_valid_in_us());
487        assert!(Channel::new(36, Cbw::Cbw80P80 { secondary80: 155 }).is_valid_in_us());
488        assert!(Channel::new(40, Cbw::Cbw80P80 { secondary80: 155 }).is_valid_in_us());
489        assert!(Channel::new(161, Cbw::Cbw80P80 { secondary80: 42 }).is_valid_in_us());
490    }
491
492    #[test]
493    fn test_invalid_us_combo() {
494        assert!(!Channel::new(1, Cbw::Cbw40Below).is_valid_in_us());
495        assert!(!Channel::new(4, Cbw::Cbw40Below).is_valid_in_us());
496        assert!(!Channel::new(8, Cbw::Cbw40).is_valid_in_us());
497        assert!(!Channel::new(11, Cbw::Cbw40).is_valid_in_us());
498        assert!(!Channel::new(6, Cbw::Cbw80).is_valid_in_us());
499        assert!(!Channel::new(6, Cbw::Cbw160).is_valid_in_us());
500        assert!(!Channel::new(6, Cbw::Cbw80P80 { secondary80: 155 }).is_valid_in_us());
501
502        assert!(!Channel::new(36, Cbw::Cbw40Below).is_valid_in_us());
503        assert!(!Channel::new(36, Cbw::Cbw80P80 { secondary80: 58 }).is_valid_in_us());
504        assert!(!Channel::new(40, Cbw::Cbw40).is_valid_in_us());
505        assert!(!Channel::new(40, Cbw::Cbw80P80 { secondary80: 42 }).is_valid_in_us());
506
507        assert!(!Channel::new(165, Cbw::Cbw80).is_valid_in_us());
508        assert!(!Channel::new(165, Cbw::Cbw80P80 { secondary80: 42 }).is_valid_in_us());
509    }
510
511    #[test]
512    fn test_is_2ghz_or_5ghz() {
513        assert!(Channel::new(1, Cbw::Cbw20).is_2ghz());
514        assert!(!Channel::new(1, Cbw::Cbw20).is_5ghz());
515        assert!(Channel::new(13, Cbw::Cbw20).is_2ghz());
516        assert!(!Channel::new(13, Cbw::Cbw20).is_5ghz());
517        assert!(Channel::new(36, Cbw::Cbw20).is_5ghz());
518        assert!(!Channel::new(36, Cbw::Cbw20).is_2ghz());
519    }
520
521    #[test]
522    fn test_is_dfs() {
523        assert!(!Channel::new(1, Cbw::Cbw20).is_dfs());
524        assert!(!Channel::new(36, Cbw::Cbw20).is_dfs());
525        assert!(Channel::new(50, Cbw::Cbw20).is_dfs());
526        assert!(Channel::new(144, Cbw::Cbw20).is_dfs());
527        assert!(!Channel::new(149, Cbw::Cbw20).is_dfs());
528    }
529
530    #[test]
531    fn test_convert_fidl_channel() {
532        let mut f = fidl_ieee80211::WlanChannel::from(Channel::new(1, Cbw::Cbw20));
533        assert!(
534            f.primary == 1
535                && f.cbw == fidl_ieee80211::ChannelBandwidth::Cbw20
536                && f.secondary80 == 0
537        );
538
539        f = Channel::new(36, Cbw::Cbw80P80 { secondary80: 155 }).into();
540        assert!(
541            f.primary == 36
542                && f.cbw == fidl_ieee80211::ChannelBandwidth::Cbw80P80
543                && f.secondary80 == 155
544        );
545
546        let mut c = Channel::try_from(fidl_ieee80211::WlanChannel {
547            primary: 11,
548            cbw: fidl_ieee80211::ChannelBandwidth::Cbw40Below,
549            secondary80: 123,
550        })
551        .unwrap();
552        assert!(c.primary == 11 && c.cbw == Cbw::Cbw40Below);
553        c = fidl_ieee80211::WlanChannel {
554            primary: 149,
555            cbw: fidl_ieee80211::ChannelBandwidth::Cbw80P80,
556            secondary80: 42,
557        }
558        .try_into()
559        .unwrap();
560        assert!(c.primary == 149 && c.cbw == Cbw::Cbw80P80 { secondary80: 42 });
561
562        let r = Channel::try_from(fidl_ieee80211::WlanChannel {
563            primary: 11,
564            cbw: fidl_ieee80211::ChannelBandwidth::unknown(),
565            secondary80: 123,
566        });
567        assert!(r.is_err());
568    }
569
570    const RX_PRIMARY_CHAN: u8 = 11;
571    const HT_PRIMARY_CHAN: u8 = 48;
572
573    #[test]
574    fn test_derive_channel_basic() {
575        let channel = derive_channel(RX_PRIMARY_CHAN, None, None, None);
576        assert_eq!(
577            channel,
578            fidl_ieee80211::WlanChannel {
579                primary: RX_PRIMARY_CHAN,
580                cbw: fidl_ieee80211::ChannelBandwidth::Cbw20,
581                secondary80: 0,
582            }
583        );
584    }
585
586    #[test]
587    fn test_derive_channel_with_dsss_param() {
588        let channel = derive_channel(RX_PRIMARY_CHAN, Some(6), None, None);
589        assert_eq!(
590            channel,
591            fidl_ieee80211::WlanChannel {
592                primary: 6,
593                cbw: fidl_ieee80211::ChannelBandwidth::Cbw20,
594                secondary80: 0
595            }
596        );
597    }
598
599    #[test]
600    fn test_derive_channel_with_ht_20mhz() {
601        let expected_channel = fidl_ieee80211::WlanChannel {
602            primary: HT_PRIMARY_CHAN,
603            cbw: fidl_ieee80211::ChannelBandwidth::Cbw20,
604            secondary80: 0,
605        };
606
607        let test_params = [
608            (ie::StaChanWidth::TWENTY_MHZ, ie::SecChanOffset::SECONDARY_NONE),
609            (ie::StaChanWidth::TWENTY_MHZ, ie::SecChanOffset::SECONDARY_ABOVE),
610            (ie::StaChanWidth::TWENTY_MHZ, ie::SecChanOffset::SECONDARY_BELOW),
611            (ie::StaChanWidth::ANY, ie::SecChanOffset::SECONDARY_NONE),
612        ];
613
614        for (ht_width, sec_chan_offset) in test_params.iter() {
615            let ht_op = ht_op(HT_PRIMARY_CHAN, *ht_width, *sec_chan_offset);
616            let channel = derive_channel(RX_PRIMARY_CHAN, Some(6), Some(ht_op), None);
617            assert_eq!(channel, expected_channel);
618        }
619    }
620
621    #[test]
622    fn test_derive_channel_with_ht_40mhz() {
623        let ht_op =
624            ht_op(HT_PRIMARY_CHAN, ie::StaChanWidth::ANY, ie::SecChanOffset::SECONDARY_ABOVE);
625        let channel = derive_channel(RX_PRIMARY_CHAN, Some(6), Some(ht_op), None);
626        assert_eq!(
627            channel,
628            fidl_ieee80211::WlanChannel {
629                primary: HT_PRIMARY_CHAN,
630                cbw: fidl_ieee80211::ChannelBandwidth::Cbw40,
631                secondary80: 0,
632            }
633        );
634    }
635
636    #[test]
637    fn test_derive_channel_with_ht_40mhz_below() {
638        let ht_op =
639            ht_op(HT_PRIMARY_CHAN, ie::StaChanWidth::ANY, ie::SecChanOffset::SECONDARY_BELOW);
640        let channel = derive_channel(RX_PRIMARY_CHAN, Some(6), Some(ht_op), None);
641        assert_eq!(
642            channel,
643            fidl_ieee80211::WlanChannel {
644                primary: HT_PRIMARY_CHAN,
645                cbw: fidl_ieee80211::ChannelBandwidth::Cbw40Below,
646                secondary80: 0,
647            }
648        );
649    }
650
651    #[test]
652    fn test_derive_channel_with_vht_80mhz() {
653        let ht_op =
654            ht_op(HT_PRIMARY_CHAN, ie::StaChanWidth::ANY, ie::SecChanOffset::SECONDARY_ABOVE);
655        let vht_op = vht_op(ie::VhtChannelBandwidth::CBW_80_160_80P80, 8, 0);
656        let channel = derive_channel(RX_PRIMARY_CHAN, Some(6), Some(ht_op), Some(vht_op));
657        assert_eq!(
658            channel,
659            fidl_ieee80211::WlanChannel {
660                primary: HT_PRIMARY_CHAN,
661                cbw: fidl_ieee80211::ChannelBandwidth::Cbw80,
662                secondary80: 0,
663            }
664        );
665    }
666
667    #[test]
668    fn test_derive_channel_with_vht_160mhz() {
669        let ht_op =
670            ht_op(HT_PRIMARY_CHAN, ie::StaChanWidth::ANY, ie::SecChanOffset::SECONDARY_ABOVE);
671        let vht_op = vht_op(ie::VhtChannelBandwidth::CBW_80_160_80P80, 0, 8);
672        let channel = derive_channel(RX_PRIMARY_CHAN, Some(6), Some(ht_op), Some(vht_op));
673        assert_eq!(
674            channel,
675            fidl_ieee80211::WlanChannel {
676                primary: HT_PRIMARY_CHAN,
677                cbw: fidl_ieee80211::ChannelBandwidth::Cbw160,
678                secondary80: 0,
679            }
680        );
681    }
682
683    #[test]
684    fn test_derive_channel_with_vht_80plus80mhz() {
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, 18, 1);
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::Cbw80P80,
694                secondary80: 1,
695            }
696        );
697    }
698
699    #[test]
700    fn test_derive_channel_none() {
701        let channel = derive_channel(8, None, None, None);
702        assert_eq!(
703            channel,
704            fidl_ieee80211::WlanChannel {
705                primary: 8,
706                cbw: fidl_ieee80211::ChannelBandwidth::Cbw20,
707                secondary80: 0,
708            }
709        );
710    }
711
712    #[test]
713    fn test_derive_channel_no_rx_primary() {
714        let channel = derive_channel(8, Some(6), None, None);
715        assert_eq!(
716            channel,
717            fidl_ieee80211::WlanChannel {
718                primary: 6,
719                cbw: fidl_ieee80211::ChannelBandwidth::Cbw20,
720                secondary80: 0,
721            }
722        )
723    }
724
725    fn ht_op(
726        primary_channel: u8,
727        chan_width: ie::StaChanWidth,
728        offset: ie::SecChanOffset,
729    ) -> ie::HtOperation {
730        let ht_op_info =
731            ie::HtOpInfo::new().with_sta_chan_width(chan_width).with_secondary_chan_offset(offset);
732        ie::HtOperation { primary_channel, ht_op_info, basic_ht_mcs_set: ie::SupportedMcsSet(0) }
733    }
734
735    fn vht_op(vht_cbw: ie::VhtChannelBandwidth, seg0: u8, seg1: u8) -> ie::VhtOperation {
736        ie::VhtOperation {
737            vht_cbw,
738            center_freq_seg0: seg0,
739            center_freq_seg1: seg1,
740            basic_mcs_nss: ie::VhtMcsNssMap(0),
741        }
742    }
743}