use crate::{
compute_residual_error, evaluate_cubic_fixed, evaluate_linear_fixed, evaluate_quadratic_fixed,
fit_constant_fixed, fit_cubic_fixed, fit_linear_fixed, fit_quadratic_fixed, int_to_q16,
q16_to_f32, q16_to_int, should_use_linear,
};
#[repr(C)]
pub struct AliceLinearResult {
pub slope: i32,
pub intercept: i32,
}
#[repr(C)]
pub struct AliceQuadraticResult {
pub a: i32,
pub b: i32,
pub c: i32,
}
#[repr(C)]
pub struct AliceCubicResult {
pub a: i32,
pub b: i32,
pub c: i32,
pub d: i32,
}
#[no_mangle]
pub unsafe extern "C" fn alice_fit_linear(data: *const i32, len: usize) -> AliceLinearResult {
if data.is_null() || len == 0 {
return AliceLinearResult {
slope: 0,
intercept: 0,
};
}
let slice = core::slice::from_raw_parts(data, len);
let (slope, intercept) = fit_linear_fixed(slice);
AliceLinearResult { slope, intercept }
}
#[no_mangle]
pub const extern "C" fn alice_evaluate_linear(slope: i32, intercept: i32, x: i32) -> i32 {
evaluate_linear_fixed(slope, intercept, x)
}
#[no_mangle]
pub unsafe extern "C" fn alice_fit_quadratic(data: *const i32, len: usize) -> AliceQuadraticResult {
if data.is_null() || len == 0 {
return AliceQuadraticResult { a: 0, b: 0, c: 0 };
}
let slice = core::slice::from_raw_parts(data, len);
let (a, b, c) = fit_quadratic_fixed(slice);
AliceQuadraticResult { a, b, c }
}
#[no_mangle]
pub const extern "C" fn alice_evaluate_quadratic(a: i32, b: i32, c: i32, x: i32) -> i32 {
evaluate_quadratic_fixed(a, b, c, x)
}
#[no_mangle]
pub unsafe extern "C" fn alice_fit_cubic(data: *const i32, len: usize) -> AliceCubicResult {
if data.is_null() || len == 0 {
return AliceCubicResult {
a: 0,
b: 0,
c: 0,
d: 0,
};
}
let slice = core::slice::from_raw_parts(data, len);
let (a, b, c, d) = fit_cubic_fixed(slice);
AliceCubicResult { a, b, c, d }
}
#[no_mangle]
pub const extern "C" fn alice_evaluate_cubic(a: i32, b: i32, c: i32, d: i32, x: i32) -> i32 {
evaluate_cubic_fixed(a, b, c, d, x)
}
#[no_mangle]
pub const unsafe extern "C" fn alice_fit_constant(data: *const i32, len: usize) -> i32 {
if data.is_null() || len == 0 {
return 0;
}
let slice = core::slice::from_raw_parts(data, len);
fit_constant_fixed(slice)
}
#[no_mangle]
pub const extern "C" fn alice_int_to_q16(i: i32) -> i32 {
int_to_q16(i)
}
#[no_mangle]
pub const extern "C" fn alice_q16_to_int(q: i32) -> i32 {
q16_to_int(q)
}
#[no_mangle]
pub extern "C" fn alice_q16_to_f32(q: i32) -> f32 {
q16_to_f32(q)
}
#[no_mangle]
pub unsafe extern "C" fn alice_should_use_linear(data: *const i32, len: usize) -> bool {
if data.is_null() || len == 0 {
return false;
}
let slice = core::slice::from_raw_parts(data, len);
should_use_linear(slice)
}
#[no_mangle]
pub const unsafe extern "C" fn alice_residual_error(
data: *const i32,
len: usize,
slope: i32,
intercept: i32,
) -> i64 {
if data.is_null() || len == 0 {
return 0;
}
let slice = core::slice::from_raw_parts(data, len);
compute_residual_error(slice, slope, intercept)
}
#[cfg(feature = "std")]
#[no_mangle]
pub unsafe extern "C" fn alice_fit_linear_robust(
data: *const i32,
len: usize,
mad_k: i32,
) -> AliceLinearResult {
if data.is_null() || len == 0 {
return AliceLinearResult {
slope: 0,
intercept: 0,
};
}
let slice = core::slice::from_raw_parts(data, len);
let (slope, intercept) = crate::fit_linear_robust(slice, mad_k);
AliceLinearResult { slope, intercept }
}
#[no_mangle]
pub unsafe extern "C" fn alice_fit_linear_simd(data: *const i32, len: usize) -> AliceLinearResult {
if data.is_null() || len == 0 {
return AliceLinearResult {
slope: 0,
intercept: 0,
};
}
let slice = core::slice::from_raw_parts(data, len);
let (slope, intercept) = crate::fit_linear_simd(slice);
AliceLinearResult { slope, intercept }
}
#[cfg(feature = "std")]
#[no_mangle]
pub unsafe extern "C" fn alice_filter_outliers_mad(
data: *const i32,
len: usize,
k: i32,
out: *mut i32,
out_capacity: usize,
) -> usize {
if data.is_null() || out.is_null() || len == 0 || out_capacity == 0 {
return 0;
}
let slice = core::slice::from_raw_parts(data, len);
let filtered = crate::filter_outliers_mad(slice, k);
let count = filtered.len().min(out_capacity);
core::ptr::copy_nonoverlapping(filtered.as_ptr(), out, count);
count
}
#[no_mangle]
pub unsafe extern "C" fn alice_delta_encode(pairs: *mut i32, num_pairs: usize) -> usize {
if pairs.is_null() || num_pairs < 2 {
return num_pairs;
}
let mut prev_s = *pairs;
let mut prev_i = *pairs.add(1);
for idx in 1..num_pairs {
let offset = idx * 2;
let cur_s = *pairs.add(offset);
let cur_i = *pairs.add(offset + 1);
*pairs.add(offset) = cur_s - prev_s;
*pairs.add(offset + 1) = cur_i - prev_i;
prev_s = cur_s;
prev_i = cur_i;
}
num_pairs
}
#[no_mangle]
pub unsafe extern "C" fn alice_delta_decode(pairs: *mut i32, num_pairs: usize) -> usize {
if pairs.is_null() || num_pairs < 2 {
return num_pairs;
}
for idx in 1..num_pairs {
let offset = idx * 2;
*pairs.add(offset) += *pairs.add(offset - 2);
*pairs.add(offset + 1) += *pairs.add(offset - 1);
}
num_pairs
}
#[no_mangle]
pub unsafe extern "C" fn alice_zeroize(buf: *mut i32, len: usize) {
if buf.is_null() || len == 0 {
return;
}
for i in 0..len {
core::ptr::write_volatile(buf.add(i), 0);
}
core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst);
}
#[no_mangle]
pub const extern "C" fn alice_edge_version() -> *const u8 {
c"0.1.0".as_ptr().cast()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_ffi_fit_linear() {
let data = [100, 200, 300, 400, 500];
let result = unsafe { alice_fit_linear(data.as_ptr(), data.len()) };
assert_ne!(result.slope, 0);
}
#[test]
fn test_ffi_fit_linear_null() {
let result = unsafe { alice_fit_linear(core::ptr::null(), 0) };
assert_eq!(result.slope, 0);
assert_eq!(result.intercept, 0);
}
#[test]
fn test_ffi_evaluate_linear() {
let val = alice_evaluate_linear(int_to_q16(10), int_to_q16(5), 3);
let result = q16_to_int(val);
assert_eq!(result, 35);
}
#[test]
fn test_ffi_fit_quadratic() {
let data = [0, 1, 4, 9, 16]; let result = unsafe { alice_fit_quadratic(data.as_ptr(), data.len()) };
let _ = (result.a, result.b, result.c);
}
#[test]
fn test_ffi_fit_cubic() {
let data = [0, 1, 8, 27, 64, 125, 216, 343];
let result = unsafe { alice_fit_cubic(data.as_ptr(), data.len()) };
let _ = (result.a, result.b, result.c, result.d);
}
#[test]
fn test_ffi_fit_constant() {
let data = [500, 500, 500, 500];
let mean = unsafe { alice_fit_constant(data.as_ptr(), data.len()) };
assert_eq!(q16_to_int(mean), 500);
}
#[test]
fn test_ffi_q16_roundtrip() {
assert_eq!(alice_q16_to_int(alice_int_to_q16(42)), 42);
}
#[test]
fn test_ffi_q16_to_f32() {
let f = alice_q16_to_f32(alice_int_to_q16(10));
assert!((f - 10.0).abs() < 0.01);
}
#[test]
fn test_ffi_should_use_linear() {
let rising = [100, 200, 300, 400, 500];
let constant = [500, 500, 500, 500, 500];
let linear = unsafe { alice_should_use_linear(rising.as_ptr(), rising.len()) };
let not_linear = unsafe { alice_should_use_linear(constant.as_ptr(), constant.len()) };
assert!(linear);
assert!(!not_linear);
}
#[test]
fn test_ffi_residual_error() {
let data = [100, 200, 300, 400, 500];
let result = unsafe { alice_fit_linear(data.as_ptr(), data.len()) };
let err = unsafe {
alice_residual_error(data.as_ptr(), data.len(), result.slope, result.intercept)
};
assert!(err < 100);
}
#[test]
fn test_ffi_version() {
let ptr = alice_edge_version();
assert!(!ptr.is_null());
let cstr = unsafe { core::ffi::CStr::from_ptr(ptr.cast::<core::ffi::c_char>()) };
assert_eq!(cstr.to_str().unwrap(), "0.1.0");
}
#[test]
fn test_ffi_evaluate_quadratic() {
let val = alice_evaluate_quadratic(int_to_q16(1), int_to_q16(2), int_to_q16(3), 2);
let result = q16_to_int(val);
assert_eq!(result, 11);
}
#[test]
fn test_ffi_evaluate_cubic() {
let val = alice_evaluate_cubic(
int_to_q16(1),
int_to_q16(0),
int_to_q16(0),
int_to_q16(0),
2,
);
let result = q16_to_int(val);
assert_eq!(result, 8);
}
#[test]
fn test_ffi_fit_linear_robust() {
let data = [100, 200, 300, 9999, 500]; let result = unsafe { alice_fit_linear_robust(data.as_ptr(), data.len(), int_to_q16(3)) };
let _ = (result.slope, result.intercept);
}
#[test]
fn test_ffi_fit_linear_simd() {
let data = [100, 200, 300, 400, 500, 600, 700, 800, 900, 1000];
let result = unsafe { alice_fit_linear_simd(data.as_ptr(), data.len()) };
assert_ne!(result.slope, 0);
}
#[test]
fn test_ffi_filter_outliers_mad() {
let data = [100, 200, 300, 9999, 500];
let mut out = [0i32; 10];
let count = unsafe {
alice_filter_outliers_mad(
data.as_ptr(),
data.len(),
int_to_q16(2),
out.as_mut_ptr(),
out.len(),
)
};
assert!(count > 0 && count <= data.len());
}
#[test]
fn test_ffi_delta_encode_decode_roundtrip() {
let mut pairs = [100, 200, 110, 210, 130, 225];
let original = pairs;
unsafe { alice_delta_encode(pairs.as_mut_ptr(), 3) };
assert_eq!(pairs[0], 100);
assert_eq!(pairs[1], 200);
assert_eq!(pairs[2], 10); assert_eq!(pairs[3], 10);
unsafe { alice_delta_decode(pairs.as_mut_ptr(), 3) };
assert_eq!(pairs, original);
}
#[test]
fn test_ffi_zeroize() {
let mut buf = [42i32, 100, -1, i32::MAX, i32::MIN];
unsafe { alice_zeroize(buf.as_mut_ptr(), buf.len()) };
assert_eq!(buf, [0, 0, 0, 0, 0]);
}
#[test]
fn test_ffi_zeroize_null_safe() {
unsafe { alice_zeroize(core::ptr::null_mut(), 0) };
}
}