Ivan FB 430ebc0bda
feat!: expose the generated bindings, drop the hand-written FFI tier
The generated crate already provides what this tier existed to build:
CBOR marshalling, the callback dance, and a typed method per proc. So
macros.rs (trampoline, handle_ffi_call) and libwaku_response.rs
(RET_OK/RET_ERR, LibwakuResponse) are deleted rather than ported, and
node/events.rs goes with them -- its WakuEvent enum is now one generated
payload struct per event, which is what the typed listeners hand back.

What survives is what the generated crate does not give us: the domain
layer (WakuMessage, WakuContentTopic, MessageHash, pubsubtopic) and
WakuNodeConfig, which still serialises to the JSON create_node takes --
that JSON is unchanged, only its transport moved to CBOR.

BREAKING CHANGE: WakuNodeHandle and its Initialized/Running typestate,
waku_new, and waku_destroy are gone; callers use LogosDeliveryCtx::create
and let Drop tear the node down. set_event_callback is gone too: events
are per-name now, so a whole-stream callback has no equivalent -- use the
typed add_on_*_listener methods, which deliver a payload struct instead
of a JSON string to match on.

Tests and the example move with the API. The event handling gets shorter
rather than longer: no from_str, no matching five variants, no panicking
on Unrecognized -- a listener only receives the event it registered for.
Callers now base64-decode payloads themselves, since that was WakuMessage's
serde doing it before.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-16 01:08:23 +02:00

160 lines
4.9 KiB
Rust

use base64::Engine;
use multiaddr::Multiaddr;
use regex::Regex;
use secp256k1::SecretKey;
use serial_test::serial;
use std::str::FromStr;
use std::str::from_utf8;
use std::sync::{Arc, Mutex};
use std::time::Duration;
use tokio::time;
use tokio::time::sleep;
use waku_bindings::{
Encoding, LogosDeliveryCtx, WakuContentTopic, WakuMessage, WakuNodeConfig,
};
const ECHO_TIMEOUT: u64 = 1000;
const ECHO_MESSAGE: &str = "Hi from 🦀!";
const TEST_PUBSUBTOPIC: &str = "test";
const TIMEOUT: Duration = Duration::from_secs(30);
fn new_node(config: WakuNodeConfig) -> Result<LogosDeliveryCtx, String> {
let config_json = serde_json::to_string(&config).map_err(|e| e.to_string())?;
LogosDeliveryCtx::create(config_json, TIMEOUT)
}
async fn test_echo_messages(
node1: LogosDeliveryCtx,
node2: LogosDeliveryCtx,
content: &'static str,
content_topic: WakuContentTopic,
) -> Result<(), String> {
// The relay push arrives as a typed payload, so the test only has to decode
// the base64 body rather than match on an event enum.
let rx_payload: Arc<Mutex<Vec<u8>>> = Arc::new(Mutex::new(Vec::new()));
let rx_payload_cloned = rx_payload.clone();
node2.add_on_received_message_listener(move |event| {
let decoded = base64::engine::general_purpose::STANDARD
.decode(&event.waku_message.payload)
.expect("event payload should be base64");
if let Ok(mut payload) = rx_payload_cloned.lock() {
*payload = decoded;
}
});
node1.start_node_async().await?;
node2.start_node_async().await?;
node1
.waku_relay_subscribe_async(TEST_PUBSUBTOPIC.to_string())
.await?;
node2
.waku_relay_subscribe_async(TEST_PUBSUBTOPIC.to_string())
.await?;
sleep(Duration::from_secs(5)).await;
// Interconnect nodes
// Replace all matches with 127.0.0.1 to avoid issue with NAT or firewall.
let addresses1 = node1.waku_listen_addresses_async().await?;
let addresses1 = addresses1
.split(',')
.next()
.ok_or("node1 should report a listen address")?;
let re = Regex::new(r"\b(?:\d{1,3}\.){3}\d{1,3}\b").unwrap();
let addresses1 = re.replace_all(addresses1, "127.0.0.1").to_string();
// Parsed only to prove the address is well formed before handing it over.
let addresses1 = addresses1
.parse::<Multiaddr>()
.expect("parse multiaddress")
.to_string();
println!("Connecting node1 to node2: {addresses1}");
node2.waku_connect_async(addresses1, 10_000).await?;
// Wait for mesh to form
sleep(Duration::from_secs(3)).await;
dbg!("Before publish");
let message = WakuMessage::new(content, content_topic, 1, Vec::new(), false);
let message = serde_json::to_string(&message).map_err(|e| e.to_string())?;
node1
.waku_relay_publish_async(TEST_PUBSUBTOPIC.to_string(), message, 10_000)
.await
.expect("send relay messages");
// Wait for the msg to arrive
for _ in 0..50 {
let message_received = if let Ok(payload) = rx_payload.lock() {
!payload.is_empty()
&& from_utf8(&payload).expect("should be valid message") == ECHO_MESSAGE
} else {
false
};
if message_received {
node1.stop_node_async().await?;
node2.stop_node_async().await?;
return Ok(());
}
sleep(Duration::from_millis(100)).await;
}
node1.stop_node_async().await?;
node2.stop_node_async().await?;
Err("Unexpected test ending".to_string())
}
#[tokio::test]
#[serial]
async fn default_echo() -> Result<(), String> {
println!("Test default_echo");
let node1 = new_node(WakuNodeConfig {
tcp_port: Some(60010),
..Default::default()
})?;
let node2 = new_node(WakuNodeConfig {
tcp_port: Some(60020),
..Default::default()
})?;
let content_topic = WakuContentTopic::new("toychat", "2", "huilong", Encoding::Proto);
let sleep = time::sleep(Duration::from_secs(ECHO_TIMEOUT));
tokio::pin!(sleep);
// Send and receive messages. Waits until all messages received.
let got_all = tokio::select! {
_ = sleep => false,
_ = test_echo_messages(node1, node2, ECHO_MESSAGE, content_topic) => true,
};
assert!(got_all);
Ok(())
}
#[tokio::test]
#[serial]
async fn node_restart() {
let config = WakuNodeConfig {
node_key: Some(
SecretKey::from_str("05f381866cc21f6c1e2e80e07fa732008e36d942dce3206ad6dcd6793c98d609")
.unwrap(),
),
..Default::default()
};
for _ in 0..3 {
let node = new_node(config.clone()).expect("default config should be valid");
node.start_node_async()
.await
.expect("node should start with valid config");
node.stop_node_async().await.expect("node should stop");
// Resources are freed by LogosDeliveryCtx's Drop impl.
}
}