Skip to main content

hashtree_embedded_ffi/
lib.rs

1use hashtree_embedded::{HostDaemonOptions, HostDaemonRuntime};
2use std::ffi::{c_char, c_void, CString};
3use std::path::PathBuf;
4use std::ptr;
5use std::sync::Mutex;
6
7struct EmbeddedDaemonHandle {
8    runtime: HostDaemonRuntime,
9}
10
11static LAST_ERROR: Mutex<Option<CString>> = Mutex::new(None);
12
13fn set_last_error(message: impl Into<String>) {
14    let message = sanitize_c_string(message.into());
15    *LAST_ERROR.lock().expect("last error lock") = Some(message);
16}
17
18fn clear_last_error() {
19    *LAST_ERROR.lock().expect("last error lock") = None;
20}
21
22fn sanitize_c_string(input: String) -> CString {
23    let sanitized = input.replace('\0', " ");
24    CString::new(sanitized).expect("CString conversion should succeed after sanitizing NULs")
25}
26
27unsafe fn string_from_ptr(ptr: *const c_char, label: &str) -> Result<String, String> {
28    if ptr.is_null() {
29        return Err(format!("{label} pointer was null"));
30    }
31    let c_str = unsafe { std::ffi::CStr::from_ptr(ptr) };
32    c_str
33        .to_str()
34        .map(|value| value.to_owned())
35        .map_err(|_| format!("{label} was not valid UTF-8"))
36}
37
38#[unsafe(no_mangle)]
39pub extern "C" fn hashtree_embedded_start(state_root: *const c_char) -> *mut c_void {
40    clear_last_error();
41
42    let state_root = match unsafe { string_from_ptr(state_root, "state_root") } {
43        Ok(value) => value,
44        Err(error) => {
45            set_last_error(error);
46            return ptr::null_mut();
47        }
48    };
49
50    let options = HostDaemonOptions::new(PathBuf::from(state_root));
51    match HostDaemonRuntime::start(options) {
52        Ok(runtime) => Box::into_raw(Box::new(EmbeddedDaemonHandle { runtime })) as *mut c_void,
53        Err(error) => {
54            set_last_error(format!(
55                "failed to start embedded hashtree daemon: {error:#}"
56            ));
57            ptr::null_mut()
58        }
59    }
60}
61
62#[unsafe(no_mangle)]
63pub extern "C" fn hashtree_embedded_shutdown(handle: *mut c_void) {
64    if handle.is_null() {
65        return;
66    }
67    let mut handle = unsafe { Box::from_raw(handle.cast::<EmbeddedDaemonHandle>()) };
68    handle.runtime.shutdown();
69}
70
71#[unsafe(no_mangle)]
72pub extern "C" fn hashtree_embedded_reload(handle: *mut c_void) -> bool {
73    clear_last_error();
74    if handle.is_null() {
75        set_last_error("embedded daemon handle was null");
76        return false;
77    }
78    let handle = unsafe { &mut *handle.cast::<EmbeddedDaemonHandle>() };
79    match handle.runtime.reload() {
80        Ok(_) => true,
81        Err(error) => {
82            set_last_error(format!(
83                "failed to reload embedded hashtree daemon: {error:#}"
84            ));
85            false
86        }
87    }
88}
89
90#[unsafe(no_mangle)]
91pub extern "C" fn hashtree_embedded_get_base_url(handle: *const c_void) -> *mut c_char {
92    clear_last_error();
93    if handle.is_null() {
94        set_last_error("embedded daemon handle was null");
95        return ptr::null_mut();
96    }
97    let handle = unsafe { &*handle.cast::<EmbeddedDaemonHandle>() };
98    sanitize_c_string(handle.runtime.base_url()).into_raw()
99}
100
101#[unsafe(no_mangle)]
102pub extern "C" fn hashtree_embedded_get_self_npub(handle: *const c_void) -> *mut c_char {
103    clear_last_error();
104    if handle.is_null() {
105        set_last_error("embedded daemon handle was null");
106        return ptr::null_mut();
107    }
108    let handle = unsafe { &*handle.cast::<EmbeddedDaemonHandle>() };
109    sanitize_c_string(handle.runtime.self_npub().to_owned()).into_raw()
110}
111
112#[unsafe(no_mangle)]
113pub extern "C" fn hashtree_embedded_take_last_error() -> *mut c_char {
114    let mut last_error = LAST_ERROR.lock().expect("last error lock");
115    last_error
116        .take()
117        .map(CString::into_raw)
118        .unwrap_or(ptr::null_mut())
119}
120
121#[unsafe(no_mangle)]
122pub extern "C" fn hashtree_embedded_string_free(value: *mut c_char) {
123    if value.is_null() {
124        return;
125    }
126    unsafe {
127        drop(CString::from_raw(value));
128    }
129}