Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(chat): read receipt feature #5824

Merged
merged 12 commits into from
Oct 3, 2023
75 changes: 74 additions & 1 deletion base_layer/chat_ffi/chat.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ struct ChatMessageMetadataVector;

struct ChatMessages;

struct Confirmation;

struct Message;

struct TariAddress;
Expand All @@ -43,6 +45,10 @@ struct ChatFFIMessage {

typedef void (*CallbackMessageReceived)(struct ChatFFIMessage*);

typedef void (*CallbackDeliveryConfirmationReceived)(struct Confirmation*);

typedef void (*CallbackReadConfirmationReceived)(struct Confirmation*);

#ifdef __cplusplus
extern "C" {
#endif // __cplusplus
Expand All @@ -65,7 +71,9 @@ extern "C" {
struct ChatClientFFI *create_chat_client(struct ApplicationConfig *config,
int *error_out,
CallbackContactStatusChange callback_contact_status_change,
CallbackMessageReceived callback_message_received);
CallbackMessageReceived callback_message_received,
CallbackDeliveryConfirmationReceived callback_delivery_confirmation_received,
CallbackReadConfirmationReceived callback_read_confirmation_received);

/**
* Frees memory for a ChatClientFFI
Expand Down Expand Up @@ -118,6 +126,52 @@ struct ApplicationConfig *create_chat_config(const char *network_str,
*/
void destroy_chat_config(struct ApplicationConfig *config);

/**
* Get a pointer to a ChatByteVector representation of a message id
*
* ## Arguments
* `confirmation` - A pointer to the Confirmation
* `error_out` - Pointer to an int which will be modified
*
* ## Returns
* `*mut ChatByteVector` - A ptr to a ChatByteVector
*
* # Safety
* The ```confirmation``` When done with the confirmation it should be destroyed
* The ```ChatByteVector``` When done with the returned ChatByteVector it should be destroyed
*/
struct ChatByteVector *read_confirmation_message_id(struct Confirmation *confirmation,
int *error_out);

/**
* Get a c_uint timestamp for the confirmation
*
* ## Arguments
* `confirmation` - A pointer to the Confirmation
* `error_out` - Pointer to an int which will be modified
*
* ## Returns
* `c_uint` - A uint representation of time. May return 0 if casting fails
*
* # Safety
* None
*/
unsigned int read_confirmation_timestamp(struct Confirmation *confirmation, int *error_out);

/**
* Frees memory for a Confirmation
*
* ## Arguments
* `address` - The pointer of a Confirmation
*
* ## Returns
* `()` - Does not return a value, equivalent to void in C
*
* # Safety
* None
*/
void destroy_confirmation(struct Confirmation *address);

/**
* Add a contact
*
Expand Down Expand Up @@ -261,6 +315,25 @@ void add_chat_message_metadata(struct Message *message,
struct ChatByteVector *data,
int *error_out);

/**
* Sends a read confirmation for a given message
*
* ## Arguments
* `client` - The chat client
* `message` - The message that was read
* `error_out` - Pointer to an int which will be modified
*
* ## Returns
* `*mut TariAddress` - A ptr to a TariAddress
*
* # Safety
* The ```ChatClientFFI``` When done with the client it should be destroyed
* The ```Message``` When done with the Message it should be destroyed
*/
void send_read_confirmation_for_message(struct ChatClientFFI *client,
struct Message *message,
int *error_out);

/**
* Creates a tor transport config
*
Expand Down
53 changes: 49 additions & 4 deletions base_layer/chat_ffi/src/callback_handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ use std::{convert::TryFrom, ops::Deref};
use log::{debug, error, info, trace};
use tari_contacts::contacts_service::{
handle::{ContactsLivenessData, ContactsLivenessEvent, ContactsServiceHandle},
types::Message,
types::{Confirmation, Message, MessageDispatch},
};
use tari_shutdown::ShutdownSignal;

Expand All @@ -35,12 +35,16 @@ const LOG_TARGET: &str = "chat_ffi::callback_handler";

pub(crate) type CallbackContactStatusChange = unsafe extern "C" fn(*mut ChatFFIContactsLivenessData);
pub(crate) type CallbackMessageReceived = unsafe extern "C" fn(*mut ChatFFIMessage);
pub(crate) type CallbackDeliveryConfirmationReceived = unsafe extern "C" fn(*mut Confirmation);
pub(crate) type CallbackReadConfirmationReceived = unsafe extern "C" fn(*mut Confirmation);

#[derive(Clone)]
pub struct CallbackHandler {
contacts_service_handle: ContactsServiceHandle,
callback_contact_status_change: CallbackContactStatusChange,
callback_message_received: CallbackMessageReceived,
callback_delivery_confirmation_received: CallbackDeliveryConfirmationReceived,
callback_read_confirmation_received: CallbackReadConfirmationReceived,
shutdown: ShutdownSignal,
}

Expand All @@ -50,12 +54,16 @@ impl CallbackHandler {
shutdown: ShutdownSignal,
callback_contact_status_change: CallbackContactStatusChange,
callback_message_received: CallbackMessageReceived,
callback_delivery_confirmation_received: CallbackDeliveryConfirmationReceived,
callback_read_confirmation_received: CallbackReadConfirmationReceived,
) -> Self {
Self {
contacts_service_handle,
shutdown,
callback_contact_status_change,
callback_message_received,
callback_delivery_confirmation_received,
callback_read_confirmation_received,
}
}

Expand All @@ -67,9 +75,22 @@ impl CallbackHandler {
tokio::select! {
rec_message = chat_messages.recv() => {
match rec_message {
Ok(message) => {
trace!(target: LOG_TARGET, "FFI Callback monitor received a new Message");
self.trigger_message_received(message.deref().clone());
Ok(message_dispatch) => {
trace!(target: LOG_TARGET, "FFI Callback monitor received a new MessageDispatch");
match message_dispatch.deref() {
MessageDispatch::Message(m) => {
trace!(target: LOG_TARGET, "FFI Callback monitor received a new Message");
self.trigger_message_received(m.clone());
}
MessageDispatch::DeliveryConfirmation(c) => {
trace!(target: LOG_TARGET, "FFI Callback monitor received a new Delivery Confirmation");
self.trigger_delivery_confirmation_received(c.clone());
},
MessageDispatch::ReadConfirmation(c) => {
trace!(target: LOG_TARGET, "FFI Callback monitor received a new Read Confirmation");
self.trigger_read_confirmation_received(c.clone());
}
};
},
Err(_) => { debug!(target: LOG_TARGET, "FFI Callback monitor had an error receiving new messages")}
}
Expand Down Expand Up @@ -130,4 +151,28 @@ impl CallbackHandler {
Err(e) => error!(target: LOG_TARGET, "Error processing message received callback: {}", e),
}
}

fn trigger_delivery_confirmation_received(&mut self, confirmation: Confirmation) {
debug!(
target: LOG_TARGET,
"Calling DeliveryConfirmationReceived callback function for message {:?}",
confirmation.message_id,
);

unsafe {
(self.callback_delivery_confirmation_received)(Box::into_raw(Box::new(confirmation)));
}
}

fn trigger_read_confirmation_received(&mut self, confirmation: Confirmation) {
debug!(
target: LOG_TARGET,
"Calling ReadConfirmationReceived callback function for message {:?}",
confirmation.message_id,
);

unsafe {
(self.callback_read_confirmation_received)(Box::into_raw(Box::new(confirmation)));
}
}
}
147 changes: 147 additions & 0 deletions base_layer/chat_ffi/src/confirmation.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
// 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::{convert::TryFrom, ptr};

use libc::{c_int, c_uint};
use tari_contacts::contacts_service::types::Confirmation;

use crate::{
error::{InterfaceError, LibChatError},
types::{chat_byte_vector_create, ChatByteVector},
};

/// Get a pointer to a ChatByteVector representation of a message id
///
/// ## Arguments
/// `confirmation` - A pointer to the Confirmation
/// `error_out` - Pointer to an int which will be modified
///
/// ## Returns
/// `*mut ChatByteVector` - A ptr to a ChatByteVector
///
/// # Safety
/// The ```confirmation``` When done with the confirmation it should be destroyed
/// The ```ChatByteVector``` When done with the returned ChatByteVector it should be destroyed
#[no_mangle]
pub unsafe extern "C" fn read_confirmation_message_id(
confirmation: *mut Confirmation,
error_out: *mut c_int,
) -> *mut ChatByteVector {
let mut error = 0;
ptr::swap(error_out, &mut error as *mut c_int);

if confirmation.is_null() {
error = LibChatError::from(InterfaceError::NullError("client".to_string())).code;
ptr::swap(error_out, &mut error as *mut c_int);
}

let c = &(*confirmation);
let data_bytes = c.message_id.clone();
let len = u32::try_from(data_bytes.len()).expect("Can't cast from usize");
chat_byte_vector_create(data_bytes.as_ptr(), len as c_uint, error_out)
}

/// Get a c_uint timestamp for the confirmation
///
/// ## Arguments
/// `confirmation` - A pointer to the Confirmation
/// `error_out` - Pointer to an int which will be modified
///
/// ## Returns
/// `c_uint` - A uint representation of time. May return 0 if casting fails
///
/// # Safety
/// None
#[no_mangle]
pub unsafe extern "C" fn read_confirmation_timestamp(confirmation: *mut Confirmation, error_out: *mut c_int) -> c_uint {
let mut error = 0;
ptr::swap(error_out, &mut error as *mut c_int);

if confirmation.is_null() {
error = LibChatError::from(InterfaceError::NullError("client".to_string())).code;
ptr::swap(error_out, &mut error as *mut c_int);
}

let c = &(*confirmation);
c_uint::try_from(c.timestamp).unwrap_or(0)
}

/// Frees memory for a Confirmation
///
/// ## Arguments
/// `address` - The pointer of a Confirmation
///
/// ## Returns
/// `()` - Does not return a value, equivalent to void in C
///
/// # Safety
/// None
#[no_mangle]
pub unsafe extern "C" fn destroy_confirmation(address: *mut Confirmation) {
if !address.is_null() {
drop(Box::from_raw(address))
}
}

#[cfg(test)]
mod test {
use tari_contacts::contacts_service::types::{Confirmation, MessageBuilder};
use tari_utilities::epoch_time::EpochTime;

use crate::{
confirmation::{destroy_confirmation, read_confirmation_message_id, read_confirmation_timestamp},
types::{chat_byte_vector_get_at, chat_byte_vector_get_length},
};

#[test]
fn test_reading_from_confrimation() {
let message_id = MessageBuilder::new().build().message_id;
let timestamp = EpochTime::now().as_u64();
let confirmation = Confirmation {
message_id: message_id.clone(),
timestamp,
};

let confirmation_ptr = Box::into_raw(Box::new(confirmation));
let error_out = Box::into_raw(Box::new(0));

unsafe {
let id_byte_vec = read_confirmation_message_id(confirmation_ptr, error_out);
let len = chat_byte_vector_get_length(id_byte_vec, error_out);

let mut read_id = vec![];
for i in 0..len {
read_id.push(chat_byte_vector_get_at(id_byte_vec, i, error_out));
}

assert_eq!(message_id, read_id)
}

unsafe {
let read_timestamp = read_confirmation_timestamp(confirmation_ptr, error_out);
assert_eq!(timestamp, u64::from(read_timestamp))
}

unsafe { destroy_confirmation(confirmation_ptr) }
}
}
Loading
Loading