use wasmtime::{Config, Engine, Module, OptLevel};
use crate::error::WasmError;
use crate::limits::PluginLimits;
#[derive(Clone)]
pub struct CompiledModule {
module: Module,
pub name: String,
pub version: String,
pub body_access: bool,
}
impl CompiledModule {
pub fn module(&self) -> &Module {
&self.module
}
}
pub struct WasmEngine {
engine: Engine,
limits: PluginLimits,
}
impl WasmEngine {
pub fn new() -> Result<Self, WasmError> {
Self::with_limits(PluginLimits::default())
}
pub fn with_limits(limits: PluginLimits) -> Result<Self, WasmError> {
let mut config = Config::new();
config.cranelift_opt_level(OptLevel::Speed);
config.consume_fuel(true);
config.max_wasm_stack(limits.max_stack_bytes);
config.wasm_reference_types(true);
config.wasm_bulk_memory(true);
config.wasm_multi_value(true);
config.wasm_threads(false);
let engine = Engine::new(&config).map_err(|e| WasmError::EngineCreation(e.to_string()))?;
Ok(Self { engine, limits })
}
pub fn engine(&self) -> &Engine {
&self.engine
}
pub fn limits(&self) -> &PluginLimits {
&self.limits
}
pub fn compile(
&self,
wasm_bytes: &[u8],
name: String,
version: String,
body_access: bool,
) -> Result<CompiledModule, WasmError> {
let module = Module::new(&self.engine, wasm_bytes)
.map_err(|e| WasmError::Compilation(e.to_string()))?;
Ok(CompiledModule {
module,
name,
version,
body_access,
})
}
pub fn validate(&self, wasm_bytes: &[u8]) -> Result<(), WasmError> {
Module::validate(&self.engine, wasm_bytes)
.map_err(|e| WasmError::Compilation(e.to_string()))
}
}
impl Default for WasmEngine {
fn default() -> Self {
Self::new().expect("failed to create default WASM engine")
}
}
#[cfg(test)]
mod tests {
use super::*;
const MINIMAL_WASM: &[u8] = &[
0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, ];
#[test]
fn create_engine() {
let engine = WasmEngine::new();
assert!(engine.is_ok());
}
#[test]
fn create_engine_with_limits() {
let limits = PluginLimits::default().with_memory(32 * 1024 * 1024);
let engine = WasmEngine::with_limits(limits);
assert!(engine.is_ok());
}
#[test]
fn validate_minimal_wasm() {
let engine = WasmEngine::new().unwrap();
assert!(engine.validate(MINIMAL_WASM).is_ok());
}
#[test]
fn validate_invalid_wasm() {
let engine = WasmEngine::new().unwrap();
let invalid = &[0x00, 0x00, 0x00, 0x00];
assert!(engine.validate(invalid).is_err());
}
#[test]
fn compile_minimal_wasm() {
let engine = WasmEngine::new().unwrap();
let result = engine.compile(MINIMAL_WASM, "test".into(), "1.0.0".into(), false);
assert!(result.is_ok());
}
#[test]
fn compiled_module_has_metadata() {
let engine = WasmEngine::new().unwrap();
let module = engine
.compile(MINIMAL_WASM, "my-plugin".into(), "2.1.0".into(), false)
.unwrap();
assert_eq!(module.name, "my-plugin");
assert_eq!(module.version, "2.1.0");
}
}