hashtree-embedded-ffi 0.2.81

C ABI for the embedded Hashtree daemon runtime
Documentation
use hashtree_embedded::{HostDaemonOptions, HostDaemonRuntime};
use std::ffi::{c_char, c_void, CString};
use std::path::PathBuf;
use std::ptr;
use std::sync::Mutex;

struct EmbeddedDaemonHandle {
    runtime: HostDaemonRuntime,
}

static LAST_ERROR: Mutex<Option<CString>> = Mutex::new(None);

fn set_last_error(message: impl Into<String>) {
    let message = sanitize_c_string(message.into());
    *LAST_ERROR.lock().expect("last error lock") = Some(message);
}

fn clear_last_error() {
    *LAST_ERROR.lock().expect("last error lock") = None;
}

fn sanitize_c_string(input: String) -> CString {
    let sanitized = input.replace('\0', " ");
    CString::new(sanitized).expect("CString conversion should succeed after sanitizing NULs")
}

unsafe fn string_from_ptr(ptr: *const c_char, label: &str) -> Result<String, String> {
    if ptr.is_null() {
        return Err(format!("{label} pointer was null"));
    }
    let c_str = unsafe { std::ffi::CStr::from_ptr(ptr) };
    c_str
        .to_str()
        .map(|value| value.to_owned())
        .map_err(|_| format!("{label} was not valid UTF-8"))
}

#[unsafe(no_mangle)]
pub extern "C" fn hashtree_embedded_start(state_root: *const c_char) -> *mut c_void {
    clear_last_error();

    let state_root = match unsafe { string_from_ptr(state_root, "state_root") } {
        Ok(value) => value,
        Err(error) => {
            set_last_error(error);
            return ptr::null_mut();
        }
    };

    let options = HostDaemonOptions::new(PathBuf::from(state_root));
    match HostDaemonRuntime::start(options) {
        Ok(runtime) => Box::into_raw(Box::new(EmbeddedDaemonHandle { runtime })) as *mut c_void,
        Err(error) => {
            set_last_error(format!(
                "failed to start embedded hashtree daemon: {error:#}"
            ));
            ptr::null_mut()
        }
    }
}

#[unsafe(no_mangle)]
pub extern "C" fn hashtree_embedded_shutdown(handle: *mut c_void) {
    if handle.is_null() {
        return;
    }
    let mut handle = unsafe { Box::from_raw(handle.cast::<EmbeddedDaemonHandle>()) };
    handle.runtime.shutdown();
}

#[unsafe(no_mangle)]
pub extern "C" fn hashtree_embedded_reload(handle: *mut c_void) -> bool {
    clear_last_error();
    if handle.is_null() {
        set_last_error("embedded daemon handle was null");
        return false;
    }
    let handle = unsafe { &mut *handle.cast::<EmbeddedDaemonHandle>() };
    match handle.runtime.reload() {
        Ok(_) => true,
        Err(error) => {
            set_last_error(format!(
                "failed to reload embedded hashtree daemon: {error:#}"
            ));
            false
        }
    }
}

#[unsafe(no_mangle)]
pub extern "C" fn hashtree_embedded_get_base_url(handle: *const c_void) -> *mut c_char {
    clear_last_error();
    if handle.is_null() {
        set_last_error("embedded daemon handle was null");
        return ptr::null_mut();
    }
    let handle = unsafe { &*handle.cast::<EmbeddedDaemonHandle>() };
    sanitize_c_string(handle.runtime.base_url()).into_raw()
}

#[unsafe(no_mangle)]
pub extern "C" fn hashtree_embedded_get_self_npub(handle: *const c_void) -> *mut c_char {
    clear_last_error();
    if handle.is_null() {
        set_last_error("embedded daemon handle was null");
        return ptr::null_mut();
    }
    let handle = unsafe { &*handle.cast::<EmbeddedDaemonHandle>() };
    sanitize_c_string(handle.runtime.self_npub().to_owned()).into_raw()
}

#[unsafe(no_mangle)]
pub extern "C" fn hashtree_embedded_take_last_error() -> *mut c_char {
    let mut last_error = LAST_ERROR.lock().expect("last error lock");
    last_error
        .take()
        .map(CString::into_raw)
        .unwrap_or(ptr::null_mut())
}

#[unsafe(no_mangle)]
pub extern "C" fn hashtree_embedded_string_free(value: *mut c_char) {
    if value.is_null() {
        return;
    }
    unsafe {
        drop(CString::from_raw(value));
    }
}