use std::collections::HashMap;
use std::sync::Mutex;
use std::sync::OnceLock;
use std::os::raw::c_char;
use std::ffi::{CStr, CString};
use std::sync::atomic::{AtomicUsize, Ordering};
pub use serde_json::Value;
static ACTIVE_ALLOCS: AtomicUsize = AtomicUsize::new(0);
static TOTAL_ALLOCATED_BYTES: AtomicUsize = AtomicUsize::new(0);
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct MemoryStats {
pub allocated_objects: usize,
pub total_bytes_pinned: u64,
}
pub fn get_memory_stats() -> MemoryStats {
MemoryStats {
allocated_objects: ACTIVE_ALLOCS.load(Ordering::SeqCst),
total_bytes_pinned: TOTAL_ALLOCATED_BYTES.load(Ordering::SeqCst) as u64,
}
}
pub unsafe fn pin_memory(data: &[u8]) -> *mut u8 {
if data.is_empty() {
return std::ptr::null_mut();
}
let size = data.len();
let layout = std::alloc::Layout::from_size_align(size, 1).unwrap();
let ptr = std::alloc::alloc(layout);
if !ptr.is_null() {
std::ptr::copy_nonoverlapping(data.as_ptr(), ptr, size);
ACTIVE_ALLOCS.fetch_add(1, Ordering::SeqCst);
TOTAL_ALLOCATED_BYTES.fetch_add(size, Ordering::SeqCst);
}
ptr
}
pub unsafe fn unpin_memory(ptr: *mut u8, size: usize) {
if !ptr.is_null() {
let layout = std::alloc::Layout::from_size_align(size, 1).unwrap();
std::alloc::dealloc(ptr, layout);
ACTIVE_ALLOCS.fetch_sub(1, Ordering::SeqCst);
TOTAL_ALLOCATED_BYTES.fetch_sub(size, Ordering::SeqCst);
}
}
pub type NativeFn = fn(HashMap<String, Value>) -> Result<Value, String>;
static REGISTRY: OnceLock<Mutex<HashMap<String, NativeFn>>> = OnceLock::new();
fn get_registry() -> &'static Mutex<HashMap<String, NativeFn>> {
REGISTRY.get_or_init(|| Mutex::new(HashMap::new()))
}
pub fn register(name: &str, func: NativeFn) {
if let Ok(mut reg) = get_registry().lock() {
reg.insert(name.to_string(), func);
}
}
#[no_mangle]
pub unsafe extern "C" fn void_malloc(size: usize) -> *mut u8 {
let layout = std::alloc::Layout::from_size_align(size, 1).unwrap();
let ptr = std::alloc::alloc(layout);
if !ptr.is_null() {
ACTIVE_ALLOCS.fetch_add(1, Ordering::SeqCst);
TOTAL_ALLOCATED_BYTES.fetch_add(size, Ordering::SeqCst);
}
ptr
}
#[no_mangle]
pub unsafe extern "C" fn void_free(ptr: *mut u8, size: usize) {
if !ptr.is_null() {
let layout = std::alloc::Layout::from_size_align(size, 1).unwrap();
std::alloc::dealloc(ptr, layout);
ACTIVE_ALLOCS.fetch_sub(1, Ordering::SeqCst);
TOTAL_ALLOCATED_BYTES.fetch_sub(size, Ordering::SeqCst);
}
}
extern "C" {
fn void_init();
}
static INIT: std::sync::Once = std::sync::Once::new();
pub fn ensure_registered() {
INIT.call_once(|| unsafe {
void_init();
});
}
#[no_mangle]
pub extern "C" fn void_invoke(input: *const c_char) -> *mut c_char {
ensure_registered();
if input.is_null() {
return to_c_string_err("null input");
}
let c_str = unsafe { CStr::from_ptr(input) };
let str_slice = match c_str.to_str() {
Ok(s) => s,
Err(e) => return to_c_string_err(&format!("invalid utf-8 string: {}", e)),
};
let payload: serde_json::Value = match serde_json::from_str(str_slice) {
Ok(p) => p,
Err(e) => return to_c_string_err(&format!("invalid JSON payload: {}", e)),
};
let fn_name = match payload.get("fn").and_then(|v| v.as_str()) {
Some(name) => name,
None => return to_c_string_err("invalid or missing fn"),
};
if fn_name == "__list_functions__" {
if let Ok(reg) = get_registry().lock() {
let keys: Vec<String> = reg.keys().cloned().collect();
return to_c_string_ok(&serde_json::Value::from(keys));
} else {
return to_c_string_err("failed to lock registry");
}
}
let data = payload.get("data")
.and_then(|v| v.as_object())
.map(|o| {
o.iter()
.map(|(k, v)| (k.clone(), v.clone()))
.collect::<HashMap<String, serde_json::Value>>()
})
.unwrap_or_default();
let reg = match get_registry().lock() {
Ok(r) => r,
Err(_) => return to_c_string_err("failed to lock registry"),
};
let func = match reg.get(fn_name) {
Some(f) => f,
None => return to_c_string_err(&format!("function '{}' not found", fn_name)),
};
match func(data) {
Ok(val) => to_c_string_ok(&val),
Err(e) => to_c_string_err(&e),
}
}
#[no_mangle]
pub unsafe extern "C" fn void_free_string(ptr: *mut c_char) {
if !ptr.is_null() {
let c_str = CString::from_raw(ptr);
let size = c_str.as_bytes_with_nul().len();
ACTIVE_ALLOCS.fetch_sub(1, Ordering::SeqCst);
TOTAL_ALLOCATED_BYTES.fetch_sub(size, Ordering::SeqCst);
}
}
fn to_c_string_ok(val: &serde_json::Value) -> *mut c_char {
let response = serde_json::json!({
"ok": true,
"value": val
});
let s = serde_json::to_string(&response).unwrap();
let c_str = CString::new(s).unwrap();
let size = c_str.as_bytes_with_nul().len();
ACTIVE_ALLOCS.fetch_add(1, Ordering::SeqCst);
TOTAL_ALLOCATED_BYTES.fetch_add(size, Ordering::SeqCst);
c_str.into_raw()
}
fn to_c_string_err(err_msg: &str) -> *mut c_char {
let response = serde_json::json!({
"ok": false,
"error": err_msg
});
let s = serde_json::to_string(&response).unwrap();
let c_str = CString::new(s).unwrap();
let size = c_str.as_bytes_with_nul().len();
ACTIVE_ALLOCS.fetch_add(1, Ordering::SeqCst);
TOTAL_ALLOCATED_BYTES.fetch_add(size, Ordering::SeqCst);
c_str.into_raw()
}
pub fn get_string(m: &HashMap<String, Value>, key: &str) -> Result<String, String> {
match m.get(key) {
Some(Value::String(s)) => Ok(s.clone()),
Some(_) => Err(format!("invalid type for field '{}', expected String", key)),
None => Err(format!("missing required field '{}'", key)),
}
}
pub fn get_bool(m: &HashMap<String, Value>, key: &str, def: bool) -> bool {
match m.get(key) {
Some(Value::Bool(b)) => *b,
_ => def,
}
}
pub fn get_int(m: &HashMap<String, Value>, key: &str) -> Result<i64, String> {
match m.get(key) {
Some(Value::Number(n)) => {
if let Some(i) = n.as_i64() {
Ok(i)
} else if let Some(f) = n.as_f64() {
Ok(f as i64)
} else {
Err(format!("invalid number format for field '{}'", key))
}
}
Some(_) => Err(format!("invalid type for field '{}', expected Integer", key)),
None => Err(format!("missing required field '{}'", key)),
}
}
pub fn get_float(m: &HashMap<String, Value>, key: &str) -> Result<f64, String> {
match m.get(key) {
Some(Value::Number(n)) => {
if let Some(f) = n.as_f64() {
Ok(f)
} else {
Err(format!("invalid number format for field '{}'", key))
}
}
Some(_) => Err(format!("invalid type for field '{}', expected Float", key)),
None => Err(format!("missing required field '{}'", key)),
}
}
pub fn get_array(m: &HashMap<String, Value>, key: &str) -> Result<Vec<Value>, String> {
match m.get(key) {
Some(Value::Array(a)) => Ok(a.clone()),
Some(_) => Err(format!("invalid type for field '{}', expected Array", key)),
None => Err(format!("missing required field '{}'", key)),
}
}
pub fn get_object(m: &HashMap<String, Value>, key: &str) -> Result<serde_json::Map<String, Value>, String> {
match m.get(key) {
Some(Value::Object(o)) => Ok(o.clone()),
Some(_) => Err(format!("invalid type for field '{}', expected Object", key)),
None => Err(format!("missing required field '{}'", key)),
}
}
pub fn get_value(m: &HashMap<String, Value>, key: &str) -> Result<Value, String> {
match m.get(key) {
Some(v) => Ok(v.clone()),
None => Err(format!("missing required field '{}'", key)),
}
}
pub fn get_null(m: &HashMap<String, Value>, key: &str) -> Result<(), String> {
match m.get(key) {
Some(Value::Null) => Ok(()),
Some(_) => Err(format!("invalid type for field '{}', expected Null", key)),
None => Err(format!("missing required field '{}'", key)),
}
}
#[macro_export]
macro_rules! void_plugin {
($init_fn:ident) => {
#[no_mangle]
pub extern "C" fn void_init() {
$init_fn();
}
};
}