memlink-runtime 0.2.0

Dynamic module loading framework with circuit breaker, caching, pooling, health checks, versioning, and auto-discovery
Documentation
//! Module sandboxing and resource isolation.
//!
//! Provides resource limits and security constraints for module execution.

use std::sync::atomic::{AtomicU64, AtomicUsize, Ordering};
use std::time::Duration;

/// Sandbox configuration.
#[derive(Debug, Clone)]
pub struct SandboxConfig {
    /// Maximum memory usage (bytes).
    pub max_memory_bytes: usize,
    /// Maximum CPU time (seconds).
    pub max_cpu_time_secs: u64,
    /// Maximum execution time per call.
    pub max_call_duration: Duration,
    /// Whether network access is allowed.
    pub allow_network: bool,
    /// Whether file system access is allowed.
    pub allow_filesystem: bool,
    /// Allowed syscalls (empty = all allowed).
    pub allowed_syscalls: Vec<u32>,
}

impl Default for SandboxConfig {
    fn default() -> Self {
        Self {
            max_memory_bytes: 128 * 1024 * 1024, // 128 MB
            max_cpu_time_secs: 60,
            max_call_duration: Duration::from_secs(30),
            allow_network: false,
            allow_filesystem: true,
            allowed_syscalls: Vec::new(),
        }
    }
}

/// Resource usage statistics.
#[derive(Debug, Clone)]
pub struct ResourceUsage {
    /// Current memory usage (bytes).
    pub memory_bytes: usize,
    /// Peak memory usage (bytes).
    pub peak_memory_bytes: usize,
    /// CPU time used (milliseconds).
    pub cpu_time_ms: u64,
    /// Total calls executed.
    pub total_calls: u64,
    /// Total syscalls made.
    pub total_syscalls: u64,
}

/// Resource tracker for a module.
#[derive(Debug)]
pub struct ResourceTracker {
    /// Current memory usage.
    memory_bytes: AtomicUsize,
    /// Peak memory usage.
    peak_memory_bytes: AtomicUsize,
    /// CPU time used (milliseconds).
    cpu_time_ms: AtomicU64,
    /// Total calls.
    total_calls: AtomicU64,
    /// Total syscalls.
    total_syscalls: AtomicU64,
}

impl ResourceTracker {
    /// Creates a new resource tracker.
    pub fn new() -> Self {
        Self {
            memory_bytes: AtomicUsize::new(0),
            peak_memory_bytes: AtomicUsize::new(0),
            cpu_time_ms: AtomicU64::new(0),
            total_calls: AtomicU64::new(0),
            total_syscalls: AtomicU64::new(0),
        }
    }

    /// Updates memory usage.
    pub fn update_memory(&self, bytes: usize) {
        self.memory_bytes.store(bytes, Ordering::Release);

        // Update peak
        let mut peak = self.peak_memory_bytes.load(Ordering::Acquire);
        while bytes > peak {
            match self.peak_memory_bytes.compare_exchange_weak(
                peak,
                bytes,
                Ordering::Relaxed,
                Ordering::Relaxed,
            ) {
                Ok(_) => break,
                Err(x) => peak = x,
            }
        }
    }

    /// Records a call.
    pub fn record_call(&self) {
        self.total_calls.fetch_add(1, Ordering::Relaxed);
    }

    /// Records CPU time.
    pub fn record_cpu_time(&self, ms: u64) {
        self.cpu_time_ms.fetch_add(ms, Ordering::Relaxed);
    }

    /// Records a syscall.
    pub fn record_syscall(&self) {
        self.total_syscalls.fetch_add(1, Ordering::Relaxed);
    }

    /// Returns current usage.
    pub fn usage(&self) -> ResourceUsage {
        ResourceUsage {
            memory_bytes: self.memory_bytes.load(Ordering::Acquire),
            peak_memory_bytes: self.peak_memory_bytes.load(Ordering::Acquire),
            cpu_time_ms: self.cpu_time_ms.load(Ordering::Acquire),
            total_calls: self.total_calls.load(Ordering::Acquire),
            total_syscalls: self.total_syscalls.load(Ordering::Acquire),
        }
    }

    /// Checks if resource limits are exceeded.
    pub fn exceeds_limits(&self, config: &SandboxConfig) -> bool {
        self.memory_bytes.load(Ordering::Acquire) > config.max_memory_bytes
    }

    /// Resets all counters.
    pub fn reset(&self) {
        self.memory_bytes.store(0, Ordering::Release);
        self.cpu_time_ms.store(0, Ordering::Relaxed);
        self.total_calls.store(0, Ordering::Relaxed);
        self.total_syscalls.store(0, Ordering::Relaxed);
    }
}

impl Default for ResourceTracker {
    fn default() -> Self {
        Self::new()
    }
}

/// Permission flags for module operations.
#[derive(Debug, Clone, Copy)]
pub struct Permissions {
    /// Can allocate memory.
    pub can_allocate: bool,
    /// Can make network calls.
    pub can_network: bool,
    /// Can access filesystem.
    pub can_filesystem: bool,
    /// Can spawn threads.
    pub can_thread: bool,
    /// Can access environment variables.
    pub can_env: bool,
}

impl Default for Permissions {
    fn default() -> Self {
        Self {
            can_allocate: true,
            can_network: false,
            can_filesystem: true,
            can_thread: true,
            can_env: false,
        }
    }
}

impl Permissions {
    /// Creates restrictive permissions (minimal access).
    pub fn restrictive() -> Self {
        Self {
            can_allocate: true,
            can_network: false,
            can_filesystem: false,
            can_thread: false,
            can_env: false,
        }
    }

    /// Creates permissive permissions (full access).
    pub fn permissive() -> Self {
        Self {
            can_allocate: true,
            can_network: true,
            can_filesystem: true,
            can_thread: true,
            can_env: true,
        }
    }

    /// Checks if a permission is granted.
    pub fn check(&self, permission: Permission) -> bool {
        match permission {
            Permission::Allocate => self.can_allocate,
            Permission::Network => self.can_network,
            Permission::Filesystem => self.can_filesystem,
            Permission::Thread => self.can_thread,
            Permission::Env => self.can_env,
        }
    }
}

/// Individual permission types.
#[derive(Debug, Clone, Copy)]
pub enum Permission {
    Allocate,
    Network,
    Filesystem,
    Thread,
    Env,
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_sandbox_config() {
        let config = SandboxConfig::default();
        assert_eq!(config.max_memory_bytes, 128 * 1024 * 1024);
        assert!(!config.allow_network);
    }

    #[test]
    fn test_resource_tracker() {
        let tracker = ResourceTracker::new();

        tracker.update_memory(1000);
        tracker.record_call();

        let usage = tracker.usage();
        assert_eq!(usage.memory_bytes, 1000);
        assert_eq!(usage.total_calls, 1);
    }

    #[test]
    fn test_permissions() {
        let perms = Permissions::restrictive();
        assert!(perms.can_allocate);
        assert!(!perms.can_network);
        assert!(!perms.can_filesystem);
    }
}