dynpatch_core/
registry.rs1use crate::error::{Error, Result};
4use crate::loader::{Library, PatchLoader};
5use crate::metrics::global_metrics;
6use crate::validator::Validator;
7use arc_swap::ArcSwap;
8use dynpatch_interface::Version;
9use std::sync::{Arc, Mutex};
10use std::time::{SystemTime, UNIX_EPOCH};
11use tracing::{debug, info, error, warn};
12
13#[derive(Debug, Clone)]
15pub struct PatchInfo {
16 pub name: String,
17 pub version: Version,
18 pub path: String,
19 pub loaded_at: u64,
20}
21
22impl From<&Library> for PatchInfo {
23 fn from(lib: &Library) -> Self {
24 let metadata = lib.metadata();
25 Self {
26 name: metadata.name.clone(),
27 version: metadata.version.clone(),
28 path: lib.path().to_string_lossy().to_string(),
29 loaded_at: SystemTime::now()
30 .duration_since(UNIX_EPOCH)
31 .unwrap()
32 .as_secs(),
33 }
34 }
35}
36
37#[derive(Debug, Clone)]
39pub struct PatchRecord {
40 pub info: PatchInfo,
41 pub action: PatchAction,
42 pub timestamp: u64,
43}
44
45#[derive(Debug, Clone, Copy, PartialEq, Eq)]
46pub enum PatchAction {
47 Loaded,
48 Unloaded,
49 RolledBack,
50}
51
52pub struct PatchRegistry {
54 active: Arc<ArcSwap<Option<Arc<Library>>>>,
55 history: Arc<Mutex<Vec<Arc<Library>>>>,
56 records: Arc<Mutex<Vec<PatchRecord>>>,
57 loader: PatchLoader,
58 expected_interface_version: Version,
59 expected_type_hash: u64,
60}
61
62impl PatchRegistry {
63 pub fn new(expected_interface_version: Version, expected_type_hash: u64) -> Self {
64 Self {
65 active: Arc::new(ArcSwap::new(Arc::new(None))),
66 history: Arc::new(Mutex::new(Vec::new())),
67 records: Arc::new(Mutex::new(Vec::new())),
68 loader: PatchLoader::new(),
69 expected_interface_version,
70 expected_type_hash,
71 }
72 }
73
74 pub fn load(&self, path: &str) -> Result<()> {
83 info!("Loading patch from: {}", path);
84 global_metrics().record_load_attempt();
85
86 let library = match self.loader.load(path) {
88 Ok(lib) => lib,
89 Err(e) => {
90 error!("Failed to load library: {}", e);
91 global_metrics().record_load_failure();
92 return Err(e);
93 }
94 };
95
96 let validator = Validator::new(
98 self.expected_interface_version.clone(),
99 self.expected_type_hash,
100 );
101
102 if let Err(e) = validator.validate(&library) {
103 error!("Validation failed for {}: {}", path, e);
104 global_metrics().record_validation_failure();
105 global_metrics().record_load_failure();
106 return Err(e);
107 }
108
109 if let Err(e) = library.call_entry_point() {
111 error!("Entry point execution failed: {}", e);
112 global_metrics().record_load_failure();
113 return Err(e);
114 }
115
116 let patch_info = PatchInfo::from(&library);
118 let library = Arc::new(library);
119
120 let old_active = self.active.load();
122
123 if let Some(ref old_lib) = **old_active {
125 let mut history = self.history.lock().unwrap();
126 history.push(old_lib.clone());
127 debug!("Moved previous patch to history");
128 }
129
130 self.active.store(Arc::new(Some(library)));
132
133 let mut records = self.records.lock().unwrap();
135 records.push(PatchRecord {
136 info: patch_info,
137 action: PatchAction::Loaded,
138 timestamp: SystemTime::now()
139 .duration_since(UNIX_EPOCH)
140 .unwrap()
141 .as_secs(),
142 });
143
144 global_metrics().record_load_success();
145 info!("Patch loaded successfully");
146 Ok(())
147 }
148
149 pub fn rollback(&self) -> Result<()> {
154 info!("Rolling back to previous patch");
155 global_metrics().record_rollback();
156
157 let mut history = self.history.lock().unwrap();
158
159 let previous = history.pop().ok_or(Error::NoPreviousPatch)?;
160
161 let patch_info = PatchInfo::from(previous.as_ref());
162
163 self.active.store(Arc::new(Some(previous)));
165
166 let mut records = self.records.lock().unwrap();
168 records.push(PatchRecord {
169 info: patch_info,
170 action: PatchAction::RolledBack,
171 timestamp: SystemTime::now()
172 .duration_since(UNIX_EPOCH)
173 .unwrap()
174 .as_secs(),
175 });
176
177 info!("Rollback successful");
178 Ok(())
179 }
180
181 pub fn active_patch(&self) -> Option<PatchInfo> {
183 let active = self.active.load();
184 active.as_ref().as_ref().map(|lib| PatchInfo::from(lib.as_ref()))
185 }
186
187 pub fn history(&self) -> Vec<PatchRecord> {
189 self.records.lock().unwrap().clone()
190 }
191
192 pub fn clear(&self) {
194 self.active.store(Arc::new(None));
195 self.history.lock().unwrap().clear();
196 self.records.lock().unwrap().clear();
197 info!("Registry cleared");
198 }
199}
200
201static GLOBAL_REGISTRY: once_cell::sync::Lazy<PatchRegistry> = once_cell::sync::Lazy::new(|| {
203 PatchRegistry::new(Version::new(0, 1, 0), 0)
205});
206
207pub fn global_registry() -> &'static PatchRegistry {
209 &GLOBAL_REGISTRY
210}
211
212#[cfg(test)]
213mod tests {
214 use super::*;
215
216 #[test]
217 fn test_registry_creation() {
218 let registry = PatchRegistry::new(Version::new(1, 0, 0), 0x1234);
219 assert!(registry.active_patch().is_none());
220 assert_eq!(registry.history().len(), 0);
221 }
222
223 #[test]
224 fn test_global_registry() {
225 let registry = global_registry();
226 assert!(registry.active_patch().is_none());
227 }
228}