1use crate::layer::field::{FieldError, read_u16_le};
12
13#[derive(Debug, Clone, PartialEq, Eq)]
15pub struct GtsDescriptor {
16 pub short_addr: u16,
18 pub starting_slot: u8,
20 pub length: u8,
22}
23
24#[derive(Debug, Clone, PartialEq, Eq)]
26pub struct BeaconPayload {
27 pub beacon_order: u8,
30 pub superframe_order: u8,
32 pub final_cap_slot: u8,
34 pub battery_life_ext: bool,
36 pub pan_coordinator: bool,
38 pub assoc_permit: bool,
40
41 pub gts_desc_count: u8,
44 pub gts_permit: bool,
46
47 pub gts_dir_mask: u8,
50
51 pub gts_descriptors: Vec<GtsDescriptor>,
54
55 pub pa_num_short: u8,
58 pub pa_num_long: u8,
60
61 pub pa_short_addresses: Vec<u16>,
64 pub pa_long_addresses: Vec<u64>,
66}
67
68impl BeaconPayload {
69 pub fn parse(buf: &[u8], offset: usize) -> Result<(Self, usize), FieldError> {
72 let mut pos = offset;
73
74 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 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 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 let gts_desc_count = gts_spec & 0x07;
115 let gts_permit = (gts_spec & 0x80) != 0;
116
117 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 gts_dir_mask = gts_dir & 0x7F;
131 }
132
133 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 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 let pa_num_short = pa_spec & 0x07;
172 let pa_num_long = (pa_spec >> 4) & 0x07;
173
174 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 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 #[must_use]
230 pub fn build(&self) -> Vec<u8> {
231 let mut out = Vec::new();
232
233 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 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 if gts_count > 0 {
259 out.push(self.gts_dir_mask & 0x7F);
260 }
261
262 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 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 for &addr in &self.pa_short_addresses {
277 out.extend_from_slice(&addr.to_le_bytes());
278 }
279
280 for &addr in &self.pa_long_addresses {
282 out.extend_from_slice(&addr.to_le_bytes());
283 }
284
285 out
286 }
287
288 #[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 let buf = [
329 0xFF, 0x8F, 0x80, 0x00, ];
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); 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 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]; 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}