Skip to content

Commit

Permalink
feat(chat): read receipt feature (#5824)
Browse files Browse the repository at this point in the history
Description
---
This improves chat by moving chat message types that move over comms
between peers into a specific chat wrapper type `ChatDispatch`. This new
type allows for multiple chat message forms such as a standard chat
message, as well as read, and delivery confirmations.

Motivation and Context
---
To enhance the chat FFI feature with check ✅, and double check ✅✅
capabilities such as chat apps like Telegram and WhatsApp. This PR
features refactoring on the contacts service to support new message
types, as well as FFI functionality to interact with the feature,
including callbacks for receiving confirmations.

How Has This Been Tested?
---
Cucumber, CI.

What process can a PR reviewer use to test or verify this change?
---
Naming is questionable. But overall does the chat message wrapping fit
our standard practice for similar concepts? The proto/prost enum support
is unique and makes for some awkward `try_from`'s but it works well
enough.

Breaking Changes
---

- [x] None
- [ ] Requires data directory on base node to be deleted
- [ ] Requires hard fork
- [ ] Other - Please specify
  • Loading branch information
brianp authored Oct 3, 2023
1 parent f85a878 commit d81fe7d
Show file tree
Hide file tree
Showing 26 changed files with 1,014 additions and 66 deletions.
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

0 comments on commit d81fe7d

Please sign in to comment.