Skip to main content

caxton/domain/
wasm_module.rs

1//! WASM Module domain types
2//!
3//! This module defines types for WASM module validation, metadata extraction,
4//! security policies, and module lifecycle management for agent deployment.
5
6use nutype::nutype;
7use serde::{Deserialize, Serialize};
8use std::collections::{HashMap, HashSet};
9use std::time::SystemTime;
10use thiserror::Error;
11
12use super::agent_lifecycle::{AgentVersion, VersionNumber};
13use crate::domain_types::AgentName;
14
15/// Hash of WASM module content for integrity verification
16#[nutype(
17    validate(len_char_min = 64, len_char_max = 128), // SHA-256 or SHA-512
18    derive(
19        Debug,
20        Clone,
21        PartialEq,
22        Eq,
23        Hash,
24        Serialize,
25        Deserialize,
26        Display,
27        TryFrom,
28        Into
29    )
30)]
31pub struct ModuleHash(String);
32
33impl ModuleHash {
34    /// Creates hash from hex string
35    ///
36    /// # Errors
37    ///
38    /// Returns `ModuleHashError` if the hex string is invalid or wrong length.
39    pub fn from_hex(hex: &str) -> Result<Self, ModuleHashError> {
40        if hex.len() != 64 && hex.len() != 128 {
41            return Err(Self::try_new("invalid_length".to_string()).unwrap_err());
42        }
43
44        if !hex.chars().all(|c| c.is_ascii_hexdigit()) {
45            return Err(Self::try_new("invalid_chars".to_string()).unwrap_err());
46        }
47
48        Self::try_new(hex.to_string())
49    }
50
51    /// Creates SHA-256 hash from bytes
52    ///
53    /// # Panics
54    ///
55    /// Panics if the generated hash string is invalid (should never happen).
56    pub fn sha256(data: &[u8]) -> Self {
57        use std::collections::hash_map::DefaultHasher;
58        use std::hash::{Hash, Hasher};
59
60        let mut hasher = DefaultHasher::new();
61        data.hash(&mut hasher);
62        let hash = hasher.finish();
63
64        // Convert to 64-character hex string (simulated SHA-256)
65        Self::try_new(format!("{hash:016x}{hash:016x}{hash:016x}{hash:016x}")).unwrap()
66    }
67
68    /// Get hash algorithm type based on length
69    pub fn algorithm(&self) -> HashAlgorithm {
70        match self.clone().into_inner().len() {
71            64 => HashAlgorithm::Sha256,
72            128 => HashAlgorithm::Sha512,
73            _ => HashAlgorithm::Unknown,
74        }
75    }
76}
77
78/// Supported hash algorithms
79#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
80pub enum HashAlgorithm {
81    Sha256,
82    Sha512,
83    Unknown,
84}
85
86/// WASM module size validation
87#[nutype(
88    validate(greater_or_equal = 1, less_or_equal = 104_857_600), // 1 byte to 100MB
89    derive(
90        Debug,
91        Clone,
92        Copy,
93        PartialEq,
94        Eq,
95        PartialOrd,
96        Ord,
97        Serialize,
98        Deserialize,
99        Display,
100        TryFrom,
101        Into
102    )
103)]
104pub struct ModuleSize(usize);
105
106impl ModuleSize {
107    /// Creates module size from megabytes
108    /// Create module size from megabytes
109    ///
110    /// # Errors
111    ///
112    /// Returns `ModuleSizeError` if the size is outside valid limits.
113    pub fn from_mb(mb: usize) -> Result<Self, ModuleSizeError> {
114        Self::try_new(mb * 1024 * 1024)
115    }
116
117    /// Creates module size from kilobytes
118    /// Create module size from kilobytes
119    ///
120    /// # Errors
121    ///
122    /// Returns `ModuleSizeError` if the size is outside valid limits.
123    pub fn from_kb(kb: usize) -> Result<Self, ModuleSizeError> {
124        Self::try_new(kb * 1024)
125    }
126
127    /// Gets size in bytes
128    pub fn as_bytes(&self) -> usize {
129        self.into_inner()
130    }
131
132    /// Gets size in kilobytes (rounded up)
133    pub fn as_kb(&self) -> usize {
134        self.into_inner().div_ceil(1024)
135    }
136
137    /// Gets size in megabytes (rounded up)
138    pub fn as_mb(&self) -> usize {
139        self.into_inner().div_ceil(1_048_576)
140    }
141}
142
143/// WASM function export name
144#[nutype(
145    validate(len_char_min = 1, len_char_max = 100),
146    derive(
147        Debug,
148        Clone,
149        PartialEq,
150        Eq,
151        Hash,
152        Serialize,
153        Deserialize,
154        Display,
155        TryFrom,
156        Into
157    )
158)]
159pub struct WasmExportName(String);
160
161/// WASM function import name
162#[nutype(
163    validate(len_char_min = 1, len_char_max = 100),
164    derive(
165        Debug,
166        Clone,
167        PartialEq,
168        Eq,
169        Hash,
170        Serialize,
171        Deserialize,
172        Display,
173        TryFrom,
174        Into
175    )
176)]
177pub struct WasmImportName(String);
178
179/// WASM module name identifier
180#[nutype(
181    validate(len_char_min = 1, len_char_max = 100),
182    derive(
183        Debug,
184        Clone,
185        PartialEq,
186        Eq,
187        Hash,
188        Serialize,
189        Deserialize,
190        Display,
191        TryFrom,
192        Into
193    )
194)]
195pub struct WasmModuleName(String);
196
197/// Maximum number of WASM functions
198#[nutype(
199    validate(greater_or_equal = 1, less_or_equal = 10_000),
200    derive(
201        Debug,
202        Clone,
203        Copy,
204        PartialEq,
205        Eq,
206        PartialOrd,
207        Ord,
208        Serialize,
209        Deserialize,
210        Display,
211        Default,
212        TryFrom,
213        Into
214    ),
215    default = 100
216)]
217pub struct MaxWasmFunctions(u16);
218
219impl MaxWasmFunctions {
220    /// Gets the value as u16
221    pub fn as_u16(&self) -> u16 {
222        self.into_inner()
223    }
224}
225
226/// WASM function signature for validation
227#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Hash)]
228pub struct WasmFunctionSignature {
229    pub name: String, // Use String to be flexible for both imports and exports
230    pub parameters: Vec<WasmValueType>,
231    pub results: Vec<WasmValueType>,
232    pub is_host_import: bool,
233}
234
235impl WasmFunctionSignature {
236    /// Creates new function signature
237    pub fn new(
238        name: String,
239        parameters: Vec<WasmValueType>,
240        results: Vec<WasmValueType>,
241        is_host_import: bool,
242    ) -> Self {
243        Self {
244            name,
245            parameters,
246            results,
247            is_host_import,
248        }
249    }
250
251    /// Check if function has parameters
252    pub fn has_parameters(&self) -> bool {
253        !self.parameters.is_empty()
254    }
255
256    /// Check if function returns values
257    pub fn has_results(&self) -> bool {
258        !self.results.is_empty()
259    }
260
261    /// Get function arity (parameter count)
262    pub fn arity(&self) -> usize {
263        self.parameters.len()
264    }
265}
266
267/// WASM value types for function signatures
268#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Hash)]
269pub enum WasmValueType {
270    I32,
271    I64,
272    F32,
273    F64,
274    V128,
275    FuncRef,
276    ExternRef,
277}
278
279impl WasmValueType {
280    /// Check if type is numeric
281    pub fn is_numeric(&self) -> bool {
282        matches!(self, Self::I32 | Self::I64 | Self::F32 | Self::F64)
283    }
284
285    /// Check if type is reference
286    pub fn is_reference(&self) -> bool {
287        matches!(self, Self::FuncRef | Self::ExternRef)
288    }
289
290    /// Get type size in bytes (approximation)
291    pub fn size_bytes(&self) -> usize {
292        match self {
293            Self::I32 | Self::F32 => 4,
294            Self::I64 | Self::F64 | Self::FuncRef | Self::ExternRef => 8,
295            Self::V128 => 16,
296            // Pointer size
297        }
298    }
299}
300
301/// WASM module validation result
302#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
303pub enum ValidationResult {
304    Valid,
305    Invalid { reasons: Vec<ValidationFailure> },
306    Warning { warnings: Vec<ValidationWarning> },
307}
308
309impl ValidationResult {
310    /// Check if validation passed
311    pub fn is_valid(&self) -> bool {
312        matches!(self, Self::Valid | Self::Warning { .. })
313    }
314
315    /// Check if there are warnings
316    pub fn has_warnings(&self) -> bool {
317        matches!(self, Self::Warning { .. })
318    }
319
320    /// Get all error messages
321    pub fn error_messages(&self) -> Vec<String> {
322        match self {
323            Self::Invalid { reasons } => reasons
324                .iter()
325                .map(std::string::ToString::to_string)
326                .collect(),
327            _ => vec![],
328        }
329    }
330
331    /// Get all warning messages
332    pub fn warning_messages(&self) -> Vec<String> {
333        match self {
334            Self::Warning { warnings } => warnings
335                .iter()
336                .map(std::string::ToString::to_string)
337                .collect(),
338            _ => vec![],
339        }
340    }
341}
342
343/// Specific validation failure reasons
344#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
345pub enum ValidationFailure {
346    InvalidWasmFormat,
347    UnsupportedWasmVersion,
348    ModuleTooLarge {
349        size: usize,
350        limit: usize,
351    },
352    TooManyFunctions {
353        count: usize,
354        limit: usize,
355    },
356    TooManyImports {
357        count: usize,
358        limit: usize,
359    },
360    TooManyExports {
361        count: usize,
362        limit: usize,
363    },
364    UnauthorizedImport {
365        function_name: String,
366    },
367    MissingRequiredExport {
368        function_name: String,
369    },
370    InvalidFunctionSignature {
371        function_name: String,
372        reason: String,
373    },
374    SecurityViolation {
375        policy: String,
376        violation: String,
377    },
378    ResourceLimitExceeded {
379        resource: String,
380        limit: String,
381    },
382    DependencyNotFound {
383        dependency: String,
384    },
385}
386
387impl std::fmt::Display for ValidationFailure {
388    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
389        match self {
390            Self::InvalidWasmFormat => write!(f, "Invalid WASM format"),
391            Self::UnsupportedWasmVersion => write!(f, "Unsupported WASM version"),
392            Self::ModuleTooLarge { size, limit } => {
393                write!(f, "Module too large: {size} bytes, limit {limit} bytes")
394            }
395            Self::TooManyFunctions { count, limit } => {
396                write!(f, "Too many functions: {count}, limit {limit}")
397            }
398            Self::TooManyImports { count, limit } => {
399                write!(f, "Too many imports: {count}, limit {limit}")
400            }
401            Self::TooManyExports { count, limit } => {
402                write!(f, "Too many exports: {count}, limit {limit}")
403            }
404            Self::UnauthorizedImport { function_name } => {
405                write!(f, "Unauthorized import: {function_name}")
406            }
407            Self::MissingRequiredExport { function_name } => {
408                write!(f, "Missing required export: {function_name}")
409            }
410            Self::InvalidFunctionSignature {
411                function_name,
412                reason,
413            } => {
414                write!(
415                    f,
416                    "Invalid function signature for {function_name}: {reason}"
417                )
418            }
419            Self::SecurityViolation { policy, violation } => {
420                write!(f, "Security policy '{policy}' violation: {violation}")
421            }
422            Self::ResourceLimitExceeded { resource, limit } => {
423                write!(f, "Resource limit exceeded for {resource}: {limit}")
424            }
425            Self::DependencyNotFound { dependency } => {
426                write!(f, "Dependency not found: {dependency}")
427            }
428        }
429    }
430}
431
432/// Validation warnings for non-critical issues
433#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
434pub enum ValidationWarning {
435    UnusedFunction { function_name: String },
436    UnusedImport { import_name: String },
437    LargeFunctionCount { count: usize },
438    DeprecatedFeature { feature: String },
439    PerformanceWarning { warning: String },
440    CompatibilityIssue { issue: String },
441}
442
443impl std::fmt::Display for ValidationWarning {
444    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
445        match self {
446            Self::UnusedFunction { function_name } => {
447                write!(f, "Unused function: {function_name}")
448            }
449            Self::UnusedImport { import_name } => {
450                write!(f, "Unused import: {import_name}")
451            }
452            Self::LargeFunctionCount { count } => {
453                write!(f, "Large function count: {count}")
454            }
455            Self::DeprecatedFeature { feature } => {
456                write!(f, "Deprecated feature: {feature}")
457            }
458            Self::PerformanceWarning { warning } => {
459                write!(f, "Performance warning: {warning}")
460            }
461            Self::CompatibilityIssue { issue } => {
462                write!(f, "Compatibility issue: {issue}")
463            }
464        }
465    }
466}
467
468/// WASM module security policy
469#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
470pub struct WasmSecurityPolicy {
471    pub name: String,
472    pub version: String,
473    pub allowed_imports: HashSet<WasmImportName>,
474    pub required_exports: HashSet<WasmExportName>,
475    pub forbidden_instructions: HashSet<String>,
476    pub max_memory_pages: u32,
477    pub max_table_elements: u32,
478    pub allow_bulk_memory: bool,
479    pub allow_simd: bool,
480    pub allow_threads: bool,
481    pub custom_validations: Vec<CustomValidationRule>,
482}
483
484impl WasmSecurityPolicy {
485    /// Creates a strict security policy
486    ///
487    /// # Panics
488    ///
489    /// Panics if hardcoded export names are invalid (should never happen).
490    pub fn strict() -> Self {
491        Self {
492            name: "strict".to_string(),
493            version: "1.0".to_string(),
494            allowed_imports: HashSet::new(),
495            required_exports: {
496                let mut exports = HashSet::new();
497                exports.insert(WasmExportName::try_new("_start".to_string()).unwrap());
498                exports
499            },
500            forbidden_instructions: {
501                let mut forbidden = HashSet::new();
502                forbidden.insert("unreachable".to_string());
503                forbidden.insert("memory.grow".to_string());
504                forbidden
505            },
506            max_memory_pages: 16, // 1MB (64KB per page)
507            max_table_elements: 100,
508            allow_bulk_memory: false,
509            allow_simd: false,
510            allow_threads: false,
511            custom_validations: vec![],
512        }
513    }
514
515    /// Creates a permissive security policy
516    pub fn permissive() -> Self {
517        Self {
518            name: "permissive".to_string(),
519            version: "1.0".to_string(),
520            allowed_imports: HashSet::new(), // Allow all
521            required_exports: HashSet::new(),
522            forbidden_instructions: HashSet::new(),
523            max_memory_pages: 1024, // 64MB
524            max_table_elements: 10000,
525            allow_bulk_memory: true,
526            allow_simd: true,
527            allow_threads: false, // Still restrict threads
528            custom_validations: vec![],
529        }
530    }
531
532    /// Creates policy for testing environments
533    pub fn testing() -> Self {
534        let mut policy = Self::permissive();
535        policy.name = "testing".to_string();
536        policy.max_memory_pages = 32; // 2MB
537        policy.max_table_elements = 1000;
538        policy
539    }
540
541    /// Check if import is allowed
542    pub fn is_import_allowed(&self, import_name: &str) -> bool {
543        if self.allowed_imports.is_empty() {
544            return true; // Allow all if no restrictions
545        }
546        // Convert string to WasmImportName for checking
547        if let Ok(import) = WasmImportName::try_new(import_name.to_string()) {
548            self.allowed_imports.contains(&import)
549        } else {
550            false
551        }
552    }
553
554    /// Check if export is required
555    pub fn is_export_required(&self, export_name: &str) -> bool {
556        if let Ok(export) = WasmExportName::try_new(export_name.to_string()) {
557            self.required_exports.contains(&export)
558        } else {
559            false
560        }
561    }
562
563    /// Validate module against this policy
564    pub fn validate_module(&self, module: &WasmModule) -> ValidationResult {
565        let mut failures = vec![];
566        let mut warnings = vec![];
567
568        // Check size limits
569        if module.size.as_bytes() > 50 * 1024 * 1024 {
570            // 50MB
571            failures.push(ValidationFailure::ModuleTooLarge {
572                size: module.size.as_bytes(),
573                limit: 50 * 1024 * 1024,
574            });
575        }
576
577        // Check function count
578        if module.functions.len() > 1000 {
579            failures.push(ValidationFailure::TooManyFunctions {
580                count: module.functions.len(),
581                limit: 1000,
582            });
583        } else if module.functions.len() > 500 {
584            warnings.push(ValidationWarning::LargeFunctionCount {
585                count: module.functions.len(),
586            });
587        }
588
589        // Check imports
590        for import in &module.imports {
591            if !self.is_import_allowed(&import.name) {
592                failures.push(ValidationFailure::UnauthorizedImport {
593                    function_name: import.name.clone(),
594                });
595            }
596        }
597
598        // Check required exports
599        let export_names: HashSet<_> = module.exports.iter().map(|e| &e.name).collect();
600
601        for required_export in &self.required_exports {
602            let export_name = required_export.clone().into_inner();
603            if !export_names.contains(&&export_name) {
604                failures.push(ValidationFailure::MissingRequiredExport {
605                    function_name: export_name,
606                });
607            }
608        }
609
610        // Return validation result
611        if !failures.is_empty() {
612            ValidationResult::Invalid { reasons: failures }
613        } else if !warnings.is_empty() {
614            ValidationResult::Warning { warnings }
615        } else {
616            ValidationResult::Valid
617        }
618    }
619}
620
621impl Default for WasmSecurityPolicy {
622    fn default() -> Self {
623        Self::strict()
624    }
625}
626
627/// Custom validation rule for extensible policies
628#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
629pub struct CustomValidationRule {
630    pub name: String,
631    pub description: String,
632    pub rule_type: ValidationRuleType,
633    pub parameters: HashMap<String, String>,
634}
635
636/// Types of custom validation rules
637#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
638pub enum ValidationRuleType {
639    FunctionNamePattern,
640    ImportWhitelist,
641    ExportBlacklist,
642    InstructionCount,
643    CallDepth,
644    Custom(String),
645}
646
647/// WASM module metadata
648#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
649pub struct WasmModule {
650    pub version: AgentVersion,
651    pub version_number: VersionNumber,
652    pub name: Option<WasmModuleName>,
653    pub agent_name: Option<AgentName>,
654    pub hash: ModuleHash,
655    pub size: ModuleSize,
656    pub functions: Vec<WasmFunctionSignature>,
657    pub imports: Vec<WasmFunctionSignature>,
658    pub exports: Vec<WasmFunctionSignature>,
659    pub memory_pages: u32,
660    pub table_elements: u32,
661    pub features_used: HashSet<WasmFeature>,
662    pub security_policy: WasmSecurityPolicy,
663    pub validation_result: ValidationResult,
664    pub created_at: SystemTime,
665    pub metadata: HashMap<String, String>,
666}
667
668impl WasmModule {
669    /// Creates a new WASM module from bytes
670    /// Create WASM module from bytes with validation
671    ///
672    /// # Errors
673    ///
674    /// Returns `WasmValidationError` if the WASM bytes are invalid or fail validation.
675    pub fn from_bytes(
676        version: AgentVersion,
677        version_number: VersionNumber,
678        name: Option<WasmModuleName>,
679        agent_name: Option<AgentName>,
680        wasm_bytes: &[u8],
681        security_policy: &WasmSecurityPolicy,
682    ) -> Result<Self, WasmValidationError> {
683        if wasm_bytes.is_empty() {
684            return Err(WasmValidationError::EmptyModule);
685        }
686
687        let hash = ModuleHash::sha256(wasm_bytes);
688        let size = ModuleSize::try_new(wasm_bytes.len()).map_err(|_| {
689            WasmValidationError::ModuleTooLarge {
690                size: wasm_bytes.len(),
691                limit: 104_857_600,
692            }
693        })?;
694
695        // Simulate parsing (in real implementation, would use wasmparser)
696        let functions = Self::extract_functions(wasm_bytes);
697        let imports = Self::extract_imports(wasm_bytes);
698        let exports = Self::extract_exports(wasm_bytes);
699        let features_used = Self::extract_features(wasm_bytes);
700
701        let mut module = Self {
702            version,
703            version_number,
704            name,
705            agent_name,
706            hash,
707            size,
708            functions,
709            imports,
710            exports,
711            memory_pages: 16, // Default
712            table_elements: 0,
713            features_used,
714            security_policy: security_policy.clone(),
715            validation_result: ValidationResult::Valid,
716            created_at: SystemTime::now(),
717            metadata: HashMap::new(),
718        };
719
720        // Validate against security policy
721        module.validation_result = security_policy.validate_module(&module);
722
723        // Update security policy (clone for ownership)
724        module.security_policy = security_policy.clone();
725
726        Ok(module)
727    }
728
729    /// Validate module integrity
730    pub fn validate(&self) -> ValidationResult {
731        self.security_policy.validate_module(self)
732    }
733
734    /// Check if module is valid
735    pub fn is_valid(&self) -> bool {
736        self.validation_result.is_valid()
737    }
738
739    /// Get total function count
740    pub fn total_function_count(&self) -> usize {
741        self.functions.len() + self.imports.len()
742    }
743
744    /// Get memory usage estimate in bytes
745    pub fn estimated_memory_usage(&self) -> usize {
746        (self.memory_pages as usize) * 65536 // 64KB per page
747    }
748
749    /// Check if module uses specific WASM feature
750    pub fn uses_feature(&self, feature: WasmFeature) -> bool {
751        self.features_used.contains(&feature)
752    }
753
754    /// Add custom metadata
755    pub fn add_metadata(&mut self, key: String, value: String) {
756        self.metadata.insert(key, value);
757    }
758
759    // Helper methods for parsing (simulated)
760    fn extract_functions(_wasm_bytes: &[u8]) -> Vec<WasmFunctionSignature> {
761        // In real implementation, would use wasmparser to extract function signatures
762        vec![]
763    }
764
765    fn extract_imports(_wasm_bytes: &[u8]) -> Vec<WasmFunctionSignature> {
766        vec![]
767    }
768
769    fn extract_exports(_wasm_bytes: &[u8]) -> Vec<WasmFunctionSignature> {
770        vec![]
771    }
772
773    fn extract_features(_wasm_bytes: &[u8]) -> HashSet<WasmFeature> {
774        HashSet::new()
775    }
776}
777
778/// WASM features that can be used in modules
779#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
780pub enum WasmFeature {
781    BulkMemory,
782    Simd,
783    Threads,
784    TailCall,
785    ReferenceTypes,
786    MultiValue,
787    SignExtension,
788    ExceptionHandling,
789    GarbageCollection,
790    RelaxedSimd,
791}
792
793impl WasmFeature {
794    /// Check if feature is considered stable
795    pub fn is_stable(&self) -> bool {
796        matches!(
797            self,
798            Self::BulkMemory
799                | Self::Simd
800                | Self::ReferenceTypes
801                | Self::MultiValue
802                | Self::SignExtension
803        )
804    }
805
806    /// Check if feature is experimental
807    pub fn is_experimental(&self) -> bool {
808        !self.is_stable()
809    }
810
811    /// Get feature name as string
812    pub fn name(&self) -> &'static str {
813        match self {
814            Self::BulkMemory => "bulk-memory",
815            Self::Simd => "simd",
816            Self::Threads => "threads",
817            Self::TailCall => "tail-call",
818            Self::ReferenceTypes => "reference-types",
819            Self::MultiValue => "multi-value",
820            Self::SignExtension => "sign-extension",
821            Self::ExceptionHandling => "exception-handling",
822            Self::GarbageCollection => "garbage-collection",
823            Self::RelaxedSimd => "relaxed-simd",
824        }
825    }
826}
827
828/// WASM module validation errors
829#[derive(Debug, Clone, Error, PartialEq, Eq)]
830pub enum WasmValidationError {
831    #[error("Empty WASM module")]
832    EmptyModule,
833
834    #[error("Module too large: {size} bytes, limit {limit} bytes")]
835    ModuleTooLarge { size: usize, limit: usize },
836
837    #[error("Invalid WASM format: {reason}")]
838    InvalidFormat { reason: String },
839
840    #[error("Unsupported WASM version: {version}")]
841    UnsupportedVersion { version: u32 },
842
843    #[error("Function limit exceeded: {count}, limit {limit}")]
844    FunctionLimitExceeded { count: usize, limit: usize },
845
846    #[error("Import not allowed: {function_name}")]
847    ImportNotAllowed { function_name: String },
848
849    #[error("Required export missing: {function_name}")]
850    RequiredExportMissing { function_name: String },
851
852    #[error("Security policy violation: {policy} - {violation}")]
853    SecurityPolicyViolation { policy: String, violation: String },
854
855    #[error("Feature not supported: {feature}")]
856    FeatureNotSupported { feature: String },
857
858    #[error("Hash verification failed")]
859    HashVerificationFailed,
860}
861
862#[cfg(test)]
863mod tests {
864    use super::*;
865
866    #[test]
867    fn test_module_hash() {
868        let data = b"test wasm module";
869        let hash = ModuleHash::sha256(data);
870        assert_eq!(hash.algorithm(), HashAlgorithm::Sha256);
871
872        let hex_hash = ModuleHash::from_hex(
873            "abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789",
874        )
875        .unwrap();
876        assert_eq!(hex_hash.algorithm(), HashAlgorithm::Sha256);
877    }
878
879    #[test]
880    fn test_module_size() {
881        let size_10mb = ModuleSize::from_mb(10).unwrap();
882        assert_eq!(size_10mb.as_mb(), 10);
883        assert_eq!(size_10mb.as_kb(), 10240);
884
885        let size_512kb = ModuleSize::from_kb(512).unwrap();
886        assert_eq!(size_512kb.as_kb(), 512);
887        assert_eq!(size_512kb.as_mb(), 1); // Rounded up
888    }
889
890    #[test]
891    fn test_wasm_value_type() {
892        let i32_type = WasmValueType::I32;
893        assert!(i32_type.is_numeric());
894        assert!(!i32_type.is_reference());
895        assert_eq!(i32_type.size_bytes(), 4);
896
897        let funcref_type = WasmValueType::FuncRef;
898        assert!(!funcref_type.is_numeric());
899        assert!(funcref_type.is_reference());
900        assert_eq!(funcref_type.size_bytes(), 8);
901    }
902
903    #[test]
904    fn test_security_policy() {
905        let strict_policy = WasmSecurityPolicy::strict();
906        let permissive_policy = WasmSecurityPolicy::permissive();
907
908        assert_eq!(strict_policy.max_memory_pages, 16);
909        assert_eq!(permissive_policy.max_memory_pages, 1024);
910
911        assert!(!strict_policy.allow_simd);
912        assert!(permissive_policy.allow_simd);
913    }
914
915    #[test]
916    fn test_validation_result() {
917        let valid = ValidationResult::Valid;
918        assert!(valid.is_valid());
919        assert!(!valid.has_warnings());
920
921        let invalid = ValidationResult::Invalid {
922            reasons: vec![ValidationFailure::InvalidWasmFormat],
923        };
924        assert!(!invalid.is_valid());
925        assert_eq!(invalid.error_messages().len(), 1);
926
927        let warning = ValidationResult::Warning {
928            warnings: vec![ValidationWarning::LargeFunctionCount { count: 600 }],
929        };
930        assert!(warning.is_valid());
931        assert!(warning.has_warnings());
932    }
933
934    #[test]
935    fn test_wasm_features() {
936        let simd = WasmFeature::Simd;
937        assert!(simd.is_stable());
938        assert!(!simd.is_experimental());
939        assert_eq!(simd.name(), "simd");
940
941        let gc = WasmFeature::GarbageCollection;
942        assert!(!gc.is_stable());
943        assert!(gc.is_experimental());
944        assert_eq!(gc.name(), "garbage-collection");
945    }
946
947    #[test]
948    fn test_wasm_module_creation() {
949        let version = AgentVersion::generate();
950        let version_number = VersionNumber::first();
951        let policy = WasmSecurityPolicy::testing();
952        let wasm_bytes = b"fake wasm module content";
953
954        let module =
955            WasmModule::from_bytes(version, version_number, None, None, wasm_bytes, &policy)
956                .unwrap();
957
958        assert!(module.is_valid());
959        assert_eq!(module.size.as_bytes(), wasm_bytes.len());
960        assert_eq!(module.total_function_count(), 0); // No functions in fake module
961    }
962}