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