hashtree_embedded_ffi/
lib.rs1use 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}