Skip to main content

packc/
component_host_stubs.rs

1#![forbid(unsafe_code)]
2
3use anyhow::Result;
4use wasmtime::component::Linker;
5use wasmtime_wasi::p2::add_to_linker_sync;
6use wasmtime_wasi::{ResourceTable, WasiCtx, WasiCtxBuilder, WasiCtxView, WasiView};
7use wasmtime_wasi_http::WasiHttpCtx;
8use wasmtime_wasi_http::p2::{
9    HttpError, HttpResult, WasiHttpCtxView, WasiHttpHooks, WasiHttpView,
10    add_only_http_to_linker_sync,
11};
12use wasmtime_wasi_http::p2::{body, types};
13
14pub struct DescribeHostState {
15    table: ResourceTable,
16    wasi: WasiCtx,
17    wasi_http: WasiHttpCtx,
18    http_hooks: DescribeHttpHooks,
19}
20
21#[derive(Default)]
22struct DescribeHttpHooks;
23
24impl Default for DescribeHostState {
25    fn default() -> Self {
26        // Describe-only paths should be offline-safe: no inherited env/args,
27        // no preopened directories, and no ambient stdio requirements.
28        let mut wasi = WasiCtxBuilder::new();
29        Self {
30            table: ResourceTable::new(),
31            wasi: wasi.build(),
32            wasi_http: WasiHttpCtx::new(),
33            http_hooks: DescribeHttpHooks,
34        }
35    }
36}
37
38impl WasiHttpHooks for DescribeHttpHooks {
39    fn send_request(
40        &mut self,
41        _request: hyper::Request<body::HyperOutgoingBody>,
42        _config: types::OutgoingRequestConfig,
43    ) -> HttpResult<types::HostFutureIncomingResponse> {
44        Err(HttpError::trap(wasmtime::Error::msg(
45            "wasi:http outgoing requests are unavailable in describe-only host stubs",
46        )))
47    }
48}
49
50impl WasiView for DescribeHostState {
51    fn ctx(&mut self) -> WasiCtxView<'_> {
52        WasiCtxView {
53            table: &mut self.table,
54            ctx: &mut self.wasi,
55        }
56    }
57}
58
59impl WasiHttpView for DescribeHostState {
60    fn http(&mut self) -> WasiHttpCtxView<'_> {
61        WasiHttpCtxView {
62            ctx: &mut self.wasi_http,
63            table: &mut self.table,
64            hooks: &mut self.http_hooks,
65        }
66    }
67}
68
69pub fn add_describe_host_imports(linker: &mut Linker<DescribeHostState>) -> Result<()> {
70    // Some WASI helper registrars may re-export overlapping interface names
71    // (for example `wasi:io/*`) across preview2, TLS, and HTTP worlds.
72    linker.allow_shadowing(true);
73
74    add_to_linker_sync(linker)
75        .map_err(|err| anyhow::anyhow!("register wasi preview2 describe host stubs: {err}"))?;
76
77    add_only_http_to_linker_sync(linker)
78        .map_err(|err| anyhow::anyhow!("register wasi http describe host stubs: {err}"))?;
79
80    // NOTE: greentic host interfaces (state-store, secrets-store, http-client,
81    // interfaces-types, TLS, etc.) are NOT registered here. They are handled by
82    // `stub_remaining_imports` which uses `define_unknown_imports_as_traps` to
83    // provide trap stubs for any component imports not already in the linker.
84    // This avoids having to maintain an exhaustive list of every greentic
85    // interface version a component might import.
86    Ok(())
87}
88
89/// Stub any component imports not already registered in the linker as traps.
90///
91/// This must be called AFTER `add_describe_host_imports` so that real WASI
92/// implementations take priority. All remaining imports (greentic host
93/// interfaces like secrets-store, http-client, etc.) become traps — safe
94/// because the describe-only code path never invokes them.
95pub fn stub_remaining_imports(
96    linker: &mut Linker<DescribeHostState>,
97    component: &wasmtime::component::Component,
98) -> Result<()> {
99    linker
100        .define_unknown_imports_as_traps(component)
101        .map_err(|err| anyhow::anyhow!("stub remaining component imports: {err}"))
102}
103
104#[cfg(test)]
105mod tests {
106    use super::*;
107
108    #[test]
109    fn default_host_state_exposes_wasi_and_http_views() {
110        let mut state = DescribeHostState::default();
111
112        let ctx_view = state.ctx();
113        let _ = ctx_view.table;
114        let _ = ctx_view.ctx;
115
116        let http_view = state.http();
117        let _ = http_view.table;
118        let _ = http_view.ctx;
119    }
120
121    #[test]
122    fn describe_host_imports_register_without_error() {
123        let engine = wasmtime::Engine::default();
124        let mut linker = Linker::new(&engine);
125        add_describe_host_imports(&mut linker).expect("host imports should register");
126    }
127}