Skip to main content

embeddenator_interop/
ffi.rs

1//! FFI (Foreign Function Interface) bindings for C/C++ integration.
2//!
3//! This module provides C-compatible bindings for embeddenator core types.
4//! All functions are marked `unsafe` and require careful memory management.
5//!
6//! ## Safety Considerations
7//!
8//! - All pointers must be valid and properly aligned
9//! - String pointers must be null-terminated UTF-8
10//! - Caller is responsible for freeing memory allocated by FFI functions
11//! - No Rust objects should be accessed after being freed
12//!
13//! ## Example (C)
14//!
15//! ```c
16//! #include "embeddenator_interop.h"
17//!
18//! // Create a vector
19//! SparseVecHandle* vec = sparse_vec_new();
20//!
21//! // Use the vector...
22//!
23//! // Free the vector
24//! sparse_vec_free(vec);
25//! ```
26
27use embeddenator_vsa::{ReversibleVSAConfig, SparseVec};
28use std::ffi::CStr;
29use std::os::raw::c_char;
30use std::ptr;
31use std::slice;
32
33// ============================================================================
34// Opaque Handle Types
35// ============================================================================
36
37/// Opaque handle to a SparseVec
38#[repr(C)]
39pub struct SparseVecHandle {
40    _private: [u8; 0],
41}
42
43/// Opaque handle to a ReversibleVSAConfig
44#[repr(C)]
45pub struct VSAConfigHandle {
46    _private: [u8; 0],
47}
48
49/// Result buffer for returning data to C
50#[repr(C)]
51pub struct ByteBuffer {
52    pub data: *mut u8,
53    pub len: usize,
54    pub capacity: usize,
55}
56
57// ============================================================================
58// Helper Functions
59// ============================================================================
60
61/// Convert a Rust object to an opaque pointer
62unsafe fn to_handle<T>(obj: T) -> *mut T {
63    Box::into_raw(Box::new(obj))
64}
65
66/// Convert an opaque pointer back to a Rust object
67unsafe fn from_handle<T>(handle: *mut T) -> Box<T> {
68    assert!(!handle.is_null(), "FFI: null handle");
69    Box::from_raw(handle)
70}
71
72/// Borrow an object from a handle without taking ownership
73unsafe fn borrow_handle<T>(handle: *const T) -> &'static T {
74    assert!(!handle.is_null(), "FFI: null handle");
75    &*handle
76}
77
78/// Borrow an object mutably from a handle without taking ownership
79#[allow(dead_code)]
80unsafe fn borrow_handle_mut<T>(handle: *mut T) -> &'static mut T {
81    assert!(!handle.is_null(), "FFI: null handle");
82    &mut *handle
83}
84
85// ============================================================================
86// SparseVec FFI
87// ============================================================================
88
89/// Create a new SparseVec
90///
91/// # Safety
92/// Must call `sparse_vec_free` to deallocate
93#[no_mangle]
94pub unsafe extern "C" fn sparse_vec_new() -> *mut SparseVecHandle {
95    let vec = SparseVec::new();
96    to_handle(vec) as *mut SparseVecHandle
97}
98
99/// Free a SparseVec
100///
101/// # Safety
102/// - `handle` must be a valid pointer from `sparse_vec_new`
103/// - Must not use `handle` after calling this function
104#[no_mangle]
105pub unsafe extern "C" fn sparse_vec_free(handle: *mut SparseVecHandle) {
106    if !handle.is_null() {
107        let _ = from_handle(handle as *mut SparseVec);
108    }
109}
110
111/// Bundle two SparseVecs
112///
113/// # Safety
114/// - All handles must be valid
115/// - Returns a new handle that must be freed with `sparse_vec_free`
116#[no_mangle]
117pub unsafe extern "C" fn sparse_vec_bundle(
118    a: *const SparseVecHandle,
119    b: *const SparseVecHandle,
120) -> *mut SparseVecHandle {
121    let a_vec = borrow_handle(a as *const SparseVec);
122    let b_vec = borrow_handle(b as *const SparseVec);
123    let result = a_vec.bundle(b_vec);
124    to_handle(result) as *mut SparseVecHandle
125}
126
127/// Bind two SparseVecs
128///
129/// # Safety
130/// - All handles must be valid
131/// - Returns a new handle that must be freed with `sparse_vec_free`
132#[no_mangle]
133pub unsafe extern "C" fn sparse_vec_bind(
134    a: *const SparseVecHandle,
135    b: *const SparseVecHandle,
136) -> *mut SparseVecHandle {
137    let a_vec = borrow_handle(a as *const SparseVec);
138    let b_vec = borrow_handle(b as *const SparseVec);
139    let result = a_vec.bind(b_vec);
140    to_handle(result) as *mut SparseVecHandle
141}
142
143/// Compute cosine similarity between two SparseVecs
144///
145/// # Safety
146/// All handles must be valid
147#[no_mangle]
148pub unsafe extern "C" fn sparse_vec_cosine(
149    a: *const SparseVecHandle,
150    b: *const SparseVecHandle,
151) -> f64 {
152    let a_vec = borrow_handle(a as *const SparseVec);
153    let b_vec = borrow_handle(b as *const SparseVec);
154    a_vec.cosine(b_vec)
155}
156
157/// Serialize a SparseVec to JSON
158///
159/// # Safety
160/// - `handle` must be valid
161/// - Returns a ByteBuffer that must be freed with `byte_buffer_free`
162#[no_mangle]
163pub unsafe extern "C" fn sparse_vec_to_json(handle: *const SparseVecHandle) -> ByteBuffer {
164    let vec = borrow_handle(handle as *const SparseVec);
165    match serde_json::to_vec(vec) {
166        Ok(mut bytes) => {
167            let len = bytes.len();
168            let capacity = bytes.capacity();
169            let data = bytes.as_mut_ptr();
170            std::mem::forget(bytes);
171            ByteBuffer {
172                data,
173                len,
174                capacity,
175            }
176        }
177        Err(_) => ByteBuffer {
178            data: ptr::null_mut(),
179            len: 0,
180            capacity: 0,
181        },
182    }
183}
184
185/// Deserialize a SparseVec from JSON
186///
187/// # Safety
188/// - `data` must point to valid JSON bytes
189/// - `len` must be the correct length
190/// - Returns a handle that must be freed with `sparse_vec_free`
191#[no_mangle]
192pub unsafe extern "C" fn sparse_vec_from_json(data: *const u8, len: usize) -> *mut SparseVecHandle {
193    if data.is_null() {
194        return ptr::null_mut();
195    }
196    let bytes = slice::from_raw_parts(data, len);
197    match serde_json::from_slice::<SparseVec>(bytes) {
198        Ok(vec) => to_handle(vec) as *mut SparseVecHandle,
199        Err(_) => ptr::null_mut(),
200    }
201}
202
203// ============================================================================
204// VSAConfig FFI
205// ============================================================================
206
207/// Create a new default ReversibleVSAConfig
208///
209/// # Safety
210/// Must call `vsa_config_free` to deallocate
211#[no_mangle]
212pub unsafe extern "C" fn vsa_config_new() -> *mut VSAConfigHandle {
213    let config = ReversibleVSAConfig::default();
214    to_handle(config) as *mut VSAConfigHandle
215}
216
217/// Create a new ReversibleVSAConfig with custom parameters
218///
219/// # Safety
220/// Must call `vsa_config_free` to deallocate
221#[no_mangle]
222pub unsafe extern "C" fn vsa_config_new_custom(
223    block_size: usize,
224    max_path_depth: usize,
225    base_shift: usize,
226    target_sparsity: usize,
227) -> *mut VSAConfigHandle {
228    let config = ReversibleVSAConfig {
229        block_size,
230        max_path_depth,
231        base_shift,
232        target_sparsity,
233    };
234    to_handle(config) as *mut VSAConfigHandle
235}
236
237/// Free a VSAConfig
238///
239/// # Safety
240/// - `handle` must be a valid pointer from `vsa_config_new*`
241/// - Must not use `handle` after calling this function
242#[no_mangle]
243pub unsafe extern "C" fn vsa_config_free(handle: *mut VSAConfigHandle) {
244    if !handle.is_null() {
245        let _ = from_handle(handle as *mut ReversibleVSAConfig);
246    }
247}
248
249/// Encode data into a SparseVec
250///
251/// # Safety
252/// - All handles must be valid
253/// - `data` must point to valid bytes
254/// - `len` must be correct
255/// - `path` may be null or must be null-terminated UTF-8
256/// - Returns a handle that must be freed with `sparse_vec_free`
257#[no_mangle]
258pub unsafe extern "C" fn vsa_encode_data(
259    config: *const VSAConfigHandle,
260    data: *const u8,
261    len: usize,
262    path: *const c_char,
263) -> *mut SparseVecHandle {
264    let config_ref = borrow_handle(config as *const ReversibleVSAConfig);
265    let bytes = slice::from_raw_parts(data, len);
266
267    let path_str = if path.is_null() {
268        None
269    } else {
270        CStr::from_ptr(path).to_str().ok()
271    };
272
273    let vec = SparseVec::encode_data(bytes, config_ref, path_str);
274    to_handle(vec) as *mut SparseVecHandle
275}
276
277/// Decode a SparseVec back to data
278///
279/// # Safety
280/// - All handles must be valid
281/// - `path` may be null or must be null-terminated UTF-8
282/// - Returns a ByteBuffer that must be freed with `byte_buffer_free`
283#[no_mangle]
284pub unsafe extern "C" fn vsa_decode_data(
285    config: *const VSAConfigHandle,
286    vec: *const SparseVecHandle,
287    path: *const c_char,
288    expected_size: usize,
289) -> ByteBuffer {
290    let config_ref = borrow_handle(config as *const ReversibleVSAConfig);
291    let vec_ref = borrow_handle(vec as *const SparseVec);
292
293    let path_str = if path.is_null() {
294        None
295    } else {
296        CStr::from_ptr(path).to_str().ok()
297    };
298
299    let mut decoded = vec_ref.decode_data(config_ref, path_str, expected_size);
300    let len = decoded.len();
301    let capacity = decoded.capacity();
302    let data = decoded.as_mut_ptr();
303    std::mem::forget(decoded);
304
305    ByteBuffer {
306        data,
307        len,
308        capacity,
309    }
310}
311
312// ============================================================================
313// ByteBuffer Management
314// ============================================================================
315
316/// Free a ByteBuffer returned by FFI functions
317///
318/// # Safety
319/// - `buffer` must be a valid ByteBuffer returned by this library
320/// - Must not use `buffer` after calling this function
321#[no_mangle]
322pub unsafe extern "C" fn byte_buffer_free(buffer: ByteBuffer) {
323    if !buffer.data.is_null() {
324        let _ = Vec::from_raw_parts(buffer.data, buffer.len, buffer.capacity);
325    }
326}
327
328// ============================================================================
329// Error Handling
330// ============================================================================
331
332/// Get the last error message (if any)
333///
334/// # Safety
335/// - Returns a pointer to a static string (do not free)
336/// - Returns null if no error occurred
337#[no_mangle]
338pub unsafe extern "C" fn embeddenator_last_error() -> *const c_char {
339    // Thread-local error storage could be added here
340    ptr::null()
341}
342
343#[cfg(test)]
344mod tests {
345    use super::*;
346
347    #[test]
348    fn test_sparse_vec_create_free() {
349        unsafe {
350            let handle = sparse_vec_new();
351            assert!(!handle.is_null());
352            sparse_vec_free(handle);
353        }
354    }
355
356    #[test]
357    fn test_sparse_vec_operations() {
358        unsafe {
359            let a = sparse_vec_new();
360            let b = sparse_vec_new();
361
362            let bundled = sparse_vec_bundle(a, b);
363            assert!(!bundled.is_null());
364
365            let bound = sparse_vec_bind(a, b);
366            assert!(!bound.is_null());
367
368            let cosine = sparse_vec_cosine(a, b);
369            assert!(cosine.is_finite());
370
371            sparse_vec_free(bundled);
372            sparse_vec_free(bound);
373            sparse_vec_free(a);
374            sparse_vec_free(b);
375        }
376    }
377
378    #[test]
379    fn test_sparse_vec_json_roundtrip() {
380        unsafe {
381            let vec = sparse_vec_new();
382
383            let buffer = sparse_vec_to_json(vec);
384            assert!(!buffer.data.is_null());
385            assert!(buffer.len > 0);
386
387            let decoded = sparse_vec_from_json(buffer.data, buffer.len);
388            assert!(!decoded.is_null());
389
390            byte_buffer_free(buffer);
391            sparse_vec_free(vec);
392            sparse_vec_free(decoded);
393        }
394    }
395
396    #[test]
397    fn test_vsa_config() {
398        unsafe {
399            let config = vsa_config_new();
400            assert!(!config.is_null());
401            vsa_config_free(config);
402
403            let custom = vsa_config_new_custom(256, 10, 1000, 200);
404            assert!(!custom.is_null());
405            vsa_config_free(custom);
406        }
407    }
408
409    #[test]
410    fn test_encode_decode() {
411        unsafe {
412            let config = vsa_config_new();
413            let data = b"Hello, FFI!";
414
415            let vec = vsa_encode_data(config, data.as_ptr(), data.len(), ptr::null());
416            assert!(!vec.is_null());
417
418            let decoded = vsa_decode_data(config, vec, ptr::null(), data.len());
419            assert!(!decoded.data.is_null());
420            assert_eq!(decoded.len, data.len());
421
422            byte_buffer_free(decoded);
423            sparse_vec_free(vec);
424            vsa_config_free(config);
425        }
426    }
427}