#![allow(non_camel_case_types)]
#![allow(unsafe_code)]
#![allow(clippy::not_unsafe_ptr_arg_deref)]
pub mod arena;
use std::ffi::{CStr, CString};
use std::os::raw::c_char;
use std::ptr;
use std::sync::atomic::{AtomicBool, Ordering};
pub type weaveffi_handle_t = u64;
#[repr(C)]
#[derive(Debug)]
pub struct weaveffi_error {
pub code: i32,
pub message: *const c_char,
}
impl Default for weaveffi_error {
fn default() -> Self {
Self {
code: 0,
message: ptr::null(),
}
}
}
pub fn error_set_ok(out_err: *mut weaveffi_error) {
if out_err.is_null() {
return;
}
let err = unsafe { &mut *out_err };
if !err.message.is_null() {
unsafe { drop(CString::from_raw(err.message as *mut c_char)) };
}
err.code = 0;
err.message = ptr::null();
}
pub fn error_set(out_err: *mut weaveffi_error, code: i32, message: &str) {
if out_err.is_null() {
return;
}
let err = unsafe { &mut *out_err };
if !err.message.is_null() {
unsafe { drop(CString::from_raw(err.message as *mut c_char)) };
}
err.code = code;
let owned_message = message.replace('\0', "");
let cstr = CString::new(owned_message).expect("CString::new sanitized input");
err.message = cstr.into_raw();
}
pub fn result_to_out_err<T, E: std::fmt::Display>(
result: Result<T, E>,
out_err: *mut weaveffi_error,
) -> Option<T> {
match result {
Ok(value) => {
error_set_ok(out_err);
Some(value)
}
Err(e) => {
error_set(out_err, -1, &e.to_string());
None
}
}
}
pub fn string_to_c_ptr(s: impl AsRef<str>) -> *const c_char {
let s = s.as_ref();
let sanitized = if s.as_bytes().contains(&0) {
s.replace('\0', "")
} else {
s.to_owned()
};
let cstr = CString::new(sanitized).expect("string_to_c_ptr: unexpected NUL after sanitization");
cstr.into_raw()
}
pub fn free_string(ptr: *const c_char) {
if ptr.is_null() {
return;
}
unsafe { drop(CString::from_raw(ptr as *mut c_char)) };
}
pub fn free_bytes(ptr: *mut u8, len: usize) {
if ptr.is_null() {
return;
}
unsafe { drop(Box::from_raw(std::ptr::slice_from_raw_parts_mut(ptr, len))) };
}
pub fn error_clear(err: *mut weaveffi_error) {
error_set_ok(err);
}
#[repr(C)]
pub struct weaveffi_cancel_token {
cancelled: AtomicBool,
}
pub fn cancel_token_create() -> *mut weaveffi_cancel_token {
Box::into_raw(Box::new(weaveffi_cancel_token {
cancelled: AtomicBool::new(false),
}))
}
pub fn cancel_token_cancel(token: *mut weaveffi_cancel_token) {
if token.is_null() {
return;
}
let t = unsafe { &*token };
t.cancelled.store(true, Ordering::Release);
}
pub fn cancel_token_is_cancelled(token: *const weaveffi_cancel_token) -> bool {
if token.is_null() {
return false;
}
let t = unsafe { &*token };
t.cancelled.load(Ordering::Acquire)
}
pub fn cancel_token_destroy(token: *mut weaveffi_cancel_token) {
if token.is_null() {
return;
}
unsafe { drop(Box::from_raw(token)) };
}
pub fn c_ptr_to_string(ptr: *const c_char) -> Option<String> {
if ptr.is_null() {
return None;
}
let c = unsafe { CStr::from_ptr(ptr) };
c.to_str().ok().map(|s| s.to_owned())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn string_roundtrip_and_free() {
let ptr = string_to_c_ptr("hello world");
assert!(!ptr.is_null());
let recovered = c_ptr_to_string(ptr).unwrap();
assert_eq!(recovered, "hello world");
free_string(ptr);
}
#[test]
fn free_string_null_is_safe() {
free_string(ptr::null());
}
#[test]
fn free_bytes_null_is_safe() {
free_bytes(ptr::null_mut(), 0);
}
#[test]
fn bytes_alloc_and_free() {
let data: Vec<u8> = vec![1, 2, 3, 4, 5];
let len = data.len();
let boxed = data.into_boxed_slice();
let ptr = Box::into_raw(boxed) as *mut u8;
free_bytes(ptr, len);
}
#[test]
fn error_default_is_ok() {
let err = weaveffi_error::default();
assert_eq!(err.code, 0);
assert!(err.message.is_null());
}
#[test]
fn error_set_and_clear() {
let mut err = weaveffi_error::default();
error_set(&mut err, -1, "something went wrong");
assert_eq!(err.code, -1);
assert!(!err.message.is_null());
let msg = c_ptr_to_string(err.message).unwrap();
assert_eq!(msg, "something went wrong");
error_clear(&mut err);
assert_eq!(err.code, 0);
assert!(err.message.is_null());
}
#[test]
fn error_clear_null_is_safe() {
error_clear(ptr::null_mut());
}
#[test]
fn error_set_ok_frees_prior_message() {
let mut err = weaveffi_error::default();
error_set(&mut err, 1, "first");
error_set_ok(&mut err);
assert_eq!(err.code, 0);
assert!(err.message.is_null());
}
#[test]
fn error_set_replaces_prior_message() {
let mut err = weaveffi_error::default();
error_set(&mut err, 1, "first");
error_set(&mut err, 2, "second");
assert_eq!(err.code, 2);
let msg = c_ptr_to_string(err.message).unwrap();
assert_eq!(msg, "second");
error_clear(&mut err);
}
#[test]
fn result_to_out_err_ok_path() {
let mut err = weaveffi_error::default();
let val: Result<i32, String> = Ok(42);
let opt = result_to_out_err(val, &mut err);
assert_eq!(opt, Some(42));
assert_eq!(err.code, 0);
assert!(err.message.is_null());
}
#[test]
fn result_to_out_err_error_path() {
let mut err = weaveffi_error::default();
let val: Result<i32, String> = Err("bad input".to_string());
let opt = result_to_out_err(val, &mut err);
assert_eq!(opt, None);
assert_eq!(err.code, -1);
let msg = c_ptr_to_string(err.message).unwrap();
assert_eq!(msg, "bad input");
error_clear(&mut err);
}
#[test]
fn string_with_interior_nul_is_sanitized() {
let ptr = string_to_c_ptr("hel\0lo");
let recovered = c_ptr_to_string(ptr).unwrap();
assert_eq!(recovered, "hello");
free_string(ptr);
}
#[test]
fn c_ptr_to_string_null_returns_none() {
assert_eq!(c_ptr_to_string(ptr::null()), None);
}
#[test]
fn cancel_token_lifecycle() {
let token = cancel_token_create();
assert!(!token.is_null());
assert!(!cancel_token_is_cancelled(token));
cancel_token_cancel(token);
assert!(cancel_token_is_cancelled(token));
cancel_token_destroy(token);
}
#[test]
fn cancel_token_null_is_safe() {
cancel_token_cancel(ptr::null_mut());
assert!(!cancel_token_is_cancelled(ptr::null()));
cancel_token_destroy(ptr::null_mut());
}
}