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