#[allow(dead_code)]
#[rustfmt::skip] #[path = "wasm/generated/v1/guest_bindings.rs"]
mod bindings;
std::cfg_select! {
all(target_family = "wasm", feature = "wasm-component-hash-experiment") => {
#[path = "guest_component_hash.rs"]
mod component_hash;
use component_hash::Blake3Hasher as HashBackend;
}
_ => { use bindings::Blake3Hasher as HashBackend; }
}
std::cfg_select! {
all(target_family = "wasm", feature = "wasm-component-compiler-experiment") => {
#[path = "guest_component_compiler.rs"]
mod component_compiler;
use component_compiler::{CompilerGrant as CompilerGrantBackend, CompilerProcess as CompilerProcessBackend};
}
_ => { use bindings::{CompilerGrant as CompilerGrantBackend, CompilerProcess as CompilerProcessBackend}; }
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum OperationError {
Rejected,
Cancelled,
Closed,
Failed,
TimedOut,
}
impl From<bindings::OperationError> for OperationError {
fn from(error: bindings::OperationError) -> Self {
match error {
bindings::OperationError::Rejected => Self::Rejected,
bindings::OperationError::Cancelled => Self::Cancelled,
bindings::OperationError::Closed => Self::Closed,
bindings::OperationError::Failed => Self::Failed,
bindings::OperationError::TimedOut => Self::TimedOut,
}
}
}
impl From<OperationError> for bindings::OperationError {
fn from(error: OperationError) -> Self {
match error {
OperationError::Rejected => Self::Rejected,
OperationError::Cancelled => Self::Cancelled,
OperationError::Closed => Self::Closed,
OperationError::Failed => Self::Failed,
OperationError::TimedOut => Self::TimedOut,
}
}
}
pub fn run<T>(
future: impl std::future::Future<Output = Result<T, OperationError>>,
) -> Result<T, OperationError> {
bindings::run(async { future.await.map_err(bindings::OperationError::from) })
.map_err(OperationError::from)
}
pub async fn sleep(milliseconds: u32) -> Result<(), OperationError> {
bindings::clock_sleep(milliseconds)?.wait().await?;
Ok(())
}
pub struct Blake3Hasher {
inner: HashBackend,
}
impl Blake3Hasher {
pub async fn new() -> Result<Self, OperationError> {
Ok(Self {
inner: HashBackend::new().await?,
})
}
pub async fn update(&mut self, bytes: &[u8]) -> Result<(), OperationError> {
self.inner.update(bytes).await?;
Ok(())
}
pub async fn finalize(self) -> Result<[u8; 32], OperationError> {
Ok(self.inner.finalize().await?)
}
}
pub struct CompilerGrant {
inner: CompilerGrantBackend,
}
pub struct CompilerProcess {
inner: CompilerProcessBackend,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum CompilerCacheStatus {
Hit,
Miss,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum CompilerOutputEvent {
Stdout(usize),
Stderr(usize),
StdoutEof,
StderrEof,
StdoutAbandoned,
StderrAbandoned,
StdoutError,
StderrError,
Exhausted,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct CompilerExit {
pub code: Option<i32>,
pub success: bool,
}
impl CompilerGrant {
pub fn granted() -> Result<Option<Self>, OperationError> {
Ok(CompilerGrantBackend::granted()?.map(|inner| Self { inner }))
}
pub async fn spawn(self) -> Result<CompilerProcess, OperationError> {
Ok(CompilerProcess {
inner: self.inner.spawn().await?,
})
}
pub fn cache_status(&self, key: &[u8; 32]) -> Result<CompilerCacheStatus, OperationError> {
Ok(if self.inner.cache_status(key)? {
CompilerCacheStatus::Hit
} else {
CompilerCacheStatus::Miss
})
}
}
impl CompilerProcess {
pub async fn read_output(
&mut self,
destination: &mut [u8],
) -> Result<CompilerOutputEvent, OperationError> {
use bindings::CompilerOutputEvent as Event;
Ok(match self.inner.read_output(destination).await? {
Event::Stdout(count) => CompilerOutputEvent::Stdout(count),
Event::Stderr(count) => CompilerOutputEvent::Stderr(count),
Event::StdoutEof => CompilerOutputEvent::StdoutEof,
Event::StderrEof => CompilerOutputEvent::StderrEof,
Event::StdoutAbandoned => CompilerOutputEvent::StdoutAbandoned,
Event::StderrAbandoned => CompilerOutputEvent::StderrAbandoned,
Event::StdoutError => CompilerOutputEvent::StdoutError,
Event::StderrError => CompilerOutputEvent::StderrError,
Event::Exhausted => CompilerOutputEvent::Exhausted,
})
}
pub async fn wait(&self) -> Result<CompilerExit, OperationError> {
let exit = self.inner.wait().await?;
Ok(CompilerExit {
code: exit.code,
success: exit.success,
})
}
pub async fn close(self) -> Result<(), OperationError> {
Ok(self.inner.close().await?)
}
}
pub struct EncryptedArchive {
inner: bindings::EncryptedArchive,
}
impl EncryptedArchive {
pub fn granted() -> Result<Option<Self>, OperationError> {
Ok(bindings::EncryptedArchive::granted()?.map(|inner| Self { inner }))
}
pub async fn read_header(&self, destination: &mut [u8]) -> Result<usize, OperationError> {
self.inner
.read_header(destination)
.map_err(OperationError::from)
}
pub async fn authenticate(
self,
nonce: [u8; 12],
) -> Result<AuthenticatedArchive, OperationError> {
let inner = self.inner.authenticate(&nonce)?.wait().await?;
Ok(AuthenticatedArchive { inner })
}
}
pub struct AuthenticatedArchive {
inner: bindings::AuthenticatedArchive,
}
impl AuthenticatedArchive {
pub async fn next_entry(&mut self) -> Result<Option<ArchiveEntry>, OperationError> {
let Some(inner) = self.inner.next_entry()?.wait().await? else {
return Ok(None);
};
let mut record = [0; 4108];
let count = inner.metadata(&mut record)?;
let bytes = u64::from_le_bytes(record[..8].try_into().map_err(|_| OperationError::Failed)?);
let length = u32::from_le_bytes(
record[8..12]
.try_into()
.map_err(|_| OperationError::Failed)?,
) as usize;
if length != count - 12 {
return Err(OperationError::Failed);
}
let name = std::str::from_utf8(&record[12..count])
.map_err(|_| OperationError::Failed)?
.to_owned();
Ok(Some(ArchiveEntry {
_inner: inner,
name,
bytes,
}))
}
pub async fn close(self) -> Result<(), OperationError> {
self.inner.close().map_err(OperationError::from)
}
}
pub struct ArchiveEntry {
_inner: bindings::ArchiveEntry,
name: String,
bytes: u64,
}
impl ArchiveEntry {
pub async fn open(self) -> Result<Blob, OperationError> {
Ok(Blob {
inner: self._inner.open()?.wait().await?,
})
}
pub fn name(&self) -> &str {
&self.name
}
pub fn uncompressed_bytes(&self) -> u64 {
self.bytes
}
}
pub struct Blob {
inner: bindings::BlobHandle,
}
impl Blob {
pub async fn create() -> Result<Self, OperationError> {
let token = bindings::BlobHandle::create()?.wait().await?;
if token == 0 {
return Err(OperationError::Failed);
}
Ok(Self {
inner: bindings::BlobHandle::from_create_payload(token),
})
}
pub fn write_chunk(&self, bytes: &[u8]) -> Result<PendingWrite, OperationError> {
Ok(PendingWrite {
inner: self.inner.write_chunk(bytes)?,
terminal: false,
})
}
pub fn write(&mut self, bytes: &[u8]) -> Result<usize, OperationError> {
self.inner.write(bytes).map_err(OperationError::from)
}
pub fn read(&mut self, destination: &mut [u8]) -> Result<usize, OperationError> {
self.inner.read(destination).map_err(OperationError::from)
}
pub fn read_chunk(&self, maximum_bytes: u32) -> Result<PendingRead, OperationError> {
Ok(PendingRead {
inner: self.inner.read_chunk(maximum_bytes)?,
terminal: false,
})
}
pub async fn seal(&self) -> Result<(), OperationError> {
self.inner.seal()?.wait().await?;
Ok(())
}
pub async fn close(self) -> Result<(), OperationError> {
self.inner.close()?;
Ok(())
}
}
pub struct PendingWrite {
inner: bindings::OperationFuture,
terminal: bool,
}
impl PendingWrite {
pub fn poll(&mut self) -> Result<Option<()>, OperationError> {
let result = self
.inner
.poll()
.map(|value| value.map(|_| ()))
.map_err(OperationError::from);
self.terminal |= matches!(result, Ok(Some(_)));
result
}
pub fn cancel(&self) {
self.inner.cancel();
}
pub fn yield_now(&self) -> Result<(), OperationError> {
self.inner.yield_now().map_err(OperationError::from)
}
pub async fn wait(mut self) -> Result<(), OperationError> {
loop {
if self.poll()?.is_some() {
return Ok(());
}
self.yield_now()?;
}
}
}
impl Drop for PendingWrite {
fn drop(&mut self) {
if !self.terminal {
self.inner.abandon_transfer();
}
}
}
pub struct PendingRead {
inner: bindings::BlobReadFuture,
terminal: bool,
}
impl PendingRead {
pub fn poll_into(&mut self, destination: &mut [u8]) -> Result<Option<usize>, OperationError> {
let result = self
.inner
.poll_into(destination)
.map_err(OperationError::from);
self.terminal |= matches!(result, Ok(Some(_)));
result
}
pub fn cancel(&self) {
self.inner.cancel();
}
pub fn yield_now(&self) -> Result<(), OperationError> {
self.inner.yield_now().map_err(OperationError::from)
}
pub async fn read_into(mut self, destination: &mut [u8]) -> Result<usize, OperationError> {
loop {
if let Some(count) = self.poll_into(destination)? {
return Ok(count);
}
self.yield_now()?;
}
}
}
impl Drop for PendingRead {
fn drop(&mut self) {
if !self.terminal {
self.inner.abandon_transfer();
}
}
}
pub struct OutputFile {
inner: bindings::OutputFile,
}
impl OutputFile {
pub fn granted() -> Result<Option<Self>, OperationError> {
Ok(bindings::OutputFile::granted()?.map(|inner| Self { inner }))
}
pub async fn write_blob(&self, blob: &Blob) -> Result<(), OperationError> {
self.inner.write_blob(&blob.inner)?.wait().await?;
Ok(())
}
}
pub struct WebviewUrl {
inner: bindings::WebviewUrl,
}
impl WebviewUrl {
pub fn granted() -> Result<Option<Self>, OperationError> {
Ok(bindings::WebviewUrl::granted()?.map(|inner| Self { inner }))
}
pub async fn open(&self) -> Result<Webview, OperationError> {
Ok(Webview {
inner: self.inner.open().await?,
})
}
}
pub struct Webview {
inner: bindings::Webview,
}
impl Webview {
pub async fn wait_until_loaded(&self) -> Result<(), OperationError> {
self.inner
.wait_until_loaded()
.await
.map_err(OperationError::from)
}
pub async fn capture_visible_png(&self) -> Result<Blob, OperationError> {
Ok(Blob {
inner: self.inner.capture_visible_png().await?,
})
}
pub async fn close(self) -> Result<(), OperationError> {
self.inner.close().await.map_err(OperationError::from)
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::{
atomic::{AtomicBool, AtomicI64, AtomicUsize, Ordering},
Mutex,
};
static COMPLETED: AtomicBool = AtomicBool::new(false);
static CANCELLATIONS: AtomicUsize = AtomicUsize::new(0);
static ABANDONMENTS: AtomicUsize = AtomicUsize::new(0);
static BLOB_RELEASES: AtomicUsize = AtomicUsize::new(0);
static LAST_BLOB_RELEASE: AtomicI64 = AtomicI64::new(-1);
static REJECT_BLOB_TRANSFERS: AtomicBool = AtomicBool::new(false);
static BLOB_RELEASE_LOCK: Mutex<()> = Mutex::new(());
#[export_name = "operation_submit"]
extern "C" fn submit(kind: i32, _: i64, reserved: i64) -> i64 {
if kind == 18 {
assert_eq!(reserved, 0);
ABANDONMENTS.fetch_add(1, Ordering::SeqCst);
return 1;
}
17
}
#[export_name = "operation_poll"]
extern "C" fn poll(_: i64) -> i64 {
i64::from(COMPLETED.load(Ordering::SeqCst))
}
#[export_name = "operation_cancel"]
extern "C" fn cancel(_: i64) -> i32 {
CANCELLATIONS.fetch_add(1, Ordering::SeqCst);
1
}
#[export_name = "operation_yield"]
extern "C" fn suspend(_: i64) -> i32 {
1
}
#[export_name = "kernel_yield"]
extern "C" fn kernel_yield() {}
#[export_name = "stream_close"]
extern "C" fn stream_close(blob: i64) -> i32 {
LAST_BLOB_RELEASE.store(blob, Ordering::SeqCst);
BLOB_RELEASES.fetch_add(1, Ordering::SeqCst);
0
}
#[export_name = "stream_read"]
extern "C" fn stream_read(_: i64, _: i32, length: i32) -> i32 {
if REJECT_BLOB_TRANSFERS.load(Ordering::SeqCst) {
-1
} else {
length
}
}
#[export_name = "stream_write"]
extern "C" fn stream_write(_: i64, _: i32, length: i32) -> i32 {
if REJECT_BLOB_TRANSFERS.load(Ordering::SeqCst) {
-1
} else {
length
}
}
#[export_name = "resource_release_encrypted_archive"]
extern "C" fn resource_release_encrypted_archive(_: i64) -> i32 {
0
}
#[export_name = "resource_release_authenticated_archive"]
extern "C" fn resource_release_authenticated_archive(_: i64) -> i32 {
0
}
#[export_name = "resource_release_archive_entry"]
extern "C" fn resource_release_archive_entry(_: i64) -> i32 {
0
}
#[test]
fn generated_blob_release_is_exactly_once_for_drop_and_explicit_close() {
let _release_lock = BLOB_RELEASE_LOCK.lock().unwrap();
BLOB_RELEASES.store(0, Ordering::SeqCst);
LAST_BLOB_RELEASE.store(-1, Ordering::SeqCst);
drop(bindings::BlobHandle::from_create_payload(41));
assert_eq!(BLOB_RELEASES.load(Ordering::SeqCst), 1);
assert_eq!(LAST_BLOB_RELEASE.load(Ordering::SeqCst), 41);
run(async {
Blob {
inner: bindings::BlobHandle::from_create_payload(99),
}
.close()
.await
})
.unwrap();
assert_eq!(BLOB_RELEASES.load(Ordering::SeqCst), 2);
assert_eq!(LAST_BLOB_RELEASE.load(Ordering::SeqCst), 99);
}
#[test]
fn blob_direct_stream_controls_stay_behind_the_guest_facade() {
let _release_lock = BLOB_RELEASE_LOCK.lock().unwrap();
let mut blob = Blob {
inner: bindings::BlobHandle::from_create_payload(7),
};
assert_eq!(blob.write(b"ping"), Ok(4));
let mut destination = [0; 2];
assert_eq!(blob.read(&mut destination), Ok(2));
}
#[test]
fn blob_stream_rejection_is_a_semantic_guest_error() {
let _release_lock = BLOB_RELEASE_LOCK.lock().unwrap();
REJECT_BLOB_TRANSFERS.store(true, Ordering::SeqCst);
let mut blob = Blob {
inner: bindings::BlobHandle::from_create_payload(7),
};
assert_eq!(blob.write(b"ping"), Err(OperationError::Rejected));
let mut destination = [0; 2];
assert_eq!(blob.read(&mut destination), Err(OperationError::Rejected));
REJECT_BLOB_TRANSFERS.store(false, Ordering::SeqCst);
}
#[test]
fn dropping_transfers_abandons_but_preserves_explicit_cancellation() {
let _release_lock = BLOB_RELEASE_LOCK.lock().unwrap();
CANCELLATIONS.store(0, Ordering::SeqCst);
ABANDONMENTS.store(0, Ordering::SeqCst);
COMPLETED.store(false, Ordering::SeqCst);
let mut write = PendingWrite {
inner: bindings::clock_sleep(1).unwrap(),
terminal: false,
};
assert_eq!(write.poll(), Ok(None));
write.cancel();
assert_eq!(CANCELLATIONS.load(Ordering::SeqCst), 1);
drop(write);
assert_eq!(ABANDONMENTS.load(Ordering::SeqCst), 1);
let read = PendingRead {
inner: bindings::BlobHandle::from_create_payload(1)
.read_chunk(1)
.unwrap(),
terminal: false,
};
drop(read);
assert_eq!(ABANDONMENTS.load(Ordering::SeqCst), 2);
COMPLETED.store(true, Ordering::SeqCst);
let mut write = PendingWrite {
inner: bindings::clock_sleep(1).unwrap(),
terminal: false,
};
assert_eq!(write.poll(), Ok(Some(())));
drop(write);
assert_eq!(ABANDONMENTS.load(Ordering::SeqCst), 2);
assert_eq!(CANCELLATIONS.load(Ordering::SeqCst), 1);
}
#[test]
fn terminal_errors_survive_the_private_adapter() {
for error in [
OperationError::Rejected,
OperationError::Cancelled,
OperationError::Closed,
OperationError::Failed,
OperationError::TimedOut,
] {
assert_eq!(
OperationError::from(bindings::OperationError::from(error)),
error
);
assert_eq!(run::<()>(async { Err(error) }), Err(error));
}
assert_eq!(run(async { Ok(42) }), Ok(42));
}
}