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