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}