speck-core 0.2.0

Secure runtime package manager for MMU-less microcontrollers
Documentation
//! Streaming delta applier for memory-constrained environments

use alloc::vec::Vec;
use crate::delta::{Delta, Op};
use crate::error::{Error, Result};

/// Applies deltas with configurable buffer sizes for embedded use
pub struct DeltaApplier {
    /// Buffer size for streaming operations
    buffer_size: usize,
}

impl Default for DeltaApplier {
    fn default() -> Self {
        Self {
            buffer_size: 4096,
        }
    }
}

impl DeltaApplier {
    /// Create with default settings
    pub fn new() -> Self {
        Self::default()
    }
    
    /// Set buffer size for I/O operations
    pub fn buffer_size(mut self, size: usize) -> Self {
        self.buffer_size = size.max(256);
        self
    }
    
    /// Apply delta to source in memory
    pub fn apply(&self, source: &[u8], delta: &Delta) -> Result<Vec<u8>> {
        if source.len() as u64 != delta.source_size {
            return Err(Error::delta(format!(
                "source size mismatch: expected {}, got {}",
                delta.source_size, source.len()
            )));
        }
        
        let capacity = (self.buffer_size * 10).min(delta.target_size as usize);
        let mut result = Vec::with_capacity(capacity);
        
        for op in &delta.ops {
            match op {
                Op::Copy { src_offset, length } => {
                    let start = *src_offset as usize;
                    let len = *length as usize;
                    
                    if start.saturating_add(len) > source.len() {
                        return Err(Error::delta(format!(
                            "copy out of bounds: offset={}, len={}, source={}",
                            start, len, source.len()
                        )));
                    }
                    
                    result.extend_from_slice(&source[start..start + len]);
                }
                Op::Insert { data } => {
                    result.extend_from_slice(data);
                }
            }
            
            // Check for overflow
            if result.len() > delta.target_size as usize {
                return Err(Error::delta("output exceeded expected size"));
            }
        }
        
        if result.len() as u64 != delta.target_size {
            return Err(Error::delta(format!(
                "size mismatch: expected {}, got {}",
                delta.target_size, result.len()
            )));
        }
        
        Ok(result)
    }
    
    /// Verify delta can be applied without actually applying
    pub fn verify(&self, source_len: usize, delta: &Delta) -> Result<()> {
        if source_len as u64 != delta.source_size {
            return Err(Error::delta("source length mismatch"));
        }
        
        let mut output_size: u64 = 0;
        
        for op in &delta.ops {
            match op {
                Op::Copy { src_offset, length } => {
                    if src_offset.saturating_add(*length) > source_len as u64 {
                        return Err(Error::delta("copy extends past source"));
                    }
                    output_size += length;
                }
                Op::Insert { data } => {
                    output_size += data.len() as u64;
                }
            }
            
            if output_size > delta.target_size {
                return Err(Error::delta("output would exceed target size"));
            }
        }
        
        if output_size != delta.target_size {
            return Err(Error::delta("final size mismatch"));
        }
        
        Ok(())
    }
}