Skip to main content

stackforge_core/layer/dot11/
management.rs

1//! IEEE 802.11 management frame subtypes.
2//!
3//! Management frames are used for establishing and maintaining communications.
4//! This module provides parsers for all major management frame subtypes
5//! including Beacon, Probe Request/Response, Authentication, Association, etc.
6
7use crate::layer::field::{FieldError, MacAddress};
8
9// ============================================================================
10// Dot11Beacon
11// ============================================================================
12
13/// 802.11 Beacon frame body.
14///
15/// Fixed fields: timestamp(8B) + beacon_interval(2B) + capability(2B) = 12 bytes.
16/// Followed by an IE chain (parsed separately by the ie module).
17#[derive(Debug, Clone)]
18pub struct Dot11Beacon {
19    pub offset: usize,
20}
21
22/// Beacon fixed fields length.
23pub const BEACON_FIXED_LEN: usize = 12;
24
25impl Dot11Beacon {
26    pub fn new(offset: usize) -> Self {
27        Self { offset }
28    }
29
30    /// Validate the buffer contains enough data.
31    pub fn validate(buf: &[u8], offset: usize) -> Result<(), FieldError> {
32        if buf.len() < offset + BEACON_FIXED_LEN {
33            return Err(FieldError::BufferTooShort {
34                offset,
35                need: BEACON_FIXED_LEN,
36                have: buf.len().saturating_sub(offset),
37            });
38        }
39        Ok(())
40    }
41
42    /// Timestamp (8 bytes, little-endian).
43    pub fn timestamp(&self, buf: &[u8]) -> Result<u64, FieldError> {
44        let off = self.offset;
45        if buf.len() < off + 8 {
46            return Err(FieldError::BufferTooShort {
47                offset: off,
48                need: 8,
49                have: buf.len(),
50            });
51        }
52        Ok(u64::from_le_bytes([
53            buf[off],
54            buf[off + 1],
55            buf[off + 2],
56            buf[off + 3],
57            buf[off + 4],
58            buf[off + 5],
59            buf[off + 6],
60            buf[off + 7],
61        ]))
62    }
63
64    /// Beacon interval in TUs (1 TU = 1024 microseconds), little-endian u16.
65    pub fn beacon_interval(&self, buf: &[u8]) -> Result<u16, FieldError> {
66        let off = self.offset + 8;
67        if buf.len() < off + 2 {
68            return Err(FieldError::BufferTooShort {
69                offset: off,
70                need: 2,
71                have: buf.len(),
72            });
73        }
74        Ok(u16::from_le_bytes([buf[off], buf[off + 1]]))
75    }
76
77    /// Capability information (2 bytes, little-endian).
78    pub fn capability(&self, buf: &[u8]) -> Result<u16, FieldError> {
79        let off = self.offset + 10;
80        if buf.len() < off + 2 {
81            return Err(FieldError::BufferTooShort {
82                offset: off,
83                need: 2,
84                have: buf.len(),
85            });
86        }
87        Ok(u16::from_le_bytes([buf[off], buf[off + 1]]))
88    }
89
90    /// Offset where the IE chain starts.
91    pub fn ie_offset(&self) -> usize {
92        self.offset + BEACON_FIXED_LEN
93    }
94
95    /// Header length (fixed fields only).
96    pub fn header_len(&self) -> usize {
97        BEACON_FIXED_LEN
98    }
99
100    /// Build beacon fixed fields.
101    pub fn build(timestamp: u64, beacon_interval: u16, capability: u16) -> Vec<u8> {
102        let mut out = Vec::with_capacity(BEACON_FIXED_LEN);
103        out.extend_from_slice(&timestamp.to_le_bytes());
104        out.extend_from_slice(&beacon_interval.to_le_bytes());
105        out.extend_from_slice(&capability.to_le_bytes());
106        out
107    }
108}
109
110// ============================================================================
111// Dot11ProbeReq
112// ============================================================================
113
114/// 802.11 Probe Request frame body.
115///
116/// Has no fixed fields; just an IE chain.
117#[derive(Debug, Clone)]
118pub struct Dot11ProbeReq {
119    pub offset: usize,
120}
121
122impl Dot11ProbeReq {
123    pub fn new(offset: usize) -> Self {
124        Self { offset }
125    }
126
127    /// Offset where the IE chain starts (immediately).
128    pub fn ie_offset(&self) -> usize {
129        self.offset
130    }
131
132    /// Header length (no fixed fields).
133    pub fn header_len(&self) -> usize {
134        0
135    }
136}
137
138// ============================================================================
139// Dot11ProbeResp
140// ============================================================================
141
142/// 802.11 Probe Response frame body.
143///
144/// Same fixed fields as Beacon: timestamp(8B) + beacon_interval(2B) + capability(2B).
145#[derive(Debug, Clone)]
146pub struct Dot11ProbeResp {
147    pub offset: usize,
148}
149
150impl Dot11ProbeResp {
151    pub fn new(offset: usize) -> Self {
152        Self { offset }
153    }
154
155    /// Timestamp (8 bytes, little-endian).
156    pub fn timestamp(&self, buf: &[u8]) -> Result<u64, FieldError> {
157        let off = self.offset;
158        if buf.len() < off + 8 {
159            return Err(FieldError::BufferTooShort {
160                offset: off,
161                need: 8,
162                have: buf.len(),
163            });
164        }
165        Ok(u64::from_le_bytes([
166            buf[off],
167            buf[off + 1],
168            buf[off + 2],
169            buf[off + 3],
170            buf[off + 4],
171            buf[off + 5],
172            buf[off + 6],
173            buf[off + 7],
174        ]))
175    }
176
177    /// Beacon interval in TUs, little-endian.
178    pub fn beacon_interval(&self, buf: &[u8]) -> Result<u16, FieldError> {
179        let off = self.offset + 8;
180        if buf.len() < off + 2 {
181            return Err(FieldError::BufferTooShort {
182                offset: off,
183                need: 2,
184                have: buf.len(),
185            });
186        }
187        Ok(u16::from_le_bytes([buf[off], buf[off + 1]]))
188    }
189
190    /// Capability information (2 bytes, little-endian).
191    pub fn capability(&self, buf: &[u8]) -> Result<u16, FieldError> {
192        let off = self.offset + 10;
193        if buf.len() < off + 2 {
194            return Err(FieldError::BufferTooShort {
195                offset: off,
196                need: 2,
197                have: buf.len(),
198            });
199        }
200        Ok(u16::from_le_bytes([buf[off], buf[off + 1]]))
201    }
202
203    /// Offset where the IE chain starts.
204    pub fn ie_offset(&self) -> usize {
205        self.offset + BEACON_FIXED_LEN
206    }
207
208    /// Header length (fixed fields only).
209    pub fn header_len(&self) -> usize {
210        BEACON_FIXED_LEN
211    }
212
213    /// Build probe response fixed fields.
214    pub fn build(timestamp: u64, beacon_interval: u16, capability: u16) -> Vec<u8> {
215        Dot11Beacon::build(timestamp, beacon_interval, capability)
216    }
217}
218
219// ============================================================================
220// Dot11Auth
221// ============================================================================
222
223/// 802.11 Authentication frame body.
224///
225/// Fixed fields: auth_algo(2B) + auth_seq(2B) + status_code(2B) = 6 bytes.
226/// May be followed by IEs (e.g., challenge text for shared key auth).
227#[derive(Debug, Clone)]
228pub struct Dot11Auth {
229    pub offset: usize,
230}
231
232pub const AUTH_FIXED_LEN: usize = 6;
233
234impl Dot11Auth {
235    pub fn new(offset: usize) -> Self {
236        Self { offset }
237    }
238
239    /// Validate buffer.
240    pub fn validate(buf: &[u8], offset: usize) -> Result<(), FieldError> {
241        if buf.len() < offset + AUTH_FIXED_LEN {
242            return Err(FieldError::BufferTooShort {
243                offset,
244                need: AUTH_FIXED_LEN,
245                have: buf.len().saturating_sub(offset),
246            });
247        }
248        Ok(())
249    }
250
251    /// Authentication algorithm number (little-endian u16).
252    pub fn algo(&self, buf: &[u8]) -> Result<u16, FieldError> {
253        let off = self.offset;
254        if buf.len() < off + 2 {
255            return Err(FieldError::BufferTooShort {
256                offset: off,
257                need: 2,
258                have: buf.len(),
259            });
260        }
261        Ok(u16::from_le_bytes([buf[off], buf[off + 1]]))
262    }
263
264    /// Authentication sequence number (little-endian u16).
265    pub fn seqnum(&self, buf: &[u8]) -> Result<u16, FieldError> {
266        let off = self.offset + 2;
267        if buf.len() < off + 2 {
268            return Err(FieldError::BufferTooShort {
269                offset: off,
270                need: 2,
271                have: buf.len(),
272            });
273        }
274        Ok(u16::from_le_bytes([buf[off], buf[off + 1]]))
275    }
276
277    /// Status code (little-endian u16).
278    pub fn status_code(&self, buf: &[u8]) -> Result<u16, FieldError> {
279        let off = self.offset + 4;
280        if buf.len() < off + 2 {
281            return Err(FieldError::BufferTooShort {
282                offset: off,
283                need: 2,
284                have: buf.len(),
285            });
286        }
287        Ok(u16::from_le_bytes([buf[off], buf[off + 1]]))
288    }
289
290    /// Offset where the IE chain starts.
291    pub fn ie_offset(&self) -> usize {
292        self.offset + AUTH_FIXED_LEN
293    }
294
295    /// Header length.
296    pub fn header_len(&self) -> usize {
297        AUTH_FIXED_LEN
298    }
299
300    /// Check if this auth frame answers another.
301    pub fn answers(&self, buf: &[u8], other: &Dot11Auth, other_buf: &[u8]) -> bool {
302        let self_algo = self.algo(buf).unwrap_or(0);
303        let other_algo = other.algo(other_buf).unwrap_or(0);
304        if self_algo != other_algo {
305            return false;
306        }
307        let self_seq = self.seqnum(buf).unwrap_or(0);
308        let other_seq = other.seqnum(other_buf).unwrap_or(0);
309        self_seq == other_seq + 1 || (self_algo == 3 && self_seq == other_seq)
310    }
311
312    /// Build authentication fixed fields.
313    pub fn build(algo: u16, seqnum: u16, status_code: u16) -> Vec<u8> {
314        let mut out = Vec::with_capacity(AUTH_FIXED_LEN);
315        out.extend_from_slice(&algo.to_le_bytes());
316        out.extend_from_slice(&seqnum.to_le_bytes());
317        out.extend_from_slice(&status_code.to_le_bytes());
318        out
319    }
320}
321
322// ============================================================================
323// Dot11Deauth
324// ============================================================================
325
326/// 802.11 Deauthentication frame body.
327///
328/// Fixed fields: reason_code(2B).
329#[derive(Debug, Clone)]
330pub struct Dot11Deauth {
331    pub offset: usize,
332}
333
334pub const DEAUTH_FIXED_LEN: usize = 2;
335
336impl Dot11Deauth {
337    pub fn new(offset: usize) -> Self {
338        Self { offset }
339    }
340
341    /// Reason code (little-endian u16).
342    pub fn reason_code(&self, buf: &[u8]) -> Result<u16, FieldError> {
343        let off = self.offset;
344        if buf.len() < off + 2 {
345            return Err(FieldError::BufferTooShort {
346                offset: off,
347                need: 2,
348                have: buf.len(),
349            });
350        }
351        Ok(u16::from_le_bytes([buf[off], buf[off + 1]]))
352    }
353
354    /// Header length.
355    pub fn header_len(&self) -> usize {
356        DEAUTH_FIXED_LEN
357    }
358
359    /// Build deauthentication body.
360    pub fn build(reason_code: u16) -> Vec<u8> {
361        reason_code.to_le_bytes().to_vec()
362    }
363}
364
365// ============================================================================
366// Dot11Disas (Disassociation)
367// ============================================================================
368
369/// 802.11 Disassociation frame body.
370///
371/// Fixed fields: reason_code(2B).
372#[derive(Debug, Clone)]
373pub struct Dot11Disas {
374    pub offset: usize,
375}
376
377pub const DISAS_FIXED_LEN: usize = 2;
378
379impl Dot11Disas {
380    pub fn new(offset: usize) -> Self {
381        Self { offset }
382    }
383
384    /// Reason code (little-endian u16).
385    pub fn reason_code(&self, buf: &[u8]) -> Result<u16, FieldError> {
386        let off = self.offset;
387        if buf.len() < off + 2 {
388            return Err(FieldError::BufferTooShort {
389                offset: off,
390                need: 2,
391                have: buf.len(),
392            });
393        }
394        Ok(u16::from_le_bytes([buf[off], buf[off + 1]]))
395    }
396
397    /// Header length.
398    pub fn header_len(&self) -> usize {
399        DISAS_FIXED_LEN
400    }
401
402    /// Build disassociation body.
403    pub fn build(reason_code: u16) -> Vec<u8> {
404        reason_code.to_le_bytes().to_vec()
405    }
406}
407
408// ============================================================================
409// Dot11AssocReq (Association Request)
410// ============================================================================
411
412/// 802.11 Association Request frame body.
413///
414/// Fixed fields: capability(2B) + listen_interval(2B) = 4 bytes.
415/// Followed by an IE chain.
416#[derive(Debug, Clone)]
417pub struct Dot11AssocReq {
418    pub offset: usize,
419}
420
421pub const ASSOC_REQ_FIXED_LEN: usize = 4;
422
423impl Dot11AssocReq {
424    pub fn new(offset: usize) -> Self {
425        Self { offset }
426    }
427
428    /// Capability information (little-endian u16).
429    pub fn capability(&self, buf: &[u8]) -> Result<u16, FieldError> {
430        let off = self.offset;
431        if buf.len() < off + 2 {
432            return Err(FieldError::BufferTooShort {
433                offset: off,
434                need: 2,
435                have: buf.len(),
436            });
437        }
438        Ok(u16::from_le_bytes([buf[off], buf[off + 1]]))
439    }
440
441    /// Listen interval (little-endian u16).
442    pub fn listen_interval(&self, buf: &[u8]) -> Result<u16, FieldError> {
443        let off = self.offset + 2;
444        if buf.len() < off + 2 {
445            return Err(FieldError::BufferTooShort {
446                offset: off,
447                need: 2,
448                have: buf.len(),
449            });
450        }
451        Ok(u16::from_le_bytes([buf[off], buf[off + 1]]))
452    }
453
454    /// Offset where the IE chain starts.
455    pub fn ie_offset(&self) -> usize {
456        self.offset + ASSOC_REQ_FIXED_LEN
457    }
458
459    /// Header length.
460    pub fn header_len(&self) -> usize {
461        ASSOC_REQ_FIXED_LEN
462    }
463
464    /// Build association request fixed fields.
465    pub fn build(capability: u16, listen_interval: u16) -> Vec<u8> {
466        let mut out = Vec::with_capacity(ASSOC_REQ_FIXED_LEN);
467        out.extend_from_slice(&capability.to_le_bytes());
468        out.extend_from_slice(&listen_interval.to_le_bytes());
469        out
470    }
471}
472
473// ============================================================================
474// Dot11AssocResp (Association Response)
475// ============================================================================
476
477/// 802.11 Association Response frame body.
478///
479/// Fixed fields: capability(2B) + status_code(2B) + AID(2B) = 6 bytes.
480/// Followed by an IE chain.
481#[derive(Debug, Clone)]
482pub struct Dot11AssocResp {
483    pub offset: usize,
484}
485
486pub const ASSOC_RESP_FIXED_LEN: usize = 6;
487
488impl Dot11AssocResp {
489    pub fn new(offset: usize) -> Self {
490        Self { offset }
491    }
492
493    /// Capability information (little-endian u16).
494    pub fn capability(&self, buf: &[u8]) -> Result<u16, FieldError> {
495        let off = self.offset;
496        if buf.len() < off + 2 {
497            return Err(FieldError::BufferTooShort {
498                offset: off,
499                need: 2,
500                have: buf.len(),
501            });
502        }
503        Ok(u16::from_le_bytes([buf[off], buf[off + 1]]))
504    }
505
506    /// Status code (little-endian u16).
507    pub fn status_code(&self, buf: &[u8]) -> Result<u16, FieldError> {
508        let off = self.offset + 2;
509        if buf.len() < off + 2 {
510            return Err(FieldError::BufferTooShort {
511                offset: off,
512                need: 2,
513                have: buf.len(),
514            });
515        }
516        Ok(u16::from_le_bytes([buf[off], buf[off + 1]]))
517    }
518
519    /// Association ID (little-endian u16).
520    pub fn aid(&self, buf: &[u8]) -> Result<u16, FieldError> {
521        let off = self.offset + 4;
522        if buf.len() < off + 2 {
523            return Err(FieldError::BufferTooShort {
524                offset: off,
525                need: 2,
526                have: buf.len(),
527            });
528        }
529        Ok(u16::from_le_bytes([buf[off], buf[off + 1]]))
530    }
531
532    /// Offset where the IE chain starts.
533    pub fn ie_offset(&self) -> usize {
534        self.offset + ASSOC_RESP_FIXED_LEN
535    }
536
537    /// Header length.
538    pub fn header_len(&self) -> usize {
539        ASSOC_RESP_FIXED_LEN
540    }
541
542    /// Build association response fixed fields.
543    pub fn build(capability: u16, status_code: u16, aid: u16) -> Vec<u8> {
544        let mut out = Vec::with_capacity(ASSOC_RESP_FIXED_LEN);
545        out.extend_from_slice(&capability.to_le_bytes());
546        out.extend_from_slice(&status_code.to_le_bytes());
547        out.extend_from_slice(&aid.to_le_bytes());
548        out
549    }
550}
551
552// ============================================================================
553// Dot11ReassocReq (Reassociation Request)
554// ============================================================================
555
556/// 802.11 Reassociation Request frame body.
557///
558/// Fixed fields: capability(2B) + listen_interval(2B) + current_ap(6B) = 10 bytes.
559/// Followed by an IE chain.
560#[derive(Debug, Clone)]
561pub struct Dot11ReassocReq {
562    pub offset: usize,
563}
564
565pub const REASSOC_REQ_FIXED_LEN: usize = 10;
566
567impl Dot11ReassocReq {
568    pub fn new(offset: usize) -> Self {
569        Self { offset }
570    }
571
572    /// Capability information (little-endian u16).
573    pub fn capability(&self, buf: &[u8]) -> Result<u16, FieldError> {
574        let off = self.offset;
575        if buf.len() < off + 2 {
576            return Err(FieldError::BufferTooShort {
577                offset: off,
578                need: 2,
579                have: buf.len(),
580            });
581        }
582        Ok(u16::from_le_bytes([buf[off], buf[off + 1]]))
583    }
584
585    /// Listen interval (little-endian u16).
586    pub fn listen_interval(&self, buf: &[u8]) -> Result<u16, FieldError> {
587        let off = self.offset + 2;
588        if buf.len() < off + 2 {
589            return Err(FieldError::BufferTooShort {
590                offset: off,
591                need: 2,
592                have: buf.len(),
593            });
594        }
595        Ok(u16::from_le_bytes([buf[off], buf[off + 1]]))
596    }
597
598    /// Current AP MAC address (6 bytes).
599    pub fn current_ap(&self, buf: &[u8]) -> Result<MacAddress, FieldError> {
600        let off = self.offset + 4;
601        if buf.len() < off + 6 {
602            return Err(FieldError::BufferTooShort {
603                offset: off,
604                need: 6,
605                have: buf.len(),
606            });
607        }
608        Ok(MacAddress::new([
609            buf[off],
610            buf[off + 1],
611            buf[off + 2],
612            buf[off + 3],
613            buf[off + 4],
614            buf[off + 5],
615        ]))
616    }
617
618    /// Offset where the IE chain starts.
619    pub fn ie_offset(&self) -> usize {
620        self.offset + REASSOC_REQ_FIXED_LEN
621    }
622
623    /// Header length.
624    pub fn header_len(&self) -> usize {
625        REASSOC_REQ_FIXED_LEN
626    }
627
628    /// Build reassociation request fixed fields.
629    pub fn build(capability: u16, listen_interval: u16, current_ap: MacAddress) -> Vec<u8> {
630        let mut out = Vec::with_capacity(REASSOC_REQ_FIXED_LEN);
631        out.extend_from_slice(&capability.to_le_bytes());
632        out.extend_from_slice(&listen_interval.to_le_bytes());
633        out.extend_from_slice(current_ap.as_bytes());
634        out
635    }
636}
637
638// ============================================================================
639// Dot11ReassocResp (Reassociation Response)
640// ============================================================================
641
642/// 802.11 Reassociation Response frame body (same as Association Response).
643#[derive(Debug, Clone)]
644pub struct Dot11ReassocResp {
645    pub offset: usize,
646}
647
648impl Dot11ReassocResp {
649    pub fn new(offset: usize) -> Self {
650        Self { offset }
651    }
652
653    /// Capability information (little-endian u16).
654    pub fn capability(&self, buf: &[u8]) -> Result<u16, FieldError> {
655        let off = self.offset;
656        if buf.len() < off + 2 {
657            return Err(FieldError::BufferTooShort {
658                offset: off,
659                need: 2,
660                have: buf.len(),
661            });
662        }
663        Ok(u16::from_le_bytes([buf[off], buf[off + 1]]))
664    }
665
666    /// Status code (little-endian u16).
667    pub fn status_code(&self, buf: &[u8]) -> Result<u16, FieldError> {
668        let off = self.offset + 2;
669        if buf.len() < off + 2 {
670            return Err(FieldError::BufferTooShort {
671                offset: off,
672                need: 2,
673                have: buf.len(),
674            });
675        }
676        Ok(u16::from_le_bytes([buf[off], buf[off + 1]]))
677    }
678
679    /// Association ID (little-endian u16).
680    pub fn aid(&self, buf: &[u8]) -> Result<u16, FieldError> {
681        let off = self.offset + 4;
682        if buf.len() < off + 2 {
683            return Err(FieldError::BufferTooShort {
684                offset: off,
685                need: 2,
686                have: buf.len(),
687            });
688        }
689        Ok(u16::from_le_bytes([buf[off], buf[off + 1]]))
690    }
691
692    /// Offset where the IE chain starts.
693    pub fn ie_offset(&self) -> usize {
694        self.offset + ASSOC_RESP_FIXED_LEN
695    }
696
697    /// Header length.
698    pub fn header_len(&self) -> usize {
699        ASSOC_RESP_FIXED_LEN
700    }
701
702    /// Build reassociation response fixed fields (same as assoc resp).
703    pub fn build(capability: u16, status_code: u16, aid: u16) -> Vec<u8> {
704        Dot11AssocResp::build(capability, status_code, aid)
705    }
706}
707
708// ============================================================================
709// Dot11Action
710// ============================================================================
711
712/// 802.11 Action frame body.
713///
714/// Fixed fields: category(1B) + variable action data.
715#[derive(Debug, Clone)]
716pub struct Dot11Action {
717    pub offset: usize,
718}
719
720pub const ACTION_MIN_LEN: usize = 1;
721
722impl Dot11Action {
723    pub fn new(offset: usize) -> Self {
724        Self { offset }
725    }
726
727    /// Action category (1 byte).
728    pub fn category(&self, buf: &[u8]) -> Result<u8, FieldError> {
729        let off = self.offset;
730        if buf.len() <= off {
731            return Err(FieldError::BufferTooShort {
732                offset: off,
733                need: 1,
734                have: buf.len(),
735            });
736        }
737        Ok(buf[off])
738    }
739
740    /// Action code (1 byte, second byte of action frame body).
741    pub fn action_code(&self, buf: &[u8]) -> Result<u8, FieldError> {
742        let off = self.offset + 1;
743        if buf.len() <= off {
744            return Err(FieldError::BufferTooShort {
745                offset: off,
746                need: 1,
747                have: buf.len(),
748            });
749        }
750        Ok(buf[off])
751    }
752
753    /// Payload offset (after category byte).
754    pub fn payload_offset(&self) -> usize {
755        self.offset + ACTION_MIN_LEN
756    }
757
758    /// Header length (just the category byte).
759    pub fn header_len(&self) -> usize {
760        ACTION_MIN_LEN
761    }
762
763    /// Build action frame body.
764    pub fn build(category: u8) -> Vec<u8> {
765        vec![category]
766    }
767}
768
769// ============================================================================
770// Dot11ATIM
771// ============================================================================
772
773/// 802.11 ATIM (Announcement Traffic Indication Message) frame body.
774///
775/// Has no body fields.
776#[derive(Debug, Clone)]
777pub struct Dot11ATIM {
778    pub offset: usize,
779}
780
781impl Dot11ATIM {
782    pub fn new(offset: usize) -> Self {
783        Self { offset }
784    }
785
786    pub fn header_len(&self) -> usize {
787        0
788    }
789}
790
791#[cfg(test)]
792mod tests {
793    use super::*;
794
795    #[test]
796    fn test_beacon_parse() {
797        let mut buf = vec![0u8; 12];
798        // timestamp = 0x0102030405060708 (LE)
799        buf[0..8].copy_from_slice(&0x0102030405060708u64.to_le_bytes());
800        // beacon_interval = 100 (0x0064)
801        buf[8..10].copy_from_slice(&100u16.to_le_bytes());
802        // capability = 0x0411 (ESS + privacy + short-slot)
803        buf[10..12].copy_from_slice(&0x0411u16.to_le_bytes());
804
805        let beacon = Dot11Beacon::new(0);
806        assert_eq!(beacon.timestamp(&buf).unwrap(), 0x0102030405060708);
807        assert_eq!(beacon.beacon_interval(&buf).unwrap(), 100);
808        assert_eq!(beacon.capability(&buf).unwrap(), 0x0411);
809        assert_eq!(beacon.ie_offset(), 12);
810    }
811
812    #[test]
813    fn test_beacon_build_roundtrip() {
814        let data = Dot11Beacon::build(0x1234567890ABCDEF, 100, 0x0411);
815        assert_eq!(data.len(), BEACON_FIXED_LEN);
816
817        let beacon = Dot11Beacon::new(0);
818        assert_eq!(beacon.timestamp(&data).unwrap(), 0x1234567890ABCDEF);
819        assert_eq!(beacon.beacon_interval(&data).unwrap(), 100);
820        assert_eq!(beacon.capability(&data).unwrap(), 0x0411);
821    }
822
823    #[test]
824    fn test_auth_parse() {
825        let mut buf = vec![0u8; 6];
826        // algo = 0 (open)
827        buf[0..2].copy_from_slice(&0u16.to_le_bytes());
828        // seqnum = 1
829        buf[2..4].copy_from_slice(&1u16.to_le_bytes());
830        // status = 0 (success)
831        buf[4..6].copy_from_slice(&0u16.to_le_bytes());
832
833        let auth = Dot11Auth::new(0);
834        assert_eq!(auth.algo(&buf).unwrap(), 0);
835        assert_eq!(auth.seqnum(&buf).unwrap(), 1);
836        assert_eq!(auth.status_code(&buf).unwrap(), 0);
837    }
838
839    #[test]
840    fn test_auth_build_roundtrip() {
841        let data = Dot11Auth::build(0, 1, 0);
842        assert_eq!(data.len(), AUTH_FIXED_LEN);
843
844        let auth = Dot11Auth::new(0);
845        assert_eq!(auth.algo(&data).unwrap(), 0);
846        assert_eq!(auth.seqnum(&data).unwrap(), 1);
847        assert_eq!(auth.status_code(&data).unwrap(), 0);
848    }
849
850    #[test]
851    fn test_auth_answers() {
852        let req = Dot11Auth::build(0, 1, 0);
853        let resp = Dot11Auth::build(0, 2, 0);
854
855        let req_auth = Dot11Auth::new(0);
856        let resp_auth = Dot11Auth::new(0);
857
858        assert!(resp_auth.answers(&resp, &req_auth, &req));
859        assert!(!req_auth.answers(&req, &resp_auth, &resp));
860    }
861
862    #[test]
863    fn test_deauth_parse() {
864        let buf = 1u16.to_le_bytes().to_vec();
865        let deauth = Dot11Deauth::new(0);
866        assert_eq!(deauth.reason_code(&buf).unwrap(), 1);
867    }
868
869    #[test]
870    fn test_deauth_build_roundtrip() {
871        let data = Dot11Deauth::build(4);
872        let deauth = Dot11Deauth::new(0);
873        assert_eq!(deauth.reason_code(&data).unwrap(), 4);
874    }
875
876    #[test]
877    fn test_disas_parse() {
878        let buf = 5u16.to_le_bytes().to_vec();
879        let disas = Dot11Disas::new(0);
880        assert_eq!(disas.reason_code(&buf).unwrap(), 5);
881    }
882
883    #[test]
884    fn test_assoc_req_parse() {
885        let mut buf = vec![0u8; 4];
886        buf[0..2].copy_from_slice(&0x0411u16.to_le_bytes()); // cap
887        buf[2..4].copy_from_slice(&200u16.to_le_bytes()); // listen interval
888
889        let assoc = Dot11AssocReq::new(0);
890        assert_eq!(assoc.capability(&buf).unwrap(), 0x0411);
891        assert_eq!(assoc.listen_interval(&buf).unwrap(), 200);
892    }
893
894    #[test]
895    fn test_assoc_req_build_roundtrip() {
896        let data = Dot11AssocReq::build(0x0411, 200);
897        let assoc = Dot11AssocReq::new(0);
898        assert_eq!(assoc.capability(&data).unwrap(), 0x0411);
899        assert_eq!(assoc.listen_interval(&data).unwrap(), 200);
900    }
901
902    #[test]
903    fn test_assoc_resp_parse() {
904        let mut buf = vec![0u8; 6];
905        buf[0..2].copy_from_slice(&0x0411u16.to_le_bytes());
906        buf[2..4].copy_from_slice(&0u16.to_le_bytes());
907        buf[4..6].copy_from_slice(&1u16.to_le_bytes());
908
909        let assoc = Dot11AssocResp::new(0);
910        assert_eq!(assoc.capability(&buf).unwrap(), 0x0411);
911        assert_eq!(assoc.status_code(&buf).unwrap(), 0);
912        assert_eq!(assoc.aid(&buf).unwrap(), 1);
913    }
914
915    #[test]
916    fn test_reassoc_req_parse() {
917        let mut buf = vec![0u8; 10];
918        buf[0..2].copy_from_slice(&0x0411u16.to_le_bytes());
919        buf[2..4].copy_from_slice(&200u16.to_le_bytes());
920        buf[4..10].copy_from_slice(&[0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF]);
921
922        let reassoc = Dot11ReassocReq::new(0);
923        assert_eq!(reassoc.capability(&buf).unwrap(), 0x0411);
924        assert_eq!(reassoc.listen_interval(&buf).unwrap(), 200);
925        assert_eq!(
926            reassoc.current_ap(&buf).unwrap(),
927            MacAddress::new([0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF])
928        );
929    }
930
931    #[test]
932    fn test_reassoc_req_build_roundtrip() {
933        let ap = MacAddress::new([0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF]);
934        let data = Dot11ReassocReq::build(0x0411, 200, ap);
935        assert_eq!(data.len(), REASSOC_REQ_FIXED_LEN);
936
937        let reassoc = Dot11ReassocReq::new(0);
938        assert_eq!(reassoc.current_ap(&data).unwrap(), ap);
939    }
940
941    #[test]
942    fn test_action_parse() {
943        let buf = vec![0x00u8, 0x04]; // category=spectrum_mgmt, action=CSA
944        let action = Dot11Action::new(0);
945        assert_eq!(action.category(&buf).unwrap(), 0x00);
946        assert_eq!(action.action_code(&buf).unwrap(), 0x04);
947    }
948
949    #[test]
950    fn test_action_build() {
951        let data = Dot11Action::build(0x0A); // WNM category
952        assert_eq!(data.len(), 1);
953        assert_eq!(data[0], 0x0A);
954    }
955
956    #[test]
957    fn test_probe_req() {
958        let req = Dot11ProbeReq::new(0);
959        assert_eq!(req.header_len(), 0);
960        assert_eq!(req.ie_offset(), 0);
961    }
962
963    #[test]
964    fn test_probe_resp_parse() {
965        let mut buf = vec![0u8; 12];
966        buf[0..8].copy_from_slice(&1000u64.to_le_bytes());
967        buf[8..10].copy_from_slice(&100u16.to_le_bytes());
968        buf[10..12].copy_from_slice(&0x0001u16.to_le_bytes());
969
970        let resp = Dot11ProbeResp::new(0);
971        assert_eq!(resp.timestamp(&buf).unwrap(), 1000);
972        assert_eq!(resp.beacon_interval(&buf).unwrap(), 100);
973        assert_eq!(resp.capability(&buf).unwrap(), 0x0001);
974    }
975
976    #[test]
977    fn test_reassoc_resp() {
978        let data = Dot11ReassocResp::build(0x0411, 0, 1);
979        let resp = Dot11ReassocResp::new(0);
980        assert_eq!(resp.capability(&data).unwrap(), 0x0411);
981        assert_eq!(resp.status_code(&data).unwrap(), 0);
982        assert_eq!(resp.aid(&data).unwrap(), 1);
983    }
984}