diff --git a/.gitignore b/.gitignore index 79749b6..368d2bb 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,7 @@ /.idea /.fleet nimcache/ + +# SDS state written by nodes at runtime (e.g. the channels tests) +data/ +.submodules-initialized diff --git a/waku-bindings/src/lib.rs b/waku-bindings/src/lib.rs index ca01ae9..7576a56 100644 --- a/waku-bindings/src/lib.rs +++ b/waku-bindings/src/lib.rs @@ -15,8 +15,10 @@ pub use general::libwaku_response::LibwakuResponse; use rln; pub use node::{ - waku_create_content_topic, waku_new, Initialized, Key, Multiaddr, PublicKey, RLNConfig, - Running, SecretKey, WakuEvent, WakuMessageEvent, WakuNodeConfig, WakuNodeHandle, + waku_create_content_topic, waku_new, ChannelMessageErrorEvent, ChannelMessageReceivedEvent, + ChannelMessageSentEvent, ConnectionStatusChangeEvent, Initialized, Key, MessageErrorEvent, + MessagePropagatedEvent, MessageReceivedEvent, MessageSentEvent, Multiaddr, PublicKey, + RLNConfig, Running, SecretKey, WakuEvent, WakuMessageEvent, WakuNodeConfig, WakuNodeHandle, }; pub use general::contenttopic::{Encoding, WakuContentTopic}; diff --git a/waku-bindings/src/macros.rs b/waku-bindings/src/macros.rs index 4ad9608..665a1b0 100644 --- a/waku-bindings/src/macros.rs +++ b/waku-bindings/src/macros.rs @@ -1,7 +1,7 @@ use crate::general::libwaku_response::LibwakuResponse; use std::{slice, str}; -use waku_sys::WakuCallBack; +use waku_sys::FFICallBack; unsafe extern "C" fn trampoline( ret_code: ::std::os::raw::c_int, @@ -26,7 +26,7 @@ unsafe extern "C" fn trampoline( closure(result); } -pub fn get_trampoline(_closure: &F) -> WakuCallBack +pub fn get_trampoline(_closure: &F) -> FFICallBack where F: FnMut(LibwakuResponse), { @@ -58,9 +58,9 @@ macro_rules! handle_ffi_call { let cb = get_trampoline(&closure); $waku_fn( $ctx, // Pass the context - $($($arg),*,)? // Expand the variadic arguments if provided cb, // Pass the callback trampoline - &mut closure as *mut _ as *mut c_void + &mut closure as *mut _ as *mut c_void, + $($($arg),*)? // Expand the variadic arguments if provided ) }; diff --git a/waku-bindings/src/node/channels.rs b/waku-bindings/src/node/channels.rs new file mode 100644 index 0000000..b43e145 --- /dev/null +++ b/waku-bindings/src/node/channels.rs @@ -0,0 +1,93 @@ +//! Reliable channels: an ordered, gap-detecting message stream over a content topic. + +// std +use std::ffi::CString; +// crates +use base64::Engine; +use serde::Serialize; +// internal +use crate::general::libwaku_response::{handle_no_response, handle_response, LibwakuResponse}; +use crate::general::Result; +use crate::handle_ffi_call; +use crate::node::context::WakuNodeContext; + +/// Body of `logosdelivery_channel_send`, whose payload travels base64-encoded. +#[derive(Serialize)] +struct ChannelMessage { + payload: String, + ephemeral: bool, +} + +/// Create (or restore) a reliable channel, returning its id. +/// +/// Channel state is persisted, so re-creating a previously closed channel +/// resumes it rather than starting a new one. +pub async fn logosdelivery_channel_create( + ctx: &WakuNodeContext, + channel_id: &str, + content_topic: &str, + sender_id: &str, +) -> Result { + let channel_id = + CString::new(channel_id).expect("CString should build properly from the channel id"); + let content_topic = + CString::new(content_topic).expect("CString should build properly from the content topic"); + let sender_id = + CString::new(sender_id).expect("CString should build properly from the sender id"); + + handle_ffi_call!( + waku_sys::logosdelivery_channel_create, + handle_response, + ctx.get_ptr(), + channel_id.as_ptr(), + content_topic.as_ptr(), + sender_id.as_ptr() + ) +} + +/// Send a message over a reliable channel, returning the id of the send request. +/// +/// That id is what correlates this call with the `onChannelMessageSent` / +/// `onChannelMessageError` events reporting the message's fate. +pub async fn logosdelivery_channel_send( + ctx: &WakuNodeContext, + channel_id: &str, + payload: &[u8], + ephemeral: bool, +) -> Result { + let channel_id = + CString::new(channel_id).expect("CString should build properly from the channel id"); + + let message = ChannelMessage { + payload: base64::engine::general_purpose::STANDARD.encode(payload), + ephemeral, + }; + let message = CString::new( + serde_json::to_string(&message) + .expect("ChannelMessage should always be able to success serializing"), + ) + .expect("CString should build properly from the serialized channel message"); + + handle_ffi_call!( + waku_sys::logosdelivery_channel_send, + handle_response, + ctx.get_ptr(), + channel_id.as_ptr(), + message.as_ptr() + ) +} + +/// Close a reliable channel, stopping its loops. +/// +/// Persisted state survives, so the channel can be restored by re-creating it. +pub async fn logosdelivery_channel_close(ctx: &WakuNodeContext, channel_id: &str) -> Result<()> { + let channel_id = + CString::new(channel_id).expect("CString should build properly from the channel id"); + + handle_ffi_call!( + waku_sys::logosdelivery_channel_close, + handle_no_response, + ctx.get_ptr(), + channel_id.as_ptr() + ) +} diff --git a/waku-bindings/src/node/config.rs b/waku-bindings/src/node/config.rs index 6bf5fb0..faa3daa 100644 --- a/waku-bindings/src/node/config.rs +++ b/waku-bindings/src/node/config.rs @@ -12,13 +12,18 @@ use smart_default::SmartDefault; #[serde(rename_all = "camelCase")] pub struct WakuNodeConfig { /// Listening IP address. Default `0.0.0.0` + #[serde(skip_serializing_if = "Option::is_none", rename = "listen-address")] #[default(Some(std::net::IpAddr::V4(std::net::Ipv4Addr::new(0, 0, 0, 0))))] pub host: Option, /// Libp2p TCP listening port. Default `60000`. Use `0` for **random** #[default(Some(60000))] pub tcp_port: Option, /// Secp256k1 private key in Hex format (`0x123...abc`). Default random - #[serde(with = "secret_key_serde", rename = "key")] + #[serde( + with = "secret_key_serde", + skip_serializing_if = "Option::is_none", + rename = "nodekey" + )] pub node_key: Option, /// Cluster id that the node is running in #[default(Some(0))] @@ -27,6 +32,7 @@ pub struct WakuNodeConfig { /// Relay protocol #[default(Some(true))] pub relay: Option, + #[serde(skip_serializing_if = "Vec::is_empty")] pub relay_topics: Vec, #[default(vec![1])] pub shards: Vec, diff --git a/waku-bindings/src/node/context.rs b/waku-bindings/src/node/context.rs index 87afae5..30b3c14 100644 --- a/waku-bindings/src/node/context.rs +++ b/waku-bindings/src/node/context.rs @@ -49,7 +49,7 @@ impl WakuNodeContext { *boxed_closure = Box::new(closure); unsafe { let cb = get_trampoline(&(*boxed_closure)); - waku_sys::waku_set_event_callback( + waku_sys::logosdelivery_set_event_callback( self.obj_ptr, cb, &mut (*boxed_closure) as *mut _ as *mut c_void, diff --git a/waku-bindings/src/node/events.rs b/waku-bindings/src/node/events.rs index afd9a13..028b9e9 100644 --- a/waku-bindings/src/node/events.rs +++ b/waku-bindings/src/node/events.rs @@ -27,6 +27,30 @@ pub enum WakuEvent { #[serde(rename = "connection_change")] ConnectionChange(ConnectionChangeEvent), + #[serde(rename = "connection_status_change")] + ConnectionStatusChange(ConnectionStatusChangeEvent), + + #[serde(rename = "message_sent")] + MessageSent(MessageSentEvent), + + #[serde(rename = "message_error")] + MessageError(MessageErrorEvent), + + #[serde(rename = "message_propagated")] + MessagePropagated(MessagePropagatedEvent), + + #[serde(rename = "message_received")] + MessageReceived(MessageReceivedEvent), + + #[serde(rename = "channel_message_received")] + ChannelMessageReceived(ChannelMessageReceivedEvent), + + #[serde(rename = "channel_message_sent")] + ChannelMessageSent(ChannelMessageSentEvent), + + #[serde(rename = "channel_message_error")] + ChannelMessageError(ChannelMessageErrorEvent), + Unrecognized(serde_json::Value), } @@ -62,6 +86,116 @@ pub struct ConnectionChangeEvent { pub peer_event: String, } +/// Type of `event` field for a `message sent` event +#[derive(Serialize, Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct MessageSentEvent { + /// The id returned by the originating send + pub request_id: String, + /// The message hash + pub message_hash: String, +} + +/// Type of `event` field for a `message error` event +#[derive(Serialize, Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct MessageErrorEvent { + /// The id returned by the originating send + pub request_id: String, + /// The message hash + pub message_hash: String, + /// Why the send failed + pub error: String, +} + +/// Type of `event` field for a `message propagated` event +#[derive(Serialize, Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct MessagePropagatedEvent { + /// The id returned by the originating send + pub request_id: String, + /// The message hash + pub message_hash: String, +} + +/// Type of `event` field for a `message received` event +#[derive(Serialize, Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct MessageReceivedEvent { + /// The message hash + pub message_hash: String, + /// The message in [`WakuMessage`] format + pub message: WakuMessage, +} + +/// Type of `event` field for a `connection status change` event +#[derive(Serialize, Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct ConnectionStatusChangeEvent { + /// Whether the node is online, and how it is connected + pub connection_status: String, +} + +/// Type of `event` field for a `channel message received` event +#[derive(Serialize, Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct ChannelMessageReceivedEvent { + /// The channel the message arrived on + pub channel_id: String, + /// The participant that sent the message + pub sender_id: String, + /// The message contents + #[serde(with = "base64_payload")] + pub payload: Vec, +} + +/// Type of `event` field for a `channel message sent` event +#[derive(Serialize, Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct ChannelMessageSentEvent { + /// The channel the message was sent on + pub channel_id: String, + /// The id returned by the originating send + pub request_id: String, +} + +/// Type of `event` field for a `channel message error` event +#[derive(Serialize, Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct ChannelMessageErrorEvent { + /// The channel the message was sent on + pub channel_id: String, + /// The id returned by the originating send + pub request_id: String, + /// Why the send failed + pub error: String, +} + +mod base64_payload { + use base64::Engine; + use serde::de::Error; + use serde::{Deserialize, Deserializer, Serialize, Serializer}; + + pub fn serialize(payload: &[u8], serializer: S) -> Result + where + S: Serializer, + { + base64::engine::general_purpose::STANDARD + .encode(payload) + .serialize(serializer) + } + + pub fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> + where + D: Deserializer<'de>, + { + let as_string = String::deserialize(deserializer)?; + base64::engine::general_purpose::STANDARD + .decode(as_string) + .map_err(|e| D::Error::custom(format!("{e}"))) + } +} + #[cfg(test)] mod tests { use crate::WakuEvent; @@ -87,6 +221,46 @@ mod tests { } } + #[test] + fn deserialize_channel_message_received_event() { + let s = "{\"eventType\":\"channel_message_received\",\"channelId\":\"my-channel\",\"senderId\":\"alice\",\"payload\":\"SGkgZnJvbSDwn6aAIQ==\"}"; + let evt: WakuEvent = serde_json::from_str(s).unwrap(); + match evt { + WakuEvent::ChannelMessageReceived(event) => { + assert_eq!(event.channel_id, "my-channel"); + assert_eq!(event.sender_id, "alice"); + assert_eq!(event.payload, "Hi from 🦀!".as_bytes()); + } + _ => panic!("Expected ChannelMessageReceived event, but got {:?}", evt), + } + } + + #[test] + fn deserialize_channel_message_error_event() { + let s = "{\"eventType\":\"channel_message_error\",\"channelId\":\"my-channel\",\"requestId\":\"req-1\",\"error\":\"boom\"}"; + let evt: WakuEvent = serde_json::from_str(s).unwrap(); + match evt { + WakuEvent::ChannelMessageError(event) => { + assert_eq!(event.channel_id, "my-channel"); + assert_eq!(event.request_id, "req-1"); + assert_eq!(event.error, "boom"); + } + _ => panic!("Expected ChannelMessageError event, but got {:?}", evt), + } + } + + #[test] + fn deserialize_connection_status_change_event() { + let s = "{\"eventType\":\"connection_status_change\",\"connectionStatus\":\"Online\"}"; + let evt: WakuEvent = serde_json::from_str(s).unwrap(); + match evt { + WakuEvent::ConnectionStatusChange(event) => { + assert_eq!(event.connection_status, "Online"); + } + _ => panic!("Expected ConnectionStatusChange event, but got {:?}", evt), + } + } + #[test] fn deserialize_connection_change_event() { let s = "{\"eventType\":\"connection_change\", \"peerId\":\"16Uiu2HAmAR24Mbb6VuzoyUiGx42UenDkshENVDj4qnmmbabLvo31\",\"peerEvent\":\"Joined\"}"; diff --git a/waku-bindings/src/node/management.rs b/waku-bindings/src/node/management.rs index fc01c38..e7385c2 100644 --- a/waku-bindings/src/node/management.rs +++ b/waku-bindings/src/node/management.rs @@ -36,7 +36,7 @@ pub async fn waku_new(config: Option) -> Result let mut closure = result_cb; let obj_ptr = unsafe { let cb = get_trampoline(&closure); - waku_sys::waku_new(config_ptr, cb, &mut closure as *mut _ as *mut c_void) + waku_sys::logosdelivery_create_node(config_ptr, cb, &mut closure as *mut _ as *mut c_void) }; notify.notified().await; // Wait until a result is received @@ -49,19 +49,31 @@ pub async fn waku_new(config: Option) -> Result } pub async fn waku_destroy(ctx: &WakuNodeContext) -> Result<()> { - handle_ffi_call!(waku_sys::waku_destroy, handle_no_response, ctx.get_ptr()) + handle_ffi_call!( + waku_sys::logosdelivery_destroy, + handle_no_response, + ctx.get_ptr() + ) } /// Start a Waku node mounting all the protocols that were enabled during the Waku node instantiation. /// as per the [specification](https://rfc.vac.dev/spec/36/#extern-char-waku_start) pub async fn waku_start(ctx: &WakuNodeContext) -> Result<()> { - handle_ffi_call!(waku_sys::waku_start, handle_no_response, ctx.get_ptr()) + handle_ffi_call!( + waku_sys::logosdelivery_start_node, + handle_no_response, + ctx.get_ptr() + ) } /// Stops a Waku node /// as per the [specification](https://rfc.vac.dev/spec/36/#extern-char-waku_stop) pub async fn waku_stop(ctx: &WakuNodeContext) -> Result<()> { - handle_ffi_call!(waku_sys::waku_stop, handle_no_response, ctx.get_ptr()) + handle_ffi_call!( + waku_sys::logosdelivery_stop_node, + handle_no_response, + ctx.get_ptr() + ) } /// nwaku version diff --git a/waku-bindings/src/node/mod.rs b/waku-bindings/src/node/mod.rs index e24ada9..f01a3b4 100644 --- a/waku-bindings/src/node/mod.rs +++ b/waku-bindings/src/node/mod.rs @@ -1,5 +1,6 @@ //! Waku node implementation +mod channels; mod config; mod context; mod events; @@ -26,7 +27,11 @@ use crate::general::{messagehash::MessageHash, Result, WakuMessage}; use crate::node::context::WakuNodeContext; pub use config::RLNConfig; pub use config::WakuNodeConfig; -pub use events::{WakuEvent, WakuMessageEvent}; +pub use events::{ + ChannelMessageErrorEvent, ChannelMessageReceivedEvent, ChannelMessageSentEvent, + ConnectionStatusChangeEvent, MessageErrorEvent, MessagePropagatedEvent, MessageReceivedEvent, + MessageSentEvent, WakuEvent, WakuMessageEvent, +}; pub use relay::waku_create_content_topic; // Define state marker types @@ -113,6 +118,41 @@ impl WakuNodeHandle { peers::waku_connect(&self.ctx, address, timeout).await } + /// Create (or restore) a reliable channel on `content_topic`, returning its id. + /// + /// `sender_id` identifies this participant within the channel. Messages + /// arrive through the `onChannelMessageReceived` event, so register an event + /// callback before creating the channel to avoid missing any. + pub async fn channel_create( + &self, + channel_id: &str, + content_topic: &str, + sender_id: &str, + ) -> Result { + channels::logosdelivery_channel_create(&self.ctx, channel_id, content_topic, sender_id) + .await + } + + /// Send `payload` over a reliable channel, returning the send request's id. + /// + /// The id correlates this call with the `onChannelMessageSent` / + /// `onChannelMessageError` events that report the message's fate. An + /// `ephemeral` message is not persisted and is not retransmitted. + pub async fn channel_send( + &self, + channel_id: &str, + payload: &[u8], + ephemeral: bool, + ) -> Result { + channels::logosdelivery_channel_send(&self.ctx, channel_id, payload, ephemeral).await + } + + /// Close a reliable channel. Persisted state survives, so re-creating the + /// channel restores it. + pub async fn channel_close(&self, channel_id: &str) -> Result<()> { + channels::logosdelivery_channel_close(&self.ctx, channel_id).await + } + pub async fn relay_publish_txt( &self, pubsub_topic: &PubsubTopic, diff --git a/waku-bindings/tests/channels.rs b/waku-bindings/tests/channels.rs new file mode 100644 index 0000000..2d7f07f --- /dev/null +++ b/waku-bindings/tests/channels.rs @@ -0,0 +1,68 @@ +use serial_test::serial; +use std::sync::{Arc, Mutex}; +use waku_bindings::{waku_new, LibwakuResponse, WakuEvent, WakuNodeConfig}; + +const TEST_CHANNEL_ID: &str = "test-channel"; +const TEST_CONTENT_TOPIC: &str = "/test/1/channels/proto"; +const TEST_SENDER_ID: &str = "test-sender"; + +#[tokio::test] +#[serial] +async fn channel_create_send_close() { + let node = waku_new(Some(WakuNodeConfig { + tcp_port: Some(60070), + ..Default::default() + })) + .await + .expect("node should instantiate"); + + // Channel traffic is reported through events, so capture them to prove the + // channel event payloads decode into the WakuEvent variants. + let events: Arc>> = Arc::new(Mutex::new(Vec::new())); + let events_cloned = events.clone(); + node.set_event_callback(move |response| { + if let LibwakuResponse::Success(Some(v)) = response { + let event: WakuEvent = serde_json::from_str(&v).expect("event should parse"); + events_cloned.lock().unwrap().push(event); + } + }) + .expect("event callback should be set"); + + let node = node.start().await.expect("node should start"); + + let channel_id = node + .channel_create(TEST_CHANNEL_ID, TEST_CONTENT_TOPIC, TEST_SENDER_ID) + .await + .expect("channel should be created"); + assert_eq!(channel_id, TEST_CHANNEL_ID); + + let request_id = node + .channel_send(TEST_CHANNEL_ID, b"Hi from a reliable channel!", false) + .await + .expect("channel send should succeed"); + assert!(!request_id.is_empty(), "send should return a request id"); + + // Give the send a moment to finalise and emit its outcome event. + tokio::time::sleep(std::time::Duration::from_secs(2)).await; + + node.channel_close(TEST_CHANNEL_ID) + .await + .expect("channel should close"); + + // Every event this node emitted parsed into a known variant rather than + // falling through to Unrecognized. Scoped so the guard is dropped before the + // awaits below. + { + let events = events.lock().unwrap(); + println!("events observed: {:?}", events); + assert!( + !events + .iter() + .any(|event| matches!(event, WakuEvent::Unrecognized(_))), + "no event should be Unrecognized, got: {events:?}" + ); + } + + let node = node.stop().await.expect("node should stop"); + node.waku_destroy().await.expect("node should be destroyed"); +} diff --git a/waku-sys/build.rs b/waku-sys/build.rs index 6758a67..8b52c9e 100644 --- a/waku-sys/build.rs +++ b/waku-sys/build.rs @@ -53,60 +53,114 @@ fn build_nwaku_lib(project_dir: &Path) { set_current_dir(nwaku_path).expect("Moving to vendor dir"); let mut cmd = Command::new("make"); - cmd.arg("libwaku").arg("STATIC=1"); - cmd.status() - .map_err(|e| println!("cargo:warning=make build failed due to: {e}")) - .unwrap(); + cmd.arg("liblogosdelivery").arg("STATIC=1"); + let status = cmd + .status() + .unwrap_or_else(|e| panic!("Failed to run 'make liblogosdelivery': {e}")); + + // The exit status has to be checked, not just the spawn: a failed make would + // otherwise pass silently and the crate would link a stale library from a + // previous build. + if !status.success() { + panic!("'make liblogosdelivery STATIC=1' failed with {status}"); + } set_current_dir(project_dir).expect("Going back to project dir"); } +/// Resolves a package directory under the vendor's `nimbledeps/pkgs2`, whose +/// names carry a version and hash (e.g. `nat_traversal-0.0.1-1a376d3e...`) that +/// change whenever `nimble.lock` moves. +fn nimble_pkg_dir(nwaku_path: &Path, pkg_name: &str) -> PathBuf { + let pkgs_dir = nwaku_path.join("nimbledeps/pkgs2"); + let prefix = format!("{pkg_name}-"); + std::fs::read_dir(&pkgs_dir) + .unwrap_or_else(|e| panic!("Cannot read {}: {e}", pkgs_dir.display())) + .filter_map(|entry| entry.ok()) + .map(|entry| entry.path()) + .find(|path| { + path.file_name() + .and_then(|name| name.to_str()) + .is_some_and(|name| name.starts_with(&prefix)) + }) + .unwrap_or_else(|| { + panic!( + "No '{pkg_name}' package under {}. Was 'make liblogosdelivery' run?", + pkgs_dir.display() + ) + }) +} + +/// Finds the vendor's `librln_.a` and returns the name to link it by +/// (the file stem without the `lib` prefix). +fn find_librln(nwaku_path: &Path) -> String { + std::fs::read_dir(nwaku_path) + .unwrap_or_else(|e| panic!("Cannot read {}: {e}", nwaku_path.display())) + .filter_map(|entry| entry.ok()) + .filter_map(|entry| entry.file_name().into_string().ok()) + .find_map(|name| { + name.strip_prefix("lib") + .and_then(|name| name.strip_suffix(".a")) + .filter(|name| name.starts_with("rln")) + .map(str::to_owned) + }) + .unwrap_or_else(|| { + panic!( + "No 'librln_*.a' in {}. Was 'make librln' run?", + nwaku_path.display() + ) + }) +} + fn generate_bindgen_code(project_dir: &Path) { let nwaku_path = project_dir.join("vendor"); - let header_path = nwaku_path.join("library/libwaku.h"); - - cc::Build::new() - .object( - nwaku_path - .join("vendor/nim-libbacktrace/libbacktrace_wrapper.o") - .display() - .to_string(), - ) - .compile("libbacktrace_wrapper"); + // The kernel header includes the stable one, so this single entry point + // yields both the low-level waku_* tier and the logosdelivery_* messaging + // and reliable-channel surface. + let header_path = nwaku_path.join("library/liblogosdelivery_kernel.h"); println!("cargo:rerun-if-changed={}", header_path.display()); println!( "cargo:rustc-link-search={}", nwaku_path.join("build").display() ); - println!("cargo:rustc-link-lib=static=waku"); + println!("cargo:rustc-link-lib=static=logosdelivery"); + + // nat_traversal moved from a git submodule under vendor/ to a nimble + // dependency, and builds its NAT archives inside its own package dir. + let nat_traversal = nimble_pkg_dir(&nwaku_path, "nat_traversal"); println!( "cargo:rustc-link-search={}", - nwaku_path - .join("vendor/nim-nat-traversal/vendor/miniupnp/miniupnpc/build") + nat_traversal + .join("vendor/miniupnp/miniupnpc/build") .display() ); println!("cargo:rustc-link-lib=static=miniupnpc"); println!( "cargo:rustc-link-search={}", - nwaku_path - .join("vendor/nim-nat-traversal/vendor/libnatpmp-upstream") - .display() + nat_traversal.join("vendor/libnatpmp-upstream").display() ); println!("cargo:rustc-link-lib=static=natpmp"); println!("cargo:rustc-link-lib=dl"); println!("cargo:rustc-link-lib=m"); - println!( - "cargo:rustc-link-search=native={}", - nwaku_path - .join("vendor/nim-libbacktrace/install/usr/lib") - .display() - ); - println!("cargo:rustc-link-lib=static=backtrace"); + // boringssl (pulled in by libp2p) is C++, so its runtime has to be linked. + if cfg!(target_os = "macos") { + println!("cargo:rustc-link-lib=c++"); + } else { + println!("cargo:rustc-link-lib=stdc++"); + } + + // The vendor's Makefile fetches librln into its root, naming it after the + // RLN version it pins, so the archive is discovered rather than hardcoded. + let librln = find_librln(&nwaku_path); + println!("cargo:rustc-link-search={}", nwaku_path.display()); + println!("cargo:rustc-link-lib=static={librln}"); + + // libbacktrace is not linked: the vendor builds with -d:disable_libbacktrace. cc::Build::new() .file("src/cmd.c") // Compile the C file diff --git a/waku-sys/vendor b/waku-sys/vendor index 4117449..77cb8a6 160000 --- a/waku-sys/vendor +++ b/waku-sys/vendor @@ -1 +1 @@ -Subproject commit 4117449b9af6c0304a6115dd4bc0d1d745159685 +Subproject commit 77cb8a6c7a199b7823e5149bb72f8ce384883b8f