1use 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#[nutype(
17 validate(len_char_min = 64, len_char_max = 128), 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 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 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 Self::try_new(format!("{hash:016x}{hash:016x}{hash:016x}{hash:016x}")).unwrap()
66 }
67
68 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#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
80pub enum HashAlgorithm {
81 Sha256,
82 Sha512,
83 Unknown,
84}
85
86#[nutype(
88 validate(greater_or_equal = 1, less_or_equal = 104_857_600), 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 pub fn from_mb(mb: usize) -> Result<Self, ModuleSizeError> {
114 Self::try_new(mb * 1024 * 1024)
115 }
116
117 pub fn from_kb(kb: usize) -> Result<Self, ModuleSizeError> {
124 Self::try_new(kb * 1024)
125 }
126
127 pub fn as_bytes(&self) -> usize {
129 self.into_inner()
130 }
131
132 pub fn as_kb(&self) -> usize {
134 self.into_inner().div_ceil(1024)
135 }
136
137 pub fn as_mb(&self) -> usize {
139 self.into_inner().div_ceil(1_048_576)
140 }
141}
142
143#[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#[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#[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#[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 pub fn as_u16(&self) -> u16 {
222 self.into_inner()
223 }
224}
225
226#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Hash)]
228pub struct WasmFunctionSignature {
229 pub name: String, pub parameters: Vec<WasmValueType>,
231 pub results: Vec<WasmValueType>,
232 pub is_host_import: bool,
233}
234
235impl WasmFunctionSignature {
236 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 pub fn has_parameters(&self) -> bool {
253 !self.parameters.is_empty()
254 }
255
256 pub fn has_results(&self) -> bool {
258 !self.results.is_empty()
259 }
260
261 pub fn arity(&self) -> usize {
263 self.parameters.len()
264 }
265}
266
267#[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 pub fn is_numeric(&self) -> bool {
282 matches!(self, Self::I32 | Self::I64 | Self::F32 | Self::F64)
283 }
284
285 pub fn is_reference(&self) -> bool {
287 matches!(self, Self::FuncRef | Self::ExternRef)
288 }
289
290 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 }
298 }
299}
300
301#[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 pub fn is_valid(&self) -> bool {
312 matches!(self, Self::Valid | Self::Warning { .. })
313 }
314
315 pub fn has_warnings(&self) -> bool {
317 matches!(self, Self::Warning { .. })
318 }
319
320 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 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#[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#[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#[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 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, 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 pub fn permissive() -> Self {
517 Self {
518 name: "permissive".to_string(),
519 version: "1.0".to_string(),
520 allowed_imports: HashSet::new(), required_exports: HashSet::new(),
522 forbidden_instructions: HashSet::new(),
523 max_memory_pages: 1024, max_table_elements: 10000,
525 allow_bulk_memory: true,
526 allow_simd: true,
527 allow_threads: false, custom_validations: vec![],
529 }
530 }
531
532 pub fn testing() -> Self {
534 let mut policy = Self::permissive();
535 policy.name = "testing".to_string();
536 policy.max_memory_pages = 32; policy.max_table_elements = 1000;
538 policy
539 }
540
541 pub fn is_import_allowed(&self, import_name: &str) -> bool {
543 if self.allowed_imports.is_empty() {
544 return true; }
546 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 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 pub fn validate_module(&self, module: &WasmModule) -> ValidationResult {
565 let mut failures = vec![];
566 let mut warnings = vec![];
567
568 if module.size.as_bytes() > 50 * 1024 * 1024 {
570 failures.push(ValidationFailure::ModuleTooLarge {
572 size: module.size.as_bytes(),
573 limit: 50 * 1024 * 1024,
574 });
575 }
576
577 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 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 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 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#[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#[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#[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 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 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, 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 module.validation_result = security_policy.validate_module(&module);
722
723 module.security_policy = security_policy.clone();
725
726 Ok(module)
727 }
728
729 pub fn validate(&self) -> ValidationResult {
731 self.security_policy.validate_module(self)
732 }
733
734 pub fn is_valid(&self) -> bool {
736 self.validation_result.is_valid()
737 }
738
739 pub fn total_function_count(&self) -> usize {
741 self.functions.len() + self.imports.len()
742 }
743
744 pub fn estimated_memory_usage(&self) -> usize {
746 (self.memory_pages as usize) * 65536 }
748
749 pub fn uses_feature(&self, feature: WasmFeature) -> bool {
751 self.features_used.contains(&feature)
752 }
753
754 pub fn add_metadata(&mut self, key: String, value: String) {
756 self.metadata.insert(key, value);
757 }
758
759 fn extract_functions(_wasm_bytes: &[u8]) -> Vec<WasmFunctionSignature> {
761 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#[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 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 pub fn is_experimental(&self) -> bool {
808 !self.is_stable()
809 }
810
811 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#[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); }
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); }
962}