dynpatch_core/
validator.rs

1//! ABI and type compatibility validation
2
3use crate::error::{Error, Result};
4use crate::loader::Library;
5use dynpatch_interface::{PatchMetadata, TypeLayout, Version};
6use tracing::{debug, warn};
7
8/// Configuration for validation
9#[derive(Debug, Clone)]
10pub struct ValidationConfig {
11    /// Check version compatibility
12    pub check_version: bool,
13    /// Check type layouts
14    pub check_layouts: bool,
15    /// Check interface hash
16    pub check_hash: bool,
17    /// Strict mode (all checks must pass)
18    pub strict: bool,
19}
20
21impl Default for ValidationConfig {
22    fn default() -> Self {
23        Self {
24            check_version: true,
25            check_layouts: true,
26            check_hash: true,
27            strict: true,
28        }
29    }
30}
31
32impl ValidationConfig {
33    pub fn permissive() -> Self {
34        Self {
35            check_version: false,
36            check_layouts: false,
37            check_hash: false,
38            strict: false,
39        }
40    }
41
42    pub fn strict() -> Self {
43        Self {
44            check_version: true,
45            check_layouts: true,
46            check_hash: true,
47            strict: true,
48        }
49    }
50}
51
52/// Validator for patch compatibility
53pub struct Validator {
54    config: ValidationConfig,
55    expected_interface_version: Version,
56    expected_type_hash: u64,
57}
58
59impl Validator {
60    pub fn new(expected_interface_version: Version, expected_type_hash: u64) -> Self {
61        Self {
62            config: ValidationConfig::default(),
63            expected_interface_version,
64            expected_type_hash,
65        }
66    }
67
68    pub fn with_config(mut self, config: ValidationConfig) -> Self {
69        self.config = config;
70        self
71    }
72
73    /// Validate a loaded library
74    pub fn validate(&self, library: &Library) -> Result<()> {
75        let metadata = library.metadata();
76        debug!("Validating patch: {}", metadata.name);
77
78        let mut errors = Vec::new();
79
80        // Version check
81        if self.config.check_version {
82            if let Err(e) = self.validate_version(metadata) {
83                if self.config.strict {
84                    return Err(e);
85                }
86                warn!("Version validation failed: {}", e);
87                errors.push(e);
88            }
89        }
90
91        // Hash check
92        if self.config.check_hash {
93            if let Err(e) = self.validate_hash(metadata) {
94                if self.config.strict {
95                    return Err(e);
96                }
97                warn!("Hash validation failed: {}", e);
98                errors.push(e);
99            }
100        }
101
102        if !errors.is_empty() && self.config.strict {
103            return Err(Error::AbiValidationFailed(format!(
104                "Multiple validation errors: {:?}",
105                errors
106            )));
107        }
108
109        debug!("Validation passed for: {}", metadata.name);
110        Ok(())
111    }
112
113    fn validate_version(&self, metadata: &PatchMetadata) -> Result<()> {
114        if !self
115            .expected_interface_version
116            .is_compatible(&metadata.interface_version)
117        {
118            return Err(Error::VersionMismatch {
119                expected: self.expected_interface_version.to_string(),
120                found: metadata.interface_version.to_string(),
121            });
122        }
123        Ok(())
124    }
125
126    fn validate_hash(&self, metadata: &PatchMetadata) -> Result<()> {
127        if self.expected_type_hash != metadata.type_hash {
128            return Err(Error::AbiValidationFailed(format!(
129                "Type hash mismatch: expected {:x}, found {:x}",
130                self.expected_type_hash, metadata.type_hash
131            )));
132        }
133        Ok(())
134    }
135
136    /// Validate type layout compatibility
137    pub fn validate_layout(&self, expected: &TypeLayout, found: &TypeLayout) -> Result<()> {
138        if !expected.matches(found) {
139            return Err(Error::TypeLayoutMismatch {
140                type_name: "unknown".to_string(),
141                expected_size: expected.size,
142                expected_align: expected.alignment,
143                found_size: found.size,
144                found_align: found.alignment,
145            });
146        }
147        Ok(())
148    }
149}
150
151#[cfg(test)]
152mod tests {
153    use super::*;
154    use dynpatch_interface::Version;
155
156    #[test]
157    fn test_validation_config() {
158        let config = ValidationConfig::default();
159        assert!(config.strict);
160
161        let config = ValidationConfig::permissive();
162        assert!(!config.strict);
163    }
164
165    #[test]
166    fn test_validator_creation() {
167        let version = Version::new(1, 0, 0);
168        let validator = Validator::new(version, 0x1234);
169        assert!(validator.config.strict);
170    }
171
172    #[test]
173    fn test_type_layout_validation() {
174        let validator = Validator::new(Version::new(1, 0, 0), 0);
175        
176        let layout1 = TypeLayout {
177            size: 8,
178            alignment: 8,
179            type_id: 0x1234,
180        };
181        let layout2 = TypeLayout {
182            size: 8,
183            alignment: 8,
184            type_id: 0x1234,
185        };
186        let layout3 = TypeLayout {
187            size: 16,
188            alignment: 8,
189            type_id: 0x1234,
190        };
191
192        assert!(validator.validate_layout(&layout1, &layout2).is_ok());
193        assert!(validator.validate_layout(&layout1, &layout3).is_err());
194    }
195}