diff --git a/Cargo.lock b/Cargo.lock index a0b6249..3a6fb9f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1674,13 +1674,14 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "waku-bindings" -version = "0.3.1" +version = "0.4.0" dependencies = [ "aes-gcm", "base64 0.21.0", "enr", "futures", "hex", + "libc", "multiaddr", "once_cell", "rand", diff --git a/waku-bindings/Cargo.toml b/waku-bindings/Cargo.toml index 9636321..1ac48c7 100644 --- a/waku-bindings/Cargo.toml +++ b/waku-bindings/Cargo.toml @@ -27,6 +27,7 @@ sscanf = "0.4" smart-default = "0.6" url = "2.3" waku-sys = { version = "0.4.0", path = "../waku-sys" } +libc = "0.2" [dev-dependencies] futures = "0.3.25" diff --git a/waku-bindings/src/decrypt.rs b/waku-bindings/src/decrypt.rs index b1482ba..430ea5e 100644 --- a/waku-bindings/src/decrypt.rs +++ b/waku-bindings/src/decrypt.rs @@ -4,10 +4,11 @@ use std::ffi::CString; // crates use aes_gcm::{Aes256Gcm, Key}; +use libc::*; use secp256k1::SecretKey; // internal use crate::general::{DecodedPayload, Result, WakuMessage}; -use crate::utils::decode_and_free_response; +use crate::utils::{get_trampoline, handle_json_response}; /// Decrypt a message using a symmetric key /// @@ -28,14 +29,25 @@ pub fn waku_decode_symmetric( .expect("CString should build properly from hex encoded symmetric key") .into_raw(); - let result_ptr = unsafe { - let res = waku_sys::waku_decode_symmetric(message_ptr, symk_ptr); + let mut result: String = Default::default(); + let result_cb = |v: &str| result = v.to_string(); + let code = unsafe { + let mut closure = result_cb; + let cb = get_trampoline(&closure); + let out = waku_sys::waku_decode_symmetric( + message_ptr, + symk_ptr, + cb, + &mut closure as *mut _ as *mut c_void, + ); + drop(CString::from_raw(message_ptr)); drop(CString::from_raw(symk_ptr)); - res + + out }; - decode_and_free_response(result_ptr) + handle_json_response(code, &result) } /// Decrypt a message using a symmetric key @@ -57,12 +69,23 @@ pub fn waku_decode_asymmetric( .expect("CString should build properly from hex encoded symmetric key") .into_raw(); - let result_ptr = unsafe { - let res = waku_sys::waku_decode_asymmetric(message_ptr, sk_ptr); + let mut result: String = Default::default(); + let result_cb = |v: &str| result = v.to_string(); + let code = unsafe { + let mut closure = result_cb; + let cb = get_trampoline(&closure); + let out = waku_sys::waku_decode_asymmetric( + message_ptr, + sk_ptr, + cb, + &mut closure as *mut _ as *mut c_void, + ); + drop(CString::from_raw(message_ptr)); drop(CString::from_raw(sk_ptr)); - res + + out }; - decode_and_free_response(result_ptr) + handle_json_response(code, &result) } diff --git a/waku-bindings/src/encrypt.rs b/waku-bindings/src/encrypt.rs index 2a8bd9a..0933c86 100644 --- a/waku-bindings/src/encrypt.rs +++ b/waku-bindings/src/encrypt.rs @@ -2,10 +2,11 @@ use std::ffi::CString; // crates use aes_gcm::{Aes256Gcm, Key}; +use libc::*; use secp256k1::{PublicKey, SecretKey}; // internal use crate::general::{Result, WakuMessage}; -use crate::utils::decode_and_free_response; +use crate::utils::{get_trampoline, handle_json_response}; /// Optionally sign and encrypt a message using asymmetric encryption pub fn waku_encode_asymmetric( @@ -16,7 +17,7 @@ pub fn waku_encode_asymmetric( let pk = hex::encode(public_key.serialize_uncompressed()); let sk = signing_key .map(|signing_key| hex::encode(signing_key.secret_bytes())) - .unwrap_or_else(String::new); + .unwrap_or_default(); let message_ptr = CString::new( serde_json::to_string(&message) .expect("WakuMessages should always be able to success serializing"), @@ -30,15 +31,27 @@ pub fn waku_encode_asymmetric( .expect("CString should build properly from hex encoded signing key") .into_raw(); - let result_ptr = unsafe { - let res = waku_sys::waku_encode_asymmetric(message_ptr, pk_ptr, sk_ptr); + let mut result: String = Default::default(); + let result_cb = |v: &str| result = v.to_string(); + let code = unsafe { + let mut closure = result_cb; + let cb = get_trampoline(&closure); + let out = waku_sys::waku_encode_asymmetric( + message_ptr, + pk_ptr, + sk_ptr, + cb, + &mut closure as *mut _ as *mut c_void, + ); + drop(CString::from_raw(message_ptr)); drop(CString::from_raw(pk_ptr)); drop(CString::from_raw(sk_ptr)); - res + + out }; - decode_and_free_response(result_ptr) + handle_json_response(code, &result) } /// Optionally sign and encrypt a message using symmetric encryption @@ -50,7 +63,7 @@ pub fn waku_encode_symmetric( let symk = hex::encode(symmetric_key.as_slice()); let sk = signing_key .map(|signing_key| hex::encode(signing_key.secret_bytes())) - .unwrap_or_else(String::new); + .unwrap_or_default(); let message_ptr = CString::new( serde_json::to_string(&message) .expect("WakuMessages should always be able to success serializing"), @@ -64,13 +77,25 @@ pub fn waku_encode_symmetric( .expect("CString should build properly from hex encoded signing key") .into_raw(); - let result_ptr = unsafe { - let res = waku_sys::waku_encode_symmetric(message_ptr, symk_ptr, sk_ptr); + let mut result: String = Default::default(); + let result_cb = |v: &str| result = v.to_string(); + let code = unsafe { + let mut closure = result_cb; + let cb = get_trampoline(&closure); + let out = waku_sys::waku_encode_symmetric( + message_ptr, + symk_ptr, + sk_ptr, + cb, + &mut closure as *mut _ as *mut c_void, + ); + drop(CString::from_raw(message_ptr)); drop(CString::from_raw(symk_ptr)); drop(CString::from_raw(sk_ptr)); - res + + out }; - decode_and_free_response(result_ptr) + handle_json_response(code, &result) } diff --git a/waku-bindings/src/events/mod.rs b/waku-bindings/src/events/mod.rs index 08b728b..445fc49 100644 --- a/waku-bindings/src/events/mod.rs +++ b/waku-bindings/src/events/mod.rs @@ -5,7 +5,7 @@ //! When an event is emitted, this callback will be triggered receiving a [`Signal`] // std -use std::ffi::{c_char, CStr}; +use std::ffi::{c_char, c_void, CStr}; use std::ops::Deref; use std::sync::Mutex; // crates @@ -79,7 +79,7 @@ fn set_callback(f: F) { /// Wrapper callback, it transformst the `*const c_char` into a [`Signal`] /// and executes the [`CALLBACK`] funtion with it -extern "C" fn callback(data: *const c_char) { +extern "C" fn callback(data: *const c_char, _user_data: *mut c_void) { let raw_response = unsafe { CStr::from_ptr(data) } .to_str() .expect("Not null ptr"); @@ -95,7 +95,7 @@ extern "C" fn callback(data: *const c_char) { /// which are used to react to asynchronous events in Waku pub fn waku_set_event_callback(f: F) { set_callback(f); - unsafe { waku_sys::waku_set_event_callback(callback as *mut std::ffi::c_void) }; + unsafe { waku_sys::waku_set_event_callback(Some(callback)) }; } #[cfg(test)] diff --git a/waku-bindings/src/general/mod.rs b/waku-bindings/src/general/mod.rs index ae35658..dfcbf51 100644 --- a/waku-bindings/src/general/mod.rs +++ b/waku-bindings/src/general/mod.rs @@ -51,29 +51,9 @@ impl Display for ProtocolId { } } -/// JsonResponse wrapper. -/// `go-waku` ffi returns this type as a `char *` as per the [specification](https://rfc.vac.dev/spec/36/#jsonresponse-type) -/// This is internal, as it is better to use rust plain `Result` type. -#[derive(Deserialize, Debug)] -#[serde(rename_all = "snake_case")] -pub(crate) enum JsonResponse { - Result(T), - Error(String), -} - /// Waku response, just a `Result` with an `String` error. pub type Result = std::result::Result; -/// Convenient we can transform a [`JsonResponse`] into a [`std::result::Result`] -impl From> for Result { - fn from(response: JsonResponse) -> Self { - match response { - JsonResponse::Result(t) => Ok(t), - JsonResponse::Error(e) => Err(e), - } - } -} - // TODO: Properly type and deserialize payload form base64 encoded string /// Waku message in JSON format. /// as per the [specification](https://rfc.vac.dev/spec/36/#jsonmessage-type) diff --git a/waku-bindings/src/node/discovery.rs b/waku-bindings/src/node/discovery.rs index 024012e..5a38af5 100644 --- a/waku-bindings/src/node/discovery.rs +++ b/waku-bindings/src/node/discovery.rs @@ -3,11 +3,12 @@ use std::ffi::CString; use std::time::Duration; // crates use enr::Enr; +use libc::*; use multiaddr::Multiaddr; use serde::Deserialize; use url::{Host, Url}; // internal -use crate::utils::decode_and_free_response; +use crate::utils::{get_trampoline, handle_json_response, handle_no_response}; use crate::{PeerId, Result}; #[derive(Deserialize, Debug)] @@ -30,15 +31,16 @@ pub fn waku_dns_discovery( let url = CString::new(url.to_string()) .expect("CString should build properly from a valid Url") .into_raw(); - let server = CString::new( - server - .map(|host| host.to_string()) - .unwrap_or_else(|| "".to_string()), - ) - .expect("CString should build properly from a String nameserver") - .into_raw(); - let result_ptr = unsafe { - let res = waku_sys::waku_dns_discovery( + let server = CString::new(server.map(|host| host.to_string()).unwrap_or_default()) + .expect("CString should build properly from a String nameserver") + .into_raw(); + + let mut result: String = Default::default(); + let result_cb = |v: &str| result = v.to_string(); + let code = unsafe { + let mut closure = result_cb; + let cb = get_trampoline(&closure); + let out = waku_sys::waku_dns_discovery( url, server, timeout @@ -49,14 +51,17 @@ pub fn waku_dns_discovery( .expect("Duration as milliseconds should fit in a i32") }) .unwrap_or(0), + cb, + &mut closure as *mut _ as *mut c_void, ); - // Recover strings and drop them + drop(CString::from_raw(url)); drop(CString::from_raw(server)); - res + + out }; - decode_and_free_response(result_ptr) + handle_json_response(code, &result) } /// Update the bootnodes used by DiscoveryV5 by passing a list of ENRs @@ -68,13 +73,23 @@ pub fn waku_discv5_update_bootnodes(bootnodes: Vec) -> Result<()> { .expect("CString should build properly from the string vector") .into_raw(); - let result_ptr = unsafe { - let res = waku_sys::waku_discv5_update_bootnodes(bootnodes_ptr); + let mut error: String = Default::default(); + let error_cb = |v: &str| error = v.to_string(); + let code = unsafe { + let mut closure = error_cb; + let cb = get_trampoline(&closure); + let out = waku_sys::waku_discv5_update_bootnodes( + bootnodes_ptr, + cb, + &mut closure as *mut _ as *mut c_void, + ); + drop(CString::from_raw(bootnodes_ptr)); - res + + out }; - decode_and_free_response::(result_ptr).map(|_| ()) + handle_no_response(code, &error) } #[cfg(test)] diff --git a/waku-bindings/src/node/filter.rs b/waku-bindings/src/node/filter.rs index 77a9c2b..8b18122 100644 --- a/waku-bindings/src/node/filter.rs +++ b/waku-bindings/src/node/filter.rs @@ -4,11 +4,11 @@ use std::ffi::CString; use std::time::Duration; // crates - +use libc::*; // internal use crate::general::Result; use crate::general::{FilterSubscription, PeerId}; -use crate::utils::decode_and_free_response; +use crate::utils::{get_trampoline, handle_no_response}; /// Creates a subscription in a lightnode for messages that matches a content filter and optionally a [`WakuPubSubTopic`](`crate::general::WakuPubSubTopic`) /// As per the [specification](https://rfc.vac.dev/spec/36/#extern-char-waku_filter_subscribechar-filterjson-char-peerid-int-timeoutms) @@ -27,20 +27,29 @@ pub fn waku_filter_subscribe( .expect("PeerId should always be able to be serialized") .into_raw(); - let result_ptr = unsafe { - let result_ptr = waku_sys::waku_legacy_filter_subscribe( + let mut error: String = Default::default(); + let error_cb = |v: &str| error = v.to_string(); + let code = unsafe { + let mut closure = error_cb; + let cb = get_trampoline(&closure); + let out = waku_sys::waku_legacy_filter_subscribe( filter_subscription_ptr, peer_id_ptr, timeout .as_millis() .try_into() .expect("Duration as milliseconds should fit in a i32"), + cb, + &mut closure as *mut _ as *mut c_void, ); + drop(CString::from_raw(filter_subscription_ptr)); drop(CString::from_raw(peer_id_ptr)); - result_ptr + + out }; - decode_and_free_response::(result_ptr).map(|_| ()) + + handle_no_response(code, &error) } /// Removes subscriptions in a light node matching a content filter and, optionally, a [`WakuPubSubTopic`](`crate::general::WakuPubSubTopic`) @@ -55,17 +64,26 @@ pub fn waku_filter_unsubscribe( ) .expect("CString should build properly from the serialized filter subscription") .into_raw(); - let result_ptr = unsafe { - let res = waku_sys::waku_legacy_filter_unsubscribe( + + let mut error: String = Default::default(); + let error_cb = |v: &str| error = v.to_string(); + let code = unsafe { + let mut closure = error_cb; + let cb = get_trampoline(&closure); + let out = waku_sys::waku_legacy_filter_unsubscribe( filter_subscription_ptr, timeout .as_millis() .try_into() .expect("Duration as milliseconds should fit in a i32"), + cb, + &mut closure as *mut _ as *mut c_void, ); + drop(CString::from_raw(filter_subscription_ptr)); - res + + out }; - decode_and_free_response::(result_ptr).map(|_| ()) + handle_no_response(code, &error) } diff --git a/waku-bindings/src/node/lightpush.rs b/waku-bindings/src/node/lightpush.rs index 858424f..f51432a 100644 --- a/waku-bindings/src/node/lightpush.rs +++ b/waku-bindings/src/node/lightpush.rs @@ -3,10 +3,12 @@ // std use std::ffi::CString; use std::time::Duration; +// crates +use libc::*; // internal use crate::general::{MessageId, PeerId, Result, WakuMessage, WakuPubSubTopic}; use crate::node::waku_default_pubsub_topic; -use crate::utils::decode_and_free_response; +use crate::utils::{get_trampoline, handle_response}; /// Publish a message using Waku Lightpush /// As per the [specification](https://rfc.vac.dev/spec/36/#extern-char-waku_lightpush_publishchar-messagejson-char-topic-char-peerid-int-timeoutms) @@ -31,8 +33,13 @@ pub fn waku_lightpush_publish( let peer_id_ptr = CString::new(peer_id) .expect("CString should build properly from peer id") .into_raw(); - let result_ptr = unsafe { - let res = waku_sys::waku_lightpush_publish( + + let mut result: String = Default::default(); + let result_cb = |v: &str| result = v.to_string(); + let code = unsafe { + let mut closure = result_cb; + let cb = get_trampoline(&closure); + let out = waku_sys::waku_lightpush_publish( message_ptr, topic_ptr, peer_id_ptr, @@ -44,12 +51,16 @@ pub fn waku_lightpush_publish( .expect("Duration as milliseconds should fit in a i32") }) .unwrap_or(0), + cb, + &mut closure as *mut _ as *mut c_void, ); + drop(CString::from_raw(message_ptr)); drop(CString::from_raw(topic_ptr)); drop(CString::from_raw(peer_id_ptr)); - res + + out }; - decode_and_free_response(result_ptr) + handle_response(code, &result) } diff --git a/waku-bindings/src/node/management.rs b/waku-bindings/src/node/management.rs index f5f1c70..2317564 100644 --- a/waku-bindings/src/node/management.rs +++ b/waku-bindings/src/node/management.rs @@ -4,14 +4,15 @@ use multiaddr::Multiaddr; use std::ffi::CString; // crates +use libc::*; // internal use super::config::WakuNodeConfig; use crate::general::{PeerId, Result}; -use crate::utils::decode_and_free_response; +use crate::utils::{get_trampoline, handle_json_response, handle_no_response, handle_response}; /// Instantiates a Waku node /// as per the [specification](https://rfc.vac.dev/spec/36/#extern-char-waku_newchar-jsonconfig) -pub fn waku_new(config: Option) -> Result { +pub fn waku_new(config: Option) -> Result<()> { let config = config.unwrap_or_default(); let config_ptr = CString::new( @@ -21,41 +22,75 @@ pub fn waku_new(config: Option) -> Result { .expect("CString should build properly from the config") .into_raw(); - let result_ptr = unsafe { - let res = waku_sys::waku_new(config_ptr); + let mut error: String = Default::default(); + let error_cb = |v: &str| error = v.to_string(); + let code = unsafe { + let mut closure = error_cb; + let cb = get_trampoline(&closure); + let out = waku_sys::waku_new(config_ptr, cb, &mut closure as *mut _ as *mut c_void); + drop(CString::from_raw(config_ptr)); - res + + out }; - decode_and_free_response(result_ptr) + handle_no_response(code, &error) } /// 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 fn waku_start() -> Result { - let response_ptr = unsafe { waku_sys::waku_start() }; - decode_and_free_response(response_ptr) +pub fn waku_start() -> Result<()> { + let mut error: String = Default::default(); + let error_cb = |v: &str| error = v.to_string(); + let code = unsafe { + let mut closure = error_cb; + let cb = get_trampoline(&closure); + waku_sys::waku_start(cb, &mut closure as *mut _ as *mut c_void) + }; + + handle_no_response(code, &error) } /// Stops a Waku node /// as per the [specification](https://rfc.vac.dev/spec/36/#extern-char-waku_stop) -pub fn waku_stop() -> Result { - let response_ptr = unsafe { waku_sys::waku_stop() }; - decode_and_free_response(response_ptr) +pub fn waku_stop() -> Result<()> { + let mut error: String = Default::default(); + let error_cb = |v: &str| error = v.to_string(); + let code = unsafe { + let mut closure = error_cb; + let cb = get_trampoline(&closure); + waku_sys::waku_stop(cb, &mut closure as *mut _ as *mut c_void) + }; + + handle_no_response(code, &error) } /// If the execution is successful, the result is the peer ID as a string (base58 encoded) /// as per the [specification](https://rfc.vac.dev/spec/36/#extern-char-waku_stop) pub fn waku_peer_id() -> Result { - let response_ptr = unsafe { waku_sys::waku_peerid() }; - decode_and_free_response(response_ptr) + let mut result: String = Default::default(); + let result_cb = |v: &str| result = v.to_string(); + let code = unsafe { + let mut closure = result_cb; + let cb = get_trampoline(&closure); + waku_sys::waku_peerid(cb, &mut closure as *mut _ as *mut c_void) + }; + + handle_response(code, &result) } /// Get the multiaddresses the Waku node is listening to /// as per [specification](https://rfc.vac.dev/spec/36/#extern-char-waku_listen_addresses) pub fn waku_listen_addresses() -> Result> { - let response_ptr = unsafe { waku_sys::waku_listen_addresses() }; - decode_and_free_response(response_ptr) + let mut result: String = Default::default(); + let result_cb = |v: &str| result = v.to_string(); + let code = unsafe { + let mut closure = result_cb; + let cb = get_trampoline(&closure); + waku_sys::waku_listen_addresses(cb, &mut closure as *mut _ as *mut c_void) + }; + + handle_json_response(code, &result) } #[cfg(test)] diff --git a/waku-bindings/src/node/peers.rs b/waku-bindings/src/node/peers.rs index 647b184..d6e3bae 100644 --- a/waku-bindings/src/node/peers.rs +++ b/waku-bindings/src/node/peers.rs @@ -4,11 +4,12 @@ use std::ffi::CString; use std::time::Duration; // crates +use libc::*; use multiaddr::Multiaddr; use serde::Deserialize; // internal use crate::general::{PeerId, ProtocolId, Result}; -use crate::utils::decode_and_free_response; +use crate::utils::{get_trampoline, handle_json_response, handle_no_response, handle_response}; /// Add a node multiaddress and protocol to the waku node’s peerstore. /// As per the [specification](https://rfc.vac.dev/spec/36/#extern-char-waku_add_peerchar-address-char-protocolid) @@ -20,14 +21,25 @@ pub fn waku_add_peers(address: &Multiaddr, protocol_id: ProtocolId) -> Result(response_ptr).map(|_| ()) + handle_no_response(code, &error) } /// Dial peer using a peer id @@ -65,18 +86,27 @@ pub fn waku_connect_peer_with_id(peer_id: &PeerId, timeout: Option) -> let peer_id_ptr = CString::new(peer_id.as_bytes()) .expect("CString should build properly from peer id") .into_raw(); - let result_ptr = unsafe { - let res = waku_sys::waku_connect_peerid( + + let mut error: String = Default::default(); + let error_cb = |v: &str| error = v.to_string(); + let code = unsafe { + let mut closure = error_cb; + let cb = get_trampoline(&closure); + let out = waku_sys::waku_connect_peerid( peer_id_ptr, timeout .map(|duration| duration.as_millis().try_into().unwrap_or(i32::MAX)) .unwrap_or(0), + cb, + &mut closure as *mut _ as *mut c_void, ); + drop(CString::from_raw(peer_id_ptr)); - res + + out }; - decode_and_free_response::(result_ptr).map(|_| ()) + handle_no_response(code, &error) } /// Disconnect a peer using its peer id @@ -86,23 +116,33 @@ pub fn waku_disconnect_peer_with_id(peer_id: &PeerId) -> Result<()> { .expect("CString should build properly from peer id") .into_raw(); - let response_ptr = unsafe { - let res = waku_sys::waku_disconnect(peer_id_ptr); + let mut error: String = Default::default(); + let error_cb = |v: &str| error = v.to_string(); + let code = unsafe { + let mut closure = error_cb; + let cb = get_trampoline(&closure); + let out = waku_sys::waku_disconnect(peer_id_ptr, cb, &mut closure as *mut _ as *mut c_void); + drop(CString::from_raw(peer_id_ptr)); - res + + out }; - decode_and_free_response::(response_ptr).map(|_| ()) + + handle_no_response(code, &error) } /// Get number of connected peers /// As per the [specification](https://rfc.vac.dev/spec/36/#extern-char-waku_peer_count) pub fn waku_peer_count() -> Result { - let response_ptr = unsafe { waku_sys::waku_peer_cnt() }; - let num_str = decode_and_free_response::(response_ptr)?; - let num = num_str - .parse::() - .map_err(|_| "could not convert peer count into u32".to_string())?; - usize::try_from(num).map_err(|_| "could not convert peer count into usize".to_string()) + let mut result: String = Default::default(); + let result_cb = |v: &str| result = v.to_string(); + let code = unsafe { + let mut closure = result_cb; + let cb = get_trampoline(&closure); + waku_sys::waku_peer_cnt(cb, &mut closure as *mut _ as *mut c_void) + }; + + handle_response(code, &result) } /// Waku peer supported protocol @@ -153,8 +193,15 @@ pub type WakuPeers = Vec; /// Retrieve the list of peers known by the Waku node /// As per the [specification](https://rfc.vac.dev/spec/36/#extern-char-waku_peers) pub fn waku_peers() -> Result { - let response_ptr = unsafe { waku_sys::waku_peers() }; - decode_and_free_response(response_ptr) + let mut result: String = Default::default(); + let result_cb = |v: &str| result = v.to_string(); + let code = unsafe { + let mut closure = result_cb; + let cb = get_trampoline(&closure); + waku_sys::waku_peers(cb, &mut closure as *mut _ as *mut c_void) + }; + + handle_json_response(code, &result) } #[cfg(test)] diff --git a/waku-bindings/src/node/relay.rs b/waku-bindings/src/node/relay.rs index e4b226a..bce3483 100644 --- a/waku-bindings/src/node/relay.rs +++ b/waku-bindings/src/node/relay.rs @@ -1,11 +1,13 @@ //! Waku [relay](https://rfc.vac.dev/spec/36/#waku-relay) protocol related methods // std -use std::ffi::{CStr, CString}; +use std::ffi::CString; use std::time::Duration; +// crates +use libc::*; // internal use crate::general::{Encoding, MessageId, Result, WakuContentTopic, WakuMessage, WakuPubSubTopic}; -use crate::utils::decode_and_free_response; +use crate::utils::{get_trampoline, handle_json_response, handle_no_response, handle_response}; /// Create a content topic according to [RFC 23](https://rfc.vac.dev/spec/23/) /// As per the [specification](https://rfc.vac.dev/spec/36/#extern-char-waku_content_topicchar-applicationname-unsigned-int-applicationversion-char-contenttopicname-char-encoding) @@ -28,27 +30,29 @@ pub fn waku_create_content_topic( .expect("Encoding should always transform to CString") .into_raw(); - let result_ptr = unsafe { - let res = waku_sys::waku_content_topic( + let mut result: String = Default::default(); + let result_cb = |v: &str| result = v.to_string(); + let code = unsafe { + let mut closure = result_cb; + let cb = get_trampoline(&closure); + let out = waku_sys::waku_content_topic( application_name_ptr, application_version, content_topic_name_ptr, encoding_ptr, + cb, + &mut closure as *mut _ as *mut c_void, ); + drop(CString::from_raw(application_name_ptr)); drop(CString::from_raw(content_topic_name_ptr)); drop(CString::from_raw(encoding_ptr)); - res + + out }; - let result = unsafe { CStr::from_ptr(result_ptr) } - .to_str() + + handle_response::(code, &result) .expect("&str from result should always be extracted") - .parse() - .expect("Content topic data should be always parseable"); - - unsafe { waku_sys::waku_utils_free(result_ptr) }; - - result } /// Create a pubsub topic according to [RFC 23](https://rfc.vac.dev/spec/23/) @@ -61,44 +65,52 @@ pub fn waku_create_pubsub_topic(topic_name: &str, encoding: Encoding) -> WakuPub .expect("Encoding should always transform to CString") .into_raw(); - let result_ptr = unsafe { - let res = waku_sys::waku_pubsub_topic(topic_name_ptr, encoding_ptr); + let mut result: String = Default::default(); + let result_cb = |v: &str| result = v.to_string(); + let code = unsafe { + let mut closure = result_cb; + let cb = get_trampoline(&closure); + let out = waku_sys::waku_pubsub_topic( + topic_name_ptr, + encoding_ptr, + cb, + &mut closure as *mut _ as *mut c_void, + ); + drop(CString::from_raw(topic_name_ptr)); drop(CString::from_raw(encoding_ptr)); - res + + out }; - let result = unsafe { CStr::from_ptr(result_ptr) } - .to_str() - .expect("&str from result should always be extracted") - .parse() - .expect("Pubsub topic data should be always parseable"); - - unsafe { waku_sys::waku_utils_free(result_ptr) }; - - result + handle_response(code, &result).expect("&str from result should always be extracted") } /// Default pubsub topic used for exchanging waku messages defined in [RFC 10](https://rfc.vac.dev/spec/10/) pub fn waku_default_pubsub_topic() -> WakuPubSubTopic { - let result_ptr = unsafe { waku_sys::waku_default_pubsub_topic() }; + let mut result: String = Default::default(); + let result_cb = |v: &str| result = v.to_string(); + let code = unsafe { + let mut closure = result_cb; + let cb = get_trampoline(&closure); + waku_sys::waku_default_pubsub_topic(cb, &mut closure as *mut _ as *mut c_void) + }; - let result = unsafe { CStr::from_ptr(result_ptr) } - .to_str() - .expect("&str from result should always be extracted") - .parse() - .expect("Default pubsub topic should always be parseable"); - - unsafe { waku_sys::waku_utils_free(result_ptr) }; - - result + handle_response(code, &result).expect("&str from result should always be extracted") } /// Get the list of subscribed pubsub topics in Waku Relay. /// As per the [specification](https://rfc.vac.dev/spec/36/#extern-char-waku_relay_topics) pub fn waku_relay_topics() -> Result> { - let result_ptr = unsafe { waku_sys::waku_relay_topics() }; - decode_and_free_response(result_ptr) + let mut result: String = Default::default(); + let result_cb = |v: &str| result = v.to_string(); + let code = unsafe { + let mut closure = result_cb; + let cb = get_trampoline(&closure); + waku_sys::waku_relay_topics(cb, &mut closure as *mut _ as *mut c_void) + }; + + handle_json_response(code, &result) } /// Publish a message using Waku Relay @@ -122,8 +134,12 @@ pub fn waku_relay_publish_message( .expect("CString should build properly from pubsub topic") .into_raw(); - let result_ptr = unsafe { - let res = waku_sys::waku_relay_publish( + let mut result: String = Default::default(); + let result_cb = |v: &str| result = v.to_string(); + let code = unsafe { + let mut closure = result_cb; + let cb = get_trampoline(&closure); + let out = waku_sys::waku_relay_publish( message_ptr, pubsub_topic_ptr, timeout @@ -134,13 +150,17 @@ pub fn waku_relay_publish_message( .expect("Duration as milliseconds should fit in a i32") }) .unwrap_or(0), + cb, + &mut closure as *mut _ as *mut c_void, ); + drop(CString::from_raw(message_ptr)); drop(CString::from_raw(pubsub_topic_ptr)); - res + + out }; - decode_and_free_response(result_ptr) + handle_response(code, &result) } pub fn waku_enough_peers(pubsub_topic: Option) -> Result { @@ -152,13 +172,23 @@ pub fn waku_enough_peers(pubsub_topic: Option) -> Result .expect("CString should build properly from pubsub topic") .into_raw(); - let result_ptr = unsafe { - let res = waku_sys::waku_relay_enough_peers(pubsub_topic_ptr); + let mut result: String = Default::default(); + let result_cb = |v: &str| result = v.to_string(); + let code = unsafe { + let mut closure = result_cb; + let cb = get_trampoline(&closure); + let out = waku_sys::waku_relay_enough_peers( + pubsub_topic_ptr, + cb, + &mut closure as *mut _ as *mut c_void, + ); + drop(CString::from_raw(pubsub_topic_ptr)); - res + + out }; - decode_and_free_response(result_ptr) + handle_response(code, &result) } pub fn waku_relay_subscribe(pubsub_topic: Option) -> Result<()> { @@ -170,13 +200,23 @@ pub fn waku_relay_subscribe(pubsub_topic: Option) -> Result<()> .expect("CString should build properly from pubsub topic") .into_raw(); - let result_ptr = unsafe { - let res = waku_sys::waku_relay_subscribe(pubsub_topic_ptr); + let mut error: String = Default::default(); + let error_cb = |v: &str| error = v.to_string(); + let code = unsafe { + let mut closure = error_cb; + let cb = get_trampoline(&closure); + let out = waku_sys::waku_relay_subscribe( + pubsub_topic_ptr, + cb, + &mut closure as *mut _ as *mut c_void, + ); + drop(CString::from_raw(pubsub_topic_ptr)); - res + + out }; - decode_and_free_response::(result_ptr).map(|_| ()) + handle_no_response(code, &error) } pub fn waku_relay_unsubscribe(pubsub_topic: Option) -> Result<()> { @@ -188,11 +228,21 @@ pub fn waku_relay_unsubscribe(pubsub_topic: Option) -> Result<( .expect("CString should build properly from pubsub topic") .into_raw(); - let result_ptr = unsafe { - let res = waku_sys::waku_relay_unsubscribe(pubsub_topic_ptr); + let mut error: String = Default::default(); + let error_cb = |v: &str| error = v.to_string(); + let code = unsafe { + let mut closure = error_cb; + let cb = get_trampoline(&closure); + let out = waku_sys::waku_relay_subscribe( + pubsub_topic_ptr, + cb, + &mut closure as *mut _ as *mut c_void, + ); + drop(CString::from_raw(pubsub_topic_ptr)); - res + + out }; - decode_and_free_response::(result_ptr).map(|_| ()) + handle_no_response(code, &error) } diff --git a/waku-bindings/src/node/store.rs b/waku-bindings/src/node/store.rs index c5dca16..32f9ad0 100644 --- a/waku-bindings/src/node/store.rs +++ b/waku-bindings/src/node/store.rs @@ -4,9 +4,10 @@ use std::ffi::CString; use std::time::Duration; // crates +use libc::*; // internal use crate::general::{PeerId, Result, StoreQuery, StoreResponse}; -use crate::utils::decode_and_free_response; +use crate::utils::{get_trampoline, handle_json_response}; /// Retrieves historical messages on specific content topics. This method may be called with [`PagingOptions`](`crate::general::PagingOptions`), /// to retrieve historical messages on a per-page basis. If the request included [`PagingOptions`](`crate::general::PagingOptions`), @@ -26,8 +27,12 @@ pub fn waku_store_query( .expect("CString should build properly from peer id") .into_raw(); - let result_ptr = unsafe { - let res = waku_sys::waku_store_query( + let mut result: String = Default::default(); + let result_cb = |v: &str| result = v.to_string(); + let code = unsafe { + let mut closure = result_cb; + let cb = get_trampoline(&closure); + let out = waku_sys::waku_store_query( query_ptr, peer_id_ptr, timeout @@ -38,13 +43,17 @@ pub fn waku_store_query( .expect("Duration as milliseconds should fit in a i32") }) .unwrap_or(0), + cb, + &mut closure as *mut _ as *mut c_void, ); + drop(CString::from_raw(query_ptr)); drop(CString::from_raw(peer_id_ptr)); - res + + out }; - decode_and_free_response(result_ptr) + handle_json_response(code, &result) } /// Retrieves locally stored historical messages on specific content topics from the local archive system. This method may be called with [`PagingOptions`](`crate::general::PagingOptions`), @@ -57,10 +66,19 @@ pub fn waku_local_store_query(query: &StoreQuery) -> Result { ) .expect("CString should build properly from the serialized filter subscription") .into_raw(); - let result_ptr = unsafe { - let res = waku_sys::waku_store_local_query(query_ptr); + + let mut result: String = Default::default(); + let result_cb = |v: &str| result = v.to_string(); + let code = unsafe { + let mut closure = result_cb; + let cb = get_trampoline(&closure); + let out = + waku_sys::waku_store_local_query(query_ptr, cb, &mut closure as *mut _ as *mut c_void); + drop(CString::from_raw(query_ptr)); - res + + out }; - decode_and_free_response(result_ptr) + + handle_json_response(code, &result) } diff --git a/waku-bindings/src/utils.rs b/waku-bindings/src/utils.rs index e1a3a17..07be7a4 100644 --- a/waku-bindings/src/utils.rs +++ b/waku-bindings/src/utils.rs @@ -1,25 +1,66 @@ -use crate::general::{JsonResponse, Result}; +use crate::general::Result; +use core::str::FromStr; use serde::de::DeserializeOwned; -use std::ffi::{c_char, CStr}; +use std::ffi::CStr; +use waku_sys::WakuCallBack; +use waku_sys::{RET_ERR, RET_MISSING_CALLBACK, RET_OK}; +pub fn decode(input: &str) -> Result { + serde_json::from_str(input) + .map_err(|err| format!("could not deserialize waku response: {}", err)) +} -/// Safety: The caller is responsible for ensuring that the pointer is valid for the duration of the call. -/// This takes a pointer to a C string coming from the waku lib, that data is consumed and then freed using [`waku_sys::waku_utils_free`]. -pub fn decode_and_free_response(response_ptr: *mut c_char) -> Result { - let response = unsafe { CStr::from_ptr(response_ptr) } +unsafe extern "C" fn trampoline( + data: *const ::std::os::raw::c_char, + user_data: *mut ::std::os::raw::c_void, +) where + F: FnMut(&str), +{ + let user_data = &mut *(user_data as *mut F); + let response = unsafe { CStr::from_ptr(data) } .to_str() .map_err(|err| { format!( "could not retrieve response from pointer returned by waku: {}", err ) - })?; + }) + .expect("could not retrieve response"); - let response: JsonResponse = serde_json::from_str(response) - .map_err(|err| format!("could not deserialize waku JsonResponse: {}", err))?; - - unsafe { - waku_sys::waku_utils_free(response_ptr); - } - - response.into() + user_data(response); +} + +pub fn get_trampoline(_closure: &F) -> WakuCallBack +where + F: FnMut(&str), +{ + Some(trampoline::) +} + +pub fn handle_no_response(code: i32, error: &str) -> Result<()> { + match code { + RET_OK => Ok(()), + RET_ERR => Err(format!("waku error: {}", error)), + RET_MISSING_CALLBACK => Err("missing callback".to_string()), + _ => Err(format!("undefined return code {}", code)), + } +} + +pub fn handle_json_response(code: i32, result: &str) -> Result { + match code { + RET_OK => decode(result), + RET_ERR => Err(format!("waku error: {}", result)), + RET_MISSING_CALLBACK => Err("missing callback".to_string()), + _ => Err(format!("undefined return code {}", code)), + } +} + +pub fn handle_response(code: i32, result: &str) -> Result { + match code { + RET_OK => result + .parse() + .map_err(|_| format!("could not parse value: {}", result)), + RET_ERR => Err(format!("waku error: {}", result)), + RET_MISSING_CALLBACK => Err("missing callback".to_string()), + _ => Err(format!("undefined return code {}", code)), + } } diff --git a/waku-sys/build.rs b/waku-sys/build.rs index 9b34747..7693548 100644 --- a/waku-sys/build.rs +++ b/waku-sys/build.rs @@ -1,5 +1,6 @@ use std::env; use std::env::set_current_dir; +use std::fs; use std::path::{Path, PathBuf}; use std::process::Command; @@ -57,15 +58,24 @@ fn build_go_waku_lib(go_bin: &str, project_dir: &Path) { set_current_dir(project_dir).expect("Going back to project dir"); } +fn patch_gowaku_lib() { + // Replacing cgo_utils.h as it is only needed when compiling go-waku bindings + let lib_dir: PathBuf = env::var_os("OUT_DIR").unwrap().into(); + let file_path = lib_dir.join("libgowaku.h"); + let data = fs::read_to_string(&file_path).expect("Unable to read file"); + let new_data = data.replace("#include ", ""); + fs::write(&file_path, new_data).expect("Unable to write file"); +} + fn generate_bindgen_code() { let lib_dir: PathBuf = env::var_os("OUT_DIR").unwrap().into(); - // let lib_dir = project_dir.join("vendor/build/lib"); - println!("cargo:rustc-link-search={}", lib_dir.display()); println!("cargo:rustc-link-lib=static=gowaku"); println!("cargo:rerun-if-changed=libgowaku.h"); + patch_gowaku_lib(); + // Generate waku bindings with bindgen let bindings = bindgen::Builder::default() // The input header we would like to generate diff --git a/waku-sys/vendor b/waku-sys/vendor index 8671e3a..b3bd45f 160000 --- a/waku-sys/vendor +++ b/waku-sys/vendor @@ -1 +1 @@ -Subproject commit 8671e3a68c7c1cfefdf971f95217ea4f8baaeb32 +Subproject commit b3bd45f01f1211cb18fb44ced5277758ab38eee7