Skip to main content

mofa_plugins/wasm_runtime/
types.rs

1//! WASM type definitions
2//!
3//! Core types for the WASM plugin runtime
4
5use serde::{Deserialize, Serialize};
6use std::collections::HashMap;
7use std::fmt;
8use thiserror::Error;
9
10/// WASM value types
11#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
12pub enum WasmValue {
13    /// 32-bit integer
14    I32(i32),
15    /// 64-bit integer
16    I64(i64),
17    /// 32-bit float
18    F32(f32),
19    /// 64-bit float
20    F64(f64),
21    /// Boolean
22    Bool(bool),
23    /// String
24    String(String),
25    /// Byte array
26    Bytes(Vec<u8>),
27    /// Null value
28    Null,
29    /// Array of values
30    Array(Vec<WasmValue>),
31    /// Map of values
32    Map(HashMap<String, WasmValue>),
33}
34
35impl WasmValue {
36    pub fn as_i32(&self) -> Option<i32> {
37        match self {
38            WasmValue::I32(v) => Some(*v),
39            WasmValue::I64(v) => Some(*v as i32),
40            _ => None,
41        }
42    }
43
44    pub fn as_i64(&self) -> Option<i64> {
45        match self {
46            WasmValue::I32(v) => Some(*v as i64),
47            WasmValue::I64(v) => Some(*v),
48            _ => None,
49        }
50    }
51
52    pub fn as_f32(&self) -> Option<f32> {
53        match self {
54            WasmValue::F32(v) => Some(*v),
55            WasmValue::F64(v) => Some(*v as f32),
56            _ => None,
57        }
58    }
59
60    pub fn as_f64(&self) -> Option<f64> {
61        match self {
62            WasmValue::F32(v) => Some(*v as f64),
63            WasmValue::F64(v) => Some(*v),
64            _ => None,
65        }
66    }
67
68    pub fn as_bool(&self) -> Option<bool> {
69        match self {
70            WasmValue::Bool(v) => Some(*v),
71            WasmValue::I32(v) => Some(*v != 0),
72            _ => None,
73        }
74    }
75
76    pub fn as_string(&self) -> Option<&str> {
77        match self {
78            WasmValue::String(s) => Some(s),
79            _ => None,
80        }
81    }
82
83    pub fn as_bytes(&self) -> Option<&[u8]> {
84        match self {
85            WasmValue::Bytes(b) => Some(b),
86            _ => None,
87        }
88    }
89
90    pub fn is_null(&self) -> bool {
91        matches!(self, WasmValue::Null)
92    }
93}
94
95impl From<i32> for WasmValue {
96    fn from(v: i32) -> Self {
97        WasmValue::I32(v)
98    }
99}
100
101impl From<i64> for WasmValue {
102    fn from(v: i64) -> Self {
103        WasmValue::I64(v)
104    }
105}
106
107impl From<f32> for WasmValue {
108    fn from(v: f32) -> Self {
109        WasmValue::F32(v)
110    }
111}
112
113impl From<f64> for WasmValue {
114    fn from(v: f64) -> Self {
115        WasmValue::F64(v)
116    }
117}
118
119impl From<bool> for WasmValue {
120    fn from(v: bool) -> Self {
121        WasmValue::Bool(v)
122    }
123}
124
125impl From<String> for WasmValue {
126    fn from(v: String) -> Self {
127        WasmValue::String(v)
128    }
129}
130
131impl From<&str> for WasmValue {
132    fn from(v: &str) -> Self {
133        WasmValue::String(v.to_string())
134    }
135}
136
137impl From<Vec<u8>> for WasmValue {
138    fn from(v: Vec<u8>) -> Self {
139        WasmValue::Bytes(v)
140    }
141}
142
143/// WASM type definitions
144#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
145pub enum WasmType {
146    I32,
147    I64,
148    F32,
149    F64,
150    V128,
151    FuncRef,
152    ExternRef,
153}
154
155impl fmt::Display for WasmType {
156    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
157        match self {
158            WasmType::I32 => write!(f, "i32"),
159            WasmType::I64 => write!(f, "i64"),
160            WasmType::F32 => write!(f, "f32"),
161            WasmType::F64 => write!(f, "f64"),
162            WasmType::V128 => write!(f, "v128"),
163            WasmType::FuncRef => write!(f, "funcref"),
164            WasmType::ExternRef => write!(f, "externref"),
165        }
166    }
167}
168
169/// WASM runtime errors
170#[derive(Debug, Error)]
171pub enum WasmError {
172    #[error("Failed to compile WASM module: {0}")]
173    CompilationError(String),
174
175    #[error("Failed to instantiate WASM module: {0}")]
176    InstantiationError(String),
177
178    #[error("Failed to load WASM module: {0}")]
179    LoadError(String),
180
181    #[error("Export not found: {0}")]
182    ExportNotFound(String),
183
184    #[error("Import not found: {module}.{name}")]
185    ImportNotFound { module: String, name: String },
186
187    #[error("Type mismatch: expected {expected}, got {actual}")]
188    TypeMismatch { expected: String, actual: String },
189
190    #[error("Memory access out of bounds: offset={offset}, size={size}")]
191    MemoryOutOfBounds { offset: u32, size: u32 },
192
193    #[error("Memory allocation failed: requested {size} bytes")]
194    AllocationFailed { size: u32 },
195
196    #[error("Execution error: {0}")]
197    ExecutionError(String),
198
199    #[error("Timeout: execution exceeded {0}ms")]
200    Timeout(u64),
201
202    #[error("Resource limit exceeded: {0}")]
203    ResourceLimitExceeded(String),
204
205    #[error("Invalid plugin manifest: {0}")]
206    InvalidManifest(String),
207
208    #[error("Plugin not found: {0}")]
209    PluginNotFound(String),
210
211    #[error("Plugin already loaded: {0}")]
212    PluginAlreadyLoaded(String),
213
214    #[error("Host function error: {0}")]
215    HostFunctionError(String),
216
217    #[error("Serialization error: {0}")]
218    SerializationError(String),
219
220    #[error("IO error: {0}")]
221    IoError(#[from] std::io::Error),
222
223    #[error("Internal error: {0}")]
224    Internal(String),
225}
226
227/// WASM result type
228pub type WasmResult<T> = Result<T, WasmError>;
229
230/// Plugin capabilities
231#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
232pub enum PluginCapability {
233    /// Can read configuration
234    ReadConfig,
235    /// Can write configuration
236    WriteConfig,
237    /// Can send messages
238    SendMessage,
239    /// Can receive messages
240    ReceiveMessage,
241    /// Can call tools
242    CallTool,
243    /// Can access storage
244    Storage,
245    /// Can make HTTP requests
246    HttpClient,
247    /// Can access filesystem (sandboxed)
248    FileSystem,
249    /// Can use timers
250    Timer,
251    /// Can use random number generation
252    Random,
253    /// Custom capability
254    Custom(String),
255}
256
257impl fmt::Display for PluginCapability {
258    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
259        match self {
260            PluginCapability::ReadConfig => write!(f, "read_config"),
261            PluginCapability::WriteConfig => write!(f, "write_config"),
262            PluginCapability::SendMessage => write!(f, "send_message"),
263            PluginCapability::ReceiveMessage => write!(f, "receive_message"),
264            PluginCapability::CallTool => write!(f, "call_tool"),
265            PluginCapability::Storage => write!(f, "storage"),
266            PluginCapability::HttpClient => write!(f, "http_client"),
267            PluginCapability::FileSystem => write!(f, "filesystem"),
268            PluginCapability::Timer => write!(f, "timer"),
269            PluginCapability::Random => write!(f, "random"),
270            PluginCapability::Custom(s) => write!(f, "custom:{}", s),
271        }
272    }
273}
274
275/// Plugin manifest describing the plugin
276#[derive(Debug, Clone, Serialize, Deserialize)]
277pub struct PluginManifest {
278    /// Plugin name
279    pub name: String,
280    /// Plugin version
281    pub version: String,
282    /// Plugin description
283    pub description: Option<String>,
284    /// Plugin author
285    pub author: Option<String>,
286    /// Plugin license
287    pub license: Option<String>,
288    /// Required capabilities
289    pub capabilities: Vec<PluginCapability>,
290    /// Exported functions
291    pub exports: Vec<PluginExport>,
292    /// Minimum runtime version
293    pub min_runtime_version: Option<String>,
294    /// Plugin-specific configuration schema
295    pub config_schema: Option<serde_json::Value>,
296    /// Plugin metadata
297    pub metadata: HashMap<String, String>,
298}
299
300impl Default for PluginManifest {
301    fn default() -> Self {
302        Self {
303            name: "unknown".to_string(),
304            version: "0.0.0".to_string(),
305            description: None,
306            author: None,
307            license: None,
308            capabilities: Vec::new(),
309            exports: Vec::new(),
310            min_runtime_version: None,
311            config_schema: None,
312            metadata: HashMap::new(),
313        }
314    }
315}
316
317impl PluginManifest {
318    pub fn new(name: &str, version: &str) -> Self {
319        Self {
320            name: name.to_string(),
321            version: version.to_string(),
322            ..Default::default()
323        }
324    }
325
326    pub fn with_description(mut self, description: &str) -> Self {
327        self.description = Some(description.to_string());
328        self
329    }
330
331    pub fn with_capability(mut self, capability: PluginCapability) -> Self {
332        if !self.capabilities.contains(&capability) {
333            self.capabilities.push(capability);
334        }
335        self
336    }
337
338    pub fn with_export(mut self, export: PluginExport) -> Self {
339        self.exports.push(export);
340        self
341    }
342
343    pub fn has_capability(&self, capability: &PluginCapability) -> bool {
344        self.capabilities.contains(capability)
345    }
346}
347
348/// Plugin export definition
349#[derive(Debug, Clone, Serialize, Deserialize)]
350pub struct PluginExport {
351    /// Export name
352    pub name: String,
353    /// Export kind
354    pub kind: ExportKind,
355    /// Parameter types (for functions)
356    pub params: Vec<WasmType>,
357    /// Return types (for functions)
358    pub returns: Vec<WasmType>,
359    /// Description
360    pub description: Option<String>,
361}
362
363impl PluginExport {
364    pub fn function(name: &str, params: Vec<WasmType>, returns: Vec<WasmType>) -> Self {
365        Self {
366            name: name.to_string(),
367            kind: ExportKind::Function,
368            params,
369            returns,
370            description: None,
371        }
372    }
373
374    pub fn memory(name: &str) -> Self {
375        Self {
376            name: name.to_string(),
377            kind: ExportKind::Memory,
378            params: Vec::new(),
379            returns: Vec::new(),
380            description: None,
381        }
382    }
383
384    pub fn with_description(mut self, description: &str) -> Self {
385        self.description = Some(description.to_string());
386        self
387    }
388}
389
390/// Export kind
391#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
392pub enum ExportKind {
393    Function,
394    Memory,
395    Table,
396    Global,
397}
398
399/// Resource limits for WASM execution
400#[derive(Debug, Clone, Serialize, Deserialize)]
401pub struct ResourceLimits {
402    /// Maximum memory pages (64KB each)
403    pub max_memory_pages: u32,
404    /// Maximum table elements
405    pub max_table_elements: u32,
406    /// Maximum instances per module
407    pub max_instances: u32,
408    /// Maximum execution time in milliseconds
409    pub max_execution_time_ms: u64,
410    /// Maximum fuel (instruction count)
411    pub max_fuel: Option<u64>,
412    /// Maximum call stack depth
413    pub max_call_depth: u32,
414}
415
416impl Default for ResourceLimits {
417    fn default() -> Self {
418        Self {
419            max_memory_pages: 256, // 16MB
420            max_table_elements: 10000,
421            max_instances: 10,
422            max_execution_time_ms: 30000, // 30 seconds
423            max_fuel: Some(100_000_000),  // ~100M instructions
424            max_call_depth: 1000,
425        }
426    }
427}
428
429impl ResourceLimits {
430    pub fn unlimited() -> Self {
431        Self {
432            max_memory_pages: u32::MAX,
433            max_table_elements: u32::MAX,
434            max_instances: u32::MAX,
435            max_execution_time_ms: u64::MAX,
436            max_fuel: None,
437            max_call_depth: u32::MAX,
438        }
439    }
440
441    pub fn restrictive() -> Self {
442        Self {
443            max_memory_pages: 16, // 1MB
444            max_table_elements: 1000,
445            max_instances: 1,
446            max_execution_time_ms: 5000, // 5 seconds
447            max_fuel: Some(10_000_000),  // ~10M instructions
448            max_call_depth: 100,
449        }
450    }
451
452    pub fn max_memory_bytes(&self) -> u64 {
453        self.max_memory_pages as u64 * 65536
454    }
455}
456
457/// Memory configuration
458#[derive(Debug, Clone, Serialize, Deserialize)]
459pub struct MemoryConfig {
460    /// Initial memory pages
461    pub initial_pages: u32,
462    /// Maximum memory pages
463    pub maximum_pages: Option<u32>,
464    /// Memory is shared
465    pub shared: bool,
466    /// Memory growth limit per call
467    pub growth_limit: Option<u32>,
468}
469
470impl Default for MemoryConfig {
471    fn default() -> Self {
472        Self {
473            initial_pages: 1,         // 64KB
474            maximum_pages: Some(256), // 16MB
475            shared: false,
476            growth_limit: Some(16), // 1MB per growth
477        }
478    }
479}
480
481/// Execution configuration
482#[derive(Debug, Clone, Serialize, Deserialize)]
483pub struct ExecutionConfig {
484    /// Enable async execution
485    pub async_support: bool,
486    /// Enable fuel metering
487    pub fuel_metering: bool,
488    /// Enable epoch interruption
489    pub epoch_interruption: bool,
490    /// Epoch tick interval in milliseconds
491    pub epoch_tick_ms: u64,
492    /// Enable debug info
493    pub debug_info: bool,
494    /// Enable reference types
495    pub reference_types: bool,
496    /// Enable SIMD
497    pub simd: bool,
498    /// Enable bulk memory operations
499    pub bulk_memory: bool,
500    /// Enable multi-value returns
501    pub multi_value: bool,
502    /// Enable threads
503    pub threads: bool,
504}
505
506impl Default for ExecutionConfig {
507    fn default() -> Self {
508        Self {
509            async_support: true,
510            fuel_metering: true,
511            epoch_interruption: true,
512            epoch_tick_ms: 10,
513            debug_info: false,
514            reference_types: true,
515            simd: true,
516            bulk_memory: true,
517            multi_value: true,
518            threads: false,
519        }
520    }
521}
522
523#[cfg(test)]
524mod tests {
525    use super::*;
526
527    #[test]
528    fn test_wasm_value_conversions() {
529        let v = WasmValue::from(42i32);
530        assert_eq!(v.as_i32(), Some(42));
531        assert_eq!(v.as_i64(), Some(42));
532
533        let v = WasmValue::from("hello");
534        assert_eq!(v.as_string(), Some("hello"));
535
536        let v = WasmValue::from(true);
537        assert_eq!(v.as_bool(), Some(true));
538    }
539
540    #[test]
541    fn test_plugin_manifest() {
542        let manifest = PluginManifest::new("test-plugin", "1.0.0")
543            .with_description("A test plugin")
544            .with_capability(PluginCapability::ReadConfig)
545            .with_capability(PluginCapability::SendMessage);
546
547        assert_eq!(manifest.name, "test-plugin");
548        assert_eq!(manifest.version, "1.0.0");
549        assert!(manifest.has_capability(&PluginCapability::ReadConfig));
550        assert!(!manifest.has_capability(&PluginCapability::Storage));
551    }
552
553    #[test]
554    fn test_resource_limits() {
555        let limits = ResourceLimits::default();
556        assert_eq!(limits.max_memory_bytes(), 16 * 1024 * 1024); // 16MB
557
558        let restrictive = ResourceLimits::restrictive();
559        assert_eq!(restrictive.max_memory_bytes(), 1024 * 1024); // 1MB
560    }
561}