wlan_common/
tx_vector.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::SupportedRate;
6use crate::mac::WlanGi;
7use anyhow::{bail, Error};
8use {fidl_fuchsia_wlan_common as fidl_common, fidl_fuchsia_wlan_softmac as fidl_softmac};
9
10pub const HT_NUM_MCS: u8 = 32; // Only support MCS 0-31
11pub const HT_NUM_UNIQUE_MCS: u8 = 8;
12pub const ERP_NUM_TX_VECTOR: u8 = 8;
13
14const INVALID_TX_VECTOR_IDX: u16 = fidl_common::WLAN_TX_VECTOR_IDX_INVALID;
15
16const HT_NUM_GI: u8 = 2;
17const HT_NUM_CBW: u8 = 2;
18const HT_NUM_TX_VECTOR: u8 = HT_NUM_GI * HT_NUM_CBW * HT_NUM_MCS;
19
20const DSSS_CCK_NUM_TX_VECTOR: u8 = 4;
21
22pub const START_IDX: u16 = 1 + INVALID_TX_VECTOR_IDX;
23pub const HT_START_IDX: u16 = START_IDX;
24pub const ERP_START_IDX: u16 = HT_START_IDX + HT_NUM_TX_VECTOR as u16;
25pub const DSSS_CCK_START_IDX: u16 = ERP_START_IDX + ERP_NUM_TX_VECTOR as u16;
26pub const MAX_VALID_IDX: u16 = DSSS_CCK_START_IDX + DSSS_CCK_NUM_TX_VECTOR as u16 - 1;
27
28// Notes about HT:
29// Changing CBW (channel bandwidth) from 20 MHz to 40 MHz advances index by 32
30// Changing GI (gap interval) from 800 ns to 400 ns advances index by 64
31//
32//  Group   tx_vec_idx_t range    PHY   GI   CBW NSS MCS_IDX
33//  0         1 -  32             HT    800  20  -   0-31
34//  1        33 -  64             HT    800  40  -   0-31
35//  2        65 -  96             HT    400  20  -   0-31
36//  3        97 - 128             HT    400  40  -   0-31
37//  4       129 - 136             ERP   -    -   -   0-7
38//  5       137 - 138             DSSS  -    -   -   0-1
39//  6       139 - 140             CCK   -    -   -   2-3
40//
41// TODO(https://fxbug.dev/42094755) VHT will be inserted between HT and ERP.
42
43#[derive(PartialEq, Debug)]
44/// Encapsulates parameters for transmitting a packet over a PHY.
45///
46/// MCS index is defined in
47/// * HT: IEEE 802.11-2016 Table 19-27
48/// * VHT: IEEE 802.11-2016 Table 21-30
49///
50/// We extend the definition of MCS index beyond IEEE 802.11-2016 as follows:
51/// * For ERP/ERP-OFDM (WlanPhyType::Erp):
52///     * 0: BPSK,   1/2 -> Data rate  6 Mbps
53///     * 1: BPSK,   3/4 -> Data rate  9 Mbps
54///     * 2: QPSK,   1/2 -> Data rate 12 Mbps
55///     * 3: QPSK,   3/4 -> Data rate 18 Mbps
56///     * 4: 16-QAM, 1/2 -> Data rate 24 Mbps
57///     * 5: 16-QAM, 3/4 -> Data rate 36 Mbps
58///     * 6: 64-QAM, 2/3 -> Data rate 48 Mbps
59///     * 7: 64-QAM, 3/4 -> Data rate 54 Mbps
60/// * For DSSS, HR/DSSS, and ERP-DSSS/CCK (WlanPhyType::Dsss and WlanPhyType::Cck):
61///     * 0:  2 -> 1   Mbps DSSS
62///     * 1:  4 -> 2   Mbps DSSS
63///     * 2: 11 -> 5.5 Mbps CCK
64///     * 3: 22 -> 11  Mbps CCK
65pub struct TxVector {
66    phy: fidl_common::WlanPhyType,
67    gi: WlanGi,
68    cbw: fidl_common::ChannelBandwidth,
69    nss: u8, // Number of spatial streams for VHT and beyond.
70    // For HT,  see IEEE 802.11-2016 Table 19-27
71    // For VHT, see IEEE 802.11-2016 Table 21-30
72    // For ERP, see comment above (this is a Fuchsia extension)
73    mcs_idx: u8,
74}
75
76impl TxVector {
77    pub fn new(
78        phy: fidl_common::WlanPhyType,
79        gi: WlanGi,
80        cbw: fidl_common::ChannelBandwidth,
81        mcs_idx: u8,
82    ) -> Result<Self, Error> {
83        let supported_mcs = match phy {
84            fidl_common::WlanPhyType::Dsss => mcs_idx == 0 || mcs_idx == 1,
85            fidl_common::WlanPhyType::Hr => mcs_idx == 2 || mcs_idx == 3,
86            fidl_common::WlanPhyType::Ht => {
87                match gi {
88                    WlanGi::G_800NS | WlanGi::G_400NS => (),
89                    other => bail!("Unsupported GI for HT PHY: {:?}", other),
90                }
91                match cbw {
92                    fidl_common::ChannelBandwidth::Cbw20
93                    | fidl_common::ChannelBandwidth::Cbw40
94                    | fidl_common::ChannelBandwidth::Cbw40Below => (),
95                    other => bail!("Unsupported CBW for HT PHY: {:?}", other),
96                }
97                mcs_idx < HT_NUM_MCS
98            }
99            fidl_common::WlanPhyType::Erp => mcs_idx < ERP_NUM_TX_VECTOR,
100            other => bail!("Unsupported phy type: {:?}", other),
101        };
102        if supported_mcs {
103            let nss = match phy {
104                fidl_common::WlanPhyType::Ht => 1 + mcs_idx / HT_NUM_UNIQUE_MCS,
105                // TODO(https://fxbug.dev/42094755): Support VHT NSS
106                _ => 1,
107            };
108            Ok(Self { phy, gi, cbw, nss, mcs_idx })
109        } else {
110            bail!("Unsupported MCS {:?} for phy type {:?}", mcs_idx, phy);
111        }
112    }
113
114    pub fn phy(&self) -> fidl_common::WlanPhyType {
115        self.phy
116    }
117
118    pub fn from_supported_rate(erp_rate: &SupportedRate) -> Result<Self, Error> {
119        let (phy, mcs_idx) = match erp_rate.rate() {
120            2 => (fidl_common::WlanPhyType::Dsss, 0),
121            4 => (fidl_common::WlanPhyType::Dsss, 1),
122            11 => (fidl_common::WlanPhyType::Hr, 2),
123            22 => (fidl_common::WlanPhyType::Hr, 3),
124            12 => (fidl_common::WlanPhyType::Erp, 0),
125            18 => (fidl_common::WlanPhyType::Erp, 1),
126            24 => (fidl_common::WlanPhyType::Erp, 2),
127            36 => (fidl_common::WlanPhyType::Erp, 3),
128            48 => (fidl_common::WlanPhyType::Erp, 4),
129            72 => (fidl_common::WlanPhyType::Erp, 5),
130            96 => (fidl_common::WlanPhyType::Erp, 6),
131            108 => (fidl_common::WlanPhyType::Erp, 7),
132            other_rate => {
133                bail!("Invalid rate {} * 0.5 Mbps for 802.11a/b/g.", other_rate);
134            }
135        };
136        Self::new(phy, WlanGi::G_800NS, fidl_common::ChannelBandwidth::Cbw20, mcs_idx)
137    }
138
139    // We guarantee safety of the unwraps in the following two functions by testing all TxVecIdx
140    // values exhaustively.
141
142    pub fn from_idx(idx: TxVecIdx) -> Self {
143        let phy = idx.to_phy();
144        match phy {
145            fidl_common::WlanPhyType::Ht => {
146                let group_idx = (*idx - HT_START_IDX) / HT_NUM_MCS as u16;
147                let gi = match (group_idx / HT_NUM_CBW as u16) % HT_NUM_GI as u16 {
148                    1 => WlanGi::G_400NS,
149                    _ => WlanGi::G_800NS,
150                };
151                let cbw = match group_idx % HT_NUM_CBW as u16 {
152                    0 => fidl_common::ChannelBandwidth::Cbw20,
153                    _ => fidl_common::ChannelBandwidth::Cbw40,
154                };
155                let mcs_idx = ((*idx - HT_START_IDX) % HT_NUM_MCS as u16) as u8;
156                Self::new(phy, gi, cbw, mcs_idx).unwrap()
157            }
158            fidl_common::WlanPhyType::Erp => Self::new(
159                phy,
160                WlanGi::G_800NS,
161                fidl_common::ChannelBandwidth::Cbw20,
162                (*idx - ERP_START_IDX) as u8,
163            )
164            .unwrap(),
165            fidl_common::WlanPhyType::Dsss | fidl_common::WlanPhyType::Hr => Self::new(
166                phy,
167                WlanGi::G_800NS,
168                fidl_common::ChannelBandwidth::Cbw20,
169                (*idx - DSSS_CCK_START_IDX) as u8,
170            )
171            .unwrap(),
172            _ => unreachable!(),
173        }
174    }
175
176    pub fn to_idx(&self) -> TxVecIdx {
177        match self.phy {
178            fidl_common::WlanPhyType::Ht => {
179                let group_idx = match self.gi {
180                    WlanGi::G_400NS => HT_NUM_CBW as u16,
181                    _ => 0,
182                } + match self.cbw {
183                    fidl_common::ChannelBandwidth::Cbw40
184                    | fidl_common::ChannelBandwidth::Cbw40Below => 1,
185                    _ => 0,
186                };
187                TxVecIdx::new(HT_START_IDX + group_idx * HT_NUM_MCS as u16 + self.mcs_idx as u16)
188                    .unwrap()
189            }
190            fidl_common::WlanPhyType::Erp => {
191                TxVecIdx::new(ERP_START_IDX + self.mcs_idx as u16).unwrap()
192            }
193            fidl_common::WlanPhyType::Hr | fidl_common::WlanPhyType::Dsss => {
194                TxVecIdx::new(DSSS_CCK_START_IDX + self.mcs_idx as u16).unwrap()
195            }
196            _ => unreachable!(),
197        }
198    }
199
200    pub fn to_fidl_tx_info(
201        &self,
202        tx_flags: fidl_softmac::WlanTxInfoFlags,
203        minstrel_enabled: bool,
204    ) -> fidl_softmac::WlanTxInfo {
205        fidl_softmac::WlanTxInfo {
206            tx_flags: tx_flags.bits(),
207            valid_fields: (fidl_softmac::WlanTxInfoValid::CHANNEL_BANDWIDTH
208                | fidl_softmac::WlanTxInfoValid::PHY
209                | fidl_softmac::WlanTxInfoValid::MCS
210                | if minstrel_enabled {
211                    fidl_softmac::WlanTxInfoValid::TX_VECTOR_IDX
212                } else {
213                    fidl_softmac::WlanTxInfoValid::empty()
214                })
215            .bits(),
216            tx_vector_idx: self.to_idx().0,
217            phy: self.phy,
218            channel_bandwidth: self.cbw,
219            mcs: self.mcs_idx,
220        }
221    }
222}
223
224#[derive(Hash, PartialEq, Eq, Debug, Copy, Clone, Ord, PartialOrd)]
225pub struct TxVecIdx(u16);
226impl std::ops::Deref for TxVecIdx {
227    type Target = u16;
228    fn deref(&self) -> &u16 {
229        &self.0
230    }
231}
232
233impl TxVecIdx {
234    pub fn new(value: u16) -> Option<Self> {
235        if INVALID_TX_VECTOR_IDX < value && value <= MAX_VALID_IDX {
236            Some(Self(value))
237        } else {
238            None
239        }
240    }
241
242    // TODO(https://fxbug.dev/42163096): Add a const fn new when it's a stable feature.
243
244    pub fn to_erp_rate(&self) -> Option<SupportedRate> {
245        const ERP_RATE_LIST: [u8; ERP_NUM_TX_VECTOR as usize] = [12, 18, 24, 36, 48, 72, 96, 108];
246        if self.is_erp() {
247            Some(SupportedRate(ERP_RATE_LIST[(self.0 - ERP_START_IDX) as usize]))
248        } else {
249            None
250        }
251    }
252
253    pub fn to_phy(&self) -> fidl_common::WlanPhyType {
254        match self.0 {
255            idx if idx < HT_START_IDX + HT_NUM_TX_VECTOR as u16 => fidl_common::WlanPhyType::Ht,
256            idx if idx < ERP_START_IDX + ERP_NUM_TX_VECTOR as u16 => fidl_common::WlanPhyType::Erp,
257            idx if idx < DSSS_CCK_START_IDX + 2 => fidl_common::WlanPhyType::Dsss,
258            idx if idx < DSSS_CCK_START_IDX + DSSS_CCK_NUM_TX_VECTOR as u16 => {
259                fidl_common::WlanPhyType::Hr
260            }
261            // This panic is unreachable for any TxVecIdx constructed with TxVecIdx::new.
262            // Verified by exhaustive test cases.
263            _ => panic!("TxVecIdx has invalid value"),
264        }
265    }
266
267    pub fn is_ht(&self) -> bool {
268        HT_START_IDX <= self.0 && self.0 < HT_START_IDX + HT_NUM_TX_VECTOR as u16
269    }
270
271    pub fn is_erp(&self) -> bool {
272        ERP_START_IDX <= self.0 && self.0 < ERP_START_IDX + ERP_NUM_TX_VECTOR as u16
273    }
274}
275
276impl std::fmt::Display for TxVecIdx {
277    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
278        let tx_vector = TxVector::from_idx(*self);
279        write!(f, "TxVecIdx {:3}: {:?}", self.0, tx_vector)
280    }
281}
282
283#[cfg(test)]
284mod tests {
285    use super::*;
286
287    #[test]
288    fn valid_tx_vector_idxs() {
289        for idx in INVALID_TX_VECTOR_IDX + 1..=MAX_VALID_IDX {
290            let idx = TxVecIdx::new(idx).expect("Could not make TxVecIdx from valid index");
291            idx.to_phy(); // Shouldn't panic for any value.
292        }
293        assert!(
294            TxVecIdx::new(INVALID_TX_VECTOR_IDX).is_none(),
295            "Should not be able to construct invalid tx vector idx"
296        );
297        assert!(
298            TxVecIdx::new(MAX_VALID_IDX + 1).is_none(),
299            "Should not be able to construct invalid tx vector idx"
300        );
301    }
302
303    #[test]
304    fn erp_rates() {
305        for idx in INVALID_TX_VECTOR_IDX + 1..=MAX_VALID_IDX {
306            let idx = TxVecIdx::new(idx).expect("Could not make TxVecIdx from valid index");
307            assert_eq!(idx.is_erp(), idx.to_erp_rate().is_some());
308        }
309    }
310
311    #[test]
312    fn phy_types() {
313        for idx in INVALID_TX_VECTOR_IDX + 1..=MAX_VALID_IDX {
314            let idx = TxVecIdx::new(idx).expect("Could not make TxVecIdx from valid index");
315            if idx.is_erp() {
316                assert_eq!(idx.to_phy(), fidl_common::WlanPhyType::Erp);
317            } else if idx.is_ht() {
318                assert_eq!(idx.to_phy(), fidl_common::WlanPhyType::Ht);
319            } else {
320                assert!(
321                    idx.to_phy() == fidl_common::WlanPhyType::Dsss
322                        || idx.to_phy() == fidl_common::WlanPhyType::Hr
323                );
324            }
325        }
326    }
327
328    #[test]
329    fn to_and_from_idx() {
330        for idx in INVALID_TX_VECTOR_IDX + 1..=MAX_VALID_IDX {
331            let idx = TxVecIdx::new(idx).expect("Could not make TxVecIdx from valid index");
332            let tx_vector = TxVector::from_idx(idx);
333            assert_eq!(idx, tx_vector.to_idx());
334        }
335    }
336
337    #[test]
338    fn ht_and_erp_phy_types() {
339        for idx in INVALID_TX_VECTOR_IDX + 1..=MAX_VALID_IDX {
340            let idx = TxVecIdx::new(idx).expect("Could not make TxVecIdx from valid index");
341            let tx_vector = TxVector::from_idx(idx);
342            if idx.is_erp() {
343                assert_eq!(tx_vector.phy(), fidl_common::WlanPhyType::Erp);
344            } else if idx.is_ht() {
345                assert_eq!(tx_vector.phy(), fidl_common::WlanPhyType::Ht);
346            }
347        }
348    }
349
350    #[test]
351    fn from_erp_rates() {
352        for idx in INVALID_TX_VECTOR_IDX + 1..=MAX_VALID_IDX {
353            let idx = TxVecIdx::new(idx).expect("Could not make TxVecIdx from valid index");
354            if idx.is_erp() {
355                let erp_rate = idx.to_erp_rate().unwrap();
356                let tx_vector = TxVector::from_supported_rate(&erp_rate)
357                    .expect("Could not make TxVector from ERP rate.");
358                assert_eq!(idx, tx_vector.to_idx());
359            }
360        }
361    }
362}