Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/monetize

crates/monetize-product-guest/src/guest_export.rs

//! **The wasm exports, generated per module** — the same reason holger's
//! `export_meter!` exists. The macro takes the module's grant list and emits
//! **only the imports that list unlocks**: a guest declaring `grants = [grpc]`
//! contains no `host_http` import at all, so the manifest it publishes and the
//! import list a host reads out of its `.wasm` are the same statement made
//! twice — which is exactly what the host's gate B checks.
//!
//! Everything emitted is `#[cfg(target_arch = "wasm32")]`; on any other target
//! the crate compiles to nothing, so a guest crate is an ordinary workspace
//! member `cargo test --workspace` covers.

/// One grant's `env` imports. Item position, so each expansion is its own
/// `extern` block.
#[doc(hidden)]
#[macro_export]
macro_rules! __product_grant_extern {
    (http) => {
        #[link(wasm_import_module = "env")]
        extern "C" {
            fn host_http(ptr: u32, len: u32) -> u64;
        }
    };
    (grpc) => {
        #[link(wasm_import_module = "env")]
        extern "C" {
            fn host_grpc_unary(ptr: u32, len: u32) -> u64;
            fn host_grpc_stream_open(ptr: u32, len: u32) -> u64;
            fn host_grpc_stream_send(ptr: u32, len: u32) -> u64;
            fn host_grpc_stream_close(ptr: u32, len: u32) -> u64;
            fn host_grpc_stream_cancel(ptr: u32, len: u32) -> u64;
            fn host_grpc_server_stream(ptr: u32, len: u32) -> u64;
            fn host_grpc_stream_recv(ptr: u32, len: u32) -> u64;
        }
    };
    (endpoint) => {
        #[link(wasm_import_module = "env")]
        extern "C" {
            fn host_endpoint(ptr: u32, len: u32) -> u64;
        }
    };
    (state) => {
        #[link(wasm_import_module = "env")]
        extern "C" {
            fn host_state_get(ptr: u32, len: u32) -> u64;
            fn host_state_put(ptr: u32, len: u32) -> u64;
        }
    };
    (hmac) => {
        #[link(wasm_import_module = "env")]
        extern "C" {
            fn host_hmac_verify(ptr: u32, len: u32) -> u64;
        }
    };
}

/// One grant's [`crate::Host`] methods. Anything not listed falls through to
/// the trait default, which is a **named refusal**.
#[doc(hidden)]
#[macro_export]
macro_rules! __product_grant_method {
    (http) => {
        fn http(&self, req: &$crate::wire::HttpRequest) -> ::std::result::Result<$crate::wire::HttpResponse, ::std::string::String> {
            let head = $crate::wire::HttpHead { method: req.method.clone(), url: req.url.clone(), headers: req.headers.clone() };
            let f = $crate::wire::frame::encode_json(&head, &req.body);
            let packed = unsafe { host_http(f.as_ptr() as u32, f.len() as u32) };
            let raw = unpack(packed).ok_or_else(|| ::std::string::String::from("host_http failed on the host side"))?;
            let (h, body) = $crate::wire::frame::host_answer(&raw)?;
            ::std::result::Result::Ok($crate::wire::HttpResponse { status: h.status.unwrap_or(0), headers: h.headers, body: body.to_vec() })
        }
    };
    (grpc) => {
        fn grpc_unary_at(&self, path: &str, request: &[u8], headers: &[(::std::string::String, ::std::string::String)], endpoint: ::std::option::Option<&str>) -> ::std::result::Result<::std::vec::Vec<u8>, $crate::wire::GrpcError> {
            let head = $crate::wire::GrpcHead { path: path.into(), headers: headers.to_vec(), endpoint: endpoint.map(::std::string::String::from) };
            let f = $crate::wire::frame::encode_json(&head, request);
            let packed = unsafe { host_grpc_unary(f.as_ptr() as u32, f.len() as u32) };
            let fail = |m: ::std::string::String| $crate::wire::GrpcError { code: "Unknown".into(), message: m };
            let raw = unpack(packed).ok_or_else(|| fail("host_grpc_unary failed on the host side".into()))?;
            let (h, body): ($crate::wire::HostHead, &[u8]) = $crate::wire::frame::decode_json(&raw).map_err(fail)?;
            if h.ok {
                return ::std::result::Result::Ok(body.to_vec());
            }
            ::std::result::Result::Err(h.grpc.unwrap_or_else(|| fail(h.error.unwrap_or_default())))
        }
        fn grpc_stream_open(&self, path: &str, headers: &[(::std::string::String, ::std::string::String)], expect_secs: u32, endpoint: ::std::option::Option<&str>) -> ::std::result::Result<u64, $crate::wire::GrpcError> {
            let head = $crate::wire::StreamOpen { path: path.into(), headers: headers.to_vec(), expect_secs, endpoint: endpoint.map(::std::string::String::from) };
            let f = $crate::wire::frame::encode_json(&head, &[]);
            let packed = unsafe { host_grpc_stream_open(f.as_ptr() as u32, f.len() as u32) };
            let (h, _) = $crate::__stream_answer(unpack(packed), "host_grpc_stream_open")?;
            h.stream.ok_or_else(|| $crate::wire::GrpcError { code: "Unknown".into(), message: "host_grpc_stream_open answered no handle".into() })
        }
        fn grpc_stream_send(&self, stream: u64, chunk: &[u8]) -> ::std::result::Result<(), $crate::wire::GrpcError> {
            let f = $crate::wire::frame::encode_json(&$crate::wire::StreamRef { stream }, chunk);
            let packed = unsafe { host_grpc_stream_send(f.as_ptr() as u32, f.len() as u32) };
            $crate::__stream_answer(unpack(packed), "host_grpc_stream_send").map(|_| ())
        }
        fn grpc_stream_close(&self, stream: u64) -> ::std::result::Result<::std::vec::Vec<u8>, $crate::wire::GrpcError> {
            let f = $crate::wire::frame::encode_json(&$crate::wire::StreamRef { stream }, &[]);
            let packed = unsafe { host_grpc_stream_close(f.as_ptr() as u32, f.len() as u32) };
            let (_, body) = $crate::__stream_answer(unpack(packed), "host_grpc_stream_close")?;
            ::std::result::Result::Ok(body)
        }
        fn grpc_server_stream(&self, path: &str, headers: &[(::std::string::String, ::std::string::String)], request: &[u8], endpoint: ::std::option::Option<&str>) -> ::std::result::Result<u64, $crate::wire::GrpcError> {
            let head = $crate::wire::StreamOpen { path: path.into(), headers: headers.to_vec(), expect_secs: 0, endpoint: endpoint.map(::std::string::String::from) };
            let f = $crate::wire::frame::encode_json(&head, request);
            let packed = unsafe { host_grpc_server_stream(f.as_ptr() as u32, f.len() as u32) };
            let (h, _) = $crate::__stream_answer(unpack(packed), "host_grpc_server_stream")?;
            h.stream.ok_or_else(|| $crate::wire::GrpcError { code: "Unknown".into(), message: "host_grpc_server_stream answered no handle".into() })
        }
        fn grpc_stream_recv(&self, stream: u64) -> ::std::result::Result<::std::option::Option<::std::vec::Vec<u8>>, $crate::wire::GrpcError> {
            let f = $crate::wire::frame::encode_json(&$crate::wire::StreamRef { stream }, &[]);
            let packed = unsafe { host_grpc_stream_recv(f.as_ptr() as u32, f.len() as u32) };
            let (h, body) = $crate::__stream_answer(unpack(packed), "host_grpc_stream_recv")?;
            if h.end {
                return ::std::result::Result::Ok(::std::option::Option::None);
            }
            ::std::result::Result::Ok(::std::option::Option::Some(body))
        }
        fn grpc_stream_cancel(&self, stream: u64) {
            let f = $crate::wire::frame::encode_json(&$crate::wire::StreamRef { stream }, &[]);
            let _ = unsafe { host_grpc_stream_cancel(f.as_ptr() as u32, f.len() as u32) };
        }
    };
    (state) => {
        fn state_get(&self, key: &str) -> ::std::result::Result<::std::option::Option<::std::vec::Vec<u8>>, ::std::string::String> {
            let f = $crate::wire::frame::encode_json(&$crate::wire::StateKey { key: key.into() }, &[]);
            let packed = unsafe { host_state_get(f.as_ptr() as u32, f.len() as u32) };
            let raw = unpack(packed).ok_or_else(|| ::std::string::String::from("host_state_get failed on the host side"))?;
            let (h, body) = $crate::wire::frame::host_answer(&raw)?;
            if h.end {
                return ::std::result::Result::Ok(::std::option::Option::None);
            }
            ::std::result::Result::Ok(::std::option::Option::Some(body.to_vec()))
        }
        fn state_put(&self, key: &str, value: &[u8]) -> ::std::result::Result<(), ::std::string::String> {
            let f = $crate::wire::frame::encode_json(&$crate::wire::StateKey { key: key.into() }, value);
            let packed = unsafe { host_state_put(f.as_ptr() as u32, f.len() as u32) };
            let raw = unpack(packed).ok_or_else(|| ::std::string::String::from("host_state_put failed on the host side"))?;
            let _ = $crate::wire::frame::host_answer(&raw)?;
            ::std::result::Result::Ok(())
        }
    };
    (hmac) => {
        fn hmac_verify(&self, secret: &str, message: &[u8], candidates_hex: &[::std::string::String]) -> ::std::result::Result<bool, ::std::string::String> {
            let head = $crate::wire::HmacHead { secret: secret.into(), algo: "sha256".into(), candidates_hex: candidates_hex.to_vec() };
            let f = $crate::wire::frame::encode_json(&head, message);
            let packed = unsafe { host_hmac_verify(f.as_ptr() as u32, f.len() as u32) };
            let raw = unpack(packed).ok_or_else(|| ::std::string::String::from("host_hmac_verify failed on the host side"))?;
            let (h, _) = $crate::wire::frame::host_answer(&raw)?;
            match h.value.as_deref() {
                ::std::option::Option::Some("match") => ::std::result::Result::Ok(true),
                ::std::option::Option::Some("mismatch") => ::std::result::Result::Ok(false),
                other => ::std::result::Result::Err(::std::format!("host_hmac_verify answered {other:?}")),
            }
        }
        fn secret_equals(&self, secret: &str, message: &[u8]) -> ::std::result::Result<bool, ::std::string::String> {
            let head = $crate::wire::HmacHead { secret: secret.into(), algo: "eq".into(), candidates_hex: ::std::vec::Vec::new() };
            let f = $crate::wire::frame::encode_json(&head, message);
            let packed = unsafe { host_hmac_verify(f.as_ptr() as u32, f.len() as u32) };
            let raw = unpack(packed).ok_or_else(|| ::std::string::String::from("host_hmac_verify failed on the host side"))?;
            let (h, _) = $crate::wire::frame::host_answer(&raw)?;
            match h.value.as_deref() {
                ::std::option::Option::Some("match") => ::std::result::Result::Ok(true),
                ::std::option::Option::Some("mismatch") => ::std::result::Result::Ok(false),
                other => ::std::result::Result::Err(::std::format!("host_hmac_verify answered {other:?}")),
            }
        }
    };
    (endpoint) => {
        fn endpoint(&self) -> ::std::result::Result<::std::string::String, ::std::string::String> {
            let f = $crate::wire::frame::encode(&[], &[]);
            let packed = unsafe { host_endpoint(f.as_ptr() as u32, f.len() as u32) };
            let raw = unpack(packed).ok_or_else(|| ::std::string::String::from("host_endpoint failed on the host side"))?;
            let (h, _) = $crate::wire::frame::host_answer(&raw)?;
            ::std::result::Result::Ok(h.value.unwrap_or_default())
        }
    };
}

/// Generate the exports for a [`crate::Guest`].
///
/// ```ignore
/// monetize_product_guest::export_product!(holger_monetize_guest::HolgerGuest, grants = []);
/// ```
///
/// The type must implement `Guest` and `Default`. The grant tokens must match
/// what the type's own manifest declares — the host's gate B refuses the
/// module at load if they do not.
#[macro_export]
macro_rules! export_product {
    ($guest:ty, grants = [$($grant:ident),* $(,)?] $(,)?) => {
        $crate::__export_guest!($guest, $crate::Guest, $crate::dispatch, grants = [$($grant),*]);
    };
}

/// **The scaffolding every guest kind shares**: `alloc`/`dealloc`/`result_len`,
/// the host bridge over exactly the granted imports, `plugin_abi`,
/// `plugin_manifest` and `plugin_call` routed to `$dispatch`. A kind (product,
/// vendor, cloud) names its trait and its op switch; nothing else differs, so
/// the ABI cannot drift between kinds. `$trait` must have
/// `fn manifest(&self) -> wire::Manifest`.
#[doc(hidden)]
#[macro_export]
macro_rules! __export_guest {
    ($guest:ty, $trait:path, $dispatch:path, grants = [$($grant:ident),* $(,)?] $(,)?) => {
        $crate::__export_guest!($guest, $trait, $dispatch, sleep = no, grants = [$($grant),*]);
    };
    // `sleep = yes`: the kind imports `host_sleep` (a cloud, which polls a
    // provider). A product and a vendor do not, so a module of theirs still
    // instantiates on a server older than the door.
    ($guest:ty, $trait:path, $dispatch:path, sleep = $sleep:ident, grants = [$($grant:ident),* $(,)?] $(,)?) => {
        #[cfg(target_arch = "wasm32")]
        const _: () = {
            use ::std::cell::{Cell, RefCell};
            use ::std::vec::Vec;

            use $crate::Host;

            ::std::thread_local! {
                static ALLOCS: RefCell<Vec<Vec<u8>>> = const { RefCell::new(Vec::new()) };
                static RESULT_LEN: Cell<u32> = const { Cell::new(0) };
            }

            #[no_mangle]
            pub extern "C" fn alloc(size: u32) -> u32 {
                ALLOCS.with(|a| {
                    let mut buf = ::std::vec![0u8; size as usize];
                    let ptr = buf.as_mut_ptr() as u32;
                    a.borrow_mut().push(buf);
                    ptr
                })
            }

            #[no_mangle]
            pub extern "C" fn dealloc(ptr: u32, len: u32) {
                ALLOCS.with(|a| {
                    let mut v = a.borrow_mut();
                    if let Some(i) = v.iter().position(|b| b.as_ptr() as u32 == ptr && b.len() == len as usize) {
                        v.swap_remove(i);
                    }
                });
            }

            #[no_mangle]
            pub extern "C" fn result_len() -> u32 {
                RESULT_LEN.with(|l| l.get())
            }

            fn ret(bytes: &[u8]) -> u32 {
                let ptr = alloc(bytes.len() as u32);
                // SAFETY: `ptr` was just returned by `alloc`, which reserved
                // exactly `bytes.len()` bytes and keeps the buffer alive.
                unsafe {
                    ::std::ptr::copy_nonoverlapping(bytes.as_ptr(), ptr as *mut u8, bytes.len());
                }
                RESULT_LEN.with(|l| l.set(bytes.len() as u32));
                ptr
            }

            /// # Safety
            /// `ptr`/`len` must be an address previously returned by `alloc`.
            unsafe fn arg<'a>(ptr: u32, len: u32) -> &'a [u8] {
                ::std::slice::from_raw_parts(ptr as *const u8, len as usize)
            }

            /// Unpack a `ptr << 32 | len` result from a host door. `0` means
            /// the host call itself failed — never "absent".
            #[allow(dead_code)]
            fn unpack(packed: u64) -> ::std::option::Option<Vec<u8>> {
                if packed == 0 {
                    return ::std::option::Option::None;
                }
                let ptr = (packed >> 32) as u32;
                let len = (packed & 0xffff_ffff) as usize;
                // SAFETY: the host allocated this through our own `alloc`.
                let out = unsafe { ::std::slice::from_raw_parts(ptr as *const u8, len) }.to_vec();
                dealloc(ptr, len as u32);
                ::std::option::Option::Some(out)
            }

            #[link(wasm_import_module = "env")]
            extern "C" {
                fn host_clock() -> u64;
                fn host_log(ptr: u32, len: u32);
            }

            $crate::__guest_sleep_extern!($sleep);

            $( $crate::__product_grant_extern!($grant); )*

            /// The host, reached through exactly the imports this module's
            /// grants unlock. Everything else falls through to `Host`'s
            /// defaults, which refuse by name.
            struct HostBridge;

            impl Host for HostBridge {
                fn clock_unix_ms(&self) -> u64 {
                    unsafe { host_clock() }
                }
                fn log(&self, line: &str) {
                    unsafe { host_log(line.as_ptr() as u32, line.len() as u32) }
                }
                $crate::__guest_sleep_method!($sleep);
                $( $crate::__product_grant_method!($grant); )*
            }

            /// `'static`, so a kind whose op switch takes `&'static (dyn Host
            /// + Sync)` (a cloud: its provider keeps the host inside its HTTP
            /// client) can be handed the bridge; a `&dyn Host` switch takes it too.
            static BRIDGE: HostBridge = HostBridge;

            fn guest() -> $guest {
                <$guest as ::std::default::Default>::default()
            }

            #[no_mangle]
            pub extern "C" fn plugin_abi() -> u64 {
                let m = <$guest as $trait>::manifest(&guest());
                $crate::abi::pack(m.abi_min, m.abi_max)
            }

            #[no_mangle]
            pub extern "C" fn plugin_manifest() -> u32 {
                ret(&$crate::wire::encode(&<$guest as $trait>::manifest(&guest())))
            }

            /// # Safety
            /// `ptr`/`len` must address a payload the host wrote through `alloc`.
            #[no_mangle]
            pub unsafe extern "C" fn plugin_call(op: u32, ptr: u32, len: u32) -> u64 {
                let payload = if len == 0 { &[][..] } else { unsafe { arg(ptr, len) } };
                let out = $dispatch(&guest(), &BRIDGE, op, payload);
                let out_ptr = ret(&out);
                ((out_ptr as u64) << 32) | (out.len() as u64)
            }
        };
    };
}

/// The `host_sleep` import, for a kind that polls a provider (`yes`), or none
/// (`no`).
#[doc(hidden)]
#[macro_export]
macro_rules! __guest_sleep_extern {
    (yes) => {
        #[link(wasm_import_module = "env")]
        extern "C" {
            fn host_sleep(ms: u64);
        }
    };
    (no) => {};
}

#[doc(hidden)]
#[macro_export]
macro_rules! __guest_sleep_method {
    (yes) => {
        fn sleep_ms(&self, ms: u64) {
            unsafe { host_sleep(ms) }
        }
    };
    (no) => {};
}