rocketsplash-formats 0.3.0

Shared data types and serialization formats for Rocketsplash TUI animations
Documentation
// <FILE>crates/rocketsplash-formats/src/limits.rs</FILE>
// <DESC>Safety limits and allocation tracking for serialized assets</DESC>
// <VERS>VERSION: 2.0.0</VERS>
// <WCTX>Foundation crates 0.3.0 (RELEASE_PLAN D2 typed errors, D3 adversarial hardening)</WCTX>
// <CLOG>2.0.0: typed FormatError results (breaking); add MAX_SERIALIZED_BYTES and MAX_ANIMATION_FRAMES for the decode path. 1.0.0: dimension validation and allocation tracker.</CLOG>

use crate::FormatError;

/// Maximum width in terminal columns for any glyph or splash.
pub const MAX_WIDTH: u32 = 4096;

/// Maximum height in terminal rows for any glyph or splash.
pub const MAX_HEIGHT: u32 = 4096;

/// Maximum number of glyphs in a font atlas.
pub const MAX_GLYPHS: usize = 65_536;

/// Maximum total memory allocation for a single font atlas (256 MB).
pub const MAX_TOTAL_ALLOCATION: usize = 256 * 1024 * 1024;

/// Maximum number of animation frames in a splash.
pub const MAX_ANIMATION_FRAMES: usize = 1024;

/// Maximum serialized payload size accepted by the decode entry points (256 MB).
///
/// Decoding from a byte slice allocates memory proportional to the input, so
/// bounding the input bounds decode-time allocation (RELEASE_PLAN D3). Any
/// asset within the runtime allocation budget fits well inside this cap.
pub const MAX_SERIALIZED_BYTES: usize = 256 * 1024 * 1024;

/// Validate dimensions before any allocation.
pub fn validate_dimensions(width: u32, height: u32) -> Result<usize, FormatError> {
    if width > MAX_WIDTH {
        return Err(FormatError::WidthExceeded {
            width,
            max: MAX_WIDTH,
        });
    }
    if height > MAX_HEIGHT {
        return Err(FormatError::HeightExceeded {
            height,
            max: MAX_HEIGHT,
        });
    }
    let w = width as usize;
    let h = height as usize;
    w.checked_mul(h)
        .ok_or(FormatError::DimensionOverflow { width, height })
}

/// Track cumulative allocation during font loading.
#[derive(Debug, Default)]
pub struct AllocationTracker {
    total: usize,
    limit: usize,
}

impl AllocationTracker {
    pub fn new() -> Self {
        Self {
            total: 0,
            limit: MAX_TOTAL_ALLOCATION,
        }
    }

    pub fn reserve(&mut self, bytes: usize) -> Result<(), FormatError> {
        let new_total = self
            .total
            .checked_add(bytes)
            .ok_or(FormatError::AllocationOverflow)?;
        if new_total > self.limit {
            return Err(FormatError::AllocationExceeded {
                requested: new_total,
                limit: self.limit,
            });
        }
        self.total = new_total;
        Ok(())
    }
}

// <FILE>crates/rocketsplash-formats/src/limits.rs</FILE>
// <VERS>END OF VERSION: 2.0.0</VERS>