Sandboxing and the simplest smart-contract runtime (#140)

* Add primitives for sandboxing.

* Add sandbox module.

* Implement the runtime part of the sandbox.

* Rebuild binaries.

* Implement smart-contract execution.

* Add more documentation.
This commit is contained in:
Sergey Pepyakin
2018-05-01 21:32:01 +03:00
committed by Robert Habermeier
parent f116f67382
commit 5a56fbcea3
39 changed files with 2470 additions and 56 deletions
+27
View File
@@ -0,0 +1,27 @@
[package]
name = "substrate-runtime-sandbox"
version = "0.1.0"
authors = ["Parity Technologies <admin@parity.io>"]
build = "build.rs"
[build-dependencies]
rustc_version = "0.2"
[dependencies]
wasmi = { version = "0.1", optional = true }
substrate-primitives = { path = "../primitives", default_features = false }
substrate-runtime-std = { path = "../runtime-std", default_features = false }
substrate-runtime-io = { path = "../runtime-io", default_features = false }
substrate-codec = { path = "../codec", default_features = false }
[features]
default = ["std"]
std = [
"wasmi",
"substrate-primitives/std",
"substrate-runtime-std/std",
"substrate-codec/std",
"substrate-runtime-io/std",
]
nightly = []
strict = []
+14
View File
@@ -0,0 +1,14 @@
//! Set a nightly feature
extern crate rustc_version;
use rustc_version::{version, version_meta, Channel};
fn main() {
// Assert we haven't travelled back in time
assert!(version().unwrap().major >= 1);
// Set cfg flags depending on release channel
if let Channel::Nightly = version_meta().unwrap().channel {
println!("cargo:rustc-cfg=feature=\"nightly\"");
}
}
+203
View File
@@ -0,0 +1,203 @@
// Copyright 2018 Parity Technologies (UK) Ltd.
// This file is part of Substrate.
// Substrate is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Substrate is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
//! This crate provides means of instantiation and execution of wasm modules.
//!
//! It works even when the user of this library is itself executes
//! inside the wasm VM. In this case same VM is used for execution
//! of both the sandbox owner and the sandboxed module, without compromising security
//! and without performance penalty of full wasm emulation inside wasm.
//!
//! This is achieved by using bindings to wasm VM which are published by the host API.
//! This API is thin and consists of only handful functions. It contains functions for instantiating
//! modules and executing them and for example doesn't contain functions for inspecting the module
//! structure. The user of this library is supposed to read wasm module by it's own means.
//!
//! When this crate is used in `std` environment all these functions are implemented by directly
//! calling wasm VM.
//!
//! Typical use-case for this library might be used for implementing smart-contract runtimes
//! which uses wasm for contract code.
#![warn(missing_docs)]
#![cfg_attr(not(feature = "std"), no_std)]
#![cfg_attr(not(feature = "std"), feature(lang_items))]
#![cfg_attr(not(feature = "std"), feature(core_intrinsics))]
#![cfg_attr(not(feature = "std"), feature(alloc))]
extern crate substrate_codec as codec;
extern crate substrate_runtime_io as runtime_io;
extern crate substrate_runtime_std as rstd;
extern crate substrate_primitives as primitives;
use rstd::prelude::*;
pub use primitives::sandbox::{TypedValue, ReturnValue, HostError};
mod imp {
#[cfg(feature = "std")]
include!("../with_std.rs");
#[cfg(not(feature = "std"))]
include!("../without_std.rs");
}
/// Error that can occur while using this crate.
#[cfg_attr(feature = "std", derive(Debug))]
pub enum Error {
/// Module is not valid, couldn't be instantiated or it's `start` function trapped
/// when executed.
Module,
/// Access to a memory or table was made with an address or an index which is out of bounds.
///
/// Note that if wasm module makes an out-of-bounds access then trap will occur.
OutOfBounds,
/// Failed to invoke an exported function for some reason.
Execution,
}
impl From<Error> for HostError {
fn from(_e: Error) -> HostError {
HostError
}
}
/// Function pointer for specifying functions by the
/// supervisor in [`EnvironmentDefinitionBuilder`].
///
/// [`EnvironmentDefinitionBuilder`]: struct.EnvironmentDefinitionBuilder.html
pub type HostFuncType<T> = fn(&mut T, &[TypedValue]) -> Result<ReturnValue, HostError>;
/// Reference to a sandboxed linear memory, that
/// will be used by the guest module.
///
/// The memory can't be directly accessed by supervisor, but only
/// through designated functions [`get`] and [`set`].
///
/// [`get`]: #method.get
/// [`set`]: #method.set
#[derive(Clone)]
pub struct Memory {
inner: imp::Memory,
}
impl Memory {
/// Construct a new linear memory instance.
///
/// The memory allocated with initial number of pages specified by `initial`.
/// Minimal possible value for `initial` is 0 and maximum possible is `65536`.
/// (Since maximum addressible memory is 2<sup>32</sup> = 4GiB = 65536 * 64KiB).
///
/// It is possible to limit maximum number of pages this memory instance can have by specifying
/// `maximum`. If not specified, this memory instance would be able to allocate up to 4GiB.
///
/// Allocated memory is always zeroed.
pub fn new(initial: u32, maximum: Option<u32>) -> Result<Memory, Error> {
Ok(Memory {
inner: imp::Memory::new(initial, maximum)?,
})
}
/// Read a memory area at the address `ptr` with the size of the provided slice `buf`.
///
/// Returns `Err` if the range is out-of-bounds.
pub fn get(&self, ptr: u32, buf: &mut [u8]) -> Result<(), Error> {
self.inner.get(ptr, buf)
}
/// Write a memory area at the address `ptr` with contents of the provided slice `buf`.
///
/// Returns `Err` if the range is out-of-bounds.
pub fn set(&self, ptr: u32, value: &[u8]) -> Result<(), Error> {
self.inner.set(ptr, value)
}
}
/// Struct that can be used for defining an environment for a sandboxed module.
///
/// The sandboxed module can access only the entities which were defined and passed
/// to the module at the instantiation time.
pub struct EnvironmentDefinitionBuilder<T> {
inner: imp::EnvironmentDefinitionBuilder<T>,
}
impl<T> EnvironmentDefinitionBuilder<T> {
/// Construct a new `EnvironmentDefinitionBuilder`.
pub fn new() -> EnvironmentDefinitionBuilder<T> {
EnvironmentDefinitionBuilder {
inner: imp::EnvironmentDefinitionBuilder::new(),
}
}
/// Register a host function in this environment defintion.
pub fn add_host_func<N1, N2>(&mut self, module: N1, field: N2, f: HostFuncType<T>)
where
N1: Into<Vec<u8>>,
N2: Into<Vec<u8>>,
{
self.inner.add_host_func(module, field, f);
}
/// Register a memory in this environment definition.
pub fn add_memory<N1, N2>(&mut self, module: N1, field: N2, mem: Memory)
where
N1: Into<Vec<u8>>,
N2: Into<Vec<u8>>,
{
self.inner.add_memory(module, field, mem.inner);
}
}
/// Sandboxed instance of a wasm module.
///
/// This instance can be used for invoking exported functions.
pub struct Instance<T> {
inner: imp::Instance<T>,
}
impl<T> Instance<T> {
/// Instantiate a module with the given [`EnvironmentDefinitionBuilder`].
///
/// [`EnvironmentDefinitionBuilder`]: struct.EnvironmentDefinitionBuilder.html
pub fn new(code: &[u8], env_def_builder: &EnvironmentDefinitionBuilder<T>, state: &mut T) -> Result<Instance<T>, Error> {
Ok(Instance {
inner: imp::Instance::new(code, &env_def_builder.inner, state)?,
})
}
/// Invoke an exported function with the given name.
///
/// # Errors
///
/// Returns `Err(Error::Execution)` if:
///
/// - An export function name isn't a proper utf8 byte sequence,
/// - This module doesn't have an exported function with the given name,
/// - If types of the arguments passed to the function doesn't match function signature
/// then trap occurs (as if the exported function was called via call_indirect),
/// - Trap occured at the execution time.
pub fn invoke(
&mut self,
name: &[u8],
args: &[TypedValue],
state: &mut T,
) -> Result<ReturnValue, Error> {
self.inner.invoke(name, args, state)
}
}
+309
View File
@@ -0,0 +1,309 @@
// Copyright 2018 Parity Technologies (UK) Ltd.
// This file is part of Substrate.
// Substrate is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Substrate is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
extern crate wasmi;
use rstd::collections::btree_map::BTreeMap;
use rstd::fmt;
use self::wasmi::{Externals, FuncInstance, FuncRef, GlobalDescriptor, GlobalRef, ImportResolver,
MemoryDescriptor, MemoryInstance, MemoryRef, Module, ModuleInstance, ModuleRef,
RuntimeArgs, RuntimeValue, Signature, TableDescriptor, TableRef, Trap, TrapKind};
use self::wasmi::memory_units::Pages;
use super::{Error, TypedValue, ReturnValue, HostFuncType, HostError};
#[derive(Clone)]
pub struct Memory {
memref: MemoryRef,
}
impl Memory {
pub fn new(initial: u32, maximum: Option<u32>) -> Result<Memory, Error> {
Ok(Memory {
memref: MemoryInstance::alloc(
Pages(initial as usize),
maximum.map(|m| Pages(m as usize)),
).map_err(|_| Error::Module)?,
})
}
pub fn get(&self, ptr: u32, buf: &mut [u8]) -> Result<(), Error> {
self.memref.get_into(ptr, buf).map_err(|_| Error::OutOfBounds)?;
Ok(())
}
pub fn set(&self, ptr: u32, value: &[u8]) -> Result<(), Error> {
self.memref.set(ptr, value).map_err(|_| Error::OutOfBounds)?;
Ok(())
}
}
struct HostFuncIndex(usize);
struct DefinedHostFunctions<T> {
funcs: Vec<HostFuncType<T>>,
}
impl<T> Clone for DefinedHostFunctions<T> {
fn clone(&self) -> DefinedHostFunctions<T> {
DefinedHostFunctions {
funcs: self.funcs.clone(),
}
}
}
impl<T> DefinedHostFunctions<T> {
fn new() -> DefinedHostFunctions<T> {
DefinedHostFunctions {
funcs: Vec::new(),
}
}
fn define(&mut self, f: HostFuncType<T>) -> HostFuncIndex {
let idx = self.funcs.len();
self.funcs.push(f);
HostFuncIndex(idx)
}
}
#[derive(Debug)]
struct DummyHostError;
impl fmt::Display for DummyHostError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "DummyHostError")
}
}
impl self::wasmi::HostError for DummyHostError {
}
fn from_runtime_value(v: RuntimeValue) -> TypedValue {
match v {
RuntimeValue::I32(v) => TypedValue::I32(v),
RuntimeValue::I64(v) => TypedValue::I64(v),
RuntimeValue::F32(v) => TypedValue::F32(v.to_bits() as i32),
RuntimeValue::F64(v) => TypedValue::F64(v.to_bits() as i64),
}
}
fn to_runtime_value(v: TypedValue) -> RuntimeValue {
match v {
TypedValue::I32(v) => RuntimeValue::I32(v as i32),
TypedValue::I64(v) => RuntimeValue::I64(v as i64),
TypedValue::F32(v_bits) => RuntimeValue::F32(f32::from_bits(v_bits as u32)),
TypedValue::F64(v_bits) => RuntimeValue::F64(f64::from_bits(v_bits as u64)),
}
}
struct GuestExternals<'a, T: 'a> {
state: &'a mut T,
defined_host_functions: &'a DefinedHostFunctions<T>,
}
impl<'a, T> Externals for GuestExternals<'a, T> {
fn invoke_index(
&mut self,
index: usize,
args: RuntimeArgs,
) -> Result<Option<RuntimeValue>, Trap> {
let args = args.as_ref()
.iter()
.cloned()
.map(from_runtime_value)
.collect::<Vec<_>>();
let result = (self.defined_host_functions.funcs[index])(self.state, &args);
match result {
Ok(value) => Ok(match value {
ReturnValue::Value(v) => Some(to_runtime_value(v)),
ReturnValue::Unit => None,
}),
Err(HostError) => Err(TrapKind::Host(Box::new(DummyHostError)).into()),
}
}
}
enum ExternVal {
HostFunc(HostFuncIndex),
Memory(Memory),
}
pub struct EnvironmentDefinitionBuilder<T> {
map: BTreeMap<(Vec<u8>, Vec<u8>), ExternVal>,
defined_host_functions: DefinedHostFunctions<T>,
}
impl<T> EnvironmentDefinitionBuilder<T> {
pub fn new() -> EnvironmentDefinitionBuilder<T> {
EnvironmentDefinitionBuilder {
map: BTreeMap::new(),
defined_host_functions: DefinedHostFunctions::new(),
}
}
pub fn add_host_func<N1, N2>(&mut self, module: N1, field: N2, f: HostFuncType<T>)
where
N1: Into<Vec<u8>>,
N2: Into<Vec<u8>>,
{
let idx = self.defined_host_functions.define(f);
self.map
.insert((module.into(), field.into()), ExternVal::HostFunc(idx));
}
pub fn add_memory<N1, N2>(&mut self, module: N1, field: N2, mem: Memory)
where
N1: Into<Vec<u8>>,
N2: Into<Vec<u8>>,
{
self.map
.insert((module.into(), field.into()), ExternVal::Memory(mem));
}
}
impl<T> ImportResolver for EnvironmentDefinitionBuilder<T> {
fn resolve_func(
&self,
module_name: &str,
field_name: &str,
signature: &Signature,
) -> Result<FuncRef, wasmi::Error> {
let key = (
module_name.as_bytes().to_owned(),
field_name.as_bytes().to_owned(),
);
let externval = self.map.get(&key).ok_or_else(|| {
wasmi::Error::Instantiation(format!("Export {}:{} not found", module_name, field_name))
})?;
let host_func_idx = match *externval {
ExternVal::HostFunc(ref idx) => idx,
_ => {
return Err(wasmi::Error::Instantiation(format!(
"Export {}:{} is not a host func",
module_name, field_name
)))
}
};
Ok(FuncInstance::alloc_host(signature.clone(), host_func_idx.0))
}
fn resolve_global(
&self,
_module_name: &str,
_field_name: &str,
_global_type: &GlobalDescriptor,
) -> Result<GlobalRef, wasmi::Error> {
// TODO: Implement sandboxed globals.
unimplemented!()
}
fn resolve_memory(
&self,
module_name: &str,
field_name: &str,
_memory_type: &MemoryDescriptor,
) -> Result<MemoryRef, wasmi::Error> {
let key = (
module_name.as_bytes().to_owned(),
field_name.as_bytes().to_owned(),
);
let externval = self.map.get(&key).ok_or_else(|| {
wasmi::Error::Instantiation(format!("Export {}:{} not found", module_name, field_name))
})?;
let memory = match *externval {
ExternVal::Memory(ref m) => m,
_ => {
return Err(wasmi::Error::Instantiation(format!(
"Export {}:{} is not a memory",
module_name, field_name
)))
}
};
Ok(memory.memref.clone())
}
fn resolve_table(
&self,
_module_name: &str,
_field_name: &str,
_table_type: &TableDescriptor,
) -> Result<TableRef, wasmi::Error> {
// TODO: Implement sandboxed tables.
unimplemented!()
}
}
pub struct Instance<T> {
instance: ModuleRef,
defined_host_functions: DefinedHostFunctions<T>,
_marker: ::std::marker::PhantomData<T>,
}
impl<T> Instance<T> {
pub fn new(code: &[u8], env_def_builder: &EnvironmentDefinitionBuilder<T>, state: &mut T) -> Result<Instance<T>, Error> {
let module = Module::from_buffer(code).map_err(|_| Error::Module)?;
let not_started_instance = ModuleInstance::new(&module, env_def_builder)
.map_err(|_| Error::Module)?;
let defined_host_functions = env_def_builder.defined_host_functions.clone();
let instance = {
let mut externals = GuestExternals {
state,
defined_host_functions: &defined_host_functions,
};
let instance = not_started_instance.run_start(&mut externals).map_err(|_| Error::Module)?;
instance
};
Ok(Instance {
instance,
defined_host_functions,
_marker: ::std::marker::PhantomData::<T>,
})
}
pub fn invoke(
&mut self,
name: &[u8],
args: &[TypedValue],
state: &mut T,
) -> Result<ReturnValue, Error> {
if args.len() > 0 {
// TODO: Convert args into `RuntimeValue` and use it.
unimplemented!();
}
let name = ::std::str::from_utf8(name).map_err(|_| Error::Execution)?;
let mut externals = GuestExternals {
state,
defined_host_functions: &self.defined_host_functions,
};
let result = self.instance
.invoke_export(&name, &[], &mut externals);
match result {
Ok(None) => Ok(ReturnValue::Unit),
Ok(_val) => {
// TODO: Convert result value into `TypedValue` and return it.
unimplemented!();
}
Err(_err) => Err(Error::Execution),
}
}
}
+267
View File
@@ -0,0 +1,267 @@
// Copyright 2018 Parity Technologies (UK) Ltd.
// This file is part of Substrate.
// Substrate is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Substrate is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
use rstd::prelude::*;
use rstd::{slice, marker, mem};
use codec::Slicable;
use primitives::sandbox as sandbox_primitives;
use super::{Error, TypedValue, ReturnValue, HostFuncType};
mod ffi {
use rstd::mem;
use super::HostFuncType;
/// Index into the default table that points to a `HostFuncType`.
pub type HostFuncIndex = usize;
/// Coerce `HostFuncIndex` to a callable host function pointer.
///
/// # Safety
///
/// This function should be only called with a `HostFuncIndex` that was previously registered
/// in the environment defintion. Typically this should only
/// be called with an argument received in `dispatch_thunk`.
pub unsafe fn coerce_host_index_to_func<T>(idx: HostFuncIndex) -> HostFuncType<T> {
// We need to ensure that sizes of a callable function pointer and host function index is
// indeed equal.
// We can't use `static_assertions` create because it makes compiler panic, fallback to runtime assert.
// const_assert!(mem::size_of::<HostFuncIndex>() == mem::size_of::<HostFuncType<T>>(),);
assert!(mem::size_of::<HostFuncIndex>() == mem::size_of::<HostFuncType<T>>());
mem::transmute::<HostFuncIndex, HostFuncType<T>>(idx)
}
extern "C" {
pub fn ext_sandbox_instantiate(
dispatch_thunk: extern "C" fn(
serialized_args_ptr: *const u8,
serialized_args_len: usize,
state: usize,
f: HostFuncIndex,
) -> u64,
wasm_ptr: *const u8,
wasm_len: usize,
imports_ptr: *const u8,
imports_len: usize,
state: usize,
) -> u32;
pub fn ext_sandbox_invoke(
instance_idx: u32,
export_ptr: *const u8,
export_len: usize,
state: usize,
) -> u32;
pub fn ext_sandbox_memory_new(initial: u32, maximum: u32) -> u32;
pub fn ext_sandbox_memory_get(
memory_idx: u32,
offset: u32,
buf_ptr: *mut u8,
buf_len: usize,
) -> u32;
pub fn ext_sandbox_memory_set(
memory_idx: u32,
offset: u32,
val_ptr: *const u8,
val_len: usize,
) -> u32;
// TODO: ext_instance_teardown
// TODO: ext_memory_teardown
}
}
#[derive(Clone)]
pub struct Memory {
memory_idx: u32,
}
impl Memory {
pub fn new(initial: u32, maximum: Option<u32>) -> Result<Memory, Error> {
let result = unsafe {
let maximum = if let Some(maximum) = maximum {
maximum
} else {
sandbox_primitives::MEM_UNLIMITED
};
ffi::ext_sandbox_memory_new(initial, maximum)
};
match result {
sandbox_primitives::ERR_MODULE => Err(Error::Module),
memory_idx => Ok(Memory { memory_idx }),
}
}
pub fn get(&self, offset: u32, buf: &mut [u8]) -> Result<(), Error> {
let result = unsafe { ffi::ext_sandbox_memory_get(self.memory_idx, offset, buf.as_mut_ptr(), buf.len()) };
match result {
sandbox_primitives::ERR_OK => Ok(()),
sandbox_primitives::ERR_OUT_OF_BOUNDS => Err(Error::OutOfBounds),
_ => unreachable!(),
}
}
pub fn set(&self, offset: u32, val: &[u8]) -> Result<(), Error> {
let result = unsafe { ffi::ext_sandbox_memory_set(self.memory_idx, offset, val.as_ptr(), val.len()) };
match result {
sandbox_primitives::ERR_OK => Ok(()),
sandbox_primitives::ERR_OUT_OF_BOUNDS => Err(Error::OutOfBounds),
_ => unreachable!(),
}
}
}
pub struct EnvironmentDefinitionBuilder<T> {
env_def: sandbox_primitives::EnvironmentDefinition,
_marker: marker::PhantomData<T>,
}
impl<T> EnvironmentDefinitionBuilder<T> {
pub fn new() -> EnvironmentDefinitionBuilder<T> {
EnvironmentDefinitionBuilder {
env_def: sandbox_primitives::EnvironmentDefinition {
entries: Vec::new(),
},
_marker: marker::PhantomData::<T>,
}
}
fn add_entry<N1, N2>(
&mut self,
module: N1,
field: N2,
extern_entity: sandbox_primitives::ExternEntity,
) where
N1: Into<Vec<u8>>,
N2: Into<Vec<u8>>,
{
let entry = sandbox_primitives::Entry {
module_name: module.into(),
field_name: field.into(),
entity: extern_entity,
};
self.env_def.entries.push(entry);
}
pub fn add_host_func<N1, N2>(&mut self, module: N1, field: N2, f: HostFuncType<T>)
where
N1: Into<Vec<u8>>,
N2: Into<Vec<u8>>,
{
let f = sandbox_primitives::ExternEntity::Function(f as u32);
self.add_entry(module, field, f);
}
pub fn add_memory<N1, N2>(&mut self, module: N1, field: N2, mem: Memory)
where
N1: Into<Vec<u8>>,
N2: Into<Vec<u8>>,
{
let mem = sandbox_primitives::ExternEntity::Memory(mem.memory_idx as u32);
self.add_entry(module, field, mem);
}
}
pub struct Instance<T> {
instance_idx: u32,
_marker: marker::PhantomData<T>,
}
/// The primary responsibility of this thunk is to deserialize arguments and
/// call the original function, specified by the index.
extern "C" fn dispatch_thunk<T>(
serialized_args_ptr: *const u8,
serialized_args_len: usize,
state: usize,
f: ffi::HostFuncIndex,
) -> u64 {
let serialized_args = unsafe {
if serialized_args_len == 0 {
&[]
} else {
slice::from_raw_parts(serialized_args_ptr, serialized_args_len)
}
};
let args = Vec::<TypedValue>::decode(&mut &serialized_args[..]).expect(
"serialized args should be provided by the runtime;
correctly serialized data should be deserializable;
qed",
);
unsafe {
// This should be safe since `coerce_host_index_to_func` is called with an argument
// received in an `dispatch_thunk` implementation, so `f` should point
// on a valid host function.
let f = ffi::coerce_host_index_to_func(f);
// This should be safe since mutable reference to T is passed upon the invocation.
let state = &mut *(state as *mut T);
// Pass control flow to the designated function.
let result = f(state, &args).encode();
// Leak the result vector and return the pointer to return data.
let result_ptr = result.as_ptr() as u64;
let result_len = result.len() as u64;
mem::forget(result);
(result_ptr << 32) | result_len
}
}
impl<T> Instance<T> {
pub fn new(code: &[u8], env_def_builder: &EnvironmentDefinitionBuilder<T>, state: &mut T) -> Result<Instance<T>, Error> {
let serialized_env_def: Vec<u8> = env_def_builder.env_def.encode();
let result = unsafe {
// It's very important to instantiate thunk with the right type.
let dispatch_thunk = dispatch_thunk::<T>;
ffi::ext_sandbox_instantiate(
dispatch_thunk,
code.as_ptr(),
code.len(),
serialized_env_def.as_ptr(),
serialized_env_def.len(),
state as *const T as usize,
)
};
let instance_idx = match result {
sandbox_primitives::ERR_MODULE => return Err(Error::Module),
instance_idx => instance_idx,
};
Ok(Instance {
instance_idx,
_marker: marker::PhantomData::<T>,
})
}
pub fn invoke(
&mut self,
name: &[u8],
_args: &[TypedValue],
state: &mut T,
) -> Result<ReturnValue, Error> {
// TODO: Serialize arguments and pass them thru.
let result =
unsafe { ffi::ext_sandbox_invoke(self.instance_idx, name.as_ptr(), name.len(), state as *const T as usize) };
match result {
sandbox_primitives::ERR_OK => {
// TODO: Fetch the result of the execution.
Ok(ReturnValue::Unit)
}
sandbox_primitives::ERR_EXECUTION => Err(Error::Execution),
_ => unreachable!(),
}
}
}