dynpatch-interface 0.1.0

Shared interface types and traits for dynpatch hot-patching system
Documentation
//! # dynpatch-interface
//!
//! Shared interface types and traits for the dynpatch hot-patching system.
//! This crate must be used by both the host binary and patch dynamic libraries
//! to ensure type and ABI consistency.
//!
//! ## Core Concepts
//!
//! - **PatchMetadata**: Version and compatibility information
//! - **PatchInterface**: Base trait for all patchable interfaces
//! - **PatchEntry**: Optional initialization/teardown hooks
//! - **TypeHash**: Deterministic type layout hashing

#![cfg_attr(all(not(feature = "std"), not(test)), no_std)]

extern crate alloc;

use alloc::string::String;
use core::fmt;
use serde::{Deserialize, Serialize};

/// Version information for patches
#[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,
        }
    }

    /// Check if this version is compatible with another (same major version)
    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)
    }
}

/// Metadata about a patch
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PatchMetadata {
    /// Patch name/identifier
    pub name: String,
    /// Patch version
    pub version: Version,
    /// Interface version this patch implements
    pub interface_version: Version,
    /// Type hash for ABI compatibility checking
    pub type_hash: u64,
    /// Optional description
    pub description: Option<String>,
    /// Author information
    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
    }
}

/// Information about type layout for ABI validation
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct TypeLayout {
    pub size: usize,
    pub alignment: usize,
    pub type_id: u64, // We store TypeId as u64 for serialization
}

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
    }
}

/// Convert TypeId to u64 for serialization
fn type_id_as_u64<T: 'static>() -> u64 {
    // We use the type name hash since TypeId itself isn't directly convertible
    // This provides a reasonable approximation for same-build compatibility
    compute_type_hash(
        core::any::type_name::<T>(),
        core::mem::size_of::<T>(),
        core::mem::align_of::<T>(),
    )
}

/// Trait for patchable interfaces
pub trait PatchInterface: Send + Sync {
    /// Get metadata about this patch
    fn metadata(&self) -> &PatchMetadata;

    /// Get the type layout for validation
    fn type_layout(&self) -> TypeLayout;
}

/// Optional entry point for patches with initialization/teardown
pub trait PatchEntry: Send + Sync {
    /// Called when the patch is loaded and validated but before activation
    fn init(&mut self) -> Result<(), PatchError>;

    /// Called when the patch is being unloaded or rolled back
    fn teardown(&mut self);
}

/// Errors that can occur during patch operations
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum PatchError {
    /// ABI/type mismatch detected
    IncompatibleAbi {
        expected: String,
        found: String,
    },
    /// Version incompatibility
    IncompatibleVersion {
        expected: String,
        found: String,
    },
    /// Symbol not found
    SymbolNotFound {
        symbol: String,
    },
    /// Initialization failed
    InitializationFailed {
        message: String,
    },
    /// Invalid patch format
    InvalidFormat {
        message: String,
    },
    /// Other error
    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 {}

/// Compute a deterministic hash of a type's name for compatibility checking
pub fn compute_type_hash(type_name: &str, size: usize, align: usize) -> u64 {
    // Simple FNV-1a hash
    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
}

/// State migration trait for carrying state across patch versions
pub trait StateMigration<Old, New> {
    /// Migrate from old state to new state
    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);
    }
}