1use std::collections::HashMap;
7use std::sync::Mutex;
8use std::sync::OnceLock;
9use std::os::raw::c_char;
10use std::ffi::{CStr, CString};
11use std::sync::atomic::{AtomicUsize, Ordering};
12
13pub use serde_json::Value;
14
15static ACTIVE_ALLOCS: AtomicUsize = AtomicUsize::new(0);
16static TOTAL_ALLOCATED_BYTES: AtomicUsize = AtomicUsize::new(0);
17
18#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
20pub struct MemoryStats {
21 pub allocated_objects: usize,
22 pub total_bytes_pinned: u64,
23}
24
25pub fn get_memory_stats() -> MemoryStats {
27 MemoryStats {
28 allocated_objects: ACTIVE_ALLOCS.load(Ordering::SeqCst),
29 total_bytes_pinned: TOTAL_ALLOCATED_BYTES.load(Ordering::SeqCst) as u64,
30 }
31}
32
33pub unsafe fn pin_memory(data: &[u8]) -> *mut u8 {
36 if data.is_empty() {
37 return std::ptr::null_mut();
38 }
39 let size = data.len();
40 let layout = std::alloc::Layout::from_size_align(size, 1).unwrap();
41 let ptr = std::alloc::alloc(layout);
42 if !ptr.is_null() {
43 std::ptr::copy_nonoverlapping(data.as_ptr(), ptr, size);
44 ACTIVE_ALLOCS.fetch_add(1, Ordering::SeqCst);
45 TOTAL_ALLOCATED_BYTES.fetch_add(size, Ordering::SeqCst);
46 }
47 ptr
48}
49
50pub unsafe fn unpin_memory(ptr: *mut u8, size: usize) {
52 if !ptr.is_null() {
53 let layout = std::alloc::Layout::from_size_align(size, 1).unwrap();
54 std::alloc::dealloc(ptr, layout);
55 ACTIVE_ALLOCS.fetch_sub(1, Ordering::SeqCst);
56 TOTAL_ALLOCATED_BYTES.fetch_sub(size, Ordering::SeqCst);
57 }
58}
59
60pub type NativeFn = fn(HashMap<String, Value>) -> Result<Value, String>;
63
64static REGISTRY: OnceLock<Mutex<HashMap<String, NativeFn>>> = OnceLock::new();
65
66fn get_registry() -> &'static Mutex<HashMap<String, NativeFn>> {
67 REGISTRY.get_or_init(|| Mutex::new(HashMap::new()))
68}
69
70pub fn register(name: &str, func: NativeFn) {
84 if let Ok(mut reg) = get_registry().lock() {
85 reg.insert(name.to_string(), func);
86 }
87}
88
89#[no_mangle]
92pub unsafe extern "C" fn void_malloc(size: usize) -> *mut u8 {
93 let layout = std::alloc::Layout::from_size_align(size, 1).unwrap();
94 let ptr = std::alloc::alloc(layout);
95 if !ptr.is_null() {
96 ACTIVE_ALLOCS.fetch_add(1, Ordering::SeqCst);
97 TOTAL_ALLOCATED_BYTES.fetch_add(size, Ordering::SeqCst);
98 }
99 ptr
100}
101
102#[no_mangle]
105pub unsafe extern "C" fn void_free(ptr: *mut u8, size: usize) {
106 if !ptr.is_null() {
107 let layout = std::alloc::Layout::from_size_align(size, 1).unwrap();
108 std::alloc::dealloc(ptr, layout);
109 ACTIVE_ALLOCS.fetch_sub(1, Ordering::SeqCst);
110 TOTAL_ALLOCATED_BYTES.fetch_sub(size, Ordering::SeqCst);
111 }
112}
113
114extern "C" {
115 fn void_init();
117}
118
119static INIT: std::sync::Once = std::sync::Once::new();
120
121pub fn ensure_registered() {
123 INIT.call_once(|| unsafe {
124 void_init();
125 });
126}
127
128#[no_mangle]
131pub extern "C" fn void_invoke(input: *const c_char) -> *mut c_char {
132 ensure_registered();
133
134 if input.is_null() {
135 return to_c_string_err("null input");
136 }
137
138 let c_str = unsafe { CStr::from_ptr(input) };
139 let str_slice = match c_str.to_str() {
140 Ok(s) => s,
141 Err(e) => return to_c_string_err(&format!("invalid utf-8 string: {}", e)),
142 };
143
144 let payload: serde_json::Value = match serde_json::from_str(str_slice) {
145 Ok(p) => p,
146 Err(e) => return to_c_string_err(&format!("invalid JSON payload: {}", e)),
147 };
148
149 let fn_name = match payload.get("fn").and_then(|v| v.as_str()) {
150 Some(name) => name,
151 None => return to_c_string_err("invalid or missing fn"),
152 };
153
154 if fn_name == "__list_functions__" {
156 if let Ok(reg) = get_registry().lock() {
157 let keys: Vec<String> = reg.keys().cloned().collect();
158 return to_c_string_ok(&serde_json::Value::from(keys));
159 } else {
160 return to_c_string_err("failed to lock registry");
161 }
162 }
163
164 let data = payload.get("data")
165 .and_then(|v| v.as_object())
166 .map(|o| {
167 o.iter()
168 .map(|(k, v)| (k.clone(), v.clone()))
169 .collect::<HashMap<String, serde_json::Value>>()
170 })
171 .unwrap_or_default();
172
173 let reg = match get_registry().lock() {
174 Ok(r) => r,
175 Err(_) => return to_c_string_err("failed to lock registry"),
176 };
177
178 let func = match reg.get(fn_name) {
179 Some(f) => f,
180 None => return to_c_string_err(&format!("function '{}' not found", fn_name)),
181 };
182
183 match func(data) {
184 Ok(val) => to_c_string_ok(&val),
185 Err(e) => to_c_string_err(&e),
186 }
187}
188
189#[no_mangle]
192pub unsafe extern "C" fn void_free_string(ptr: *mut c_char) {
193 if !ptr.is_null() {
194 let c_str = CString::from_raw(ptr);
195 let size = c_str.as_bytes_with_nul().len();
196 ACTIVE_ALLOCS.fetch_sub(1, Ordering::SeqCst);
197 TOTAL_ALLOCATED_BYTES.fetch_sub(size, Ordering::SeqCst);
198 }
199}
200
201fn to_c_string_ok(val: &serde_json::Value) -> *mut c_char {
202 let response = serde_json::json!({
203 "ok": true,
204 "value": val
205 });
206 let s = serde_json::to_string(&response).unwrap();
207 let c_str = CString::new(s).unwrap();
208 let size = c_str.as_bytes_with_nul().len();
209 ACTIVE_ALLOCS.fetch_add(1, Ordering::SeqCst);
210 TOTAL_ALLOCATED_BYTES.fetch_add(size, Ordering::SeqCst);
211 c_str.into_raw()
212}
213
214fn to_c_string_err(err_msg: &str) -> *mut c_char {
215 let response = serde_json::json!({
216 "ok": false,
217 "error": err_msg
218 });
219 let s = serde_json::to_string(&response).unwrap();
220 let c_str = CString::new(s).unwrap();
221 let size = c_str.as_bytes_with_nul().len();
222 ACTIVE_ALLOCS.fetch_add(1, Ordering::SeqCst);
223 TOTAL_ALLOCATED_BYTES.fetch_add(size, Ordering::SeqCst);
224 c_str.into_raw()
225}
226
227pub fn get_string(m: &HashMap<String, Value>, key: &str) -> Result<String, String> {
231 match m.get(key) {
232 Some(Value::String(s)) => Ok(s.clone()),
233 Some(_) => Err(format!("invalid type for field '{}', expected String", key)),
234 None => Err(format!("missing required field '{}'", key)),
235 }
236}
237
238pub fn get_bool(m: &HashMap<String, Value>, key: &str, def: bool) -> bool {
242 match m.get(key) {
243 Some(Value::Bool(b)) => *b,
244 _ => def,
245 }
246}
247
248pub fn get_int(m: &HashMap<String, Value>, key: &str) -> Result<i64, String> {
252 match m.get(key) {
253 Some(Value::Number(n)) => {
254 if let Some(i) = n.as_i64() {
255 Ok(i)
256 } else if let Some(f) = n.as_f64() {
257 Ok(f as i64)
258 } else {
259 Err(format!("invalid number format for field '{}'", key))
260 }
261 }
262 Some(_) => Err(format!("invalid type for field '{}', expected Integer", key)),
263 None => Err(format!("missing required field '{}'", key)),
264 }
265}
266
267pub fn get_float(m: &HashMap<String, Value>, key: &str) -> Result<f64, String> {
271 match m.get(key) {
272 Some(Value::Number(n)) => {
273 if let Some(f) = n.as_f64() {
274 Ok(f)
275 } else {
276 Err(format!("invalid number format for field '{}'", key))
277 }
278 }
279 Some(_) => Err(format!("invalid type for field '{}', expected Float", key)),
280 None => Err(format!("missing required field '{}'", key)),
281 }
282}
283
284pub fn get_array(m: &HashMap<String, Value>, key: &str) -> Result<Vec<Value>, String> {
288 match m.get(key) {
289 Some(Value::Array(a)) => Ok(a.clone()),
290 Some(_) => Err(format!("invalid type for field '{}', expected Array", key)),
291 None => Err(format!("missing required field '{}'", key)),
292 }
293}
294
295pub fn get_object(m: &HashMap<String, Value>, key: &str) -> Result<serde_json::Map<String, Value>, String> {
299 match m.get(key) {
300 Some(Value::Object(o)) => Ok(o.clone()),
301 Some(_) => Err(format!("invalid type for field '{}', expected Object", key)),
302 None => Err(format!("missing required field '{}'", key)),
303 }
304}
305
306pub fn get_value(m: &HashMap<String, Value>, key: &str) -> Result<Value, String> {
310 match m.get(key) {
311 Some(v) => Ok(v.clone()),
312 None => Err(format!("missing required field '{}'", key)),
313 }
314}
315
316pub fn get_null(m: &HashMap<String, Value>, key: &str) -> Result<(), String> {
320 match m.get(key) {
321 Some(Value::Null) => Ok(()),
322 Some(_) => Err(format!("invalid type for field '{}', expected Null", key)),
323 None => Err(format!("missing required field '{}'", key)),
324 }
325}
326
327#[macro_export]
341macro_rules! void_plugin {
342 ($init_fn:ident) => {
343 #[no_mangle]
344 pub extern "C" fn void_init() {
345 $init_fn();
346 }
347 };
348}