Skip to main content

stackforge_core/layer/dot11/
data.rs

1//! IEEE 802.11 data frame subtypes and `QoS` handling.
2//!
3//! Data frames carry the actual payload data in 802.11 networks.
4//! `QoS` data frames include a 2-byte `QoS` Control field after the
5//! 802.11 header (and before the payload/security headers).
6
7use crate::layer::field::FieldError;
8
9/// `QoS` Control field length.
10pub const QOS_CTRL_LEN: usize = 2;
11
12/// LLC/SNAP header length.
13pub const LLC_SNAP_LEN: usize = 8;
14
15/// LLC/SNAP header: AA:AA:03:00:00:00 followed by 2-byte `EtherType`.
16pub const LLC_SNAP_PREFIX: [u8; 6] = [0xAA, 0xAA, 0x03, 0x00, 0x00, 0x00];
17
18// ============================================================================
19// Dot11QoS
20// ============================================================================
21
22/// 802.11 `QoS` Control field (2 bytes).
23///
24/// Layout (Scapy bit ordering for byte 0):
25/// ```text
26/// Byte 0: [A-MSDU Present(1) | Ack Policy(2) | EOSP(1) | TID(4)]
27/// Byte 1: TXOP Limit / Queue Size
28/// ```
29///
30/// Present in `QoS` Data frames (subtype >= 8).
31#[derive(Debug, Clone)]
32pub struct Dot11QoS {
33    pub offset: usize,
34}
35
36impl Dot11QoS {
37    #[must_use]
38    pub fn new(offset: usize) -> Self {
39        Self { offset }
40    }
41
42    /// Validate buffer length.
43    pub fn validate(buf: &[u8], offset: usize) -> Result<(), FieldError> {
44        if buf.len() < offset + QOS_CTRL_LEN {
45            return Err(FieldError::BufferTooShort {
46                offset,
47                need: QOS_CTRL_LEN,
48                have: buf.len().saturating_sub(offset),
49            });
50        }
51        Ok(())
52    }
53
54    /// Raw `QoS` Control field (2 bytes, little-endian).
55    pub fn raw(&self, buf: &[u8]) -> Result<u16, FieldError> {
56        let off = self.offset;
57        if buf.len() < off + 2 {
58            return Err(FieldError::BufferTooShort {
59                offset: off,
60                need: 2,
61                have: buf.len(),
62            });
63        }
64        Ok(u16::from_le_bytes([buf[off], buf[off + 1]]))
65    }
66
67    /// TID (Traffic Identifier, lower 4 bits of byte 0).
68    pub fn tid(&self, buf: &[u8]) -> Result<u8, FieldError> {
69        let off = self.offset;
70        if buf.len() <= off {
71            return Err(FieldError::BufferTooShort {
72                offset: off,
73                need: 1,
74                have: buf.len(),
75            });
76        }
77        Ok(buf[off] & 0x0F)
78    }
79
80    /// EOSP (End Of Service Period, bit 4 of byte 0).
81    pub fn eosp(&self, buf: &[u8]) -> Result<bool, FieldError> {
82        let off = self.offset;
83        if buf.len() <= off {
84            return Err(FieldError::BufferTooShort {
85                offset: off,
86                need: 1,
87                have: buf.len(),
88            });
89        }
90        Ok(buf[off] & 0x10 != 0)
91    }
92
93    /// Ack Policy (bits 5-6 of byte 0).
94    pub fn ack_policy(&self, buf: &[u8]) -> Result<u8, FieldError> {
95        let off = self.offset;
96        if buf.len() <= off {
97            return Err(FieldError::BufferTooShort {
98                offset: off,
99                need: 1,
100                have: buf.len(),
101            });
102        }
103        Ok((buf[off] >> 5) & 0x03)
104    }
105
106    /// A-MSDU Present flag (bit 7 of byte 0).
107    pub fn a_msdu_present(&self, buf: &[u8]) -> Result<bool, FieldError> {
108        let off = self.offset;
109        if buf.len() <= off {
110            return Err(FieldError::BufferTooShort {
111                offset: off,
112                need: 1,
113                have: buf.len(),
114            });
115        }
116        Ok(buf[off] & 0x80 != 0)
117    }
118
119    /// TXOP Limit or Queue Size (byte 1).
120    pub fn txop(&self, buf: &[u8]) -> Result<u8, FieldError> {
121        let off = self.offset + 1;
122        if buf.len() <= off {
123            return Err(FieldError::BufferTooShort {
124                offset: off,
125                need: 1,
126                have: buf.len(),
127            });
128        }
129        Ok(buf[off])
130    }
131
132    /// Header length.
133    #[must_use]
134    pub fn header_len(&self) -> usize {
135        QOS_CTRL_LEN
136    }
137
138    /// Build `QoS` Control field bytes.
139    #[must_use]
140    pub fn build(tid: u8, eosp: bool, ack_policy: u8, a_msdu: bool, txop: u8) -> Vec<u8> {
141        let byte0 = (tid & 0x0F)
142            | (if eosp { 0x10 } else { 0 })
143            | ((ack_policy & 0x03) << 5)
144            | (if a_msdu { 0x80 } else { 0 });
145        vec![byte0, txop]
146    }
147}
148
149// ============================================================================
150// LLC/SNAP detection
151// ============================================================================
152
153/// Check if the data at the given offset starts with an LLC/SNAP header.
154#[must_use]
155pub fn is_llc_snap(buf: &[u8], offset: usize) -> bool {
156    if buf.len() < offset + LLC_SNAP_LEN {
157        return false;
158    }
159    buf[offset..offset + 6] == LLC_SNAP_PREFIX
160}
161
162/// Extract the `EtherType` from an LLC/SNAP header at the given offset.
163pub fn llc_snap_ethertype(buf: &[u8], offset: usize) -> Result<u16, FieldError> {
164    if buf.len() < offset + LLC_SNAP_LEN {
165        return Err(FieldError::BufferTooShort {
166            offset: offset + 6,
167            need: 2,
168            have: buf.len().saturating_sub(offset + 6),
169        });
170    }
171    Ok(u16::from_be_bytes([buf[offset + 6], buf[offset + 7]]))
172}
173
174// ============================================================================
175// A-MSDU subframe
176// ============================================================================
177
178/// A single A-MSDU subframe.
179///
180/// Layout:
181/// - DA (6 bytes)
182/// - SA (6 bytes)
183/// - Length (2 bytes, big-endian)
184/// - MSDU (variable)
185/// - Padding (0-3 bytes to align to 4-byte boundary)
186#[derive(Debug, Clone)]
187pub struct AMsduSubframe {
188    /// Destination address.
189    pub da: [u8; 6],
190    /// Source address.
191    pub sa: [u8; 6],
192    /// MSDU data.
193    pub data: Vec<u8>,
194}
195
196/// A-MSDU subframe header length: DA(6) + SA(6) + Length(2) = 14.
197pub const AMSDU_SUBFRAME_HEADER_LEN: usize = 14;
198
199impl AMsduSubframe {
200    /// Parse A-MSDU subframes from the given buffer.
201    pub fn parse_all(buf: &[u8], offset: usize) -> Result<Vec<AMsduSubframe>, FieldError> {
202        let mut subframes = Vec::new();
203        let mut pos = offset;
204
205        while pos + AMSDU_SUBFRAME_HEADER_LEN <= buf.len() {
206            let mut da = [0u8; 6];
207            let mut sa = [0u8; 6];
208            da.copy_from_slice(&buf[pos..pos + 6]);
209            sa.copy_from_slice(&buf[pos + 6..pos + 12]);
210            let length = u16::from_be_bytes([buf[pos + 12], buf[pos + 13]]) as usize;
211
212            let data_start = pos + AMSDU_SUBFRAME_HEADER_LEN;
213            let data_end = data_start + length;
214            if data_end > buf.len() {
215                break;
216            }
217
218            subframes.push(AMsduSubframe {
219                da,
220                sa,
221                data: buf[data_start..data_end].to_vec(),
222            });
223
224            // Align to 4-byte boundary
225            pos = data_end;
226            let padding = (4 - (pos % 4)) % 4;
227            pos += padding;
228        }
229
230        Ok(subframes)
231    }
232
233    /// Build an A-MSDU subframe.
234    #[must_use]
235    pub fn build(&self) -> Vec<u8> {
236        let length = self.data.len() as u16;
237        let mut out = Vec::with_capacity(AMSDU_SUBFRAME_HEADER_LEN + self.data.len() + 3);
238        out.extend_from_slice(&self.da);
239        out.extend_from_slice(&self.sa);
240        out.extend_from_slice(&length.to_be_bytes());
241        out.extend_from_slice(&self.data);
242        // Add padding to 4-byte boundary
243        let padding = (4 - (out.len() % 4)) % 4;
244        out.extend(std::iter::repeat_n(0u8, padding));
245        out
246    }
247}
248
249#[cfg(test)]
250mod tests {
251    use super::*;
252
253    #[test]
254    fn test_qos_parse() {
255        let buf = vec![0x03, 0x00];
256        let qos = Dot11QoS::new(0);
257
258        assert_eq!(qos.tid(&buf).unwrap(), 3);
259        assert!(!qos.eosp(&buf).unwrap());
260        assert_eq!(qos.ack_policy(&buf).unwrap(), 0);
261        assert!(!qos.a_msdu_present(&buf).unwrap());
262        assert_eq!(qos.txop(&buf).unwrap(), 0);
263    }
264
265    #[test]
266    fn test_qos_with_eosp_and_amsdu() {
267        // TID=5, EOSP=1, AckPolicy=2, A-MSDU=1
268        // byte0: TID=5(0x05) | EOSP(0x10) | AckPolicy=2(0x40) | A-MSDU(0x80) = 0xD5
269        let buf = vec![0xD5, 0x10];
270        let qos = Dot11QoS::new(0);
271
272        assert_eq!(qos.tid(&buf).unwrap(), 5);
273        assert!(qos.eosp(&buf).unwrap());
274        assert_eq!(qos.ack_policy(&buf).unwrap(), 2);
275        assert!(qos.a_msdu_present(&buf).unwrap());
276        assert_eq!(qos.txop(&buf).unwrap(), 0x10);
277    }
278
279    #[test]
280    fn test_qos_build_roundtrip() {
281        let data = Dot11QoS::build(5, true, 2, true, 0x10);
282        assert_eq!(data.len(), QOS_CTRL_LEN);
283
284        let qos = Dot11QoS::new(0);
285        assert_eq!(qos.tid(&data).unwrap(), 5);
286        assert!(qos.eosp(&data).unwrap());
287        assert_eq!(qos.ack_policy(&data).unwrap(), 2);
288        assert!(qos.a_msdu_present(&data).unwrap());
289        assert_eq!(qos.txop(&data).unwrap(), 0x10);
290    }
291
292    #[test]
293    fn test_llc_snap_detection() {
294        let mut buf = vec![0u8; 8];
295        buf[0..6].copy_from_slice(&LLC_SNAP_PREFIX);
296        buf[6] = 0x08;
297        buf[7] = 0x00;
298
299        assert!(is_llc_snap(&buf, 0));
300        assert_eq!(llc_snap_ethertype(&buf, 0).unwrap(), 0x0800);
301    }
302
303    #[test]
304    fn test_llc_snap_not_present() {
305        let buf = vec![0x00; 8];
306        assert!(!is_llc_snap(&buf, 0));
307    }
308
309    #[test]
310    fn test_amsdu_parse() {
311        let subframe1 = AMsduSubframe {
312            da: [0x01, 0x02, 0x03, 0x04, 0x05, 0x06],
313            sa: [0x11, 0x12, 0x13, 0x14, 0x15, 0x16],
314            data: vec![0xAA, 0xBB, 0xCC],
315        };
316        let built = subframe1.build();
317        assert_eq!(built.len(), 20);
318
319        let parsed = AMsduSubframe::parse_all(&built, 0).unwrap();
320        assert_eq!(parsed.len(), 1);
321        assert_eq!(parsed[0].da, [0x01, 0x02, 0x03, 0x04, 0x05, 0x06]);
322        assert_eq!(parsed[0].sa, [0x11, 0x12, 0x13, 0x14, 0x15, 0x16]);
323        assert_eq!(parsed[0].data, vec![0xAA, 0xBB, 0xCC]);
324    }
325
326    #[test]
327    fn test_amsdu_multiple_subframes() {
328        let sf1 = AMsduSubframe {
329            da: [0x01; 6],
330            sa: [0x02; 6],
331            data: vec![0x10, 0x20],
332        };
333        let sf2 = AMsduSubframe {
334            da: [0x03; 6],
335            sa: [0x04; 6],
336            data: vec![0x30, 0x40, 0x50, 0x60],
337        };
338
339        let mut buf = sf1.build();
340        buf.extend(sf2.build());
341
342        let parsed = AMsduSubframe::parse_all(&buf, 0).unwrap();
343        assert_eq!(parsed.len(), 2);
344        assert_eq!(parsed[0].data, vec![0x10, 0x20]);
345        assert_eq!(parsed[1].data, vec![0x30, 0x40, 0x50, 0x60]);
346    }
347
348    #[test]
349    fn test_qos_header_len() {
350        let qos = Dot11QoS::new(0);
351        assert_eq!(qos.header_len(), 2);
352    }
353
354    #[test]
355    fn test_qos_raw() {
356        let buf = vec![0xD5, 0x10];
357        let qos = Dot11QoS::new(0);
358        assert_eq!(qos.raw(&buf).unwrap(), 0x10D5);
359    }
360}