Refactor FFI to work on node pointer

Instead of using a singleton in the interface

Signed-off-by: Lee Smet <lee.smet@hotmail.com>
This commit is contained in:
Lee Smet
2026-04-27 14:02:57 +02:00
parent a14a7df72a
commit 16d3e0afb3
4 changed files with 207 additions and 124 deletions
+43 -20
View File
@@ -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.
+132 -89
View File
@@ -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<String>) -> Result<PeerDisco
}
}
/// Borrow the inner running [`NodeHandle`] from a node pointer, or return
/// the appropriate error code if the pointer is NULL or the node has been
/// stopped.
macro_rules! with_running_node {
($node:expr, $body:expr) => {{
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<mycelium_peer_info_t> = 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<mycelium_route_t> = 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<mycelium_route_t> = 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<mycelium_queried_subnet_t> = 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<mycelium_packet_stat_entry_t> = 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
+4 -15
View File
@@ -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<Mutex<Option<NodeHandle>>> = OnceLock::new();
pub(crate) fn node_slot() -> &'static Mutex<Option<NodeHandle>> {
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,
+28
View File
@@ -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<NodeState>,
}
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<T>(ptr: *mut T, len: usize) -> Vec<T> {
// 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) {