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