diff --git a/library/c_bindings/logosdelivery.h b/library/c_bindings/logosdelivery.h index 4c73428ad..bc52fe3da 100644 --- a/library/c_bindings/logosdelivery.h +++ b/library/c_bindings/logosdelivery.h @@ -62,6 +62,15 @@ typedef struct { NimFfiStr messageHash; WakuMessagePayload wakuMessage; } ReceivedMessagePayload; +typedef struct { + NimFfiStr contentTopic; + NimFfiStr payload; + bool ephemeral; +} SendRequest; +typedef struct { + NimFfiStr payload; + bool ephemeral; +} ChannelSendRequest; typedef struct { NimFfiStr configJson; } LogosdeliveryCreateNodeCtorReq; @@ -78,7 +87,7 @@ typedef struct { NimFfiStr contentTopicStr; } LogosdeliveryUnsubscribeReq; typedef struct { - NimFfiStr messageJson; + SendRequest req; } LogosdeliverySendReq; typedef struct { char _nimffi_empty; /* C forbids empty structs */ @@ -228,7 +237,7 @@ typedef struct { } LogosdeliveryChannelCreateReq; typedef struct { NimFfiStr channelIdStr; - NimFfiStr messageJson; + ChannelSendRequest req; } LogosdeliveryChannelSendReq; typedef struct { NimFfiStr channelIdStr; @@ -755,6 +764,88 @@ static inline void logosdelivery_free_ReceivedMessagePayload(ReceivedMessagePayl nimffi_free_str(&v->messageHash); logosdelivery_free_WakuMessagePayload(&v->wakuMessage); } +static inline CborError logosdelivery_enc_SendRequest( + CborEncoder* e, const SendRequest* v) { + CborEncoder m; + CborError err = cbor_encoder_create_map(e, &m, 3); + if (err) return err; + err = cbor_encode_text_stringz(&m, "contentTopic"); + if (err) return err; + err = nimffi_enc_str(&m, &v->contentTopic); + if (err) return err; + err = cbor_encode_text_stringz(&m, "payload"); + if (err) return err; + err = nimffi_enc_str(&m, &v->payload); + if (err) return err; + err = cbor_encode_text_stringz(&m, "ephemeral"); + if (err) return err; + err = nimffi_enc_bool(&m, &v->ephemeral); + if (err) return err; + return cbor_encoder_close_container(e, &m); +} +static inline CborError logosdelivery_dec_SendRequest( + CborValue* it, SendRequest* out) { + if (!cbor_value_is_map(it)) return CborErrorImproperValue; + CborValue field; + CborError err; + err = cbor_value_map_find_value(it, "contentTopic", &field); + if (err) return err; + if (!cbor_value_is_valid(&field)) return CborErrorImproperValue; + err = nimffi_dec_str(&field, &out->contentTopic); + if (err) return err; + err = cbor_value_map_find_value(it, "payload", &field); + if (err) return err; + if (!cbor_value_is_valid(&field)) return CborErrorImproperValue; + err = nimffi_dec_str(&field, &out->payload); + if (err) return err; + err = cbor_value_map_find_value(it, "ephemeral", &field); + if (err) return err; + if (!cbor_value_is_valid(&field)) return CborErrorImproperValue; + err = nimffi_dec_bool(&field, &out->ephemeral); + if (err) return err; + return cbor_value_advance(it); +} +static inline void logosdelivery_free_SendRequest(SendRequest* v) { + if (!v) return; + nimffi_free_str(&v->contentTopic); + nimffi_free_str(&v->payload); +} +static inline CborError logosdelivery_enc_ChannelSendRequest( + CborEncoder* e, const ChannelSendRequest* v) { + CborEncoder m; + CborError err = cbor_encoder_create_map(e, &m, 2); + if (err) return err; + err = cbor_encode_text_stringz(&m, "payload"); + if (err) return err; + err = nimffi_enc_str(&m, &v->payload); + if (err) return err; + err = cbor_encode_text_stringz(&m, "ephemeral"); + if (err) return err; + err = nimffi_enc_bool(&m, &v->ephemeral); + if (err) return err; + return cbor_encoder_close_container(e, &m); +} +static inline CborError logosdelivery_dec_ChannelSendRequest( + CborValue* it, ChannelSendRequest* out) { + if (!cbor_value_is_map(it)) return CborErrorImproperValue; + CborValue field; + CborError err; + err = cbor_value_map_find_value(it, "payload", &field); + if (err) return err; + if (!cbor_value_is_valid(&field)) return CborErrorImproperValue; + err = nimffi_dec_str(&field, &out->payload); + if (err) return err; + err = cbor_value_map_find_value(it, "ephemeral", &field); + if (err) return err; + if (!cbor_value_is_valid(&field)) return CborErrorImproperValue; + err = nimffi_dec_bool(&field, &out->ephemeral); + if (err) return err; + return cbor_value_advance(it); +} +static inline void logosdelivery_free_ChannelSendRequest(ChannelSendRequest* v) { + if (!v) return; + nimffi_free_str(&v->payload); +} static inline CborError logosdelivery_enc_LogosdeliveryCreateNodeCtorReq( CborEncoder* e, const LogosdeliveryCreateNodeCtorReq* v) { CborEncoder m; @@ -869,9 +960,9 @@ static inline CborError logosdelivery_enc_LogosdeliverySendReq( CborEncoder m; CborError err = cbor_encoder_create_map(e, &m, 1); if (err) return err; - err = cbor_encode_text_stringz(&m, "messageJson"); + err = cbor_encode_text_stringz(&m, "req"); if (err) return err; - err = nimffi_enc_str(&m, &v->messageJson); + err = logosdelivery_enc_SendRequest(&m, &v->req); if (err) return err; return cbor_encoder_close_container(e, &m); } @@ -880,16 +971,16 @@ static inline CborError logosdelivery_dec_LogosdeliverySendReq( if (!cbor_value_is_map(it)) return CborErrorImproperValue; CborValue field; CborError err; - err = cbor_value_map_find_value(it, "messageJson", &field); + err = cbor_value_map_find_value(it, "req", &field); if (err) return err; if (!cbor_value_is_valid(&field)) return CborErrorImproperValue; - err = nimffi_dec_str(&field, &out->messageJson); + err = logosdelivery_dec_SendRequest(&field, &out->req); if (err) return err; return cbor_value_advance(it); } static inline void logosdelivery_free_LogosdeliverySendReq(LogosdeliverySendReq* v) { if (!v) return; - nimffi_free_str(&v->messageJson); + logosdelivery_free_SendRequest(&v->req); } static inline CborError logosdelivery_enc_LogosdeliveryGetAvailableNodeInfoIdsReq( CborEncoder* e, const LogosdeliveryGetAvailableNodeInfoIdsReq* v) { @@ -2015,9 +2106,9 @@ static inline CborError logosdelivery_enc_LogosdeliveryChannelSendReq( if (err) return err; err = nimffi_enc_str(&m, &v->channelIdStr); if (err) return err; - err = cbor_encode_text_stringz(&m, "messageJson"); + err = cbor_encode_text_stringz(&m, "req"); if (err) return err; - err = nimffi_enc_str(&m, &v->messageJson); + err = logosdelivery_enc_ChannelSendRequest(&m, &v->req); if (err) return err; return cbor_encoder_close_container(e, &m); } @@ -2031,17 +2122,17 @@ static inline CborError logosdelivery_dec_LogosdeliveryChannelSendReq( if (!cbor_value_is_valid(&field)) return CborErrorImproperValue; err = nimffi_dec_str(&field, &out->channelIdStr); if (err) return err; - err = cbor_value_map_find_value(it, "messageJson", &field); + err = cbor_value_map_find_value(it, "req", &field); if (err) return err; if (!cbor_value_is_valid(&field)) return CborErrorImproperValue; - err = nimffi_dec_str(&field, &out->messageJson); + err = logosdelivery_dec_ChannelSendRequest(&field, &out->req); if (err) return err; return cbor_value_advance(it); } static inline void logosdelivery_free_LogosdeliveryChannelSendReq(LogosdeliveryChannelSendReq* v) { if (!v) return; nimffi_free_str(&v->channelIdStr); - nimffi_free_str(&v->messageJson); + logosdelivery_free_ChannelSendRequest(&v->req); } static inline CborError logosdelivery_enc_LogosdeliveryChannelCloseReq( CborEncoder* e, const LogosdeliveryChannelCloseReq* v) { @@ -3003,10 +3094,10 @@ static void logosdelivery_send_reply_trampoline(int ret, const char* msg, size_t nimffi_free_str(&out); free(box); } -static inline int logosdelivery_ctx_send(const LogosDeliveryCtx* ctx, NimFfiStr messageJson, LogosDeliverySendReplyFn on_reply, void* user_data) { +static inline int logosdelivery_ctx_send(const LogosDeliveryCtx* ctx, const SendRequest* req, LogosDeliverySendReplyFn on_reply, void* user_data) { LogosdeliverySendReq ffi_req; memset(&ffi_req, 0, sizeof(ffi_req)); - ffi_req.messageJson = messageJson; + ffi_req.req = *req; uint8_t* req_buf = NULL; size_t req_len = 0; char* err = NULL; @@ -5614,11 +5705,11 @@ static void logosdelivery_channel_send_reply_trampoline(int ret, const char* msg nimffi_free_str(&out); free(box); } -static inline int logosdelivery_ctx_channel_send(const LogosDeliveryCtx* ctx, NimFfiStr channelIdStr, NimFfiStr messageJson, LogosDeliveryChannelSendReplyFn on_reply, void* user_data) { +static inline int logosdelivery_ctx_channel_send(const LogosDeliveryCtx* ctx, NimFfiStr channelIdStr, const ChannelSendRequest* req, LogosDeliveryChannelSendReplyFn on_reply, void* user_data) { LogosdeliveryChannelSendReq ffi_req; memset(&ffi_req, 0, sizeof(ffi_req)); ffi_req.channelIdStr = channelIdStr; - ffi_req.messageJson = messageJson; + ffi_req.req = *req; uint8_t* req_buf = NULL; size_t req_len = 0; char* err = NULL; diff --git a/library/channels_api/channel_api.nim b/library/channels_api/channel_api.nim index 083a31d55..47b52c438 100644 --- a/library/channels_api/channel_api.nim +++ b/library/channels_api/channel_api.nim @@ -1,4 +1,3 @@ -import std/json import chronos, results, ffi import logos_delivery/waku/common/base64, @@ -8,6 +7,12 @@ import logos_delivery/api/types, ../declare_lib +# Typed CBOR request; `payload` is base64 for the same reason as SendRequest in +# messaging_api. Fields unexported to keep the export marker out of the bindings. +type ChannelSendRequest* {.ffi.} = object + payload: string + ephemeral: bool + proc installEncryption(mechanism: string): Result[void, string] = ## The Encrypt/Decrypt brokers dispatch to a single provider, so this installs ## it process-wide even though the mechanism is named per channel: channels @@ -43,28 +48,18 @@ proc logosdeliveryChannelCreate*( return ok(string(id)) proc logosdeliveryChannelSend*( - lib: LogosDelivery, channelIdStr: string, messageJson: string + lib: LogosDelivery, channelIdStr: string, req: ChannelSendRequest ): Future[Result[string, string]] {.ffi.} = - ## `messageJson` carries `{ "payload": , "ephemeral": }`. requireChannels(lib, "ChannelSend"): return err(errMsg) - var jsonNode: JsonNode - try: - jsonNode = parseJson(messageJson) - except Exception as e: - return err("Failed to parse channel message JSON: " & e.msg) - - if not jsonNode.hasKey("payload"): - return err("Missing payload field") - - let payload = base64.decode(Base64String(jsonNode["payload"].getStr())).valueOr: + let payload = base64.decode(Base64String(req.payload)).valueOr: return err("invalid payload format: " & error) - let ephemeral = jsonNode.getOrDefault("ephemeral").getBool(false) - let requestId = ( - await lib.reliableChannelManager.send(ChannelId(channelIdStr), payload, ephemeral) + await lib.reliableChannelManager.send( + ChannelId(channelIdStr), payload, req.ephemeral + ) ).valueOr: return err("ChannelSend failed: " & $error) diff --git a/library/logos_delivery_api/messaging_api.nim b/library/logos_delivery_api/messaging_api.nim index c1466addf..397dc9f9c 100644 --- a/library/logos_delivery_api/messaging_api.nim +++ b/library/logos_delivery_api/messaging_api.nim @@ -1,6 +1,4 @@ -import std/[json] import chronos, results, ffi -import stew/byteutils import logos_delivery/waku/common/base64, logos_delivery/waku/waku, @@ -8,6 +6,17 @@ import logos_delivery/api/types, ../declare_lib +# A typed CBOR request rather than a JSON blob the proc has to parse. Fields are +# unexported: genBindings copies field names verbatim, so an export marker would +# leak into the generated Rust as `pub payload*`; the proc reads them from this +# same module. `payload` is base64 because nim-ffi's Rust codegen emits seq[byte] +# as Vec without the serde_bytes annotation CBOR byte strings need, so native +# bytes do not round-trip yet -- a base64 string value does. +type SendRequest* {.ffi.} = object + contentTopic: string + payload: string + ephemeral: bool + proc logosdeliverySubscribe*( lib: LogosDelivery, contentTopicStr: string ): Future[Result[string, string]] {.ffi.} = @@ -39,39 +48,18 @@ proc logosdeliveryUnsubscribe*( return ok("") proc logosdeliverySend*( - lib: LogosDelivery, messageJson: string + lib: LogosDelivery, req: SendRequest ): Future[Result[string, string]] {.ffi.} = requireMessaging(lib, "Send"): return err(errMsg) - ## Parse the message JSON and send the message - var jsonNode: JsonNode - try: - jsonNode = parseJson(messageJson) - except Exception as e: - return err("Failed to parse message JSON: " & e.msg) - - # Extract content topic - if not jsonNode.hasKey("contentTopic"): - return err("Missing contentTopic field") - - # ContentTopic is just a string type alias - let contentTopic = ContentTopic(jsonNode["contentTopic"].getStr()) - - # Extract payload (expect base64 encoded string) - if not jsonNode.hasKey("payload"): - return err("Missing payload field") - - let payloadStr = jsonNode["payload"].getStr() - let payload = base64.decode(Base64String(payloadStr)).valueOr: + let payload = base64.decode(Base64String(req.payload)).valueOr: return err("invalid payload format: " & error) - # Extract ephemeral flag - let ephemeral = jsonNode.getOrDefault("ephemeral").getBool(false) - - # Create message envelope let envelope = MessageEnvelope.init( - contentTopic = contentTopic, payload = payload, ephemeral = ephemeral + contentTopic = ContentTopic(req.contentTopic), + payload = payload, + ephemeral = req.ephemeral, ) # Send the message via the messaging layer's own API. diff --git a/library/rust_bindings/src/api.rs b/library/rust_bindings/src/api.rs index 1ddd3b745..c8301667b 100644 --- a/library/rust_bindings/src/api.rs +++ b/library/rust_bindings/src/api.rs @@ -586,8 +586,8 @@ impl LogosDeliveryCtx { decode_cbor::(&raw_bytes) } - pub fn send(&self, message_json: String) -> Result { - let req = LogosdeliverySendReq { message_json }; + pub fn send(&self, req: SendRequest) -> Result { + let req = LogosdeliverySendReq { req }; let req_bytes = encode_cbor(&req)?; let raw_bytes = ffi_call_sync(self.timeout, |cb, ud| unsafe { ffi::logosdelivery_send(self.ptr, cb, ud, req_bytes.as_ptr(), req_bytes.len()) @@ -595,8 +595,8 @@ impl LogosDeliveryCtx { decode_cbor::(&raw_bytes) } - pub async fn send_async(&self, message_json: String) -> Result { - let req = LogosdeliverySendReq { message_json }; + pub async fn send_async(&self, req: SendRequest) -> Result { + let req = LogosdeliverySendReq { req }; let req_bytes = encode_cbor(&req)?; let ptr = self.ptr as usize; let raw_bytes = ffi_call_async(self.timeout, move |cb, ud| unsafe { @@ -1384,8 +1384,8 @@ impl LogosDeliveryCtx { decode_cbor::(&raw_bytes) } - pub fn channel_send(&self, channel_id_str: String, message_json: String) -> Result { - let req = LogosdeliveryChannelSendReq { channel_id_str, message_json }; + pub fn channel_send(&self, channel_id_str: String, req: ChannelSendRequest) -> Result { + let req = LogosdeliveryChannelSendReq { channel_id_str, req }; let req_bytes = encode_cbor(&req)?; let raw_bytes = ffi_call_sync(self.timeout, |cb, ud| unsafe { ffi::logosdelivery_channel_send(self.ptr, cb, ud, req_bytes.as_ptr(), req_bytes.len()) @@ -1393,8 +1393,8 @@ impl LogosDeliveryCtx { decode_cbor::(&raw_bytes) } - pub async fn channel_send_async(&self, channel_id_str: String, message_json: String) -> Result { - let req = LogosdeliveryChannelSendReq { channel_id_str, message_json }; + pub async fn channel_send_async(&self, channel_id_str: String, req: ChannelSendRequest) -> Result { + let req = LogosdeliveryChannelSendReq { channel_id_str, req }; let req_bytes = encode_cbor(&req)?; let ptr = self.ptr as usize; let raw_bytes = ffi_call_async(self.timeout, move |cb, ud| unsafe { diff --git a/library/rust_bindings/src/types.rs b/library/rust_bindings/src/types.rs index 03ab1c270..6f5e0d846 100644 --- a/library/rust_bindings/src/types.rs +++ b/library/rust_bindings/src/types.rs @@ -102,6 +102,20 @@ pub struct ReceivedMessagePayload { pub waku_message: WakuMessagePayload, } +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct SendRequest { + #[serde(rename = "contentTopic")] + pub content_topic: String, + pub payload: String, + pub ephemeral: bool, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ChannelSendRequest { + pub payload: String, + pub ephemeral: bool, +} + #[derive(Debug, Clone, Serialize, Deserialize)] pub struct LogosdeliveryCreateNodeCtorReq { #[serde(rename = "configJson")] @@ -128,8 +142,7 @@ pub struct LogosdeliveryUnsubscribeReq { #[derive(Debug, Clone, Serialize, Deserialize)] pub struct LogosdeliverySendReq { - #[serde(rename = "messageJson")] - pub message_json: String, + pub req: SendRequest, } #[derive(Debug, Clone, Serialize, Deserialize)] @@ -375,8 +388,7 @@ pub struct LogosdeliveryChannelCreateReq { pub struct LogosdeliveryChannelSendReq { #[serde(rename = "channelIdStr")] pub channel_id_str: String, - #[serde(rename = "messageJson")] - pub message_json: String, + pub req: ChannelSendRequest, } #[derive(Debug, Clone, Serialize, Deserialize)]