pvmfw: Add support for variable config entry count

Refactor Header::entries out of the struct so that it keeps a constant
size and parse the variable-sized (version-dependent) entry array from
Config, which will store a validated list of ranges describing the
blobs. This should make adding support for new blobs considerably
easier.

Derive Entry variant count (Entry::COUNT) programatically, using the
implementation suggested by jaewan@.

Bug: 291191157
Test: atest DebugPolicyHostTests
Change-Id: I7e7ad7d249a4463b3979c22b3ce10b00b97ad492
diff --git a/pvmfw/src/config.rs b/pvmfw/src/config.rs
index 47a06df..8a24347 100644
--- a/pvmfw/src/config.rs
+++ b/pvmfw/src/config.rs
@@ -19,7 +19,7 @@
 use core::ops::Range;
 use core::result;
 use static_assertions::const_assert_eq;
-use vmbase::util::{unchecked_align_up, RangeExt};
+use vmbase::util::RangeExt;
 use zerocopy::{FromBytes, LayoutVerified};
 
 /// Configuration data header.
@@ -34,8 +34,6 @@
     total_size: u32,
     /// Feature flags; currently reserved and must be zero.
     flags: u32,
-    /// (offset, size) pairs used to locate individual entries appended to the header.
-    entries: [HeaderEntry; Entry::COUNT],
 }
 
 #[derive(Debug)]
@@ -88,35 +86,38 @@
         self.total_size as usize
     }
 
-    pub fn body_offset(&self) -> usize {
-        unchecked_align_up(mem::size_of::<Self>(), mem::size_of::<u64>())
+    pub fn body_offset(&self) -> Result<usize> {
+        let entries_offset = mem::size_of::<Self>();
+
+        // Ensure that the entries are properly aligned and do not require padding.
+        const_assert_eq!(mem::align_of::<Header>() % mem::align_of::<HeaderEntry>(), 0);
+        const_assert_eq!(mem::size_of::<Header>() % mem::align_of::<HeaderEntry>(), 0);
+
+        let entries_size = self.entry_count()?.checked_mul(mem::size_of::<HeaderEntry>()).unwrap();
+
+        Ok(entries_offset.checked_add(entries_size).unwrap())
     }
 
-    fn get_body_range(&self, entry: Entry) -> Result<Option<Range<usize>>> {
-        let Some(range) = self.entries[entry as usize].as_range() else {
-            return Ok(None);
+    pub fn entry_count(&self) -> Result<usize> {
+        let last_entry = match self.version {
+            Self::VERSION_1_0 => Entry::DebugPolicy,
+            v => return Err(Error::UnsupportedVersion(v)),
         };
 
-        let limits = self.body_offset()..self.total_size();
-        if range.start <= range.end && range.is_within(&limits) {
-            let start = range.start - limits.start;
-            let end = range.end - limits.start;
-
-            Ok(Some(start..end))
-        } else {
-            Err(Error::EntryOutOfBounds(entry, range, limits))
-        }
+        Ok(last_entry as usize + 1)
     }
 }
 
 #[derive(Clone, Copy, Debug)]
 pub enum Entry {
-    Bcc = 0,
-    DebugPolicy = 1,
+    Bcc,
+    DebugPolicy,
+    #[allow(non_camel_case_types)] // TODO: Use mem::variant_count once stable.
+    _VARIANT_COUNT,
 }
 
 impl Entry {
-    const COUNT: usize = 2;
+    const COUNT: usize = Self::_VARIANT_COUNT as usize;
 }
 
 #[repr(packed)]
@@ -157,8 +158,7 @@
 #[derive(Debug)]
 pub struct Config<'a> {
     body: &'a mut [u8],
-    bcc_range: Range<usize>,
-    dp_range: Option<Range<usize>>,
+    ranges: [Option<Range<usize>>; Entry::COUNT],
 }
 
 impl<'a> Config<'a> {
@@ -177,10 +177,6 @@
             return Err(Error::InvalidMagic);
         }
 
-        if header.version != Header::VERSION_1_0 {
-            return Err(Error::UnsupportedVersion(header.version));
-        }
-
         if header.flags != 0 {
             return Err(Error::InvalidFlags(header.flags));
         }
@@ -197,22 +193,36 @@
             return Err(Error::InvalidSize(total_size));
         };
 
-        let bcc_range =
-            header.get_body_range(Entry::Bcc)?.ok_or(Error::MissingEntry(Entry::Bcc))?;
-        let dp_range = header.get_body_range(Entry::DebugPolicy)?;
+        let (header_entries, body) =
+            LayoutVerified::<_, [HeaderEntry]>::new_slice_from_prefix(rest, header.entry_count()?)
+                .ok_or(Error::BufferTooSmall)?;
 
-        Ok(Self { body: rest, bcc_range, dp_range })
+        // Validate that we won't get an invalid alignment in the following due to padding to u64.
+        const_assert_eq!(mem::size_of::<HeaderEntry>() % mem::size_of::<u64>(), 0);
+
+        let limits = header.body_offset()?..total_size;
+        let ranges = [
+            // TODO: Find a way to do this programmatically even if the trait
+            // `core::marker::Copy` is not implemented for `core::ops::Range<usize>`.
+            Self::validated_body_range(Entry::Bcc, &header_entries, &limits)?,
+            Self::validated_body_range(Entry::DebugPolicy, &header_entries, &limits)?,
+        ];
+
+        Ok(Self { body, ranges })
     }
 
     /// Get slice containing the platform BCC.
-    pub fn get_entries(&mut self) -> (&mut [u8], Option<&mut [u8]>) {
-        let bcc_start = self.bcc_range.start;
-        let bcc_end = self.bcc_range.len();
+    pub fn get_entries(&mut self) -> Result<(&mut [u8], Option<&mut [u8]>)> {
+        // This assumes that the blobs are in-order w.r.t. the entries.
+        let bcc_range = self.get_entry_range(Entry::Bcc).ok_or(Error::MissingEntry(Entry::Bcc))?;
+        let dp_range = self.get_entry_range(Entry::DebugPolicy);
+        let bcc_start = bcc_range.start;
+        let bcc_end = bcc_range.len();
         let (_, rest) = self.body.split_at_mut(bcc_start);
         let (bcc, rest) = rest.split_at_mut(bcc_end);
 
-        let dp = if let Some(dp_range) = &self.dp_range {
-            let dp_start = dp_range.start.checked_sub(self.bcc_range.end).unwrap();
+        let dp = if let Some(dp_range) = dp_range {
+            let dp_start = dp_range.start.checked_sub(bcc_range.end).unwrap();
             let dp_end = dp_range.len();
             let (_, rest) = rest.split_at_mut(dp_start);
             let (dp, _) = rest.split_at_mut(dp_end);
@@ -221,6 +231,31 @@
             None
         };
 
-        (bcc, dp)
+        Ok((bcc, dp))
+    }
+
+    pub fn get_entry_range(&self, entry: Entry) -> Option<Range<usize>> {
+        self.ranges[entry as usize].clone()
+    }
+
+    fn validated_body_range(
+        entry: Entry,
+        header_entries: &[HeaderEntry],
+        limits: &Range<usize>,
+    ) -> Result<Option<Range<usize>>> {
+        if let Some(header_entry) = header_entries.get(entry as usize) {
+            if let Some(r) = header_entry.as_range() {
+                return if r.start <= r.end && r.is_within(limits) {
+                    let start = r.start - limits.start;
+                    let end = r.end - limits.start;
+
+                    Ok(Some(start..end))
+                } else {
+                    Err(Error::EntryOutOfBounds(entry, r, limits.clone()))
+                };
+            }
+        }
+
+        Ok(None)
     }
 }
diff --git a/pvmfw/src/entry.rs b/pvmfw/src/entry.rs
index 9c929a9..3efa61e 100644
--- a/pvmfw/src/entry.rs
+++ b/pvmfw/src/entry.rs
@@ -207,7 +207,10 @@
         RebootReason::InvalidConfig
     })?;
 
-    let (bcc_slice, debug_policy) = appended.get_entries();
+    let (bcc_slice, debug_policy) = appended.get_entries().map_err(|e| {
+        error!("Failed to obtained the config entries: {e}");
+        RebootReason::InvalidConfig
+    })?;
 
     // Up to this point, we were using the built-in static (from .rodata) page tables.
     MEMORY.lock().replace(MemoryTracker::new(
@@ -427,10 +430,10 @@
         }
     }
 
-    fn get_entries(&mut self) -> (&mut [u8], Option<&mut [u8]>) {
+    fn get_entries(&mut self) -> config::Result<(&mut [u8], Option<&mut [u8]>)> {
         match self {
             Self::Config(ref mut cfg) => cfg.get_entries(),
-            Self::LegacyBcc(ref mut bcc) => (bcc, None),
+            Self::LegacyBcc(ref mut bcc) => Ok((bcc, None)),
         }
     }
 }