use std::alloc::{alloc, dealloc, Layout};
use std::ptr;
#[repr(C)]
pub struct FfiBuffer {
pub data: *mut u8,
pub len: usize,
pub capacity: usize,
}
unsafe impl Send for FfiBuffer {}
unsafe impl Sync for FfiBuffer {}
impl FfiBuffer {
#[inline]
pub fn null() -> Self {
FfiBuffer {
data: ptr::null_mut(),
len: 0,
capacity: 0,
}
}
pub fn new(capacity: usize) -> Self {
if capacity == 0 {
return Self::null();
}
let layout = Layout::array::<u8>(capacity).expect("capacity overflow");
let data = unsafe { alloc(layout) };
if data.is_null() {
panic!("ffi_buffer_alloc: out of memory (capacity={})", capacity);
}
FfiBuffer {
data,
len: 0,
capacity,
}
}
pub fn from_vec(mut vec: Vec<u8>) -> Self {
vec.shrink_to_fit();
let buf = FfiBuffer {
data: vec.as_mut_ptr(),
len: vec.len(),
capacity: vec.capacity(),
};
std::mem::forget(vec);
buf
}
pub fn from_json<T: serde::Serialize>(value: &T) -> Result<Self, crate::errors::FfiError> {
let json = serde_json::to_vec(value)
.map_err(|e| crate::errors::FfiError::Serialization(e.to_string()))?;
Ok(Self::from_vec(json))
}
#[inline]
pub unsafe fn as_slice(&self) -> &[u8] {
if self.data.is_null() || self.len == 0 {
return &[];
}
std::slice::from_raw_parts(self.data, self.len)
}
pub unsafe fn to_json<T: serde::de::DeserializeOwned>(
&self,
) -> Result<T, crate::errors::FfiError> {
serde_json::from_slice(self.as_slice())
.map_err(|e| crate::errors::FfiError::Serialization(e.to_string()))
}
pub unsafe fn dealloc(self) {
if self.data.is_null() || self.capacity == 0 {
return;
}
let layout = Layout::array::<u8>(self.capacity).expect("capacity overflow");
dealloc(self.data, layout);
}
}
#[no_mangle]
pub extern "C" fn ffi_buffer_alloc(capacity: usize) -> FfiBuffer {
FfiBuffer::new(capacity)
}
#[no_mangle]
pub extern "C" fn ffi_buffer_free(buf: FfiBuffer) {
unsafe { buf.dealloc() };
}
#[repr(C)]
pub struct FfiString {
pub data: *mut u8,
pub len: usize,
}
unsafe impl Send for FfiString {}
unsafe impl Sync for FfiString {}
impl FfiString {
#[inline]
pub fn null() -> Self {
FfiString {
data: ptr::null_mut(),
len: 0,
}
}
pub fn new(s: &str) -> Self {
let bytes = s.as_bytes();
if bytes.is_empty() {
return Self::null();
}
let layout = Layout::array::<u8>(bytes.len()).expect("string too large");
let data = unsafe { alloc(layout) };
if data.is_null() {
panic!("ffi_string_alloc: out of memory");
}
unsafe { ptr::copy_nonoverlapping(bytes.as_ptr(), data, bytes.len()) };
FfiString {
data,
len: bytes.len(),
}
}
pub unsafe fn as_str(&self) -> &str {
if self.data.is_null() || self.len == 0 {
return "";
}
let bytes = std::slice::from_raw_parts(self.data as *const u8, self.len);
std::str::from_utf8_unchecked(bytes)
}
pub unsafe fn dealloc(self) {
if self.data.is_null() || self.len == 0 {
return;
}
let layout = Layout::array::<u8>(self.len).expect("string too large");
dealloc(self.data, layout);
}
}
#[no_mangle]
pub unsafe extern "C" fn ffi_string_alloc(str: *const u8, len: usize) -> FfiString {
if str.is_null() || len == 0 {
return FfiString::null();
}
let bytes = std::slice::from_raw_parts(str, len);
let s = match std::str::from_utf8(bytes) {
Ok(s) => s,
Err(_) => return FfiString::null(), };
FfiString::new(s)
}
#[no_mangle]
pub extern "C" fn ffi_string_free(str: FfiString) {
unsafe { str.dealloc() };
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn buffer_new_zero_capacity_is_null() {
let buf = FfiBuffer::new(0);
assert!(buf.data.is_null());
assert_eq!(buf.len, 0);
assert_eq!(buf.capacity, 0);
}
#[test]
fn buffer_alloc_and_free() {
let buf = FfiBuffer::new(64);
assert!(!buf.data.is_null());
assert_eq!(buf.capacity, 64);
assert_eq!(buf.len, 0);
ffi_buffer_free(buf);
}
#[test]
fn buffer_from_vec_round_trip() {
let data = b"hello ffi".to_vec();
let buf = FfiBuffer::from_vec(data);
assert_eq!(buf.len, 9);
let slice = unsafe { buf.as_slice() };
assert_eq!(slice, b"hello ffi");
ffi_buffer_free(buf);
}
#[test]
fn buffer_from_json_round_trip() {
#[derive(serde::Serialize, serde::Deserialize, PartialEq, Debug)]
struct Msg {
value: u32,
}
let msg = Msg { value: 42 };
let buf = FfiBuffer::from_json(&msg).unwrap();
let decoded: Msg = unsafe { buf.to_json() }.unwrap();
assert_eq!(decoded, msg);
ffi_buffer_free(buf);
}
#[test]
fn string_null_on_zero_len() {
let s = FfiString::new("");
assert!(s.data.is_null());
}
#[test]
fn string_roundtrip() {
let s = FfiString::new("hello world");
assert_eq!(s.len, 11);
let back = unsafe { s.as_str() };
assert_eq!(back, "hello world");
ffi_string_free(s);
}
}