ffi_toolkit/
lib.rs

1use std::borrow::Cow;
2use std::ffi::{CStr, CString};
3use std::panic;
4use std::path::PathBuf;
5
6#[repr(C)]
7#[derive(PartialEq, Debug, Copy, Clone)]
8pub enum FCPResponseStatus {
9    // Don't use FCPSuccess, since that complicates description of 'successful' verification.
10    FCPNoError = 0,
11    FCPUnclassifiedError = 1,
12    FCPCallerError = 2,
13    FCPReceiverError = 3,
14}
15
16/// All FFI responses need to implement this trait in order to be able to use `catch_panic()`
17pub trait CodeAndMessage {
18    /// Set the status code and error message
19    fn set_error(&mut self, code_and_message: (FCPResponseStatus, *const libc::c_char));
20}
21
22/// A simple macro to create implementations for the `CodeAndMessage` trait
23///
24/// The only requirement is that the response has an `status_code: FCPResponseStatus` and
25/// `error_msg: *const libc::c_char` field.
26#[macro_export]
27macro_rules! code_and_message_impl {
28    { $response:ty } => {
29        impl CodeAndMessage for $response {
30            fn set_error(&mut self, (code, message): (FCPResponseStatus, *const libc::c_char)) {
31                self.status_code = code;
32                self.error_msg = message;
33            }
34        }
35    }
36}
37
38// produce a C string from a Rust string
39pub fn rust_str_to_c_str<T: Into<String>>(s: T) -> *mut libc::c_char {
40    CString::new(s.into()).unwrap().into_raw()
41}
42
43// consume a C string-pointer and free its memory
44pub unsafe fn free_c_str(ptr: *mut libc::c_char) {
45    if !ptr.is_null() {
46        let _ = CString::from_raw(ptr);
47    }
48}
49
50// return a forgotten raw pointer to something of type T
51pub fn raw_ptr<T>(thing: T) -> *mut T {
52    Box::into_raw(Box::new(thing))
53}
54
55// transmutes a C string to a copy-on-write Rust string
56pub unsafe fn c_str_to_rust_str<'a>(x: *const libc::c_char) -> Cow<'a, str> {
57    if x.is_null() {
58        Cow::from("")
59    } else {
60        CStr::from_ptr(x).to_string_lossy()
61    }
62}
63
64// cast from mutable to constant reference
65pub unsafe fn cast_const<'a, T>(x: *mut T) -> &'a T {
66    assert!(!x.is_null(), "Object argument was null");
67    (&(*x))
68}
69
70// transmutes a C string to a PathBuf
71pub unsafe fn c_str_to_pbuf(x: *const libc::c_char) -> PathBuf {
72    PathBuf::from(String::from(c_str_to_rust_str(x)))
73}
74
75///// Catch panics and return an error response
76pub fn catch_panic_response<F, T>(callback: F) -> *mut T
77where
78    T: Default + CodeAndMessage,
79    F: FnOnce() -> *mut T,
80{
81    // Using AssertUnwindSafe is code smell. Though catching our panics here is really
82    // last resort, so it should be OK.
83    let maybe_panic = panic::catch_unwind(panic::AssertUnwindSafe(callback));
84    match maybe_panic {
85        Ok(return_value) => return_value,
86        Err(panic) => {
87            let error_msg = match panic.downcast_ref::<&'static str>() {
88                Some(message) => message,
89                _ => "no unwind information",
90            };
91            let mut response = T::default();
92            let message = CString::new(format!("Rust panic: {}", error_msg))
93                .unwrap()
94                .into_raw();
95            response.set_error((FCPResponseStatus::FCPUnclassifiedError, message));
96            raw_ptr(response)
97        }
98    }
99}