Skip to main content

stackforge_core/layer/dot15d4/
beacon.rs

1//! IEEE 802.15.4 Beacon frame parsing and building.
2//!
3//! A beacon frame payload follows the base 802.15.4 header and contains:
4//! - Superframe Specification (2 bytes, LE)
5//! - GTS Specification (1 byte)
6//! - GTS Directions (0 or 1 byte, present if GTS descriptor count > 0)
7//! - GTS Descriptor List (variable, 3 bytes per descriptor)
8//! - Pending Address Specification (1 byte)
9//! - Pending Address List (variable)
10
11use crate::layer::field::{FieldError, read_u16_le};
12
13/// GTS descriptor: short address + starting slot/length.
14#[derive(Debug, Clone, PartialEq, Eq)]
15pub struct GtsDescriptor {
16    /// Short address of the device (2 bytes, LE).
17    pub short_addr: u16,
18    /// GTS starting slot (4 bits).
19    pub starting_slot: u8,
20    /// GTS length (4 bits).
21    pub length: u8,
22}
23
24/// Parsed beacon frame payload.
25#[derive(Debug, Clone, PartialEq, Eq)]
26pub struct BeaconPayload {
27    // Superframe Specification (2 bytes, LE)
28    /// Beacon order (4 bits).
29    pub beacon_order: u8,
30    /// Superframe order (4 bits).
31    pub superframe_order: u8,
32    /// Final CAP slot (4 bits).
33    pub final_cap_slot: u8,
34    /// Battery life extension (1 bit).
35    pub battery_life_ext: bool,
36    /// PAN coordinator (1 bit).
37    pub pan_coordinator: bool,
38    /// Association permit (1 bit).
39    pub assoc_permit: bool,
40
41    // GTS Specification (1 byte)
42    /// GTS descriptor count (3 bits).
43    pub gts_desc_count: u8,
44    /// GTS permit (1 bit).
45    pub gts_permit: bool,
46
47    // GTS Directions (0 or 1 byte)
48    /// GTS direction mask (7 bits), present if `gts_desc_count` > 0.
49    pub gts_dir_mask: u8,
50
51    // GTS Descriptor List
52    /// GTS descriptors (3 bytes each).
53    pub gts_descriptors: Vec<GtsDescriptor>,
54
55    // Pending Address Specification (1 byte)
56    /// Number of short addresses pending (3 bits).
57    pub pa_num_short: u8,
58    /// Number of extended (long) addresses pending (3 bits).
59    pub pa_num_long: u8,
60
61    // Pending Address List
62    /// Short addresses pending (2 bytes each).
63    pub pa_short_addresses: Vec<u16>,
64    /// Long addresses pending (8 bytes each).
65    pub pa_long_addresses: Vec<u64>,
66}
67
68impl BeaconPayload {
69    /// Parse a beacon payload from the buffer at the given offset.
70    /// Returns the parsed payload and the number of bytes consumed.
71    pub fn parse(buf: &[u8], offset: usize) -> Result<(Self, usize), FieldError> {
72        let mut pos = offset;
73
74        // Superframe Specification (2 bytes, LE)
75        if buf.len() < pos + 2 {
76            return Err(FieldError::BufferTooShort {
77                offset: pos,
78                need: 2,
79                have: buf.len().saturating_sub(pos),
80            });
81        }
82        let sf_spec = read_u16_le(buf, pos)?;
83        pos += 2;
84
85        // Scapy bit ordering in bytes: sf_spec is LE u16
86        // Bits 0-3: Beacon Order
87        // Bits 4-7: Superframe Order
88        // Bits 8-11: Final CAP Slot
89        // Bit 12: Battery Life Extension
90        // Bit 13: Reserved
91        // Bit 14: PAN Coordinator
92        // Bit 15: Association Permit
93        let beacon_order = (sf_spec & 0x0F) as u8;
94        let superframe_order = ((sf_spec >> 4) & 0x0F) as u8;
95        let final_cap_slot = ((sf_spec >> 8) & 0x0F) as u8;
96        let battery_life_ext = (sf_spec & 0x1000) != 0;
97        let pan_coordinator = (sf_spec & 0x4000) != 0;
98        let assoc_permit = (sf_spec & 0x8000) != 0;
99
100        // GTS Specification (1 byte)
101        if buf.len() < pos + 1 {
102            return Err(FieldError::BufferTooShort {
103                offset: pos,
104                need: 1,
105                have: buf.len().saturating_sub(pos),
106            });
107        }
108        let gts_spec = buf[pos];
109        pos += 1;
110
111        // Bits 0-2: GTS Descriptor Count
112        // Bits 3-6: Reserved
113        // Bit 7: GTS Permit
114        let gts_desc_count = gts_spec & 0x07;
115        let gts_permit = (gts_spec & 0x80) != 0;
116
117        // GTS Directions (1 byte, present if gts_desc_count > 0)
118        let mut gts_dir_mask: u8 = 0;
119        if gts_desc_count > 0 {
120            if buf.len() < pos + 1 {
121                return Err(FieldError::BufferTooShort {
122                    offset: pos,
123                    need: 1,
124                    have: buf.len().saturating_sub(pos),
125                });
126            }
127            let gts_dir = buf[pos];
128            pos += 1;
129            // Bits 0-6: Direction Mask, Bit 7: Reserved
130            gts_dir_mask = gts_dir & 0x7F;
131        }
132
133        // GTS Descriptor List (3 bytes per descriptor)
134        let gts_list_len = (gts_desc_count as usize) * 3;
135        if buf.len() < pos + gts_list_len {
136            return Err(FieldError::BufferTooShort {
137                offset: pos,
138                need: gts_list_len,
139                have: buf.len().saturating_sub(pos),
140            });
141        }
142        let mut gts_descriptors = Vec::with_capacity(gts_desc_count as usize);
143        for _ in 0..gts_desc_count {
144            let short_addr = read_u16_le(buf, pos)?;
145            let slot_len = buf[pos + 2];
146            let starting_slot = slot_len & 0x0F;
147            let length = (slot_len >> 4) & 0x0F;
148            gts_descriptors.push(GtsDescriptor {
149                short_addr,
150                starting_slot,
151                length,
152            });
153            pos += 3;
154        }
155
156        // Pending Address Specification (1 byte)
157        if buf.len() < pos + 1 {
158            return Err(FieldError::BufferTooShort {
159                offset: pos,
160                need: 1,
161                have: buf.len().saturating_sub(pos),
162            });
163        }
164        let pa_spec = buf[pos];
165        pos += 1;
166
167        // Bits 0-2: Number of Short Addresses Pending
168        // Bit 3: Reserved
169        // Bits 4-6: Number of Extended Addresses Pending
170        // Bit 7: Reserved
171        let pa_num_short = pa_spec & 0x07;
172        let pa_num_long = (pa_spec >> 4) & 0x07;
173
174        // Pending Short Addresses (2 bytes each)
175        let pa_short_len = (pa_num_short as usize) * 2;
176        if buf.len() < pos + pa_short_len {
177            return Err(FieldError::BufferTooShort {
178                offset: pos,
179                need: pa_short_len,
180                have: buf.len().saturating_sub(pos),
181            });
182        }
183        let mut pa_short_addresses = Vec::with_capacity(pa_num_short as usize);
184        for _ in 0..pa_num_short {
185            pa_short_addresses.push(read_u16_le(buf, pos)?);
186            pos += 2;
187        }
188
189        // Pending Long Addresses (8 bytes each)
190        let pa_long_len = (pa_num_long as usize) * 8;
191        if buf.len() < pos + pa_long_len {
192            return Err(FieldError::BufferTooShort {
193                offset: pos,
194                need: pa_long_len,
195                have: buf.len().saturating_sub(pos),
196            });
197        }
198        let mut pa_long_addresses = Vec::with_capacity(pa_num_long as usize);
199        for _ in 0..pa_num_long {
200            let mut bytes = [0u8; 8];
201            bytes.copy_from_slice(&buf[pos..pos + 8]);
202            pa_long_addresses.push(u64::from_le_bytes(bytes));
203            pos += 8;
204        }
205
206        let consumed = pos - offset;
207        Ok((
208            Self {
209                beacon_order,
210                superframe_order,
211                final_cap_slot,
212                battery_life_ext,
213                pan_coordinator,
214                assoc_permit,
215                gts_desc_count,
216                gts_permit,
217                gts_dir_mask,
218                gts_descriptors,
219                pa_num_short,
220                pa_num_long,
221                pa_short_addresses,
222                pa_long_addresses,
223            },
224            consumed,
225        ))
226    }
227
228    /// Build the beacon payload bytes.
229    #[must_use]
230    pub fn build(&self) -> Vec<u8> {
231        let mut out = Vec::new();
232
233        // Superframe Specification (2 bytes, LE)
234        let mut sf_spec: u16 = 0;
235        sf_spec |= u16::from(self.beacon_order) & 0x0F;
236        sf_spec |= (u16::from(self.superframe_order) & 0x0F) << 4;
237        sf_spec |= (u16::from(self.final_cap_slot) & 0x0F) << 8;
238        if self.battery_life_ext {
239            sf_spec |= 0x1000;
240        }
241        if self.pan_coordinator {
242            sf_spec |= 0x4000;
243        }
244        if self.assoc_permit {
245            sf_spec |= 0x8000;
246        }
247        out.extend_from_slice(&sf_spec.to_le_bytes());
248
249        // GTS Specification (1 byte)
250        let gts_count = self.gts_descriptors.len().min(7) as u8;
251        let mut gts_spec: u8 = gts_count & 0x07;
252        if self.gts_permit {
253            gts_spec |= 0x80;
254        }
255        out.push(gts_spec);
256
257        // GTS Directions (1 byte, only if descriptors present)
258        if gts_count > 0 {
259            out.push(self.gts_dir_mask & 0x7F);
260        }
261
262        // GTS Descriptor List (3 bytes each)
263        for desc in &self.gts_descriptors {
264            out.extend_from_slice(&desc.short_addr.to_le_bytes());
265            let slot_len = (desc.starting_slot & 0x0F) | ((desc.length & 0x0F) << 4);
266            out.push(slot_len);
267        }
268
269        // Pending Address Specification (1 byte)
270        let pa_num_short = self.pa_short_addresses.len().min(7) as u8;
271        let pa_num_long = self.pa_long_addresses.len().min(7) as u8;
272        let pa_spec = (pa_num_short & 0x07) | ((pa_num_long & 0x07) << 4);
273        out.push(pa_spec);
274
275        // Pending Short Addresses
276        for &addr in &self.pa_short_addresses {
277            out.extend_from_slice(&addr.to_le_bytes());
278        }
279
280        // Pending Long Addresses
281        for &addr in &self.pa_long_addresses {
282            out.extend_from_slice(&addr.to_le_bytes());
283        }
284
285        out
286    }
287
288    /// Get a human-readable summary of this beacon.
289    #[must_use]
290    pub fn summary(&self) -> String {
291        format!(
292            "802.15.4 Beacon assocPermit({}) panCoord({}) beaconOrder={} sfOrder={}",
293            self.assoc_permit, self.pan_coordinator, self.beacon_order, self.superframe_order,
294        )
295    }
296}
297
298impl Default for BeaconPayload {
299    fn default() -> Self {
300        Self {
301            beacon_order: 15,
302            superframe_order: 15,
303            final_cap_slot: 15,
304            battery_life_ext: false,
305            pan_coordinator: false,
306            assoc_permit: false,
307            gts_desc_count: 0,
308            gts_permit: true,
309            gts_dir_mask: 0,
310            gts_descriptors: Vec::new(),
311            pa_num_short: 0,
312            pa_num_long: 0,
313            pa_short_addresses: Vec::new(),
314            pa_long_addresses: Vec::new(),
315        }
316    }
317}
318
319#[cfg(test)]
320mod tests {
321    use super::*;
322
323    #[test]
324    fn test_parse_minimal_beacon() {
325        // Minimal beacon: sf_spec=0xFF0F (beacon_order=15, sf_order=15, final_cap=15),
326        //   gts_spec=0x80 (permit=true, count=0),
327        //   pa_spec=0x00
328        let buf = [
329            0xFF, 0x8F, // Superframe Spec (LE): beacon=15, sf=15, final_cap=15, pan_coord=1
330            0x80, // GTS spec: permit=true, count=0
331            0x00, // Pending addr spec: 0 short, 0 long
332        ];
333        let (beacon, consumed) = BeaconPayload::parse(&buf, 0).unwrap();
334        assert_eq!(consumed, 4);
335        assert_eq!(beacon.beacon_order, 15);
336        assert_eq!(beacon.superframe_order, 15);
337        assert_eq!(beacon.final_cap_slot, 15); // bits 8-11 of 0x8FFF = 0xF
338        assert!(beacon.gts_permit);
339        assert_eq!(beacon.gts_desc_count, 0);
340        assert_eq!(beacon.pa_num_short, 0);
341        assert_eq!(beacon.pa_num_long, 0);
342    }
343
344    #[test]
345    fn test_parse_beacon_with_defaults() {
346        // Build with defaults and round-trip
347        let beacon = BeaconPayload::default();
348        let bytes = beacon.build();
349        let (parsed, consumed) = BeaconPayload::parse(&bytes, 0).unwrap();
350        assert_eq!(consumed, bytes.len());
351        assert_eq!(parsed.beacon_order, 15);
352        assert_eq!(parsed.superframe_order, 15);
353        assert_eq!(parsed.final_cap_slot, 15);
354        assert!(!parsed.battery_life_ext);
355        assert!(!parsed.pan_coordinator);
356        assert!(!parsed.assoc_permit);
357        assert!(parsed.gts_permit);
358        assert_eq!(parsed.gts_desc_count, 0);
359    }
360
361    #[test]
362    fn test_beacon_with_gts_descriptors() {
363        let beacon = BeaconPayload {
364            beacon_order: 7,
365            superframe_order: 3,
366            final_cap_slot: 10,
367            battery_life_ext: false,
368            pan_coordinator: true,
369            assoc_permit: true,
370            gts_desc_count: 2,
371            gts_permit: true,
372            gts_dir_mask: 0x03,
373            gts_descriptors: vec![
374                GtsDescriptor {
375                    short_addr: 0x1234,
376                    starting_slot: 5,
377                    length: 3,
378                },
379                GtsDescriptor {
380                    short_addr: 0x5678,
381                    starting_slot: 8,
382                    length: 2,
383                },
384            ],
385            pa_num_short: 0,
386            pa_num_long: 0,
387            pa_short_addresses: Vec::new(),
388            pa_long_addresses: Vec::new(),
389        };
390
391        let bytes = beacon.build();
392        let (parsed, consumed) = BeaconPayload::parse(&bytes, 0).unwrap();
393        assert_eq!(consumed, bytes.len());
394        assert_eq!(parsed.beacon_order, 7);
395        assert_eq!(parsed.superframe_order, 3);
396        assert_eq!(parsed.final_cap_slot, 10);
397        assert!(parsed.pan_coordinator);
398        assert!(parsed.assoc_permit);
399        assert_eq!(parsed.gts_desc_count, 2);
400        assert!(parsed.gts_permit);
401        assert_eq!(parsed.gts_dir_mask, 0x03);
402        assert_eq!(parsed.gts_descriptors.len(), 2);
403        assert_eq!(parsed.gts_descriptors[0].short_addr, 0x1234);
404        assert_eq!(parsed.gts_descriptors[0].starting_slot, 5);
405        assert_eq!(parsed.gts_descriptors[0].length, 3);
406        assert_eq!(parsed.gts_descriptors[1].short_addr, 0x5678);
407        assert_eq!(parsed.gts_descriptors[1].starting_slot, 8);
408        assert_eq!(parsed.gts_descriptors[1].length, 2);
409    }
410
411    #[test]
412    fn test_beacon_with_pending_addresses() {
413        let beacon = BeaconPayload {
414            beacon_order: 15,
415            superframe_order: 15,
416            final_cap_slot: 15,
417            battery_life_ext: false,
418            pan_coordinator: false,
419            assoc_permit: false,
420            gts_desc_count: 0,
421            gts_permit: false,
422            gts_dir_mask: 0,
423            gts_descriptors: Vec::new(),
424            pa_num_short: 2,
425            pa_num_long: 1,
426            pa_short_addresses: vec![0x1111, 0x2222],
427            pa_long_addresses: vec![0x0102030405060708],
428        };
429
430        let bytes = beacon.build();
431        let (parsed, consumed) = BeaconPayload::parse(&bytes, 0).unwrap();
432        assert_eq!(consumed, bytes.len());
433        assert_eq!(parsed.pa_num_short, 2);
434        assert_eq!(parsed.pa_num_long, 1);
435        assert_eq!(parsed.pa_short_addresses, vec![0x1111, 0x2222]);
436        assert_eq!(parsed.pa_long_addresses, vec![0x0102030405060708]);
437    }
438
439    #[test]
440    fn test_parse_with_offset() {
441        let beacon = BeaconPayload::default();
442        let bytes = beacon.build();
443        let mut buf = vec![0xAA, 0xBB, 0xCC];
444        buf.extend_from_slice(&bytes);
445
446        let (parsed, _) = BeaconPayload::parse(&buf, 3).unwrap();
447        assert_eq!(parsed.beacon_order, 15);
448    }
449
450    #[test]
451    fn test_parse_buffer_too_short() {
452        let buf = [0xFF]; // Too short for superframe spec
453        let result = BeaconPayload::parse(&buf, 0);
454        assert!(result.is_err());
455    }
456
457    #[test]
458    fn test_build_roundtrip() {
459        let beacon = BeaconPayload {
460            beacon_order: 4,
461            superframe_order: 2,
462            final_cap_slot: 9,
463            battery_life_ext: true,
464            pan_coordinator: true,
465            assoc_permit: true,
466            gts_desc_count: 1,
467            gts_permit: true,
468            gts_dir_mask: 0x01,
469            gts_descriptors: vec![GtsDescriptor {
470                short_addr: 0xABCD,
471                starting_slot: 3,
472                length: 4,
473            }],
474            pa_num_short: 1,
475            pa_num_long: 0,
476            pa_short_addresses: vec![0x9999],
477            pa_long_addresses: Vec::new(),
478        };
479
480        let bytes = beacon.build();
481        let (parsed, _) = BeaconPayload::parse(&bytes, 0).unwrap();
482        assert_eq!(parsed.beacon_order, beacon.beacon_order);
483        assert_eq!(parsed.superframe_order, beacon.superframe_order);
484        assert_eq!(parsed.final_cap_slot, beacon.final_cap_slot);
485        assert_eq!(parsed.battery_life_ext, beacon.battery_life_ext);
486        assert_eq!(parsed.pan_coordinator, beacon.pan_coordinator);
487        assert_eq!(parsed.assoc_permit, beacon.assoc_permit);
488        assert_eq!(parsed.gts_permit, beacon.gts_permit);
489        assert_eq!(parsed.gts_dir_mask, beacon.gts_dir_mask);
490        assert_eq!(parsed.gts_descriptors, beacon.gts_descriptors);
491        assert_eq!(parsed.pa_short_addresses, beacon.pa_short_addresses);
492        assert_eq!(parsed.pa_long_addresses, beacon.pa_long_addresses);
493    }
494
495    #[test]
496    fn test_summary() {
497        let beacon = BeaconPayload {
498            assoc_permit: true,
499            pan_coordinator: true,
500            beacon_order: 7,
501            superframe_order: 3,
502            ..BeaconPayload::default()
503        };
504        let summary = beacon.summary();
505        assert!(summary.contains("assocPermit(true)"));
506        assert!(summary.contains("panCoord(true)"));
507        assert!(summary.contains("beaconOrder=7"));
508    }
509}