use std::collections::HashMap;
use std::sync::Arc;
use parking_lot::RwLock;
use wasmtime::{Caller, Linker, Memory};
use super::runtime::PluginError;
#[derive(Clone, Default)]
pub struct KvBackend {
inner: Arc<RwLock<HashMap<String, HashMap<Vec<u8>, Vec<u8>>>>>,
}
impl KvBackend {
pub fn new() -> Self {
Self::default()
}
pub fn get(&self, plugin: &str, key: &[u8]) -> Option<Vec<u8>> {
let g = self.inner.read();
g.get(plugin).and_then(|m| m.get(key).cloned())
}
pub fn set(&self, plugin: &str, key: Vec<u8>, value: Vec<u8>) {
let mut g = self.inner.write();
g.entry(plugin.to_string())
.or_default()
.insert(key, value);
}
pub fn delete(&self, plugin: &str, key: &[u8]) {
let mut g = self.inner.write();
if let Some(m) = g.get_mut(plugin) {
m.remove(key);
}
}
pub fn len(&self, plugin: &str) -> usize {
self.inner
.read()
.get(plugin)
.map(|m| m.len())
.unwrap_or(0)
}
}
pub struct StoreCtx {
pub plugin_name: String,
pub kv: KvBackend,
}
pub fn register_kv_imports(linker: &mut Linker<StoreCtx>) -> Result<(), PluginError> {
linker
.func_wrap(
"env",
"kv_get",
|mut caller: Caller<'_, StoreCtx>, key_ptr: i32, key_len: i32, val_out_ptr: i32, val_max_len: i32| -> i32 {
let memory = match get_memory(&mut caller) {
Some(m) => m,
None => return -1,
};
let key = match read_bytes(&memory, &caller, key_ptr, key_len) {
Some(b) => b,
None => return -1,
};
let plugin_name = caller.data().plugin_name.clone();
let kv = caller.data().kv.clone();
let value = match kv.get(&plugin_name, &key) {
Some(v) => v,
None => return -1,
};
if (value.len() as i32) > val_max_len {
return -2;
}
if write_bytes(&memory, &mut caller, val_out_ptr, &value).is_err() {
return -1;
}
value.len() as i32
},
)
.map_err(|e| PluginError::RuntimeError(format!("link kv_get: {}", e)))?;
linker
.func_wrap(
"env",
"kv_set",
|mut caller: Caller<'_, StoreCtx>, key_ptr: i32, key_len: i32, val_ptr: i32, val_len: i32| -> i32 {
let memory = match get_memory(&mut caller) {
Some(m) => m,
None => return -1,
};
let key = match read_bytes(&memory, &caller, key_ptr, key_len) {
Some(b) => b,
None => return -1,
};
let val = match read_bytes(&memory, &caller, val_ptr, val_len) {
Some(b) => b,
None => return -1,
};
let plugin_name = caller.data().plugin_name.clone();
let kv = caller.data().kv.clone();
kv.set(&plugin_name, key, val);
0
},
)
.map_err(|e| PluginError::RuntimeError(format!("link kv_set: {}", e)))?;
linker
.func_wrap(
"env",
"kv_delete",
|mut caller: Caller<'_, StoreCtx>, key_ptr: i32, key_len: i32| -> i32 {
let memory = match get_memory(&mut caller) {
Some(m) => m,
None => return -1,
};
let key = match read_bytes(&memory, &caller, key_ptr, key_len) {
Some(b) => b,
None => return -1,
};
let plugin_name = caller.data().plugin_name.clone();
let kv = caller.data().kv.clone();
kv.delete(&plugin_name, &key);
0
},
)
.map_err(|e| PluginError::RuntimeError(format!("link kv_delete: {}", e)))?;
Ok(())
}
pub fn register_crypto_imports(linker: &mut Linker<StoreCtx>) -> Result<(), PluginError> {
use sha2::{Digest, Sha256};
linker
.func_wrap(
"env",
"sha256_hex",
|mut caller: Caller<'_, StoreCtx>, in_ptr: i32, in_len: i32, out_ptr: i32| -> i32 {
let memory = match get_memory(&mut caller) {
Some(m) => m,
None => return -1,
};
let input = match read_bytes(&memory, &caller, in_ptr, in_len) {
Some(b) => b,
None => return -1,
};
let digest = Sha256::digest(&input);
let mut hex = [0u8; 64];
const HEX: &[u8; 16] = b"0123456789abcdef";
for (i, b) in digest.iter().enumerate() {
hex[i * 2] = HEX[(b >> 4) as usize];
hex[i * 2 + 1] = HEX[(b & 0x0f) as usize];
}
if write_bytes(&memory, &mut caller, out_ptr, &hex).is_err() {
return -1;
}
64
},
)
.map_err(|e| PluginError::RuntimeError(format!("link sha256_hex: {}", e)))?;
Ok(())
}
fn get_memory(caller: &mut Caller<'_, StoreCtx>) -> Option<Memory> {
caller.get_export("memory").and_then(|e| e.into_memory())
}
fn read_bytes(memory: &Memory, caller: &Caller<'_, StoreCtx>, ptr: i32, len: i32) -> Option<Vec<u8>> {
if len < 0 {
return None;
}
let start = ptr as usize;
let end = start.checked_add(len as usize)?;
let data = memory.data(caller);
data.get(start..end).map(|s| s.to_vec())
}
fn write_bytes(
memory: &Memory,
caller: &mut Caller<'_, StoreCtx>,
ptr: i32,
bytes: &[u8],
) -> Result<(), ()> {
let start = ptr as usize;
let end = start.checked_add(bytes.len()).ok_or(())?;
let data = memory.data_mut(caller);
let slot = data.get_mut(start..end).ok_or(())?;
slot.copy_from_slice(bytes);
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn kv_namespaced_per_plugin() {
let kv = KvBackend::new();
kv.set("plugin-a", b"k".to_vec(), b"v1".to_vec());
kv.set("plugin-b", b"k".to_vec(), b"v2".to_vec());
assert_eq!(kv.get("plugin-a", b"k"), Some(b"v1".to_vec()));
assert_eq!(kv.get("plugin-b", b"k"), Some(b"v2".to_vec()));
assert_eq!(kv.get("plugin-c", b"k"), None);
}
#[test]
fn kv_overwrite_is_idempotent() {
let kv = KvBackend::new();
kv.set("p", b"k".to_vec(), b"v1".to_vec());
kv.set("p", b"k".to_vec(), b"v2".to_vec());
assert_eq!(kv.get("p", b"k"), Some(b"v2".to_vec()));
assert_eq!(kv.len("p"), 1);
}
#[test]
fn kv_delete_idempotent_on_missing() {
let kv = KvBackend::new();
kv.delete("p", b"never-set");
kv.set("p", b"k".to_vec(), b"v".to_vec());
kv.delete("p", b"k");
assert_eq!(kv.get("p", b"k"), None);
}
}