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 pub fn build(&self) -> Vec<u8> {
230 let mut out = Vec::new();
231
232 let mut sf_spec: u16 = 0;
234 sf_spec |= (self.beacon_order as u16) & 0x0F;
235 sf_spec |= ((self.superframe_order as u16) & 0x0F) << 4;
236 sf_spec |= ((self.final_cap_slot as u16) & 0x0F) << 8;
237 if self.battery_life_ext {
238 sf_spec |= 0x1000;
239 }
240 if self.pan_coordinator {
241 sf_spec |= 0x4000;
242 }
243 if self.assoc_permit {
244 sf_spec |= 0x8000;
245 }
246 out.extend_from_slice(&sf_spec.to_le_bytes());
247
248 let gts_count = self.gts_descriptors.len().min(7) as u8;
250 let mut gts_spec: u8 = gts_count & 0x07;
251 if self.gts_permit {
252 gts_spec |= 0x80;
253 }
254 out.push(gts_spec);
255
256 if gts_count > 0 {
258 out.push(self.gts_dir_mask & 0x7F);
259 }
260
261 for desc in &self.gts_descriptors {
263 out.extend_from_slice(&desc.short_addr.to_le_bytes());
264 let slot_len = (desc.starting_slot & 0x0F) | ((desc.length & 0x0F) << 4);
265 out.push(slot_len);
266 }
267
268 let pa_num_short = self.pa_short_addresses.len().min(7) as u8;
270 let pa_num_long = self.pa_long_addresses.len().min(7) as u8;
271 let pa_spec = (pa_num_short & 0x07) | ((pa_num_long & 0x07) << 4);
272 out.push(pa_spec);
273
274 for &addr in &self.pa_short_addresses {
276 out.extend_from_slice(&addr.to_le_bytes());
277 }
278
279 for &addr in &self.pa_long_addresses {
281 out.extend_from_slice(&addr.to_le_bytes());
282 }
283
284 out
285 }
286
287 pub fn summary(&self) -> String {
289 format!(
290 "802.15.4 Beacon assocPermit({}) panCoord({}) beaconOrder={} sfOrder={}",
291 self.assoc_permit, self.pan_coordinator, self.beacon_order, self.superframe_order,
292 )
293 }
294}
295
296impl Default for BeaconPayload {
297 fn default() -> Self {
298 Self {
299 beacon_order: 15,
300 superframe_order: 15,
301 final_cap_slot: 15,
302 battery_life_ext: false,
303 pan_coordinator: false,
304 assoc_permit: false,
305 gts_desc_count: 0,
306 gts_permit: true,
307 gts_dir_mask: 0,
308 gts_descriptors: Vec::new(),
309 pa_num_short: 0,
310 pa_num_long: 0,
311 pa_short_addresses: Vec::new(),
312 pa_long_addresses: Vec::new(),
313 }
314 }
315}
316
317#[cfg(test)]
318mod tests {
319 use super::*;
320
321 #[test]
322 fn test_parse_minimal_beacon() {
323 let buf = [
327 0xFF, 0x8F, 0x80, 0x00, ];
331 let (beacon, consumed) = BeaconPayload::parse(&buf, 0).unwrap();
332 assert_eq!(consumed, 4);
333 assert_eq!(beacon.beacon_order, 15);
334 assert_eq!(beacon.superframe_order, 15);
335 assert_eq!(beacon.final_cap_slot, 15); assert!(beacon.gts_permit);
337 assert_eq!(beacon.gts_desc_count, 0);
338 assert_eq!(beacon.pa_num_short, 0);
339 assert_eq!(beacon.pa_num_long, 0);
340 }
341
342 #[test]
343 fn test_parse_beacon_with_defaults() {
344 let beacon = BeaconPayload::default();
346 let bytes = beacon.build();
347 let (parsed, consumed) = BeaconPayload::parse(&bytes, 0).unwrap();
348 assert_eq!(consumed, bytes.len());
349 assert_eq!(parsed.beacon_order, 15);
350 assert_eq!(parsed.superframe_order, 15);
351 assert_eq!(parsed.final_cap_slot, 15);
352 assert!(!parsed.battery_life_ext);
353 assert!(!parsed.pan_coordinator);
354 assert!(!parsed.assoc_permit);
355 assert!(parsed.gts_permit);
356 assert_eq!(parsed.gts_desc_count, 0);
357 }
358
359 #[test]
360 fn test_beacon_with_gts_descriptors() {
361 let beacon = BeaconPayload {
362 beacon_order: 7,
363 superframe_order: 3,
364 final_cap_slot: 10,
365 battery_life_ext: false,
366 pan_coordinator: true,
367 assoc_permit: true,
368 gts_desc_count: 2,
369 gts_permit: true,
370 gts_dir_mask: 0x03,
371 gts_descriptors: vec![
372 GtsDescriptor {
373 short_addr: 0x1234,
374 starting_slot: 5,
375 length: 3,
376 },
377 GtsDescriptor {
378 short_addr: 0x5678,
379 starting_slot: 8,
380 length: 2,
381 },
382 ],
383 pa_num_short: 0,
384 pa_num_long: 0,
385 pa_short_addresses: Vec::new(),
386 pa_long_addresses: Vec::new(),
387 };
388
389 let bytes = beacon.build();
390 let (parsed, consumed) = BeaconPayload::parse(&bytes, 0).unwrap();
391 assert_eq!(consumed, bytes.len());
392 assert_eq!(parsed.beacon_order, 7);
393 assert_eq!(parsed.superframe_order, 3);
394 assert_eq!(parsed.final_cap_slot, 10);
395 assert!(parsed.pan_coordinator);
396 assert!(parsed.assoc_permit);
397 assert_eq!(parsed.gts_desc_count, 2);
398 assert!(parsed.gts_permit);
399 assert_eq!(parsed.gts_dir_mask, 0x03);
400 assert_eq!(parsed.gts_descriptors.len(), 2);
401 assert_eq!(parsed.gts_descriptors[0].short_addr, 0x1234);
402 assert_eq!(parsed.gts_descriptors[0].starting_slot, 5);
403 assert_eq!(parsed.gts_descriptors[0].length, 3);
404 assert_eq!(parsed.gts_descriptors[1].short_addr, 0x5678);
405 assert_eq!(parsed.gts_descriptors[1].starting_slot, 8);
406 assert_eq!(parsed.gts_descriptors[1].length, 2);
407 }
408
409 #[test]
410 fn test_beacon_with_pending_addresses() {
411 let beacon = BeaconPayload {
412 beacon_order: 15,
413 superframe_order: 15,
414 final_cap_slot: 15,
415 battery_life_ext: false,
416 pan_coordinator: false,
417 assoc_permit: false,
418 gts_desc_count: 0,
419 gts_permit: false,
420 gts_dir_mask: 0,
421 gts_descriptors: Vec::new(),
422 pa_num_short: 2,
423 pa_num_long: 1,
424 pa_short_addresses: vec![0x1111, 0x2222],
425 pa_long_addresses: vec![0x0102030405060708],
426 };
427
428 let bytes = beacon.build();
429 let (parsed, consumed) = BeaconPayload::parse(&bytes, 0).unwrap();
430 assert_eq!(consumed, bytes.len());
431 assert_eq!(parsed.pa_num_short, 2);
432 assert_eq!(parsed.pa_num_long, 1);
433 assert_eq!(parsed.pa_short_addresses, vec![0x1111, 0x2222]);
434 assert_eq!(parsed.pa_long_addresses, vec![0x0102030405060708]);
435 }
436
437 #[test]
438 fn test_parse_with_offset() {
439 let beacon = BeaconPayload::default();
440 let bytes = beacon.build();
441 let mut buf = vec![0xAA, 0xBB, 0xCC];
442 buf.extend_from_slice(&bytes);
443
444 let (parsed, _) = BeaconPayload::parse(&buf, 3).unwrap();
445 assert_eq!(parsed.beacon_order, 15);
446 }
447
448 #[test]
449 fn test_parse_buffer_too_short() {
450 let buf = [0xFF]; let result = BeaconPayload::parse(&buf, 0);
452 assert!(result.is_err());
453 }
454
455 #[test]
456 fn test_build_roundtrip() {
457 let beacon = BeaconPayload {
458 beacon_order: 4,
459 superframe_order: 2,
460 final_cap_slot: 9,
461 battery_life_ext: true,
462 pan_coordinator: true,
463 assoc_permit: true,
464 gts_desc_count: 1,
465 gts_permit: true,
466 gts_dir_mask: 0x01,
467 gts_descriptors: vec![GtsDescriptor {
468 short_addr: 0xABCD,
469 starting_slot: 3,
470 length: 4,
471 }],
472 pa_num_short: 1,
473 pa_num_long: 0,
474 pa_short_addresses: vec![0x9999],
475 pa_long_addresses: Vec::new(),
476 };
477
478 let bytes = beacon.build();
479 let (parsed, _) = BeaconPayload::parse(&bytes, 0).unwrap();
480 assert_eq!(parsed.beacon_order, beacon.beacon_order);
481 assert_eq!(parsed.superframe_order, beacon.superframe_order);
482 assert_eq!(parsed.final_cap_slot, beacon.final_cap_slot);
483 assert_eq!(parsed.battery_life_ext, beacon.battery_life_ext);
484 assert_eq!(parsed.pan_coordinator, beacon.pan_coordinator);
485 assert_eq!(parsed.assoc_permit, beacon.assoc_permit);
486 assert_eq!(parsed.gts_permit, beacon.gts_permit);
487 assert_eq!(parsed.gts_dir_mask, beacon.gts_dir_mask);
488 assert_eq!(parsed.gts_descriptors, beacon.gts_descriptors);
489 assert_eq!(parsed.pa_short_addresses, beacon.pa_short_addresses);
490 assert_eq!(parsed.pa_long_addresses, beacon.pa_long_addresses);
491 }
492
493 #[test]
494 fn test_summary() {
495 let beacon = BeaconPayload {
496 assoc_permit: true,
497 pan_coordinator: true,
498 beacon_order: 7,
499 superframe_order: 3,
500 ..BeaconPayload::default()
501 };
502 let summary = beacon.summary();
503 assert!(summary.contains("assocPermit(true)"));
504 assert!(summary.contains("panCoord(true)"));
505 assert!(summary.contains("beaconOrder=7"));
506 }
507}