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};
#[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(),
}
}
}
#[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,
}
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,
}
}
pub fn load(&self, path: &str) -> Result<()> {
info!("Loading patch from: {}", path);
global_metrics().record_load_attempt();
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);
}
};
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);
}
if let Err(e) = library.call_entry_point() {
error!("Entry point execution failed: {}", e);
global_metrics().record_load_failure();
return Err(e);
}
let patch_info = PatchInfo::from(&library);
let library = Arc::new(library);
let old_active = self.active.load();
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");
}
self.active.store(Arc::new(Some(library)));
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(())
}
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());
self.active.store(Arc::new(Some(previous)));
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(())
}
pub fn active_patch(&self) -> Option<PatchInfo> {
let active = self.active.load();
active.as_ref().as_ref().map(|lib| PatchInfo::from(lib.as_ref()))
}
pub fn history(&self) -> Vec<PatchRecord> {
self.records.lock().unwrap().clone()
}
pub fn clear(&self) {
self.active.store(Arc::new(None));
self.history.lock().unwrap().clear();
self.records.lock().unwrap().clear();
info!("Registry cleared");
}
}
static GLOBAL_REGISTRY: once_cell::sync::Lazy<PatchRegistry> = once_cell::sync::Lazy::new(|| {
PatchRegistry::new(Version::new(0, 1, 0), 0)
});
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());
}
}