use crate::error::{Error, Result};
use crate::loader::Library;
use dynpatch_interface::{PatchMetadata, TypeLayout, Version};
use tracing::{debug, warn};
#[derive(Debug, Clone)]
pub struct ValidationConfig {
pub check_version: bool,
pub check_layouts: bool,
pub check_hash: bool,
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,
}
}
}
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
}
pub fn validate(&self, library: &Library) -> Result<()> {
let metadata = library.metadata();
debug!("Validating patch: {}", metadata.name);
let mut errors = Vec::new();
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);
}
}
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(())
}
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());
}
}