dynpatch-core 0.1.0

Runtime engine for dynpatch - dynamic library loading, ABI validation, and transactional patching
Documentation
//! Patch registry and lifecycle management

use crate::error::{Error, Result};
use crate::loader::{Library, PatchLoader};
use crate::metrics::global_metrics;
use crate::validator::Validator;
use arc_swap::ArcSwap;
use dynpatch_interface::Version;
use std::sync::{Arc, Mutex};
use std::time::{SystemTime, UNIX_EPOCH};
use tracing::{debug, info, error, warn};

/// Information about a loaded patch
#[derive(Debug, Clone)]
pub struct PatchInfo {
    pub name: String,
    pub version: Version,
    pub path: String,
    pub loaded_at: u64,
}

impl From<&Library> for PatchInfo {
    fn from(lib: &Library) -> Self {
        let metadata = lib.metadata();
        Self {
            name: metadata.name.clone(),
            version: metadata.version.clone(),
            path: lib.path().to_string_lossy().to_string(),
            loaded_at: SystemTime::now()
                .duration_since(UNIX_EPOCH)
                .unwrap()
                .as_secs(),
        }
    }
}

/// Historical record of a patch load/unload
#[derive(Debug, Clone)]
pub struct PatchRecord {
    pub info: PatchInfo,
    pub action: PatchAction,
    pub timestamp: u64,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PatchAction {
    Loaded,
    Unloaded,
    RolledBack,
}

/// Registry for managing patches
pub struct PatchRegistry {
    active: Arc<ArcSwap<Option<Arc<Library>>>>,
    history: Arc<Mutex<Vec<Arc<Library>>>>,
    records: Arc<Mutex<Vec<PatchRecord>>>,
    loader: PatchLoader,
    expected_interface_version: Version,
    expected_type_hash: u64,
}

impl PatchRegistry {
    pub fn new(expected_interface_version: Version, expected_type_hash: u64) -> Self {
        Self {
            active: Arc::new(ArcSwap::new(Arc::new(None))),
            history: Arc::new(Mutex::new(Vec::new())),
            records: Arc::new(Mutex::new(Vec::new())),
            loader: PatchLoader::new(),
            expected_interface_version,
            expected_type_hash,
        }
    }

    /// Load a patch from the specified path
    ///
    /// This performs a transactional load with the following steps:
    /// 1. Load the dynamic library
    /// 2. Validate ABI/version compatibility
    /// 3. Execute entry point (if present)
    /// 4. On success, atomically swap the active patch
    /// 5. On failure at any step, rollback and return error
    pub fn load(&self, path: &str) -> Result<()> {
        info!("Loading patch from: {}", path);
        global_metrics().record_load_attempt();

        // Load the library
        let library = match self.loader.load(path) {
            Ok(lib) => lib,
            Err(e) => {
                error!("Failed to load library: {}", e);
                global_metrics().record_load_failure();
                return Err(e);
            }
        };

        // Validate
        let validator = Validator::new(
            self.expected_interface_version.clone(),
            self.expected_type_hash,
        );
        
        if let Err(e) = validator.validate(&library) {
            error!("Validation failed for {}: {}", path, e);
            global_metrics().record_validation_failure();
            global_metrics().record_load_failure();
            return Err(e);
        }

        // Execute entry point (transactional - rollback on failure)
        if let Err(e) = library.call_entry_point() {
            error!("Entry point execution failed: {}", e);
            global_metrics().record_load_failure();
            return Err(e);
        }

        // Create patch info
        let patch_info = PatchInfo::from(&library);
        let library = Arc::new(library);

        // Transactional swap
        let old_active = self.active.load();
        
        // If there was an old patch, move it to history
        if let Some(ref old_lib) = **old_active {
            let mut history = self.history.lock().unwrap();
            history.push(old_lib.clone());
            debug!("Moved previous patch to history");
        }

        // Activate new patch
        self.active.store(Arc::new(Some(library)));

        // Record the action
        let mut records = self.records.lock().unwrap();
        records.push(PatchRecord {
            info: patch_info,
            action: PatchAction::Loaded,
            timestamp: SystemTime::now()
                .duration_since(UNIX_EPOCH)
                .unwrap()
                .as_secs(),
        });

        global_metrics().record_load_success();
        info!("Patch loaded successfully");
        Ok(())
    }

    /// Rollback to the previous patch
    ///
    /// Restores the most recent patch from history.
    /// This is an atomic operation.
    pub fn rollback(&self) -> Result<()> {
        info!("Rolling back to previous patch");
        global_metrics().record_rollback();

        let mut history = self.history.lock().unwrap();
        
        let previous = history.pop().ok_or(Error::NoPreviousPatch)?;
        
        let patch_info = PatchInfo::from(previous.as_ref());
        
        // Swap back to previous
        self.active.store(Arc::new(Some(previous)));

        // Record the rollback
        let mut records = self.records.lock().unwrap();
        records.push(PatchRecord {
            info: patch_info,
            action: PatchAction::RolledBack,
            timestamp: SystemTime::now()
                .duration_since(UNIX_EPOCH)
                .unwrap()
                .as_secs(),
        });

        info!("Rollback successful");
        Ok(())
    }

    /// Get the currently active patch
    pub fn active_patch(&self) -> Option<PatchInfo> {
        let active = self.active.load();
        active.as_ref().as_ref().map(|lib| PatchInfo::from(lib.as_ref()))
    }

    /// Get the patch history
    pub fn history(&self) -> Vec<PatchRecord> {
        self.records.lock().unwrap().clone()
    }

    /// Clear all patches and history
    pub fn clear(&self) {
        self.active.store(Arc::new(None));
        self.history.lock().unwrap().clear();
        self.records.lock().unwrap().clear();
        info!("Registry cleared");
    }
}

// Global registry instance
static GLOBAL_REGISTRY: once_cell::sync::Lazy<PatchRegistry> = once_cell::sync::Lazy::new(|| {
    // Default version and hash - applications should initialize properly
    PatchRegistry::new(Version::new(0, 1, 0), 0)
});

/// Get the global patch registry
pub fn global_registry() -> &'static PatchRegistry {
    &GLOBAL_REGISTRY
}

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

    #[test]
    fn test_registry_creation() {
        let registry = PatchRegistry::new(Version::new(1, 0, 0), 0x1234);
        assert!(registry.active_patch().is_none());
        assert_eq!(registry.history().len(), 0);
    }

    #[test]
    fn test_global_registry() {
        let registry = global_registry();
        assert!(registry.active_patch().is_none());
    }
}