1use snafu::Backtrace;
11use snafu::GenerateImplicitData;
12use wasmtime::Caller;
13use wasmtime::Engine;
14use wasmtime::InstancePre;
15use wasmtime::Linker;
16use wasmtime::Module;
17use wasmtime::Store;
18
19use crate::Error;
20use crate::Result;
21use crate::error::WasmtimeError;
22use crate::imports::CapabilitySet;
23use crate::imports::HostImports;
24use crate::imports::ImportOutcome;
25use crate::sandbox::Capability;
26use crate::sandbox::Sandbox;
27use crate::sandbox::SandboxInstance;
28use crate::sandbox::Value;
29
30pub(crate) struct HostState {
34 pub caps: CapabilitySet,
35}
36
37const DEFAULT_MEMORY_BYTES: usize = 32 * 1024 * 1024;
42
43#[derive(Clone)]
50pub struct WasmSandbox {
51 engine: Engine,
52 linker_template: std::sync::Arc<Linker<HostState>>,
55}
56
57impl WasmSandbox {
58 pub fn new() -> Result<Self> {
62 let config = wasmtime::Config::new();
65 let engine = Engine::new(&config).map_err(|e| Error::Engine {
66 source: WasmtimeError::from_display(e),
67 backtrace: Backtrace::generate(),
68 })?;
69 let linker = build_linker(&engine);
70 Ok(Self {
71 engine,
72 linker_template: std::sync::Arc::new(linker),
73 })
74 }
75
76 pub fn with_engine(engine: Engine) -> Result<Self> {
79 let linker = build_linker(&engine);
80 Ok(Self {
81 engine,
82 linker_template: std::sync::Arc::new(linker),
83 })
84 }
85
86 pub fn engine(&self) -> &Engine {
88 &self.engine
89 }
90}
91
92const _: usize = DEFAULT_MEMORY_BYTES;
94
95impl Sandbox for WasmSandbox {
96 fn load_module(&self, bytes: &[u8]) -> Result<Box<dyn SandboxInstance>> {
97 let module = Module::new(&self.engine, bytes).map_err(|e| Error::ModuleCompile {
98 source: WasmtimeError::from_display(e),
99 backtrace: Backtrace::generate(),
100 })?;
101 let linker = (*self.linker_template).clone();
104 let instance_pre = linker
105 .instantiate_pre(&module)
106 .map_err(|e| Error::Instantiation {
107 source: WasmtimeError::from_display(e),
108 backtrace: Backtrace::generate(),
109 })?;
110
111 let state = HostState {
112 caps: CapabilitySet::new(),
113 };
114 let store = Store::new(&self.engine, state);
115
116 Ok(Box::new(WasmInstance {
117 instance_pre,
118 store,
119 }))
120 }
121
122 fn name(&self) -> &'static str {
123 "wasmtime"
124 }
125}
126
127impl Default for WasmSandbox {
128 fn default() -> Self {
129 Self::new().expect("default wasmtime engine config must succeed")
130 }
131}
132
133pub struct WasmInstance {
140 instance_pre: InstancePre<HostState>,
141 store: Store<HostState>,
142}
143
144impl WasmInstance {
145 fn store_mut(&mut self) -> &mut Store<HostState> {
148 &mut self.store
149 }
150
151 fn invocation_error<E: std::fmt::Display>(function: &str, e: E) -> Error {
152 Error::Invocation {
153 function: function.to_string(),
154 source: WasmtimeError::from_display(e),
155 backtrace: Backtrace::generate(),
156 }
157 }
158}
159
160impl SandboxInstance for WasmInstance {
161 fn call(&mut self, function: &str, args: &[Value]) -> Result<Vec<Value>> {
162 let instance_pre = self.instance_pre.clone();
165 let store = self.store_mut();
166 let instance = instance_pre
167 .instantiate(&mut *store)
168 .map_err(|e| Self::invocation_error(function, e))?;
169
170 let export =
171 instance
172 .get_export(&mut *store, function)
173 .ok_or_else(|| Error::FunctionNotFound {
174 function: function.to_string(),
175 backtrace: Backtrace::generate(),
176 })?;
177
178 let func = export.into_func().ok_or_else(|| Error::ExportNotFound {
179 export: function.to_string(),
180 backtrace: Backtrace::generate(),
181 })?;
182
183 if let Ok(typed) = func.typed::<i32, i64>(&mut *store) {
187 let arg = match args.first() {
188 Some(Value::I32(v)) => *v,
189 _ => 0,
190 };
191 let out = typed
192 .call(&mut *store, arg)
193 .map_err(|e| Self::invocation_error(function, e))?;
194 return Ok(vec![Value::I64(out)]);
195 }
196
197 if let Ok(typed) = func.typed::<i64, i64>(&mut *store) {
198 let arg = match args.first() {
199 Some(Value::I64(v)) => *v,
200 Some(Value::I32(v)) => i64::from(*v),
201 _ => 0,
202 };
203 let out = typed
204 .call(&mut *store, arg)
205 .map_err(|e| Self::invocation_error(function, e))?;
206 return Ok(vec![Value::I64(out)]);
207 }
208
209 if let Ok(typed) = func.typed::<(), i32>(&mut *store) {
210 let out = typed
211 .call(&mut *store, ())
212 .map_err(|e| Self::invocation_error(function, e))?;
213 return Ok(vec![Value::I32(out)]);
214 }
215
216 let wasm_args: Vec<wasmtime::Val> = args
218 .iter()
219 .map(value_to_wasmval)
220 .collect::<std::result::Result<_, _>>()
221 .map_err(|()| Error::ArgumentType {
222 function: function.to_string(),
223 backtrace: Backtrace::generate(),
224 })?;
225
226 let result_count = func.ty(&mut *store).results().len();
227 let mut wasm_outs = vec![wasmtime::Val::I32(0); result_count];
228 func.call(&mut *store, &wasm_args, &mut wasm_outs)
229 .map_err(|e| Self::invocation_error(function, e))?;
230
231 wasm_outs
232 .iter()
233 .map(wasmval_to_value)
234 .collect::<std::result::Result<_, _>>()
235 .map_err(|()| Error::ArgumentType {
236 function: function.to_string(),
237 backtrace: Backtrace::generate(),
238 })
239 }
240
241 fn grant_capability(&mut self, cap: Capability) -> Result<()> {
242 self.store.data().caps.grant(cap);
243 Ok(())
244 }
245
246 fn revoke_capability(&mut self, cap: &Capability) -> Result<()> {
247 self.store.data().caps.revoke(cap);
248 Ok(())
249 }
250}
251
252fn value_to_wasmval(v: &Value) -> std::result::Result<wasmtime::Val, ()> {
253 Ok(match v {
254 Value::I32(x) => wasmtime::Val::I32(*x),
255 Value::I64(x) => wasmtime::Val::I64(*x),
256 Value::F32(x) => wasmtime::Val::F32(x.to_bits()),
257 Value::F64(x) => wasmtime::Val::F64(x.to_bits()),
258 Value::Bytes(_) => return Err(()),
262 })
263}
264
265fn wasmval_to_value(v: &wasmtime::Val) -> std::result::Result<Value, ()> {
266 Ok(match v {
267 wasmtime::Val::I32(x) => Value::I32(*x),
268 wasmtime::Val::I64(x) => Value::I64(*x),
269 wasmtime::Val::F32(x) => Value::F32(f32::from_bits(*x)),
270 wasmtime::Val::F64(x) => Value::F64(f64::from_bits(*x)),
271 _ => return Err(()),
272 })
273}
274
275fn build_linker(engine: &Engine) -> Linker<HostState> {
280 let mut linker: Linker<HostState> = Linker::new(engine);
281
282 let _ = linker.func_wrap(
285 "confium",
286 "cfm_hash_update",
287 |caller: Caller<'_, HostState>, len: i32| -> i64 {
288 let caps = &caller.data().caps;
289 match HostImports::cfm_hash_update(caps, len) {
290 ImportOutcome::Done(v) => v,
291 ImportOutcome::Denied => -1,
294 }
295 },
296 );
297
298 let _ = linker.func_wrap(
300 "confium",
301 "cfm_net_send",
302 |caller: Caller<'_, HostState>, url_id: i64| -> i64 {
303 let caps = &caller.data().caps;
304 match HostImports::cfm_net_send(caps, url_id) {
305 ImportOutcome::Done(v) => v,
306 ImportOutcome::Denied => -1,
307 }
308 },
309 );
310
311 let _ = linker.func_wrap(
313 "confium",
314 "cfm_key_get_secret",
315 |caller: Caller<'_, HostState>, key_id: i64| -> i64 {
316 let caps = &caller.data().caps;
317 match HostImports::cfm_key_get_secret(caps, key_id) {
318 ImportOutcome::Done(v) => v,
319 ImportOutcome::Denied => -1,
320 }
321 },
322 );
323
324 linker
325}
326
327#[cfg(test)]
328mod tests {
329 use super::*;
330
331 #[test]
332 fn sandbox_name_is_wasmtime() {
333 let sb = WasmSandbox::new().unwrap();
334 assert_eq!(sb.name(), "wasmtime");
335 }
336
337 #[test]
338 fn sandbox_is_cloneable_and_shares_engine() {
339 let sb = WasmSandbox::new().unwrap();
340 let _clone = sb.clone();
341 }
342}