diff --git a/mycelium/include/mycelium.h b/mycelium/include/mycelium.h index 68cd83a..6b41898 100644 --- a/mycelium/include/mycelium.h +++ b/mycelium/include/mycelium.h @@ -43,6 +43,16 @@ */ #define RouterId_BYTE_SIZE 40 +/* + Opaque handle to a running (or stopped) mycelium node. + + Created by [`mycelium_start`](super::mycelium_start), released by + [`mycelium_node_free`](super::mycelium_node_free). The C side only ever + sees an opaque pointer — cbindgen emits a forward declaration for this + struct because it is not `#[repr(C)]`. + */ +typedef struct mycelium_node_t mycelium_node_t; + /* Configuration passed to `mycelium_start`. The library reads strings out during the call and does not retain any of the pointers — caller still @@ -195,52 +205,65 @@ typedef struct mycelium_string_array_t { const char *mycelium_last_error_message(void); /* - Start the mycelium node. Returns 0 on success, a negative code on failure. - Calling while a node is already running returns `MYCELIUM_ERR_INVALID_STATE`. + Start a mycelium node. Returns an opaque handle on success, or NULL on + failure — call `mycelium_last_error_message` for details. The returned + handle must eventually be released with `mycelium_node_free`. */ -int32_t mycelium_start(const struct mycelium_start_config_t *cfg); +struct mycelium_node_t *mycelium_start(const struct mycelium_start_config_t *cfg); /* - Stop the running node. No-op (returns 0) if no node is running. + Halt the node and release its internal resources. The handle remains + valid for `mycelium_is_running` queries (which will return false) until + `mycelium_node_free` is called. No-op on a NULL or already-stopped node. */ -int32_t mycelium_stop(void); +int32_t mycelium_stop(struct mycelium_node_t *node); /* Write `true`/`false` into `out` reflecting whether the node is running. */ -int32_t mycelium_is_running(bool *out); +int32_t mycelium_is_running(struct mycelium_node_t *node, bool *out); -int32_t mycelium_get_node_info(struct mycelium_node_info_t *out); +int32_t mycelium_get_node_info(struct mycelium_node_t *node, struct mycelium_node_info_t *out); -int32_t mycelium_get_public_key_from_ip(const char *ip, char **out); +int32_t mycelium_get_public_key_from_ip(struct mycelium_node_t *node, const char *ip, char **out); -int32_t mycelium_get_peers(struct mycelium_peer_info_array_t *out); +int32_t mycelium_get_peers(struct mycelium_node_t *node, struct mycelium_peer_info_array_t *out); -int32_t mycelium_add_peer(const char *endpoint, bool *out); +int32_t mycelium_add_peer(struct mycelium_node_t *node, const char *endpoint, bool *out); -int32_t mycelium_remove_peer(const char *endpoint, bool *out); +int32_t mycelium_remove_peer(struct mycelium_node_t *node, const char *endpoint, bool *out); -int32_t mycelium_get_selected_routes(struct mycelium_route_array_t *out); +int32_t mycelium_get_selected_routes(struct mycelium_node_t *node, + struct mycelium_route_array_t *out); -int32_t mycelium_get_fallback_routes(struct mycelium_route_array_t *out); +int32_t mycelium_get_fallback_routes(struct mycelium_node_t *node, + struct mycelium_route_array_t *out); -int32_t mycelium_get_queried_subnets(struct mycelium_queried_subnet_array_t *out); +int32_t mycelium_get_queried_subnets(struct mycelium_node_t *node, + struct mycelium_queried_subnet_array_t *out); -int32_t mycelium_get_packet_stats(struct mycelium_packet_stats_t *out); +int32_t mycelium_get_packet_stats(struct mycelium_node_t *node, + struct mycelium_packet_stats_t *out); int32_t mycelium_generate_secret_key(struct mycelium_secret_key_t *out); int32_t mycelium_address_from_secret_key(const struct mycelium_secret_key_t *key, char **out); -int32_t mycelium_start_proxy_probe(void); +int32_t mycelium_start_proxy_probe(struct mycelium_node_t *node); -int32_t mycelium_stop_proxy_probe(void); +int32_t mycelium_stop_proxy_probe(struct mycelium_node_t *node); -int32_t mycelium_list_proxies(struct mycelium_string_array_t *out); +int32_t mycelium_list_proxies(struct mycelium_node_t *node, struct mycelium_string_array_t *out); -int32_t mycelium_proxy_connect(const char *remote, char **out); +int32_t mycelium_proxy_connect(struct mycelium_node_t *node, const char *remote, char **out); -int32_t mycelium_proxy_disconnect(void); +int32_t mycelium_proxy_disconnect(struct mycelium_node_t *node); + +/* + Release a node handle returned by `mycelium_start`. If the node is still + running it is stopped first. Safe to call with NULL. + */ +void mycelium_node_free(struct mycelium_node_t *node); /* Free a single C string returned by the library. Safe to call with NULL. diff --git a/mycelium/src/ffi/exports.rs b/mycelium/src/ffi/exports.rs index 305552d..ac58fee 100644 --- a/mycelium/src/ffi/exports.rs +++ b/mycelium/src/ffi/exports.rs @@ -3,8 +3,8 @@ //! Each entry point follows the same shape: //! 1. Clear the per-thread last-error. //! 2. Validate inputs; on failure, set the error message and return a -//! negative status code. -//! 3. Lock the singleton node slot, dispatch into the running [`NodeHandle`]. +//! negative status code (or NULL for `mycelium_start`). +//! 3. Lock the handle's internal state; reject if the node is stopped. //! 4. Use `h.rt().block_on(...)` to bridge the synchronous C caller to the //! underlying async APIs. //! 5. Convert the result into the `repr(C)` mirror type and write through @@ -16,18 +16,19 @@ use core::ffi::c_char; use std::ffi::CStr; use std::net::{IpAddr, SocketAddr}; +use std::sync::Mutex; use tracing::{error, info, warn}; use super::error::{ self, MYCELIUM_ERR_INTERNAL, MYCELIUM_ERR_INVALID_ARG, MYCELIUM_ERR_INVALID_STATE, MYCELIUM_OK, }; -use super::node_slot; use super::types::{ - cstring, mycelium_node_info_t, mycelium_packet_stat_entry_t, mycelium_packet_stats_t, - mycelium_peer_info_array_t, mycelium_peer_info_t, mycelium_queried_subnet_array_t, - mycelium_queried_subnet_t, mycelium_route_array_t, mycelium_route_t, mycelium_secret_key_t, - mycelium_start_config_t, mycelium_string_array_t, vec_into_c, + cstring, mycelium_node_info_t, mycelium_node_t, mycelium_packet_stat_entry_t, + mycelium_packet_stats_t, mycelium_peer_info_array_t, mycelium_peer_info_t, + mycelium_queried_subnet_array_t, mycelium_queried_subnet_t, mycelium_route_array_t, + mycelium_route_t, mycelium_secret_key_t, mycelium_start_config_t, mycelium_string_array_t, + vec_into_c, NodeState, }; use crate::crypto; @@ -93,38 +94,59 @@ fn parse_discovery_mode(mode: &str, interfaces: Vec) -> Result {{ + if $node.is_null() { + return error::set_and_return("node is null", MYCELIUM_ERR_INVALID_ARG); + } + let node_ref = &*$node; + let guard = match node_ref.state.lock() { + Ok(g) => g, + Err(_) => return error::set_and_return("node lock poisoned", MYCELIUM_ERR_INTERNAL), + }; + let h: &NodeHandle = match &*guard { + NodeState::Running(h) => h, + NodeState::Stopped => { + return error::set_and_return("node is not running", MYCELIUM_ERR_INVALID_STATE) + } + }; + $body(h) + }}; +} + // --------------------------------------------------------------------------- // Lifecycle // --------------------------------------------------------------------------- -/// Start the mycelium node. Returns 0 on success, a negative code on failure. -/// Calling while a node is already running returns `MYCELIUM_ERR_INVALID_STATE`. +/// Start a mycelium node. Returns an opaque handle on success, or NULL on +/// failure — call `mycelium_last_error_message` for details. The returned +/// handle must eventually be released with `mycelium_node_free`. #[no_mangle] -pub unsafe extern "C" fn mycelium_start(cfg: *const mycelium_start_config_t) -> i32 { +pub unsafe extern "C" fn mycelium_start( + cfg: *const mycelium_start_config_t, +) -> *mut mycelium_node_t { error::clear(); let cfg = match cfg.as_ref() { Some(c) => c, - None => return error::set_and_return("cfg is null", MYCELIUM_ERR_INVALID_ARG), + None => { + error::set("cfg is null"); + return std::ptr::null_mut(); + } }; - let mut guard = match node_slot().lock() { - Ok(g) => g, - Err(_) => return error::set_and_return("node lock poisoned", MYCELIUM_ERR_INTERNAL), - }; - if guard.is_some() { - return error::set_and_return("node is already running", MYCELIUM_ERR_INVALID_STATE); - } - let peers_strs = match cstr_array_to_vec(cfg.peers, cfg.peers_len, "peers") { Ok(v) => v, - Err(code) => return code, + Err(_) => return std::ptr::null_mut(), }; let endpoints = peers_strs.iter().filter_map(|p| p.parse().ok()).collect(); let mode = match cstr_to_str(cfg.peer_discovery_mode, "peer_discovery_mode") { Ok(s) => s.to_owned(), - Err(code) => return code, + Err(_) => return std::ptr::null_mut(), }; let interfaces = match cstr_array_to_vec( cfg.peer_discovery_interfaces, @@ -132,16 +154,16 @@ pub unsafe extern "C" fn mycelium_start(cfg: *const mycelium_start_config_t) -> "peer_discovery_interfaces", ) { Ok(v) => v, - Err(code) => return code, + Err(_) => return std::ptr::null_mut(), }; let peer_discovery_mode = match parse_discovery_mode(&mode, interfaces) { Ok(m) => m, - Err(code) => return code, + Err(_) => return std::ptr::null_mut(), }; let tun_name = match cstr_to_str(cfg.tun_name, "tun_name") { Ok(s) => s.to_owned(), - Err(code) => return code, + Err(_) => return std::ptr::null_mut(), }; info!(peers = peers_strs.len(), "starting mycelium node"); @@ -151,10 +173,8 @@ pub unsafe extern "C" fn mycelium_start(cfg: *const mycelium_start_config_t) -> Ok(fd) => fd, Err(e) => { error!("Failed to create TUN fd: {e}"); - return error::set_and_return( - format!("failed to create tun fd: {e}"), - MYCELIUM_ERR_INTERNAL, - ); + error::set(format!("failed to create tun fd: {e}")); + return std::ptr::null_mut(); } }; @@ -195,80 +215,77 @@ pub unsafe extern "C" fn mycelium_start(cfg: *const mycelium_start_config_t) -> }; match NodeHandle::start(config) { - Ok(h) => { - *guard = Some(h); - MYCELIUM_OK - } + Ok(h) => Box::into_raw(Box::new(mycelium_node_t { + state: Mutex::new(NodeState::Running(h)), + })), Err(e) => { error!("failed to start node: {e}"); - error::set_and_return(format!("failed to start node: {e}"), MYCELIUM_ERR_INTERNAL) + error::set(format!("failed to start node: {e}")); + std::ptr::null_mut() } } } -/// Stop the running node. No-op (returns 0) if no node is running. +/// Halt the node and release its internal resources. The handle remains +/// valid for `mycelium_is_running` queries (which will return false) until +/// `mycelium_node_free` is called. No-op on a NULL or already-stopped node. #[no_mangle] -pub unsafe extern "C" fn mycelium_stop() -> i32 { +pub unsafe extern "C" fn mycelium_stop(node: *mut mycelium_node_t) -> i32 { error::clear(); - let mut guard = match node_slot().lock() { + if node.is_null() { + return error::set_and_return("node is null", MYCELIUM_ERR_INVALID_ARG); + } + let node = &*node; + let mut guard = match node.state.lock() { Ok(g) => g, Err(_) => return error::set_and_return("node lock poisoned", MYCELIUM_ERR_INTERNAL), }; - if let Some(h) = guard.as_mut() { - info!("stopping mycelium node"); - h.stop(); - } else { - warn!("mycelium_stop called but node is not running"); + match &mut *guard { + NodeState::Running(h) => { + info!("stopping mycelium node"); + h.stop(); + *guard = NodeState::Stopped; + } + NodeState::Stopped => { + warn!("mycelium_stop called on a stopped node"); + } } MYCELIUM_OK } /// Write `true`/`false` into `out` reflecting whether the node is running. #[no_mangle] -pub unsafe extern "C" fn mycelium_is_running(out: *mut bool) -> i32 { +pub unsafe extern "C" fn mycelium_is_running(node: *mut mycelium_node_t, out: *mut bool) -> i32 { error::clear(); + if node.is_null() { + return error::set_and_return("node is null", MYCELIUM_ERR_INVALID_ARG); + } if out.is_null() { return error::set_and_return("out is null", MYCELIUM_ERR_INVALID_ARG); } - let guard = match node_slot().lock() { + let node = &*node; + let guard = match node.state.lock() { Ok(g) => g, Err(_) => return error::set_and_return("node lock poisoned", MYCELIUM_ERR_INTERNAL), }; - *out = guard.as_ref().is_some_and(|h| h.is_running()); + *out = matches!(&*guard, NodeState::Running(h) if h.is_running()); MYCELIUM_OK } -// --------------------------------------------------------------------------- -// Internal: take a guard on the running node, or set an error -// --------------------------------------------------------------------------- - -macro_rules! with_running_node { - ($body:expr) => {{ - let guard = match node_slot().lock() { - Ok(g) => g, - Err(_) => return error::set_and_return("node lock poisoned", MYCELIUM_ERR_INTERNAL), - }; - let h = match guard.as_ref() { - Some(h) => h, - None => { - return error::set_and_return("node is not running", MYCELIUM_ERR_INVALID_STATE) - } - }; - $body(h) - }}; -} - // --------------------------------------------------------------------------- // Identity / introspection // --------------------------------------------------------------------------- #[no_mangle] -pub unsafe extern "C" fn mycelium_get_node_info(out: *mut mycelium_node_info_t) -> i32 { +pub unsafe extern "C" fn mycelium_get_node_info( + node: *mut mycelium_node_t, + out: *mut mycelium_node_info_t, +) -> i32 { error::clear(); if out.is_null() { return error::set_and_return("out is null", MYCELIUM_ERR_INVALID_ARG); } - with_running_node!(|h: &NodeHandle| { + with_running_node!(node, |h: &NodeHandle| { h.rt().block_on(async { let info = h.node().lock().await.info(); *out = mycelium_node_info_t { @@ -282,6 +299,7 @@ pub unsafe extern "C" fn mycelium_get_node_info(out: *mut mycelium_node_info_t) #[no_mangle] pub unsafe extern "C" fn mycelium_get_public_key_from_ip( + node: *mut mycelium_node_t, ip: *const c_char, out: *mut *mut c_char, ) -> i32 { @@ -299,7 +317,7 @@ pub unsafe extern "C" fn mycelium_get_public_key_from_ip( return error::set_and_return("ip is not a valid address", MYCELIUM_ERR_INVALID_ARG) } }; - with_running_node!(|h: &NodeHandle| { + with_running_node!(node, |h: &NodeHandle| { h.rt().block_on(async { let pubkey = h .node() @@ -319,12 +337,15 @@ pub unsafe extern "C" fn mycelium_get_public_key_from_ip( // --------------------------------------------------------------------------- #[no_mangle] -pub unsafe extern "C" fn mycelium_get_peers(out: *mut mycelium_peer_info_array_t) -> i32 { +pub unsafe extern "C" fn mycelium_get_peers( + node: *mut mycelium_node_t, + out: *mut mycelium_peer_info_array_t, +) -> i32 { error::clear(); if out.is_null() { return error::set_and_return("out is null", MYCELIUM_ERR_INVALID_ARG); } - with_running_node!(|h: &NodeHandle| { + with_running_node!(node, |h: &NodeHandle| { h.rt().block_on(async { let peers: Vec = h .node() @@ -342,7 +363,11 @@ pub unsafe extern "C" fn mycelium_get_peers(out: *mut mycelium_peer_info_array_t } #[no_mangle] -pub unsafe extern "C" fn mycelium_add_peer(endpoint: *const c_char, out: *mut bool) -> i32 { +pub unsafe extern "C" fn mycelium_add_peer( + node: *mut mycelium_node_t, + endpoint: *const c_char, + out: *mut bool, +) -> i32 { error::clear(); if out.is_null() { return error::set_and_return("out is null", MYCELIUM_ERR_INVALID_ARG); @@ -355,7 +380,7 @@ pub unsafe extern "C" fn mycelium_add_peer(endpoint: *const c_char, out: *mut bo Ok(e) => e, Err(_) => return error::set_and_return("invalid endpoint", MYCELIUM_ERR_INVALID_ARG), }; - with_running_node!(|h: &NodeHandle| { + with_running_node!(node, |h: &NodeHandle| { h.rt().block_on(async { *out = h.node().lock().await.add_peer(endpoint).is_ok(); MYCELIUM_OK @@ -364,7 +389,11 @@ pub unsafe extern "C" fn mycelium_add_peer(endpoint: *const c_char, out: *mut bo } #[no_mangle] -pub unsafe extern "C" fn mycelium_remove_peer(endpoint: *const c_char, out: *mut bool) -> i32 { +pub unsafe extern "C" fn mycelium_remove_peer( + node: *mut mycelium_node_t, + endpoint: *const c_char, + out: *mut bool, +) -> i32 { error::clear(); if out.is_null() { return error::set_and_return("out is null", MYCELIUM_ERR_INVALID_ARG); @@ -377,7 +406,7 @@ pub unsafe extern "C" fn mycelium_remove_peer(endpoint: *const c_char, out: *mut Ok(e) => e, Err(_) => return error::set_and_return("invalid endpoint", MYCELIUM_ERR_INVALID_ARG), }; - with_running_node!(|h: &NodeHandle| { + with_running_node!(node, |h: &NodeHandle| { h.rt().block_on(async { *out = h.node().lock().await.remove_peer(endpoint).is_ok(); MYCELIUM_OK @@ -390,12 +419,15 @@ pub unsafe extern "C" fn mycelium_remove_peer(endpoint: *const c_char, out: *mut // --------------------------------------------------------------------------- #[no_mangle] -pub unsafe extern "C" fn mycelium_get_selected_routes(out: *mut mycelium_route_array_t) -> i32 { +pub unsafe extern "C" fn mycelium_get_selected_routes( + node: *mut mycelium_node_t, + out: *mut mycelium_route_array_t, +) -> i32 { error::clear(); if out.is_null() { return error::set_and_return("out is null", MYCELIUM_ERR_INVALID_ARG); } - with_running_node!(|h: &NodeHandle| { + with_running_node!(node, |h: &NodeHandle| { h.rt().block_on(async { let routes: Vec = h .node() @@ -413,12 +445,15 @@ pub unsafe extern "C" fn mycelium_get_selected_routes(out: *mut mycelium_route_a } #[no_mangle] -pub unsafe extern "C" fn mycelium_get_fallback_routes(out: *mut mycelium_route_array_t) -> i32 { +pub unsafe extern "C" fn mycelium_get_fallback_routes( + node: *mut mycelium_node_t, + out: *mut mycelium_route_array_t, +) -> i32 { error::clear(); if out.is_null() { return error::set_and_return("out is null", MYCELIUM_ERR_INVALID_ARG); } - with_running_node!(|h: &NodeHandle| { + with_running_node!(node, |h: &NodeHandle| { h.rt().block_on(async { let routes: Vec = h .node() @@ -437,13 +472,14 @@ pub unsafe extern "C" fn mycelium_get_fallback_routes(out: *mut mycelium_route_a #[no_mangle] pub unsafe extern "C" fn mycelium_get_queried_subnets( + node: *mut mycelium_node_t, out: *mut mycelium_queried_subnet_array_t, ) -> i32 { error::clear(); if out.is_null() { return error::set_and_return("out is null", MYCELIUM_ERR_INVALID_ARG); } - with_running_node!(|h: &NodeHandle| { + with_running_node!(node, |h: &NodeHandle| { h.rt().block_on(async { let now = tokio::time::Instant::now(); let qs: Vec = h @@ -470,12 +506,15 @@ pub unsafe extern "C" fn mycelium_get_queried_subnets( // --------------------------------------------------------------------------- #[no_mangle] -pub unsafe extern "C" fn mycelium_get_packet_stats(out: *mut mycelium_packet_stats_t) -> i32 { +pub unsafe extern "C" fn mycelium_get_packet_stats( + node: *mut mycelium_node_t, + out: *mut mycelium_packet_stats_t, +) -> i32 { error::clear(); if out.is_null() { return error::set_and_return("out is null", MYCELIUM_ERR_INVALID_ARG); } - with_running_node!(|h: &NodeHandle| { + with_running_node!(node, |h: &NodeHandle| { h.rt().block_on(async { let stats = h.node().lock().await.packet_statistics(); let by_source: Vec = stats @@ -542,9 +581,9 @@ pub unsafe extern "C" fn mycelium_address_from_secret_key( // --------------------------------------------------------------------------- #[no_mangle] -pub unsafe extern "C" fn mycelium_start_proxy_probe() -> i32 { +pub unsafe extern "C" fn mycelium_start_proxy_probe(node: *mut mycelium_node_t) -> i32 { error::clear(); - with_running_node!(|h: &NodeHandle| { + with_running_node!(node, |h: &NodeHandle| { h.rt().block_on(async { h.node().lock().await.start_proxy_scan(); MYCELIUM_OK @@ -553,9 +592,9 @@ pub unsafe extern "C" fn mycelium_start_proxy_probe() -> i32 { } #[no_mangle] -pub unsafe extern "C" fn mycelium_stop_proxy_probe() -> i32 { +pub unsafe extern "C" fn mycelium_stop_proxy_probe(node: *mut mycelium_node_t) -> i32 { error::clear(); - with_running_node!(|h: &NodeHandle| { + with_running_node!(node, |h: &NodeHandle| { h.rt().block_on(async { h.node().lock().await.stop_proxy_scan(); MYCELIUM_OK @@ -564,12 +603,15 @@ pub unsafe extern "C" fn mycelium_stop_proxy_probe() -> i32 { } #[no_mangle] -pub unsafe extern "C" fn mycelium_list_proxies(out: *mut mycelium_string_array_t) -> i32 { +pub unsafe extern "C" fn mycelium_list_proxies( + node: *mut mycelium_node_t, + out: *mut mycelium_string_array_t, +) -> i32 { error::clear(); if out.is_null() { return error::set_and_return("out is null", MYCELIUM_ERR_INVALID_ARG); } - with_running_node!(|h: &NodeHandle| { + with_running_node!(node, |h: &NodeHandle| { h.rt().block_on(async { let proxies: Vec<*mut c_char> = h .node() @@ -591,6 +633,7 @@ pub unsafe extern "C" fn mycelium_list_proxies(out: *mut mycelium_string_array_t #[no_mangle] pub unsafe extern "C" fn mycelium_proxy_connect( + node: *mut mycelium_node_t, remote: *const c_char, out: *mut *mut c_char, ) -> i32 { @@ -606,7 +649,7 @@ pub unsafe extern "C" fn mycelium_proxy_connect( Ok(r) => r, Err(_) => return error::set_and_return("invalid proxy remote", MYCELIUM_ERR_INVALID_ARG), }; - with_running_node!(|h: &NodeHandle| { + with_running_node!(node, |h: &NodeHandle| { h.rt().block_on(async { let future = h.node().lock().await.connect_proxy(remote_addr); match future.await { @@ -627,9 +670,9 @@ pub unsafe extern "C" fn mycelium_proxy_connect( } #[no_mangle] -pub unsafe extern "C" fn mycelium_proxy_disconnect() -> i32 { +pub unsafe extern "C" fn mycelium_proxy_disconnect(node: *mut mycelium_node_t) -> i32 { error::clear(); - with_running_node!(|h: &NodeHandle| { + with_running_node!(node, |h: &NodeHandle| { h.rt().block_on(async { h.node().lock().await.disconnect_proxy(); MYCELIUM_OK diff --git a/mycelium/src/ffi/mod.rs b/mycelium/src/ffi/mod.rs index 4455d3c..5b3fc42 100644 --- a/mycelium/src/ffi/mod.rs +++ b/mycelium/src/ffi/mod.rs @@ -4,27 +4,16 @@ //! `cdylib` (`libmycelium.so` / `.dylib`) and `staticlib` (`libmycelium.a`), //! and `build.rs` runs `cbindgen` to write `include/mycelium.h`. //! -//! The surface is process-singleton: a single `NodeHandle` lives in -//! `NODE` and every entry point operates against it. Two processes that -//! both load the library each get their own `NODE` (writable globals are -//! per-process under copy-on-write), so this is exactly what you want when -//! the library is wrapped by an external daemon. +//! The surface is opaque-handle: `mycelium_start` returns a +//! `mycelium_node_t *` that the caller passes back into every other entry +//! point. Lifecycle (singleton-or-not, lifetime, threading) is the +//! consumer's choice, not ours. mod conversions; mod error; mod exports; mod types; -use std::sync::{Mutex, OnceLock}; - -use crate::node_handle::NodeHandle; - -static NODE: OnceLock>> = OnceLock::new(); - -pub(crate) fn node_slot() -> &'static Mutex> { - NODE.get_or_init(|| Mutex::new(None)) -} - pub use error::{ mycelium_last_error_message, MYCELIUM_ERR_INTERNAL, MYCELIUM_ERR_INVALID_ARG, MYCELIUM_ERR_INVALID_STATE, MYCELIUM_OK, diff --git a/mycelium/src/ffi/types.rs b/mycelium/src/ffi/types.rs index 451ad6e..7d16bfc 100644 --- a/mycelium/src/ffi/types.rs +++ b/mycelium/src/ffi/types.rs @@ -6,6 +6,24 @@ use core::ffi::c_char; use std::ffi::CString; +use std::sync::Mutex; + +use crate::node_handle::NodeHandle; + +/// Opaque handle to a running (or stopped) mycelium node. +/// +/// Created by [`mycelium_start`](super::mycelium_start), released by +/// [`mycelium_node_free`](super::mycelium_node_free). The C side only ever +/// sees an opaque pointer — cbindgen emits a forward declaration for this +/// struct because it is not `#[repr(C)]`. +pub struct mycelium_node_t { + pub(super) state: Mutex, +} + +pub(super) enum NodeState { + Running(NodeHandle), + Stopped, +} /// Configuration passed to `mycelium_start`. The library reads strings out /// during the call and does not retain any of the pointers — caller still @@ -160,6 +178,16 @@ pub(super) unsafe fn drain_array(ptr: *mut T, len: usize) -> Vec { // Per-type destructors. // --------------------------------------------------------------------------- +/// Release a node handle returned by `mycelium_start`. If the node is still +/// running it is stopped first. Safe to call with NULL. +#[no_mangle] +pub unsafe extern "C" fn mycelium_node_free(node: *mut mycelium_node_t) { + if node.is_null() { + return; + } + let _ = Box::from_raw(node); +} + /// Free a single C string returned by the library. Safe to call with NULL. #[no_mangle] pub unsafe extern "C" fn mycelium_string_free(ptr: *mut c_char) {