Skip to main content

stackforge_core/layer/dot11/
ie.rs

1//! IEEE 802.11 Information Elements (IEs).
2//!
3//! Information elements are TLV (Tag-Length-Value) structures appended to
4//! management frame bodies (beacons, probe requests/responses, etc.).
5//!
6//! Each IE has a 1-byte ID, 1-byte length, and variable-length data.
7//! This module provides a generic `Dot11Elt` for arbitrary IEs, plus
8//! specialized parsers for common IEs (SSID, Rates, RSN, HT, VHT, etc.).
9
10use crate::layer::dot11::types;
11use crate::layer::field::FieldError;
12
13// ============================================================================
14// Dot11Elt — Generic Information Element
15// ============================================================================
16
17/// Generic 802.11 Information Element (TLV).
18///
19/// Layout:
20/// - ID (1 byte)
21/// - Length (1 byte)
22/// - Info (variable, `length` bytes)
23#[derive(Debug, Clone, PartialEq, Eq)]
24pub struct Dot11Elt {
25    /// Element ID.
26    pub id: u8,
27    /// Length of the info field.
28    pub len: u8,
29    /// Info/data bytes.
30    pub info: Vec<u8>,
31}
32
33impl Dot11Elt {
34    /// Create a new IE with the given ID and data.
35    pub fn new(id: u8, info: Vec<u8>) -> Self {
36        Self {
37            id,
38            len: info.len() as u8,
39            info,
40        }
41    }
42
43    /// Parse a single IE from a buffer at the given offset.
44    /// Returns the parsed IE and the number of bytes consumed.
45    pub fn parse(buf: &[u8], offset: usize) -> Result<(Self, usize), FieldError> {
46        if buf.len() < offset + 2 {
47            return Err(FieldError::BufferTooShort {
48                offset,
49                need: 2,
50                have: buf.len().saturating_sub(offset),
51            });
52        }
53        let id = buf[offset];
54        let len = buf[offset + 1] as usize;
55        if buf.len() < offset + 2 + len {
56            return Err(FieldError::BufferTooShort {
57                offset: offset + 2,
58                need: len,
59                have: buf.len().saturating_sub(offset + 2),
60            });
61        }
62        let info = buf[offset + 2..offset + 2 + len].to_vec();
63        Ok((
64            Self {
65                id,
66                len: len as u8,
67                info,
68            },
69            2 + len,
70        ))
71    }
72
73    /// Parse all IEs from a buffer starting at the given offset.
74    /// Stops on error (truncated IE) and returns what was parsed so far.
75    pub fn parse_all(buf: &[u8], mut offset: usize) -> Vec<Self> {
76        let mut elements = Vec::new();
77        while offset < buf.len() {
78            match Self::parse(buf, offset) {
79                Ok((elt, consumed)) => {
80                    offset += consumed;
81                    elements.push(elt);
82                }
83                Err(_) => break,
84            }
85        }
86        elements
87    }
88
89    /// Build this IE to wire format (ID + Length + Info).
90    pub fn build(&self) -> Vec<u8> {
91        let mut buf = Vec::with_capacity(2 + self.info.len());
92        buf.push(self.id);
93        buf.push(self.len);
94        buf.extend_from_slice(&self.info);
95        buf
96    }
97
98    /// Build a chain of IEs to wire format.
99    pub fn build_chain(elements: &[Dot11Elt]) -> Vec<u8> {
100        let total: usize = elements.iter().map(|e| 2 + e.info.len()).sum();
101        let mut buf = Vec::with_capacity(total);
102        for elt in elements {
103            buf.extend(elt.build());
104        }
105        buf
106    }
107
108    /// Create an SSID element (ID=0).
109    pub fn ssid(name: &str) -> Self {
110        Self::new(types::ie_id::SSID, name.as_bytes().to_vec())
111    }
112
113    /// Create a hidden/broadcast SSID element (empty SSID).
114    pub fn ssid_hidden() -> Self {
115        Self::new(types::ie_id::SSID, Vec::new())
116    }
117
118    /// Check if this is an SSID element.
119    pub fn is_ssid(&self) -> bool {
120        self.id == types::ie_id::SSID
121    }
122
123    /// Get SSID as string (if this is an SSID element).
124    pub fn ssid_str(&self) -> Option<String> {
125        if self.id == types::ie_id::SSID {
126            Some(String::from_utf8_lossy(&self.info).into_owned())
127        } else {
128            None
129        }
130    }
131
132    /// Get the IE name from the types module.
133    pub fn name(&self) -> &'static str {
134        types::ie_id::name(self.id)
135    }
136
137    /// Total wire length of this IE (2 + data length).
138    pub fn wire_len(&self) -> usize {
139        2 + self.info.len()
140    }
141}
142
143// ============================================================================
144// Dot11EltSSID — SSID (ID=0)
145// ============================================================================
146
147/// SSID Information Element (ID=0).
148#[derive(Debug, Clone, PartialEq, Eq)]
149pub struct Dot11EltSSID {
150    /// SSID string (may be empty for broadcast/hidden).
151    pub ssid: String,
152}
153
154impl Dot11EltSSID {
155    /// Create a new SSID IE.
156    pub fn new(ssid: &str) -> Self {
157        Self {
158            ssid: ssid.to_string(),
159        }
160    }
161
162    /// Parse from a generic Dot11Elt.
163    pub fn parse(elt: &Dot11Elt) -> Option<Self> {
164        if elt.id != types::ie_id::SSID {
165            return None;
166        }
167        Some(Self {
168            ssid: String::from_utf8_lossy(&elt.info).into_owned(),
169        })
170    }
171
172    /// Build as a Dot11Elt.
173    pub fn build(&self) -> Dot11Elt {
174        Dot11Elt::new(types::ie_id::SSID, self.ssid.as_bytes().to_vec())
175    }
176
177    /// Check if this is a hidden/broadcast SSID.
178    pub fn is_hidden(&self) -> bool {
179        self.ssid.is_empty()
180    }
181}
182
183// ============================================================================
184// Dot11EltRates — Supported Rates (ID=1)
185// ============================================================================
186
187/// Supported Rates Information Element (ID=1).
188///
189/// Each rate byte: bit 7 = basic rate flag, bits 0-6 = rate in 0.5 Mbps units.
190#[derive(Debug, Clone, PartialEq, Eq)]
191pub struct Dot11EltRates {
192    /// Raw rate bytes.
193    pub rates: Vec<u8>,
194}
195
196impl Dot11EltRates {
197    /// Create a new Rates IE.
198    pub fn new(rates: Vec<u8>) -> Self {
199        Self { rates }
200    }
201
202    /// Parse from a generic Dot11Elt.
203    pub fn parse(elt: &Dot11Elt) -> Option<Self> {
204        if elt.id != types::ie_id::RATES {
205            return None;
206        }
207        Some(Self {
208            rates: elt.info.clone(),
209        })
210    }
211
212    /// Build as a Dot11Elt.
213    pub fn build(&self) -> Dot11Elt {
214        Dot11Elt::new(types::ie_id::RATES, self.rates.clone())
215    }
216
217    /// Get the rate in Mbps for a given rate byte.
218    pub fn rate_mbps(rate_byte: u8) -> f32 {
219        (rate_byte & 0x7F) as f32 * 0.5
220    }
221
222    /// Check if a rate byte indicates a basic rate.
223    pub fn is_basic(rate_byte: u8) -> bool {
224        rate_byte & 0x80 != 0
225    }
226
227    /// Get all rates in Mbps.
228    pub fn rates_mbps(&self) -> Vec<f32> {
229        self.rates.iter().map(|&r| Self::rate_mbps(r)).collect()
230    }
231}
232
233// ============================================================================
234// Dot11EltDSSSet — DS Parameter Set (ID=3)
235// ============================================================================
236
237/// DS Parameter Set Information Element (ID=3).
238///
239/// Contains the current channel number.
240#[derive(Debug, Clone, PartialEq, Eq)]
241pub struct Dot11EltDSSSet {
242    /// Current channel number.
243    pub channel: u8,
244}
245
246impl Dot11EltDSSSet {
247    /// Create a new DS Parameter Set IE.
248    pub fn new(channel: u8) -> Self {
249        Self { channel }
250    }
251
252    /// Parse from a generic Dot11Elt.
253    pub fn parse(elt: &Dot11Elt) -> Option<Self> {
254        if elt.id != types::ie_id::DS_PARAMETER_SET || elt.info.is_empty() {
255            return None;
256        }
257        Some(Self {
258            channel: elt.info[0],
259        })
260    }
261
262    /// Build as a Dot11Elt.
263    pub fn build(&self) -> Dot11Elt {
264        Dot11Elt::new(types::ie_id::DS_PARAMETER_SET, vec![self.channel])
265    }
266}
267
268// ============================================================================
269// Dot11EltTIM — Traffic Indication Map (ID=5)
270// ============================================================================
271
272/// Traffic Indication Map (TIM) Information Element (ID=5).
273#[derive(Debug, Clone, PartialEq, Eq)]
274pub struct Dot11EltTIM {
275    /// DTIM Count.
276    pub dtim_count: u8,
277    /// DTIM Period.
278    pub dtim_period: u8,
279    /// Bitmap Control.
280    pub bitmap_control: u8,
281    /// Partial Virtual Bitmap.
282    pub bitmap: Vec<u8>,
283}
284
285impl Dot11EltTIM {
286    /// Create a new TIM IE.
287    pub fn new(dtim_count: u8, dtim_period: u8, bitmap_control: u8, bitmap: Vec<u8>) -> Self {
288        Self {
289            dtim_count,
290            dtim_period,
291            bitmap_control,
292            bitmap,
293        }
294    }
295
296    /// Parse from a generic Dot11Elt.
297    pub fn parse(elt: &Dot11Elt) -> Option<Self> {
298        if elt.id != types::ie_id::TIM || elt.info.len() < 3 {
299            return None;
300        }
301        Some(Self {
302            dtim_count: elt.info[0],
303            dtim_period: elt.info[1],
304            bitmap_control: elt.info[2],
305            bitmap: elt.info[3..].to_vec(),
306        })
307    }
308
309    /// Build as a Dot11Elt.
310    pub fn build(&self) -> Dot11Elt {
311        let mut info = Vec::with_capacity(3 + self.bitmap.len());
312        info.push(self.dtim_count);
313        info.push(self.dtim_period);
314        info.push(self.bitmap_control);
315        info.extend_from_slice(&self.bitmap);
316        Dot11Elt::new(types::ie_id::TIM, info)
317    }
318}
319
320// ============================================================================
321// Dot11EltCountry — Country (ID=7)
322// ============================================================================
323
324/// A regulatory triplet within a Country IE.
325#[derive(Debug, Clone, PartialEq, Eq)]
326pub struct CountryTriplet {
327    /// First channel number.
328    pub first_channel: u8,
329    /// Number of channels.
330    pub num_channels: u8,
331    /// Maximum transmit power (dBm).
332    pub max_power: u8,
333}
334
335/// Country Information Element (ID=7).
336#[derive(Debug, Clone, PartialEq, Eq)]
337pub struct Dot11EltCountry {
338    /// Country string (3 bytes, e.g., "US\x20").
339    pub country: [u8; 3],
340    /// Regulatory triplets.
341    pub triplets: Vec<CountryTriplet>,
342}
343
344impl Dot11EltCountry {
345    /// Create a new Country IE.
346    pub fn new(country: [u8; 3], triplets: Vec<CountryTriplet>) -> Self {
347        Self { country, triplets }
348    }
349
350    /// Parse from a generic Dot11Elt.
351    pub fn parse(elt: &Dot11Elt) -> Option<Self> {
352        if elt.id != types::ie_id::COUNTRY || elt.info.len() < 3 {
353            return None;
354        }
355        let mut country = [0u8; 3];
356        country.copy_from_slice(&elt.info[0..3]);
357
358        let mut triplets = Vec::new();
359        let mut pos = 3;
360        while pos + 3 <= elt.info.len() {
361            triplets.push(CountryTriplet {
362                first_channel: elt.info[pos],
363                num_channels: elt.info[pos + 1],
364                max_power: elt.info[pos + 2],
365            });
366            pos += 3;
367        }
368
369        Some(Self { country, triplets })
370    }
371
372    /// Build as a Dot11Elt.
373    pub fn build(&self) -> Dot11Elt {
374        let mut info = Vec::with_capacity(3 + self.triplets.len() * 3);
375        info.extend_from_slice(&self.country);
376        for t in &self.triplets {
377            info.push(t.first_channel);
378            info.push(t.num_channels);
379            info.push(t.max_power);
380        }
381        Dot11Elt::new(types::ie_id::COUNTRY, info)
382    }
383
384    /// Get the country code as a string.
385    pub fn country_str(&self) -> String {
386        String::from_utf8_lossy(&self.country).into_owned()
387    }
388}
389
390// ============================================================================
391// Dot11EltCSA — Channel Switch Announcement (ID=37)
392// ============================================================================
393
394/// Channel Switch Announcement Information Element (ID=37).
395#[derive(Debug, Clone, PartialEq, Eq)]
396pub struct Dot11EltCSA {
397    /// Channel switch mode (0 = no restriction, 1 = stop transmitting).
398    pub mode: u8,
399    /// New channel number.
400    pub new_channel: u8,
401    /// Channel switch count (number of TBTTs until switch).
402    pub count: u8,
403}
404
405impl Dot11EltCSA {
406    /// Create a new CSA IE.
407    pub fn new(mode: u8, new_channel: u8, count: u8) -> Self {
408        Self {
409            mode,
410            new_channel,
411            count,
412        }
413    }
414
415    /// Parse from a generic Dot11Elt.
416    pub fn parse(elt: &Dot11Elt) -> Option<Self> {
417        if elt.id != types::ie_id::CHANNEL_SWITCH_ANNOUNCEMENT || elt.info.len() < 3 {
418            return None;
419        }
420        Some(Self {
421            mode: elt.info[0],
422            new_channel: elt.info[1],
423            count: elt.info[2],
424        })
425    }
426
427    /// Build as a Dot11Elt.
428    pub fn build(&self) -> Dot11Elt {
429        Dot11Elt::new(
430            types::ie_id::CHANNEL_SWITCH_ANNOUNCEMENT,
431            vec![self.mode, self.new_channel, self.count],
432        )
433    }
434}
435
436// ============================================================================
437// Dot11EltHTCapabilities — HT Capabilities (ID=45)
438// ============================================================================
439
440/// HT Capabilities Information Element (ID=45).
441///
442/// Contains 802.11n (HT) capability information.
443#[derive(Debug, Clone, PartialEq, Eq)]
444pub struct Dot11EltHTCapabilities {
445    /// HT Capabilities Info (2 bytes, little-endian).
446    pub ht_cap_info: u16,
447    /// A-MPDU Parameters (1 byte).
448    pub ampdu_params: u8,
449    /// Supported MCS Set (16 bytes).
450    pub mcs_set: [u8; 16],
451    /// HT Extended Capabilities (2 bytes, little-endian).
452    pub ht_ext_cap: u16,
453    /// Transmit Beamforming Capabilities (4 bytes, little-endian).
454    pub txbf_cap: u32,
455    /// ASEL Capabilities (1 byte).
456    pub asel_cap: u8,
457}
458
459/// HT Capabilities IE data length.
460pub const HT_CAP_LEN: usize = 26;
461
462impl Dot11EltHTCapabilities {
463    /// Parse from a generic Dot11Elt.
464    pub fn parse(elt: &Dot11Elt) -> Option<Self> {
465        if elt.id != types::ie_id::HT_CAPABILITIES || elt.info.len() < HT_CAP_LEN {
466            return None;
467        }
468        let d = &elt.info;
469        let ht_cap_info = u16::from_le_bytes([d[0], d[1]]);
470        let ampdu_params = d[2];
471        let mut mcs_set = [0u8; 16];
472        mcs_set.copy_from_slice(&d[3..19]);
473        let ht_ext_cap = u16::from_le_bytes([d[19], d[20]]);
474        let txbf_cap = u32::from_le_bytes([d[21], d[22], d[23], d[24]]);
475        let asel_cap = d[25];
476
477        Some(Self {
478            ht_cap_info,
479            ampdu_params,
480            mcs_set,
481            ht_ext_cap,
482            txbf_cap,
483            asel_cap,
484        })
485    }
486
487    /// Build as a Dot11Elt.
488    pub fn build(&self) -> Dot11Elt {
489        let mut info = Vec::with_capacity(HT_CAP_LEN);
490        info.extend_from_slice(&self.ht_cap_info.to_le_bytes());
491        info.push(self.ampdu_params);
492        info.extend_from_slice(&self.mcs_set);
493        info.extend_from_slice(&self.ht_ext_cap.to_le_bytes());
494        info.extend_from_slice(&self.txbf_cap.to_le_bytes());
495        info.push(self.asel_cap);
496        Dot11Elt::new(types::ie_id::HT_CAPABILITIES, info)
497    }
498
499    /// Check LDPC Coding Capability (bit 0 of HT Cap Info).
500    pub fn ldpc(&self) -> bool {
501        self.ht_cap_info & 0x0001 != 0
502    }
503
504    /// Channel Width Set (bit 1): 0 = 20 MHz only, 1 = 20/40 MHz.
505    pub fn channel_width_set(&self) -> bool {
506        self.ht_cap_info & 0x0002 != 0
507    }
508
509    /// Short GI for 20 MHz (bit 5).
510    pub fn short_gi_20(&self) -> bool {
511        self.ht_cap_info & 0x0020 != 0
512    }
513
514    /// Short GI for 40 MHz (bit 6).
515    pub fn short_gi_40(&self) -> bool {
516        self.ht_cap_info & 0x0040 != 0
517    }
518}
519
520// ============================================================================
521// Dot11EltHTInfo — HT Information (ID=61)
522// ============================================================================
523
524/// HT Information (Operation) Element (ID=61).
525#[derive(Debug, Clone, PartialEq, Eq)]
526pub struct Dot11EltHTInfo {
527    /// Primary channel.
528    pub primary_channel: u8,
529    /// HT Operation Information (5 bytes).
530    pub ht_info: [u8; 5],
531    /// Basic MCS Set (16 bytes).
532    pub basic_mcs_set: [u8; 16],
533}
534
535/// HT Info IE data length.
536pub const HT_INFO_LEN: usize = 22;
537
538impl Dot11EltHTInfo {
539    /// Parse from a generic Dot11Elt.
540    pub fn parse(elt: &Dot11Elt) -> Option<Self> {
541        if elt.id != types::ie_id::HT_INFORMATION || elt.info.len() < HT_INFO_LEN {
542            return None;
543        }
544        let d = &elt.info;
545        let primary_channel = d[0];
546        let mut ht_info = [0u8; 5];
547        ht_info.copy_from_slice(&d[1..6]);
548        let mut basic_mcs_set = [0u8; 16];
549        basic_mcs_set.copy_from_slice(&d[6..22]);
550
551        Some(Self {
552            primary_channel,
553            ht_info,
554            basic_mcs_set,
555        })
556    }
557
558    /// Build as a Dot11Elt.
559    pub fn build(&self) -> Dot11Elt {
560        let mut info = Vec::with_capacity(HT_INFO_LEN);
561        info.push(self.primary_channel);
562        info.extend_from_slice(&self.ht_info);
563        info.extend_from_slice(&self.basic_mcs_set);
564        Dot11Elt::new(types::ie_id::HT_INFORMATION, info)
565    }
566
567    /// Secondary channel offset (bits 0-1 of ht_info byte 0).
568    /// 0 = no secondary, 1 = above, 3 = below.
569    pub fn secondary_channel_offset(&self) -> u8 {
570        self.ht_info[0] & 0x03
571    }
572
573    /// STA Channel Width (bit 2 of ht_info byte 0).
574    pub fn sta_channel_width(&self) -> bool {
575        self.ht_info[0] & 0x04 != 0
576    }
577}
578
579// ============================================================================
580// Dot11EltVHTCapabilities — VHT Capabilities (ID=191)
581// ============================================================================
582
583/// VHT Capabilities Information Element (ID=191).
584///
585/// Contains 802.11ac (VHT) capability information.
586#[derive(Debug, Clone, PartialEq, Eq)]
587pub struct Dot11EltVHTCapabilities {
588    /// VHT Capabilities Info (4 bytes, little-endian).
589    pub vht_cap_info: u32,
590    /// Supported VHT-MCS and NSS Set (8 bytes).
591    pub mcs_nss_set: [u8; 8],
592}
593
594/// VHT Capabilities IE data length.
595pub const VHT_CAP_LEN: usize = 12;
596
597impl Dot11EltVHTCapabilities {
598    /// Parse from a generic Dot11Elt.
599    pub fn parse(elt: &Dot11Elt) -> Option<Self> {
600        if elt.id != types::ie_id::VHT_CAPABILITIES || elt.info.len() < VHT_CAP_LEN {
601            return None;
602        }
603        let d = &elt.info;
604        let vht_cap_info = u32::from_le_bytes([d[0], d[1], d[2], d[3]]);
605        let mut mcs_nss_set = [0u8; 8];
606        mcs_nss_set.copy_from_slice(&d[4..12]);
607
608        Some(Self {
609            vht_cap_info,
610            mcs_nss_set,
611        })
612    }
613
614    /// Build as a Dot11Elt.
615    pub fn build(&self) -> Dot11Elt {
616        let mut info = Vec::with_capacity(VHT_CAP_LEN);
617        info.extend_from_slice(&self.vht_cap_info.to_le_bytes());
618        info.extend_from_slice(&self.mcs_nss_set);
619        Dot11Elt::new(types::ie_id::VHT_CAPABILITIES, info)
620    }
621
622    /// Max MPDU Length (bits 0-1 of VHT Cap Info).
623    /// 0 = 3895, 1 = 7991, 2 = 11454.
624    pub fn max_mpdu_length(&self) -> u8 {
625        (self.vht_cap_info & 0x03) as u8
626    }
627
628    /// Supported Channel Width Set (bits 2-3).
629    pub fn supported_channel_width(&self) -> u8 {
630        ((self.vht_cap_info >> 2) & 0x03) as u8
631    }
632
633    /// Short GI for 80 MHz (bit 5).
634    pub fn short_gi_80(&self) -> bool {
635        self.vht_cap_info & 0x0020 != 0
636    }
637
638    /// Short GI for 160/80+80 MHz (bit 6).
639    pub fn short_gi_160(&self) -> bool {
640        self.vht_cap_info & 0x0040 != 0
641    }
642}
643
644// ============================================================================
645// Dot11EltVHTOperation — VHT Operation (ID=192)
646// ============================================================================
647
648/// VHT Operation Information Element (ID=192).
649#[derive(Debug, Clone, PartialEq, Eq)]
650pub struct Dot11EltVHTOperation {
651    /// Channel Width (1 byte): 0=20/40, 1=80, 2=160, 3=80+80.
652    pub channel_width: u8,
653    /// Channel Center Frequency Segment 0.
654    pub center_freq_seg0: u8,
655    /// Channel Center Frequency Segment 1.
656    pub center_freq_seg1: u8,
657    /// Basic VHT-MCS and NSS Set (2 bytes, little-endian).
658    pub basic_mcs_nss: u16,
659}
660
661/// VHT Operation IE data length.
662pub const VHT_OP_LEN: usize = 5;
663
664impl Dot11EltVHTOperation {
665    /// Parse from a generic Dot11Elt.
666    pub fn parse(elt: &Dot11Elt) -> Option<Self> {
667        if elt.id != types::ie_id::VHT_OPERATION || elt.info.len() < VHT_OP_LEN {
668            return None;
669        }
670        let d = &elt.info;
671        Some(Self {
672            channel_width: d[0],
673            center_freq_seg0: d[1],
674            center_freq_seg1: d[2],
675            basic_mcs_nss: u16::from_le_bytes([d[3], d[4]]),
676        })
677    }
678
679    /// Build as a Dot11Elt.
680    pub fn build(&self) -> Dot11Elt {
681        let mut info = Vec::with_capacity(VHT_OP_LEN);
682        info.push(self.channel_width);
683        info.push(self.center_freq_seg0);
684        info.push(self.center_freq_seg1);
685        info.extend_from_slice(&self.basic_mcs_nss.to_le_bytes());
686        Dot11Elt::new(types::ie_id::VHT_OPERATION, info)
687    }
688}
689
690// ============================================================================
691// Dot11EltRSN — RSN (Robust Security Network) (ID=48)
692// ============================================================================
693
694/// A cipher suite descriptor (OUI + type).
695#[derive(Debug, Clone, PartialEq, Eq)]
696pub struct CipherSuite {
697    /// OUI (3 bytes).
698    pub oui: [u8; 3],
699    /// Suite type.
700    pub suite_type: u8,
701}
702
703impl CipherSuite {
704    /// Create from raw 4-byte data.
705    pub fn from_bytes(data: &[u8; 4]) -> Self {
706        Self {
707            oui: [data[0], data[1], data[2]],
708            suite_type: data[3],
709        }
710    }
711
712    /// Convert to raw 4-byte data.
713    pub fn to_bytes(&self) -> [u8; 4] {
714        [self.oui[0], self.oui[1], self.oui[2], self.suite_type]
715    }
716
717    /// Get the cipher suite name.
718    pub fn name(&self) -> &'static str {
719        if self.oui == [0x00, 0x0F, 0xAC] {
720            types::cipher_suite::name(self.suite_type)
721        } else {
722            "Vendor-Specific"
723        }
724    }
725}
726
727/// An AKM (Authentication and Key Management) suite descriptor.
728#[derive(Debug, Clone, PartialEq, Eq)]
729pub struct AkmSuite {
730    /// OUI (3 bytes).
731    pub oui: [u8; 3],
732    /// Suite type.
733    pub suite_type: u8,
734}
735
736impl AkmSuite {
737    /// Create from raw 4-byte data.
738    pub fn from_bytes(data: &[u8; 4]) -> Self {
739        Self {
740            oui: [data[0], data[1], data[2]],
741            suite_type: data[3],
742        }
743    }
744
745    /// Convert to raw 4-byte data.
746    pub fn to_bytes(&self) -> [u8; 4] {
747        [self.oui[0], self.oui[1], self.oui[2], self.suite_type]
748    }
749
750    /// Get the AKM suite name.
751    pub fn name(&self) -> &'static str {
752        if self.oui == [0x00, 0x0F, 0xAC] {
753            types::akm_suite::name(self.suite_type)
754        } else {
755            "Vendor-Specific"
756        }
757    }
758}
759
760/// Parsed RSN (WPA2/WPA3) information.
761#[derive(Debug, Clone, PartialEq, Eq)]
762pub struct RsnInfo {
763    /// RSN version (should be 1).
764    pub version: u16,
765    /// Group data cipher suite.
766    pub group_cipher: CipherSuite,
767    /// Pairwise cipher suites.
768    pub pairwise_ciphers: Vec<CipherSuite>,
769    /// AKM suites.
770    pub akm_suites: Vec<AkmSuite>,
771    /// RSN capabilities (2 bytes, little-endian).
772    pub rsn_capabilities: u16,
773    /// PMKID count.
774    pub pmkid_count: u16,
775    /// PMKIDs (16 bytes each).
776    pub pmkids: Vec<[u8; 16]>,
777    /// Group management cipher suite (optional).
778    pub group_mgmt_cipher: Option<CipherSuite>,
779}
780
781/// RSN (Robust Security Network) Information Element (ID=48).
782#[derive(Debug, Clone, PartialEq, Eq)]
783pub struct Dot11EltRSN {
784    /// Parsed RSN information.
785    pub info: RsnInfo,
786}
787
788impl Dot11EltRSN {
789    /// Parse from a generic Dot11Elt.
790    pub fn parse(elt: &Dot11Elt) -> Option<Self> {
791        if elt.id != types::ie_id::RSN || elt.info.len() < 2 {
792            return None;
793        }
794        let data = &elt.info;
795        let mut offset = 0;
796
797        // Version
798        if offset + 2 > data.len() {
799            return None;
800        }
801        let version = u16::from_le_bytes([data[offset], data[offset + 1]]);
802        offset += 2;
803
804        // Group Data Cipher Suite
805        if offset + 4 > data.len() {
806            return None;
807        }
808        let mut gc = [0u8; 4];
809        gc.copy_from_slice(&data[offset..offset + 4]);
810        let group_cipher = CipherSuite::from_bytes(&gc);
811        offset += 4;
812
813        // Pairwise Cipher Suite Count
814        let pairwise_count = if offset + 2 <= data.len() {
815            let c = u16::from_le_bytes([data[offset], data[offset + 1]]) as usize;
816            offset += 2;
817            c
818        } else {
819            0
820        };
821
822        // Pairwise Cipher Suites
823        let mut pairwise_ciphers = Vec::with_capacity(pairwise_count);
824        for _ in 0..pairwise_count {
825            if offset + 4 > data.len() {
826                break;
827            }
828            let mut suite = [0u8; 4];
829            suite.copy_from_slice(&data[offset..offset + 4]);
830            pairwise_ciphers.push(CipherSuite::from_bytes(&suite));
831            offset += 4;
832        }
833
834        // AKM Suite Count
835        let akm_count = if offset + 2 <= data.len() {
836            let c = u16::from_le_bytes([data[offset], data[offset + 1]]) as usize;
837            offset += 2;
838            c
839        } else {
840            0
841        };
842
843        // AKM Suites
844        let mut akm_suites = Vec::with_capacity(akm_count);
845        for _ in 0..akm_count {
846            if offset + 4 > data.len() {
847                break;
848            }
849            let mut suite = [0u8; 4];
850            suite.copy_from_slice(&data[offset..offset + 4]);
851            akm_suites.push(AkmSuite::from_bytes(&suite));
852            offset += 4;
853        }
854
855        // RSN Capabilities
856        let rsn_capabilities = if offset + 2 <= data.len() {
857            let c = u16::from_le_bytes([data[offset], data[offset + 1]]);
858            offset += 2;
859            c
860        } else {
861            0
862        };
863
864        // PMKID Count
865        let pmkid_count = if offset + 2 <= data.len() {
866            let c = u16::from_le_bytes([data[offset], data[offset + 1]]);
867            offset += 2;
868            c
869        } else {
870            0
871        };
872
873        // PMKIDs
874        let mut pmkids = Vec::new();
875        for _ in 0..pmkid_count {
876            if offset + 16 > data.len() {
877                break;
878            }
879            let mut pmkid = [0u8; 16];
880            pmkid.copy_from_slice(&data[offset..offset + 16]);
881            pmkids.push(pmkid);
882            offset += 16;
883        }
884
885        // Group Management Cipher Suite (optional)
886        let group_mgmt_cipher = if offset + 4 <= data.len() {
887            let mut suite = [0u8; 4];
888            suite.copy_from_slice(&data[offset..offset + 4]);
889            Some(CipherSuite::from_bytes(&suite))
890        } else {
891            None
892        };
893
894        Some(Self {
895            info: RsnInfo {
896                version,
897                group_cipher,
898                pairwise_ciphers,
899                akm_suites,
900                rsn_capabilities,
901                pmkid_count,
902                pmkids,
903                group_mgmt_cipher,
904            },
905        })
906    }
907
908    /// Build as a Dot11Elt.
909    pub fn build(&self) -> Dot11Elt {
910        let rsn = &self.info;
911        let mut data = Vec::new();
912
913        // Version
914        data.extend_from_slice(&rsn.version.to_le_bytes());
915
916        // Group Data Cipher Suite
917        data.extend_from_slice(&rsn.group_cipher.to_bytes());
918
919        // Pairwise Cipher Suite Count + Suites
920        data.extend_from_slice(&(rsn.pairwise_ciphers.len() as u16).to_le_bytes());
921        for cs in &rsn.pairwise_ciphers {
922            data.extend_from_slice(&cs.to_bytes());
923        }
924
925        // AKM Suite Count + Suites
926        data.extend_from_slice(&(rsn.akm_suites.len() as u16).to_le_bytes());
927        for akm in &rsn.akm_suites {
928            data.extend_from_slice(&akm.to_bytes());
929        }
930
931        // RSN Capabilities
932        data.extend_from_slice(&rsn.rsn_capabilities.to_le_bytes());
933
934        // PMKIDs (if any)
935        if !rsn.pmkids.is_empty() {
936            data.extend_from_slice(&(rsn.pmkids.len() as u16).to_le_bytes());
937            for pmkid in &rsn.pmkids {
938                data.extend_from_slice(pmkid);
939            }
940        }
941
942        // Group Management Cipher Suite (if present)
943        if let Some(ref gmc) = rsn.group_mgmt_cipher {
944            // If no PMKIDs were written, we need to write PMKID count = 0
945            if rsn.pmkids.is_empty() {
946                data.extend_from_slice(&0u16.to_le_bytes());
947            }
948            data.extend_from_slice(&gmc.to_bytes());
949        }
950
951        Dot11Elt::new(types::ie_id::RSN, data)
952    }
953
954    /// Check if pre-authentication is supported (bit 0 of RSN capabilities).
955    pub fn pre_auth(&self) -> bool {
956        self.info.rsn_capabilities & 0x0001 != 0
957    }
958
959    /// Check if no pairwise is set (bit 1 of RSN capabilities).
960    pub fn no_pairwise(&self) -> bool {
961        self.info.rsn_capabilities & 0x0002 != 0
962    }
963
964    /// PTKSA Replay Counter (bits 2-3 of RSN capabilities).
965    pub fn ptksa_replay_counter(&self) -> u8 {
966        ((self.info.rsn_capabilities >> 2) & 0x03) as u8
967    }
968
969    /// GTKSA Replay Counter (bits 4-5 of RSN capabilities).
970    pub fn gtksa_replay_counter(&self) -> u8 {
971        ((self.info.rsn_capabilities >> 4) & 0x03) as u8
972    }
973
974    /// Management Frame Protection Required (bit 6).
975    pub fn mfp_required(&self) -> bool {
976        self.info.rsn_capabilities & 0x0040 != 0
977    }
978
979    /// Management Frame Protection Capable (bit 7).
980    pub fn mfp_capable(&self) -> bool {
981        self.info.rsn_capabilities & 0x0080 != 0
982    }
983}
984
985// ============================================================================
986// Dot11EltVendorSpecific — Vendor Specific (ID=221)
987// ============================================================================
988
989/// Vendor Specific Information Element (ID=221).
990#[derive(Debug, Clone, PartialEq, Eq)]
991pub struct Dot11EltVendorSpecific {
992    /// OUI (3 bytes).
993    pub oui: [u8; 3],
994    /// Vendor-specific data (after OUI).
995    pub data: Vec<u8>,
996}
997
998impl Dot11EltVendorSpecific {
999    /// Create a new Vendor Specific IE.
1000    pub fn new(oui: [u8; 3], data: Vec<u8>) -> Self {
1001        Self { oui, data }
1002    }
1003
1004    /// Parse from a generic Dot11Elt.
1005    pub fn parse(elt: &Dot11Elt) -> Option<Self> {
1006        if elt.id != types::ie_id::VENDOR_SPECIFIC || elt.info.len() < 3 {
1007            return None;
1008        }
1009        let mut oui = [0u8; 3];
1010        oui.copy_from_slice(&elt.info[0..3]);
1011        Some(Self {
1012            oui,
1013            data: elt.info[3..].to_vec(),
1014        })
1015    }
1016
1017    /// Build as a Dot11Elt.
1018    pub fn build(&self) -> Dot11Elt {
1019        let mut info = Vec::with_capacity(3 + self.data.len());
1020        info.extend_from_slice(&self.oui);
1021        info.extend_from_slice(&self.data);
1022        Dot11Elt::new(types::ie_id::VENDOR_SPECIFIC, info)
1023    }
1024
1025    /// Check if this is a Microsoft WPA IE.
1026    pub fn is_wpa(&self) -> bool {
1027        self.oui == types::MICROSOFT_WPA_OUI
1028            && !self.data.is_empty()
1029            && self.data[0] == types::MICROSOFT_WPA_TYPE
1030    }
1031
1032    /// Check if this is a WMM/WME IE (Microsoft OUI, type 2).
1033    pub fn is_wmm(&self) -> bool {
1034        self.oui == types::MICROSOFT_WPA_OUI && !self.data.is_empty() && self.data[0] == 0x02
1035    }
1036}
1037
1038// ============================================================================
1039// Dot11EltMicrosoftWPA — Microsoft WPA (Vendor Specific, OUI=00:50:F2, Type=1)
1040// ============================================================================
1041
1042/// Microsoft WPA Information Element (WPA1).
1043///
1044/// This is a Vendor Specific IE (ID=221) with Microsoft OUI and type=1.
1045/// The structure is similar to RSN but with WPA OUI (00:50:F2).
1046#[derive(Debug, Clone, PartialEq, Eq)]
1047pub struct Dot11EltMicrosoftWPA {
1048    /// WPA version (should be 1).
1049    pub version: u16,
1050    /// Group cipher suite.
1051    pub group_cipher: CipherSuite,
1052    /// Pairwise cipher suites.
1053    pub pairwise_ciphers: Vec<CipherSuite>,
1054    /// AKM suites.
1055    pub akm_suites: Vec<AkmSuite>,
1056}
1057
1058impl Dot11EltMicrosoftWPA {
1059    /// Parse from a Vendor Specific IE.
1060    pub fn parse(vs: &Dot11EltVendorSpecific) -> Option<Self> {
1061        if !vs.is_wpa() {
1062            return None;
1063        }
1064        // data[0] = type (1), data[1..] = WPA IE body
1065        let data = &vs.data;
1066        if data.len() < 7 {
1067            // type(1) + version(2) + group cipher(4) = 7 minimum
1068            return None;
1069        }
1070        let mut offset = 1; // skip type byte
1071
1072        let version = u16::from_le_bytes([data[offset], data[offset + 1]]);
1073        offset += 2;
1074
1075        if offset + 4 > data.len() {
1076            return None;
1077        }
1078        let mut gc = [0u8; 4];
1079        gc.copy_from_slice(&data[offset..offset + 4]);
1080        let group_cipher = CipherSuite::from_bytes(&gc);
1081        offset += 4;
1082
1083        // Pairwise ciphers
1084        let pw_count = if offset + 2 <= data.len() {
1085            let c = u16::from_le_bytes([data[offset], data[offset + 1]]) as usize;
1086            offset += 2;
1087            c
1088        } else {
1089            0
1090        };
1091
1092        let mut pairwise_ciphers = Vec::with_capacity(pw_count);
1093        for _ in 0..pw_count {
1094            if offset + 4 > data.len() {
1095                break;
1096            }
1097            let mut suite = [0u8; 4];
1098            suite.copy_from_slice(&data[offset..offset + 4]);
1099            pairwise_ciphers.push(CipherSuite::from_bytes(&suite));
1100            offset += 4;
1101        }
1102
1103        // AKM suites
1104        let akm_count = if offset + 2 <= data.len() {
1105            let c = u16::from_le_bytes([data[offset], data[offset + 1]]) as usize;
1106            offset += 2;
1107            c
1108        } else {
1109            0
1110        };
1111
1112        let mut akm_suites = Vec::with_capacity(akm_count);
1113        for _ in 0..akm_count {
1114            if offset + 4 > data.len() {
1115                break;
1116            }
1117            let mut suite = [0u8; 4];
1118            suite.copy_from_slice(&data[offset..offset + 4]);
1119            akm_suites.push(AkmSuite::from_bytes(&suite));
1120            offset += 4;
1121        }
1122
1123        Some(Self {
1124            version,
1125            group_cipher,
1126            pairwise_ciphers,
1127            akm_suites,
1128        })
1129    }
1130
1131    /// Build as a Dot11Elt (Vendor Specific).
1132    pub fn build(&self) -> Dot11Elt {
1133        let mut data = Vec::new();
1134
1135        // OUI + Type
1136        data.extend_from_slice(&types::MICROSOFT_WPA_OUI);
1137        data.push(types::MICROSOFT_WPA_TYPE);
1138
1139        // Version
1140        data.extend_from_slice(&self.version.to_le_bytes());
1141
1142        // Group cipher
1143        data.extend_from_slice(&self.group_cipher.to_bytes());
1144
1145        // Pairwise ciphers
1146        data.extend_from_slice(&(self.pairwise_ciphers.len() as u16).to_le_bytes());
1147        for cs in &self.pairwise_ciphers {
1148            data.extend_from_slice(&cs.to_bytes());
1149        }
1150
1151        // AKM suites
1152        data.extend_from_slice(&(self.akm_suites.len() as u16).to_le_bytes());
1153        for akm in &self.akm_suites {
1154            data.extend_from_slice(&akm.to_bytes());
1155        }
1156
1157        Dot11Elt::new(types::ie_id::VENDOR_SPECIFIC, data)
1158    }
1159}
1160
1161// ============================================================================
1162// Helper: find IEs by ID
1163// ============================================================================
1164
1165/// Find the first IE with the given ID in a list of IEs.
1166pub fn find_ie(elements: &[Dot11Elt], id: u8) -> Option<&Dot11Elt> {
1167    elements.iter().find(|e| e.id == id)
1168}
1169
1170/// Find all IEs with the given ID in a list of IEs.
1171pub fn find_all_ies(elements: &[Dot11Elt], id: u8) -> Vec<&Dot11Elt> {
1172    elements.iter().filter(|e| e.id == id).collect()
1173}
1174
1175/// Extract the SSID from a list of IEs.
1176pub fn extract_ssid(elements: &[Dot11Elt]) -> Option<String> {
1177    find_ie(elements, types::ie_id::SSID).and_then(|e| e.ssid_str())
1178}
1179
1180/// Extract the channel from DS Parameter Set IE.
1181pub fn extract_channel(elements: &[Dot11Elt]) -> Option<u8> {
1182    find_ie(elements, types::ie_id::DS_PARAMETER_SET)
1183        .and_then(|e| Dot11EltDSSSet::parse(e))
1184        .map(|ds| ds.channel)
1185}
1186
1187// ============================================================================
1188// Tests
1189// ============================================================================
1190
1191#[cfg(test)]
1192mod tests {
1193    use super::*;
1194
1195    #[test]
1196    fn test_dot11elt_parse_build_roundtrip() {
1197        let elt = Dot11Elt::new(0, b"TestSSID".to_vec());
1198        let wire = elt.build();
1199        assert_eq!(wire[0], 0); // ID
1200        assert_eq!(wire[1], 8); // Length
1201        assert_eq!(&wire[2..], b"TestSSID");
1202
1203        let (parsed, consumed) = Dot11Elt::parse(&wire, 0).unwrap();
1204        assert_eq!(consumed, 10);
1205        assert_eq!(parsed.id, 0);
1206        assert_eq!(parsed.len, 8);
1207        assert_eq!(&parsed.info, b"TestSSID");
1208    }
1209
1210    #[test]
1211    fn test_dot11elt_parse_all() {
1212        let ssid = Dot11Elt::ssid("MyNetwork");
1213        let rates = Dot11Elt::new(1, vec![0x82, 0x84, 0x8B, 0x96]);
1214        let chain = Dot11Elt::build_chain(&[ssid.clone(), rates.clone()]);
1215
1216        let parsed = Dot11Elt::parse_all(&chain, 0);
1217        assert_eq!(parsed.len(), 2);
1218        assert_eq!(parsed[0].id, 0);
1219        assert_eq!(parsed[0].ssid_str().unwrap(), "MyNetwork");
1220        assert_eq!(parsed[1].id, 1);
1221        assert_eq!(parsed[1].info, vec![0x82, 0x84, 0x8B, 0x96]);
1222    }
1223
1224    #[test]
1225    fn test_ssid_ie() {
1226        let ssid_ie = Dot11EltSSID::new("HelloWiFi");
1227        assert_eq!(ssid_ie.ssid, "HelloWiFi");
1228        assert!(!ssid_ie.is_hidden());
1229
1230        let elt = ssid_ie.build();
1231        assert_eq!(elt.id, 0);
1232        let parsed = Dot11EltSSID::parse(&elt).unwrap();
1233        assert_eq!(parsed.ssid, "HelloWiFi");
1234
1235        // Hidden SSID
1236        let hidden = Dot11EltSSID::new("");
1237        assert!(hidden.is_hidden());
1238    }
1239
1240    #[test]
1241    fn test_rates_ie() {
1242        let rates = Dot11EltRates::new(vec![0x82, 0x84, 0x8B, 0x96, 0x0C, 0x12, 0x18, 0x24]);
1243        let elt = rates.build();
1244        assert_eq!(elt.id, 1);
1245
1246        let parsed = Dot11EltRates::parse(&elt).unwrap();
1247        assert_eq!(parsed.rates.len(), 8);
1248        assert!(Dot11EltRates::is_basic(0x82)); // 1 Mbps basic
1249        assert!(!Dot11EltRates::is_basic(0x0C)); // 6 Mbps not basic
1250        assert_eq!(Dot11EltRates::rate_mbps(0x82), 1.0);
1251        assert_eq!(Dot11EltRates::rate_mbps(0x0C), 6.0);
1252    }
1253
1254    #[test]
1255    fn test_dsset_ie() {
1256        let ds = Dot11EltDSSSet::new(6);
1257        let elt = ds.build();
1258        assert_eq!(elt.id, 3);
1259        assert_eq!(elt.info, vec![6]);
1260
1261        let parsed = Dot11EltDSSSet::parse(&elt).unwrap();
1262        assert_eq!(parsed.channel, 6);
1263    }
1264
1265    #[test]
1266    fn test_tim_ie() {
1267        let tim = Dot11EltTIM::new(0, 3, 0, vec![0x00]);
1268        let elt = tim.build();
1269        assert_eq!(elt.id, 5);
1270        assert_eq!(elt.info.len(), 4);
1271
1272        let parsed = Dot11EltTIM::parse(&elt).unwrap();
1273        assert_eq!(parsed.dtim_count, 0);
1274        assert_eq!(parsed.dtim_period, 3);
1275        assert_eq!(parsed.bitmap_control, 0);
1276        assert_eq!(parsed.bitmap, vec![0x00]);
1277    }
1278
1279    #[test]
1280    fn test_country_ie() {
1281        let country = Dot11EltCountry::new(
1282            *b"US ",
1283            vec![
1284                CountryTriplet {
1285                    first_channel: 1,
1286                    num_channels: 11,
1287                    max_power: 30,
1288                },
1289                CountryTriplet {
1290                    first_channel: 36,
1291                    num_channels: 4,
1292                    max_power: 17,
1293                },
1294            ],
1295        );
1296        let elt = country.build();
1297        assert_eq!(elt.id, 7);
1298
1299        let parsed = Dot11EltCountry::parse(&elt).unwrap();
1300        assert_eq!(parsed.country_str(), "US ");
1301        assert_eq!(parsed.triplets.len(), 2);
1302        assert_eq!(parsed.triplets[0].first_channel, 1);
1303        assert_eq!(parsed.triplets[0].num_channels, 11);
1304        assert_eq!(parsed.triplets[0].max_power, 30);
1305        assert_eq!(parsed.triplets[1].first_channel, 36);
1306    }
1307
1308    #[test]
1309    fn test_csa_ie() {
1310        let csa = Dot11EltCSA::new(1, 11, 5);
1311        let elt = csa.build();
1312        assert_eq!(elt.id, 37);
1313
1314        let parsed = Dot11EltCSA::parse(&elt).unwrap();
1315        assert_eq!(parsed.mode, 1);
1316        assert_eq!(parsed.new_channel, 11);
1317        assert_eq!(parsed.count, 5);
1318    }
1319
1320    #[test]
1321    fn test_ht_capabilities_ie() {
1322        let ht = Dot11EltHTCapabilities {
1323            ht_cap_info: 0x016F, // LDPC, 20/40MHz, SGI20, SGI40
1324            ampdu_params: 0x17,
1325            mcs_set: [0xFF, 0xFF, 0xFF, 0x00, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
1326            ht_ext_cap: 0x0000,
1327            txbf_cap: 0x00000000,
1328            asel_cap: 0x00,
1329        };
1330        let elt = ht.build();
1331        assert_eq!(elt.id, 45);
1332        assert_eq!(elt.info.len(), HT_CAP_LEN);
1333
1334        let parsed = Dot11EltHTCapabilities::parse(&elt).unwrap();
1335        assert!(parsed.ldpc());
1336        assert!(parsed.channel_width_set());
1337        assert!(parsed.short_gi_20());
1338        assert!(parsed.short_gi_40());
1339        assert_eq!(parsed.ampdu_params, 0x17);
1340    }
1341
1342    #[test]
1343    fn test_ht_info_ie() {
1344        let ht_info = Dot11EltHTInfo {
1345            primary_channel: 6,
1346            ht_info: [0x05, 0x00, 0x00, 0x00, 0x00], // sec offset=1, STA width=1
1347            basic_mcs_set: [0; 16],
1348        };
1349        let elt = ht_info.build();
1350        assert_eq!(elt.id, 61);
1351        assert_eq!(elt.info.len(), HT_INFO_LEN);
1352
1353        let parsed = Dot11EltHTInfo::parse(&elt).unwrap();
1354        assert_eq!(parsed.primary_channel, 6);
1355        assert_eq!(parsed.secondary_channel_offset(), 1);
1356        assert!(parsed.sta_channel_width());
1357    }
1358
1359    #[test]
1360    fn test_vht_capabilities_ie() {
1361        let vht = Dot11EltVHTCapabilities {
1362            vht_cap_info: 0x00000062, // SGI 80, SGI 160
1363            mcs_nss_set: [0xFF, 0xFE, 0x00, 0x00, 0xFF, 0xFE, 0x00, 0x00],
1364        };
1365        let elt = vht.build();
1366        assert_eq!(elt.id, 191);
1367        assert_eq!(elt.info.len(), VHT_CAP_LEN);
1368
1369        let parsed = Dot11EltVHTCapabilities::parse(&elt).unwrap();
1370        assert_eq!(parsed.max_mpdu_length(), 2);
1371        assert!(parsed.short_gi_80());
1372        assert!(parsed.short_gi_160());
1373    }
1374
1375    #[test]
1376    fn test_vht_operation_ie() {
1377        let vht_op = Dot11EltVHTOperation {
1378            channel_width: 1,
1379            center_freq_seg0: 42,
1380            center_freq_seg1: 0,
1381            basic_mcs_nss: 0xFFFC,
1382        };
1383        let elt = vht_op.build();
1384        assert_eq!(elt.id, 192);
1385        assert_eq!(elt.info.len(), VHT_OP_LEN);
1386
1387        let parsed = Dot11EltVHTOperation::parse(&elt).unwrap();
1388        assert_eq!(parsed.channel_width, 1);
1389        assert_eq!(parsed.center_freq_seg0, 42);
1390        assert_eq!(parsed.center_freq_seg1, 0);
1391        assert_eq!(parsed.basic_mcs_nss, 0xFFFC);
1392    }
1393
1394    #[test]
1395    fn test_rsn_ie_wpa2_psk() {
1396        let rsn = Dot11EltRSN {
1397            info: RsnInfo {
1398                version: 1,
1399                group_cipher: CipherSuite::from_bytes(&[0x00, 0x0F, 0xAC, 0x04]), // CCMP
1400                pairwise_ciphers: vec![CipherSuite::from_bytes(&[0x00, 0x0F, 0xAC, 0x04])],
1401                akm_suites: vec![AkmSuite::from_bytes(&[0x00, 0x0F, 0xAC, 0x02])], // PSK
1402                rsn_capabilities: 0x000C,
1403                pmkid_count: 0,
1404                pmkids: Vec::new(),
1405                group_mgmt_cipher: None,
1406            },
1407        };
1408
1409        let elt = rsn.build();
1410        assert_eq!(elt.id, 48);
1411
1412        let parsed = Dot11EltRSN::parse(&elt).unwrap();
1413        assert_eq!(parsed.info.version, 1);
1414        assert_eq!(parsed.info.group_cipher.name(), "CCMP-128");
1415        assert_eq!(parsed.info.pairwise_ciphers.len(), 1);
1416        assert_eq!(parsed.info.pairwise_ciphers[0].name(), "CCMP-128");
1417        assert_eq!(parsed.info.akm_suites.len(), 1);
1418        assert_eq!(parsed.info.akm_suites[0].name(), "PSK");
1419        assert_eq!(parsed.info.rsn_capabilities, 0x000C);
1420    }
1421
1422    #[test]
1423    fn test_rsn_ie_with_mfp() {
1424        let rsn = Dot11EltRSN {
1425            info: RsnInfo {
1426                version: 1,
1427                group_cipher: CipherSuite::from_bytes(&[0x00, 0x0F, 0xAC, 0x04]),
1428                pairwise_ciphers: vec![CipherSuite::from_bytes(&[0x00, 0x0F, 0xAC, 0x04])],
1429                akm_suites: vec![AkmSuite::from_bytes(&[0x00, 0x0F, 0xAC, 0x08])], // SAE
1430                rsn_capabilities: 0x00CC, // MFP Required + Capable
1431                pmkid_count: 0,
1432                pmkids: Vec::new(),
1433                group_mgmt_cipher: Some(CipherSuite::from_bytes(&[0x00, 0x0F, 0xAC, 0x06])), // BIP-CMAC-128
1434            },
1435        };
1436
1437        let elt = rsn.build();
1438        let parsed = Dot11EltRSN::parse(&elt).unwrap();
1439        assert!(parsed.mfp_required());
1440        assert!(parsed.mfp_capable());
1441        assert!(parsed.info.group_mgmt_cipher.is_some());
1442        assert_eq!(
1443            parsed.info.group_mgmt_cipher.unwrap().name(),
1444            "BIP-CMAC-128"
1445        );
1446    }
1447
1448    #[test]
1449    fn test_vendor_specific_ie() {
1450        let vs = Dot11EltVendorSpecific::new([0x00, 0x50, 0xF2], vec![0x01, 0x01, 0x00]);
1451        assert!(vs.is_wpa());
1452        assert!(!vs.is_wmm());
1453
1454        let elt = vs.build();
1455        assert_eq!(elt.id, 221);
1456
1457        let parsed = Dot11EltVendorSpecific::parse(&elt).unwrap();
1458        assert_eq!(parsed.oui, [0x00, 0x50, 0xF2]);
1459        assert!(parsed.is_wpa());
1460    }
1461
1462    #[test]
1463    fn test_microsoft_wpa_ie() {
1464        let wpa = Dot11EltMicrosoftWPA {
1465            version: 1,
1466            group_cipher: CipherSuite::from_bytes(&[0x00, 0x50, 0xF2, 0x02]), // TKIP
1467            pairwise_ciphers: vec![CipherSuite::from_bytes(&[0x00, 0x50, 0xF2, 0x02])], // TKIP
1468            akm_suites: vec![AkmSuite::from_bytes(&[0x00, 0x50, 0xF2, 0x02])], // PSK
1469        };
1470
1471        let elt = wpa.build();
1472        assert_eq!(elt.id, 221);
1473
1474        // Parse back through vendor specific
1475        let vs = Dot11EltVendorSpecific::parse(&elt).unwrap();
1476        assert!(vs.is_wpa());
1477
1478        let parsed = Dot11EltMicrosoftWPA::parse(&vs).unwrap();
1479        assert_eq!(parsed.version, 1);
1480        assert_eq!(parsed.group_cipher.to_bytes(), [0x00, 0x50, 0xF2, 0x02]);
1481        assert_eq!(parsed.pairwise_ciphers.len(), 1);
1482        assert_eq!(parsed.akm_suites.len(), 1);
1483    }
1484
1485    #[test]
1486    fn test_find_ie_helpers() {
1487        let elements = vec![
1488            Dot11Elt::ssid("TestNet"),
1489            Dot11Elt::new(1, vec![0x82, 0x84]),
1490            Dot11Elt::new(3, vec![6]),
1491        ];
1492
1493        assert_eq!(extract_ssid(&elements), Some("TestNet".to_string()));
1494        assert_eq!(extract_channel(&elements), Some(6));
1495        assert!(find_ie(&elements, 48).is_none()); // No RSN
1496    }
1497
1498    #[test]
1499    fn test_dot11elt_name() {
1500        let ssid = Dot11Elt::ssid("Test");
1501        assert_eq!(ssid.name(), "SSID");
1502
1503        let rsn = Dot11Elt::new(48, vec![]);
1504        assert_eq!(rsn.name(), "RSN");
1505
1506        let ht = Dot11Elt::new(45, vec![]);
1507        assert_eq!(ht.name(), "HT Capabilities");
1508    }
1509
1510    #[test]
1511    fn test_dot11elt_wire_len() {
1512        let elt = Dot11Elt::new(0, b"Test".to_vec());
1513        assert_eq!(elt.wire_len(), 6); // 2 header + 4 data
1514    }
1515
1516    #[test]
1517    fn test_parse_truncated_ie() {
1518        // Only 1 byte - too short for IE header
1519        let buf = vec![0x00];
1520        assert!(Dot11Elt::parse(&buf, 0).is_err());
1521
1522        // Header says 10 bytes but only 5 available
1523        let buf = vec![0x00, 0x0A, 0x01, 0x02, 0x03, 0x04, 0x05];
1524        assert!(Dot11Elt::parse(&buf, 0).is_err());
1525    }
1526
1527    #[test]
1528    fn test_parse_all_partial() {
1529        // Two complete IEs followed by a truncated one
1530        let mut buf = Dot11Elt::ssid("AB").build();
1531        buf.extend(Dot11Elt::new(1, vec![0x82]).build());
1532        buf.extend(&[0x03, 0x05]); // truncated: says 5 bytes but none follow
1533
1534        let parsed = Dot11Elt::parse_all(&buf, 0);
1535        assert_eq!(parsed.len(), 2); // only the 2 complete ones
1536    }
1537
1538    #[test]
1539    fn test_rates_mbps() {
1540        let rates = Dot11EltRates::new(vec![0x82, 0x84, 0x8B, 0x96, 0x0C, 0x12, 0x18, 0x24]);
1541        let mbps = rates.rates_mbps();
1542        assert_eq!(mbps, vec![1.0, 2.0, 5.5, 11.0, 6.0, 9.0, 12.0, 18.0]);
1543    }
1544}