@@ -20,7 +20,10 @@ use crate::{
20
20
use abi:: { ImageHeader , CABOOSE_MAGIC , HEADER_MAGIC } ;
21
21
use core:: ops:: Range ;
22
22
use core:: ptr:: addr_of;
23
- use drv_lpc55_update_api:: { RawCabooseError , RotComponent , SlotId } ;
23
+ use drv_caboose:: CabooseReader ;
24
+ use drv_lpc55_update_api:: {
25
+ RawCabooseError , RotComponent , SlotId , BLOCK_SIZE_BYTES ,
26
+ } ;
24
27
use drv_update_api:: UpdateError ;
25
28
use zerocopy:: { AsBytes , FromBytes } ;
26
29
@@ -47,6 +50,7 @@ pub const HEADER_BLOCK: usize = 0;
47
50
// An image may have an ImageHeader located after the
48
51
// LPC55's mixed header/vector table.
49
52
pub const IMAGE_HEADER_OFFSET : u32 = 0x130 ;
53
+ pub const CABOOSE_TAG_EPOC : [ u8 ; 4 ] = [ b'E' , b'P' , b'O' , b'C' ] ;
50
54
51
55
/// Address ranges that may contain an image during storage and active use.
52
56
/// `stored` and `at_runtime` ranges are the same except for `stage0next`.
@@ -181,7 +185,7 @@ impl TryFrom<&[u8]> for ImageVectorsLpc55 {
181
185
/// the end of optional caboose and the beginning of the signature block.
182
186
pub fn validate_header_block (
183
187
header_access : & ImageAccess < ' _ > ,
184
- ) -> Result < u32 , UpdateError > {
188
+ ) -> Result < ( Option < Epoch > , u32 ) , UpdateError > {
185
189
let mut vectors = ImageVectorsLpc55 :: new_zeroed ( ) ;
186
190
let mut header = ImageHeader :: new_zeroed ( ) ;
187
191
@@ -208,15 +212,17 @@ pub fn validate_header_block(
208
212
// Note that `ImageHeader.epoch` is used by rollback protection for early
209
213
// rejection of invalid images.
210
214
// TODO: Improve estimate of where the first executable instruction can be.
211
- let code_offset = if header. magic == HEADER_MAGIC {
215
+ let ( code_offset, epoch ) = if header. magic == HEADER_MAGIC {
212
216
if header. total_image_len != vectors. nxp_offset_to_specific_header {
213
217
// ImageHeader disagrees with LPC55 vectors.
214
218
return Err ( UpdateError :: InvalidHeaderBlock ) ;
215
219
}
216
- // Adding constants should be resolved at compile time: no call to panic.
217
- IMAGE_HEADER_OFFSET + ( core:: mem:: size_of :: < ImageHeader > ( ) as u32 )
220
+ (
221
+ IMAGE_HEADER_OFFSET + ( core:: mem:: size_of :: < ImageHeader > ( ) as u32 ) ,
222
+ Some ( Epoch :: from ( header. epoch ) ) ,
223
+ )
218
224
} else {
219
- IMAGE_HEADER_OFFSET
225
+ ( IMAGE_HEADER_OFFSET , None )
220
226
} ;
221
227
222
228
if vectors. nxp_image_length as usize > header_access. at_runtime ( ) . len ( ) {
@@ -243,7 +249,7 @@ pub fn validate_header_block(
243
249
return Err ( UpdateError :: InvalidHeaderBlock ) ;
244
250
}
245
251
246
- Ok ( vectors. nxp_offset_to_specific_header )
252
+ Ok ( ( epoch , vectors. nxp_offset_to_specific_header ) )
247
253
}
248
254
249
255
/// Get the range of the caboose contained within an image if it exists.
@@ -260,7 +266,7 @@ pub fn caboose_slice(
260
266
//
261
267
// In this context, NoImageHeader actually means that the image
262
268
// is not well formed.
263
- let image_end_offset = validate_header_block ( image)
269
+ let ( _epoch , image_end_offset) = validate_header_block ( image)
264
270
. map_err ( |_| RawCabooseError :: NoImageHeader ) ?;
265
271
266
272
// By construction, the last word of the caboose is its size as a `u32`
@@ -318,7 +324,7 @@ enum Accessor<'a> {
318
324
} ,
319
325
// Hybrid is used for later implementation of rollback protection.
320
326
// The buffer is used in place of the beginning of the flash range.
321
- _Hybrid {
327
+ Hybrid {
322
328
buffer : & ' a [ u8 ] ,
323
329
flash : & ' a drv_lpc55_flash:: Flash < ' a > ,
324
330
span : FlashRange ,
@@ -330,7 +336,7 @@ impl Accessor<'_> {
330
336
match self {
331
337
Accessor :: Flash { span, .. }
332
338
| Accessor :: Ram { span, .. }
333
- | Accessor :: _Hybrid { span, .. } => & span. at_runtime ,
339
+ | Accessor :: Hybrid { span, .. } => & span. at_runtime ,
334
340
}
335
341
}
336
342
}
@@ -375,15 +381,15 @@ impl ImageAccess<'_> {
375
381
}
376
382
}
377
383
378
- pub fn _new_hybrid < ' a > (
384
+ pub fn new_hybrid < ' a > (
379
385
flash : & ' a drv_lpc55_flash:: Flash < ' a > ,
380
386
buffer : & ' a [ u8 ] ,
381
387
component : RotComponent ,
382
388
slot : SlotId ,
383
389
) -> ImageAccess < ' a > {
384
390
let span = flash_range ( component, slot) ;
385
391
ImageAccess {
386
- accessor : Accessor :: _Hybrid {
392
+ accessor : Accessor :: Hybrid {
387
393
flash,
388
394
buffer,
389
395
span,
@@ -430,7 +436,7 @@ impl ImageAccess<'_> {
430
436
. and_then ( u32:: read_from)
431
437
. ok_or ( UpdateError :: OutOfBounds ) ?)
432
438
}
433
- Accessor :: _Hybrid {
439
+ Accessor :: Hybrid {
434
440
buffer,
435
441
flash,
436
442
span,
@@ -491,7 +497,7 @@ impl ImageAccess<'_> {
491
497
Err ( UpdateError :: OutOfBounds )
492
498
}
493
499
}
494
- Accessor :: _Hybrid {
500
+ Accessor :: Hybrid {
495
501
buffer : ram,
496
502
flash,
497
503
span,
@@ -547,7 +553,7 @@ impl ImageAccess<'_> {
547
553
ImageVectorsLpc55 :: read_from_prefix ( & buffer[ ..] )
548
554
. ok_or ( UpdateError :: OutOfBounds )
549
555
}
550
- Accessor :: Ram { buffer, .. } | Accessor :: _Hybrid { buffer, .. } => {
556
+ Accessor :: Ram { buffer, .. } | Accessor :: Hybrid { buffer, .. } => {
551
557
ImageVectorsLpc55 :: read_from_prefix ( buffer)
552
558
. ok_or ( UpdateError :: OutOfBounds )
553
559
}
@@ -556,3 +562,122 @@ impl ImageAccess<'_> {
556
562
round_up_to_flash_page ( len) . ok_or ( UpdateError :: BadLength )
557
563
}
558
564
}
565
+
566
+ #[ derive( Clone , PartialEq ) ]
567
+ pub struct Epoch {
568
+ value : u32 ,
569
+ }
570
+
571
+ /// Convert from the ImageHeader.epoch format
572
+ impl From < u32 > for Epoch {
573
+ fn from ( number : u32 ) -> Self {
574
+ Epoch { value : number }
575
+ }
576
+ }
577
+
578
+ /// Convert from the caboose EPOC value format.
579
+ //
580
+ // Invalid EPOC values converted to Epoch{value:0} include:
581
+ // - empty slice
582
+ // - any non-ASCII-digits in slice
583
+ // - any non-UTF8 in slice
584
+ // - leading '+' normally allowed by parse()
585
+ // - values greater than u32::MAX
586
+ //
587
+ // Hand coding reduces size by about 950 bytes.
588
+ impl From < & [ u8 ] > for Epoch {
589
+ fn from ( chars : & [ u8 ] ) -> Self {
590
+ let epoch =
591
+ if chars. first ( ) . map ( |c| c. is_ascii_digit ( ) ) . unwrap_or ( false ) {
592
+ if let Ok ( chars) = core:: str:: from_utf8 ( chars) {
593
+ chars. parse :: < u32 > ( ) . unwrap_or ( 0 )
594
+ } else {
595
+ 0
596
+ }
597
+ } else {
598
+ 0
599
+ } ;
600
+ Epoch :: from ( epoch)
601
+ }
602
+ }
603
+
604
+ impl Epoch {
605
+ pub fn can_update_to ( & self , next : Epoch ) -> bool {
606
+ self . value >= next. value
607
+ }
608
+ }
609
+
610
+ /// Check a next image against an active image to determine
611
+ /// if rollback policy allows the next image.
612
+ /// If ImageHeader and Caboose are absent or Caboose does
613
+ /// not have an `EPOC` tag, then the Epoch is defaulted to zero.
614
+ /// This test is also used when the header block first arrives
615
+ /// so that images can be rejected early, i.e. before any flash has
616
+ /// been altered.
617
+ pub fn check_rollback_policy (
618
+ next_image : ImageAccess < ' _ > ,
619
+ active_image : ImageAccess < ' _ > ,
620
+ complete : bool ,
621
+ ) -> Result < ( ) , UpdateError > {
622
+ let next_epoch = get_image_epoch ( & next_image) ?;
623
+ let active_epoch = get_image_epoch ( & active_image) ?;
624
+ match ( active_epoch, next_epoch) {
625
+ // No active_epoch is treated as zero; update can proceed.
626
+ ( None , _) => Ok ( ( ) ) ,
627
+ ( Some ( active_epoch) , None ) => {
628
+ // If next_image is partial and HEADER_BLOCK has no ImageHeader,
629
+ // then there is no early rejection, proceed.
630
+ if !complete || active_epoch. can_update_to ( Epoch :: from ( 0u32 ) ) {
631
+ Ok ( ( ) )
632
+ } else {
633
+ Err ( UpdateError :: RollbackProtection )
634
+ }
635
+ }
636
+ ( Some ( active_epoch) , Some ( next_epoch) ) => {
637
+ if active_epoch. can_update_to ( next_epoch) {
638
+ Ok ( ( ) )
639
+ } else {
640
+ Err ( UpdateError :: RollbackProtection )
641
+ }
642
+ }
643
+ }
644
+ }
645
+
646
+ /// Get ImageHeader epoch and/or caboose EPOC from an Image if it exists.
647
+ /// Return default of zero epoch if neither is present.
648
+ /// This function is called at points where the image's signature has not been
649
+ /// checked or the image is incomplete. Sanity checks are required before using
650
+ /// any data.
651
+ fn get_image_epoch (
652
+ image : & ImageAccess < ' _ > ,
653
+ ) -> Result < Option < Epoch > , UpdateError > {
654
+ let ( header_epoch, _caboose_offset) = validate_header_block ( image) ?;
655
+
656
+ if let Ok ( span) = caboose_slice ( image) {
657
+ let mut block = [ 0u8 ; BLOCK_SIZE_BYTES ] ;
658
+ let caboose = block[ 0 ..span. len ( ) ] . as_bytes_mut ( ) ;
659
+ image. read_bytes ( span. start , caboose) ?;
660
+ let reader = CabooseReader :: new ( caboose) ;
661
+ let caboose_epoch = if let Ok ( epoc) = reader. get ( CABOOSE_TAG_EPOC ) {
662
+ Some ( Epoch :: from ( epoc) )
663
+ } else {
664
+ None
665
+ } ;
666
+ match ( header_epoch, caboose_epoch) {
667
+ ( None , None ) => Ok ( None ) ,
668
+ ( Some ( header_epoch) , None ) => Ok ( Some ( header_epoch) ) ,
669
+ ( None , Some ( caboose_epoch) ) => Ok ( Some ( caboose_epoch) ) ,
670
+ ( Some ( header_epoch) , Some ( caboose_epoch) ) => {
671
+ if caboose_epoch == header_epoch {
672
+ Ok ( Some ( caboose_epoch) )
673
+ } else {
674
+ // Epochs present in both and not matching is invalid.
675
+ // The image will be rejected after epoch 0.
676
+ Ok ( Some ( Epoch :: from ( 0u32 ) ) )
677
+ }
678
+ }
679
+ }
680
+ } else {
681
+ Ok ( header_epoch)
682
+ }
683
+ }
0 commit comments