Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/jamsocket/wasmbox
Turns running Rust code into a serializable data structure.
https://github.com/jamsocket/wasmbox
wasm webassembly
Last synced: 2 months ago
JSON representation
Turns running Rust code into a serializable data structure.
- Host: GitHub
- URL: https://github.com/jamsocket/wasmbox
- Owner: jamsocket
- Created: 2022-05-25T01:15:22.000Z (over 2 years ago)
- Default Branch: main
- Last Pushed: 2022-11-08T12:20:37.000Z (about 2 years ago)
- Last Synced: 2024-10-29T15:40:59.465Z (3 months ago)
- Topics: wasm, webassembly
- Language: Rust
- Homepage:
- Size: 3.72 MB
- Stars: 23
- Watchers: 3
- Forks: 2
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# WasmBox
[![GitHub Repo stars](https://img.shields.io/github/stars/drifting-in-space/wasmbox?style=social)](https://github.com/drifting-in-space/wasmbox)
[![crates.io](https://img.shields.io/crates/v/wasmbox.svg)](https://crates.io/crates/wasmbox)
[![docs.rs](https://img.shields.io/badge/docs-release-brightgreen)](https://docs.rs/wasmbox/)
[![Rust](https://github.com/drifting-in-space/wasmbox/actions/workflows/rust.yml/badge.svg)](https://github.com/drifting-in-space/wasmbox/actions/workflows/rust.yml)WasmBox turns running Rust code into a serializable data structure.
It does this by compiling it to WebAssembly and running it in a sandbox. To snapshot the running code, it serializes the sandbox's linear memory, which contains the entire heap of the program.
**WasmBox is new and experimental.** Before relying on it in production code, feel free to open an issue and we can discuss 🙂.
## Interface
WasmBox has two components: the host environment and the guest module. The host environment is the program that interacts with the WasmBox from the outside. The guest module is the program that runs *inside* the WasmBox. The guest module is a separate Rust compiler artifact, compiled to target `wasm32-wasi`.
The two components interact through bidirectional, typed communication provided by WasmBox. Both synchronous and asynchronous interfaces are provided for developing the guest module.
To use the asynchronous interface, create a function with the signature `async fn run(ctx: WasmBoxContext`, and decorate it with the `#[wasmbox]` annotation.
The following example implements a trivial stateful WasmBox guest module which stores counter state internally. It waits for input from the host environment. When it recieves the inputs `"up"` or `"down"` from the host environment, it modifies the counter state internally and publishes it back to the host environment.
```rust,no_run
use wasmbox::prelude::*;#[wasmbox]
async fn run(ctx: WasmBoxContext) {
let mut c = 0;
loop {
let message = ctx.next().await;
match message.as_ref() {
"up" => c += 1,
"down" => c -= 1,
_ => continue,
}
ctx.send(format!("value={}", c));
}
}
```The `` attributes of `WasmBoxContext` are the types of data passed into and out of the WasmBox, respectively. `ctx.next()` returns a value of the first type, and `ctx.send()` expects a value of the second type. If you are writing your own host environment, you can use any [(de)serializable](https://serde.rs/) type here, **as long as the pair of types is the same on both the host environment and the guest module**. Since the guest module is loaded in dynamically at runtime, this can't be enforced by the compiler, so it's up to you to ensure.
The demonstration host environment provided by `wasmbox-cli` only supports ``, so that's what we use here.
#### Compiling guest modules
Guest modules should have the following in their `Cargo.toml`:
```text
[lib]
crate-type = ["cdylib", "rlib"]
```They should be compiled with the target `wasm32-wasi` like so:
```text
cargo build --release --target=wasm32-wasi
```You might have to install the `wasm32-wasi` target (e.g. using `rustup`).
Look for a `.wasm` file under `target/wasm32-wasi/release`.
### Host environment
The host environment is always the same (synchronous) interface, regardless of whether the guest module is using the asynchronous or synchronous interface.
Constructing a host environment (`WasmBoxHost`) requires two things: the module to load, and a callback to use for receiving messages from the guest module. The module can either be passed in as a `.wasm` file, or as a pre-compiled module.
See `wasmbox-cli` for an example of implementing a host environment.
```rust,no_run
use wasmbox_host::WasmBoxHost;
use anyhow::Result;fn main() -> Result<()> {
let mut mybox: WasmBoxHost = WasmBoxHost::from_wasm_file("path/to/some/module.wasm",
|st: String| println!("guest module says: {}", st))?;// Send some messages into the box:
mybox.message(&"The guest module will receive this message.".into());
mybox.message(&"And this one.".into());// Turn the state into a serializable object.
let state = mybox.snapshot_state()?;
// Or, serialize directly to disk:
mybox.snapshot_to_file("snapshot.bin")?;// We can interact more with the box:
mybox.message(&"Pretend this message has a side-effect on the box's state.".into());// And then restore the state, undoing the last side-effect.
mybox.restore_snapshot(&state)?;// Or, restore directly from disk:
mybox.restore_snapshot_from_file("snapshot.bin")?;Ok(())
}
```### Synchronous Guest Interface
Rather than writing an async function to implement a guest, you can implement a `trait` and use the `#[wasmbox_sync]` macro.
Each WasmBox is constructed with a call to `init`. Each message from the host is passed through a call to the trait's `message` function. To pass messages back to the host, a boxed `callback` function is provided in `init`.
Both the `init` function and `message` functions are allowed to call the callback, and may do so multiple times.
In order to call the callback from `message`, you can store it in the type itself.```rust,no_run
use wasmbox::prelude::*;#[wasmbox_sync]
struct Counter {
count: u32,
callback: Box,
}impl WasmBox for Counter {
type Input = String;
type Output = String;fn init(callback: Box) -> Self
where
Self: Sized,
{
Counter { count: 0, callback }
}fn message(&mut self, input: Self::Input) {
match input.as_ref() {
"up" => self.count += 1,
"down" => self.count -= 1,
_ => return
}(self.callback)(format!("value={}", self.count));
}
}
```## CLI Tool
A CLI tool is provided for loading and interacting with guest modules. It relays messages to and from the guest module over `stdin` and `stdout`. It only supports guest modules that have the types ``, since `stdin` and `stdout` deal with string data.
Each line is treated as a separate message and relayed to the guest module, except for two special commands. `!!snapshot` takes a snapshot of the guest module and saves it to disk, printing the name of the resulting file. `!!restore ` restores the guest module state from one of these snapshots.
## Safety
This module uses `unsafe` a lot, in particular within the WASM code. The host also uses unsafe when loading a pre-compiled module, which can lead to arbitrary code execution. Pre-compiled modules are safe **only** if you can be sure that they were created by wasmtime/cranelift.
## Limitations
- It's likely to be slower than native code, because it uses WebAssembly.
- To provide a deterministic environment, access to anything outside the sandbox is blocked. The system clock is mocked to create a deterministic (but monotonically increasing) clock. Random entropy is not random, but comes from a seeded pseudo-random number generator.
- To avoid unnecessary repetition, the state does not include the program module itself; it is up to the caller to ensure that the same WASM module that created a snapshot is running when the snapshot is restored.
- Currently, the `WasmBoxHost` environment owns *everything* about the WebAssembly environment, including things which could be shared between instances. This is inefficient if you want to run many instances of the same module, for instance.
- Probably lots of other things.