1use crate::{PluginError, PluginMetadata, PluginOutput};
4use anyhow::Result;
5use rma_common::{Finding, Language};
6use wasmtime::{Caller, Engine, Instance, Linker, Store};
7
8pub struct HostState {
10 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), 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
39pub fn create_linker(engine: &Engine) -> Result<Linker<HostState>> {
41 let mut linker = Linker::new(engine);
42
43 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 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 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 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 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
123pub fn get_plugin_metadata(
125 store: &mut Store<HostState>,
126 instance: &Instance,
127) -> Result<PluginMetadata, PluginError> {
128 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 get_metadata
135 .call(&mut *store, ())
136 .map_err(|e| PluginError::ExecutionError(format!("get_metadata failed: {}", e)))?;
137
138 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
146pub fn call_analyze(
148 store: &mut Store<HostState>,
149 instance: &Instance,
150 source: &str,
151 language: Language,
152) -> Result<Vec<Finding>> {
153 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 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 let output = store.data().get_output();
178 let plugin_output: PluginOutput = serde_json::from_slice(output)?;
179
180 let findings = plugin_output
182 .findings
183 .into_iter()
184 .map(|pf| pf.into())
185 .collect();
186
187 Ok(findings)
188}