1use wasmtime::{Config, Engine, Module, OptLevel};
7
8use crate::error::WasmError;
9use crate::limits::PluginLimits;
10
11#[derive(Clone)]
13pub struct CompiledModule {
14 module: Module,
15 pub name: String,
17 pub version: String,
19 pub body_access: bool,
21}
22
23impl CompiledModule {
24 pub fn module(&self) -> &Module {
26 &self.module
27 }
28}
29
30pub struct WasmEngine {
32 engine: Engine,
33 limits: PluginLimits,
34}
35
36impl WasmEngine {
37 pub fn new() -> Result<Self, WasmError> {
39 Self::with_limits(PluginLimits::default())
40 }
41
42 pub fn with_limits(limits: PluginLimits) -> Result<Self, WasmError> {
44 let mut config = Config::new();
45
46 config.cranelift_opt_level(OptLevel::Speed);
48
49 config.consume_fuel(true);
51
52 config.max_wasm_stack(limits.max_stack_bytes);
54
55 config.wasm_reference_types(true);
57
58 config.wasm_bulk_memory(true);
60
61 config.wasm_multi_value(true);
63
64 config.wasm_threads(false);
66
67 let engine = Engine::new(&config).map_err(|e| WasmError::EngineCreation(e.to_string()))?;
68
69 Ok(Self { engine, limits })
70 }
71
72 pub fn engine(&self) -> &Engine {
74 &self.engine
75 }
76
77 pub fn limits(&self) -> &PluginLimits {
79 &self.limits
80 }
81
82 pub fn compile(
86 &self,
87 wasm_bytes: &[u8],
88 name: String,
89 version: String,
90 body_access: bool,
91 ) -> Result<CompiledModule, WasmError> {
92 let module = Module::new(&self.engine, wasm_bytes)
93 .map_err(|e| WasmError::Compilation(e.to_string()))?;
94
95 Ok(CompiledModule {
96 module,
97 name,
98 version,
99 body_access,
100 })
101 }
102
103 pub fn validate(&self, wasm_bytes: &[u8]) -> Result<(), WasmError> {
107 Module::validate(&self.engine, wasm_bytes)
108 .map_err(|e| WasmError::Compilation(e.to_string()))
109 }
110}
111
112impl Default for WasmEngine {
113 fn default() -> Self {
114 Self::new().expect("failed to create default WASM engine")
115 }
116}
117
118#[cfg(test)]
119mod tests {
120 use super::*;
121
122 const MINIMAL_WASM: &[u8] = &[
124 0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, ];
127
128 #[test]
129 fn create_engine() {
130 let engine = WasmEngine::new();
131 assert!(engine.is_ok());
132 }
133
134 #[test]
135 fn create_engine_with_limits() {
136 let limits = PluginLimits::default().with_memory(32 * 1024 * 1024);
137 let engine = WasmEngine::with_limits(limits);
138 assert!(engine.is_ok());
139 }
140
141 #[test]
142 fn validate_minimal_wasm() {
143 let engine = WasmEngine::new().unwrap();
144 assert!(engine.validate(MINIMAL_WASM).is_ok());
145 }
146
147 #[test]
148 fn validate_invalid_wasm() {
149 let engine = WasmEngine::new().unwrap();
150 let invalid = &[0x00, 0x00, 0x00, 0x00];
151 assert!(engine.validate(invalid).is_err());
152 }
153
154 #[test]
155 fn compile_minimal_wasm() {
156 let engine = WasmEngine::new().unwrap();
157 let result = engine.compile(MINIMAL_WASM, "test".into(), "1.0.0".into(), false);
158 assert!(result.is_ok());
159 }
160
161 #[test]
162 fn compiled_module_has_metadata() {
163 let engine = WasmEngine::new().unwrap();
164 let module = engine
165 .compile(MINIMAL_WASM, "my-plugin".into(), "2.1.0".into(), false)
166 .unwrap();
167 assert_eq!(module.name, "my-plugin");
168 assert_eq!(module.version, "2.1.0");
169 }
170}