cardinal_wasm_plugins/host/
mod.rs

1use crate::context::ExecutionContext;
2use crate::runner::ExecutionPhase;
3use crate::utils::{read_bytes, with_mem_view, write_bytes};
4use crate::SharedExecutionContext;
5use std::collections::HashMap;
6use std::sync::Arc;
7use wasmer::{Exports, Function, FunctionEnv, FunctionEnvMut, Imports, Store};
8
9mod abort;
10pub mod get_header;
11mod get_query_param;
12mod get_req_var;
13mod set_header;
14mod set_req_var;
15mod set_status;
16
17use self::abort::ABORT_IMPORT;
18use self::get_header::GET_HEADER_IMPORT;
19use self::get_query_param::GET_QUERY_PARAM_IMPORT;
20use self::get_req_var::GET_REQ_VAR_IMPORT;
21use self::set_header::SET_HEADER_IMPORT;
22use self::set_req_var::SET_REQ_VAR_IMPORT;
23use self::set_status::SET_STATUS_IMPORT;
24
25pub type HostFunctionBuilder =
26    Arc<dyn Fn(&mut Store, &FunctionEnv<SharedExecutionContext>) -> Function + Send + Sync>;
27
28pub trait HostImport: Send + Sync {
29    fn namespace(&self) -> &str;
30    fn name(&self) -> &str;
31    fn build(&self, store: &mut Store, env: &FunctionEnv<SharedExecutionContext>) -> Function;
32}
33
34pub type HostImportHandle = Arc<dyn HostImport>;
35
36#[derive(Clone)]
37pub struct DynamicHostImport {
38    namespace: String,
39    name: String,
40    builder: HostFunctionBuilder,
41}
42
43impl DynamicHostImport {
44    pub fn new<N, S>(namespace: N, name: S, builder: HostFunctionBuilder) -> Self
45    where
46        N: Into<String>,
47        S: Into<String>,
48    {
49        Self {
50            namespace: namespace.into(),
51            name: name.into(),
52            builder,
53        }
54    }
55}
56
57impl HostImport for DynamicHostImport {
58    fn namespace(&self) -> &str {
59        &self.namespace
60    }
61
62    fn name(&self) -> &str {
63        &self.name
64    }
65
66    fn build(&self, store: &mut Store, env: &FunctionEnv<SharedExecutionContext>) -> Function {
67        (self.builder)(store, env)
68    }
69}
70
71pub fn make_imports(
72    store: &mut Store,
73    env: &FunctionEnv<SharedExecutionContext>,
74    phase: ExecutionPhase,
75    dynamic_imports: &[HostImportHandle],
76) -> Imports {
77    let mut namespaces: HashMap<String, Exports> = HashMap::new();
78
79    for import in builtin_imports(phase) {
80        register_import(&mut namespaces, store, env, *import);
81    }
82
83    for import in dynamic_imports {
84        register_import(&mut namespaces, store, env, import.as_ref());
85    }
86
87    let mut imports = Imports::new();
88    for (namespace, exports) in namespaces {
89        imports.register_namespace(&namespace, exports);
90    }
91    imports
92}
93
94fn register_import(
95    namespaces: &mut HashMap<String, Exports>,
96    store: &mut Store,
97    env: &FunctionEnv<SharedExecutionContext>,
98    import: &dyn HostImport,
99) {
100    let namespace = import.namespace().to_string();
101    let exports = namespaces.entry(namespace.clone()).or_default();
102    let function = import.build(store, env);
103    exports.insert(import.name(), function);
104}
105
106fn builtin_imports(phase: ExecutionPhase) -> &'static [&'static dyn HostImport] {
107    match phase {
108        ExecutionPhase::Inbound => INBOUND_IMPORTS,
109        ExecutionPhase::Outbound => OUTBOUND_IMPORTS,
110    }
111}
112
113static INBOUND_IMPORTS: &[&dyn HostImport] = &[
114    &ABORT_IMPORT,
115    &GET_HEADER_IMPORT,
116    &GET_QUERY_PARAM_IMPORT,
117    &SET_HEADER_IMPORT,
118    &SET_STATUS_IMPORT,
119    &SET_REQ_VAR_IMPORT,
120    &GET_REQ_VAR_IMPORT,
121];
122
123static OUTBOUND_IMPORTS: &[&dyn HostImport] = &[
124    &ABORT_IMPORT,
125    &GET_HEADER_IMPORT,
126    &GET_QUERY_PARAM_IMPORT,
127    &SET_HEADER_IMPORT,
128    &SET_STATUS_IMPORT,
129    &SET_REQ_VAR_IMPORT,
130    &GET_REQ_VAR_IMPORT,
131];
132
133/// Read key from guest memory and write lookup result back into guest memory.
134/// Returns number of bytes written or -1 on failure.
135pub fn read_key_lookup_and_write(
136    ctx: &FunctionEnvMut<SharedExecutionContext>,
137    key_ptr: i32,
138    key_len: i32,
139    out_ptr: i32,
140    out_cap: i32,
141    normalize_key: bool,
142    lookup: impl Fn(&ExecutionContext, &str) -> Option<Vec<u8>>,
143) -> i32 {
144    let view = match with_mem_view(ctx) {
145        Ok(v) => v,
146        Err(_) => return -1,
147    };
148
149    let raw_key = match String::from_utf8(read_bytes(&view, key_ptr, key_len).unwrap_or_default()) {
150        Ok(key) => key,
151        Err(_) => return -1,
152    };
153
154    let key = if normalize_key {
155        raw_key.to_ascii_lowercase()
156    } else {
157        raw_key
158    };
159
160    let guard = ctx.data().read();
161    let bytes = match lookup(&guard, &key) {
162        Some(data) => data,
163        None => return -1,
164    };
165
166    let write_len = bytes.len().min(out_cap as usize);
167    if write_len > 0 && write_bytes(&view, out_ptr, &bytes[..write_len]).is_err() {
168        return -1;
169    }
170
171    write_len as i32
172}