use alloc::vec::Vec;
use alloc::string::String;
use core::mem::size_of;
use crate::crypto::{KeyPair, PUBLIC_KEY_SIZE, SIGNATURE_SIZE};
use crate::error::{Error, Result};
pub mod header;
pub mod manifest;
pub use header::{ModuleHeader, HeaderFlags};
pub use manifest::ModuleManifest;
pub const MAGIC: &[u8] = b"SPK\x02";
pub const FIXED_HEADER_SIZE: usize = 128;
pub const MAX_CODE_SIZE: usize = 1024 * 1024;
pub const FLAG_SIGNED: u32 = 0x0001;
pub const FLAG_COMPRESSED: u32 = 0x0002;
pub const FLAG_HW_LOCKED: u32 = 0x0004;
pub const FLAG_CRITICAL: u32 = 0x0008;
#[derive(Clone, Debug, PartialEq)]
pub struct Module {
pub header: ModuleHeader,
pub public_key: [u8; PUBLIC_KEY_SIZE],
pub signature: [u8; SIGNATURE_SIZE],
pub code: Vec<u8>,
pub manifest: Option<ModuleManifest>,
}
impl Module {
pub fn builder() -> ModuleBuilder {
ModuleBuilder::default()
}
pub fn to_bytes(&self) -> Result<Vec<u8>> {
let mut result = Vec::with_capacity(FIXED_HEADER_SIZE + self.code.len());
result.extend_from_slice(MAGIC);
result.extend_from_slice(&self.header.version.to_le_bytes());
let total_size = FIXED_HEADER_SIZE + self.code.len();
result.extend_from_slice(&(total_size as u32).to_le_bytes());
result.extend_from_slice(&(self.code.len() as u32).to_le_bytes());
result.extend_from_slice(&self.header.entry_offset.to_le_bytes());
let flags = self.header.flags;
result.extend_from_slice(&flags.to_le_bytes());
result.extend_from_slice(&self.header.monotonic_version.to_le_bytes());
result.extend_from_slice(&self.header.hw_revision.to_le_bytes());
let crc = crc32fast::hash(&self.code);
result.extend_from_slice(&crc.to_le_bytes());
result.extend_from_slice(&[0u8; 8]);
result.extend_from_slice(&self.public_key);
result.extend_from_slice(&self.signature);
result.extend_from_slice(&self.code);
Ok(result)
}
pub fn from_bytes(bytes: &[u8]) -> Result<Self> {
if bytes.len() < FIXED_HEADER_SIZE {
return Err(Error::invalid_format("data too short for header"));
}
if &bytes[0..4] != MAGIC {
return Err(Error::invalid_format(format!(
"invalid magic: expected {:?}, got {:?}",
MAGIC, &bytes[0..4]
)));
}
let version = u16::from_le_bytes([bytes[4], bytes[5]]);
if version < crate::MIN_MODULE_VERSION {
return Err(Error::VersionMismatch {
expected: crate::CURRENT_MODULE_VERSION,
found: version,
});
}
let total_size = u32::from_le_bytes([bytes[6], bytes[7], bytes[8], bytes[9]]) as usize;
let code_size = u32::from_le_bytes([bytes[10], bytes[11], bytes[12], bytes[13]]) as usize;
if code_size > MAX_CODE_SIZE {
return Err(Error::invalid_format(format!(
"code size {} exceeds maximum {}", code_size, MAX_CODE_SIZE
)));
}
if total_size != FIXED_HEADER_SIZE + code_size {
return Err(Error::invalid_format("total size mismatch"));
}
if bytes.len() < total_size {
return Err(Error::invalid_format("truncated module data"));
}
let header = ModuleHeader {
version,
entry_offset: u32::from_le_bytes([bytes[14], bytes[15], bytes[16], bytes[17]]),
flags: u32::from_le_bytes([bytes[18], bytes[19], bytes[20], bytes[21]]),
monotonic_version: u64::from_le_bytes([
bytes[22], bytes[23], bytes[24], bytes[25],
bytes[26], bytes[27], bytes[28], bytes[29],
]),
hw_revision: u32::from_le_bytes([bytes[30], bytes[31], bytes[32], bytes[33]]),
code_crc: u32::from_le_bytes([bytes[34], bytes[35], bytes[36], bytes[37]]),
};
let mut public_key = [0u8; PUBLIC_KEY_SIZE];
public_key.copy_from_slice(&bytes[48..80]);
let mut signature = [0u8; SIGNATURE_SIZE];
signature.copy_from_slice(&bytes[80..144]);
let code = bytes[FIXED_HEADER_SIZE..FIXED_HEADER_SIZE + code_size].to_vec();
let actual_crc = crc32fast::hash(&code);
if actual_crc != header.code_crc {
return Err(Error::invalid_format(format!(
"CRC mismatch: expected {}, got {}", header.code_crc, actual_crc
)));
}
Ok(Self {
header,
public_key,
signature,
code,
manifest: None,
})
}
pub fn verify(&self) -> Result<()> {
KeyPair::verify_module(self)
}
pub fn serialized_size(&self) -> usize {
FIXED_HEADER_SIZE + self.code.len()
}
pub fn is_signed(&self) -> bool {
self.header.flags & FLAG_SIGNED != 0
}
pub fn is_compressed(&self) -> bool {
self.header.flags & FLAG_COMPRESSED != 0
}
}
#[derive(Default)]
pub struct ModuleBuilder {
code: Vec<u8>,
entry_offset: u32,
flags: u32,
monotonic_version: u64,
hw_revision: u32,
manifest: Option<ModuleManifest>,
}
impl ModuleBuilder {
pub fn code(mut self, code: Vec<u8>) -> Self {
self.code = code;
self
}
pub fn entry_offset(mut self, offset: u32) -> Self {
self.entry_offset = offset;
self
}
pub fn hw_revision(mut self, rev: u32) -> Self {
self.hw_revision = rev;
self.flags |= FLAG_HW_LOCKED;
self
}
pub fn version(mut self, version: u64) -> Self {
self.monotonic_version = version;
self
}
pub fn manifest(mut self, manifest: ModuleManifest) -> Self {
self.manifest = Some(manifest);
self
}
pub fn sign(self, keypair: &KeyPair) -> SignedModuleBuilder {
SignedModuleBuilder {
inner: self,
keypair: keypair.clone(),
}
}
pub fn build_unsigned(self) -> Result<Module> {
if self.code.is_empty() {
return Err(Error::invalid_format("code cannot be empty"));
}
Ok(Module {
header: ModuleHeader {
version: crate::CURRENT_MODULE_VERSION,
entry_offset: self.entry_offset,
flags: self.flags,
monotonic_version: self.monotonic_version,
hw_revision: self.hw_revision,
code_crc: crc32fast::hash(&self.code),
},
public_key: [0u8; PUBLIC_KEY_SIZE],
signature: [0u8; SIGNATURE_SIZE],
code: self.code,
manifest: self.manifest,
})
}
}
pub struct SignedModuleBuilder {
inner: ModuleBuilder,
keypair: KeyPair,
}
impl SignedModuleBuilder {
pub fn build(self) -> Result<Module> {
let mut module = self.inner.build_unsigned()?;
self.keypair.sign_module(&mut module);
Ok(module)
}
}