use crate::cursor::ManifestCursor;
use crate::epoch::{EpochId, EPOCH_FORMAT_VERSION};
use crate::error::CoreError;
pub const EPOCH_MAGIC: u32 = 0x4C49_4D45;
pub const HEADER_SIZE: usize = 200;
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
pub struct EpochFlags(u16);
impl EpochFlags {
#[must_use]
pub const fn empty() -> Self {
Self(0)
}
#[must_use]
pub const fn signed(self) -> Self {
Self(self.0 | 0x0001)
}
#[must_use]
pub const fn timestamped(self) -> Self {
Self(self.0 | 0x0002)
}
#[must_use]
pub const fn sealed(self) -> Self {
Self(self.0 | 0x0004)
}
#[must_use]
pub const fn as_u16(self) -> u16 {
self.0
}
#[must_use]
pub const fn from_u16(raw: u16) -> Self {
Self(raw)
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct EpochHeader {
pub version: u16,
pub flags: EpochFlags,
pub parent_epoch_id: EpochId,
pub base_image_root: EpochId,
pub epoch_sequence: u64,
pub ops_hash: [u8; 32],
pub drops_hash: [u8; 32],
pub own_epoch_id: EpochId,
pub timestamp_unix: u64,
}
impl Default for EpochHeader {
fn default() -> Self {
Self {
version: EPOCH_FORMAT_VERSION,
flags: EpochFlags::empty(),
parent_epoch_id: [0u8; 32],
base_image_root: [0u8; 32],
epoch_sequence: 0,
ops_hash: [0u8; 32],
drops_hash: [0u8; 32],
own_epoch_id: [0u8; 32],
timestamp_unix: 0,
}
}
}
pub fn write_epoch_header(header: &EpochHeader, out: &mut Vec<u8>) {
out.extend_from_slice(&EPOCH_MAGIC.to_le_bytes());
out.extend_from_slice(&header.version.to_le_bytes());
out.extend_from_slice(&header.flags.as_u16().to_le_bytes());
out.extend_from_slice(&header.parent_epoch_id);
out.extend_from_slice(&header.base_image_root);
out.extend_from_slice(&header.epoch_sequence.to_le_bytes());
out.extend_from_slice(&header.ops_hash);
out.extend_from_slice(&header.drops_hash);
out.extend_from_slice(&header.own_epoch_id);
out.extend_from_slice(&header.timestamp_unix.to_le_bytes());
out.extend_from_slice(&[0u8; 16]); debug_assert_eq!(out.len(), HEADER_SIZE, "header size invariant");
}
pub fn parse_epoch_header(cursor: &mut ManifestCursor<'_>) -> Result<EpochHeader, CoreError> {
fn copy_id(cursor: &mut ManifestCursor<'_>, label: &str) -> Result<[u8; 32], CoreError> {
let bytes = cursor.read_n(32).map_err(|e| CoreError::Corrupt {
reason: format!("epoch header: truncated before {label}: {e}"),
})?;
let mut id = [0u8; 32];
id.copy_from_slice(bytes);
Ok(id)
}
let magic = cursor.read_u32_le().map_err(|e| CoreError::Corrupt {
reason: format!("epoch header: truncated before magic: {e}"),
})?;
if magic != EPOCH_MAGIC {
return Err(CoreError::Corrupt {
reason: format!(
"epoch header: magic 0x{magic:08X} does not match LIME (0x{EPOCH_MAGIC:08X})"
),
});
}
let version = cursor.read_u16_le().map_err(|e| CoreError::Corrupt {
reason: format!("epoch header: truncated before version: {e}"),
})?;
if version > EPOCH_FORMAT_VERSION {
return Err(CoreError::Corrupt {
reason: format!(
"epoch header: version {version} exceeds supported {EPOCH_FORMAT_VERSION}"
),
});
}
let flags_raw = cursor.read_u16_le().map_err(|e| CoreError::Corrupt {
reason: format!("epoch header: truncated before flags: {e}"),
})?;
let parent_epoch_id = copy_id(cursor, "parent_epoch_id")?;
let base_image_root = copy_id(cursor, "base_image_root")?;
let epoch_sequence = cursor.read_u64_le().map_err(|e| CoreError::Corrupt {
reason: format!("epoch header: truncated before epoch_sequence: {e}"),
})?;
let ops_hash = copy_id(cursor, "ops_hash")?;
let drops_hash = copy_id(cursor, "drops_hash")?;
let own_epoch_id = copy_id(cursor, "own_epoch_id")?;
let timestamp_unix = cursor.read_u64_le().map_err(|e| CoreError::Corrupt {
reason: format!("epoch header: truncated before timestamp: {e}"),
})?;
cursor.skip(16).map_err(|e| CoreError::Corrupt {
reason: format!("epoch header: truncated before end of reserved: {e}"),
})?;
Ok(EpochHeader {
version,
flags: EpochFlags::from_u16(flags_raw),
parent_epoch_id,
base_image_root,
epoch_sequence,
ops_hash,
drops_hash,
own_epoch_id,
timestamp_unix,
})
}