speck-core 0.2.0

Secure runtime package manager for MMU-less microcontrollers
Documentation
//! Module format definition and serialization
//! 
//! The Speck module format is designed for:
//! - Compact representation (minimal overhead)
//! - Streaming parsing (no heap required for verification)
//! - Position independence (runtime relocatable)
//! - Extensibility (flags and reserved fields)

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;

/// Magic number for Speck modules: "SPK\x02" (version 2)
pub const MAGIC: &[u8] = b"SPK\x02";

/// Size of fixed header (everything except code)
pub const FIXED_HEADER_SIZE: usize = 128;

/// Maximum supported code size (1MB to fit in 20-bit offsets)
pub const MAX_CODE_SIZE: usize = 1024 * 1024;

/// Flag: Module is signed
pub const FLAG_SIGNED: u32 = 0x0001;

/// Flag: Code is compressed (gzip)
pub const FLAG_COMPRESSED: u32 = 0x0002;

/// Flag: Module requires specific hardware revision
pub const FLAG_HW_LOCKED: u32 = 0x0004;

/// Flag: Critical update (cannot be skipped)
pub const FLAG_CRITICAL: u32 = 0x0008;

/// Complete module structure
#[derive(Clone, Debug, PartialEq)]
pub struct Module {
    /// Header metadata
    pub header: ModuleHeader,
    /// Ed25519 public key (if signed)
    pub public_key: [u8; PUBLIC_KEY_SIZE],
    /// Ed25519 signature
    pub signature: [u8; SIGNATURE_SIZE],
    /// Code payload
    pub code: Vec<u8>,
    /// Optional manifest (JSON/TOML encoded)
    pub manifest: Option<ModuleManifest>,
}

impl Module {
    /// Create a builder for constructing modules
    pub fn builder() -> ModuleBuilder {
        ModuleBuilder::default()
    }
    
    /// Serialize to bytes
    pub fn to_bytes(&self) -> Result<Vec<u8>> {
        let mut result = Vec::with_capacity(FIXED_HEADER_SIZE + self.code.len());
        
        // Magic
        result.extend_from_slice(MAGIC);
        
        // Version
        result.extend_from_slice(&self.header.version.to_le_bytes());
        
        // Total size (header + code)
        let total_size = FIXED_HEADER_SIZE + self.code.len();
        result.extend_from_slice(&(total_size as u32).to_le_bytes());
        
        // Code size
        result.extend_from_slice(&(self.code.len() as u32).to_le_bytes());
        
        // Entry offset
        result.extend_from_slice(&self.header.entry_offset.to_le_bytes());
        
        // Flags
        let flags = self.header.flags;
        result.extend_from_slice(&flags.to_le_bytes());
        
        // Monotonic version (anti-rollback)
        result.extend_from_slice(&self.header.monotonic_version.to_le_bytes());
        
        // Hardware revision requirement
        result.extend_from_slice(&self.header.hw_revision.to_le_bytes());
        
        // CRC32 of code (integrity check separate from signature)
        let crc = crc32fast::hash(&self.code);
        result.extend_from_slice(&crc.to_le_bytes());
        
        // Reserved (8 bytes for future use)
        result.extend_from_slice(&[0u8; 8]);
        
        // Public key
        result.extend_from_slice(&self.public_key);
        
        // Signature
        result.extend_from_slice(&self.signature);
        
        // Code payload
        result.extend_from_slice(&self.code);
        
        Ok(result)
    }
    
    /// Deserialize from bytes
    pub fn from_bytes(bytes: &[u8]) -> Result<Self> {
        if bytes.len() < FIXED_HEADER_SIZE {
            return Err(Error::invalid_format("data too short for header"));
        }
        
        // Check magic
        if &bytes[0..4] != MAGIC {
            return Err(Error::invalid_format(format!(
                "invalid magic: expected {:?}, got {:?}", 
                MAGIC, &bytes[0..4]
            )));
        }
        
        // Parse version
        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,
            });
        }
        
        // Parse sizes
        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();
        
        // Verify CRC
        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,
        })
    }
    
    /// Verify signature using embedded public key
    pub fn verify(&self) -> Result<()> {
        KeyPair::verify_module(self)
    }
    
    /// Get the total serialized size
    pub fn serialized_size(&self) -> usize {
        FIXED_HEADER_SIZE + self.code.len()
    }
    
    /// Check if module is signed
    pub fn is_signed(&self) -> bool {
        self.header.flags & FLAG_SIGNED != 0
    }
    
    /// Check if code is compressed
    pub fn is_compressed(&self) -> bool {
        self.header.flags & FLAG_COMPRESSED != 0
    }
}

/// Builder for constructing modules
#[derive(Default)]
pub struct ModuleBuilder {
    code: Vec<u8>,
    entry_offset: u32,
    flags: u32,
    monotonic_version: u64,
    hw_revision: u32,
    manifest: Option<ModuleManifest>,
}

impl ModuleBuilder {
    /// Set the code payload
    pub fn code(mut self, code: Vec<u8>) -> Self {
        self.code = code;
        self
    }
    
    /// Set entry point offset
    pub fn entry_offset(mut self, offset: u32) -> Self {
        self.entry_offset = offset;
        self
    }
    
    /// Set hardware revision requirement
    pub fn hw_revision(mut self, rev: u32) -> Self {
        self.hw_revision = rev;
        self.flags |= FLAG_HW_LOCKED;
        self
    }
    
    /// Set monotonic version (for anti-rollback)
    pub fn version(mut self, version: u64) -> Self {
        self.monotonic_version = version;
        self
    }
    
    /// Add manifest metadata
    pub fn manifest(mut self, manifest: ModuleManifest) -> Self {
        self.manifest = Some(manifest);
        self
    }
    
    /// Sign the module with a key pair
    pub fn sign(self, keypair: &KeyPair) -> SignedModuleBuilder {
        SignedModuleBuilder {
            inner: self,
            keypair: keypair.clone(),
        }
    }
    
    /// Build unsigned module (for testing only)
    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,
        })
    }
}

/// Intermediate builder for signed modules
pub struct SignedModuleBuilder {
    inner: ModuleBuilder,
    keypair: KeyPair,
}

impl SignedModuleBuilder {
    /// Finalize and build the module
    pub fn build(self) -> Result<Module> {
        let mut module = self.inner.build_unsigned()?;
        self.keypair.sign_module(&mut module);
        Ok(module)
    }
}