1use std::collections::HashMap;
29use std::sync::Arc;
30
31use parking_lot::RwLock;
32use wasmtime::{Caller, Linker, Memory};
33
34use super::runtime::PluginError;
35
36type KvStore = HashMap<String, HashMap<Vec<u8>, Vec<u8>>>;
38
39#[derive(Clone, Default)]
42pub struct KvBackend {
43 inner: Arc<RwLock<KvStore>>,
44}
45
46impl KvBackend {
47 pub fn new() -> Self {
48 Self::default()
49 }
50
51 pub fn get(&self, plugin: &str, key: &[u8]) -> Option<Vec<u8>> {
53 let g = self.inner.read();
54 g.get(plugin).and_then(|m| m.get(key).cloned())
55 }
56
57 pub fn set(&self, plugin: &str, key: Vec<u8>, value: Vec<u8>) {
59 let mut g = self.inner.write();
60 g.entry(plugin.to_string()).or_default().insert(key, value);
61 }
62
63 pub fn delete(&self, plugin: &str, key: &[u8]) {
65 let mut g = self.inner.write();
66 if let Some(m) = g.get_mut(plugin) {
67 m.remove(key);
68 }
69 }
70
71 pub fn len(&self, plugin: &str) -> usize {
74 self.inner.read().get(plugin).map(|m| m.len()).unwrap_or(0)
75 }
76}
77
78pub struct StoreCtx {
84 pub plugin_name: String,
85 pub kv: KvBackend,
86}
87
88pub fn register_kv_imports(linker: &mut Linker<StoreCtx>) -> Result<(), PluginError> {
91 linker
92 .func_wrap(
93 "env",
94 "kv_get",
95 |mut caller: Caller<'_, StoreCtx>,
96 key_ptr: i32,
97 key_len: i32,
98 val_out_ptr: i32,
99 val_max_len: i32|
100 -> i32 {
101 let memory = match get_memory(&mut caller) {
102 Some(m) => m,
103 None => return -1,
104 };
105 let key = match read_bytes(&memory, &caller, key_ptr, key_len) {
106 Some(b) => b,
107 None => return -1,
108 };
109 let plugin_name = caller.data().plugin_name.clone();
110 let kv = caller.data().kv.clone();
111 let value = match kv.get(&plugin_name, &key) {
112 Some(v) => v,
113 None => return -1,
114 };
115 if (value.len() as i32) > val_max_len {
116 return -2;
117 }
118 if write_bytes(&memory, &mut caller, val_out_ptr, &value).is_err() {
119 return -1;
120 }
121 value.len() as i32
122 },
123 )
124 .map_err(|e| PluginError::RuntimeError(format!("link kv_get: {}", e)))?;
125
126 linker
127 .func_wrap(
128 "env",
129 "kv_set",
130 |mut caller: Caller<'_, StoreCtx>,
131 key_ptr: i32,
132 key_len: i32,
133 val_ptr: i32,
134 val_len: i32|
135 -> i32 {
136 let memory = match get_memory(&mut caller) {
137 Some(m) => m,
138 None => return -1,
139 };
140 let key = match read_bytes(&memory, &caller, key_ptr, key_len) {
141 Some(b) => b,
142 None => return -1,
143 };
144 let val = match read_bytes(&memory, &caller, val_ptr, val_len) {
145 Some(b) => b,
146 None => return -1,
147 };
148 let plugin_name = caller.data().plugin_name.clone();
149 let kv = caller.data().kv.clone();
150 kv.set(&plugin_name, key, val);
151 0
152 },
153 )
154 .map_err(|e| PluginError::RuntimeError(format!("link kv_set: {}", e)))?;
155
156 linker
157 .func_wrap(
158 "env",
159 "kv_delete",
160 |mut caller: Caller<'_, StoreCtx>, key_ptr: i32, key_len: i32| -> i32 {
161 let memory = match get_memory(&mut caller) {
162 Some(m) => m,
163 None => return -1,
164 };
165 let key = match read_bytes(&memory, &caller, key_ptr, key_len) {
166 Some(b) => b,
167 None => return -1,
168 };
169 let plugin_name = caller.data().plugin_name.clone();
170 let kv = caller.data().kv.clone();
171 kv.delete(&plugin_name, &key);
172 0
173 },
174 )
175 .map_err(|e| PluginError::RuntimeError(format!("link kv_delete: {}", e)))?;
176
177 Ok(())
178}
179
180pub fn register_crypto_imports(linker: &mut Linker<StoreCtx>) -> Result<(), PluginError> {
194 use sha2::{Digest, Sha256};
195
196 linker
197 .func_wrap(
198 "env",
199 "sha256_hex",
200 |mut caller: Caller<'_, StoreCtx>, in_ptr: i32, in_len: i32, out_ptr: i32| -> i32 {
201 let memory = match get_memory(&mut caller) {
202 Some(m) => m,
203 None => return -1,
204 };
205 let input = match read_bytes(&memory, &caller, in_ptr, in_len) {
206 Some(b) => b,
207 None => return -1,
208 };
209 let digest = Sha256::digest(&input);
210 let mut hex = [0u8; 64];
213 const HEX: &[u8; 16] = b"0123456789abcdef";
214 for (i, b) in digest.iter().enumerate() {
215 hex[i * 2] = HEX[(b >> 4) as usize];
216 hex[i * 2 + 1] = HEX[(b & 0x0f) as usize];
217 }
218 if write_bytes(&memory, &mut caller, out_ptr, &hex).is_err() {
219 return -1;
220 }
221 64
222 },
223 )
224 .map_err(|e| PluginError::RuntimeError(format!("link sha256_hex: {}", e)))?;
225 Ok(())
226}
227
228fn get_memory(caller: &mut Caller<'_, StoreCtx>) -> Option<Memory> {
229 caller.get_export("memory").and_then(|e| e.into_memory())
230}
231
232fn read_bytes(
233 memory: &Memory,
234 caller: &Caller<'_, StoreCtx>,
235 ptr: i32,
236 len: i32,
237) -> Option<Vec<u8>> {
238 if len < 0 {
239 return None;
240 }
241 let start = ptr as usize;
242 let end = start.checked_add(len as usize)?;
243 let data = memory.data(caller);
244 data.get(start..end).map(|s| s.to_vec())
245}
246
247fn write_bytes(
248 memory: &Memory,
249 caller: &mut Caller<'_, StoreCtx>,
250 ptr: i32,
251 bytes: &[u8],
252) -> Result<(), ()> {
253 let start = ptr as usize;
254 let end = start.checked_add(bytes.len()).ok_or(())?;
255 let data = memory.data_mut(caller);
256 let slot = data.get_mut(start..end).ok_or(())?;
257 slot.copy_from_slice(bytes);
258 Ok(())
259}
260
261#[cfg(test)]
262mod tests {
263 use super::*;
264
265 #[test]
266 fn kv_namespaced_per_plugin() {
267 let kv = KvBackend::new();
268 kv.set("plugin-a", b"k".to_vec(), b"v1".to_vec());
269 kv.set("plugin-b", b"k".to_vec(), b"v2".to_vec());
270 assert_eq!(kv.get("plugin-a", b"k"), Some(b"v1".to_vec()));
271 assert_eq!(kv.get("plugin-b", b"k"), Some(b"v2".to_vec()));
272 assert_eq!(kv.get("plugin-c", b"k"), None);
273 }
274
275 #[test]
276 fn kv_overwrite_is_idempotent() {
277 let kv = KvBackend::new();
278 kv.set("p", b"k".to_vec(), b"v1".to_vec());
279 kv.set("p", b"k".to_vec(), b"v2".to_vec());
280 assert_eq!(kv.get("p", b"k"), Some(b"v2".to_vec()));
281 assert_eq!(kv.len("p"), 1);
282 }
283
284 #[test]
285 fn kv_delete_idempotent_on_missing() {
286 let kv = KvBackend::new();
287 kv.delete("p", b"never-set");
288 kv.set("p", b"k".to_vec(), b"v".to_vec());
289 kv.delete("p", b"k");
290 assert_eq!(kv.get("p", b"k"), None);
291 }
292}