Skip to main content

stackforge_core/layer/dot11/
builder.rs

1//! IEEE 802.11 frame builder.
2//!
3//! Provides a fluent API for constructing 802.11 frames, including
4//! convenience methods for common frame types (beacon, probe, auth, etc.).
5
6use super::management::{
7    Dot11AssocReq, Dot11AssocResp, Dot11Auth, Dot11Beacon, Dot11Deauth, Dot11ProbeResp,
8};
9use super::{DOT11_MGMT_HEADER_LEN, DOT11_WDS_HEADER_LEN, build_frame_control, crc32_ieee, types};
10use crate::layer::dot11::data::Dot11QoS;
11use crate::layer::dot11::ie::Dot11Elt;
12use crate::layer::field::MacAddress;
13
14// ============================================================================
15// Dot11Builder
16// ============================================================================
17
18/// Builder for constructing 802.11 frames with a fluent API.
19///
20/// # Example
21/// ```ignore
22/// let frame = Dot11Builder::new()
23///     .frame_type(types::frame_type::MANAGEMENT)
24///     .subtype(types::mgmt_subtype::BEACON)
25///     .addr1(MacAddress::BROADCAST)
26///     .addr2(bssid)
27///     .addr3(bssid)
28///     .build();
29/// ```
30#[derive(Debug, Clone)]
31pub struct Dot11Builder {
32    /// Protocol version (default 0).
33    pub proto: u8,
34    /// Frame type (Management/Control/Data).
35    pub frame_type: u8,
36    /// Frame subtype.
37    pub subtype: u8,
38    /// Flags byte (to_DS, from_DS, retry, etc.).
39    pub flags: u8,
40    /// Duration/ID field.
41    pub duration: u16,
42    /// Address 1 (receiver/destination).
43    pub addr1: MacAddress,
44    /// Address 2 (transmitter/source).
45    pub addr2: MacAddress,
46    /// Address 3 (BSSID or other).
47    pub addr3: MacAddress,
48    /// Address 4 (only in WDS mode).
49    pub addr4: Option<MacAddress>,
50    /// Sequence Control field.
51    pub seq_ctrl: u16,
52    /// Frame body payload (management body, data, etc.).
53    pub body: Vec<u8>,
54    /// Whether to append FCS.
55    pub with_fcs: bool,
56}
57
58impl Default for Dot11Builder {
59    fn default() -> Self {
60        Self {
61            proto: 0,
62            frame_type: types::frame_type::MANAGEMENT,
63            subtype: 0,
64            flags: 0,
65            duration: 0,
66            addr1: MacAddress::BROADCAST,
67            addr2: MacAddress::ZERO,
68            addr3: MacAddress::ZERO,
69            addr4: None,
70            seq_ctrl: 0,
71            body: Vec::new(),
72            with_fcs: false,
73        }
74    }
75}
76
77impl Dot11Builder {
78    /// Create a new builder with default values.
79    pub fn new() -> Self {
80        Self::default()
81    }
82
83    /// Set the protocol version (usually 0).
84    pub fn proto(mut self, proto: u8) -> Self {
85        self.proto = proto;
86        self
87    }
88
89    /// Set the frame type.
90    pub fn frame_type(mut self, ft: u8) -> Self {
91        self.frame_type = ft;
92        self
93    }
94
95    /// Set the frame subtype.
96    pub fn subtype(mut self, st: u8) -> Self {
97        self.subtype = st;
98        self
99    }
100
101    /// Set the flags byte.
102    pub fn flags(mut self, flags: u8) -> Self {
103        self.flags = flags;
104        self
105    }
106
107    /// Set the to_DS flag.
108    pub fn to_ds(mut self, val: bool) -> Self {
109        if val {
110            self.flags |= types::fc_flags::TO_DS;
111        } else {
112            self.flags &= !types::fc_flags::TO_DS;
113        }
114        self
115    }
116
117    /// Set the from_DS flag.
118    pub fn from_ds(mut self, val: bool) -> Self {
119        if val {
120            self.flags |= types::fc_flags::FROM_DS;
121        } else {
122            self.flags &= !types::fc_flags::FROM_DS;
123        }
124        self
125    }
126
127    /// Set the retry flag.
128    pub fn retry(mut self, val: bool) -> Self {
129        if val {
130            self.flags |= types::fc_flags::RETRY;
131        } else {
132            self.flags &= !types::fc_flags::RETRY;
133        }
134        self
135    }
136
137    /// Set the protected frame flag.
138    pub fn protected(mut self, val: bool) -> Self {
139        if val {
140            self.flags |= types::fc_flags::PROTECTED;
141        } else {
142            self.flags &= !types::fc_flags::PROTECTED;
143        }
144        self
145    }
146
147    /// Set the Duration/ID field.
148    pub fn duration(mut self, dur: u16) -> Self {
149        self.duration = dur;
150        self
151    }
152
153    /// Set Address 1 (receiver/destination).
154    pub fn addr1(mut self, mac: MacAddress) -> Self {
155        self.addr1 = mac;
156        self
157    }
158
159    /// Set Address 2 (transmitter/source).
160    pub fn addr2(mut self, mac: MacAddress) -> Self {
161        self.addr2 = mac;
162        self
163    }
164
165    /// Set Address 3 (BSSID or other).
166    pub fn addr3(mut self, mac: MacAddress) -> Self {
167        self.addr3 = mac;
168        self
169    }
170
171    /// Set Address 4 (WDS mode).
172    pub fn addr4(mut self, mac: MacAddress) -> Self {
173        self.addr4 = Some(mac);
174        self
175    }
176
177    /// Set the Sequence Control field directly.
178    pub fn seq_ctrl(mut self, sc: u16) -> Self {
179        self.seq_ctrl = sc;
180        self
181    }
182
183    /// Set sequence number and fragment number.
184    pub fn seq_num(mut self, seq: u16, frag: u8) -> Self {
185        self.seq_ctrl = ((seq & 0x0FFF) << 4) | ((frag & 0x0F) as u16);
186        self
187    }
188
189    /// Set the frame body (payload after the header).
190    pub fn body(mut self, body: Vec<u8>) -> Self {
191        self.body = body;
192        self
193    }
194
195    /// Enable FCS (Frame Check Sequence) appending.
196    pub fn with_fcs(mut self, enable: bool) -> Self {
197        self.with_fcs = enable;
198        self
199    }
200
201    /// Calculate the header size based on frame type and addresses.
202    pub fn header_size(&self) -> usize {
203        if self.addr4.is_some() {
204            DOT11_WDS_HEADER_LEN
205        } else {
206            match self.frame_type {
207                types::frame_type::CONTROL => match self.subtype {
208                    types::ctrl_subtype::ACK | types::ctrl_subtype::CTS => 10,
209                    _ => 16,
210                },
211                _ => DOT11_MGMT_HEADER_LEN,
212            }
213        }
214    }
215
216    /// Build the frame bytes.
217    pub fn build(&self) -> Vec<u8> {
218        let header_len = self.header_size();
219        let total = header_len + self.body.len() + if self.with_fcs { 4 } else { 0 };
220        let mut buf = vec![0u8; total];
221
222        // Frame Control
223        let fc = build_frame_control(self.proto, self.frame_type, self.subtype, self.flags);
224        buf[0..2].copy_from_slice(&fc.to_le_bytes());
225
226        // Duration/ID
227        buf[2..4].copy_from_slice(&self.duration.to_le_bytes());
228
229        // Address 1 (always present at offset 4)
230        buf[4..10].copy_from_slice(self.addr1.as_bytes());
231
232        // Address 2 (present if header_len >= 16)
233        if header_len >= 16 {
234            buf[10..16].copy_from_slice(self.addr2.as_bytes());
235        }
236
237        // Address 3 + Sequence Control (present if header_len >= 24)
238        if header_len >= 24 {
239            buf[16..22].copy_from_slice(self.addr3.as_bytes());
240            buf[22..24].copy_from_slice(&self.seq_ctrl.to_le_bytes());
241        }
242
243        // Address 4 (present if header_len >= 30)
244        if let Some(ref a4) = self.addr4 {
245            if header_len >= 30 {
246                buf[24..30].copy_from_slice(a4.as_bytes());
247            }
248        }
249
250        // Body
251        if !self.body.is_empty() {
252            buf[header_len..header_len + self.body.len()].copy_from_slice(&self.body);
253        }
254
255        // FCS
256        if self.with_fcs {
257            let fcs_offset = header_len + self.body.len();
258            let fcs = crc32_ieee(&buf[0..fcs_offset]);
259            buf[fcs_offset..fcs_offset + 4].copy_from_slice(&fcs.to_le_bytes());
260        }
261
262        buf
263    }
264
265    // ========================================================================
266    // Convenience constructors
267    // ========================================================================
268
269    /// Create a Beacon frame builder.
270    pub fn beacon(bssid: MacAddress) -> Self {
271        Self::new()
272            .frame_type(types::frame_type::MANAGEMENT)
273            .subtype(types::mgmt_subtype::BEACON)
274            .addr1(MacAddress::BROADCAST)
275            .addr2(bssid)
276            .addr3(bssid)
277    }
278
279    /// Create a Probe Request frame builder.
280    pub fn probe_request(src: MacAddress) -> Self {
281        Self::new()
282            .frame_type(types::frame_type::MANAGEMENT)
283            .subtype(types::mgmt_subtype::PROBE_REQ)
284            .addr1(MacAddress::BROADCAST)
285            .addr2(src)
286            .addr3(MacAddress::BROADCAST)
287    }
288
289    /// Create a Probe Response frame builder.
290    pub fn probe_response(dst: MacAddress, bssid: MacAddress) -> Self {
291        Self::new()
292            .frame_type(types::frame_type::MANAGEMENT)
293            .subtype(types::mgmt_subtype::PROBE_RESP)
294            .addr1(dst)
295            .addr2(bssid)
296            .addr3(bssid)
297    }
298
299    /// Create an Authentication frame builder.
300    pub fn authentication(dst: MacAddress, src: MacAddress, bssid: MacAddress) -> Self {
301        Self::new()
302            .frame_type(types::frame_type::MANAGEMENT)
303            .subtype(types::mgmt_subtype::AUTH)
304            .addr1(dst)
305            .addr2(src)
306            .addr3(bssid)
307    }
308
309    /// Create a Deauthentication frame builder.
310    pub fn deauthentication(dst: MacAddress, src: MacAddress, bssid: MacAddress) -> Self {
311        Self::new()
312            .frame_type(types::frame_type::MANAGEMENT)
313            .subtype(types::mgmt_subtype::DEAUTH)
314            .addr1(dst)
315            .addr2(src)
316            .addr3(bssid)
317    }
318
319    /// Create an Association Request frame builder.
320    pub fn assoc_request(dst: MacAddress, src: MacAddress) -> Self {
321        Self::new()
322            .frame_type(types::frame_type::MANAGEMENT)
323            .subtype(types::mgmt_subtype::ASSOC_REQ)
324            .addr1(dst)
325            .addr2(src)
326            .addr3(dst)
327    }
328
329    /// Create an ACK frame builder.
330    pub fn ack(dst: MacAddress) -> Self {
331        Self::new()
332            .frame_type(types::frame_type::CONTROL)
333            .subtype(types::ctrl_subtype::ACK)
334            .addr1(dst)
335    }
336
337    /// Create an RTS frame builder.
338    pub fn rts(dst: MacAddress, src: MacAddress) -> Self {
339        Self::new()
340            .frame_type(types::frame_type::CONTROL)
341            .subtype(types::ctrl_subtype::RTS)
342            .addr1(dst)
343            .addr2(src)
344    }
345
346    /// Create a CTS frame builder.
347    pub fn cts(dst: MacAddress) -> Self {
348        Self::new()
349            .frame_type(types::frame_type::CONTROL)
350            .subtype(types::ctrl_subtype::CTS)
351            .addr1(dst)
352    }
353
354    /// Create a Data frame builder (to AP: to_DS=1).
355    pub fn data_to_ap(bssid: MacAddress, src: MacAddress, dst: MacAddress) -> Self {
356        Self::new()
357            .frame_type(types::frame_type::DATA)
358            .subtype(types::data_subtype::DATA)
359            .to_ds(true)
360            .addr1(bssid)
361            .addr2(src)
362            .addr3(dst)
363    }
364
365    /// Create a Data frame builder (from AP: from_DS=1).
366    pub fn data_from_ap(dst: MacAddress, bssid: MacAddress, src: MacAddress) -> Self {
367        Self::new()
368            .frame_type(types::frame_type::DATA)
369            .subtype(types::data_subtype::DATA)
370            .from_ds(true)
371            .addr1(dst)
372            .addr2(bssid)
373            .addr3(src)
374    }
375
376    /// Create a QoS Data frame builder (to AP: to_DS=1).
377    pub fn qos_data_to_ap(bssid: MacAddress, src: MacAddress, dst: MacAddress) -> Self {
378        Self::new()
379            .frame_type(types::frame_type::DATA)
380            .subtype(types::data_subtype::QOS_DATA)
381            .to_ds(true)
382            .addr1(bssid)
383            .addr2(src)
384            .addr3(dst)
385    }
386
387    /// Create a WDS Data frame builder (to_DS=1, from_DS=1).
388    pub fn data_wds(ra: MacAddress, ta: MacAddress, da: MacAddress, sa: MacAddress) -> Self {
389        Self::new()
390            .frame_type(types::frame_type::DATA)
391            .subtype(types::data_subtype::DATA)
392            .to_ds(true)
393            .from_ds(true)
394            .addr1(ra)
395            .addr2(ta)
396            .addr3(da)
397            .addr4(sa)
398    }
399
400    // ========================================================================
401    // Convenience: build with management body
402    // ========================================================================
403
404    /// Build a beacon frame with the given body parameters and IEs.
405    pub fn build_beacon(
406        bssid: MacAddress,
407        timestamp: u64,
408        beacon_interval: u16,
409        capability: u16,
410        ies: &[Dot11Elt],
411    ) -> Vec<u8> {
412        let mut body = Dot11Beacon::build(timestamp, beacon_interval, capability);
413        body.extend(Dot11Elt::build_chain(ies));
414        Self::beacon(bssid).body(body).build()
415    }
416
417    /// Build a probe request frame with the given IEs.
418    pub fn build_probe_request(src: MacAddress, ies: &[Dot11Elt]) -> Vec<u8> {
419        let body = Dot11Elt::build_chain(ies);
420        Self::probe_request(src).body(body).build()
421    }
422
423    /// Build a probe response frame with the given body parameters and IEs.
424    pub fn build_probe_response(
425        dst: MacAddress,
426        bssid: MacAddress,
427        timestamp: u64,
428        beacon_interval: u16,
429        capability: u16,
430        ies: &[Dot11Elt],
431    ) -> Vec<u8> {
432        let mut body = Dot11ProbeResp::build(timestamp, beacon_interval, capability);
433        body.extend(Dot11Elt::build_chain(ies));
434        Self::probe_response(dst, bssid).body(body).build()
435    }
436
437    /// Build an authentication frame.
438    pub fn build_auth(
439        dst: MacAddress,
440        src: MacAddress,
441        bssid: MacAddress,
442        algo: u16,
443        seqnum: u16,
444        status: u16,
445    ) -> Vec<u8> {
446        let body = Dot11Auth::build(algo, seqnum, status);
447        Self::authentication(dst, src, bssid).body(body).build()
448    }
449
450    /// Build a deauthentication frame.
451    pub fn build_deauth(
452        dst: MacAddress,
453        src: MacAddress,
454        bssid: MacAddress,
455        reason: u16,
456    ) -> Vec<u8> {
457        let body = Dot11Deauth::build(reason);
458        Self::deauthentication(dst, src, bssid).body(body).build()
459    }
460
461    /// Build an association request frame.
462    pub fn build_assoc_request(
463        dst: MacAddress,
464        src: MacAddress,
465        capability: u16,
466        listen_interval: u16,
467        ies: &[Dot11Elt],
468    ) -> Vec<u8> {
469        let mut body = Dot11AssocReq::build(capability, listen_interval);
470        body.extend(Dot11Elt::build_chain(ies));
471        Self::assoc_request(dst, src).body(body).build()
472    }
473
474    /// Build an association response frame.
475    pub fn build_assoc_response(
476        dst: MacAddress,
477        bssid: MacAddress,
478        capability: u16,
479        status: u16,
480        aid: u16,
481        ies: &[Dot11Elt],
482    ) -> Vec<u8> {
483        let mut body = Dot11AssocResp::build(capability, status, aid);
484        body.extend(Dot11Elt::build_chain(ies));
485        Self::new()
486            .frame_type(types::frame_type::MANAGEMENT)
487            .subtype(types::mgmt_subtype::ASSOC_RESP)
488            .addr1(dst)
489            .addr2(bssid)
490            .addr3(bssid)
491            .body(body)
492            .build()
493    }
494
495    /// Build a data frame with QoS header.
496    pub fn build_qos_data(
497        bssid: MacAddress,
498        src: MacAddress,
499        dst: MacAddress,
500        tid: u8,
501        payload: &[u8],
502    ) -> Vec<u8> {
503        let mut body = Dot11QoS::build(tid, false, 0, false, 0);
504        body.extend_from_slice(payload);
505        Self::qos_data_to_ap(bssid, src, dst).body(body).build()
506    }
507}
508
509// ============================================================================
510// Tests
511// ============================================================================
512
513#[cfg(test)]
514mod tests {
515    use super::*;
516    use crate::layer::dot11::Dot11Layer;
517
518    #[test]
519    fn test_builder_default() {
520        let builder = Dot11Builder::new();
521        assert_eq!(builder.frame_type, types::frame_type::MANAGEMENT);
522        assert_eq!(builder.subtype, 0);
523        assert_eq!(builder.flags, 0);
524        assert_eq!(builder.duration, 0);
525        assert_eq!(builder.addr1, MacAddress::BROADCAST);
526        assert!(!builder.with_fcs);
527    }
528
529    #[test]
530    fn test_builder_beacon() {
531        let bssid = MacAddress::new([0x00, 0x11, 0x22, 0x33, 0x44, 0x55]);
532        let frame = Dot11Builder::beacon(bssid).build();
533
534        assert_eq!(frame.len(), DOT11_MGMT_HEADER_LEN);
535
536        let layer = Dot11Layer::new(0, frame.len());
537        assert_eq!(
538            layer.frame_type(&frame).unwrap(),
539            types::frame_type::MANAGEMENT
540        );
541        assert_eq!(layer.subtype(&frame).unwrap(), types::mgmt_subtype::BEACON);
542        assert!(layer.addr1(&frame).unwrap().is_broadcast());
543        assert_eq!(layer.addr2(&frame).unwrap(), bssid);
544        assert_eq!(layer.addr3(&frame).unwrap(), bssid);
545    }
546
547    #[test]
548    fn test_builder_ack() {
549        let dst = MacAddress::new([0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF]);
550        let frame = Dot11Builder::ack(dst).build();
551
552        assert_eq!(frame.len(), 10);
553
554        let layer = Dot11Layer::new(0, frame.len());
555        assert_eq!(
556            layer.frame_type(&frame).unwrap(),
557            types::frame_type::CONTROL
558        );
559        assert_eq!(layer.subtype(&frame).unwrap(), types::ctrl_subtype::ACK);
560        assert_eq!(layer.addr1(&frame).unwrap(), dst);
561    }
562
563    #[test]
564    fn test_builder_rts() {
565        let dst = MacAddress::new([0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF]);
566        let src = MacAddress::new([0x11, 0x22, 0x33, 0x44, 0x55, 0x66]);
567        let frame = Dot11Builder::rts(dst, src).build();
568
569        assert_eq!(frame.len(), 16);
570
571        let layer = Dot11Layer::new(0, frame.len());
572        assert_eq!(
573            layer.frame_type(&frame).unwrap(),
574            types::frame_type::CONTROL
575        );
576        assert_eq!(layer.subtype(&frame).unwrap(), types::ctrl_subtype::RTS);
577        assert_eq!(layer.addr1(&frame).unwrap(), dst);
578        assert_eq!(layer.addr2(&frame).unwrap(), src);
579    }
580
581    #[test]
582    fn test_builder_wds_data() {
583        let ra = MacAddress::new([0x01; 6]);
584        let ta = MacAddress::new([0x02; 6]);
585        let da = MacAddress::new([0x03; 6]);
586        let sa = MacAddress::new([0x04; 6]);
587        let frame = Dot11Builder::data_wds(ra, ta, da, sa).build();
588
589        assert_eq!(frame.len(), DOT11_WDS_HEADER_LEN);
590
591        let layer = Dot11Layer::new(0, frame.len());
592        assert_eq!(layer.frame_type(&frame).unwrap(), types::frame_type::DATA);
593        assert!(layer.to_ds(&frame).unwrap());
594        assert!(layer.from_ds(&frame).unwrap());
595        assert!(layer.has_addr4(&frame));
596        assert_eq!(layer.addr1(&frame).unwrap(), ra);
597        assert_eq!(layer.addr2(&frame).unwrap(), ta);
598        assert_eq!(layer.addr3(&frame).unwrap(), da);
599        assert_eq!(layer.addr4(&frame).unwrap(), sa);
600    }
601
602    #[test]
603    fn test_builder_with_body() {
604        let bssid = MacAddress::new([0x00, 0x11, 0x22, 0x33, 0x44, 0x55]);
605        let body = vec![0xAA, 0xBB, 0xCC];
606        let frame = Dot11Builder::beacon(bssid).body(body.clone()).build();
607
608        assert_eq!(frame.len(), DOT11_MGMT_HEADER_LEN + 3);
609        assert_eq!(&frame[DOT11_MGMT_HEADER_LEN..], &[0xAA, 0xBB, 0xCC]);
610    }
611
612    #[test]
613    fn test_builder_with_fcs() {
614        let bssid = MacAddress::new([0x00, 0x11, 0x22, 0x33, 0x44, 0x55]);
615        let frame = Dot11Builder::beacon(bssid).with_fcs(true).build();
616
617        assert_eq!(frame.len(), DOT11_MGMT_HEADER_LEN + 4); // +4 for FCS
618
619        // Verify FCS
620        let data = &frame[..DOT11_MGMT_HEADER_LEN];
621        let expected_fcs = crc32_ieee(data);
622        let actual_fcs = u32::from_le_bytes([
623            frame[DOT11_MGMT_HEADER_LEN],
624            frame[DOT11_MGMT_HEADER_LEN + 1],
625            frame[DOT11_MGMT_HEADER_LEN + 2],
626            frame[DOT11_MGMT_HEADER_LEN + 3],
627        ]);
628        assert_eq!(actual_fcs, expected_fcs);
629    }
630
631    #[test]
632    fn test_builder_flags() {
633        let frame = Dot11Builder::new()
634            .frame_type(types::frame_type::DATA)
635            .subtype(0)
636            .to_ds(true)
637            .retry(true)
638            .protected(true)
639            .build();
640
641        let layer = Dot11Layer::new(0, frame.len());
642        assert!(layer.to_ds(&frame).unwrap());
643        assert!(!layer.from_ds(&frame).unwrap());
644        assert!(layer.retry(&frame).unwrap());
645        assert!(layer.protected(&frame).unwrap());
646    }
647
648    #[test]
649    fn test_builder_seq_num() {
650        let frame = Dot11Builder::new().seq_num(100, 3).build();
651
652        let layer = Dot11Layer::new(0, frame.len());
653        assert_eq!(layer.sequence_num(&frame).unwrap(), 100);
654        assert_eq!(layer.fragment_num(&frame).unwrap(), 3);
655    }
656
657    #[test]
658    fn test_build_beacon_convenience() {
659        let bssid = MacAddress::new([0x00, 0x11, 0x22, 0x33, 0x44, 0x55]);
660        let ies = vec![
661            Dot11Elt::ssid("TestNetwork"),
662            Dot11Elt::new(types::ie_id::RATES, vec![0x82, 0x84, 0x8B, 0x96]),
663            Dot11Elt::new(types::ie_id::DS_PARAMETER_SET, vec![6]),
664        ];
665
666        let frame = Dot11Builder::build_beacon(bssid, 0, 100, 0x0431, &ies);
667
668        let layer = Dot11Layer::new(0, frame.len());
669        assert_eq!(
670            layer.frame_type(&frame).unwrap(),
671            types::frame_type::MANAGEMENT
672        );
673        assert_eq!(layer.subtype(&frame).unwrap(), types::mgmt_subtype::BEACON);
674
675        // Verify body contains beacon fixed fields + IEs
676        let body_offset = DOT11_MGMT_HEADER_LEN;
677        // Timestamp (8) + Beacon Interval (2) + Capability (2) = 12
678        assert!(frame.len() > body_offset + 12);
679        // Check beacon interval
680        let bi = u16::from_le_bytes([frame[body_offset + 8], frame[body_offset + 9]]);
681        assert_eq!(bi, 100);
682    }
683
684    #[test]
685    fn test_build_auth_convenience() {
686        let dst = MacAddress::new([0xAA; 6]);
687        let src = MacAddress::new([0xBB; 6]);
688        let bssid = MacAddress::new([0xAA; 6]);
689
690        let frame = Dot11Builder::build_auth(dst, src, bssid, 0, 1, 0);
691
692        let layer = Dot11Layer::new(0, frame.len());
693        assert_eq!(layer.subtype(&frame).unwrap(), types::mgmt_subtype::AUTH);
694
695        // Check auth body
696        let body_offset = DOT11_MGMT_HEADER_LEN;
697        let algo = u16::from_le_bytes([frame[body_offset], frame[body_offset + 1]]);
698        let seqnum = u16::from_le_bytes([frame[body_offset + 2], frame[body_offset + 3]]);
699        let status = u16::from_le_bytes([frame[body_offset + 4], frame[body_offset + 5]]);
700        assert_eq!(algo, 0);
701        assert_eq!(seqnum, 1);
702        assert_eq!(status, 0);
703    }
704
705    #[test]
706    fn test_build_deauth_convenience() {
707        let dst = MacAddress::BROADCAST;
708        let src = MacAddress::new([0xBB; 6]);
709        let bssid = MacAddress::new([0xBB; 6]);
710
711        let frame = Dot11Builder::build_deauth(dst, src, bssid, 7);
712
713        let layer = Dot11Layer::new(0, frame.len());
714        assert_eq!(layer.subtype(&frame).unwrap(), types::mgmt_subtype::DEAUTH);
715
716        let body_offset = DOT11_MGMT_HEADER_LEN;
717        let reason = u16::from_le_bytes([frame[body_offset], frame[body_offset + 1]]);
718        assert_eq!(reason, 7);
719    }
720
721    #[test]
722    fn test_build_probe_request_convenience() {
723        let src = MacAddress::new([0xCC; 6]);
724        let ies = vec![
725            Dot11Elt::ssid(""), // broadcast probe
726            Dot11Elt::new(types::ie_id::RATES, vec![0x82, 0x84]),
727        ];
728
729        let frame = Dot11Builder::build_probe_request(src, &ies);
730
731        let layer = Dot11Layer::new(0, frame.len());
732        assert_eq!(
733            layer.subtype(&frame).unwrap(),
734            types::mgmt_subtype::PROBE_REQ
735        );
736        assert!(layer.addr1(&frame).unwrap().is_broadcast());
737        assert_eq!(layer.addr2(&frame).unwrap(), src);
738
739        // Verify IEs are in the body
740        let ie_data = &frame[DOT11_MGMT_HEADER_LEN..];
741        let ies_parsed = Dot11Elt::parse_all(ie_data, 0);
742        assert_eq!(ies_parsed.len(), 2);
743        assert_eq!(ies_parsed[0].id, 0);
744    }
745
746    #[test]
747    fn test_build_qos_data_convenience() {
748        let bssid = MacAddress::new([0xAA; 6]);
749        let src = MacAddress::new([0xBB; 6]);
750        let dst = MacAddress::new([0xCC; 6]);
751        let payload = b"hello";
752
753        let frame = Dot11Builder::build_qos_data(bssid, src, dst, 3, payload);
754
755        let layer = Dot11Layer::new(0, frame.len());
756        assert_eq!(layer.frame_type(&frame).unwrap(), types::frame_type::DATA);
757        assert_eq!(
758            layer.subtype(&frame).unwrap(),
759            types::data_subtype::QOS_DATA
760        );
761        assert!(layer.to_ds(&frame).unwrap());
762
763        // QoS header is at offset 24 (after standard header)
764        let qos = Dot11QoS::new(DOT11_MGMT_HEADER_LEN);
765        assert_eq!(qos.tid(&frame).unwrap(), 3);
766
767        // Payload follows QoS
768        let payload_offset = DOT11_MGMT_HEADER_LEN + 2;
769        assert_eq!(&frame[payload_offset..payload_offset + 5], b"hello");
770    }
771
772    #[test]
773    fn test_header_size_calculation() {
774        assert_eq!(
775            Dot11Builder::beacon(MacAddress::BROADCAST).header_size(),
776            24
777        );
778        assert_eq!(Dot11Builder::ack(MacAddress::BROADCAST).header_size(), 10);
779        assert_eq!(
780            Dot11Builder::rts(MacAddress::BROADCAST, MacAddress::ZERO).header_size(),
781            16
782        );
783        assert_eq!(
784            Dot11Builder::data_wds(
785                MacAddress::ZERO,
786                MacAddress::ZERO,
787                MacAddress::ZERO,
788                MacAddress::ZERO
789            )
790            .header_size(),
791            30
792        );
793    }
794}