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