1use pamoja_lora::region::{ChannelPlan, RelayChannel};
15
16use super::air::{Air, Sequence, MAX_SUB_BANDS};
17use super::answers::{Answers, Kind, MAX_ANSWER, MAX_ANSWERS};
18use super::channels::{Channels, MASK_GROUPS};
19use super::relayed::Relayed;
20use super::RelayStatus;
21use super::{Channel, DeviceError, EndDevice, MAX_CHANNELS};
22use crate::adr::Backoff;
23use crate::relay::{CadPeriodicity, CadToRx, Forward, RelayActivation, XtalAccuracy};
24use crate::Session;
25
26pub const SAVED_LEN: usize = HEADER_LEN
28 + SESSION_LEN
29 + SETTINGS_LEN
30 + AIR_LEN
31 + CHANNELS_LEN
32 + ANSWERS_LEN
33 + RELAY_LEN
34 + CHECKSUM_LEN;
35
36const MAGIC: [u8; 4] = *b"PJLW";
37const FORMAT: u8 = 1;
38
39const HEADER_LEN: usize = 4 + 1 + 4 + 1 + 2;
40const SESSION_LEN: usize = 4 + 16 + 16 + 4 + 4;
41const SETTINGS_LEN: usize = 7 + 4 + 4 + 4 + 4;
42const AIR_LEN: usize = 8 + 8 * MAX_SUB_BANDS;
43const CHANNEL_LEN: usize = 4 + 4 + 1 + 1;
44const CHANNELS_LEN: usize = 1 + 2 * MASK_GROUPS + 2 * MASK_GROUPS + CHANNEL_LEN * MAX_CHANNELS;
45const ANSWERS_LEN: usize = 1 + MAX_ANSWERS * (1 + MAX_ANSWER);
46const RELAY_LEN: usize = 1 + 1 + 1 + 4 + 2 + 1 + 1 + 9 + 5;
47const CHECKSUM_LEN: usize = 4;
48
49const FCNT_DOWN: u8 = 1 << 0;
50const JOINED_ON: u8 = 1 << 1;
51const UPLINK_DWELL: u8 = 1 << 2;
52const DOWNLINK_DWELL: u8 = 1 << 3;
53const ACK_OWED: u8 = 1 << 4;
54const LINK_CHECK: u8 = 1 << 5;
55const DEVICE_TIME: u8 = 1 << 6;
56const BACKOFF_RESTORED: u8 = 1 << 7;
57
58const STICKY: u8 = 1 << 7;
59const STARTED: u8 = 1 << 6;
60
61const RELAYING: u8 = 1 << 0;
62const FIXED_CHANNEL: u8 = 1 << 1;
63const SECOND_CHANNEL: u8 = 1 << 2;
64const RELAY_HEARD: u8 = 1 << 3;
65const RELAY_AUTOMATIC: u8 = 1 << 4;
66
67#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
69pub enum StateError {
70 Length,
72 Corrupt,
75 Format(u8),
77 Plan,
79}
80
81impl core::fmt::Display for StateError {
82 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
83 match self {
84 StateError::Length => write!(f, "a saved state is {SAVED_LEN} bytes"),
85 StateError::Corrupt => f.write_str("the saved state is corrupt"),
86 StateError::Format(format) => write!(f, "saved state format {format} is not read here"),
87 StateError::Plan => f.write_str("the state was saved on another channel plan"),
88 }
89 }
90}
91
92impl core::error::Error for StateError {}
93
94#[derive(Clone, Copy, PartialEq, Eq)]
98pub struct Saved {
99 bytes: [u8; SAVED_LEN],
100}
101
102impl Saved {
103 pub const fn as_bytes(&self) -> &[u8; SAVED_LEN] {
109 &self.bytes
110 }
111
112 pub fn from_bytes(bytes: &[u8]) -> Result<Saved, StateError> {
126 let bytes: [u8; SAVED_LEN] = bytes.try_into().map_err(|_| StateError::Length)?;
127 if bytes[..4] != MAGIC {
128 return Err(StateError::Corrupt);
129 }
130 let body = SAVED_LEN - CHECKSUM_LEN;
131 let stored = u32::from_le_bytes([
132 bytes[body],
133 bytes[body + 1],
134 bytes[body + 2],
135 bytes[body + 3],
136 ]);
137 if crc32(&[&bytes[..body]]) != stored {
138 return Err(StateError::Corrupt);
139 }
140 if bytes[4] != FORMAT {
141 return Err(StateError::Format(bytes[4]));
142 }
143 Ok(Saved { bytes })
144 }
145}
146
147impl core::fmt::Debug for Saved {
148 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
149 f.write_str("Saved { .. }")
150 }
151}
152
153impl<'p> EndDevice<'p> {
154 pub fn save(&self, now_us: u64) -> Result<Saved, DeviceError> {
196 let session = self.session.ok_or(DeviceError::NotJoined)?;
197 if self.pending.is_some() {
198 return Err(DeviceError::Busy);
199 }
200
201 let mut bytes = [0u8; SAVED_LEN];
202 let mut out = Writer {
203 bytes: &mut bytes,
204 at: 0,
205 };
206 let (link_check, device_time) = self.answers.requests();
207 let flags = flag(self.fcnt_down.is_some(), FCNT_DOWN)
208 | flag(self.joined_on.is_some(), JOINED_ON)
209 | flag(self.uplink_dwell, UPLINK_DWELL)
210 | flag(self.downlink_dwell, DOWNLINK_DWELL)
211 | flag(self.ack_owed, ACK_OWED)
212 | flag(link_check, LINK_CHECK)
213 | flag(device_time, DEVICE_TIME)
214 | flag(self.backoff.has_restored(), BACKOFF_RESTORED);
215 out.put(&MAGIC);
216 out.put(&[FORMAT]);
217 out.put(&fingerprint(self.plan).to_le_bytes());
218 out.put(&[flags]);
219 out.put(&self.joined_on.unwrap_or(0).to_le_bytes());
220
221 let (nwk_skey, app_skey) = session.keys();
222 out.put(&session.dev_addr().to_le_bytes());
223 out.put(&nwk_skey);
224 out.put(&app_skey);
225 out.put(&self.fcnt_up.to_le_bytes());
226 out.put(&self.fcnt_down.unwrap_or(0).to_le_bytes());
227
228 out.put(&[
229 self.data_rate,
230 self.tx_power,
231 self.nb_trans,
232 self.rx1_dr_offset,
233 self.rx2_data_rate,
234 self.max_eirp_dbm as u8,
235 self.max_duty_cycle,
236 ]);
237 out.put(&self.rx2_frequency_hz.to_le_bytes());
238 out.put(&self.rx1_delay_us.to_le_bytes());
239 out.put(&self.backoff.counter().to_le_bytes());
240 out.put(&self.sequence.state().to_le_bytes());
241
242 let (aggregated, sub_bands) = self.air.remaining(now_us);
243 out.put(&aggregated.to_le_bytes());
244 for left in sub_bands {
245 out.put(&left.to_le_bytes());
246 }
247
248 out.put(&[self.channels.default_count() as u8]);
249 for group in self.channels.mask() {
250 out.put(&group.to_le_bytes());
251 }
252 let mut defined = [0u16; MASK_GROUPS];
253 for (index, slot) in self.channels.slots().iter().enumerate() {
254 if slot.is_some() {
255 defined[index / 16] |= 1 << (index % 16);
256 }
257 }
258 for group in defined {
259 out.put(&group.to_le_bytes());
260 }
261 for slot in self.channels.slots() {
262 let channel = slot.unwrap_or(Channel::new(0, 0, 0));
263 out.put(&channel.uplink_hz.to_le_bytes());
264 out.put(&channel.downlink_hz.to_le_bytes());
265 out.put(&[channel.min_data_rate, channel.max_data_rate]);
266 }
267
268 let mut answers = [0u8; ANSWERS_LEN];
269 let mut at = 1;
270 for (answer, kind) in self.answers.owed() {
271 answers[0] += 1;
272 answers[at] = answer.len() as u8
273 | flag(kind == Kind::Sticky, STICKY)
274 | flag(kind == Kind::Started, STARTED);
275 answers[at + 1..at + 1 + answer.len()].copy_from_slice(answer);
276 at += 1 + answer.len();
277 }
278 out.put(&answers);
279
280 let relayed = self.relayed;
281 let second = relayed.second_channel.unwrap_or(RelayChannel::new(0, 0, 0));
282 let heard = relayed.heard.unwrap_or(RelayStatus {
283 cad_periodicity: CadPeriodicity::Ms1000,
284 xtal_accuracy: XtalAccuracy::Ppm40,
285 cad_to_rx: CadToRx::Symbols8,
286 relay_data_rate: 0,
287 forward: Forward::Available,
288 });
289 out.put(&[
290 flag(relayed.enabled, RELAYING)
291 | flag(relayed.fixed_channel, FIXED_CHANNEL)
292 | flag(relayed.second_channel.is_some(), SECOND_CHANNEL)
293 | flag(relayed.heard.is_some(), RELAY_HEARD)
294 | flag(relayed.automatic, RELAY_AUTOMATIC),
295 relayed.activation.code() | (relayed.smart_level << 2),
296 relayed.back_off,
297 ]);
298 out.put(&relayed.wfcnt.to_le_bytes());
299 out.put(&relayed.quiet_uplinks.to_le_bytes());
300 out.put(&[relayed.attempts, relayed.channel]);
301 out.put(&second.wor_frequency_hz.to_le_bytes());
302 out.put(&second.ack_frequency_hz.to_le_bytes());
303 out.put(&[
304 second.data_rate,
305 heard.cad_periodicity.code(),
306 heard.xtal_accuracy.code(),
307 heard.cad_to_rx.code(),
308 heard.relay_data_rate,
309 heard.forward.code(),
310 ]);
311
312 let body = out.at;
313 let checksum = crc32(&[&out.bytes[..body]]);
314 out.put(&checksum.to_le_bytes());
315 Ok(Saved { bytes })
316 }
317
318 pub fn resume(&mut self, saved: &Saved, now_us: u64) -> Result<(), DeviceError> {
338 if self.pending.is_some() {
339 return Err(DeviceError::Busy);
340 }
341 let mut input = Reader {
342 bytes: &saved.bytes,
343 at: 5,
344 };
345 let corrupt = DeviceError::State(StateError::Corrupt);
346
347 let stored_fingerprint = input.u32();
348 let flags = input.u8();
349 let joined_channel = input.u16();
350 let joined_on = (flags & JOINED_ON != 0).then_some(joined_channel);
351 let plan = match joined_on {
352 Some(channel) => {
353 self.plan
354 .join_plan(channel)
355 .ok_or(DeviceError::State(StateError::Plan))?
356 .0
357 .plan
358 }
359 None => self.plan,
360 };
361 if fingerprint(plan) != stored_fingerprint {
362 return Err(DeviceError::State(StateError::Plan));
363 }
364
365 let dev_addr = input.u32();
366 let nwk_skey = input.array::<16>();
367 let app_skey = input.array::<16>();
368 let fcnt_up = input.u32();
369 let fcnt_down = input.u32();
370
371 let [data_rate, tx_power, nb_trans, rx1_dr_offset, rx2_data_rate, max_eirp, max_duty_cycle] =
372 input.array::<7>();
373 let rx2_frequency_hz = input.u32();
374 let rx1_delay_us = input.u32();
375 let backoff_counter = input.u32();
376 let sequence = input.u32();
377
378 let aggregated = input.u64();
379 let mut sub_bands = [0u64; MAX_SUB_BANDS];
380 for left in &mut sub_bands {
381 *left = input.u64();
382 }
383
384 let defaults = usize::from(input.u8());
385 let mut enabled = [0u16; MASK_GROUPS];
386 for group in &mut enabled {
387 *group = input.u16();
388 }
389 let mut defined = [0u16; MASK_GROUPS];
390 for group in &mut defined {
391 *group = input.u16();
392 }
393 let mut slots = [None; MAX_CHANNELS];
394 for (index, slot) in slots.iter_mut().enumerate() {
395 let uplink_hz = input.u32();
396 let downlink_hz = input.u32();
397 let [min_data_rate, max_data_rate] = input.array::<2>();
398 if defined[index / 16] & (1 << (index % 16)) != 0 {
399 *slot = Some(Channel {
400 uplink_hz,
401 downlink_hz,
402 min_data_rate,
403 max_data_rate,
404 });
405 }
406 }
407 let stray = enabled
408 .iter()
409 .zip(defined)
410 .any(|(enabled, defined)| enabled & !defined != 0);
411 if defaults > MAX_CHANNELS || stray || nb_trans == 0 || nb_trans > 15 {
412 return Err(corrupt);
413 }
414
415 let mut answers = Answers::new();
416 let answers_at = input.at;
419 let count = input.u8();
420 for _ in 0..count {
421 let header = input.u8();
422 let len = usize::from(header & !(STICKY | STARTED));
423 let kind = match (header & STICKY != 0, header & STARTED != 0) {
424 (false, false) => Kind::Once,
425 (true, false) => Kind::Sticky,
426 (false, true) => Kind::Started,
427 (true, true) => return Err(corrupt),
428 };
429 if len == 0 || len > MAX_ANSWER {
430 return Err(corrupt);
431 }
432 let bytes = input.take(len);
433 if !answers.push_encoded(bytes, kind) {
434 return Err(corrupt);
435 }
436 }
437 if flags & LINK_CHECK != 0 {
438 answers.request_link_check();
439 }
440 if flags & DEVICE_TIME != 0 {
441 answers.request_device_time();
442 }
443
444 input.at = answers_at + ANSWERS_LEN;
445
446 let [relay_flags, mode, back_off] = input.array::<3>();
447 let wfcnt = input.u32();
448 let quiet_uplinks = input.u16();
449 let [attempts, relay_channel] = input.array::<2>();
450 let second_wor_hz = input.u32();
451 let second_ack_hz = input.u32();
452 let [second_data_rate, cad_periodicity, xtal, cad_to_rx, relay_data_rate, forward] =
453 input.array::<6>();
454 let mut relayed = Relayed::new();
455 relayed.activation = RelayActivation::from_code(mode);
456 relayed.smart_level = (mode >> 2) & 0x03;
457 relayed.back_off = back_off;
458 relayed.enabled = relay_flags & RELAYING != 0;
459 relayed.fixed_channel = relay_flags & FIXED_CHANNEL != 0;
460 relayed.automatic = relay_flags & RELAY_AUTOMATIC != 0;
461 relayed.wfcnt = wfcnt;
462 relayed.quiet_uplinks = quiet_uplinks;
463 relayed.attempts = attempts;
464 relayed.channel = relay_channel;
465 if relay_flags & SECOND_CHANNEL != 0 {
466 relayed.second_channel = Some(RelayChannel::new(
467 second_wor_hz,
468 second_ack_hz,
469 second_data_rate,
470 ));
471 }
472 if relay_flags & RELAY_HEARD != 0 {
473 relayed.heard = Some(RelayStatus {
474 cad_periodicity: CadPeriodicity::from_code(cad_periodicity).ok_or(corrupt)?,
475 xtal_accuracy: XtalAccuracy::from_code(xtal),
476 cad_to_rx: CadToRx::from_code(cad_to_rx),
477 relay_data_rate,
478 forward: Forward::from_code(forward),
479 });
480 }
481
482 self.plan = plan;
483 self.session = Some(Session::new(dev_addr, nwk_skey, app_skey));
484 self.joined_on = joined_on;
485 self.fcnt_up = fcnt_up;
486 self.fcnt_down = (flags & FCNT_DOWN != 0).then_some(fcnt_down);
487 self.data_rate = data_rate;
488 self.tx_power = tx_power;
489 self.nb_trans = nb_trans;
490 self.rx1_dr_offset = rx1_dr_offset;
491 self.rx2_frequency_hz = rx2_frequency_hz;
492 self.rx2_data_rate = rx2_data_rate;
493 self.rx1_delay_us = rx1_delay_us;
494 self.max_eirp_dbm = max_eirp as i8;
495 self.uplink_dwell = flags & UPLINK_DWELL != 0;
496 self.downlink_dwell = flags & DOWNLINK_DWELL != 0;
497 self.max_duty_cycle = max_duty_cycle;
498 self.channels = Channels::from_parts(slots, enabled, defaults);
499 self.answers = answers;
500 self.relayed = relayed;
501 self.backoff = Backoff::recommended(self.settings.version)
502 .resumed(backoff_counter, flags & BACKOFF_RESTORED != 0);
503 self.air = Air::resumed(now_us, aggregated, sub_bands);
504 self.sequence = Sequence::from_state(sequence);
505 self.ack_owed = flags & ACK_OWED != 0;
506 self.joins = 0;
507 self.join_used = [0; MASK_GROUPS];
508 self.join_slot = 0;
509 self.quiet_until_us = 0;
510 Ok(())
511 }
512}
513
514const fn flag(on: bool, bit: u8) -> u8 {
515 if on {
516 bit
517 } else {
518 0
519 }
520}
521
522fn fingerprint(plan: &ChannelPlan) -> u32 {
525 let mut crc = Crc32::new();
526 crc.update(plan.name.as_bytes());
527 for blocks in [plan.default_channels, plan.downlink_channels] {
528 crc.update(&(blocks.len() as u32).to_le_bytes());
529 for block in blocks {
530 crc.update(&block.start_hz.to_le_bytes());
531 crc.update(&block.step_hz.to_le_bytes());
532 crc.update(&block.count.to_le_bytes());
533 crc.update(&[block.min_data_rate, block.max_data_rate]);
534 }
535 }
536 crc.update(&plan.rx2_frequency_hz.to_le_bytes());
537 crc.update(&[plan.rx2_data_rate]);
538 crc.finish()
539}
540
541fn crc32(parts: &[&[u8]]) -> u32 {
543 let mut crc = Crc32::new();
544 for part in parts {
545 crc.update(part);
546 }
547 crc.finish()
548}
549
550struct Crc32(u32);
551
552impl Crc32 {
553 const fn new() -> Crc32 {
554 Crc32(0xFFFF_FFFF)
555 }
556
557 fn update(&mut self, bytes: &[u8]) {
558 for &byte in bytes {
559 self.0 ^= u32::from(byte);
560 for _ in 0..8 {
561 let mask = (self.0 & 1).wrapping_neg();
562 self.0 = (self.0 >> 1) ^ (0xEDB8_8320 & mask);
563 }
564 }
565 }
566
567 const fn finish(&self) -> u32 {
568 !self.0
569 }
570}
571
572struct Writer<'a> {
573 bytes: &'a mut [u8; SAVED_LEN],
574 at: usize,
575}
576
577impl Writer<'_> {
578 fn put(&mut self, bytes: &[u8]) {
579 self.bytes[self.at..self.at + bytes.len()].copy_from_slice(bytes);
580 self.at += bytes.len();
581 }
582}
583
584struct Reader<'a> {
585 bytes: &'a [u8; SAVED_LEN],
586 at: usize,
587}
588
589impl<'a> Reader<'a> {
590 fn take(&mut self, len: usize) -> &'a [u8] {
591 let end = (self.at + len).min(SAVED_LEN);
592 let taken = &self.bytes[self.at.min(end)..end];
593 self.at = end;
594 taken
595 }
596
597 fn array<const N: usize>(&mut self) -> [u8; N] {
598 let mut out = [0u8; N];
599 let taken = self.take(N);
600 out[..taken.len()].copy_from_slice(taken);
601 out
602 }
603
604 fn u8(&mut self) -> u8 {
605 self.array::<1>()[0]
606 }
607
608 fn u16(&mut self) -> u16 {
609 u16::from_le_bytes(self.array())
610 }
611
612 fn u32(&mut self) -> u32 {
613 u32::from_le_bytes(self.array())
614 }
615
616 fn u64(&mut self) -> u64 {
617 u64::from_le_bytes(self.array())
618 }
619}
620
621#[cfg(test)]
622mod tests {
623 use super::*;
624
625 #[test]
626 fn the_checksum_is_the_iso_hdlc_crc_32() {
627 assert_eq!(crc32(&[b"123456789"]), 0xCBF4_3926);
629 assert_eq!(crc32(&[b"1234", b"56789"]), 0xCBF4_3926);
630 assert_eq!(crc32(&[]), 0);
631 }
632
633 #[test]
634 fn the_layout_adds_up() {
635 assert_eq!(SAVED_LEN, 1_678);
636 }
637
638 #[test]
639 fn a_state_in_a_later_format_is_named_rather_than_misread() {
640 use pamoja_lora::region::Region;
641
642 let session = Session::new(0x2601_2E43, [1; 16], [2; 16]);
643 let device = EndDevice::personalized(
644 Region::Eu868.plan(),
645 session,
646 super::super::Settings::new(2, 14),
647 )
648 .expect("the plan fits");
649 let mut bytes = *device
650 .save(0)
651 .expect("a personalized device has a session")
652 .as_bytes();
653 assert_eq!(
654 Saved::from_bytes(&bytes).map(|saved| saved.bytes),
655 Ok(bytes)
656 );
657
658 bytes[4] = 2;
659 let body = SAVED_LEN - CHECKSUM_LEN;
660 let checksum = crc32(&[&bytes[..body]]);
661 bytes[body..].copy_from_slice(&checksum.to_le_bytes());
662 assert_eq!(Saved::from_bytes(&bytes), Err(StateError::Format(2)));
663 }
664}