1use std::ffi::{CStr, CString};
19use std::os::raw::{c_char, c_int};
20use std::panic::{catch_unwind, AssertUnwindSafe};
21use std::ptr;
22use std::slice;
23
24#[repr(C)]
26#[derive(Debug, Clone, Copy, PartialEq, Eq)]
27pub enum IpfrsErrorCode {
28 Success = 0,
30 NullPointer = -1,
32 InvalidUtf8 = -2,
34 InvalidCid = -3,
36 NotFound = -4,
38 IoError = -5,
40 OutOfMemory = -6,
42 InternalError = -7,
44 InvalidArgument = -8,
46 Timeout = -9,
48 Unknown = -99,
50}
51
52#[repr(C)]
54pub struct IpfrsClient {
55 _private: [u8; 0],
56}
57
58#[repr(C)]
60pub struct IpfrsBlock {
61 _private: [u8; 0],
62}
63
64struct ClientInner {
66 _placeholder: u8,
72}
73
74#[allow(dead_code)]
76struct BlockInner {
77 cid: String,
78 data: Vec<u8>,
79}
80
81thread_local! {
83 static LAST_ERROR: std::cell::RefCell<Option<String>> = const { std::cell::RefCell::new(None) };
84}
85
86fn set_last_error(msg: String) {
88 LAST_ERROR.with(|e| {
89 *e.borrow_mut() = Some(msg);
90 });
91}
92
93fn clear_last_error() {
95 LAST_ERROR.with(|e| {
96 *e.borrow_mut() = None;
97 });
98}
99
100#[no_mangle]
116pub unsafe extern "C" fn ipfrs_client_new(config_path: *const c_char) -> *mut IpfrsClient {
117 clear_last_error();
118
119 let result = catch_unwind(AssertUnwindSafe(|| {
120 let _config = if !config_path.is_null() {
122 let c_str = unsafe { CStr::from_ptr(config_path) };
123 match c_str.to_str() {
124 Ok(s) => Some(s.to_string()),
125 Err(_) => {
126 set_last_error("Invalid UTF-8 in config_path".to_string());
127 return ptr::null_mut();
128 }
129 }
130 } else {
131 None
132 };
133
134 let inner = Box::new(ClientInner { _placeholder: 0 });
136
137 Box::into_raw(inner) as *mut IpfrsClient
138 }));
139
140 match result {
141 Ok(ptr) => ptr,
142 Err(_) => {
143 set_last_error("Panic occurred in ipfrs_client_new".to_string());
144 ptr::null_mut()
145 }
146 }
147}
148
149#[no_mangle]
157pub unsafe extern "C" fn ipfrs_client_free(client: *mut IpfrsClient) {
158 if client.is_null() {
159 return;
160 }
161
162 let _ = catch_unwind(AssertUnwindSafe(|| unsafe {
163 let _ = Box::from_raw(client as *mut ClientInner);
164 }));
165}
166
167#[no_mangle]
186pub unsafe extern "C" fn ipfrs_add(
187 client: *mut IpfrsClient,
188 data: *const u8,
189 data_len: usize,
190 out_cid: *mut *mut c_char,
191) -> c_int {
192 clear_last_error();
193
194 if client.is_null() {
196 set_last_error("client is NULL".to_string());
197 return IpfrsErrorCode::NullPointer as c_int;
198 }
199 if data.is_null() {
200 set_last_error("data is NULL".to_string());
201 return IpfrsErrorCode::NullPointer as c_int;
202 }
203 if out_cid.is_null() {
204 set_last_error("out_cid is NULL".to_string());
205 return IpfrsErrorCode::NullPointer as c_int;
206 }
207
208 let result = catch_unwind(AssertUnwindSafe(|| {
209 let _inner = &*(client as *mut ClientInner);
210 let data_slice = unsafe { slice::from_raw_parts(data, data_len) };
211
212 let mock_cid = format!("bafkreidummy{:016x}", data_slice.len());
220
221 match CString::new(mock_cid) {
223 Ok(c_string) => {
224 unsafe {
225 *out_cid = c_string.into_raw();
226 }
227 IpfrsErrorCode::Success as c_int
228 }
229 Err(_) => {
230 set_last_error("Failed to create CID string".to_string());
231 IpfrsErrorCode::InternalError as c_int
232 }
233 }
234 }));
235
236 match result {
237 Ok(code) => code,
238 Err(_) => {
239 set_last_error("Panic occurred in ipfrs_add".to_string());
240 IpfrsErrorCode::InternalError as c_int
241 }
242 }
243}
244
245#[no_mangle]
265pub unsafe extern "C" fn ipfrs_get(
266 client: *mut IpfrsClient,
267 cid: *const c_char,
268 out_data: *mut *mut u8,
269 out_len: *mut usize,
270) -> c_int {
271 clear_last_error();
272
273 if client.is_null() {
275 set_last_error("client is NULL".to_string());
276 return IpfrsErrorCode::NullPointer as c_int;
277 }
278 if cid.is_null() {
279 set_last_error("cid is NULL".to_string());
280 return IpfrsErrorCode::NullPointer as c_int;
281 }
282 if out_data.is_null() {
283 set_last_error("out_data is NULL".to_string());
284 return IpfrsErrorCode::NullPointer as c_int;
285 }
286 if out_len.is_null() {
287 set_last_error("out_len is NULL".to_string());
288 return IpfrsErrorCode::NullPointer as c_int;
289 }
290
291 let result = catch_unwind(AssertUnwindSafe(|| {
292 let _inner = &*(client as *mut ClientInner);
293
294 let c_str = unsafe { CStr::from_ptr(cid) };
296 let cid_str = match c_str.to_str() {
297 Ok(s) => s,
298 Err(_) => {
299 set_last_error("Invalid UTF-8 in CID".to_string());
300 return IpfrsErrorCode::InvalidUtf8 as c_int;
301 }
302 };
303
304 let mock_data = format!("Data for CID: {}", cid_str).into_bytes();
311 let len = mock_data.len();
312
313 let mut boxed_data = mock_data.into_boxed_slice();
315 let data_ptr = boxed_data.as_mut_ptr();
316 std::mem::forget(boxed_data); unsafe {
319 *out_data = data_ptr;
320 *out_len = len;
321 }
322
323 IpfrsErrorCode::Success as c_int
324 }));
325
326 match result {
327 Ok(code) => code,
328 Err(_) => {
329 set_last_error("Panic occurred in ipfrs_get".to_string());
330 IpfrsErrorCode::InternalError as c_int
331 }
332 }
333}
334
335#[no_mangle]
353pub unsafe extern "C" fn ipfrs_has(
354 client: *mut IpfrsClient,
355 cid: *const c_char,
356 out_exists: *mut c_int,
357) -> c_int {
358 clear_last_error();
359
360 if client.is_null() {
362 set_last_error("client is NULL".to_string());
363 return IpfrsErrorCode::NullPointer as c_int;
364 }
365 if cid.is_null() {
366 set_last_error("cid is NULL".to_string());
367 return IpfrsErrorCode::NullPointer as c_int;
368 }
369 if out_exists.is_null() {
370 set_last_error("out_exists is NULL".to_string());
371 return IpfrsErrorCode::NullPointer as c_int;
372 }
373
374 let result = catch_unwind(AssertUnwindSafe(|| {
375 let _inner = &*(client as *mut ClientInner);
376
377 let c_str = unsafe { CStr::from_ptr(cid) };
379 let _cid_str = match c_str.to_str() {
380 Ok(s) => s,
381 Err(_) => {
382 set_last_error("Invalid UTF-8 in CID".to_string());
383 return IpfrsErrorCode::InvalidUtf8 as c_int;
384 }
385 };
386
387 unsafe {
390 *out_exists = 1;
391 }
392
393 IpfrsErrorCode::Success as c_int
394 }));
395
396 match result {
397 Ok(code) => code,
398 Err(_) => {
399 set_last_error("Panic occurred in ipfrs_has".to_string());
400 IpfrsErrorCode::InternalError as c_int
401 }
402 }
403}
404
405#[no_mangle]
413pub extern "C" fn ipfrs_get_last_error() -> *const c_char {
414 LAST_ERROR.with(|e| {
415 e.borrow()
416 .as_ref()
417 .map_or(ptr::null(), |s| s.as_ptr() as *const c_char)
418 })
419}
420
421#[no_mangle]
429pub unsafe extern "C" fn ipfrs_string_free(s: *mut c_char) {
430 if s.is_null() {
431 return;
432 }
433
434 let _ = catch_unwind(AssertUnwindSafe(|| unsafe {
435 let _ = CString::from_raw(s);
436 }));
437}
438
439#[no_mangle]
448pub unsafe extern "C" fn ipfrs_data_free(data: *mut u8, len: usize) {
449 if data.is_null() {
450 return;
451 }
452
453 let _ = catch_unwind(AssertUnwindSafe(|| unsafe {
454 let _ = Vec::from_raw_parts(data, len, len);
455 }));
456}
457
458#[no_mangle]
464pub extern "C" fn ipfrs_version() -> *const c_char {
465 static VERSION: &[u8] = b"ipfrs-interface 0.1.0\0";
467 VERSION.as_ptr() as *const c_char
468}
469
470#[cfg(test)]
471mod tests {
472 use super::*;
473
474 #[test]
475 fn test_client_lifecycle() {
476 unsafe {
477 let client = ipfrs_client_new(ptr::null());
478 assert!(!client.is_null());
479 ipfrs_client_free(client);
480 }
481 }
482
483 #[test]
484 fn test_add_and_get() {
485 unsafe {
486 let client = ipfrs_client_new(ptr::null());
487 assert!(!client.is_null());
488
489 let data = b"Hello, IPFRS!";
491 let mut cid_ptr: *mut c_char = ptr::null_mut();
492 let result = ipfrs_add(client, data.as_ptr(), data.len(), &mut cid_ptr);
493 assert_eq!(result, IpfrsErrorCode::Success as c_int);
494 assert!(!cid_ptr.is_null());
495
496 let mut out_data: *mut u8 = ptr::null_mut();
498 let mut out_len: usize = 0;
499 let result = ipfrs_get(client, cid_ptr, &mut out_data, &mut out_len);
500 assert_eq!(result, IpfrsErrorCode::Success as c_int);
501 assert!(!out_data.is_null());
502 assert!(out_len > 0);
503
504 ipfrs_string_free(cid_ptr);
506 ipfrs_data_free(out_data, out_len);
507 ipfrs_client_free(client);
508 }
509 }
510
511 #[test]
512 fn test_has_block() {
513 unsafe {
514 let client = ipfrs_client_new(ptr::null());
515 assert!(!client.is_null());
516
517 let cid = CString::new("bafytest123").unwrap();
518 let mut exists: c_int = 0;
519 let result = ipfrs_has(client, cid.as_ptr(), &mut exists);
520 assert_eq!(result, IpfrsErrorCode::Success as c_int);
521
522 ipfrs_client_free(client);
523 }
524 }
525
526 #[test]
527 fn test_null_pointer_handling() {
528 unsafe {
529 let mut cid_ptr: *mut c_char = ptr::null_mut();
531 let data = b"test";
532 let result = ipfrs_add(ptr::null_mut(), data.as_ptr(), data.len(), &mut cid_ptr);
533 assert_eq!(result, IpfrsErrorCode::NullPointer as c_int);
534 }
535 }
536
537 #[test]
538 fn test_version() {
539 let version = ipfrs_version();
540 assert!(!version.is_null());
541 unsafe {
542 let c_str = CStr::from_ptr(version);
543 let version_str = c_str.to_str().unwrap();
544 assert!(version_str.contains("ipfrs-interface"));
545 }
546 }
547}