orbis_plugin_api/sdk/
ffi.rs

1//! FFI declarations and memory management.
2//!
3//! This module handles all the low-level FFI details so plugin developers don't have to.
4
5use core::slice;
6
7// ============================================================================
8// Host function declarations
9// ============================================================================
10
11#[cfg(target_arch = "wasm32")]
12#[link(wasm_import_module = "env")]
13unsafe extern "C" {
14    // State management
15    pub fn state_get(key_ptr: i32, key_len: i32) -> i32;
16    pub fn state_set(key_ptr: i32, key_len: i32, value_ptr: i32, value_len: i32) -> i32;
17    pub fn state_remove(key_ptr: i32, key_len: i32) -> i32;
18
19    // Logging
20    pub fn log(level: i32, ptr: i32, len: i32);
21
22    // Database (new)
23    pub fn db_query(query_ptr: i32, query_len: i32, params_ptr: i32, params_len: i32) -> i32;
24    pub fn db_execute(query_ptr: i32, query_len: i32, params_ptr: i32, params_len: i32) -> i32;
25
26    // HTTP (new)
27    pub fn http_request(
28        method_ptr: i32,
29        method_len: i32,
30        url_ptr: i32,
31        url_len: i32,
32        headers_ptr: i32,
33        headers_len: i32,
34        body_ptr: i32,
35        body_len: i32,
36    ) -> i32;
37
38    // Events (new)
39    pub fn emit_event(event_ptr: i32, event_len: i32, payload_ptr: i32, payload_len: i32) -> i32;
40
41    // Config (new)
42    pub fn get_config(key_ptr: i32, key_len: i32) -> i32;
43
44    // Crypto (new)
45    pub fn crypto_hash(algorithm: i32, data_ptr: i32, data_len: i32) -> i32;
46    pub fn crypto_random(len: i32) -> i32;
47}
48
49/// Shadow implementation of the log function for non-WASM targets
50#[cfg(not(target_arch = "wasm32"))]
51pub fn log(level: i32, ptr: i32, len: i32) {
52    let message = unsafe {
53        let slice = slice::from_raw_parts(ptr as *const u8, len as usize);
54        std::str::from_utf8(slice).unwrap_or("<invalid utf8>")
55    };
56    let level_str = match level {
57        0 => "ERROR",
58        1 => "WARN",
59        2 => "INFO",
60        3 => "DEBUG",
61        _ => "TRACE",
62    };
63    eprintln!("[{}] {}", level_str, message);
64}
65
66// ============================================================================
67// Memory management - Plugin side
68// ============================================================================
69
70/// Internal allocator for WASM - exported through orbis_plugin! macro
71pub fn allocate_internal(size: i32) -> *mut u8 {
72    let layout = core::alloc::Layout::from_size_align(size as usize, 1).unwrap();
73    // SAFETY: We're using the global allocator which is valid in WASM
74    unsafe { std::alloc::alloc(layout) }
75}
76
77/// Internal deallocator - exported through orbis_plugin! macro
78pub fn deallocate_internal(ptr: *mut u8, size: i32) {
79    if ptr.is_null() {
80        return;
81    }
82    let layout = core::alloc::Layout::from_size_align(size as usize, 1).unwrap();
83    // SAFETY: ptr was allocated by allocate() with the same layout
84    unsafe { std::alloc::dealloc(ptr, layout) }
85}
86
87// ============================================================================
88// Memory utilities
89// ============================================================================
90
91/// Read bytes from a raw pointer with length
92///
93/// # Safety
94/// Caller must ensure ptr is valid for len bytes
95#[inline]
96pub unsafe fn read_bytes(ptr: *const u8, len: usize) -> Vec<u8> {
97    if ptr.is_null() || len == 0 {
98        return Vec::new();
99    }
100    unsafe { slice::from_raw_parts(ptr, len).to_vec() }
101}
102
103/// Read a length-prefixed value from a pointer
104///
105/// Format: [4 bytes u32 LE length][data]
106///
107/// # Safety
108/// Caller must ensure ptr points to valid length-prefixed data
109#[inline]
110pub unsafe fn read_length_prefixed(ptr: i32) -> Vec<u8> {
111    if ptr == 0 {
112        return Vec::new();
113    }
114    
115    let ptr = ptr as *const u8;
116    
117    unsafe {
118        // Read the length prefix
119        let len_bytes = slice::from_raw_parts(ptr, 4);
120        let len = u32::from_le_bytes([len_bytes[0], len_bytes[1], len_bytes[2], len_bytes[3]]);
121        
122        // Validate length (prevent reading huge invalid values)
123        // Max 10MB for safety
124        const MAX_LENGTH: u32 = 10 * 1024 * 1024;
125        if len > MAX_LENGTH {
126            log(0, "Invalid length in read_length_prefixed".as_ptr() as i32, "Invalid length in read_length_prefixed".len() as i32);
127            return Vec::new();
128        }
129        
130        // Read the actual data
131        if len == 0 {
132            return Vec::new();
133        }
134        
135        let data_ptr = ptr.add(4);
136        let data_slice = slice::from_raw_parts(data_ptr, len as usize);
137        
138        // Use Vec::from to safely copy the data
139        Vec::from(data_slice)
140    }
141}
142
143/// Write data with length prefix
144///
145/// Returns pointer to allocated memory containing: [4 bytes length][data]
146#[inline]
147pub fn write_length_prefixed(data: &[u8]) -> *mut u8 {
148    let len = data.len() as u32;
149    let total_size = 4 + data.len();
150    let ptr = allocate_internal(total_size as i32);
151
152    unsafe {
153        // Write length prefix
154        *(ptr as *mut u32) = len;
155        // Write data
156        let data_ptr = ptr.add(4);
157        core::ptr::copy_nonoverlapping(data.as_ptr(), data_ptr, data.len());
158    }
159
160    ptr
161}
162
163/// Write a string/bytes and return ptr as i32 (for returning from handlers)
164#[inline]
165pub fn return_bytes(data: &[u8]) -> i32 {
166    write_length_prefixed(data) as i32
167}
168
169/// Write JSON and return ptr as i32
170#[inline]
171pub fn return_json<T: serde::Serialize>(value: &T) -> Result<i32, serde_json::Error> {
172    let json = serde_json::to_vec(value)?;
173    Ok(return_bytes(&json))
174}
175
176// ============================================================================
177// Handler wrapper macro
178// ============================================================================
179
180/// Wraps a handler function to handle FFI details automatically
181///
182/// Converts: `fn(Context) -> Result<Response>` into `extern "C" fn(i32, i32) -> i32`
183///
184/// # Usage
185///
186/// ```rust,ignore
187/// // Define your handler function
188/// fn my_handler_impl(ctx: Context) -> Result<Response> {
189///     Ok(Response::json(&json!({"status": "ok"}))?)
190/// }
191///
192/// // Wrap it for FFI export
193/// wrap_handler!(my_handler, my_handler_impl);
194/// ```
195#[macro_export]
196macro_rules! wrap_handler {
197    ($export_name:ident, $handler_fn:ident) => {
198        #[unsafe(no_mangle)]
199        pub extern "C" fn $export_name(ctx_ptr: i32, ctx_len: i32) -> i32 {
200            use $crate::sdk::prelude::*;
201
202            // Deserialize context
203            let ctx = match Context::from_raw(ctx_ptr, ctx_len) {
204                Ok(c) => c,
205                Err(e) => {
206                    // Log the error using the logging FFI
207                    let error_message = format!("Failed to parse context: {}", e);
208                    unsafe { $crate::sdk::ffi::log(0, error_message.as_ptr() as i32, error_message.len() as i32); }
209                    return Response::error(400, &format!("Invalid context: {}", e))
210                        .to_raw()
211                        .unwrap_or(0);
212                }
213            };
214
215            // Call the actual handler
216            match $handler_fn(ctx) {
217                Ok(response) => response.to_raw().unwrap_or(0),
218                Err(e) => {
219                    let error_message = format!("Handler error: {}", e);
220                    unsafe { $crate::sdk::ffi::log(0, error_message.as_ptr() as i32, error_message.len() as i32); }
221                    Response::error(500, &e.to_string())
222                        .to_raw()
223                        .unwrap_or(0)
224                }
225            }
226        }
227    };
228}
229/// Define memory allocation functions for the plugin
230/// 
231/// # Usage
232/// ```rust,ignore
233/// orbis_allocators!();
234/// ```
235#[macro_export]
236macro_rules! orbis_allocators {
237    () => {
238        // Memory management functions - defined directly to avoid optimization issues
239        #[unsafe(no_mangle)]
240        #[inline(never)]
241        pub extern "C" fn allocate(size: i32) -> *mut u8 {
242            if size <= 0 {
243                return core::ptr::null_mut();
244            }
245            use core::alloc::Layout;
246            let layout = match Layout::from_size_align(size as usize, 1) {
247                Ok(l) => l,
248                Err(_) => return core::ptr::null_mut(),
249            };
250            // SAFETY: We're using the global allocator which is valid in WASM
251            let ptr = unsafe { std::alloc::alloc(layout) };
252            if ptr.is_null() {
253                return core::ptr::null_mut();
254            }
255            ptr
256        }
257        
258        #[unsafe(no_mangle)]
259        #[inline(never)]
260        pub extern "C" fn deallocate(ptr: *mut u8, size: i32) {
261            if ptr.is_null() || size <= 0 {
262                return;
263            }
264            use core::alloc::Layout;
265            let layout = match Layout::from_size_align(size as usize, 1) {
266                Ok(l) => l,
267                Err(_) => return,
268            };
269            // SAFETY: ptr was allocated by allocate() with the same layout
270            unsafe { std::alloc::dealloc(ptr, layout) }
271        }
272    };
273}
274
275/// Define a complete plugin with minimal boilerplate
276///
277/// # Example
278///
279/// ```rust,ignore
280/// use orbis_plugin_api::prelude::*;
281///
282/// orbis_plugin! {
283///     init: || {
284///         log::info!("Plugin starting!");
285///         state::set("initialized", &true)?;
286///         Ok(())
287///     },
288///     cleanup: || {
289///         log::info!("Plugin stopping!");
290///         Ok(())
291///     }
292/// }
293/// ```
294#[macro_export]
295macro_rules! orbis_plugin {
296    // With init and cleanup
297    (
298        init: $init:expr,
299        cleanup: $cleanup:expr $(,)?
300    ) => {
301        #[unsafe(no_mangle)]
302        pub extern "C" fn init() -> i32 {
303            let init_fn: fn() -> $crate::sdk::Result<()> = $init;
304            match init_fn() {
305                Ok(()) => 1,
306                Err(e) => {
307                    let error_message = format!("Init failed: {}", e);
308                    unsafe { $crate::sdk::ffi::log(0, error_message.as_ptr() as i32, error_message.len() as i32); }
309                    0
310                }
311            }
312        }
313
314        #[unsafe(no_mangle)]
315        pub extern "C" fn cleanup() -> i32 {
316            let cleanup_fn: fn() -> $crate::sdk::Result<()> = $cleanup;
317            match cleanup_fn() {
318                Ok(()) => 1,
319                Err(e) => {
320                    let error_message = format!("Cleanup failed: {}", e);
321                    unsafe { $crate::sdk::ffi::log(0, error_message.as_ptr() as i32, error_message.len() as i32); }
322                    0
323                }
324            }
325        }
326
327        $crate::sdk::ffi::orbis_allocators!();
328    };
329
330    // With only init
331    (init: $init:expr $(,)?) => {
332        #[unsafe(no_mangle)]
333        pub extern "C" fn init() -> i32 {
334            let init_fn: fn() -> $crate::sdk::Result<()> = $init;
335            match init_fn() {
336                Ok(()) => 1,
337                Err(e) => {
338                    let error_message = format!("Init failed: {}", e);
339                    unsafe { $crate::sdk::ffi::log(0, error_message.as_ptr() as i32, error_message.len() as i32); }
340                    0
341                }
342            }
343        }
344
345        #[unsafe(no_mangle)]
346        pub extern "C" fn cleanup() -> i32 {
347            1 // No-op cleanup
348        }
349
350        $crate::sdk::ffi::orbis_allocators!();
351    };
352
353    // No init or cleanup (just lifecycle stubs)
354    () => {
355        #[unsafe(no_mangle)]
356        pub extern "C" fn init() -> i32 {
357            1
358        }
359
360        #[unsafe(no_mangle)]
361        pub extern "C" fn cleanup() -> i32 {
362            1
363        }
364        
365        $crate::sdk::ffi::orbis_allocators!();
366    };
367}
368
369pub use orbis_plugin;
370pub use wrap_handler;
371pub use orbis_allocators;