Skip to main content

rma_plugins/
host.rs

1//! Host functions exposed to WASM plugins
2
3use crate::{PluginError, PluginMetadata, PluginOutput};
4use anyhow::Result;
5use rma_common::{Finding, Language};
6use wasmtime::{Caller, Engine, Instance, Linker, Store};
7
8/// State held by the host for plugin execution
9pub struct HostState {
10    /// Memory for passing data to/from plugins
11    input_buffer: Vec<u8>,
12    output_buffer: Vec<u8>,
13}
14
15impl HostState {
16    pub fn new() -> Self {
17        Self {
18            input_buffer: Vec::with_capacity(1024 * 1024), // 1MB
19            output_buffer: Vec::with_capacity(1024 * 1024),
20        }
21    }
22
23    pub fn set_input(&mut self, data: &[u8]) {
24        self.input_buffer.clear();
25        self.input_buffer.extend_from_slice(data);
26    }
27
28    pub fn get_output(&self) -> &[u8] {
29        &self.output_buffer
30    }
31}
32
33impl Default for HostState {
34    fn default() -> Self {
35        Self::new()
36    }
37}
38
39/// Create a linker with host functions for plugins
40pub fn create_linker(engine: &Engine) -> Result<Linker<HostState>> {
41    let mut linker = Linker::new(engine);
42
43    // Host function: get input length
44    linker.func_wrap(
45        "env",
46        "rma_get_input_len",
47        |caller: Caller<'_, HostState>| -> i32 { caller.data().input_buffer.len() as i32 },
48    )?;
49
50    // Host function: read input into plugin memory
51    linker.func_wrap(
52        "env",
53        "rma_read_input",
54        |mut caller: Caller<'_, HostState>, ptr: i32, len: i32| -> i32 {
55            let memory = match caller.get_export("memory") {
56                Some(wasmtime::Extern::Memory(mem)) => mem,
57                _ => return -1,
58            };
59
60            // Copy input buffer to avoid borrow conflict
61            let input_len = caller.data().input_buffer.len();
62            let len = (len as usize).min(input_len);
63            let input_data: Vec<u8> = caller.data().input_buffer[..len].to_vec();
64
65            if memory
66                .write(&mut caller, ptr as usize, &input_data)
67                .is_err()
68            {
69                return -1;
70            }
71
72            len as i32
73        },
74    )?;
75
76    // Host function: write output from plugin memory
77    linker.func_wrap(
78        "env",
79        "rma_write_output",
80        |mut caller: Caller<'_, HostState>, ptr: i32, len: i32| -> i32 {
81            let memory = match caller.get_export("memory") {
82                Some(wasmtime::Extern::Memory(mem)) => mem,
83                _ => return -1,
84            };
85
86            let mut buffer = vec![0u8; len as usize];
87            if memory.read(&caller, ptr as usize, &mut buffer).is_err() {
88                return -1;
89            }
90
91            caller.data_mut().output_buffer = buffer;
92            0
93        },
94    )?;
95
96    // Host function: log from plugin
97    linker.func_wrap(
98        "env",
99        "rma_log",
100        |mut caller: Caller<'_, HostState>, level: i32, ptr: i32, len: i32| {
101            let memory = match caller.get_export("memory") {
102                Some(wasmtime::Extern::Memory(mem)) => mem,
103                _ => return,
104            };
105
106            let mut buffer = vec![0u8; len as usize];
107            if memory.read(&caller, ptr as usize, &mut buffer).is_ok()
108                && let Ok(msg) = String::from_utf8(buffer)
109            {
110                match level {
111                    0 => tracing::error!(target: "plugin", "{}", msg),
112                    1 => tracing::warn!(target: "plugin", "{}", msg),
113                    2 => tracing::info!(target: "plugin", "{}", msg),
114                    _ => tracing::debug!(target: "plugin", "{}", msg),
115                }
116            }
117        },
118    )?;
119
120    Ok(linker)
121}
122
123/// Get plugin metadata by calling the plugin's get_metadata export
124pub fn get_plugin_metadata(
125    store: &mut Store<HostState>,
126    instance: &Instance,
127) -> Result<PluginMetadata, PluginError> {
128    // Try to get the metadata export
129    let get_metadata = instance
130        .get_typed_func::<(), i32>(&mut *store, "rma_get_metadata")
131        .map_err(|_| PluginError::InterfaceError("Missing rma_get_metadata export".into()))?;
132
133    // Call to populate output buffer
134    get_metadata
135        .call(&mut *store, ())
136        .map_err(|e| PluginError::ExecutionError(format!("get_metadata failed: {}", e)))?;
137
138    // Parse metadata from output buffer
139    let output = store.data().get_output();
140    let metadata: PluginMetadata = serde_json::from_slice(output)
141        .map_err(|e| PluginError::InterfaceError(format!("Invalid metadata JSON: {}", e)))?;
142
143    Ok(metadata)
144}
145
146/// Call the plugin's analyze function
147pub fn call_analyze(
148    store: &mut Store<HostState>,
149    instance: &Instance,
150    source: &str,
151    language: Language,
152) -> Result<Vec<Finding>> {
153    // Prepare input
154    let input = crate::PluginInput {
155        source: source.to_string(),
156        file_path: String::new(),
157        language: language.to_string(),
158    };
159    let input_json = serde_json::to_vec(&input)?;
160    store.data_mut().set_input(&input_json);
161
162    // Get and call analyze function
163    let analyze = instance
164        .get_typed_func::<(), i32>(&mut *store, "rma_analyze")
165        .map_err(|_| anyhow::anyhow!("Missing rma_analyze export"))?;
166
167    let result = analyze.call(&mut *store, ())?;
168
169    if result != 0 {
170        return Err(anyhow::anyhow!(
171            "Plugin analysis returned error code: {}",
172            result
173        ));
174    }
175
176    // Parse output
177    let output = store.data().get_output();
178    let plugin_output: PluginOutput = serde_json::from_slice(output)?;
179
180    // Convert to Finding
181    let findings = plugin_output
182        .findings
183        .into_iter()
184        .map(|pf| pf.into())
185        .collect();
186
187    Ok(findings)
188}