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}