Skip to main content

rns_hooks/
runtime.rs

1#[cfg(feature = "wasm")]
2use crate::engine_access::EngineAccess;
3#[cfg(feature = "wasm")]
4use crate::wire::ActionWire;
5
6#[derive(Debug, Clone)]
7pub struct RawProviderEvent {
8    pub payload_type: String,
9    pub payload: Vec<u8>,
10}
11
12/// Default fuel budget per WASM invocation.
13pub const DEFAULT_FUEL: u64 = 10_000_000;
14/// Default maximum memory for WASM modules (16 MB).
15pub const DEFAULT_MAX_MEMORY: usize = 16 * 1024 * 1024;
16
17/// Data stored in each wasmtime `Store`.
18///
19/// Uses a raw pointer for `EngineAccess` because `Linker<T>` requires `T`
20/// without lifetime parameters. The Store is cached across calls for instance
21/// persistence, but `engine_access` is refreshed each call via `reset_per_call`
22/// and must only be dereferenced during the active call.
23#[cfg(feature = "wasm")]
24pub struct StoreData {
25    pub engine_access: *const dyn EngineAccess,
26    pub now: f64,
27    pub injected_actions: Vec<ActionWire>,
28    pub log_messages: Vec<String>,
29    pub provider_events: Vec<RawProviderEvent>,
30    pub provider_events_enabled: bool,
31}
32
33// Safety: StoreData is only used within a single-threaded driver loop.
34// The `engine_access` raw pointer is refreshed each call and only dereferenced
35// during that call while the borrow is live.
36#[cfg(feature = "wasm")]
37unsafe impl Send for StoreData {}
38#[cfg(feature = "wasm")]
39unsafe impl Sync for StoreData {}
40
41#[cfg(feature = "wasm")]
42impl StoreData {
43    /// Access the engine through the raw pointer.
44    ///
45    /// # Safety
46    /// The caller must ensure the pointer is still valid.
47    pub unsafe fn engine(&self) -> &dyn EngineAccess {
48        &*self.engine_access
49    }
50
51    /// Reset per-call fields while preserving the store (and WASM linear memory).
52    pub fn reset_per_call(
53        &mut self,
54        engine_access: *const dyn EngineAccess,
55        now: f64,
56        provider_events_enabled: bool,
57    ) {
58        self.engine_access = engine_access;
59        self.now = now;
60        self.injected_actions.clear();
61        self.log_messages.clear();
62        self.provider_events.clear();
63        self.provider_events_enabled = provider_events_enabled;
64    }
65}
66
67/// Wrapper around `wasmtime::Engine` with fuel-metering enabled.
68#[cfg(feature = "wasm")]
69pub struct WasmRuntime {
70    engine: wasmtime::Engine,
71    fuel: u64,
72}
73
74#[cfg(feature = "wasm")]
75impl WasmRuntime {
76    pub fn new() -> Result<Self, wasmtime::Error> {
77        let mut config = wasmtime::Config::new();
78        config.consume_fuel(true);
79        // Hooks use the stable core-module ABI only. Keep proposals which add
80        // reference types and exception control flow outside the sandbox even
81        // if a Wasmtime release enables them by default.
82        config.wasm_gc(false);
83        config.wasm_exceptions(false);
84        let engine = wasmtime::Engine::new(&config)?;
85        Ok(WasmRuntime {
86            engine,
87            fuel: DEFAULT_FUEL,
88        })
89    }
90
91    pub fn compile(&self, bytes: &[u8]) -> Result<wasmtime::Module, wasmtime::Error> {
92        wasmtime::Module::new(&self.engine, bytes)
93    }
94
95    pub fn engine(&self) -> &wasmtime::Engine {
96        &self.engine
97    }
98
99    pub fn fuel(&self) -> u64 {
100        self.fuel
101    }
102}
103
104#[cfg(all(test, feature = "wasm"))]
105mod tests {
106    use super::WasmRuntime;
107
108    #[test]
109    fn compiles_core_hook_instruction_set() {
110        let runtime = WasmRuntime::new().unwrap();
111        runtime
112            .compile(
113                br#"(module
114                    (memory 2)
115                    (func (export "bulk")
116                        i32.const 0
117                        i32.const 90
118                        i32.const 4096
119                        memory.fill
120                        i32.const 8192
121                        i32.const 0
122                        i32.const 4096
123                        memory.copy))"#,
124            )
125            .unwrap();
126    }
127
128    #[test]
129    fn rejects_webassembly_gc_modules() {
130        let runtime = WasmRuntime::new().unwrap();
131        let result = runtime.compile(
132            br#"(module
133                (type $record (struct (field i32)))
134                (func (export "make") (result (ref null $record))
135                    ref.null $record))"#,
136        );
137
138        assert!(result.is_err(), "the hook sandbox must not enable Wasm GC");
139    }
140
141    #[test]
142    fn rejects_webassembly_exception_modules() {
143        let runtime = WasmRuntime::new().unwrap();
144        let result = runtime.compile(
145            br#"(module
146                (tag $failure)
147                (func (export "raise")
148                    throw $failure))"#,
149        );
150
151        assert!(
152            result.is_err(),
153            "the hook sandbox must not enable Wasm exception handling"
154        );
155    }
156}