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