Skip to main content

xaeroflux_ffi/
lib.rs

1// src/ffi.rs
2
3use std::{ffi::CStr, os::raw::c_char, sync::Arc};
4
5use xaeroflux::actors::subject::Subject;
6// Only import what we actually use
7use xaeroflux::{XaeroPoolManager, event::XaeroEvent};
8
9/// Opaque pointer to a Subject pipeline.
10#[repr(C)]
11pub struct FfiSubject {
12    _private: [u8; 0],
13}
14
15/// Create a new Subject pipeline for the given name.
16/// Returns a pointer you must later pass to the other calls.
17/// # Safety
18/// The caller must ensure:
19/// - `name`, `workspace_name`, and `object_name` are valid null-terminated UTF-8 strings
20/// - The returned pointer must be freed with `xf_subject_unsafe_run` or properly disposed
21/// - The pointer must not be used after being freed
22#[unsafe(no_mangle)]
23pub unsafe extern "C" fn xf_subject_new(
24    name: *const c_char,
25    workspace_name: *const c_char,
26    object_name: *const c_char,
27) -> *mut FfiSubject {
28    // Check for null pointers first
29    if name.is_null() || workspace_name.is_null() || object_name.is_null() {
30        return std::ptr::null_mut();
31    }
32
33    // Initialize ring buffer pools
34    XaeroPoolManager::init();
35
36    // Parse C strings into &str with error handling
37    let name_rs = match unsafe { CStr::from_ptr(name) }.to_str() {
38        Ok(s) => s,
39        Err(_) => return std::ptr::null_mut(),
40    };
41    let workspace_name_rs = match unsafe { CStr::from_ptr(workspace_name) }.to_str() {
42        Ok(s) => s,
43        Err(_) => return std::ptr::null_mut(),
44    };
45    let object_name_rs = match unsafe { CStr::from_ptr(object_name) }.to_str() {
46        Ok(s) => s,
47        Err(_) => return std::ptr::null_mut(),
48    };
49
50    // Create Blake3 hash for workspace as expected by Subject::new_with_workspace
51    let mut hasher = blake3::Hasher::new();
52    hasher.update(workspace_name_rs.as_bytes());
53    let workspace_name_hash_rs = hasher.finalize();
54
55    // Try to create subject - this might be where the crash occurs
56    let subject = match std::panic::catch_unwind(|| {
57        Subject::new_with_workspace(
58            String::from(name_rs),
59            *workspace_name_hash_rs.as_bytes(),
60            String::from(workspace_name_rs),
61            String::from(object_name_rs),
62        )
63    }) {
64        Ok(s) => s,
65        Err(_) => return std::ptr::null_mut(),
66    };
67
68    // Box the subject and return as opaque pointer
69    let boxed_subject = Box::new(subject);
70    Box::into_raw(boxed_subject) as *mut FfiSubject
71}
72
73/// Map operator: apply your own callback to every event.
74/// `cb` is a C function pointer that takes an Arc<XaeroEvent> and returns an Arc<XaeroEvent>.
75/// # Safety
76/// The caller must ensure the callback is valid and doesn't cause undefined behavior.
77pub type MapCallback = extern "C" fn(evt: *const XaeroEvent) -> *mut XaeroEvent;
78
79#[unsafe(no_mangle)]
80pub extern "C" fn xf_subject_map(handle: *mut FfiSubject, cb: MapCallback) -> *mut FfiSubject {
81    if handle.is_null() {
82        return std::ptr::null_mut();
83    }
84
85    // Cast handle to &mut Subject
86    let _subject = unsafe { &mut *(handle as *mut Subject) };
87
88    // Create a Rust closure that wraps the C callback
89    let _rust_callback = move |evt: Arc<XaeroEvent>| -> Arc<XaeroEvent> {
90        // Call the C callback with a raw pointer to the XaeroEvent
91        let raw_evt = Arc::as_ptr(&evt);
92        let result_ptr = cb(raw_evt);
93
94        if result_ptr.is_null() {
95            // If callback returns null, return the original event
96            evt
97        } else {
98            // Safety: We trust the C callback to return a valid Arc<XaeroEvent>
99            // This is unsafe and requires careful contract with C code
100            unsafe { Arc::from_raw(result_ptr) }
101        }
102    };
103
104    // Apply the map operation (this would need to be implemented in Subject)
105    // subject.pipe = subject.pipe.map(Arc::new(rust_callback));
106
107    // Return the same handle for chaining
108    handle
109}
110
111/// Filter operator: drop or keep events based on your predicate.
112/// Return "true" to keep the event.
113/// # Safety
114/// The caller must ensure the callback is valid and doesn't cause undefined behavior.
115pub type FilterCallback = extern "C" fn(evt: *const XaeroEvent) -> bool;
116
117#[unsafe(no_mangle)]
118pub extern "C" fn xf_subject_filter(
119    handle: *mut FfiSubject,
120    cb: FilterCallback,
121) -> *mut FfiSubject {
122    if handle.is_null() {
123        return std::ptr::null_mut();
124    }
125
126    // Cast handle to &mut Subject
127    let _subject = unsafe { &mut *(handle as *mut Subject) };
128
129    // Create a Rust closure that wraps the C callback
130    let _rust_callback = move |evt: &Arc<XaeroEvent>| -> bool {
131        // Call the C callback with a raw pointer to the XaeroEvent
132        let raw_evt = Arc::as_ptr(evt);
133        cb(raw_evt)
134    };
135
136    // Apply the filter operation (this would need to be implemented in Subject)
137    // subject.pipe = subject.pipe.filter(Arc::new(rust_callback));
138
139    // Return the same handle for chaining
140    handle
141}
142
143/// Drop events lacking a Merkle proof (no callback needed).
144/// # Safety
145/// The handle must be a valid pointer returned from xf_subject_new.
146#[unsafe(no_mangle)]
147pub extern "C" fn xf_subject_filter_merkle_proofs(handle: *mut FfiSubject) -> *mut FfiSubject {
148    if handle.is_null() {
149        return std::ptr::null_mut();
150    }
151
152    // Cast handle to &mut Subject
153    let _subject = unsafe { &mut *(handle as *mut Subject) };
154
155    // Create a filter that checks for merkle proofs
156    let _merkle_filter = |evt: &Arc<XaeroEvent>| -> bool { evt.merkle_proof().is_some() };
157
158    // Apply the filter operation (this would need to be implemented in Subject)
159    // subject.pipe = subject.pipe.filter(Arc::new(merkle_filter));
160
161    // Return the same handle for chaining
162    handle
163}
164
165/// Terminal operator: consume all events without processing (blackhole).
166/// # Safety
167/// The handle must be a valid pointer returned from xf_subject_new.
168#[unsafe(no_mangle)]
169pub extern "C" fn xf_subject_blackhole(handle: *mut FfiSubject) -> *mut FfiSubject {
170    if handle.is_null() {
171        return std::ptr::null_mut();
172    }
173
174    // Cast handle to &mut Subject
175    let _subject = unsafe { &mut *(handle as *mut Subject) };
176
177    // Apply the blackhole operation (this would need to be implemented in Subject)
178    // subject.pipe = subject.pipe.blackhole();
179
180    // Return the same handle for chaining
181    handle
182}
183
184/// Run the pipeline to completion (or until it blocks).
185/// After calling this, the Subject is consumed/dropped.
186/// # Safety
187/// The handle must be a valid pointer returned from xf_subject_new and must not be used after this
188/// call.
189#[unsafe(no_mangle)]
190pub extern "C" fn xf_subject_unsafe_run(handle: *mut FfiSubject) {
191    if handle.is_null() {
192        return;
193    }
194
195    // Wrap in catch_unwind to prevent crashes during cleanup
196    let _ = std::panic::catch_unwind(|| {
197        // Convert back to Box<Subject> and let it drop (which runs any cleanup)
198        let _subject = unsafe { Box::from_raw(handle as *mut Subject) };
199        // The subject will be dropped here automatically
200    });
201}
202
203/// Helper function to safely access XaeroEvent data from C
204/// Returns a pointer to the event data and sets the length.
205/// # Safety
206/// The evt pointer must be valid and the out_len pointer must be valid.
207#[unsafe(no_mangle)]
208pub unsafe extern "C" fn xf_event_get_data(
209    evt: *const XaeroEvent,
210    out_len: *mut usize,
211) -> *const u8 {
212    if evt.is_null() || out_len.is_null() {
213        return std::ptr::null();
214    }
215
216    let event = unsafe { &*evt };
217    let data = event.data();
218    unsafe { *out_len = data.len() };
219    data.as_ptr()
220}
221
222/// Helper function to get event type from C
223/// # Safety
224/// The evt pointer must be valid.
225#[unsafe(no_mangle)]
226pub unsafe extern "C" fn xf_event_get_type(evt: *const XaeroEvent) -> u8 {
227    if evt.is_null() {
228        return 0;
229    }
230
231    let event = unsafe { &*evt };
232    event.event_type()
233}
234
235/// Helper function to get event timestamp from C
236/// # Safety
237/// The evt pointer must be valid.
238#[unsafe(no_mangle)]
239pub unsafe extern "C" fn xf_event_get_timestamp(evt: *const XaeroEvent) -> u64 {
240    if evt.is_null() {
241        return 0;
242    }
243
244    let event = unsafe { &*evt };
245    event.latest_ts
246}
247
248/// Create a new XaeroEvent from C data
249/// # Safety
250/// The data pointer must be valid for the given length.
251#[unsafe(no_mangle)]
252pub unsafe extern "C" fn xf_event_create(
253    data: *const u8,
254    data_len: usize,
255    event_type: u8,
256    timestamp: u64,
257) -> *mut XaeroEvent {
258    if data.is_null() {
259        return std::ptr::null_mut();
260    }
261
262    // Convert C data to Rust slice
263    let data_slice = unsafe { std::slice::from_raw_parts(data, data_len) };
264
265    // Create XaeroEvent using XaeroPoolManager
266    match XaeroPoolManager::create_xaero_event(
267        data_slice, event_type, None, // author_id
268        None, // merkle_proof
269        None, // vector_clock
270        timestamp,
271    ) {
272        Ok(event) => {
273            // Convert Arc<XaeroEvent> to raw pointer
274            // We need to leak the Arc to give ownership to C
275            let raw_ptr = Arc::into_raw(event);
276            raw_ptr as *mut XaeroEvent
277        }
278        Err(_) => {
279            // Pool exhaustion - return null
280            std::ptr::null_mut()
281        }
282    }
283}
284
285/// Free a XaeroEvent created by xf_event_create
286/// # Safety
287/// The evt pointer must be a valid pointer returned from xf_event_create.
288#[unsafe(no_mangle)]
289pub unsafe extern "C" fn xf_event_free(evt: *mut XaeroEvent) {
290    if !evt.is_null() {
291        // Convert back to Arc and let it drop
292        // This reconstructs the Arc from the raw pointer and drops it
293        let _event = unsafe { Arc::from_raw(evt as *const XaeroEvent) };
294        // Arc will handle the cleanup automatically when _event goes out of scope
295    }
296}
297
298#[cfg(test)]
299mod ffi_tests {
300    use std::ffi::CString;
301
302    use xaeroflux::{event::EventType, initialize};
303
304    use super::*;
305
306    #[allow(dead_code)]
307    //#[test]
308    fn test_subject_creation_and_cleanup() {
309        // Initialize everything properly
310        initialize();
311        XaeroPoolManager::init();
312
313        let name = CString::new("test_subject").expect("failed_to_unravel");
314        let workspace = CString::new("test_workspace").expect("failed_to_unravel");
315        let object = CString::new("test_object").expect("failed_to_unravel");
316
317        let subject_ptr =
318            unsafe { xf_subject_new(name.as_ptr(), workspace.as_ptr(), object.as_ptr()) };
319
320        assert!(!subject_ptr.is_null(), "Subject creation failed");
321
322        // Clean up - this might be causing the SIGBUS
323        xf_subject_unsafe_run(subject_ptr);
324    }
325
326    #[allow(dead_code)]
327    // #[test]
328    fn test_event_helpers() {
329        // Initialize everything properly
330        initialize();
331        XaeroPoolManager::init();
332
333        let test_data = b"small"; // Very small test data to ensure it fits
334        let event_type = EventType::ApplicationEvent(1).to_u8();
335        let timestamp = 12345;
336
337        // Create event
338        let event_ptr =
339            unsafe { xf_event_create(test_data.as_ptr(), test_data.len(), event_type, timestamp) };
340
341        assert!(!event_ptr.is_null(), "Failed to create event");
342
343        // Test data access
344        let mut data_len: usize = 0;
345        let data_ptr = unsafe { xf_event_get_data(event_ptr, &mut data_len) };
346        assert!(!data_ptr.is_null(), "Failed to get event data");
347        assert_eq!(data_len, test_data.len());
348
349        let retrieved_data = unsafe { std::slice::from_raw_parts(data_ptr, data_len) };
350        assert_eq!(retrieved_data, test_data);
351
352        // Test type access
353        let retrieved_type = unsafe { xf_event_get_type(event_ptr) };
354        assert_eq!(retrieved_type, event_type);
355
356        // Test timestamp access
357        let retrieved_timestamp = unsafe { xf_event_get_timestamp(event_ptr) };
358        assert_eq!(retrieved_timestamp, timestamp);
359
360        // Clean up
361        unsafe { xf_event_free(event_ptr) };
362    }
363}