From 9a43eab2e81aaaa0a5ad53b3dc5d9388b9d43452 Mon Sep 17 00:00:00 2001 From: Brian Pearce Date: Thu, 28 Sep 2023 13:06:06 +0200 Subject: [PATCH] feat(chatffi): better message metadata parsing (#5820) Description --- - Change metadata adding to byte vectors from strings - Adds Message Metadata byte vector reading - Changes ptrs to uint for message paging params - doc updates Motivation and Context --- Be able to parse metadata mo'betta How Has This Been Tested? --- CI Breaking Changes --- - [x] None - [ ] Requires data directory on base node to be deleted - [ ] Requires hard fork - [ ] Other - Please specify --- base_layer/chat_ffi/chat.h | 109 ++++++++++++- base_layer/chat_ffi/src/contacts.rs | 6 +- base_layer/chat_ffi/src/error.rs | 12 ++ base_layer/chat_ffi/src/message.rs | 31 +++- base_layer/chat_ffi/src/message_metadata.rs | 115 ++++++++++--- base_layer/chat_ffi/src/types/byte_vector.rs | 153 ++++++++++++++++++ .../chat_ffi/src/types/chat_ffi_message.rs | 26 ++- base_layer/chat_ffi/src/types/mod.rs | 10 +- base_layer/chat_ffi/src/types/wrappers.rs | 3 + integration_tests/src/chat_ffi.rs | 31 ++-- 10 files changed, 448 insertions(+), 48 deletions(-) create mode 100644 base_layer/chat_ffi/src/types/byte_vector.rs diff --git a/base_layer/chat_ffi/chat.h b/base_layer/chat_ffi/chat.h index a35f9ee83e..08445cc2a3 100644 --- a/base_layer/chat_ffi/chat.h +++ b/base_layer/chat_ffi/chat.h @@ -14,6 +14,8 @@ struct ChatByteVector; struct ChatClientFFI; +struct ChatMessageMetadataVector; + struct ChatMessages; struct Message; @@ -35,6 +37,8 @@ struct ChatFFIMessage { const char *from_address; uint64_t stored_at; const char *message_id; + struct ChatMessageMetadataVector metadata; + int metadata_len; }; typedef void (*CallbackMessageReceived)(struct ChatFFIMessage*); @@ -139,7 +143,11 @@ void add_chat_contact(struct ChatClientFFI *client, struct TariAddress *address, * `error_out` - Pointer to an int which will be modified * * ## Returns - * `()` - Does not return a value, equivalent to void in C + * `status` - Returns an int representing of the online status + * Online = 1, + * Offline = 2, + * NeverSeen = 3, + * Banned = 4, * * # Safety * The ```address``` should be destroyed after use @@ -165,10 +173,10 @@ struct Message *create_chat_message(struct TariAddress *receiver, int *error_out); /** - * Frees memory for messages + * Frees memory for message * * ## Arguments - * `messages_ptr` - The pointer of a Vec + * `messages_ptr` - The pointer of a Message * * ## Returns * `()` - Does not return a value, equivalent to void in C @@ -176,7 +184,7 @@ struct Message *create_chat_message(struct TariAddress *receiver, * # Safety * None */ -void destroy_chat_messages(struct ChatMessages *messages_ptr); +void destroy_chat_message(struct Message *messages_ptr); /** * Sends a message over a client @@ -213,10 +221,24 @@ void send_chat_message(struct ChatClientFFI *client, struct Message *message, in */ struct ChatMessages *get_chat_messages(struct ChatClientFFI *client, struct TariAddress *address, - int *limit, - int *page, + int limit, + int page, int *error_out); +/** + * Frees memory for messages + * + * ## Arguments + * `ptr` - The pointer of a Message + * + * ## Returns + * `()` - Does not return a value, equivalent to void in C + * + * # Safety + * None + */ +void destroy_chat_messages(struct ChatMessages *ptr); + /** * Creates message metadata and appends it to a Message * @@ -236,7 +258,7 @@ struct ChatMessages *get_chat_messages(struct ChatClientFFI *client, */ void add_chat_message_metadata(struct Message *message, int metadata_type, - const char *data, + struct ChatByteVector *data, int *error_out); /** @@ -338,6 +360,79 @@ void destroy_chat_ffi_liveness_data(struct ChatFFIContactsLivenessData *address) */ void destroy_chat_ffi_message(struct ChatFFIMessage *address); +/** + * Creates a ChatByteVector + * + * ## Arguments + * `byte_array` - The pointer to the byte array + * `element_count` - The number of elements in byte_array + * `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions + * as an out parameter. + * + * ## Returns + * `*mut ChatByteVector` - Pointer to the created ChatByteVector. Note that it will be ptr::null_mut() + * if the byte_array pointer was null or if the elements in the byte_vector don't match + * element_count when it is created + * + * # Safety + * The ```byte_vector_destroy``` function must be called when finished with a ChatByteVector to prevent a memory leak + */ +struct ChatByteVector *chat_byte_vector_create(const unsigned char *byte_array, + unsigned int element_count, + int *error_out); + +/** + * Frees memory for a ChatByteVector + * + * ## Arguments + * `bytes` - The pointer to a ChatByteVector + * + * ## Returns + * `()` - Does not return a value, equivalent to void in C + * + * # Safety + * None + */ +void chat_byte_vector_destroy(struct ChatByteVector *bytes); + +/** + * Gets a c_uchar at position in a ChatByteVector + * + * ## Arguments + * `ptr` - The pointer to a ChatByteVector + * `position` - The integer position + * `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions + * as an out parameter. + * + * ## Returns + * `c_uchar` - Returns a character. Note that the character will be a null terminator (0) if ptr + * is null or if the position is invalid + * + * # Safety + * None + */ +unsigned char chat_byte_vector_get_at(struct ChatByteVector *ptr, + unsigned int position, + int *error_out); + +/** + * Gets the number of elements in a ChatByteVector + * + * ## Arguments + * `ptr` - The pointer to a ChatByteVector + * `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions + * as an out parameter. + * + * ## Returns + * `c_uint` - Returns the integer number of elements in the ChatByteVector. Note that it will be zero + * if ptr is null + * + * # Safety + * None + */ +unsigned int chat_byte_vector_get_length(const struct ChatByteVector *vec, + int *error_out); + #ifdef __cplusplus } // extern "C" #endif // __cplusplus diff --git a/base_layer/chat_ffi/src/contacts.rs b/base_layer/chat_ffi/src/contacts.rs index be50491f58..a17aa0116f 100644 --- a/base_layer/chat_ffi/src/contacts.rs +++ b/base_layer/chat_ffi/src/contacts.rs @@ -73,7 +73,11 @@ pub unsafe extern "C" fn add_chat_contact( /// `error_out` - Pointer to an int which will be modified /// /// ## Returns -/// `()` - Does not return a value, equivalent to void in C +/// `status` - Returns an int representing of the online status +/// Online = 1, +/// Offline = 2, +/// NeverSeen = 3, +/// Banned = 4, /// /// # Safety /// The ```address``` should be destroyed after use diff --git a/base_layer/chat_ffi/src/error.rs b/base_layer/chat_ffi/src/error.rs index f100f92b0a..8e6d642404 100644 --- a/base_layer/chat_ffi/src/error.rs +++ b/base_layer/chat_ffi/src/error.rs @@ -32,6 +32,10 @@ pub enum InterfaceError { TokioError(String), #[error("Something about the argument is invalid: `{0}`")] InvalidArgument(String), + #[error("An error has occurred when checking the length of the allocated object")] + AllocationError, + #[error("An error because the supplied position was out of range")] + PositionInvalidError, } /// This struct is meant to hold an error for use by FFI client applications. The error has an integer code and string @@ -54,6 +58,14 @@ impl From for LibChatError { code: 4, message: format!("{:?}", v), }, + InterfaceError::AllocationError => Self { + code: 5, + message: format!("{:?}", v), + }, + InterfaceError::PositionInvalidError => Self { + code: 6, + message: format!("{:?}", v), + }, InterfaceError::InvalidArgument(_) => Self { code: 7, message: format!("{:?}", v), diff --git a/base_layer/chat_ffi/src/message.rs b/base_layer/chat_ffi/src/message.rs index 752fc82536..c9ba48046b 100644 --- a/base_layer/chat_ffi/src/message.rs +++ b/base_layer/chat_ffi/src/message.rs @@ -79,10 +79,10 @@ pub unsafe extern "C" fn create_chat_message( Box::into_raw(Box::new(message_out)) } -/// Frees memory for messages +/// Frees memory for message /// /// ## Arguments -/// `messages_ptr` - The pointer of a Vec +/// `messages_ptr` - The pointer of a Message /// /// ## Returns /// `()` - Does not return a value, equivalent to void in C @@ -90,7 +90,7 @@ pub unsafe extern "C" fn create_chat_message( /// # Safety /// None #[no_mangle] -pub unsafe extern "C" fn destroy_chat_messages(messages_ptr: *mut ChatMessages) { +pub unsafe extern "C" fn destroy_chat_message(messages_ptr: *mut Message) { if !messages_ptr.is_null() { drop(Box::from_raw(messages_ptr)) } @@ -147,8 +147,8 @@ pub unsafe extern "C" fn send_chat_message(client: *mut ChatClientFFI, message: pub unsafe extern "C" fn get_chat_messages( client: *mut ChatClientFFI, address: *mut TariAddress, - limit: *mut c_int, - page: *mut c_int, + limit: c_int, + page: c_int, error_out: *mut c_int, ) -> *mut ChatMessages { let mut error = 0; @@ -164,8 +164,8 @@ pub unsafe extern "C" fn get_chat_messages( ptr::swap(error_out, &mut error as *mut c_int); } - let mlimit = u64::try_from(*limit).unwrap_or(DEFAULT_MESSAGE_LIMIT); - let mpage = u64::try_from(*page).unwrap_or(DEFAULT_MESSAGE_PAGE); + let mlimit = u64::try_from(limit).unwrap_or(DEFAULT_MESSAGE_LIMIT); + let mpage = u64::try_from(page).unwrap_or(DEFAULT_MESSAGE_PAGE); let mut messages = Vec::new(); @@ -176,3 +176,20 @@ pub unsafe extern "C" fn get_chat_messages( Box::into_raw(Box::new(ChatMessages(messages))) } + +/// Frees memory for messages +/// +/// ## Arguments +/// `ptr` - The pointer of a Message +/// +/// ## Returns +/// `()` - Does not return a value, equivalent to void in C +/// +/// # Safety +/// None +#[no_mangle] +pub unsafe extern "C" fn destroy_chat_messages(ptr: *mut ChatMessages) { + if !ptr.is_null() { + drop(Box::from_raw(ptr)) + } +} diff --git a/base_layer/chat_ffi/src/message_metadata.rs b/base_layer/chat_ffi/src/message_metadata.rs index 3b1492671f..2f77fdba32 100644 --- a/base_layer/chat_ffi/src/message_metadata.rs +++ b/base_layer/chat_ffi/src/message_metadata.rs @@ -20,12 +20,22 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -use std::{convert::TryFrom, ffi::CStr, ptr}; +use std::{convert::TryFrom, ptr}; -use libc::{c_char, c_int}; +use libc::{c_int, c_uint}; use tari_contacts::contacts_service::types::{Message, MessageMetadata, MessageMetadataType}; -use crate::error::{InterfaceError, LibChatError}; +use crate::{ + error::{InterfaceError, LibChatError}, + types::{chat_byte_vector_get_at, chat_byte_vector_get_length, ChatByteVector, ChatFFIMessage}, +}; + +#[derive(Debug, PartialEq, Clone)] +#[repr(C)] +pub struct ChatFFIMessageMetadata { + pub data: ChatByteVector, + pub metadata_type: c_int, +} /// Creates message metadata and appends it to a Message /// @@ -46,7 +56,7 @@ use crate::error::{InterfaceError, LibChatError}; pub unsafe extern "C" fn add_chat_message_metadata( message: *mut Message, metadata_type: c_int, - data: *const c_char, + data: *mut ChatByteVector, error_out: *mut c_int, ) { let mut error = 0; @@ -85,39 +95,104 @@ pub unsafe extern "C" fn add_chat_message_metadata( return; } - let data: Vec = match CStr::from_ptr(data).to_str() { - Ok(str) => str.as_bytes().into(), - Err(e) => { - error = LibChatError::from(InterfaceError::InvalidArgument(e.to_string())).code; - ptr::swap(error_out, &mut error as *mut c_int); - return; - }, - }; + let chat_byte_vector_length = chat_byte_vector_get_length(data, error_out); + let mut bytes: Vec = Vec::new(); + for c in 0..chat_byte_vector_length { + let byte = chat_byte_vector_get_at(data, c as c_uint, error_out); + assert_eq!(error, 0); + bytes.push(byte); + } - let metadata = MessageMetadata { metadata_type, data }; + let metadata = MessageMetadata { + metadata_type, + data: bytes, + }; (*message).push(metadata); } +#[allow(dead_code)] // Not dead code? False positive +pub unsafe extern "C" fn read_chat_metadata_at_position( + message: *mut ChatFFIMessage, + position: c_uint, + error_out: *mut c_int, +) -> *mut ChatFFIMessageMetadata { + let mut error = 0; + ptr::swap(error_out, &mut error as *mut c_int); + + if message.is_null() { + error = LibChatError::from(InterfaceError::NullError("message".to_string())).code; + ptr::swap(error_out, &mut error as *mut c_int); + return ptr::null_mut(); + } + + let message = &(*message); + + let len = message.metadata_len - 1; + if len < 0 || position > len as c_uint { + error = LibChatError::from(InterfaceError::PositionInvalidError).code; + ptr::swap(error_out, &mut error as *mut c_int); + return ptr::null_mut(); + } + Box::into_raw(Box::new(message.metadata.0[len as usize].clone())) +} + #[cfg(test)] mod test { - use std::ffi::CString; + use std::convert::TryFrom; - use libc::{c_char, c_int}; + use libc::{c_int, c_uint}; + use tari_common_types::tari_address::TariAddress; + use tari_contacts::contacts_service::types::MessageBuilder; use super::add_chat_message_metadata; + use crate::{ + message_metadata::read_chat_metadata_at_position, + types::{chat_byte_vector_create, ChatFFIMessage}, + }; #[test] fn test_metadata_adding() { let message_ptr = Box::into_raw(Box::default()); + let error_out = Box::into_raw(Box::new(0)); + + let data = "hello".to_string(); + let data_bytes = data.as_bytes(); + let len = u32::try_from(data.len()).expect("Can't cast from usize"); + let data = unsafe { chat_byte_vector_create(data_bytes.as_ptr(), len as c_uint, error_out) }; - let data_c_str = CString::new("hello".to_string()).unwrap(); - let data_char: *const c_char = CString::into_raw(data_c_str) as *const c_char; + unsafe { add_chat_message_metadata(message_ptr, 0 as c_int, data, error_out) } + let message = unsafe { Box::from_raw(message_ptr) }; + assert_eq!(message.metadata.len(), 1); + assert_eq!(message.metadata[0].data, data_bytes); + } + + #[test] + fn test_reading_metadata() { + let address = TariAddress::default(); + let message_ptr = Box::into_raw(Box::new( + MessageBuilder::new() + .message("hello".to_string()) + .address(address) + .build(), + )); let error_out = Box::into_raw(Box::new(0)); - unsafe { add_chat_message_metadata(message_ptr, 0 as c_int, data_char, error_out) } + unsafe { + let data = "hello".to_string(); + let data_bytes = data.as_bytes(); + let len = u32::try_from(data.len()).expect("Can't cast from usize"); + let data = chat_byte_vector_create(data_bytes.as_ptr(), len as c_uint, error_out); - let message = unsafe { Box::from_raw(message_ptr) }; - assert_eq!(message.metadata.len(), 1) + add_chat_message_metadata(message_ptr, 0 as c_int, data, error_out); + + let chat_ffi_msg = + ChatFFIMessage::try_from((*message_ptr).clone()).expect("A ChatFFI Message from a Message"); + let chat_ffi_msg_ptr = Box::into_raw(Box::new(chat_ffi_msg)); + + let metadata = &(*read_chat_metadata_at_position(chat_ffi_msg_ptr, 0, error_out)); + + assert_eq!(metadata.data.0, data_bytes); + } } } diff --git a/base_layer/chat_ffi/src/types/byte_vector.rs b/base_layer/chat_ffi/src/types/byte_vector.rs new file mode 100644 index 0000000000..655b605a88 --- /dev/null +++ b/base_layer/chat_ffi/src/types/byte_vector.rs @@ -0,0 +1,153 @@ +// Copyright 2023, The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use std::{ptr, slice}; + +use libc::{c_int, c_uchar, c_uint}; + +use crate::{ + error::{InterfaceError, LibChatError}, + types::ChatByteVector, +}; + +/// Creates a ChatByteVector +/// +/// ## Arguments +/// `byte_array` - The pointer to the byte array +/// `element_count` - The number of elements in byte_array +/// `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions +/// as an out parameter. +/// +/// ## Returns +/// `*mut ChatByteVector` - Pointer to the created ChatByteVector. Note that it will be ptr::null_mut() +/// if the byte_array pointer was null or if the elements in the byte_vector don't match +/// element_count when it is created +/// +/// # Safety +/// The ```byte_vector_destroy``` function must be called when finished with a ChatByteVector to prevent a memory leak +#[no_mangle] +pub unsafe extern "C" fn chat_byte_vector_create( + byte_array: *const c_uchar, + element_count: c_uint, + error_out: *mut c_int, +) -> *mut ChatByteVector { + let mut error = 0; + ptr::swap(error_out, &mut error as *mut c_int); + let mut bytes = ChatByteVector(Vec::new()); + if byte_array.is_null() { + error = LibChatError::from(InterfaceError::NullError("byte_array".to_string())).code; + ptr::swap(error_out, &mut error as *mut c_int); + return ptr::null_mut(); + } else { + let array: &[c_uchar] = slice::from_raw_parts(byte_array, element_count as usize); + bytes.0 = array.to_vec(); + if bytes.0.len() != element_count as usize { + error = LibChatError::from(InterfaceError::AllocationError).code; + ptr::swap(error_out, &mut error as *mut c_int); + return ptr::null_mut(); + } + } + Box::into_raw(Box::new(bytes)) +} + +/// Frees memory for a ChatByteVector +/// +/// ## Arguments +/// `bytes` - The pointer to a ChatByteVector +/// +/// ## Returns +/// `()` - Does not return a value, equivalent to void in C +/// +/// # Safety +/// None +#[no_mangle] +pub unsafe extern "C" fn chat_byte_vector_destroy(bytes: *mut ChatByteVector) { + if !bytes.is_null() { + drop(Box::from_raw(bytes)) + } +} + +/// Gets a c_uchar at position in a ChatByteVector +/// +/// ## Arguments +/// `ptr` - The pointer to a ChatByteVector +/// `position` - The integer position +/// `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions +/// as an out parameter. +/// +/// ## Returns +/// `c_uchar` - Returns a character. Note that the character will be a null terminator (0) if ptr +/// is null or if the position is invalid +/// +/// # Safety +/// None +// converting between here is fine as its used to clamp the the array to length +#[allow(clippy::cast_possible_wrap)] +#[no_mangle] +pub unsafe extern "C" fn chat_byte_vector_get_at( + ptr: *mut ChatByteVector, + position: c_uint, + error_out: *mut c_int, +) -> c_uchar { + let mut error = 0; + ptr::swap(error_out, &mut error as *mut c_int); + if ptr.is_null() { + error = LibChatError::from(InterfaceError::NullError("ptr".to_string())).code; + ptr::swap(error_out, &mut error as *mut c_int); + return 0u8; + } + let len = chat_byte_vector_get_length(ptr, error_out) as c_int - 1; // clamp to length + if len < 0 || position > len as c_uint { + error = LibChatError::from(InterfaceError::PositionInvalidError).code; + ptr::swap(error_out, &mut error as *mut c_int); + return 0u8; + } + (*ptr).0[position as usize] +} + +/// Gets the number of elements in a ChatByteVector +/// +/// ## Arguments +/// `ptr` - The pointer to a ChatByteVector +/// `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions +/// as an out parameter. +/// +/// ## Returns +/// `c_uint` - Returns the integer number of elements in the ChatByteVector. Note that it will be zero +/// if ptr is null +/// +/// # Safety +/// None +// casting here is okay a byte vector wont go larger than u32 +#[allow(clippy::cast_possible_truncation)] +#[no_mangle] +pub unsafe extern "C" fn chat_byte_vector_get_length(vec: *const ChatByteVector, error_out: *mut c_int) -> c_uint { + let mut error = 0; + ptr::swap(error_out, &mut error as *mut c_int); + if vec.is_null() { + error = LibChatError::from(InterfaceError::NullError("vec".to_string())).code; + ptr::swap(error_out, &mut error as *mut c_int); + return 0; + } + + (*vec).0.len() as c_uint +} diff --git a/base_layer/chat_ffi/src/types/chat_ffi_message.rs b/base_layer/chat_ffi/src/types/chat_ffi_message.rs index 7a9dbcf5ba..d977afe185 100644 --- a/base_layer/chat_ffi/src/types/chat_ffi_message.rs +++ b/base_layer/chat_ffi/src/types/chat_ffi_message.rs @@ -22,15 +22,22 @@ use std::{convert::TryFrom, ffi::CString}; -use libc::c_char; +use libc::{c_char, c_int, c_uchar}; use tari_contacts::contacts_service::types::Message; +use crate::{ + message_metadata::ChatFFIMessageMetadata, + types::{ChatByteVector, ChatMessageMetadataVector}, +}; + #[repr(C)] pub struct ChatFFIMessage { pub body: *const c_char, pub from_address: *const c_char, pub stored_at: u64, pub message_id: *const c_char, + pub metadata: ChatMessageMetadataVector, + pub metadata_len: c_int, } impl TryFrom for ChatFFIMessage { @@ -42,7 +49,7 @@ impl TryFrom for ChatFFIMessage { Err(e) => return Err(e.to_string()), }; - let address = match CString::new(v.address.to_bytes()) { + let address = match CString::new(v.address.to_hex()) { Ok(s) => s, Err(e) => return Err(e.to_string()), }; @@ -52,11 +59,26 @@ impl TryFrom for ChatFFIMessage { Err(e) => return Err(e.to_string()), }; + let mut chat_message_metadata_bytes = vec![]; + for md in v.metadata.clone() { + chat_message_metadata_bytes.push(ChatFFIMessageMetadata { + data: ChatByteVector(md.data.clone().into_iter().map(|f| f as c_uchar).collect()), + metadata_type: i32::from(md.metadata_type.as_byte()) as c_int, + }); + } + + let metadata_length = match i32::try_from(v.metadata.len()) { + Ok(len) => len, + Err(e) => return Err(e.to_string()), + }; + Ok(Self { body: body.as_ptr(), from_address: address.as_ptr(), stored_at: v.stored_at, message_id: id.as_ptr(), + metadata: ChatMessageMetadataVector(chat_message_metadata_bytes), + metadata_len: metadata_length, }) } } diff --git a/base_layer/chat_ffi/src/types/mod.rs b/base_layer/chat_ffi/src/types/mod.rs index 948175b339..6a608f86cd 100644 --- a/base_layer/chat_ffi/src/types/mod.rs +++ b/base_layer/chat_ffi/src/types/mod.rs @@ -24,4 +24,12 @@ pub use chat_ffi_contacts_liveness_data::{destroy_chat_ffi_liveness_data, ChatFF mod chat_ffi_message; pub use chat_ffi_message::{destroy_chat_ffi_message, ChatFFIMessage}; mod wrappers; -pub use wrappers::{ChatByteVector, ChatMessages}; +pub use wrappers::{ChatByteVector, ChatMessageMetadataVector, ChatMessages}; + +mod byte_vector; +pub use byte_vector::{ + chat_byte_vector_create, + chat_byte_vector_destroy, + chat_byte_vector_get_at, + chat_byte_vector_get_length, +}; diff --git a/base_layer/chat_ffi/src/types/wrappers.rs b/base_layer/chat_ffi/src/types/wrappers.rs index 7a7789a9c8..c08a8c56d7 100644 --- a/base_layer/chat_ffi/src/types/wrappers.rs +++ b/base_layer/chat_ffi/src/types/wrappers.rs @@ -23,8 +23,11 @@ use libc::c_uchar; use tari_contacts::contacts_service::types::Message; +use crate::message_metadata::ChatFFIMessageMetadata; + #[derive(Debug, PartialEq, Clone)] pub struct ChatByteVector(pub Vec); // declared like this so that it can be exposed to external header +pub struct ChatMessageMetadataVector(pub Vec); #[derive(Clone)] pub struct ChatMessages(pub Vec); diff --git a/integration_tests/src/chat_ffi.rs b/integration_tests/src/chat_ffi.rs index 7836993dc6..c8a229d86c 100644 --- a/integration_tests/src/chat_ffi.rs +++ b/integration_tests/src/chat_ffi.rs @@ -32,7 +32,7 @@ use async_trait::async_trait; type ClientFFI = c_void; -use libc::{c_char, c_int}; +use libc::{c_char, c_int, c_uchar, c_uint}; use minotari_app_utilities::identity_management::setup_node_identity; use tari_chat_client::{database, ChatClient}; use tari_common_types::tari_address::TariAddress; @@ -80,11 +80,16 @@ extern "C" { pub fn get_chat_messages( client: *mut ClientFFI, sender: *mut c_void, - limit: *mut c_void, - page: *mut c_void, + limit: c_int, + page: c_int, error_out: *const c_int, ) -> *mut c_void; pub fn destroy_chat_client_ffi(client: *mut ClientFFI); + pub fn chat_byte_vector_create( + byte_array: *const c_uchar, + element_count: c_uint, + error_our: *const c_int, + ) -> *mut c_void; } #[derive(Debug)] @@ -139,8 +144,8 @@ impl ChatClient for ChatFFI { let messages; unsafe { let error_out = Box::into_raw(Box::new(0)); - let limit = Box::into_raw(Box::new(limit)) as *mut c_void; - let page = Box::into_raw(Box::new(page)) as *mut c_void; + let limit = i32::try_from(limit).expect("Truncation occurred") as c_int; + let page = i32::try_from(page).expect("Truncation occurred") as c_int; let all_messages = get_chat_messages(client.0, address_ptr, limit, page, error_out) as *mut Vec; messages = (*all_messages).clone(); } @@ -164,15 +169,21 @@ impl ChatClient for ChatFFI { fn add_metadata(&self, message: Message, metadata_type: MessageMetadataType, data: String) -> Message { let message_ptr = Box::into_raw(Box::new(message)) as *mut c_void; - let message_type = i32::from(metadata_type.as_byte()); - - let data_c_str = CString::new(data).unwrap(); - let data_c_char: *const c_char = CString::into_raw(data_c_str) as *const c_char; + let message_type = metadata_type.as_byte(); let error_out = Box::into_raw(Box::new(0)); + let bytes = data.into_bytes(); + let len = i32::try_from(bytes.len()).expect("Truncation occurred") as c_uint; + let byte_data = unsafe { chat_byte_vector_create(bytes.as_ptr(), len, error_out) }; + unsafe { - add_chat_message_metadata(message_ptr, message_type, data_c_char, error_out); + add_chat_message_metadata( + message_ptr, + i32::from(message_type), + byte_data as *const c_char, + error_out, + ); *Box::from_raw(message_ptr as *mut Message) } }