// SPDX-License-Identifier: AGPL-3.0-only
//! A real Rust guest built with wit-bindgen for wasm32 against the published
//! WIT runs in the host: the interface is usable by guest authors, not only
//! by hand-written WAT. Heimdall is a defensive sandbox.
mod common;
use common::*;
use heimdall_wasm::{codes, RunLimits, Verdict};
use std::path::Path;
use std::sync::OnceLock;
use std::time::Duration;
/// Build the guest once (wasm32-unknown-unknown, release) and componentize it.
fn rust_guest() -> &'static [u8] {
static BYTES: OnceLock<Vec<u8>> = OnceLock::new();
BYTES.get_or_init(|| {
let manifest = Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/rust-guest/Cargo.toml");
let target = Path::new(env!("CARGO_TARGET_TMPDIR")).join("rust-guest");
let cargo = std::env::var("CARGO").unwrap_or_else(|_| "cargo".into());
let st = std::process::Command::new(cargo)
.args([
"build",
"--release",
"--target",
"wasm32-unknown-unknown",
"--manifest-path",
])
.arg(&manifest)
.arg("--target-dir")
.arg(&target)
.env_remove("RUSTFLAGS")
.env_remove("CARGO_ENCODED_RUSTFLAGS")
.status()
.expect("cargo could not be started");
assert!(st.success(), "building the Rust test guest failed");
let core =
std::fs::read(target.join("wasm32-unknown-unknown/release/heimdall_test_guest.wasm"))
.unwrap();
wit_component::ComponentEncoder::default()
.module(&core)
.unwrap()
.validate(true)
.encode()
.unwrap()
})
}
#[test]
fn a_rust_guest_accepts_and_emits_its_files() {
let kit = Kit::new("rust-good");
let g = kit.host().load(&kit.place("rust", rust_guest())).unwrap();
let o = g.run(
b"hello",
¶ms(&[("b", "2"), ("a", "1")]),
&RunLimits::default(),
);
assert_eq!(
o.verdict,
Verdict::Accepted {
note: "accepted 5 bytes".into()
}
);
let names: Vec<&str> = o.files.iter().map(|f| f.name.as_str()).collect();
assert_eq!(names, ["echo.bin", "params.txt"]);
assert_eq!(o.files[0].bytes, b"hello");
assert_eq!(o.files[1].bytes, b"a=1\nb=2");
}
#[test]
fn a_rust_guest_refusal_is_passed_on() {
let kit = Kit::new("rust-refuse");
let g = kit.host().load(&kit.place("rust", rust_guest())).unwrap();
assert_eq!(
g.run(b"Tx", &none(), &RunLimits::default()).verdict,
Verdict::Refused {
code: codes::GUEST_REFUSED,
reason: "tampered input".into()
}
);
}
#[test]
fn a_rust_guest_panic_is_a_trap_not_a_host_crash() {
let kit = Kit::new("rust-panic");
let g = kit.host().load(&kit.place("rust", rust_guest())).unwrap();
assert_trapped(&g.run(b"M", &none(), &RunLimits::default()), "unreachable");
}
#[test]
fn a_rust_guest_loop_is_stopped_by_fuel_and_by_the_clock() {
let kit = Kit::new("rust-loop");
let g = kit.host().load(&kit.place("rust", rust_guest())).unwrap();
assert_trapped(
&g.run(
b"L",
&none(),
&RunLimits {
fuel: 10_000_000,
..RunLimits::default()
},
),
"out of fuel",
);
let clock = RunLimits {
fuel: u64::MAX,
timeout: Duration::from_millis(200),
..RunLimits::default()
};
assert_trapped(&g.run(b"L", &none(), &clock), "wall-clock timeout");
}
#[test]
fn a_rust_guest_imports_nothing_but_emit_file() {
// Loading proves it (anything else is refused at link); this names the claim.
let kit = Kit::new("rust-imports");
assert!(kit.host().load(&kit.place("rust", rust_guest())).is_ok());
}