dynpatch-core 0.1.0

Runtime engine for dynpatch - dynamic library loading, ABI validation, and transactional patching
Documentation
//! ABI and type compatibility validation

use crate::error::{Error, Result};
use crate::loader::Library;
use dynpatch_interface::{PatchMetadata, TypeLayout, Version};
use tracing::{debug, warn};

/// Configuration for validation
#[derive(Debug, Clone)]
pub struct ValidationConfig {
    /// Check version compatibility
    pub check_version: bool,
    /// Check type layouts
    pub check_layouts: bool,
    /// Check interface hash
    pub check_hash: bool,
    /// Strict mode (all checks must pass)
    pub strict: bool,
}

impl Default for ValidationConfig {
    fn default() -> Self {
        Self {
            check_version: true,
            check_layouts: true,
            check_hash: true,
            strict: true,
        }
    }
}

impl ValidationConfig {
    pub fn permissive() -> Self {
        Self {
            check_version: false,
            check_layouts: false,
            check_hash: false,
            strict: false,
        }
    }

    pub fn strict() -> Self {
        Self {
            check_version: true,
            check_layouts: true,
            check_hash: true,
            strict: true,
        }
    }
}

/// Validator for patch compatibility
pub struct Validator {
    config: ValidationConfig,
    expected_interface_version: Version,
    expected_type_hash: u64,
}

impl Validator {
    pub fn new(expected_interface_version: Version, expected_type_hash: u64) -> Self {
        Self {
            config: ValidationConfig::default(),
            expected_interface_version,
            expected_type_hash,
        }
    }

    pub fn with_config(mut self, config: ValidationConfig) -> Self {
        self.config = config;
        self
    }

    /// Validate a loaded library
    pub fn validate(&self, library: &Library) -> Result<()> {
        let metadata = library.metadata();
        debug!("Validating patch: {}", metadata.name);

        let mut errors = Vec::new();

        // Version check
        if self.config.check_version {
            if let Err(e) = self.validate_version(metadata) {
                if self.config.strict {
                    return Err(e);
                }
                warn!("Version validation failed: {}", e);
                errors.push(e);
            }
        }

        // Hash check
        if self.config.check_hash {
            if let Err(e) = self.validate_hash(metadata) {
                if self.config.strict {
                    return Err(e);
                }
                warn!("Hash validation failed: {}", e);
                errors.push(e);
            }
        }

        if !errors.is_empty() && self.config.strict {
            return Err(Error::AbiValidationFailed(format!(
                "Multiple validation errors: {:?}",
                errors
            )));
        }

        debug!("Validation passed for: {}", metadata.name);
        Ok(())
    }

    fn validate_version(&self, metadata: &PatchMetadata) -> Result<()> {
        if !self
            .expected_interface_version
            .is_compatible(&metadata.interface_version)
        {
            return Err(Error::VersionMismatch {
                expected: self.expected_interface_version.to_string(),
                found: metadata.interface_version.to_string(),
            });
        }
        Ok(())
    }

    fn validate_hash(&self, metadata: &PatchMetadata) -> Result<()> {
        if self.expected_type_hash != metadata.type_hash {
            return Err(Error::AbiValidationFailed(format!(
                "Type hash mismatch: expected {:x}, found {:x}",
                self.expected_type_hash, metadata.type_hash
            )));
        }
        Ok(())
    }

    /// Validate type layout compatibility
    pub fn validate_layout(&self, expected: &TypeLayout, found: &TypeLayout) -> Result<()> {
        if !expected.matches(found) {
            return Err(Error::TypeLayoutMismatch {
                type_name: "unknown".to_string(),
                expected_size: expected.size,
                expected_align: expected.alignment,
                found_size: found.size,
                found_align: found.alignment,
            });
        }
        Ok(())
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use dynpatch_interface::Version;

    #[test]
    fn test_validation_config() {
        let config = ValidationConfig::default();
        assert!(config.strict);

        let config = ValidationConfig::permissive();
        assert!(!config.strict);
    }

    #[test]
    fn test_validator_creation() {
        let version = Version::new(1, 0, 0);
        let validator = Validator::new(version, 0x1234);
        assert!(validator.config.strict);
    }

    #[test]
    fn test_type_layout_validation() {
        let validator = Validator::new(Version::new(1, 0, 0), 0);
        
        let layout1 = TypeLayout {
            size: 8,
            alignment: 8,
            type_id: 0x1234,
        };
        let layout2 = TypeLayout {
            size: 8,
            alignment: 8,
            type_id: 0x1234,
        };
        let layout3 = TypeLayout {
            size: 16,
            alignment: 8,
            type_id: 0x1234,
        };

        assert!(validator.validate_layout(&layout1, &layout2).is_ok());
        assert!(validator.validate_layout(&layout1, &layout3).is_err());
    }
}