use std::ffi::{CStr, CString};
use std::os::raw::{c_char, c_int};
use std::panic::{catch_unwind, AssertUnwindSafe};
use std::ptr;
use std::slice;
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum IpfrsErrorCode {
Success = 0,
NullPointer = -1,
InvalidUtf8 = -2,
InvalidCid = -3,
NotFound = -4,
IoError = -5,
OutOfMemory = -6,
InternalError = -7,
InvalidArgument = -8,
Timeout = -9,
Unknown = -99,
}
#[repr(C)]
pub struct IpfrsClient {
_private: [u8; 0],
}
#[repr(C)]
pub struct IpfrsBlock {
_private: [u8; 0],
}
struct ClientInner {
_placeholder: u8,
}
#[allow(dead_code)]
struct BlockInner {
cid: String,
data: Vec<u8>,
}
thread_local! {
static LAST_ERROR: std::cell::RefCell<Option<String>> = const { std::cell::RefCell::new(None) };
}
fn set_last_error(msg: String) {
LAST_ERROR.with(|e| {
*e.borrow_mut() = Some(msg);
});
}
fn clear_last_error() {
LAST_ERROR.with(|e| {
*e.borrow_mut() = None;
});
}
#[no_mangle]
pub unsafe extern "C" fn ipfrs_client_new(config_path: *const c_char) -> *mut IpfrsClient {
clear_last_error();
let result = catch_unwind(AssertUnwindSafe(|| {
let _config = if !config_path.is_null() {
let c_str = unsafe { CStr::from_ptr(config_path) };
match c_str.to_str() {
Ok(s) => Some(s.to_string()),
Err(_) => {
set_last_error("Invalid UTF-8 in config_path".to_string());
return ptr::null_mut();
}
}
} else {
None
};
let inner = Box::new(ClientInner { _placeholder: 0 });
Box::into_raw(inner) as *mut IpfrsClient
}));
match result {
Ok(ptr) => ptr,
Err(_) => {
set_last_error("Panic occurred in ipfrs_client_new".to_string());
ptr::null_mut()
}
}
}
#[no_mangle]
pub unsafe extern "C" fn ipfrs_client_free(client: *mut IpfrsClient) {
if client.is_null() {
return;
}
let _ = catch_unwind(AssertUnwindSafe(|| unsafe {
let _ = Box::from_raw(client as *mut ClientInner);
}));
}
#[no_mangle]
pub unsafe extern "C" fn ipfrs_add(
client: *mut IpfrsClient,
data: *const u8,
data_len: usize,
out_cid: *mut *mut c_char,
) -> c_int {
clear_last_error();
if client.is_null() {
set_last_error("client is NULL".to_string());
return IpfrsErrorCode::NullPointer as c_int;
}
if data.is_null() {
set_last_error("data is NULL".to_string());
return IpfrsErrorCode::NullPointer as c_int;
}
if out_cid.is_null() {
set_last_error("out_cid is NULL".to_string());
return IpfrsErrorCode::NullPointer as c_int;
}
let result = catch_unwind(AssertUnwindSafe(|| {
let _inner = &*(client as *mut ClientInner);
let data_slice = unsafe { slice::from_raw_parts(data, data_len) };
let mock_cid = format!("bafkreidummy{:016x}", data_slice.len());
match CString::new(mock_cid) {
Ok(c_string) => {
unsafe {
*out_cid = c_string.into_raw();
}
IpfrsErrorCode::Success as c_int
}
Err(_) => {
set_last_error("Failed to create CID string".to_string());
IpfrsErrorCode::InternalError as c_int
}
}
}));
match result {
Ok(code) => code,
Err(_) => {
set_last_error("Panic occurred in ipfrs_add".to_string());
IpfrsErrorCode::InternalError as c_int
}
}
}
#[no_mangle]
pub unsafe extern "C" fn ipfrs_get(
client: *mut IpfrsClient,
cid: *const c_char,
out_data: *mut *mut u8,
out_len: *mut usize,
) -> c_int {
clear_last_error();
if client.is_null() {
set_last_error("client is NULL".to_string());
return IpfrsErrorCode::NullPointer as c_int;
}
if cid.is_null() {
set_last_error("cid is NULL".to_string());
return IpfrsErrorCode::NullPointer as c_int;
}
if out_data.is_null() {
set_last_error("out_data is NULL".to_string());
return IpfrsErrorCode::NullPointer as c_int;
}
if out_len.is_null() {
set_last_error("out_len is NULL".to_string());
return IpfrsErrorCode::NullPointer as c_int;
}
let result = catch_unwind(AssertUnwindSafe(|| {
let _inner = &*(client as *mut ClientInner);
let c_str = unsafe { CStr::from_ptr(cid) };
let cid_str = match c_str.to_str() {
Ok(s) => s,
Err(_) => {
set_last_error("Invalid UTF-8 in CID".to_string());
return IpfrsErrorCode::InvalidUtf8 as c_int;
}
};
let mock_data = format!("Data for CID: {}", cid_str).into_bytes();
let len = mock_data.len();
let mut boxed_data = mock_data.into_boxed_slice();
let data_ptr = boxed_data.as_mut_ptr();
std::mem::forget(boxed_data);
unsafe {
*out_data = data_ptr;
*out_len = len;
}
IpfrsErrorCode::Success as c_int
}));
match result {
Ok(code) => code,
Err(_) => {
set_last_error("Panic occurred in ipfrs_get".to_string());
IpfrsErrorCode::InternalError as c_int
}
}
}
#[no_mangle]
pub unsafe extern "C" fn ipfrs_has(
client: *mut IpfrsClient,
cid: *const c_char,
out_exists: *mut c_int,
) -> c_int {
clear_last_error();
if client.is_null() {
set_last_error("client is NULL".to_string());
return IpfrsErrorCode::NullPointer as c_int;
}
if cid.is_null() {
set_last_error("cid is NULL".to_string());
return IpfrsErrorCode::NullPointer as c_int;
}
if out_exists.is_null() {
set_last_error("out_exists is NULL".to_string());
return IpfrsErrorCode::NullPointer as c_int;
}
let result = catch_unwind(AssertUnwindSafe(|| {
let _inner = &*(client as *mut ClientInner);
let c_str = unsafe { CStr::from_ptr(cid) };
let _cid_str = match c_str.to_str() {
Ok(s) => s,
Err(_) => {
set_last_error("Invalid UTF-8 in CID".to_string());
return IpfrsErrorCode::InvalidUtf8 as c_int;
}
};
unsafe {
*out_exists = 1;
}
IpfrsErrorCode::Success as c_int
}));
match result {
Ok(code) => code,
Err(_) => {
set_last_error("Panic occurred in ipfrs_has".to_string());
IpfrsErrorCode::InternalError as c_int
}
}
}
#[no_mangle]
pub extern "C" fn ipfrs_get_last_error() -> *const c_char {
LAST_ERROR.with(|e| {
e.borrow()
.as_ref()
.map_or(ptr::null(), |s| s.as_ptr() as *const c_char)
})
}
#[no_mangle]
pub unsafe extern "C" fn ipfrs_string_free(s: *mut c_char) {
if s.is_null() {
return;
}
let _ = catch_unwind(AssertUnwindSafe(|| unsafe {
let _ = CString::from_raw(s);
}));
}
#[no_mangle]
pub unsafe extern "C" fn ipfrs_data_free(data: *mut u8, len: usize) {
if data.is_null() {
return;
}
let _ = catch_unwind(AssertUnwindSafe(|| unsafe {
let _ = Vec::from_raw_parts(data, len, len);
}));
}
#[no_mangle]
pub extern "C" fn ipfrs_version() -> *const c_char {
static VERSION: &[u8] = b"ipfrs-interface 0.1.0\0";
VERSION.as_ptr() as *const c_char
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_client_lifecycle() {
unsafe {
let client = ipfrs_client_new(ptr::null());
assert!(!client.is_null());
ipfrs_client_free(client);
}
}
#[test]
fn test_add_and_get() {
unsafe {
let client = ipfrs_client_new(ptr::null());
assert!(!client.is_null());
let data = b"Hello, IPFRS!";
let mut cid_ptr: *mut c_char = ptr::null_mut();
let result = ipfrs_add(client, data.as_ptr(), data.len(), &mut cid_ptr);
assert_eq!(result, IpfrsErrorCode::Success as c_int);
assert!(!cid_ptr.is_null());
let mut out_data: *mut u8 = ptr::null_mut();
let mut out_len: usize = 0;
let result = ipfrs_get(client, cid_ptr, &mut out_data, &mut out_len);
assert_eq!(result, IpfrsErrorCode::Success as c_int);
assert!(!out_data.is_null());
assert!(out_len > 0);
ipfrs_string_free(cid_ptr);
ipfrs_data_free(out_data, out_len);
ipfrs_client_free(client);
}
}
#[test]
fn test_has_block() {
unsafe {
let client = ipfrs_client_new(ptr::null());
assert!(!client.is_null());
let cid = CString::new("bafytest123").unwrap();
let mut exists: c_int = 0;
let result = ipfrs_has(client, cid.as_ptr(), &mut exists);
assert_eq!(result, IpfrsErrorCode::Success as c_int);
ipfrs_client_free(client);
}
}
#[test]
fn test_null_pointer_handling() {
unsafe {
let mut cid_ptr: *mut c_char = ptr::null_mut();
let data = b"test";
let result = ipfrs_add(ptr::null_mut(), data.as_ptr(), data.len(), &mut cid_ptr);
assert_eq!(result, IpfrsErrorCode::NullPointer as c_int);
}
}
#[test]
fn test_version() {
let version = ipfrs_version();
assert!(!version.is_null());
unsafe {
let c_str = CStr::from_ptr(version);
let version_str = c_str.to_str().unwrap();
assert!(version_str.contains("ipfrs-interface"));
}
}
}