Skip to main content

openipc_core/
radiotap.rs

1pub const FRAME_TYPE_DATA: u8 = 0x08;
2pub const FRAME_TYPE_RTS: u8 = 0xb4;
3
4const IEEE80211_RADIOTAP_MCS_HAVE_BW: u8 = 0x01;
5const IEEE80211_RADIOTAP_MCS_HAVE_MCS: u8 = 0x02;
6const IEEE80211_RADIOTAP_MCS_HAVE_GI: u8 = 0x04;
7const IEEE80211_RADIOTAP_MCS_HAVE_FEC: u8 = 0x10;
8const IEEE80211_RADIOTAP_MCS_HAVE_STBC: u8 = 0x20;
9const IEEE80211_RADIOTAP_MCS_SGI: u8 = 0x04;
10const IEEE80211_RADIOTAP_MCS_FEC_LDPC: u8 = 0x10;
11const IEEE80211_RADIOTAP_MCS_STBC_SHIFT: u8 = 5;
12
13const IEEE80211_RADIOTAP_VHT_FLAG_STBC: u8 = 0x01;
14const IEEE80211_RADIOTAP_VHT_FLAG_SGI: u8 = 0x04;
15const IEEE80211_RADIOTAP_VHT_CODING_LDPC_USER0: u8 = 0x01;
16
17pub const RADIOTAP_HT_LEN: usize = 13;
18pub const RADIOTAP_VHT_LEN: usize = 22;
19
20#[derive(Debug, Clone, Copy, PartialEq, Eq)]
21pub enum ChannelBandwidth {
22    Mhz20,
23    Mhz40,
24    Mhz80,
25    Mhz160,
26}
27
28impl ChannelBandwidth {
29    pub const fn realtek_desc_bits(self) -> u8 {
30        match self {
31            Self::Mhz20 => 0,
32            Self::Mhz40 => 1,
33            Self::Mhz80 | Self::Mhz160 => 2,
34        }
35    }
36
37    const fn ht_mcs_bits(self) -> u8 {
38        match self {
39            Self::Mhz20 => 0,
40            Self::Mhz40 | Self::Mhz80 | Self::Mhz160 => 1,
41        }
42    }
43
44    const fn vht_bits(self) -> u8 {
45        match self {
46            Self::Mhz20 => 0x00,
47            Self::Mhz40 => 0x01,
48            Self::Mhz80 => 0x04,
49            Self::Mhz160 => 0x0b,
50        }
51    }
52}
53
54#[derive(Debug, Clone, Copy, PartialEq, Eq)]
55pub struct TxRadioParams {
56    pub mcs_index: u8,
57    pub nss: u8,
58    pub bandwidth: ChannelBandwidth,
59    pub short_gi: bool,
60    pub stbc: u8,
61    pub ldpc: bool,
62    pub vht: bool,
63    pub frame_type: u8,
64}
65
66impl TxRadioParams {
67    pub const fn openipc_uplink_default() -> Self {
68        Self {
69            mcs_index: 0,
70            nss: 1,
71            bandwidth: ChannelBandwidth::Mhz20,
72            short_gi: false,
73            stbc: 1,
74            ldpc: true,
75            vht: false,
76            frame_type: FRAME_TYPE_RTS,
77        }
78    }
79}
80
81impl Default for TxRadioParams {
82    fn default() -> Self {
83        Self::openipc_uplink_default()
84    }
85}
86
87#[derive(Debug, Clone, Copy, PartialEq, Eq)]
88pub struct RadiotapTxInfo {
89    pub vht: bool,
90    pub mcs_index: u8,
91    pub nss: u8,
92    pub bandwidth: ChannelBandwidth,
93    pub short_gi: bool,
94    pub stbc: u8,
95    pub ldpc: bool,
96}
97
98#[derive(Debug, Clone, PartialEq, Eq)]
99pub enum RadiotapError {
100    TooShort,
101    InvalidLength,
102    UnsupportedHeader,
103}
104
105impl std::fmt::Display for RadiotapError {
106    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
107        match self {
108            Self::TooShort => write!(f, "radiotap packet is too short"),
109            Self::InvalidLength => write!(f, "radiotap length is invalid"),
110            Self::UnsupportedHeader => write!(f, "unsupported radiotap TX header"),
111        }
112    }
113}
114
115impl std::error::Error for RadiotapError {}
116
117pub fn build_radiotap_header(params: TxRadioParams) -> Vec<u8> {
118    if params.vht {
119        build_vht_radiotap_header(params)
120    } else {
121        build_ht_radiotap_header(params)
122    }
123}
124
125pub fn build_ht_radiotap_header(params: TxRadioParams) -> Vec<u8> {
126    let known = IEEE80211_RADIOTAP_MCS_HAVE_MCS
127        | IEEE80211_RADIOTAP_MCS_HAVE_BW
128        | IEEE80211_RADIOTAP_MCS_HAVE_GI
129        | IEEE80211_RADIOTAP_MCS_HAVE_STBC
130        | IEEE80211_RADIOTAP_MCS_HAVE_FEC;
131    let mut flags = params.bandwidth.ht_mcs_bits();
132    if params.short_gi {
133        flags |= IEEE80211_RADIOTAP_MCS_SGI;
134    }
135    if params.ldpc {
136        flags |= IEEE80211_RADIOTAP_MCS_FEC_LDPC;
137    }
138    flags |= (params.stbc & 0x03) << IEEE80211_RADIOTAP_MCS_STBC_SHIFT;
139
140    vec![
141        0x00,
142        0x00,
143        RADIOTAP_HT_LEN as u8,
144        0x00,
145        0x00,
146        0x80,
147        0x08,
148        0x00,
149        0x08,
150        0x00,
151        known,
152        flags,
153        params.mcs_index.min(31),
154    ]
155}
156
157pub fn build_vht_radiotap_header(params: TxRadioParams) -> Vec<u8> {
158    let mut flags = 0u8;
159    if params.stbc != 0 {
160        flags |= IEEE80211_RADIOTAP_VHT_FLAG_STBC;
161    }
162    if params.short_gi {
163        flags |= IEEE80211_RADIOTAP_VHT_FLAG_SGI;
164    }
165    let nss = params.nss.clamp(1, 4);
166    let mcs = params.mcs_index.min(9);
167    let mcs_nss0 = (mcs << 4) | nss;
168    let coding = if params.ldpc {
169        IEEE80211_RADIOTAP_VHT_CODING_LDPC_USER0
170    } else {
171        0
172    };
173
174    vec![
175        0x00,
176        0x00,
177        RADIOTAP_VHT_LEN as u8,
178        0x00,
179        0x00,
180        0x80,
181        0x20,
182        0x00,
183        0x08,
184        0x00,
185        0x45,
186        0x00,
187        flags,
188        params.bandwidth.vht_bits(),
189        mcs_nss0,
190        0x00,
191        0x00,
192        0x00,
193        coding,
194        0x00,
195        0x00,
196        0x00,
197    ]
198}
199
200pub fn radiotap_len(packet: &[u8]) -> Result<usize, RadiotapError> {
201    if packet.len() < 4 {
202        return Err(RadiotapError::TooShort);
203    }
204    let len = u16::from_le_bytes([packet[2], packet[3]]) as usize;
205    if len == 0 || len >= packet.len() {
206        return Err(RadiotapError::InvalidLength);
207    }
208    Ok(len)
209}
210
211pub fn parse_radiotap_tx_info(packet: &[u8]) -> Result<RadiotapTxInfo, RadiotapError> {
212    let len = radiotap_len(packet)?;
213    match len {
214        RADIOTAP_HT_LEN if packet.len() >= RADIOTAP_HT_LEN => {
215            let known = packet[10];
216            let flags = packet[11];
217            let bandwidth = if flags & 0x03 == 1 {
218                ChannelBandwidth::Mhz40
219            } else {
220                ChannelBandwidth::Mhz20
221            };
222            Ok(RadiotapTxInfo {
223                vht: false,
224                mcs_index: if known & IEEE80211_RADIOTAP_MCS_HAVE_MCS != 0 {
225                    packet[12].min(31)
226                } else {
227                    0
228                },
229                nss: 1,
230                bandwidth,
231                short_gi: known & IEEE80211_RADIOTAP_MCS_HAVE_GI != 0
232                    && flags & IEEE80211_RADIOTAP_MCS_SGI != 0,
233                stbc: if known & IEEE80211_RADIOTAP_MCS_HAVE_STBC != 0 {
234                    (flags >> IEEE80211_RADIOTAP_MCS_STBC_SHIFT) & 0x03
235                } else {
236                    0
237                },
238                ldpc: known & IEEE80211_RADIOTAP_MCS_HAVE_FEC != 0
239                    && flags & IEEE80211_RADIOTAP_MCS_FEC_LDPC != 0,
240            })
241        }
242        RADIOTAP_VHT_LEN if packet.len() >= RADIOTAP_VHT_LEN => {
243            let flags = packet[12];
244            let bandwidth = match packet[13] & 0x1f {
245                1..=3 => ChannelBandwidth::Mhz40,
246                4..=10 => ChannelBandwidth::Mhz80,
247                11..=31 => ChannelBandwidth::Mhz160,
248                _ => ChannelBandwidth::Mhz20,
249            };
250            let mcs_nss = packet[14];
251            Ok(RadiotapTxInfo {
252                vht: true,
253                mcs_index: ((mcs_nss >> 4) & 0x0f).min(9),
254                nss: (mcs_nss & 0x0f).clamp(1, 4),
255                bandwidth,
256                short_gi: flags & IEEE80211_RADIOTAP_VHT_FLAG_SGI != 0,
257                stbc: u8::from(flags & IEEE80211_RADIOTAP_VHT_FLAG_STBC != 0),
258                ldpc: packet[18] & IEEE80211_RADIOTAP_VHT_CODING_LDPC_USER0 != 0,
259            })
260        }
261        _ => Err(RadiotapError::UnsupportedHeader),
262    }
263}
264
265#[cfg(test)]
266mod tests {
267    use super::*;
268
269    #[test]
270    fn ht_header_roundtrips_tx_info() {
271        let params = TxRadioParams {
272            mcs_index: 3,
273            bandwidth: ChannelBandwidth::Mhz40,
274            short_gi: true,
275            stbc: 1,
276            ldpc: true,
277            ..TxRadioParams::default()
278        };
279        let mut packet = build_radiotap_header(params);
280        packet.extend_from_slice(&[0u8; 24]);
281        let parsed = parse_radiotap_tx_info(&packet).unwrap();
282        assert_eq!(parsed.mcs_index, 3);
283        assert_eq!(parsed.bandwidth, ChannelBandwidth::Mhz40);
284        assert!(parsed.short_gi);
285        assert!(parsed.ldpc);
286        assert_eq!(parsed.stbc, 1);
287    }
288}