#![cfg_attr(all(not(feature = "std"), not(test)), no_std)]
extern crate alloc;
use alloc::string::String;
use core::fmt;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Version {
pub major: u32,
pub minor: u32,
pub patch: u32,
}
impl Version {
pub const fn new(major: u32, minor: u32, patch: u32) -> Self {
Self {
major,
minor,
patch,
}
}
pub fn is_compatible(&self, other: &Version) -> bool {
self.major == other.major
}
}
impl fmt::Display for Version {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}.{}.{}", self.major, self.minor, self.patch)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PatchMetadata {
pub name: String,
pub version: Version,
pub interface_version: Version,
pub type_hash: u64,
pub description: Option<String>,
pub author: Option<String>,
}
impl PatchMetadata {
pub fn new(name: String, version: Version, interface_version: Version, type_hash: u64) -> Self {
Self {
name,
version,
interface_version,
type_hash,
description: None,
author: None,
}
}
pub fn with_description(mut self, description: String) -> Self {
self.description = Some(description);
self
}
pub fn with_author(mut self, author: String) -> Self {
self.author = Some(author);
self
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct TypeLayout {
pub size: usize,
pub alignment: usize,
pub type_id: u64, }
impl TypeLayout {
pub fn of<T: 'static>() -> Self {
Self {
size: core::mem::size_of::<T>(),
alignment: core::mem::align_of::<T>(),
type_id: type_id_as_u64::<T>(),
}
}
pub fn matches(&self, other: &TypeLayout) -> bool {
self.size == other.size && self.alignment == other.alignment && self.type_id == other.type_id
}
}
fn type_id_as_u64<T: 'static>() -> u64 {
compute_type_hash(
core::any::type_name::<T>(),
core::mem::size_of::<T>(),
core::mem::align_of::<T>(),
)
}
pub trait PatchInterface: Send + Sync {
fn metadata(&self) -> &PatchMetadata;
fn type_layout(&self) -> TypeLayout;
}
pub trait PatchEntry: Send + Sync {
fn init(&mut self) -> Result<(), PatchError>;
fn teardown(&mut self);
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum PatchError {
IncompatibleAbi {
expected: String,
found: String,
},
IncompatibleVersion {
expected: String,
found: String,
},
SymbolNotFound {
symbol: String,
},
InitializationFailed {
message: String,
},
InvalidFormat {
message: String,
},
Other {
message: String,
},
}
impl fmt::Display for PatchError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
PatchError::IncompatibleAbi { expected, found } => {
write!(f, "ABI mismatch: expected {}, found {}", expected, found)
}
PatchError::IncompatibleVersion { expected, found } => {
write!(f, "Version mismatch: expected {}, found {}", expected, found)
}
PatchError::SymbolNotFound { symbol } => {
write!(f, "Symbol not found: {}", symbol)
}
PatchError::InitializationFailed { message } => {
write!(f, "Initialization failed: {}", message)
}
PatchError::InvalidFormat { message } => {
write!(f, "Invalid format: {}", message)
}
PatchError::Other { message } => {
write!(f, "Error: {}", message)
}
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for PatchError {}
pub fn compute_type_hash(type_name: &str, size: usize, align: usize) -> u64 {
let mut hash: u64 = 0xcbf29ce484222325;
for byte in type_name.as_bytes() {
hash ^= *byte as u64;
hash = hash.wrapping_mul(0x100000001b3);
}
hash ^= size as u64;
hash = hash.wrapping_mul(0x100000001b3);
hash ^= align as u64;
hash = hash.wrapping_mul(0x100000001b3);
hash
}
pub trait StateMigration<Old, New> {
fn migrate(old: Old) -> Result<New, PatchError>;
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_version_compatibility() {
let v1 = Version::new(1, 0, 0);
let v2 = Version::new(1, 1, 0);
let v3 = Version::new(2, 0, 0);
assert!(v1.is_compatible(&v2));
assert!(!v1.is_compatible(&v3));
}
#[test]
fn test_type_layout() {
let layout1 = TypeLayout::of::<u32>();
let layout2 = TypeLayout::of::<u32>();
let layout3 = TypeLayout::of::<u64>();
assert!(layout1.matches(&layout2));
assert!(!layout1.matches(&layout3));
}
#[test]
fn test_compute_type_hash() {
let hash1 = compute_type_hash("MyType", 8, 8);
let hash2 = compute_type_hash("MyType", 8, 8);
let hash3 = compute_type_hash("MyType", 16, 8);
assert_eq!(hash1, hash2);
assert_ne!(hash1, hash3);
}
}