Skip to main content

pamoja_lorawan/packages/
firmware.rs

1//! Firmware management, TS006-1.0.0.
2//!
3//! This is the package a server uses to ask what a device is running, what upgrade image it
4//! is holding, and when to reboot into it. Getting the image onto the device is another
5//! matter, out of this specification's scope; what this covers is everything around it:
6//! reporting the firmware and hardware versions, reporting whether the stored image is
7//! whole, authentic and meant for this hardware, deleting one, and scheduling the reboot at
8//! which an image is installed.
9//!
10//! A device stores one programmed reboot at a time. It can be set as a moment in time
11//! ([`FirmwareCommand::DevRebootTimeReq`]) or as a countdown
12//! ([`FirmwareCommand::DevRebootCountdownReq`]), and either one replaces whatever was
13//! programmed before. Both have a value that cancels the reboot and a value that means
14//! now, and a device that reboots immediately never answers, because it is gone.
15//!
16//! [`FirmwareManager`] holds that state for a device: the versions it reports, the image it
17//! is holding, and the reboot it has been given.
18//!
19//! # Examples
20//!
21//! A server asks what the device would boot into, and schedules it:
22//!
23//! ```
24//! use pamoja_lorawan::packages::firmware::{
25//!     FirmwareCommand, FirmwareCommands, FirmwareManager, Image, UpImageStatus,
26//! };
27//! use pamoja_lorawan::Direction;
28//!
29//! // A device running version 1.2.3 holds a valid image of 1.2.4, waiting to be booted.
30//! let mut manager = FirmwareManager::new(0x0001_0203, 0xAABB_CCDD)
31//!     .with_image(Image::valid(0x0001_0204));
32//!
33//! // The server asks what it would boot into, and schedules a reboot in an hour.
34//! let mut asked = [0u8; 8];
35//! let mut len = FirmwareCommand::DevUpgradeImageReq.encode(&mut asked)?;
36//! len += FirmwareCommand::DevRebootCountdownReq { countdown: 3_600 }.encode(&mut asked[len..])?;
37//!
38//! let mut out = [0u8; 16];
39//! let answered = manager.heard(&asked[..len], &mut out)?;
40//! let answers: Vec<FirmwareCommand> = FirmwareCommands::new(Direction::Uplink, &out[..answered])
41//!     .map(|answer| answer.unwrap())
42//!     .collect();
43//! assert_eq!(
44//!     answers[0],
45//!     FirmwareCommand::DevUpgradeImageAns {
46//!         status: UpImageStatus::Valid,
47//!         next_version: Some(0x0001_0204),
48//!     }
49//! );
50//! assert_eq!(manager.reboot_in_s(), Some(3_600));
51//! # Ok::<(), pamoja_lorawan::LorawanError>(())
52//! ```
53
54use super::{payload, write, PackageVersion};
55use crate::{Direction, LorawanError};
56
57/// The port the firmware management package is spoken on, section 3.
58pub const PORT: u8 = 203;
59
60/// The identifier of this package, section 3.
61pub const PACKAGE: u8 = 4;
62
63/// The version of this package implemented here, section 3.
64pub const VERSION: u8 = 1;
65
66/// The longest firmware management command, in bytes.
67pub const MAX_COMMAND: usize = 9;
68
69/// The reboot time that means as soon as possible, section 3.3.
70pub const REBOOT_NOW: u32 = 0x0000_0000;
71
72/// The reboot time that cancels a programmed reboot, section 3.3.
73pub const REBOOT_CANCEL: u32 = 0xFFFF_FFFF;
74
75/// The countdown that means as soon as possible, section 3.4.
76pub const COUNTDOWN_NOW: u32 = 0x00_0000;
77
78/// The countdown that cancels a programmed reboot, section 3.4.
79pub const COUNTDOWN_CANCEL: u32 = 0xFF_FFFF;
80
81const CID_PACKAGE_VERSION: u8 = 0x00;
82const CID_DEV_VERSION: u8 = 0x01;
83const CID_REBOOT_TIME: u8 = 0x02;
84const CID_REBOOT_COUNTDOWN: u8 = 0x03;
85const CID_UPGRADE_IMAGE: u8 = 0x04;
86const CID_DELETE_IMAGE: u8 = 0x05;
87
88/// What a device says about the firmware upgrade image it is holding, table 10.
89#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
90#[repr(u8)]
91pub enum UpImageStatus {
92    /// It is holding none.
93    None = 0,
94    /// One is there, but it is corrupt or its signature does not verify.
95    Corrupt = 1,
96    /// One is there and authentic, but it is not for this hardware.
97    WrongHardware = 2,
98    /// One is there, and it can be installed.
99    Valid = 3,
100}
101
102impl UpImageStatus {
103    /// Reads the coded status.
104    ///
105    /// # Arguments
106    ///
107    /// * `coded` - the two-bit field.
108    ///
109    /// # Returns
110    ///
111    /// The status.
112    #[must_use]
113    pub const fn from_bits(coded: u8) -> UpImageStatus {
114        match coded & 0x03 {
115            0 => UpImageStatus::None,
116            1 => UpImageStatus::Corrupt,
117            2 => UpImageStatus::WrongHardware,
118            _ => UpImageStatus::Valid,
119        }
120    }
121}
122
123/// Why a device would not delete the image it was asked to, table 13.
124#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
125pub struct DeleteStatus {
126    /// The device holds no valid upgrade image.
127    pub no_valid_image: bool,
128    /// The version asked for is not the one the device holds.
129    pub invalid_version: bool,
130}
131
132impl DeleteStatus {
133    /// Whether the delete succeeded.
134    ///
135    /// # Returns
136    ///
137    /// `true` when neither error bit is set.
138    #[must_use]
139    pub const fn deleted(&self) -> bool {
140        !self.no_valid_image && !self.invalid_version
141    }
142}
143
144/// The firmware upgrade image a device is holding.
145#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
146pub struct Image {
147    /// What the device makes of it.
148    pub status: UpImageStatus,
149    /// The version the device would be running once it is installed, which the device
150    /// reports only for an image it can install, section 3.5.
151    pub version: u32,
152}
153
154impl Image {
155    /// An image that is whole, authentic, and meant for this hardware.
156    ///
157    /// # Arguments
158    ///
159    /// * `version` - the version the device would run once it is installed.
160    ///
161    /// # Returns
162    ///
163    /// The image.
164    #[must_use]
165    pub const fn valid(version: u32) -> Image {
166        Image {
167            status: UpImageStatus::Valid,
168            version,
169        }
170    }
171
172    /// An image the device cannot install, and why.
173    ///
174    /// # Arguments
175    ///
176    /// * `status` - what is wrong with it.
177    ///
178    /// # Returns
179    ///
180    /// The image.
181    #[must_use]
182    pub const fn refused(status: UpImageStatus) -> Image {
183        Image { status, version: 0 }
184    }
185}
186
187/// One command of the firmware management package.
188#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
189pub enum FirmwareCommand {
190    /// The server asking which version of this package the device implements.
191    PackageVersionReq,
192    /// The device's answer, section 3.1.
193    PackageVersionAns(PackageVersion),
194    /// The server asking what the device is running, section 3.2.
195    DevVersionReq,
196    /// The device's answer, whose two fields the manufacturer allots, section 3.2.
197    DevVersionAns {
198        /// The firmware the device is running.
199        firmware: u32,
200        /// The hardware it is running on.
201        hardware: u32,
202    },
203    /// The server programming a reboot at a moment in time, section 3.3.
204    DevRebootTimeReq {
205        /// Seconds since the GPS epoch, [`REBOOT_NOW`] for at once, or [`REBOOT_CANCEL`]
206        /// to cancel.
207        reboot_time: u32,
208    },
209    /// The device's answer: the seconds until it reboots, zero for a time it cannot keep,
210    /// or [`REBOOT_CANCEL`] acknowledging a cancellation, section 3.3.
211    DevRebootTimeAns {
212        /// The seconds until the reboot.
213        reboot_time: u32,
214    },
215    /// The server programming a reboot after a delay, section 3.4.
216    DevRebootCountdownReq {
217        /// The seconds to wait, [`COUNTDOWN_NOW`] for at once, or [`COUNTDOWN_CANCEL`] to
218        /// cancel. Three octets on the air.
219        countdown: u32,
220    },
221    /// The device's answer, in the same three octets, section 3.4.
222    DevRebootCountdownAns {
223        /// The seconds until the reboot.
224        countdown: u32,
225    },
226    /// The server asking what upgrade image the device holds, section 3.5.
227    DevUpgradeImageReq,
228    /// The device's answer, which carries a version only for an image it can install.
229    DevUpgradeImageAns {
230        /// What the device makes of the image.
231        status: UpImageStatus,
232        /// The version it would run, present only for [`UpImageStatus::Valid`].
233        next_version: Option<u32>,
234    },
235    /// The server asking the device to delete the image it holds, section 3.6.
236    DevDeleteImageReq {
237        /// The version the server believes is there; a device that holds another refuses.
238        version: u32,
239    },
240    /// The device's answer, section 3.6.
241    DevDeleteImageAns(DeleteStatus),
242}
243
244impl FirmwareCommand {
245    /// The identifier this command travels under.
246    ///
247    /// # Returns
248    ///
249    /// The identifier, 0x00 to 0x05.
250    #[must_use]
251    pub const fn cid(&self) -> u8 {
252        match self {
253            FirmwareCommand::PackageVersionReq | FirmwareCommand::PackageVersionAns(_) => {
254                CID_PACKAGE_VERSION
255            }
256            FirmwareCommand::DevVersionReq | FirmwareCommand::DevVersionAns { .. } => {
257                CID_DEV_VERSION
258            }
259            FirmwareCommand::DevRebootTimeReq { .. } | FirmwareCommand::DevRebootTimeAns { .. } => {
260                CID_REBOOT_TIME
261            }
262            FirmwareCommand::DevRebootCountdownReq { .. }
263            | FirmwareCommand::DevRebootCountdownAns { .. } => CID_REBOOT_COUNTDOWN,
264            FirmwareCommand::DevUpgradeImageReq | FirmwareCommand::DevUpgradeImageAns { .. } => {
265                CID_UPGRADE_IMAGE
266            }
267            FirmwareCommand::DevDeleteImageReq { .. } | FirmwareCommand::DevDeleteImageAns(_) => {
268                CID_DELETE_IMAGE
269            }
270        }
271    }
272
273    /// Which way this command travels.
274    ///
275    /// # Returns
276    ///
277    /// [`Direction::Uplink`] for what a device sends, [`Direction::Downlink`] for what a
278    /// server sends.
279    #[must_use]
280    pub const fn direction(&self) -> Direction {
281        match self {
282            FirmwareCommand::PackageVersionAns(_)
283            | FirmwareCommand::DevVersionAns { .. }
284            | FirmwareCommand::DevRebootTimeAns { .. }
285            | FirmwareCommand::DevRebootCountdownAns { .. }
286            | FirmwareCommand::DevUpgradeImageAns { .. }
287            | FirmwareCommand::DevDeleteImageAns(_) => Direction::Uplink,
288            _ => Direction::Downlink,
289        }
290    }
291
292    /// Reads one command from the front of a message.
293    ///
294    /// # Arguments
295    ///
296    /// * `direction` - which way the frame carrying it traveled.
297    /// * `bytes` - the message, from this command's identifier on.
298    ///
299    /// # Returns
300    ///
301    /// The command and how many bytes it took.
302    ///
303    /// # Errors
304    ///
305    /// [`LorawanError::MalformedFrame`] when the message ends inside the command, and
306    /// [`LorawanError::UnknownCommand`] for an identifier this package does not define.
307    ///
308    /// # Examples
309    ///
310    /// ```
311    /// use pamoja_lorawan::packages::firmware::FirmwareCommand;
312    /// use pamoja_lorawan::Direction;
313    ///
314    /// // The server schedules a reboot in an hour.
315    /// let mut message = [0u8; 8];
316    /// let reboot = FirmwareCommand::DevRebootCountdownReq { countdown: 3_600 };
317    /// let written = reboot.encode(&mut message)?;
318    ///
319    /// let (command, taken) = FirmwareCommand::parse(Direction::Downlink, &message[..written])?;
320    /// assert_eq!((command, taken), (reboot, written));
321    /// # Ok::<(), pamoja_lorawan::LorawanError>(())
322    /// ```
323    pub fn parse(
324        direction: Direction,
325        bytes: &[u8],
326    ) -> Result<(FirmwareCommand, usize), LorawanError> {
327        let cid = *bytes.first().ok_or(LorawanError::MalformedFrame)?;
328        let up = matches!(direction, Direction::Uplink);
329        match (cid, up) {
330            (CID_PACKAGE_VERSION, false) => Ok((FirmwareCommand::PackageVersionReq, 1)),
331            (CID_PACKAGE_VERSION, true) => {
332                let (fields, taken) = payload(bytes, 2)?;
333                Ok((
334                    FirmwareCommand::PackageVersionAns(PackageVersion {
335                        package: fields[0],
336                        version: fields[1],
337                    }),
338                    taken,
339                ))
340            }
341            (CID_DEV_VERSION, false) => Ok((FirmwareCommand::DevVersionReq, 1)),
342            (CID_DEV_VERSION, true) => {
343                let (fields, taken) = payload(bytes, 8)?;
344                Ok((
345                    FirmwareCommand::DevVersionAns {
346                        firmware: u32::from_le_bytes([fields[0], fields[1], fields[2], fields[3]]),
347                        hardware: u32::from_le_bytes([fields[4], fields[5], fields[6], fields[7]]),
348                    },
349                    taken,
350                ))
351            }
352            (CID_REBOOT_TIME, _) => {
353                let (fields, taken) = payload(bytes, 4)?;
354                let value = u32::from_le_bytes([fields[0], fields[1], fields[2], fields[3]]);
355                Ok((
356                    if up {
357                        FirmwareCommand::DevRebootTimeAns { reboot_time: value }
358                    } else {
359                        FirmwareCommand::DevRebootTimeReq { reboot_time: value }
360                    },
361                    taken,
362                ))
363            }
364            (CID_REBOOT_COUNTDOWN, _) => {
365                let (fields, taken) = payload(bytes, 3)?;
366                let value = u32::from_le_bytes([fields[0], fields[1], fields[2], 0]);
367                Ok((
368                    if up {
369                        FirmwareCommand::DevRebootCountdownAns { countdown: value }
370                    } else {
371                        FirmwareCommand::DevRebootCountdownReq { countdown: value }
372                    },
373                    taken,
374                ))
375            }
376            (CID_UPGRADE_IMAGE, false) => Ok((FirmwareCommand::DevUpgradeImageReq, 1)),
377            (CID_UPGRADE_IMAGE, true) => {
378                let (status, _) = payload(bytes, 1)?;
379                let status = UpImageStatus::from_bits(status[0]);
380                if matches!(status, UpImageStatus::Valid) {
381                    let (fields, taken) = payload(bytes, 5)?;
382                    Ok((
383                        FirmwareCommand::DevUpgradeImageAns {
384                            status,
385                            next_version: Some(u32::from_le_bytes([
386                                fields[1], fields[2], fields[3], fields[4],
387                            ])),
388                        },
389                        taken,
390                    ))
391                } else {
392                    Ok((
393                        FirmwareCommand::DevUpgradeImageAns {
394                            status,
395                            next_version: None,
396                        },
397                        2,
398                    ))
399                }
400            }
401            (CID_DELETE_IMAGE, false) => {
402                let (fields, taken) = payload(bytes, 4)?;
403                Ok((
404                    FirmwareCommand::DevDeleteImageReq {
405                        version: u32::from_le_bytes([fields[0], fields[1], fields[2], fields[3]]),
406                    },
407                    taken,
408                ))
409            }
410            (CID_DELETE_IMAGE, true) => {
411                let (fields, taken) = payload(bytes, 1)?;
412                Ok((
413                    FirmwareCommand::DevDeleteImageAns(DeleteStatus {
414                        no_valid_image: fields[0] & 0x01 != 0,
415                        invalid_version: fields[0] & 0x02 != 0,
416                    }),
417                    taken,
418                ))
419            }
420            _ => Err(LorawanError::UnknownCommand(cid)),
421        }
422    }
423
424    /// Writes this command out.
425    ///
426    /// # Arguments
427    ///
428    /// * `out` - where to write it, at least [`MAX_COMMAND`] bytes for any command.
429    ///
430    /// # Returns
431    ///
432    /// How many bytes were written.
433    ///
434    /// # Errors
435    ///
436    /// [`LorawanError::PayloadTooLong`] when the buffer is too small.
437    ///
438    /// # Examples
439    ///
440    /// ```
441    /// use pamoja_lorawan::packages::firmware::{FirmwareCommand, UpImageStatus};
442    ///
443    /// let mut out = [0u8; 8];
444    /// let answer = FirmwareCommand::DevUpgradeImageAns {
445    ///     status: UpImageStatus::None,
446    ///     next_version: None,
447    /// };
448    /// assert_eq!(answer.encode(&mut out)?, 2, "a version rides only with a valid image");
449    /// # Ok::<(), pamoja_lorawan::LorawanError>(())
450    /// ```
451    pub fn encode(&self, out: &mut [u8]) -> Result<usize, LorawanError> {
452        match self {
453            FirmwareCommand::PackageVersionReq => write(out, CID_PACKAGE_VERSION, &[]),
454            FirmwareCommand::PackageVersionAns(version) => write(
455                out,
456                CID_PACKAGE_VERSION,
457                &[version.package, version.version],
458            ),
459            FirmwareCommand::DevVersionReq => write(out, CID_DEV_VERSION, &[]),
460            FirmwareCommand::DevVersionAns { firmware, hardware } => {
461                let mut fields = [0u8; 8];
462                fields[..4].copy_from_slice(&firmware.to_le_bytes());
463                fields[4..].copy_from_slice(&hardware.to_le_bytes());
464                write(out, CID_DEV_VERSION, &fields)
465            }
466            FirmwareCommand::DevRebootTimeReq { reboot_time }
467            | FirmwareCommand::DevRebootTimeAns { reboot_time } => {
468                write(out, CID_REBOOT_TIME, &reboot_time.to_le_bytes())
469            }
470            FirmwareCommand::DevRebootCountdownReq { countdown }
471            | FirmwareCommand::DevRebootCountdownAns { countdown } => {
472                let value = countdown.to_le_bytes();
473                write(out, CID_REBOOT_COUNTDOWN, &value[..3])
474            }
475            FirmwareCommand::DevUpgradeImageReq => write(out, CID_UPGRADE_IMAGE, &[]),
476            FirmwareCommand::DevUpgradeImageAns {
477                status,
478                next_version,
479            } => match (status, next_version) {
480                (UpImageStatus::Valid, Some(version)) => {
481                    let mut fields = [0u8; 5];
482                    fields[0] = UpImageStatus::Valid as u8;
483                    fields[1..].copy_from_slice(&version.to_le_bytes());
484                    write(out, CID_UPGRADE_IMAGE, &fields)
485                }
486                (status, _) => write(out, CID_UPGRADE_IMAGE, &[*status as u8]),
487            },
488            FirmwareCommand::DevDeleteImageReq { version } => {
489                write(out, CID_DELETE_IMAGE, &version.to_le_bytes())
490            }
491            FirmwareCommand::DevDeleteImageAns(status) => write(
492                out,
493                CID_DELETE_IMAGE,
494                &[u8::from(status.no_valid_image) | (u8::from(status.invalid_version) << 1)],
495            ),
496        }
497    }
498}
499
500/// Walks the commands packed into one firmware management message.
501///
502/// Iteration stops at an identifier this package does not define, because a command carries
503/// no length of its own and cannot be stepped over.
504pub struct FirmwareCommands<'a> {
505    direction: Direction,
506    bytes: &'a [u8],
507    at: usize,
508    stopped: bool,
509}
510
511impl<'a> FirmwareCommands<'a> {
512    /// Reads the commands in a message.
513    ///
514    /// # Arguments
515    ///
516    /// * `direction` - which way the frame carrying them traveled.
517    /// * `bytes` - the payload sent on port [`PORT`].
518    ///
519    /// # Returns
520    ///
521    /// The walk.
522    #[must_use]
523    pub const fn new(direction: Direction, bytes: &'a [u8]) -> FirmwareCommands<'a> {
524        FirmwareCommands {
525            direction,
526            bytes,
527            at: 0,
528            stopped: false,
529        }
530    }
531
532    /// What was not read.
533    ///
534    /// # Returns
535    ///
536    /// The bytes left: empty once every command has been read, and whatever followed an
537    /// identifier this package does not define.
538    #[must_use]
539    pub fn remaining(&self) -> &'a [u8] {
540        &self.bytes[self.at.min(self.bytes.len())..]
541    }
542}
543
544impl Iterator for FirmwareCommands<'_> {
545    type Item = Result<FirmwareCommand, LorawanError>;
546
547    fn next(&mut self) -> Option<Self::Item> {
548        if self.stopped || self.at >= self.bytes.len() {
549            return None;
550        }
551        match FirmwareCommand::parse(self.direction, &self.bytes[self.at..]) {
552            Ok((command, taken)) => {
553                self.at += taken;
554                Some(Ok(command))
555            }
556            Err(LorawanError::UnknownCommand(_)) => {
557                self.stopped = true;
558                None
559            }
560            Err(error) => {
561                self.stopped = true;
562                Some(Err(error))
563            }
564        }
565    }
566}
567
568/// When a device has been told to reboot.
569#[derive(Clone, Copy, Debug, PartialEq, Eq)]
570enum Reboot {
571    /// Nothing is programmed.
572    None,
573    /// At a moment in time, in seconds since the GPS epoch.
574    At(u32),
575    /// After a delay, counted from the command that set it.
576    In(u32),
577    /// As soon as the device can, which it does without answering.
578    Now,
579}
580
581/// The firmware management package running on a device, TS006-1.0.0.
582///
583/// It reports the versions the device was built with, the upgrade image it is holding, and
584/// keeps the one programmed reboot the specification allows. It owns no clock: the caller
585/// passes the time into [`FirmwareManager::heard`] when the device knows it, and a device
586/// that does not know the time refuses a reboot set for a moment in time, as section 3.3
587/// asks.
588#[derive(Clone, Copy, Debug)]
589pub struct FirmwareManager {
590    firmware: u32,
591    hardware: u32,
592    image: Option<Image>,
593    reboot: Reboot,
594    since_s: u32,
595}
596
597impl FirmwareManager {
598    /// Starts the package on a device.
599    ///
600    /// # Arguments
601    ///
602    /// * `firmware` - the version the device is running, as its manufacturer numbers it.
603    /// * `hardware` - the platform it runs on.
604    ///
605    /// # Returns
606    ///
607    /// The package, holding no image and no programmed reboot.
608    #[must_use]
609    pub const fn new(firmware: u32, hardware: u32) -> FirmwareManager {
610        FirmwareManager {
611            firmware,
612            hardware,
613            image: None,
614            reboot: Reboot::None,
615            since_s: 0,
616        }
617    }
618
619    /// Returns it holding a firmware upgrade image.
620    ///
621    /// # Arguments
622    ///
623    /// * `image` - the image the device is holding.
624    ///
625    /// # Returns
626    ///
627    /// The package, for chaining.
628    #[must_use]
629    pub const fn with_image(mut self, image: Image) -> FirmwareManager {
630        self.image = Some(image);
631        self
632    }
633
634    /// The image the device is holding, if it holds one.
635    ///
636    /// # Returns
637    ///
638    /// The image.
639    #[must_use]
640    pub const fn image(&self) -> Option<Image> {
641        self.image
642    }
643
644    /// Puts an image in the device's store, replacing whatever was there.
645    ///
646    /// # Arguments
647    ///
648    /// * `image` - the image, or `None` once one has been installed or deleted.
649    pub const fn set_image(&mut self, image: Option<Image>) {
650        self.image = image;
651    }
652
653    /// The moment the device is to reboot, where one was programmed as a time.
654    ///
655    /// # Returns
656    ///
657    /// Seconds since the GPS epoch, and `None` when the reboot is a countdown, immediate, or
658    /// not programmed at all.
659    #[must_use]
660    pub const fn reboot_at_s(&self) -> Option<u32> {
661        match self.reboot {
662            Reboot::At(when) => Some(when),
663            _ => None,
664        }
665    }
666
667    /// How long until the device is to reboot, where one was programmed as a countdown.
668    ///
669    /// # Returns
670    ///
671    /// The seconds left, and `None` when the reboot is a moment in time, immediate, or not
672    /// programmed at all.
673    #[must_use]
674    pub const fn reboot_in_s(&self) -> Option<u32> {
675        match self.reboot {
676            Reboot::In(delay) => Some(delay),
677            _ => None,
678        }
679    }
680
681    /// Whether the device was told to reboot at once.
682    ///
683    /// # Returns
684    ///
685    /// `true` when it should reboot as soon as it can, in which case it sends no answer.
686    #[must_use]
687    pub const fn reboot_now(&self) -> bool {
688        matches!(self.reboot, Reboot::Now)
689    }
690
691    /// Forgets the programmed reboot, for a device that has carried it out.
692    pub const fn rebooted(&mut self) {
693        self.reboot = Reboot::None;
694    }
695
696    /// Reads a downlink on the firmware port and writes the answers it calls for.
697    ///
698    /// Commands run in the order they arrive, and each writes its answer after the one
699    /// before it, as section 3 asks. A reboot the device was told to do at once is recorded
700    /// on [`FirmwareManager::reboot_now`] and answered with nothing, because the device will
701    /// be gone.
702    ///
703    /// # Arguments
704    ///
705    /// * `payload` - what arrived on port [`PORT`].
706    /// * `now_s` - what the device believes the time is, in seconds since the GPS epoch, or
707    ///   `None` when it does not know.
708    /// * `out` - where to write the answers.
709    ///
710    /// # Returns
711    ///
712    /// How many bytes of answer were written.
713    ///
714    /// # Errors
715    ///
716    /// The first error a command raised, or [`LorawanError::PayloadTooLong`] when the
717    /// answers do not fit.
718    pub fn heard_at(
719        &mut self,
720        payload: &[u8],
721        now_s: Option<u32>,
722        out: &mut [u8],
723    ) -> Result<usize, LorawanError> {
724        let mut at = 0;
725        for command in FirmwareCommands::new(Direction::Downlink, payload) {
726            let answer = match command? {
727                FirmwareCommand::PackageVersionReq => {
728                    Some(FirmwareCommand::PackageVersionAns(PackageVersion {
729                        package: PACKAGE,
730                        version: VERSION,
731                    }))
732                }
733                FirmwareCommand::DevVersionReq => Some(FirmwareCommand::DevVersionAns {
734                    firmware: self.firmware,
735                    hardware: self.hardware,
736                }),
737                FirmwareCommand::DevRebootTimeReq { reboot_time } => {
738                    self.reboot_time(reboot_time, now_s)
739                }
740                FirmwareCommand::DevRebootCountdownReq { countdown } => self.countdown(countdown),
741                FirmwareCommand::DevUpgradeImageReq => Some(FirmwareCommand::DevUpgradeImageAns {
742                    status: self.image.map_or(UpImageStatus::None, |image| image.status),
743                    next_version: self.image.and_then(|image| {
744                        matches!(image.status, UpImageStatus::Valid).then_some(image.version)
745                    }),
746                }),
747                FirmwareCommand::DevDeleteImageReq { version } => Some(self.delete(version)),
748                _ => None,
749            };
750            if let Some(answer) = answer {
751                at += answer.encode(out.get_mut(at..).ok_or(LorawanError::PayloadTooLong)?)?;
752            }
753        }
754        Ok(at)
755    }
756
757    /// Reads a downlink on the firmware port from a device that does not know the time.
758    ///
759    /// # Arguments
760    ///
761    /// * `payload` - what arrived on port [`PORT`].
762    /// * `out` - where to write the answers.
763    ///
764    /// # Returns
765    ///
766    /// How many bytes of answer were written.
767    ///
768    /// # Errors
769    ///
770    /// As [`FirmwareManager::heard_at`].
771    pub fn heard(&mut self, payload: &[u8], out: &mut [u8]) -> Result<usize, LorawanError> {
772        self.heard_at(payload, None, out)
773    }
774
775    /// Programs a reboot at a moment in time, section 3.3.
776    fn reboot_time(&mut self, reboot_time: u32, now_s: Option<u32>) -> Option<FirmwareCommand> {
777        match reboot_time {
778            REBOOT_NOW => {
779                self.reboot = Reboot::Now;
780                None
781            }
782            REBOOT_CANCEL => {
783                self.reboot = Reboot::None;
784                Some(FirmwareCommand::DevRebootTimeAns {
785                    reboot_time: REBOOT_CANCEL,
786                })
787            }
788            when => {
789                let left = now_s.filter(|now| when > *now).map(|now| when - now);
790                match left {
791                    Some(left) => {
792                        self.reboot = Reboot::At(when);
793                        self.since_s = now_s.unwrap_or(0);
794                        Some(FirmwareCommand::DevRebootTimeAns { reboot_time: left })
795                    }
796                    None => Some(FirmwareCommand::DevRebootTimeAns {
797                        reboot_time: REBOOT_NOW,
798                    }),
799                }
800            }
801        }
802    }
803
804    /// Programs a reboot after a delay, section 3.4.
805    fn countdown(&mut self, countdown: u32) -> Option<FirmwareCommand> {
806        match countdown {
807            COUNTDOWN_NOW => {
808                self.reboot = Reboot::Now;
809                None
810            }
811            COUNTDOWN_CANCEL => {
812                self.reboot = Reboot::None;
813                Some(FirmwareCommand::DevRebootCountdownAns {
814                    countdown: COUNTDOWN_CANCEL,
815                })
816            }
817            delay => {
818                self.reboot = Reboot::In(delay);
819                Some(FirmwareCommand::DevRebootCountdownAns { countdown: delay })
820            }
821        }
822    }
823
824    /// Deletes the image the device holds, section 3.6.
825    fn delete(&mut self, version: u32) -> FirmwareCommand {
826        let held = self
827            .image
828            .filter(|image| matches!(image.status, UpImageStatus::Valid));
829        let status = match held {
830            None => DeleteStatus {
831                no_valid_image: true,
832                invalid_version: false,
833            },
834            Some(image) if image.version != version => DeleteStatus {
835                no_valid_image: false,
836                invalid_version: true,
837            },
838            Some(_) => {
839                self.image = None;
840                DeleteStatus::default()
841            }
842        };
843        FirmwareCommand::DevDeleteImageAns(status)
844    }
845}