@@ -20,7 +20,10 @@ use crate::{
2020use abi:: { ImageHeader , CABOOSE_MAGIC , HEADER_MAGIC } ;
2121use core:: ops:: Range ;
2222use 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+ } ;
2427use drv_update_api:: UpdateError ;
2528use zerocopy:: { AsBytes , FromBytes } ;
2629
@@ -47,6 +50,7 @@ pub const HEADER_BLOCK: usize = 0;
4750// An image may have an ImageHeader located after the
4851// LPC55's mixed header/vector table.
4952pub const IMAGE_HEADER_OFFSET : u32 = 0x130 ;
53+ pub const CABOOSE_TAG_EPOC : [ u8 ; 4 ] = [ b'E' , b'P' , b'O' , b'C' ] ;
5054
5155/// Address ranges that may contain an image during storage and active use.
5256/// `stored` and `at_runtime` ranges are the same except for `stage0next`.
@@ -181,7 +185,7 @@ impl TryFrom<&[u8]> for ImageVectorsLpc55 {
181185/// the end of optional caboose and the beginning of the signature block.
182186pub fn validate_header_block (
183187 header_access : & ImageAccess < ' _ > ,
184- ) -> Result < u32 , UpdateError > {
188+ ) -> Result < ( Option < Epoch > , u32 ) , UpdateError > {
185189 let mut vectors = ImageVectorsLpc55 :: new_zeroed ( ) ;
186190 let mut header = ImageHeader :: new_zeroed ( ) ;
187191
@@ -208,15 +212,17 @@ pub fn validate_header_block(
208212 // Note that `ImageHeader.epoch` is used by rollback protection for early
209213 // rejection of invalid images.
210214 // 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 {
212216 if header. total_image_len != vectors. nxp_offset_to_specific_header {
213217 // ImageHeader disagrees with LPC55 vectors.
214218 return Err ( UpdateError :: InvalidHeaderBlock ) ;
215219 }
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+ )
218224 } else {
219- IMAGE_HEADER_OFFSET
225+ ( IMAGE_HEADER_OFFSET , None )
220226 } ;
221227
222228 if vectors. nxp_image_length as usize > header_access. at_runtime ( ) . len ( ) {
@@ -243,7 +249,7 @@ pub fn validate_header_block(
243249 return Err ( UpdateError :: InvalidHeaderBlock ) ;
244250 }
245251
246- Ok ( vectors. nxp_offset_to_specific_header )
252+ Ok ( ( epoch , vectors. nxp_offset_to_specific_header ) )
247253}
248254
249255/// Get the range of the caboose contained within an image if it exists.
@@ -260,7 +266,7 @@ pub fn caboose_slice(
260266 //
261267 // In this context, NoImageHeader actually means that the image
262268 // is not well formed.
263- let image_end_offset = validate_header_block ( image)
269+ let ( _epoch , image_end_offset) = validate_header_block ( image)
264270 . map_err ( |_| RawCabooseError :: NoImageHeader ) ?;
265271
266272 // By construction, the last word of the caboose is its size as a `u32`
@@ -318,7 +324,7 @@ enum Accessor<'a> {
318324 } ,
319325 // Hybrid is used for later implementation of rollback protection.
320326 // The buffer is used in place of the beginning of the flash range.
321- _Hybrid {
327+ Hybrid {
322328 buffer : & ' a [ u8 ] ,
323329 flash : & ' a drv_lpc55_flash:: Flash < ' a > ,
324330 span : FlashRange ,
@@ -330,7 +336,7 @@ impl Accessor<'_> {
330336 match self {
331337 Accessor :: Flash { span, .. }
332338 | Accessor :: Ram { span, .. }
333- | Accessor :: _Hybrid { span, .. } => & span. at_runtime ,
339+ | Accessor :: Hybrid { span, .. } => & span. at_runtime ,
334340 }
335341 }
336342}
@@ -375,15 +381,15 @@ impl ImageAccess<'_> {
375381 }
376382 }
377383
378- pub fn _new_hybrid < ' a > (
384+ pub fn new_hybrid < ' a > (
379385 flash : & ' a drv_lpc55_flash:: Flash < ' a > ,
380386 buffer : & ' a [ u8 ] ,
381387 component : RotComponent ,
382388 slot : SlotId ,
383389 ) -> ImageAccess < ' a > {
384390 let span = flash_range ( component, slot) ;
385391 ImageAccess {
386- accessor : Accessor :: _Hybrid {
392+ accessor : Accessor :: Hybrid {
387393 flash,
388394 buffer,
389395 span,
@@ -430,7 +436,7 @@ impl ImageAccess<'_> {
430436 . and_then ( u32:: read_from)
431437 . ok_or ( UpdateError :: OutOfBounds ) ?)
432438 }
433- Accessor :: _Hybrid {
439+ Accessor :: Hybrid {
434440 buffer,
435441 flash,
436442 span,
@@ -491,7 +497,7 @@ impl ImageAccess<'_> {
491497 Err ( UpdateError :: OutOfBounds )
492498 }
493499 }
494- Accessor :: _Hybrid {
500+ Accessor :: Hybrid {
495501 buffer : ram,
496502 flash,
497503 span,
@@ -547,7 +553,7 @@ impl ImageAccess<'_> {
547553 ImageVectorsLpc55 :: read_from_prefix ( & buffer[ ..] )
548554 . ok_or ( UpdateError :: OutOfBounds )
549555 }
550- Accessor :: Ram { buffer, .. } | Accessor :: _Hybrid { buffer, .. } => {
556+ Accessor :: Ram { buffer, .. } | Accessor :: Hybrid { buffer, .. } => {
551557 ImageVectorsLpc55 :: read_from_prefix ( buffer)
552558 . ok_or ( UpdateError :: OutOfBounds )
553559 }
@@ -556,3 +562,122 @@ impl ImageAccess<'_> {
556562 round_up_to_flash_page ( len) . ok_or ( UpdateError :: BadLength )
557563 }
558564}
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