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 unsafe extern "C" fn hashtree_embedded_start(state_root: *const c_char) -> *mut c_void {
46 clear_last_error();
47
48 let state_root = match unsafe { string_from_ptr(state_root, "state_root") } {
49 Ok(value) => value,
50 Err(error) => {
51 set_last_error(error);
52 return ptr::null_mut();
53 }
54 };
55
56 let options = HostDaemonOptions::new(PathBuf::from(state_root));
57 match HostDaemonRuntime::start(options) {
58 Ok(runtime) => Box::into_raw(Box::new(EmbeddedDaemonHandle { runtime })) as *mut c_void,
59 Err(error) => {
60 set_last_error(format!(
61 "failed to start embedded hashtree daemon: {error:#}"
62 ));
63 ptr::null_mut()
64 }
65 }
66}
67
68#[unsafe(no_mangle)]
69pub unsafe extern "C" fn hashtree_embedded_shutdown(handle: *mut c_void) {
76 if handle.is_null() {
77 return;
78 }
79 let mut handle = unsafe { Box::from_raw(handle.cast::<EmbeddedDaemonHandle>()) };
80 handle.runtime.shutdown();
81}
82
83#[unsafe(no_mangle)]
84pub unsafe extern "C" fn hashtree_embedded_reload(handle: *mut c_void) -> bool {
91 clear_last_error();
92 if handle.is_null() {
93 set_last_error("embedded daemon handle was null");
94 return false;
95 }
96 let handle = unsafe { &mut *handle.cast::<EmbeddedDaemonHandle>() };
97 match handle.runtime.reload() {
98 Ok(_) => true,
99 Err(error) => {
100 set_last_error(format!(
101 "failed to reload embedded hashtree daemon: {error:#}"
102 ));
103 false
104 }
105 }
106}
107
108#[unsafe(no_mangle)]
109pub unsafe extern "C" fn hashtree_embedded_get_base_url(handle: *const c_void) -> *mut c_char {
117 clear_last_error();
118 if handle.is_null() {
119 set_last_error("embedded daemon handle was null");
120 return ptr::null_mut();
121 }
122 let handle = unsafe { &*handle.cast::<EmbeddedDaemonHandle>() };
123 sanitize_c_string(handle.runtime.base_url()).into_raw()
124}
125
126#[unsafe(no_mangle)]
127pub unsafe extern "C" fn hashtree_embedded_get_self_npub(handle: *const c_void) -> *mut c_char {
135 clear_last_error();
136 if handle.is_null() {
137 set_last_error("embedded daemon handle was null");
138 return ptr::null_mut();
139 }
140 let handle = unsafe { &*handle.cast::<EmbeddedDaemonHandle>() };
141 sanitize_c_string(handle.runtime.self_npub().to_owned()).into_raw()
142}
143
144#[unsafe(no_mangle)]
145pub extern "C" fn hashtree_embedded_take_last_error() -> *mut c_char {
146 let mut last_error = LAST_ERROR.lock().expect("last error lock");
147 last_error
148 .take()
149 .map(CString::into_raw)
150 .unwrap_or(ptr::null_mut())
151}
152
153#[unsafe(no_mangle)]
154pub unsafe extern "C" fn hashtree_embedded_string_free(value: *mut c_char) {
161 if value.is_null() {
162 return;
163 }
164 unsafe {
165 drop(CString::from_raw(value));
166 }
167}