Skip to main content

stackforge_core/layer/dot11/
security.rs

1//! IEEE 802.11 security wrappers (WEP, TKIP, CCMP).
2//!
3//! These wrappers handle the encryption header and trailer fields
4//! that appear in protected 802.11 data frames. They do NOT perform
5//! actual encryption/decryption — they provide parsing and building
6//! of the security header/trailer structures.
7
8use crate::layer::field::FieldError;
9
10// ============================================================================
11// WEP Header/Trailer constants
12// ============================================================================
13
14/// WEP header length: IV(3) + KeyID(1) = 4 bytes.
15pub const WEP_HEADER_LEN: usize = 4;
16
17/// WEP trailer length: ICV(4) = 4 bytes.
18pub const WEP_TRAILER_LEN: usize = 4;
19
20// ============================================================================
21// Dot11WEP
22// ============================================================================
23
24/// WEP (Wired Equivalent Privacy) encryption wrapper.
25///
26/// Format:
27/// ```text
28/// [IV (3 bytes)] [KeyID/Pad (1 byte)] [Encrypted Data ...] [ICV (4 bytes)]
29/// ```
30///
31/// The KeyID byte layout:
32/// - Bits 0-5: Pad (reserved, should be 0)
33/// - Bits 6-7: Key ID (0-3)
34#[derive(Debug, Clone)]
35pub struct Dot11WEP {
36    pub offset: usize,
37}
38
39impl Dot11WEP {
40    pub fn new(offset: usize) -> Self {
41        Self { offset }
42    }
43
44    /// Validate buffer length for WEP header.
45    pub fn validate(buf: &[u8], offset: usize) -> Result<(), FieldError> {
46        if buf.len() < offset + WEP_HEADER_LEN {
47            return Err(FieldError::BufferTooShort {
48                offset,
49                need: WEP_HEADER_LEN,
50                have: buf.len().saturating_sub(offset),
51            });
52        }
53        Ok(())
54    }
55
56    /// Initialization Vector (3 bytes).
57    pub fn iv(&self, buf: &[u8]) -> Result<[u8; 3], FieldError> {
58        if buf.len() < self.offset + WEP_HEADER_LEN {
59            return Err(FieldError::BufferTooShort {
60                offset: self.offset,
61                need: WEP_HEADER_LEN,
62                have: buf.len().saturating_sub(self.offset),
63            });
64        }
65        Ok([buf[self.offset], buf[self.offset + 1], buf[self.offset + 2]])
66    }
67
68    /// Full IV as a u32 (24-bit value, big-endian).
69    pub fn iv_u32(&self, buf: &[u8]) -> Result<u32, FieldError> {
70        let iv = self.iv(buf)?;
71        Ok(((iv[0] as u32) << 16) | ((iv[1] as u32) << 8) | (iv[2] as u32))
72    }
73
74    /// Key ID (bits 6-7 of byte 3).
75    pub fn key_id(&self, buf: &[u8]) -> Result<u8, FieldError> {
76        if buf.len() < self.offset + WEP_HEADER_LEN {
77            return Err(FieldError::BufferTooShort {
78                offset: self.offset,
79                need: WEP_HEADER_LEN,
80                have: buf.len().saturating_sub(self.offset),
81            });
82        }
83        Ok((buf[self.offset + 3] >> 6) & 0x03)
84    }
85
86    /// Raw KeyID/Pad byte.
87    pub fn key_id_byte(&self, buf: &[u8]) -> Result<u8, FieldError> {
88        if buf.len() < self.offset + WEP_HEADER_LEN {
89            return Err(FieldError::BufferTooShort {
90                offset: self.offset,
91                need: WEP_HEADER_LEN,
92                have: buf.len().saturating_sub(self.offset),
93            });
94        }
95        Ok(buf[self.offset + 3])
96    }
97
98    /// ICV (Integrity Check Value) — 4 bytes at the end of the encrypted data.
99    /// `frame_end` is the offset of the end of the entire frame (after ICV).
100    pub fn icv(&self, buf: &[u8], frame_end: usize) -> Result<u32, FieldError> {
101        if frame_end < WEP_TRAILER_LEN || buf.len() < frame_end {
102            return Err(FieldError::BufferTooShort {
103                offset: frame_end.saturating_sub(WEP_TRAILER_LEN),
104                need: WEP_TRAILER_LEN,
105                have: 0,
106            });
107        }
108        let off = frame_end - WEP_TRAILER_LEN;
109        Ok(u32::from_le_bytes([
110            buf[off],
111            buf[off + 1],
112            buf[off + 2],
113            buf[off + 3],
114        ]))
115    }
116
117    /// Encrypted data (between header and ICV).
118    pub fn encrypted_data<'a>(
119        &self,
120        buf: &'a [u8],
121        frame_end: usize,
122    ) -> Result<&'a [u8], FieldError> {
123        let start = self.offset + WEP_HEADER_LEN;
124        let end = frame_end.saturating_sub(WEP_TRAILER_LEN);
125        if start > end || buf.len() < frame_end {
126            return Err(FieldError::BufferTooShort {
127                offset: start,
128                need: 0,
129                have: 0,
130            });
131        }
132        Ok(&buf[start..end])
133    }
134
135    /// Header length (always 4 bytes).
136    pub fn header_len(&self) -> usize {
137        WEP_HEADER_LEN
138    }
139
140    /// Trailer length (always 4 bytes).
141    pub fn trailer_len(&self) -> usize {
142        WEP_TRAILER_LEN
143    }
144
145    /// Build a WEP header.
146    pub fn build_header(iv: [u8; 3], key_id: u8) -> Vec<u8> {
147        vec![iv[0], iv[1], iv[2], (key_id & 0x03) << 6]
148    }
149
150    /// Build a WEP ICV trailer.
151    pub fn build_icv(icv: u32) -> Vec<u8> {
152        icv.to_le_bytes().to_vec()
153    }
154}
155
156// ============================================================================
157// TKIP Header/Trailer constants
158// ============================================================================
159
160/// TKIP header length without Extended IV: 4 bytes.
161pub const TKIP_HEADER_SHORT: usize = 4;
162
163/// TKIP header length with Extended IV: 8 bytes.
164pub const TKIP_HEADER_LONG: usize = 8;
165
166/// TKIP trailer length: MIC(8) + ICV(4) = 12 bytes.
167/// Note: The ICV is part of the encrypted payload, and the 8-byte MIC
168/// is also encrypted. The trailer for raw wire format is 4 bytes (ICV only).
169pub const TKIP_ICV_LEN: usize = 4;
170
171// ============================================================================
172// Dot11TKIP
173// ============================================================================
174
175/// TKIP (Temporal Key Integrity Protocol) encryption wrapper.
176///
177/// Format:
178/// ```text
179/// [TSC1(1)] [WEPSeed(1)] [TSC0(1)] [KeyID/ExtIV(1)] [TSC2(1) TSC3(1) TSC4(1) TSC5(1)] [Encrypted Data...] [ICV(4)]
180/// ```
181///
182/// The KeyID/ExtIV byte:
183/// - Bits 0-4: Reserved
184/// - Bit 5: ExtIV flag (always 1 for TKIP)
185/// - Bits 6-7: Key ID
186///
187/// TSC (TKIP Sequence Counter) is a 48-bit value constructed from TSC0..TSC5.
188#[derive(Debug, Clone)]
189pub struct Dot11TKIP {
190    pub offset: usize,
191}
192
193impl Dot11TKIP {
194    pub fn new(offset: usize) -> Self {
195        Self { offset }
196    }
197
198    /// Validate buffer length for TKIP header.
199    pub fn validate(buf: &[u8], offset: usize) -> Result<(), FieldError> {
200        if buf.len() < offset + TKIP_HEADER_SHORT {
201            return Err(FieldError::BufferTooShort {
202                offset,
203                need: TKIP_HEADER_SHORT,
204                have: buf.len().saturating_sub(offset),
205            });
206        }
207        Ok(())
208    }
209
210    /// TSC1 (byte 0 of TKIP header).
211    pub fn tsc1(&self, buf: &[u8]) -> Result<u8, FieldError> {
212        if buf.len() <= self.offset {
213            return Err(FieldError::BufferTooShort {
214                offset: self.offset,
215                need: 1,
216                have: 0,
217            });
218        }
219        Ok(buf[self.offset])
220    }
221
222    /// WEP Seed (byte 1 of TKIP header).
223    pub fn wep_seed(&self, buf: &[u8]) -> Result<u8, FieldError> {
224        if buf.len() <= self.offset + 1 {
225            return Err(FieldError::BufferTooShort {
226                offset: self.offset + 1,
227                need: 1,
228                have: 0,
229            });
230        }
231        Ok(buf[self.offset + 1])
232    }
233
234    /// TSC0 (byte 2 of TKIP header).
235    pub fn tsc0(&self, buf: &[u8]) -> Result<u8, FieldError> {
236        if buf.len() <= self.offset + 2 {
237            return Err(FieldError::BufferTooShort {
238                offset: self.offset + 2,
239                need: 1,
240                have: 0,
241            });
242        }
243        Ok(buf[self.offset + 2])
244    }
245
246    /// Check if Extended IV flag is set (bit 5 of byte 3).
247    pub fn ext_iv(&self, buf: &[u8]) -> Result<bool, FieldError> {
248        if buf.len() < self.offset + TKIP_HEADER_SHORT {
249            return Err(FieldError::BufferTooShort {
250                offset: self.offset,
251                need: TKIP_HEADER_SHORT,
252                have: buf.len().saturating_sub(self.offset),
253            });
254        }
255        Ok(buf[self.offset + 3] & 0x20 != 0)
256    }
257
258    /// Key ID (bits 6-7 of byte 3).
259    pub fn key_id(&self, buf: &[u8]) -> Result<u8, FieldError> {
260        if buf.len() < self.offset + TKIP_HEADER_SHORT {
261            return Err(FieldError::BufferTooShort {
262                offset: self.offset,
263                need: TKIP_HEADER_SHORT,
264                have: buf.len().saturating_sub(self.offset),
265            });
266        }
267        Ok((buf[self.offset + 3] >> 6) & 0x03)
268    }
269
270    /// TSC2-TSC5 (bytes 4-7, only present if ExtIV is set).
271    pub fn tsc_high(&self, buf: &[u8]) -> Result<[u8; 4], FieldError> {
272        if buf.len() < self.offset + TKIP_HEADER_LONG {
273            return Err(FieldError::BufferTooShort {
274                offset: self.offset + 4,
275                need: 4,
276                have: buf.len().saturating_sub(self.offset + 4),
277            });
278        }
279        Ok([
280            buf[self.offset + 4],
281            buf[self.offset + 5],
282            buf[self.offset + 6],
283            buf[self.offset + 7],
284        ])
285    }
286
287    /// Full 48-bit TSC (TKIP Sequence Counter).
288    /// TSC = TSC0 | (TSC1 << 8) | (TSC2 << 16) | (TSC3 << 24) | (TSC4 << 32) | (TSC5 << 40)
289    pub fn tsc(&self, buf: &[u8]) -> Result<u64, FieldError> {
290        let tsc0 = self.tsc0(buf)? as u64;
291        let tsc1 = self.tsc1(buf)? as u64;
292        let high = self.tsc_high(buf)?;
293        let tsc2 = high[0] as u64;
294        let tsc3 = high[1] as u64;
295        let tsc4 = high[2] as u64;
296        let tsc5 = high[3] as u64;
297        Ok(tsc0 | (tsc1 << 8) | (tsc2 << 16) | (tsc3 << 24) | (tsc4 << 32) | (tsc5 << 40))
298    }
299
300    /// Header length: 4 bytes (no ExtIV) or 8 bytes (with ExtIV).
301    pub fn header_len(&self, buf: &[u8]) -> usize {
302        if self.ext_iv(buf).unwrap_or(false) {
303            TKIP_HEADER_LONG
304        } else {
305            TKIP_HEADER_SHORT
306        }
307    }
308
309    /// Trailer length (ICV = 4 bytes).
310    pub fn trailer_len(&self) -> usize {
311        TKIP_ICV_LEN
312    }
313
314    /// Build a TKIP header with Extended IV.
315    pub fn build_header(tsc: u64, key_id: u8) -> Vec<u8> {
316        let tsc0 = (tsc & 0xFF) as u8;
317        let tsc1 = ((tsc >> 8) & 0xFF) as u8;
318        let tsc2 = ((tsc >> 16) & 0xFF) as u8;
319        let tsc3 = ((tsc >> 24) & 0xFF) as u8;
320        let tsc4 = ((tsc >> 32) & 0xFF) as u8;
321        let tsc5 = ((tsc >> 40) & 0xFF) as u8;
322        // WEP Seed = (TSC1 | 0x20) & 0x7F (per 802.11i)
323        let wep_seed = (tsc1 | 0x20) & 0x7F;
324        let key_id_byte = ((key_id & 0x03) << 6) | 0x20; // ExtIV=1
325        vec![tsc1, wep_seed, tsc0, key_id_byte, tsc2, tsc3, tsc4, tsc5]
326    }
327}
328
329// ============================================================================
330// CCMP Header/Trailer constants
331// ============================================================================
332
333/// CCMP header length: 8 bytes always.
334pub const CCMP_HEADER_LEN: usize = 8;
335
336/// CCMP trailer length: MIC(8) = 8 bytes.
337pub const CCMP_MIC_LEN: usize = 8;
338
339// ============================================================================
340// Dot11CCMP
341// ============================================================================
342
343/// CCMP (Counter Mode CBC-MAC Protocol / AES-CCM) encryption wrapper.
344///
345/// Format:
346/// ```text
347/// [PN0(1)] [PN1(1)] [Rsvd(1)] [KeyID/ExtIV(1)] [PN2(1)] [PN3(1)] [PN4(1)] [PN5(1)]
348/// [Encrypted Data ...]
349/// [MIC (8 bytes)]
350/// ```
351///
352/// The KeyID/ExtIV byte:
353/// - Bits 0-4: Reserved
354/// - Bit 5: ExtIV flag (always 1 for CCMP)
355/// - Bits 6-7: Key ID
356///
357/// PN (Packet Number) is a 48-bit value constructed from PN0..PN5.
358#[derive(Debug, Clone)]
359pub struct Dot11CCMP {
360    pub offset: usize,
361}
362
363impl Dot11CCMP {
364    pub fn new(offset: usize) -> Self {
365        Self { offset }
366    }
367
368    /// Validate buffer length for CCMP header.
369    pub fn validate(buf: &[u8], offset: usize) -> Result<(), FieldError> {
370        if buf.len() < offset + CCMP_HEADER_LEN {
371            return Err(FieldError::BufferTooShort {
372                offset,
373                need: CCMP_HEADER_LEN,
374                have: buf.len().saturating_sub(offset),
375            });
376        }
377        Ok(())
378    }
379
380    /// PN0 (byte 0 of CCMP header).
381    pub fn pn0(&self, buf: &[u8]) -> Result<u8, FieldError> {
382        if buf.len() <= self.offset {
383            return Err(FieldError::BufferTooShort {
384                offset: self.offset,
385                need: 1,
386                have: 0,
387            });
388        }
389        Ok(buf[self.offset])
390    }
391
392    /// PN1 (byte 1 of CCMP header).
393    pub fn pn1(&self, buf: &[u8]) -> Result<u8, FieldError> {
394        if buf.len() <= self.offset + 1 {
395            return Err(FieldError::BufferTooShort {
396                offset: self.offset + 1,
397                need: 1,
398                have: 0,
399            });
400        }
401        Ok(buf[self.offset + 1])
402    }
403
404    /// Reserved byte (byte 2, should be 0).
405    pub fn reserved(&self, buf: &[u8]) -> Result<u8, FieldError> {
406        if buf.len() <= self.offset + 2 {
407            return Err(FieldError::BufferTooShort {
408                offset: self.offset + 2,
409                need: 1,
410                have: 0,
411            });
412        }
413        Ok(buf[self.offset + 2])
414    }
415
416    /// Key ID (bits 6-7 of byte 3).
417    pub fn key_id(&self, buf: &[u8]) -> Result<u8, FieldError> {
418        if buf.len() < self.offset + CCMP_HEADER_LEN {
419            return Err(FieldError::BufferTooShort {
420                offset: self.offset,
421                need: CCMP_HEADER_LEN,
422                have: buf.len().saturating_sub(self.offset),
423            });
424        }
425        Ok((buf[self.offset + 3] >> 6) & 0x03)
426    }
427
428    /// ExtIV flag (bit 5 of byte 3, always 1 for CCMP).
429    pub fn ext_iv(&self, buf: &[u8]) -> Result<bool, FieldError> {
430        if buf.len() < self.offset + CCMP_HEADER_LEN {
431            return Err(FieldError::BufferTooShort {
432                offset: self.offset,
433                need: CCMP_HEADER_LEN,
434                have: buf.len().saturating_sub(self.offset),
435            });
436        }
437        Ok(buf[self.offset + 3] & 0x20 != 0)
438    }
439
440    /// PN2-PN5 (bytes 4-7).
441    pub fn pn_high(&self, buf: &[u8]) -> Result<[u8; 4], FieldError> {
442        if buf.len() < self.offset + CCMP_HEADER_LEN {
443            return Err(FieldError::BufferTooShort {
444                offset: self.offset + 4,
445                need: 4,
446                have: buf.len().saturating_sub(self.offset + 4),
447            });
448        }
449        Ok([
450            buf[self.offset + 4],
451            buf[self.offset + 5],
452            buf[self.offset + 6],
453            buf[self.offset + 7],
454        ])
455    }
456
457    /// Full 48-bit Packet Number (PN).
458    /// PN = PN0 | (PN1 << 8) | (PN2 << 16) | (PN3 << 24) | (PN4 << 32) | (PN5 << 40)
459    pub fn pn(&self, buf: &[u8]) -> Result<u64, FieldError> {
460        let pn0 = self.pn0(buf)? as u64;
461        let pn1 = self.pn1(buf)? as u64;
462        let high = self.pn_high(buf)?;
463        let pn2 = high[0] as u64;
464        let pn3 = high[1] as u64;
465        let pn4 = high[2] as u64;
466        let pn5 = high[3] as u64;
467        Ok(pn0 | (pn1 << 8) | (pn2 << 16) | (pn3 << 24) | (pn4 << 32) | (pn5 << 40))
468    }
469
470    /// MIC (Message Integrity Code) — 8 bytes at the end of the encrypted data.
471    pub fn mic(&self, buf: &[u8], frame_end: usize) -> Result<[u8; 8], FieldError> {
472        if frame_end < CCMP_MIC_LEN || buf.len() < frame_end {
473            return Err(FieldError::BufferTooShort {
474                offset: frame_end.saturating_sub(CCMP_MIC_LEN),
475                need: CCMP_MIC_LEN,
476                have: 0,
477            });
478        }
479        let off = frame_end - CCMP_MIC_LEN;
480        let mut mic = [0u8; 8];
481        mic.copy_from_slice(&buf[off..frame_end]);
482        Ok(mic)
483    }
484
485    /// Header length (always 8 bytes for CCMP).
486    pub fn header_len(&self) -> usize {
487        CCMP_HEADER_LEN
488    }
489
490    /// Trailer length (MIC = 8 bytes).
491    pub fn trailer_len(&self) -> usize {
492        CCMP_MIC_LEN
493    }
494
495    /// Build a CCMP header.
496    pub fn build_header(pn: u64, key_id: u8) -> Vec<u8> {
497        let pn0 = (pn & 0xFF) as u8;
498        let pn1 = ((pn >> 8) & 0xFF) as u8;
499        let pn2 = ((pn >> 16) & 0xFF) as u8;
500        let pn3 = ((pn >> 24) & 0xFF) as u8;
501        let pn4 = ((pn >> 32) & 0xFF) as u8;
502        let pn5 = ((pn >> 40) & 0xFF) as u8;
503        let key_id_byte = ((key_id & 0x03) << 6) | 0x20; // ExtIV=1
504        vec![pn0, pn1, 0x00, key_id_byte, pn2, pn3, pn4, pn5]
505    }
506
507    /// Build a CCMP MIC trailer (placeholder — actual MIC requires key material).
508    pub fn build_mic(mic: [u8; 8]) -> Vec<u8> {
509        mic.to_vec()
510    }
511}
512
513// ============================================================================
514// Helper: Detect security type from protected frame
515// ============================================================================
516
517/// Security protocol type detected from the header.
518#[derive(Debug, Clone, Copy, PartialEq, Eq)]
519pub enum SecurityType {
520    /// WEP (no ExtIV flag).
521    WEP,
522    /// TKIP (ExtIV flag set, WEP seed pattern).
523    TKIP,
524    /// CCMP (ExtIV flag set, reserved byte is 0).
525    CCMP,
526}
527
528/// Detect the security type from the encryption header at the given offset.
529///
530/// This uses heuristics based on the ExtIV flag and the reserved byte:
531/// - No ExtIV flag (bit 5 of byte 3 is 0) => WEP
532/// - ExtIV flag set and byte 2 is 0 (reserved) => CCMP
533/// - ExtIV flag set and byte 2 is non-zero => TKIP (WEP seed)
534///
535/// Note: This is a heuristic and may not always be correct. The actual
536/// security type should be determined from the RSN/WPA IE in the beacon.
537pub fn detect_security_type(buf: &[u8], offset: usize) -> Option<SecurityType> {
538    if buf.len() < offset + 4 {
539        return None;
540    }
541    let ext_iv = buf[offset + 3] & 0x20 != 0;
542    if !ext_iv {
543        return Some(SecurityType::WEP);
544    }
545    // ExtIV is set — TKIP or CCMP
546    // CCMP has a reserved byte (byte 2) = 0
547    if buf[offset + 2] == 0 {
548        Some(SecurityType::CCMP)
549    } else {
550        Some(SecurityType::TKIP)
551    }
552}
553
554// ============================================================================
555// Tests
556// ============================================================================
557
558#[cfg(test)]
559mod tests {
560    use super::*;
561
562    #[test]
563    fn test_wep_parse() {
564        // IV: 0x01, 0x02, 0x03; KeyID: 1 (bits 6-7 = 01 => 0x40)
565        let mut buf = vec![0x01, 0x02, 0x03, 0x40];
566        // Add some encrypted data and ICV
567        buf.extend_from_slice(&[0xAA, 0xBB, 0xCC, 0xDD]); // encrypted
568        buf.extend_from_slice(&0x12345678u32.to_le_bytes()); // ICV
569
570        let wep = Dot11WEP::new(0);
571        assert_eq!(wep.iv(&buf).unwrap(), [0x01, 0x02, 0x03]);
572        assert_eq!(wep.key_id(&buf).unwrap(), 1);
573        assert_eq!(wep.header_len(), 4);
574        assert_eq!(wep.trailer_len(), 4);
575        assert_eq!(wep.icv(&buf, buf.len()).unwrap(), 0x12345678);
576
577        let encrypted = wep.encrypted_data(&buf, buf.len()).unwrap();
578        assert_eq!(encrypted, &[0xAA, 0xBB, 0xCC, 0xDD]);
579    }
580
581    #[test]
582    fn test_wep_build() {
583        let header = Dot11WEP::build_header([0x11, 0x22, 0x33], 2);
584        assert_eq!(header, vec![0x11, 0x22, 0x33, 0x80]); // key_id=2 => bits 6-7 = 10 => 0x80
585
586        let wep = Dot11WEP::new(0);
587        assert_eq!(wep.iv(&header).unwrap(), [0x11, 0x22, 0x33]);
588        assert_eq!(wep.key_id(&header).unwrap(), 2);
589
590        let icv = Dot11WEP::build_icv(0xDEADBEEF);
591        assert_eq!(icv, 0xDEADBEEFu32.to_le_bytes().to_vec());
592    }
593
594    #[test]
595    fn test_wep_iv_u32() {
596        let buf = vec![0x01, 0x02, 0x03, 0x00];
597        let wep = Dot11WEP::new(0);
598        assert_eq!(wep.iv_u32(&buf).unwrap(), 0x010203);
599    }
600
601    #[test]
602    fn test_tkip_parse() {
603        // TKIP header with ExtIV: TSC1=0x00, WEPSeed=0x20, TSC0=0x01, KeyID/ExtIV=0x20
604        // TSC2-5: 0x02, 0x03, 0x04, 0x05
605        let buf = vec![
606            0x00, 0x20, 0x01, 0x20, // first 4 bytes
607            0x02, 0x03, 0x04, 0x05, // TSC2-5
608        ];
609
610        let tkip = Dot11TKIP::new(0);
611        assert!(tkip.ext_iv(&buf).unwrap());
612        assert_eq!(tkip.key_id(&buf).unwrap(), 0);
613        assert_eq!(tkip.tsc0(&buf).unwrap(), 0x01);
614        assert_eq!(tkip.tsc1(&buf).unwrap(), 0x00);
615        assert_eq!(tkip.wep_seed(&buf).unwrap(), 0x20);
616        assert_eq!(tkip.tsc_high(&buf).unwrap(), [0x02, 0x03, 0x04, 0x05]);
617        assert_eq!(tkip.header_len(&buf), 8);
618        assert_eq!(tkip.trailer_len(), 4);
619
620        // TSC = 0x01 | (0x00 << 8) | (0x02 << 16) | (0x03 << 24) | (0x04 << 32) | (0x05 << 40)
621        let tsc = tkip.tsc(&buf).unwrap();
622        assert_eq!(tsc & 0xFF, 0x01);
623        assert_eq!((tsc >> 8) & 0xFF, 0x00);
624        assert_eq!((tsc >> 16) & 0xFF, 0x02);
625        assert_eq!((tsc >> 24) & 0xFF, 0x03);
626        assert_eq!((tsc >> 32) & 0xFF, 0x04);
627        assert_eq!((tsc >> 40) & 0xFF, 0x05);
628    }
629
630    #[test]
631    fn test_tkip_build_roundtrip() {
632        let tsc: u64 = 0x050403020001; // TSC0=01, TSC1=00, TSC2=02, TSC3=03, TSC4=04, TSC5=05
633        let header = Dot11TKIP::build_header(tsc, 1);
634        assert_eq!(header.len(), 8);
635
636        let tkip = Dot11TKIP::new(0);
637        assert!(tkip.ext_iv(&header).unwrap());
638        assert_eq!(tkip.key_id(&header).unwrap(), 1);
639        assert_eq!(tkip.tsc(&header).unwrap(), tsc);
640    }
641
642    #[test]
643    fn test_ccmp_parse() {
644        // CCMP header: PN0=0x01, PN1=0x02, Rsvd=0x00, KeyID/ExtIV=0x20
645        // PN2-5: 0x03, 0x04, 0x05, 0x06
646        let buf = vec![
647            0x01, 0x02, 0x00, 0x20, // first 4 bytes
648            0x03, 0x04, 0x05, 0x06, // PN2-5
649        ];
650
651        let ccmp = Dot11CCMP::new(0);
652        assert_eq!(ccmp.pn0(&buf).unwrap(), 0x01);
653        assert_eq!(ccmp.pn1(&buf).unwrap(), 0x02);
654        assert_eq!(ccmp.reserved(&buf).unwrap(), 0x00);
655        assert!(ccmp.ext_iv(&buf).unwrap());
656        assert_eq!(ccmp.key_id(&buf).unwrap(), 0);
657        assert_eq!(ccmp.pn_high(&buf).unwrap(), [0x03, 0x04, 0x05, 0x06]);
658        assert_eq!(ccmp.header_len(), 8);
659        assert_eq!(ccmp.trailer_len(), 8);
660
661        let pn = ccmp.pn(&buf).unwrap();
662        assert_eq!(pn & 0xFF, 0x01);
663        assert_eq!((pn >> 8) & 0xFF, 0x02);
664        assert_eq!((pn >> 16) & 0xFF, 0x03);
665        assert_eq!((pn >> 24) & 0xFF, 0x04);
666        assert_eq!((pn >> 32) & 0xFF, 0x05);
667        assert_eq!((pn >> 40) & 0xFF, 0x06);
668    }
669
670    #[test]
671    fn test_ccmp_build_roundtrip() {
672        let pn: u64 = 0x060504030201;
673        let header = Dot11CCMP::build_header(pn, 2);
674        assert_eq!(header.len(), 8);
675
676        let ccmp = Dot11CCMP::new(0);
677        assert!(ccmp.ext_iv(&header).unwrap());
678        assert_eq!(ccmp.key_id(&header).unwrap(), 2);
679        assert_eq!(ccmp.pn(&header).unwrap(), pn);
680    }
681
682    #[test]
683    fn test_ccmp_mic() {
684        let mut buf = vec![0u8; 8]; // CCMP header
685        buf.extend_from_slice(&[0xAA, 0xBB, 0xCC, 0xDD]); // encrypted data
686        buf.extend_from_slice(&[0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08]); // MIC
687
688        let ccmp = Dot11CCMP::new(0);
689        let mic = ccmp.mic(&buf, buf.len()).unwrap();
690        assert_eq!(mic, [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08]);
691    }
692
693    #[test]
694    fn test_detect_security_type() {
695        // WEP: no ExtIV flag
696        let wep_buf = vec![0x01, 0x02, 0x03, 0x00];
697        assert_eq!(detect_security_type(&wep_buf, 0), Some(SecurityType::WEP));
698
699        // CCMP: ExtIV set, reserved byte = 0
700        let ccmp_buf = vec![0x01, 0x02, 0x00, 0x20];
701        assert_eq!(detect_security_type(&ccmp_buf, 0), Some(SecurityType::CCMP));
702
703        // TKIP: ExtIV set, byte 2 (WEP seed) non-zero
704        let tkip_buf = vec![0x01, 0x20, 0x03, 0x20];
705        assert_eq!(detect_security_type(&tkip_buf, 0), Some(SecurityType::TKIP));
706
707        // Too short
708        assert_eq!(detect_security_type(&[0x01, 0x02], 0), None);
709    }
710
711    #[test]
712    fn test_wep_validate() {
713        assert!(Dot11WEP::validate(&[0u8; 4], 0).is_ok());
714        assert!(Dot11WEP::validate(&[0u8; 3], 0).is_err());
715    }
716
717    #[test]
718    fn test_tkip_validate() {
719        assert!(Dot11TKIP::validate(&[0u8; 4], 0).is_ok());
720        assert!(Dot11TKIP::validate(&[0u8; 3], 0).is_err());
721    }
722
723    #[test]
724    fn test_ccmp_validate() {
725        assert!(Dot11CCMP::validate(&[0u8; 8], 0).is_ok());
726        assert!(Dot11CCMP::validate(&[0u8; 7], 0).is_err());
727    }
728}