diff --git a/.cargo/config.toml b/.cargo/config.toml index 5bc05eb..4838dd9 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -1,4 +1,4 @@ [target.'cfg(target_os = "macos")'] # when using osx, we need to link against some golang libraries, it did just work with this missing flags # from: https://github.com/golang/go/issues/42459 -rustflags = ["-C", "link-args=-framework CoreFoundation -framework Security"] \ No newline at end of file +rustflags = ["-C", "link-args=-framework CoreFoundation -framework Security -framework CoreServices"] \ No newline at end of file diff --git a/examples/toy-chat/src/main.rs b/examples/toy-chat/src/main.rs index bffc5d9..716fc79 100644 --- a/examples/toy-chat/src/main.rs +++ b/examples/toy-chat/src/main.rs @@ -107,7 +107,7 @@ fn setup_node_handle() -> std::result::Result, Box, ) -> Result { let symk = hex::encode(symmetric_key.as_slice()); - let result = unsafe { - CStr::from_ptr(waku_sys::waku_decode_symmetric( - CString::new( - serde_json::to_string(&message) - .expect("WakuMessages should always be able to success serializing"), - ) - .expect("CString should build properly from the serialized waku message") - .into_raw(), - CString::new(symk) - .expect("CString should build properly from hex encoded symmetric key") - .into_raw(), - )) - } - .to_str() - .expect("Response should always succeed to load to a &str"); - let response: JsonResponse = - serde_json::from_str(result).map_err(|e| format!("{e}"))?; - response.into() + + let message_ptr = CString::new( + serde_json::to_string(&message) + .expect("WakuMessages should always be able to success serializing"), + ) + .expect("CString should build properly from the serialized waku message") + .into_raw(); + let symk_ptr = CString::new(symk) + .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); + drop(CString::from_raw(message_ptr)); + drop(CString::from_raw(symk_ptr)); + res + }; + + decode_and_free_response(result_ptr) } /// Decrypt a message using a symmetric key @@ -44,22 +46,23 @@ pub fn waku_decode_asymmetric( asymmetric_key: &SecretKey, ) -> Result { let sk = hex::encode(asymmetric_key.secret_bytes()); - let result = unsafe { - CStr::from_ptr(waku_sys::waku_decode_asymmetric( - CString::new( - serde_json::to_string(&message) - .expect("WakuMessages should always be able to success serializing"), - ) - .expect("CString should build properly from the serialized waku message") - .into_raw(), - CString::new(sk) - .expect("CString should build properly from hex encoded symmetric key") - .into_raw(), - )) - } - .to_str() - .expect("Response should always succeed to load to a &str"); - let response: JsonResponse = - serde_json::from_str(result).map_err(|e| format!("{e}"))?; - response.into() + + let message_ptr = CString::new( + serde_json::to_string(&message) + .expect("WakuMessages should always be able to success serializing"), + ) + .expect("CString should build properly from the serialized waku message") + .into_raw(); + let sk_ptr = CString::new(sk) + .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); + drop(CString::from_raw(message_ptr)); + drop(CString::from_raw(sk_ptr)); + res + }; + + decode_and_free_response(result_ptr) } diff --git a/waku-bindings/src/lib.rs b/waku-bindings/src/lib.rs index a43d3ab..1c525e0 100644 --- a/waku-bindings/src/lib.rs +++ b/waku-bindings/src/lib.rs @@ -5,6 +5,7 @@ mod decrypt; mod events; mod general; mod node; +mod utils; pub use node::{ waku_create_content_topic, waku_create_pubsub_topic, waku_dafault_pubsub_topic, waku_new, diff --git a/waku-bindings/src/node/discovery.rs b/waku-bindings/src/node/discovery.rs index f7b4f35..d9164ab 100644 --- a/waku-bindings/src/node/discovery.rs +++ b/waku-bindings/src/node/discovery.rs @@ -1,11 +1,11 @@ // std -use std::ffi::{CStr, CString}; +use std::ffi::CString; use std::time::Duration; // crates use multiaddr::Multiaddr; use url::{Host, Url}; // internal -use crate::general::JsonResponse; +use crate::utils::decode_and_free_response; use crate::Result; /// RetrieveNodes returns a list of multiaddress given a url to a DNS discoverable ENR tree. @@ -15,18 +15,20 @@ pub fn waku_dns_discovery( server: Option<&Host>, timeout: Option, ) -> Result> { - let result = unsafe { - CStr::from_ptr(waku_sys::waku_dns_discovery( - CString::new(url.to_string()) - .expect("CString should build properly from a valid Url") - .into_raw(), - 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 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( + url, + server, timeout .map(|timeout| { timeout @@ -35,13 +37,12 @@ pub fn waku_dns_discovery( .expect("Duration as milliseconds should fit in a i32") }) .unwrap_or(0), - )) - } - .to_str() - .expect("Response should always succeed to load to a &str"); + ); + // Recover strings and drop them + drop(CString::from_raw(url)); + drop(CString::from_raw(server)); + res + }; - let response: JsonResponse> = - serde_json::from_str(result).expect("JsonResponse should always succeed to deserialize"); - - response.into() + decode_and_free_response(result_ptr) } diff --git a/waku-bindings/src/node/filter.rs b/waku-bindings/src/node/filter.rs index c2d3bc4..bd63e33 100644 --- a/waku-bindings/src/node/filter.rs +++ b/waku-bindings/src/node/filter.rs @@ -1,13 +1,14 @@ //! Waku [filter](https://rfc.vac.dev/spec/36/#waku-filter) protocol related methods // std -use std::ffi::{CStr, CString}; +use std::ffi::CString; use std::time::Duration; // crates // internal use crate::general::Result; -use crate::general::{FilterSubscription, JsonResponse, PeerId}; +use crate::general::{FilterSubscription, PeerId}; +use crate::utils::decode_and_free_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) @@ -16,30 +17,30 @@ pub fn waku_filter_subscribe( peer_id: PeerId, timeout: Duration, ) -> Result<()> { - let result = unsafe { - CStr::from_ptr(waku_sys::waku_filter_subscribe( - CString::new( - serde_json::to_string(filter_subscription) - .expect("FilterSubscription should always be able to be serialized"), - ) - .expect("CString should build properly from the serialized filter subscription") - .into_raw(), - CString::new(peer_id) - .expect("CString should build properly from peer id") - .into_raw(), + let filter_subscription_ptr = CString::new( + serde_json::to_string(filter_subscription) + .expect("FilterSubscription should always succeed to serialize"), + ) + .expect("FilterSubscription should always be able to be serialized") + .into_raw(); + let peer_id_ptr = CString::new(peer_id) + .expect("PeerId should always be able to be serialized") + .into_raw(); + + let result_ptr = unsafe { + let result_ptr = waku_sys::waku_filter_subscribe( + filter_subscription_ptr, + peer_id_ptr, timeout .as_millis() .try_into() .expect("Duration as milliseconds should fit in a i32"), - )) - } - .to_str() - .expect("Response should always succeed to load to a &str"); - - let response: JsonResponse = - serde_json::from_str(result).expect("JsonResponse should always succeed to deserialize"); - - Result::from(response).map(|_| ()) + ); + drop(CString::from_raw(filter_subscription_ptr)); + drop(CString::from_raw(peer_id_ptr)); + result_ptr + }; + decode_and_free_response::(result_ptr).map(|_| ()) } /// Removes subscriptions in a light node matching a content filter and, optionally, a [`WakuPubSubTopic`](`crate::general::WakuPubSubTopic`) @@ -48,25 +49,23 @@ pub fn waku_filter_unsubscribe( filter_subscription: &FilterSubscription, timeout: Duration, ) -> Result<()> { - let result = unsafe { - CStr::from_ptr(waku_sys::waku_filter_unsubscribe( - CString::new( - serde_json::to_string(filter_subscription) - .expect("FilterSubscription should always be able to be serialized"), - ) - .expect("CString should build properly from the serialized filter subscription") - .into_raw(), + let filter_subscription_ptr = CString::new( + serde_json::to_string(filter_subscription) + .expect("FilterSubscription should always succeed to serialize"), + ) + .expect("CString should build properly from the serialized filter subscription") + .into_raw(); + let result_ptr = unsafe { + let res = waku_sys::waku_filter_unsubscribe( + filter_subscription_ptr, timeout .as_millis() .try_into() .expect("Duration as milliseconds should fit in a i32"), - )) - } - .to_str() - .expect("Response should always succeed to load to a &str"); + ); + drop(CString::from_raw(filter_subscription_ptr)); + res + }; - let response: JsonResponse = - serde_json::from_str(result).expect("JsonResponse should always succeed to deserialize"); - - Result::from(response).map(|_| ()) + decode_and_free_response::(result_ptr).map(|_| ()) } diff --git a/waku-bindings/src/node/lightpush.rs b/waku-bindings/src/node/lightpush.rs index 1b25e21..7dc2cfd 100644 --- a/waku-bindings/src/node/lightpush.rs +++ b/waku-bindings/src/node/lightpush.rs @@ -1,14 +1,15 @@ //! Waku [lightpush](https://rfc.vac.dev/spec/36/#waku-lightpush) protocol related methods // std -use std::ffi::{CStr, CString}; +use std::ffi::CString; use std::time::Duration; // crates use aes_gcm::{Aes256Gcm, Key}; use secp256k1::{PublicKey, SecretKey}; // internal -use crate::general::{JsonResponse, MessageId, PeerId, Result, WakuMessage, WakuPubSubTopic}; +use crate::general::{MessageId, PeerId, Result, WakuMessage, WakuPubSubTopic}; use crate::node::waku_dafault_pubsub_topic; +use crate::utils::decode_and_free_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) @@ -21,20 +22,23 @@ pub fn waku_lightpush_publish( let pubsub_topic = pubsub_topic .unwrap_or_else(waku_dafault_pubsub_topic) .to_string(); - let result = unsafe { - CStr::from_ptr(waku_sys::waku_lightpush_publish( - CString::new( - serde_json::to_string(&message) - .expect("WakuMessages should always be able to success serializing"), - ) - .expect("CString should build properly from the serialized waku message") - .into_raw(), - CString::new(pubsub_topic) - .expect("CString should build properly from pubsub topic") - .into_raw(), - CString::new(peer_id) - .expect("CString should build properly from peer id") - .into_raw(), + let message_ptr = CString::new( + serde_json::to_string(&message) + .expect("WakuMessages should always be able to success serializing"), + ) + .expect("CString should build properly from the serialized waku message") + .into_raw(); + let topic_ptr = CString::new(pubsub_topic) + .expect("CString should build properly from pubsub topic") + .into_raw(); + 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( + message_ptr, + topic_ptr, + peer_id_ptr, timeout .map(|timeout| { timeout @@ -43,15 +47,14 @@ pub fn waku_lightpush_publish( .expect("Duration as milliseconds should fit in a i32") }) .unwrap_or(0), - )) - } - .to_str() - .expect("Response should always succeed to load to a &str"); + ); + drop(CString::from_raw(message_ptr)); + drop(CString::from_raw(topic_ptr)); + drop(CString::from_raw(peer_id_ptr)); + res + }; - let response: JsonResponse = - serde_json::from_str(result).expect("JsonResponse should always succeed to deserialize"); - - response.into() + decode_and_free_response(result_ptr) } /// Optionally sign, encrypt using asymmetric encryption and publish a message using Waku Lightpush @@ -71,26 +74,32 @@ pub fn waku_lightpush_publish_encrypt_asymmetric( let pubsub_topic = pubsub_topic .unwrap_or_else(waku_dafault_pubsub_topic) .to_string(); - let result = unsafe { - CStr::from_ptr(waku_sys::waku_lightpush_publish_enc_asymmetric( - CString::new( - serde_json::to_string(&message) - .expect("WakuMessages should always be able to success serializing"), - ) - .expect("CString should build properly from the serialized waku message") - .into_raw(), - CString::new(pubsub_topic) - .expect("CString should build properly from pubsub topic") - .into_raw(), - CString::new(peer_id) - .expect("CString should build properly from peer id") - .into_raw(), - CString::new(pk) - .expect("CString should build properly from hex encoded public key") - .into_raw(), - CString::new(sk) - .expect("CString should build properly from hex encoded signing key") - .into_raw(), + + let pubsub_topic_ptr = CString::new(pubsub_topic) + .expect("CString should build properly from pubsub topic") + .into_raw(); + let peer_id_ptr = CString::new(peer_id) + .expect("CString should build properly from peer id") + .into_raw(); + let pk_ptr = CString::new(pk) + .expect("CString should build properly from hex encoded public key") + .into_raw(); + let sk_ptr = CString::new(sk) + .expect("CString should build properly from hex encoded signing key") + .into_raw(); + let message_ptr = CString::new( + serde_json::to_string(&message) + .expect("WakuMessages should always be able to success serializing"), + ) + .expect("CString should build properly from the serialized waku message") + .into_raw(); + let result_ptr = unsafe { + let res = waku_sys::waku_lightpush_publish_enc_asymmetric( + message_ptr, + pubsub_topic_ptr, + peer_id_ptr, + pk_ptr, + sk_ptr, timeout .map(|timeout| { timeout @@ -99,13 +108,16 @@ pub fn waku_lightpush_publish_encrypt_asymmetric( .expect("Duration as milliseconds should fit in a i32") }) .unwrap_or(0), - )) - .to_str() - .expect("Response should always succeed to load to a &str") + ); + drop(CString::from_raw(message_ptr)); + drop(CString::from_raw(pubsub_topic_ptr)); + drop(CString::from_raw(peer_id_ptr)); + drop(CString::from_raw(pk_ptr)); + drop(CString::from_raw(sk_ptr)); + res }; - let message_id: JsonResponse = - serde_json::from_str(result).expect("JsonResponse should always succeed to deserialize"); - message_id.into() + + decode_and_free_response(result_ptr) } /// Optionally sign, encrypt using symmetric encryption and publish a message using Waku Lightpush @@ -125,26 +137,31 @@ pub fn waku_lightpush_publish_encrypt_symmetric( let pubsub_topic = pubsub_topic .unwrap_or_else(waku_dafault_pubsub_topic) .to_string(); - let result = unsafe { - CStr::from_ptr(waku_sys::waku_lightpush_publish_enc_symmetric( - CString::new( - serde_json::to_string(&message) - .expect("WakuMessages should always be able to success serializing"), - ) - .expect("CString should build properly from the serialized waku message") - .into_raw(), - CString::new(pubsub_topic) - .expect("CString should build properly from pubsub topic") - .into_raw(), - CString::new(peer_id) - .expect("CString should build properly from peer id") - .into_raw(), - CString::new(symk) - .expect("CString should build properly from hex encoded symmetric key") - .into_raw(), - CString::new(sk) - .expect("CString should build properly from hex encoded signing key") - .into_raw(), + let message_ptr = CString::new( + serde_json::to_string(&message) + .expect("WakuMessages should always be able to success serializing"), + ) + .expect("CString should build properly from the serialized waku message") + .into_raw(); + let pubsub_topic_ptr = CString::new(pubsub_topic) + .expect("CString should build properly from pubsub topic") + .into_raw(); + let peer_id_ptr = CString::new(peer_id) + .expect("CString should build properly from peer id") + .into_raw(); + let symk_ptr = CString::new(symk) + .expect("CString should build properly from hex encoded symmetric key") + .into_raw(); + let sk_ptr = CString::new(sk) + .expect("CString should build properly from hex encoded signing key") + .into_raw(); + let result_ptr = unsafe { + let res = waku_sys::waku_lightpush_publish_enc_symmetric( + message_ptr, + pubsub_topic_ptr, + peer_id_ptr, + symk_ptr, + sk_ptr, timeout .map(|timeout| { timeout @@ -153,11 +170,14 @@ pub fn waku_lightpush_publish_encrypt_symmetric( .expect("Duration as milliseconds should fit in a i32") }) .unwrap_or(0), - )) - .to_str() - .expect("Response should always succeed to load to a &str") + ); + drop(CString::from_raw(message_ptr)); + drop(CString::from_raw(pubsub_topic_ptr)); + drop(CString::from_raw(peer_id_ptr)); + drop(CString::from_raw(symk_ptr)); + drop(CString::from_raw(sk_ptr)); + res }; - let message_id: JsonResponse = - serde_json::from_str(result).expect("JsonResponse should always succeed to deserialize"); - message_id.into() + + decode_and_free_response(result_ptr) } diff --git a/waku-bindings/src/node/management.rs b/waku-bindings/src/node/management.rs index 3bcfb36..7459d5a 100644 --- a/waku-bindings/src/node/management.rs +++ b/waku-bindings/src/node/management.rs @@ -7,75 +7,99 @@ use std::ffi::{CStr, CString}; // internal use super::config::WakuNodeConfig; use crate::general::{JsonResponse, PeerId, Result}; +use crate::utils::decode_and_free_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 { let config = config.unwrap_or_default(); - let s_config = serde_json::to_string(&config) - .expect("Serialization from properly built NodeConfig should never fail"); - let result: &str = unsafe { - CStr::from_ptr(waku_sys::waku_new( - CString::new(s_config) - .expect("CString should build properly from the serialized node config") - .into_raw(), - )) - } - .to_str() - .expect("Response should always succeed to load to a &str"); + + let config_ptr = CString::new( + serde_json::to_string(&config) + .expect("Serialization from properly built NodeConfig should never fail"), + ) + .expect("CString should build properly from the config") + .into_raw(); + + let result_ptr = unsafe { + let res = waku_sys::waku_new(config_ptr); + drop(CString::from_raw(config_ptr)); + res + }; + + let result = unsafe { CStr::from_ptr(result_ptr) } + .to_str() + .expect("Response should always succeed to load to a &str"); + let json_response: JsonResponse = serde_json::from_str(result).expect("JsonResponse should always succeed to deserialize"); + + unsafe { + waku_sys::waku_utils_free(result_ptr); + } + json_response.into() } /// 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 = unsafe { CStr::from_ptr(waku_sys::waku_start()) } + let response_ptr = unsafe { waku_sys::waku_start() }; + let response = unsafe { CStr::from_ptr(response_ptr) } .to_str() .expect("Response should always succeed to load to a &str"); let json_response: JsonResponse = serde_json::from_str(response).expect("JsonResponse should always succeed to deserialize"); + + unsafe { + waku_sys::waku_utils_free(response_ptr); + } + json_response.into() } /// 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 = unsafe { CStr::from_ptr(waku_sys::waku_stop()) } + let response_ptr = unsafe { waku_sys::waku_stop() }; + let response = unsafe { CStr::from_ptr(response_ptr) } .to_str() .expect("Response should always succeed to load to a &str"); let json_response: JsonResponse = serde_json::from_str(response).expect("JsonResponse should always succeed to deserialize"); + + unsafe { + waku_sys::waku_utils_free(response_ptr); + } + json_response.into() } /// 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 = unsafe { CStr::from_ptr(waku_sys::waku_peerid()) } + let response_ptr = unsafe { waku_sys::waku_peerid() }; + let response = unsafe { CStr::from_ptr(response_ptr) } .to_str() .expect("Response should always succeed to load to a &str"); let json_response: JsonResponse = serde_json::from_str(response).expect("JsonResponse should always succeed to deserialize"); + unsafe { + waku_sys::waku_utils_free(response_ptr); + } + json_response.into() } /// 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 = unsafe { CStr::from_ptr(waku_sys::waku_listen_addresses()) } - .to_str() - .expect("Response should always succeed to load to a &str"); - - let json_response: JsonResponse> = - serde_json::from_str(response).expect("JsonResponse should always succeed to deserialize"); - - json_response.into() + let response_ptr = unsafe { waku_sys::waku_listen_addresses() }; + decode_and_free_response(response_ptr) } #[cfg(test)] diff --git a/waku-bindings/src/node/mod.rs b/waku-bindings/src/node/mod.rs index ab9e94f..e39c3b1 100644 --- a/waku-bindings/src/node/mod.rs +++ b/waku-bindings/src/node/mod.rs @@ -126,7 +126,7 @@ impl WakuNodeHandle { /// The peer must be already known. /// It must have been added before with [`WakuNodeHandle::add_peer`] or previously dialed with [`WakuNodeHandle::connect_peer_with_address`] /// As per the [specification](https://rfc.vac.dev/spec/36/#extern-char-waku_connect_peeridchar-peerid-int-timeoutms) - pub fn connect_peer_with_id(&self, peer_id: PeerId, timeout: Option) -> Result<()> { + pub fn connect_peer_with_id(&self, peer_id: &PeerId, timeout: Option) -> Result<()> { peers::waku_connect_peer_with_id(peer_id, timeout) } diff --git a/waku-bindings/src/node/peers.rs b/waku-bindings/src/node/peers.rs index 34168e3..29c3823 100644 --- a/waku-bindings/src/node/peers.rs +++ b/waku-bindings/src/node/peers.rs @@ -8,26 +8,36 @@ use multiaddr::Multiaddr; use serde::Deserialize; // internal use crate::general::{JsonResponse, PeerId, ProtocolId, Result}; +use crate::utils::decode_and_free_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) pub fn waku_add_peers(address: &Multiaddr, protocol_id: ProtocolId) -> Result { - let response = unsafe { - CStr::from_ptr(waku_sys::waku_add_peer( - CString::new(address.to_string()) - .expect("CString should build properly from the address") - .into_raw(), - CString::new(protocol_id.to_string()) - .expect("CString should build properly from the protocol id") - .into_raw(), - )) - } - .to_str() - .expect("&str should build properly from the returning response"); + let address_ptr = CString::new(address.to_string()) + .expect("CString should build properly from the address") + .into_raw(); + let protocol_id_ptr = CString::new(protocol_id.to_string()) + .expect("CString should build properly from the protocol id") + .into_raw(); + + let response_ptr = unsafe { + let res = waku_sys::waku_add_peer(address_ptr, protocol_id_ptr); + drop(CString::from_raw(address_ptr)); + drop(CString::from_raw(protocol_id_ptr)); + res + }; + + let response = unsafe { CStr::from_ptr(response_ptr) } + .to_str() + .expect("&str should build properly from the returning response"); let result: JsonResponse = serde_json::from_str(response).expect("JsonResponse should always succeed to deserialize"); + unsafe { + waku_sys::waku_utils_free(response_ptr); + } + result.into() } @@ -40,22 +50,31 @@ pub fn waku_connect_peer_with_address( address: &Multiaddr, timeout: Option, ) -> Result<()> { - let response = unsafe { - CStr::from_ptr(waku_sys::waku_connect( - CString::new(address.to_string()) - .expect("CString should build properly from multiaddress") - .into_raw(), + let address_ptr = CString::new(address.to_string()) + .expect("CString should build properly from multiaddress") + .into_raw(); + let response_ptr = unsafe { + let res = waku_sys::waku_connect( + address_ptr, timeout .map(|duration| duration.as_millis().try_into().unwrap_or(i32::MAX)) .unwrap_or(0), - )) - } - .to_str() - .expect("&str should build properly from the returning response"); + ); + drop(CString::from_raw(address_ptr)); + res + }; + + let response = unsafe { CStr::from_ptr(response_ptr) } + .to_str() + .expect("&str should build properly from the returning response"); let result: JsonResponse = serde_json::from_str(response).expect("JsonResponse should always succeed to deserialize"); + unsafe { + waku_sys::waku_utils_free(response_ptr); + } + Result::from(result).map(|_| ()) } @@ -64,55 +83,77 @@ pub fn waku_connect_peer_with_address( /// The peer must be already known. /// It must have been added before with [`waku_add_peers`] or previously dialed with [`waku_connect_peer_with_address`] /// As per the [specification](https://rfc.vac.dev/spec/36/#extern-char-waku_connect_peeridchar-peerid-int-timeoutms) -pub fn waku_connect_peer_with_id(peer_id: PeerId, timeout: Option) -> Result<()> { - let response = unsafe { - CStr::from_ptr(waku_sys::waku_connect_peerid( - CString::new(peer_id) - .expect("CString should build properly from peer id") - .into_raw(), +pub fn waku_connect_peer_with_id(peer_id: &PeerId, timeout: Option) -> Result<()> { + let peer_id_ptr = CString::new(peer_id.as_bytes()) + .expect("CString should build properly from peer id") + .into_raw(); + let response_ptr = unsafe { + let res = waku_sys::waku_connect_peerid( + peer_id_ptr, timeout .map(|duration| duration.as_millis().try_into().unwrap_or(i32::MAX)) .unwrap_or(0), - )) - } - .to_str() - .expect("&str should build properly from the returning response"); + ); + drop(CString::from_raw(peer_id_ptr)); + res + }; + + let response = unsafe { CStr::from_ptr(response_ptr) } + .to_str() + .expect("&str should build properly from the returning response"); let result: JsonResponse = serde_json::from_str(response).expect("JsonResponse should always succeed to deserialize"); + unsafe { + waku_sys::waku_utils_free(response_ptr); + } + Result::from(result).map(|_| ()) } /// Disconnect a peer using its peer id /// As per the [specification](https://rfc.vac.dev/spec/36/#extern-char-waku_disconnect_peerchar-peerid) pub fn waku_disconnect_peer_with_id(peer_id: &PeerId) -> Result<()> { - let response = unsafe { - CStr::from_ptr(waku_sys::waku_disconnect( - CString::new(peer_id.as_bytes()) - .expect("CString should build properly from peer id") - .into_raw(), - )) - } - .to_str() - .expect("&str should build properly from the returning response"); + let peer_id_ptr = CString::new(peer_id.as_bytes()) + .expect("CString should build properly from peer id") + .into_raw(); + + let response_ptr = unsafe { + let res = waku_sys::waku_disconnect(peer_id_ptr); + drop(CString::from_raw(peer_id_ptr)); + res + }; + let response = unsafe { CStr::from_ptr(response_ptr) } + .to_str() + .expect("&str should build properly from the returning response"); let result: JsonResponse = serde_json::from_str(response).expect("JsonResponse should always succeed to deserialize"); + unsafe { + waku_sys::waku_utils_free(response_ptr); + } + Result::from(result).map(|_| ()) } /// 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 = unsafe { CStr::from_ptr(waku_sys::waku_peer_cnt()) } + let response_ptr = unsafe { waku_sys::waku_peer_cnt() }; + + let response = unsafe { CStr::from_ptr(response_ptr) } .to_str() .expect("&str should build properly from the returning response"); let result: JsonResponse = serde_json::from_str(response).expect("JsonResponse should always succeed to deserialize"); + unsafe { + waku_sys::waku_utils_free(response_ptr); + } + result.into() } @@ -164,14 +205,8 @@ 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 = unsafe { CStr::from_ptr(waku_sys::waku_peers()) } - .to_str() - .expect("&str should build properly from the returning response"); - - let result: JsonResponse = - serde_json::from_str(response).expect("JsonResponse should always succeed to deserialize"); - - result.into() + let response_ptr = unsafe { waku_sys::waku_peers() }; + decode_and_free_response(response_ptr) } #[cfg(test)] diff --git a/waku-bindings/src/node/relay.rs b/waku-bindings/src/node/relay.rs index fc18092..f025850 100644 --- a/waku-bindings/src/node/relay.rs +++ b/waku-bindings/src/node/relay.rs @@ -10,6 +10,7 @@ use secp256k1::{PublicKey, SecretKey}; use crate::general::{ Encoding, JsonResponse, MessageId, Result, WakuContentTopic, WakuMessage, WakuPubSubTopic, }; +use crate::utils::decode_and_free_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) @@ -19,54 +20,83 @@ pub fn waku_create_content_topic( content_topic_name: &str, encoding: Encoding, ) -> WakuContentTopic { - unsafe { - CStr::from_ptr(waku_sys::waku_content_topic( - CString::new(application_name) - .expect("Application name should always transform to CString") - .into_raw(), - application_version - .try_into() - .expect("Version should fit within an u32"), - CString::new(content_topic_name) - .expect("Content topic should always transform to CString") - .into_raw(), - CString::new(encoding.to_string()) - .expect("Encoding should always transform to CString") - .into_raw(), - )) - } - .to_str() - .expect("&str from result should always be extracted") - .parse() - .expect("Content topic data should be always parseable") + let application_name_ptr = CString::new(application_name) + .expect("Application name should always transform to CString") + .into_raw(); + let application_version = application_version + .try_into() + .expect("Version should fit within an u32"); + let content_topic_name_ptr = CString::new(content_topic_name) + .expect("Content topic should always transform to CString") + .into_raw(); + let encoding_ptr = CString::new(encoding.to_string()) + .expect("Encoding should always transform to CString") + .into_raw(); + + let result_ptr = unsafe { + let res = waku_sys::waku_content_topic( + application_name_ptr, + application_version, + content_topic_name_ptr, + encoding_ptr, + ); + drop(CString::from_raw(application_name_ptr)); + drop(CString::from_raw(content_topic_name_ptr)); + drop(CString::from_raw(encoding_ptr)); + res + }; + let result = unsafe { CStr::from_ptr(result_ptr) } + .to_str() + .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/) /// As per the [specification](https://rfc.vac.dev/spec/36/#extern-char-waku_pubsub_topicchar-name-char-encoding) pub fn waku_create_pubsub_topic(topic_name: &str, encoding: Encoding) -> WakuPubSubTopic { - unsafe { - CStr::from_ptr(waku_sys::waku_pubsub_topic( - CString::new(topic_name) - .expect("Topic name should always transform to CString") - .into_raw(), - CString::new(encoding.to_string()) - .expect("Encoding should always transform to CString") - .into_raw(), - )) - } - .to_str() - .expect("&str from result should always be extracted") - .parse() - .expect("Pubsub topic data should be always parseable") + let topic_name_ptr = CString::new(topic_name) + .expect("Topic name should always transform to CString") + .into_raw(); + let encoding_ptr = CString::new(encoding.to_string()) + .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); + drop(CString::from_raw(topic_name_ptr)); + drop(CString::from_raw(encoding_ptr)); + res + }; + + let result = unsafe { CString::from_raw(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 } /// Default pubsub topic used for exchanging waku messages defined in [RFC 10](https://rfc.vac.dev/spec/10/) pub fn waku_dafault_pubsub_topic() -> WakuPubSubTopic { - unsafe { CStr::from_ptr(waku_sys::waku_default_pubsub_topic()) } + let result_ptr = unsafe { waku_sys::waku_default_pubsub_topic() }; + + let result = unsafe { CString::from_raw(result_ptr) } .to_str() .expect("&str from result should always be extracted") .parse() - .expect("Default pubsub topic should always be parseable") + .expect("Default pubsub topic should always be parseable"); + + unsafe { waku_sys::waku_utils_free(result_ptr) }; + + result } /// Publish a message using Waku Relay @@ -79,17 +109,21 @@ pub fn waku_relay_publish_message( let pubsub_topic = pubsub_topic .unwrap_or_else(waku_dafault_pubsub_topic) .to_string(); - let result = unsafe { - CStr::from_ptr(waku_sys::waku_relay_publish( - CString::new( - serde_json::to_string(&message) - .expect("WakuMessages should always be able to success serializing"), - ) - .expect("CString should build properly from the serialized waku message") - .into_raw(), - CString::new(pubsub_topic) - .expect("CString should build properly from pubsub topic") - .into_raw(), + + let message_ptr = CString::new( + serde_json::to_string(&message) + .expect("WakuMessages should always be able to success serializing"), + ) + .expect("CString should build properly from the serialized waku message") + .into_raw(); + let pubsub_topic_ptr = CString::new(pubsub_topic) + .expect("CString should build properly from pubsub topic") + .into_raw(); + + let result_ptr = unsafe { + let res = waku_sys::waku_relay_publish( + message_ptr, + pubsub_topic_ptr, timeout .map(|duration| { duration @@ -98,13 +132,13 @@ pub fn waku_relay_publish_message( .expect("Duration as milliseconds should fit in a i32") }) .unwrap_or(0), - )) - } - .to_str() - .expect("&str from result should always be extracted"); - let message_id: JsonResponse = - serde_json::from_str(result).expect("JsonResponse should always succeed to deserialize"); - message_id.into() + ); + drop(CString::from_raw(message_ptr)); + drop(CString::from_raw(pubsub_topic_ptr)); + res + }; + + decode_and_free_response(result_ptr) } /// Optionally sign, encrypt using asymmetric encryption and publish a message using Waku Relay @@ -123,23 +157,29 @@ pub fn waku_relay_publish_encrypt_asymmetric( let pubsub_topic = pubsub_topic .unwrap_or_else(waku_dafault_pubsub_topic) .to_string(); - let result = unsafe { - CStr::from_ptr(waku_sys::waku_relay_publish_enc_asymmetric( - CString::new( - serde_json::to_string(&message) - .expect("WakuMessages should always be able to success serializing"), - ) - .expect("CString should build properly from the serialized waku message") - .into_raw(), - CString::new(pubsub_topic) - .expect("CString should build properly from pubsub topic") - .into_raw(), - CString::new(pk) - .expect("CString should build properly from hex encoded public key") - .into_raw(), - CString::new(sk) - .expect("CString should build properly from hex encoded signing key") - .into_raw(), + + let message_ptr = CString::new( + serde_json::to_string(&message) + .expect("WakuMessages should always be able to success serializing"), + ) + .expect("CString should build properly from the serialized waku message") + .into_raw(); + let pubsub_topic_ptr = CString::new(pubsub_topic) + .expect("CString should build properly from pubsub topic") + .into_raw(); + let pk_ptr = CString::new(pk) + .expect("CString should build properly from hex encoded public key") + .into_raw(); + let sk_ptr = CString::new(sk) + .expect("CString should build properly from hex encoded signing key") + .into_raw(); + + let result_ptr = unsafe { + let res = waku_sys::waku_relay_publish_enc_asymmetric( + message_ptr, + pubsub_topic_ptr, + pk_ptr, + sk_ptr, timeout .map(|timeout| { timeout @@ -148,13 +188,15 @@ pub fn waku_relay_publish_encrypt_asymmetric( .expect("Duration as milliseconds should fit in a i32") }) .unwrap_or(0), - )) - .to_str() - .expect("Response should always succeed to load to a &str") + ); + drop(CString::from_raw(message_ptr)); + drop(CString::from_raw(pubsub_topic_ptr)); + drop(CString::from_raw(pk_ptr)); + drop(CString::from_raw(sk_ptr)); + res }; - let message_id: JsonResponse = - serde_json::from_str(result).expect("JsonResponse should always succeed to deserialize"); - message_id.into() + + decode_and_free_response(result_ptr) } /// Optionally sign, encrypt using symmetric encryption and publish a message using Waku Relay @@ -173,23 +215,29 @@ pub fn waku_relay_publish_encrypt_symmetric( let pubsub_topic = pubsub_topic .unwrap_or_else(waku_dafault_pubsub_topic) .to_string(); - let result = unsafe { - CStr::from_ptr(waku_sys::waku_relay_publish_enc_symmetric( - CString::new( - serde_json::to_string(&message) - .expect("WakuMessages should always be able to success serializing"), - ) - .expect("CString should build properly from the serialized waku message") - .into_raw(), - CString::new(pubsub_topic) - .expect("CString should build properly from pubsub topic") - .into_raw(), - CString::new(symk) - .expect("CString should build properly from hex encoded symmetric key") - .into_raw(), - CString::new(sk) - .expect("CString should build properly from hex encoded signing key") - .into_raw(), + + let message_ptr = CString::new( + serde_json::to_string(&message) + .expect("WakuMessages should always be able to success serializing"), + ) + .expect("CString should build properly from the serialized waku message") + .into_raw(); + let pubsub_topic_ptr = CString::new(pubsub_topic) + .expect("CString should build properly from pubsub topic") + .into_raw(); + let symk_ptr = CString::new(symk) + .expect("CString should build properly from hex encoded symmetric key") + .into_raw(); + let sk_ptr = CString::new(sk) + .expect("CString should build properly from hex encoded signing key") + .into_raw(); + + let result_ptr = unsafe { + let res = waku_sys::waku_relay_publish_enc_symmetric( + message_ptr, + pubsub_topic_ptr, + symk_ptr, + sk_ptr, timeout .map(|timeout| { timeout @@ -198,30 +246,41 @@ pub fn waku_relay_publish_encrypt_symmetric( .expect("Duration as milliseconds should fit in a i32") }) .unwrap_or(0), - )) - .to_str() - .expect("Response should always succeed to load to a &str") + ); + drop(CString::from_raw(message_ptr)); + drop(CString::from_raw(pubsub_topic_ptr)); + drop(CString::from_raw(symk_ptr)); + drop(CString::from_raw(sk_ptr)); + res }; - let message_id: JsonResponse = - serde_json::from_str(result).expect("JsonResponse should always succeed to deserialize"); - message_id.into() + + decode_and_free_response(result_ptr) } pub fn waku_enough_peers(pubsub_topic: Option) -> Result { let pubsub_topic = pubsub_topic .unwrap_or_else(waku_dafault_pubsub_topic) .to_string(); - let result = unsafe { - CStr::from_ptr(waku_sys::waku_relay_enough_peers( - CString::new(pubsub_topic) - .expect("CString should build properly from pubsub topic") - .into_raw(), - )) - } - .to_str() - .expect("Response should always succeed to load to a &str"); + + let pubsub_topic_ptr = CString::new(pubsub_topic) + .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); + drop(CString::from_raw(pubsub_topic_ptr)); + res + }; + + let result = unsafe { CStr::from_ptr(result_ptr) } + .to_str() + .expect("&str from result should always be extracted"); + let enough_peers: JsonResponse = serde_json::from_str(result).expect("JsonResponse should always succeed to deserialize"); + + unsafe { waku_sys::waku_utils_free(result_ptr) }; + enough_peers.into() } @@ -229,34 +288,34 @@ pub fn waku_relay_subscribe(pubsub_topic: Option) -> Result<()> let pubsub_topic = pubsub_topic .unwrap_or_else(waku_dafault_pubsub_topic) .to_string(); - let result = unsafe { - CStr::from_ptr(waku_sys::waku_relay_subscribe( - CString::new(pubsub_topic) - .expect("CString should build properly from pubsub topic") - .into_raw(), - )) - } - .to_str() - .expect("Response should always succeed to load to a &str"); - let enough_peers: JsonResponse = - serde_json::from_str(result).expect("JsonResponse should always succeed to deserialize"); - Result::from(enough_peers).map(|_| ()) + + let pubsub_topic_ptr = CString::new(pubsub_topic) + .expect("CString should build properly from pubsub topic") + .into_raw(); + + let result_ptr = unsafe { + let res = waku_sys::waku_relay_subscribe(pubsub_topic_ptr); + drop(CString::from_raw(pubsub_topic_ptr)); + res + }; + + decode_and_free_response::(result_ptr).map(|_| ()) } pub fn waku_relay_unsubscribe(pubsub_topic: Option) -> Result<()> { let pubsub_topic = pubsub_topic .unwrap_or_else(waku_dafault_pubsub_topic) .to_string(); - let result = unsafe { - CStr::from_ptr(waku_sys::waku_relay_unsubscribe( - CString::new(pubsub_topic) - .expect("CString should build properly from pubsub topic") - .into_raw(), - )) - } - .to_str() - .expect("Response should always succeed to load to a &str"); - let enough_peers: JsonResponse = - serde_json::from_str(result).expect("JsonResponse should always succeed to deserialize"); - Result::from(enough_peers).map(|_| ()) + + let pubsub_topic_ptr = CString::new(pubsub_topic) + .expect("CString should build properly from pubsub topic") + .into_raw(); + + let result_ptr = unsafe { + let res = waku_sys::waku_relay_unsubscribe(pubsub_topic_ptr); + drop(CString::from_raw(pubsub_topic_ptr)); + res + }; + + decode_and_free_response::(result_ptr).map(|_| ()) } diff --git a/waku-bindings/src/node/store.rs b/waku-bindings/src/node/store.rs index 818b10e..824ff04 100644 --- a/waku-bindings/src/node/store.rs +++ b/waku-bindings/src/node/store.rs @@ -1,11 +1,12 @@ //! Waku [store](https://rfc.vac.dev/spec/36/#waku-store) handling methods // std -use std::ffi::{CStr, CString}; +use std::ffi::CString; use std::time::Duration; // crates // internal -use crate::general::{JsonResponse, PeerId, Result, StoreQuery, StoreResponse}; +use crate::general::{PeerId, Result, StoreQuery, StoreResponse}; +use crate::utils::decode_and_free_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`), @@ -16,17 +17,19 @@ pub fn waku_store_query( peer_id: &PeerId, timeout: Option, ) -> Result { - let result = unsafe { - CStr::from_ptr(waku_sys::waku_store_query( - CString::new( - serde_json::to_string(query) - .expect("StoreQuery should always be able to be serialized"), - ) - .expect("CString should build properly from the serialized filter subscription") - .into_raw(), - CString::new(peer_id.clone()) - .expect("CString should build properly from peer id") - .into_raw(), + let query_ptr = CString::new( + serde_json::to_string(query).expect("StoreQuery should always be able to be serialized"), + ) + .expect("CString should build properly from the serialized filter subscription") + .into_raw(); + let peer_id_ptr = CString::new(peer_id.clone()) + .expect("CString should build properly from peer id") + .into_raw(); + + let result_ptr = unsafe { + let res = waku_sys::waku_store_query( + query_ptr, + peer_id_ptr, timeout .map(|timeout| { timeout @@ -35,12 +38,11 @@ pub fn waku_store_query( .expect("Duration as milliseconds should fit in a i32") }) .unwrap_or(0), - )) - } - .to_str() - .expect("Response should always succeed to load to a &str"); + ); + drop(CString::from_raw(query_ptr)); + drop(CString::from_raw(peer_id_ptr)); + res + }; - let response: JsonResponse = - serde_json::from_str(result).expect("JsonResponse should always succeed to deserialize"); - response.into() + decode_and_free_response(result_ptr) } diff --git a/waku-bindings/src/utils.rs b/waku-bindings/src/utils.rs new file mode 100644 index 0000000..4744c46 --- /dev/null +++ b/waku-bindings/src/utils.rs @@ -0,0 +1,20 @@ +use crate::general::{JsonResponse, Result}; +use serde::de::DeserializeOwned; +use std::ffi::{c_char, CStr}; + +/// 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) } + .to_str() + .expect("Response should always succeed to load to a &str"); + + let response: JsonResponse = + serde_json::from_str(response).expect("JsonResponse should always succeed to deserialize"); + + unsafe { + waku_sys::waku_utils_free(response_ptr); + } + + response.into() +} diff --git a/waku-bindings/tests/node.rs b/waku-bindings/tests/node.rs index 7cc726d..531255c 100644 --- a/waku-bindings/tests/node.rs +++ b/waku-bindings/tests/node.rs @@ -162,7 +162,7 @@ async fn discv5_echo() -> Result<(), String> { for node_address in NODES { let address: Multiaddr = node_address.parse().unwrap(); let peer_id = node.add_peer(&address, ProtocolId::Relay)?; - node.connect_peer_with_id(peer_id, None)?; + node.connect_peer_with_id(&peer_id, None)?; } assert!(node.peers()?.len() >= NODES.len()); @@ -213,7 +213,7 @@ async fn default_echo() -> Result<(), String> { for node_address in NODES { let address: Multiaddr = node_address.parse().unwrap(); let peer_id = node.add_peer(&address, ProtocolId::Relay)?; - node.connect_peer_with_id(peer_id, None)?; + node.connect_peer_with_id(&peer_id, None)?; } assert!(node.peers()?.len() >= NODES.len());