dynpatch_interface/
lib.rs

1//! # dynpatch-interface
2//!
3//! Shared interface types and traits for the dynpatch hot-patching system.
4//! This crate must be used by both the host binary and patch dynamic libraries
5//! to ensure type and ABI consistency.
6//!
7//! ## Core Concepts
8//!
9//! - **PatchMetadata**: Version and compatibility information
10//! - **PatchInterface**: Base trait for all patchable interfaces
11//! - **PatchEntry**: Optional initialization/teardown hooks
12//! - **TypeHash**: Deterministic type layout hashing
13
14#![cfg_attr(all(not(feature = "std"), not(test)), no_std)]
15
16extern crate alloc;
17
18use alloc::string::String;
19use core::fmt;
20use serde::{Deserialize, Serialize};
21
22/// Version information for patches
23#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
24pub struct Version {
25    pub major: u32,
26    pub minor: u32,
27    pub patch: u32,
28}
29
30impl Version {
31    pub const fn new(major: u32, minor: u32, patch: u32) -> Self {
32        Self {
33            major,
34            minor,
35            patch,
36        }
37    }
38
39    /// Check if this version is compatible with another (same major version)
40    pub fn is_compatible(&self, other: &Version) -> bool {
41        self.major == other.major
42    }
43}
44
45impl fmt::Display for Version {
46    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
47        write!(f, "{}.{}.{}", self.major, self.minor, self.patch)
48    }
49}
50
51/// Metadata about a patch
52#[derive(Debug, Clone, Serialize, Deserialize)]
53pub struct PatchMetadata {
54    /// Patch name/identifier
55    pub name: String,
56    /// Patch version
57    pub version: Version,
58    /// Interface version this patch implements
59    pub interface_version: Version,
60    /// Type hash for ABI compatibility checking
61    pub type_hash: u64,
62    /// Optional description
63    pub description: Option<String>,
64    /// Author information
65    pub author: Option<String>,
66}
67
68impl PatchMetadata {
69    pub fn new(name: String, version: Version, interface_version: Version, type_hash: u64) -> Self {
70        Self {
71            name,
72            version,
73            interface_version,
74            type_hash,
75            description: None,
76            author: None,
77        }
78    }
79
80    pub fn with_description(mut self, description: String) -> Self {
81        self.description = Some(description);
82        self
83    }
84
85    pub fn with_author(mut self, author: String) -> Self {
86        self.author = Some(author);
87        self
88    }
89}
90
91/// Information about type layout for ABI validation
92#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
93pub struct TypeLayout {
94    pub size: usize,
95    pub alignment: usize,
96    pub type_id: u64, // We store TypeId as u64 for serialization
97}
98
99impl TypeLayout {
100    pub fn of<T: 'static>() -> Self {
101        Self {
102            size: core::mem::size_of::<T>(),
103            alignment: core::mem::align_of::<T>(),
104            type_id: type_id_as_u64::<T>(),
105        }
106    }
107
108    pub fn matches(&self, other: &TypeLayout) -> bool {
109        self.size == other.size && self.alignment == other.alignment && self.type_id == other.type_id
110    }
111}
112
113/// Convert TypeId to u64 for serialization
114fn type_id_as_u64<T: 'static>() -> u64 {
115    // We use the type name hash since TypeId itself isn't directly convertible
116    // This provides a reasonable approximation for same-build compatibility
117    compute_type_hash(
118        core::any::type_name::<T>(),
119        core::mem::size_of::<T>(),
120        core::mem::align_of::<T>(),
121    )
122}
123
124/// Trait for patchable interfaces
125pub trait PatchInterface: Send + Sync {
126    /// Get metadata about this patch
127    fn metadata(&self) -> &PatchMetadata;
128
129    /// Get the type layout for validation
130    fn type_layout(&self) -> TypeLayout;
131}
132
133/// Optional entry point for patches with initialization/teardown
134pub trait PatchEntry: Send + Sync {
135    /// Called when the patch is loaded and validated but before activation
136    fn init(&mut self) -> Result<(), PatchError>;
137
138    /// Called when the patch is being unloaded or rolled back
139    fn teardown(&mut self);
140}
141
142/// Errors that can occur during patch operations
143#[derive(Debug, Clone, Serialize, Deserialize)]
144pub enum PatchError {
145    /// ABI/type mismatch detected
146    IncompatibleAbi {
147        expected: String,
148        found: String,
149    },
150    /// Version incompatibility
151    IncompatibleVersion {
152        expected: String,
153        found: String,
154    },
155    /// Symbol not found
156    SymbolNotFound {
157        symbol: String,
158    },
159    /// Initialization failed
160    InitializationFailed {
161        message: String,
162    },
163    /// Invalid patch format
164    InvalidFormat {
165        message: String,
166    },
167    /// Other error
168    Other {
169        message: String,
170    },
171}
172
173impl fmt::Display for PatchError {
174    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
175        match self {
176            PatchError::IncompatibleAbi { expected, found } => {
177                write!(f, "ABI mismatch: expected {}, found {}", expected, found)
178            }
179            PatchError::IncompatibleVersion { expected, found } => {
180                write!(f, "Version mismatch: expected {}, found {}", expected, found)
181            }
182            PatchError::SymbolNotFound { symbol } => {
183                write!(f, "Symbol not found: {}", symbol)
184            }
185            PatchError::InitializationFailed { message } => {
186                write!(f, "Initialization failed: {}", message)
187            }
188            PatchError::InvalidFormat { message } => {
189                write!(f, "Invalid format: {}", message)
190            }
191            PatchError::Other { message } => {
192                write!(f, "Error: {}", message)
193            }
194        }
195    }
196}
197
198#[cfg(feature = "std")]
199impl std::error::Error for PatchError {}
200
201/// Compute a deterministic hash of a type's name for compatibility checking
202pub fn compute_type_hash(type_name: &str, size: usize, align: usize) -> u64 {
203    // Simple FNV-1a hash
204    let mut hash: u64 = 0xcbf29ce484222325;
205    
206    for byte in type_name.as_bytes() {
207        hash ^= *byte as u64;
208        hash = hash.wrapping_mul(0x100000001b3);
209    }
210    
211    hash ^= size as u64;
212    hash = hash.wrapping_mul(0x100000001b3);
213    hash ^= align as u64;
214    hash = hash.wrapping_mul(0x100000001b3);
215    
216    hash
217}
218
219/// State migration trait for carrying state across patch versions
220pub trait StateMigration<Old, New> {
221    /// Migrate from old state to new state
222    fn migrate(old: Old) -> Result<New, PatchError>;
223}
224
225#[cfg(test)]
226mod tests {
227    use super::*;
228
229    #[test]
230    fn test_version_compatibility() {
231        let v1 = Version::new(1, 0, 0);
232        let v2 = Version::new(1, 1, 0);
233        let v3 = Version::new(2, 0, 0);
234
235        assert!(v1.is_compatible(&v2));
236        assert!(!v1.is_compatible(&v3));
237    }
238
239    #[test]
240    fn test_type_layout() {
241        let layout1 = TypeLayout::of::<u32>();
242        let layout2 = TypeLayout::of::<u32>();
243        let layout3 = TypeLayout::of::<u64>();
244
245        assert!(layout1.matches(&layout2));
246        assert!(!layout1.matches(&layout3));
247    }
248
249    #[test]
250    fn test_compute_type_hash() {
251        let hash1 = compute_type_hash("MyType", 8, 8);
252        let hash2 = compute_type_hash("MyType", 8, 8);
253        let hash3 = compute_type_hash("MyType", 16, 8);
254
255        assert_eq!(hash1, hash2);
256        assert_ne!(hash1, hash3);
257    }
258}