Skip to main content

pamoja_lorawan/device/
state.rs

1//! A joined device's state, saved to outlast a loss of power and resumed afterward.
2//!
3//! A node that sleeps between readings loses its RAM, and joining again after every wake
4//! costs airtime and a join nonce, and resets its frame counters. [`EndDevice::save`] turns
5//! everything the device has settled with its network into bytes a program keeps in flash or
6//! retained memory, and [`EndDevice::resume`] puts them back: the session and its keys, both
7//! frame counters, every setting the network's MAC commands changed, the channels, the
8//! answers still owed, and how long each duty cycle wait still has to run.
9//!
10//! The bytes hold the session keys, so keep them wherever the keys would be safe. A CRC-32
11//! catches storage that corrupted them, and a fingerprint of the channel plan refuses a
12//! state saved in another region.
13
14use 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
26/// How many bytes a saved state takes.
27pub 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/// Why a saved state could not be resumed.
68#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
69pub enum StateError {
70    /// The bytes are not the [`SAVED_LEN`] a saved state takes.
71    Length,
72    /// The bytes do not begin as a saved state does, their checksum does not match, or what
73    /// they hold is out of range.
74    Corrupt,
75    /// The state was saved in a format this version does not read.
76    Format(u8),
77    /// The state was saved on a different channel plan.
78    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/// A device's state as bytes to keep across a loss of power.
95///
96/// It holds the session keys; its `Debug` form leaves them, and everything else, out.
97#[derive(Clone, Copy, PartialEq, Eq)]
98pub struct Saved {
99    bytes: [u8; SAVED_LEN],
100}
101
102impl Saved {
103    /// Returns the bytes to store.
104    ///
105    /// # Returns
106    ///
107    /// The [`SAVED_LEN`] bytes.
108    pub const fn as_bytes(&self) -> &[u8; SAVED_LEN] {
109        &self.bytes
110    }
111
112    /// Reads bytes a device saved.
113    ///
114    /// # Arguments
115    ///
116    /// * `bytes` - what was stored.
117    ///
118    /// # Returns
119    ///
120    /// The saved state, checked for length, format and checksum.
121    ///
122    /// # Errors
123    ///
124    /// Returns [`StateError::Length`], [`StateError::Corrupt`] or [`StateError::Format`].
125    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    /// Saves what the device has settled with its network, to resume after losing power.
155    ///
156    /// Save between exchanges, once the last uplink is done, and again after each one: a
157    /// state resumed after an uplink it does not know about would send the next frame on a
158    /// counter the network has already seen.
159    ///
160    /// # Arguments
161    ///
162    /// * `now_us` - the time, in microseconds, which the duty cycle waits are counted from.
163    ///
164    /// # Returns
165    ///
166    /// The state.
167    ///
168    /// # Errors
169    ///
170    /// Returns [`DeviceError::NotJoined`] before the device has a session, and
171    /// [`DeviceError::Busy`] while a transmission waits on its windows or its repeats.
172    ///
173    /// # Examples
174    ///
175    /// ```
176    /// use pamoja_lora::region::Region;
177    /// use pamoja_lorawan::device::{EndDevice, Next, Saved, Settings};
178    /// use pamoja_lorawan::Session;
179    ///
180    /// let settings = Settings::new(2, 14);
181    /// let session = Session::new(0x2601_2E43, [0x5A; 16], [0xA5; 16]);
182    /// let mut device = EndDevice::personalized(Region::Eu868.plan(), session, settings)?;
183    /// device.send(2, b"21.5", false, 0)?;
184    /// assert_eq!(device.nothing_heard(3_000_000)?, Next::Done);
185    ///
186    /// // Before sleeping, keep the bytes somewhere that survives it.
187    /// let kept = device.save(3_000_000)?.as_bytes().to_vec();
188    ///
189    /// // On waking, build the device as before and carry on from them.
190    /// let mut woken = EndDevice::personalized(Region::Eu868.plan(), session, settings)?;
191    /// woken.resume(&Saved::from_bytes(&kept).expect("intact"), 0)?;
192    /// assert_eq!(woken.fcnt_up(), 1, "the next frame does not reuse a counter");
193    /// # Ok::<(), pamoja_lorawan::device::DeviceError>(())
194    /// ```
195    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    /// Resumes a state [`save`](EndDevice::save) produced.
319    ///
320    /// Build the device as it was first built, with the same plan, identifiers and settings,
321    /// and resume it before anything else. On CN470-510 any of the four RP002-1.0.5 plans
322    /// will do, since the state names the join channel that chose its plan. The device's
323    /// duty cycle waits run again from `now_us` for as long as they still had at the save,
324    /// which never lets it transmit sooner than it could have without the loss of power.
325    ///
326    /// # Arguments
327    ///
328    /// * `saved` - the stored state.
329    /// * `now_us` - the time, in microseconds, on the clock the device now runs by.
330    ///
331    /// # Errors
332    ///
333    /// Returns [`DeviceError::State`] with [`StateError::Plan`] for a state saved on another
334    /// plan and [`StateError::Corrupt`] for one whose contents are out of range, and
335    /// [`DeviceError::Busy`] while a transmission waits on its windows or its repeats. The
336    /// device is left as it was on any error.
337    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        // The answers take a fixed block however few of them there are, so what follows
417        // starts at the end of it rather than at the end of the last answer read.
418        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
522/// A fingerprint of what a saved state depends on in a plan: its name, its channels, and its
523/// second receive window.
524fn 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
541/// The CRC-32 of ISO-HDLC, the one Ethernet and zlib use.
542fn 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        // The check value CRC RevEng publishes for CRC-32/ISO-HDLC.
628        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}