diff --git a/Cargo.lock b/Cargo.lock index 513a18f56c..c88041476c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3066,6 +3066,7 @@ name = "minotari_chat_ffi" version = "0.52.0-pre.1" dependencies = [ "cbindgen", + "chrono", "libc", "libsqlite3-sys", "log", diff --git a/base_layer/chat_ffi/Cargo.toml b/base_layer/chat_ffi/Cargo.toml index 4bcacd07d1..e00273b9de 100644 --- a/base_layer/chat_ffi/Cargo.toml +++ b/base_layer/chat_ffi/Cargo.toml @@ -8,7 +8,7 @@ edition = "2018" [dependencies] minotari_app_utilities = { path = "../../applications/minotari_app_utilities" } -tari_chat_client = { path = "../contacts/examples/chat_client" } +tari_chat_client = { path = "../contacts/src/chat_client" } tari_common = { path = "../../common" } tari_common_types = { path = "../common_types" } tari_contacts = { path = "../contacts" } @@ -29,6 +29,9 @@ openssl = { version = "0.10.55", features = ["vendored"] } [lib] crate-type = ["staticlib","cdylib"] +[dev-dependencies] +chrono = { version = "0.4.19", default-features = false } + [build-dependencies] cbindgen = "0.24.3" tari_common = { path = "../../common", features = ["build", "static-application-info"] } diff --git a/base_layer/chat_ffi/chat.h b/base_layer/chat_ffi/chat.h index 8b325144b5..00701fccc8 100644 --- a/base_layer/chat_ffi/chat.h +++ b/base_layer/chat_ffi/chat.h @@ -12,48 +12,30 @@ struct ApplicationConfig; struct ChatByteVector; -struct ChatClientFFI; - -struct ChatMessageMetadataVector; - -struct ChatMessages; +struct ChatClient; struct Confirmation; +struct ContactsLivenessData; + struct Message; -struct TariAddress; +struct MessageMetadata; -struct TransportConfig; +struct MessageVector; -struct ChatFFIContactsLivenessData { - const char *address; - uint64_t last_seen; - uint8_t online_status; -}; +struct TariAddress; -typedef void (*CallbackContactStatusChange)(struct ChatFFIContactsLivenessData*); +struct TransportConfig; -struct ChatFFIMessage { - const char *body; - const char *from_address; - uint64_t stored_at; - const char *message_id; - struct ChatMessageMetadataVector *metadata; - int metadata_len; -}; +typedef void (*CallbackContactStatusChange)(struct ContactsLivenessData*); -typedef void (*CallbackMessageReceived)(struct ChatFFIMessage*); +typedef void (*CallbackMessageReceived)(struct Message*); typedef void (*CallbackDeliveryConfirmationReceived)(struct Confirmation*); typedef void (*CallbackReadConfirmationReceived)(struct Confirmation*); -struct ChatFFIMessageMetadata { - struct ChatByteVector *data; - int metadata_type; -}; - #ifdef __cplusplus extern "C" { #endif // __cplusplus @@ -63,28 +45,35 @@ extern "C" { * * ## Arguments * `config` - The ApplicationConfig pointer - * `identity_file_path` - The path to the node identity file * `error_out` - Pointer to an int which will be modified + * `callback_contact_status_change` - A callback function pointer. this is called whenever a + * contacts liveness event comes in. + * `callback_message_received` - A callback function pointer. This is called whenever a chat + * message is received. + * `callback_delivery_confirmation_received` - A callback function pointer. This is called when the + * client receives a confirmation of message delivery. + * `callback_read_confirmation_received` - A callback function pointer. This is called when the + * client receives a confirmation of message read. * * ## Returns * `*mut ChatClient` - Returns a pointer to a ChatClient, note that it returns ptr::null_mut() * if any error was encountered or if the runtime could not be created. * * # Safety - * The ```destroy_client``` method must be called when finished with a ClientFFI to prevent a memory leak + * The ```destroy_chat_client``` method must be called when finished with a ClientFFI to prevent a memory leak */ -struct ChatClientFFI *create_chat_client(struct ApplicationConfig *config, - int *error_out, - CallbackContactStatusChange callback_contact_status_change, - CallbackMessageReceived callback_message_received, - CallbackDeliveryConfirmationReceived callback_delivery_confirmation_received, - CallbackReadConfirmationReceived callback_read_confirmation_received); +struct ChatClient *create_chat_client(struct ApplicationConfig *config, + int *error_out, + CallbackContactStatusChange callback_contact_status_change, + CallbackMessageReceived callback_message_received, + CallbackDeliveryConfirmationReceived callback_delivery_confirmation_received, + CallbackReadConfirmationReceived callback_read_confirmation_received); /** - * Frees memory for a ChatClientFFI + * Frees memory for a ChatClient * * ## Arguments - * `client` - The pointer of a ChatClientFFI + * `ptr` - The pointer of a ChatClient * * ## Returns * `()` - Does not return a value, equivalent to void in C @@ -92,14 +81,25 @@ struct ChatClientFFI *create_chat_client(struct ApplicationConfig *config, * # Safety * None */ -void destroy_chat_client_ffi(struct ChatClientFFI *client); +void destroy_chat_client(struct ChatClient *ptr); /** - * Creates a Chat Client config + * Creates a ChatClient config * * ## Arguments * `network` - The network to run on * `public_address` - The nodes public address + * `datastore_path` - The directory for config and db files + * `identity_file_path` - The location of the identity file + * `tor_transport_config` - A pointer to the TransportConfig + * `log_path` - directory for storing log files + * `log_verbosity` - how verbose should logging be as a c_int 0-5, or 11 + * 0 => Off + * 1 => Error + * 2 => Warn + * 3 => Info + * 4 => Debug + * 5 | 11 => Trace // Cranked up to 11 * `error_out` - Pointer to an int which will be modified * * ## Returns @@ -121,7 +121,7 @@ struct ApplicationConfig *create_chat_config(const char *network_str, * Frees memory for an ApplicationConfig * * ## Arguments - * `config` - The pointer of an ApplicationConfig + * `ptr` - The pointer of an ApplicationConfig * * ## Returns * `()` - Does not return a value, equivalent to void in C @@ -129,45 +129,137 @@ struct ApplicationConfig *create_chat_config(const char *network_str, * # Safety * None */ -void destroy_chat_config(struct ApplicationConfig *config); +void destroy_chat_config(struct ApplicationConfig *ptr); /** - * Get a pointer to a ChatByteVector representation of a message id + * Creates a ChatByteVector * * ## Arguments - * `confirmation` - A pointer to the Confirmation + * `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); + +/** + * Send a read confirmation for a given message + * + * ## Arguments + * `client` - Pointer to the ChatClient + * `message` - Pointer to the Message that was read + * `error_out` - Pointer to an int which will be modified + * + * ## Returns + * `()` - Does not return a value, equivalent to void in C + * + * # Safety + * The `client` When done with the ChatClient it should be destroyed + * The `message` When done with the Message it should be destroyed + */ +void send_read_confirmation_for_message(struct ChatClient *client, + struct Message *message, + int *error_out); + +/** + * Get a pointer to a ChatByteVector representation of the message id associated to the confirmation + * + * ## Arguments + * `confirmation` - A pointer to the Confirmation you'd like to read from * `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 + * `confirmation` should be destroyed when finished + * ```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 + * Get a c_longlong 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 + * `c_longlong` - A uint representation of time since epoch. May return -1 on error * * # Safety - * None + * The ```confirmation``` When done with the Confirmation it should be destroyed */ -unsigned int read_confirmation_timestamp(struct Confirmation *confirmation, int *error_out); +long long read_confirmation_timestamp(struct Confirmation *confirmation, int *error_out); /** * Frees memory for a Confirmation * * ## Arguments - * `address` - The pointer of a Confirmation + * `ptr` - The pointer of a Confirmation * * ## Returns * `()` - Does not return a value, equivalent to void in C @@ -175,13 +267,13 @@ unsigned int read_confirmation_timestamp(struct Confirmation *confirmation, int * # Safety * None */ -void destroy_confirmation(struct Confirmation *address); +void destroy_confirmation(struct Confirmation *ptr); /** * Add a contact * * ## Arguments - * `client` - The Client pointer + * `client` - The ChatClient pointer * `address` - A TariAddress ptr * `error_out` - Pointer to an int which will be modified * @@ -191,13 +283,13 @@ void destroy_confirmation(struct Confirmation *address); * # Safety * The ```receiver``` should be destroyed after use */ -void add_chat_contact(struct ChatClientFFI *client, struct TariAddress *address, int *error_out); +void add_chat_contact(struct ChatClient *client, struct TariAddress *address, int *error_out); /** * Check the online status of a contact * * ## Arguments - * `client` - The Client pointer + * `client` - The ChatClient pointer * `address` - A TariAddress ptr * `error_out` - Pointer to an int which will be modified * @@ -211,14 +303,82 @@ void add_chat_contact(struct ChatClientFFI *client, struct TariAddress *address, * # Safety * The ```address``` should be destroyed after use */ -int check_online_status(struct ChatClientFFI *client, struct TariAddress *receiver, int *error_out); +int check_online_status(struct ChatClient *client, struct TariAddress *receiver, int *error_out); + +/** + * Returns a pointer to a TariAddress + * + * ## Arguments + * `liveness` - A pointer to a ContactsLivenessData struct + * `error_out` - Pointer to an int which will be modified + * + * ## Returns + * `*mut TariAddress` - A ptr to a TariAddress + * + * ## Safety + * `liveness` should be destroyed eventually + * the returned `TariAddress` should be destroyed eventually + */ +struct TariAddress *read_liveness_data_address(struct ContactsLivenessData *liveness, + int *error_out); + +/** + * Returns an c_uchar representation of a contacts online status + * + * ## Arguments + * `liveness` - A pointer to a ContactsLivenessData struct + * `error_out` - Pointer to an int which will be modified + * + * ## Returns + * `c_uchar` - A c_uchar rep of an enum for a contacts online status. May return 0 if an error occurs + * Online => 1 + * Offline => 2 + * NeverSeen => 3 + * Banned => 4 + * + * ## Safety + * `liveness` should be destroyed eventually + */ +unsigned char read_liveness_data_online_status(struct ContactsLivenessData *liveness, + int *error_out); + +/** + * Returns an c_longlong representation of a timestamp when the contact was last seen + * + * ## Arguments + * `liveness` - A pointer to a ContactsLivenessData struct + * `error_out` - Pointer to an int which will be modified + * + * ## Returns + * `c_longlong` - A c_longlong rep of an enum for a contacts online status. May return -1 if an error + * occurs, or 0 if the contact has never been seen + * + * ## Safety + * `liveness` should be destroyed eventually + */ +long long read_liveness_data_last_seen(struct ContactsLivenessData *liveness, + int *error_out); + +/** + * Frees memory for a ContactsLivenessData + * + * ## Arguments + * `ptr` - The pointer of a ContactsLivenessData + * + * ## Returns + * `()` - Does not return a value, equivalent to void in C + * + * # Safety + * None + */ +void destroy_contacts_liveness_data(struct ContactsLivenessData *ptr); /** - * Creates a message and returns a ptr to it + * Creates a message and returns a pointer to it * * ## Arguments - * `receiver` - A string containing a tari address - * `message` - The peer seeds config for the node + * `receiver` - A pointer to a TariAddress + * `message` - A string to send as a text message * `error_out` - Pointer to an int which will be modified * * ## Returns @@ -226,16 +386,17 @@ int check_online_status(struct ChatClientFFI *client, struct TariAddress *receiv * * # Safety * The ```receiver``` should be destroyed after use + * The ```Message``` received should be destroyed after use */ struct Message *create_chat_message(struct TariAddress *receiver, const char *message, int *error_out); /** - * Frees memory for message + * Frees memory for Message * * ## Arguments - * `messages_ptr` - The pointer of a Message + * `ptr` - The pointer of a Message * * ## Returns * `()` - Does not return a value, equivalent to void in C @@ -243,13 +404,13 @@ struct Message *create_chat_message(struct TariAddress *receiver, * # Safety * None */ -void destroy_chat_message(struct Message *messages_ptr); +void destroy_chat_message(struct Message *ptr); /** * Sends a message over a client * * ## Arguments - * `client` - The Client pointer + * `client` - The ChatClient pointer * `message` - Pointer to a Message struct * `error_out` - Pointer to an int which will be modified * @@ -259,217 +420,217 @@ void destroy_chat_message(struct Message *messages_ptr); * # Safety * The ```message``` should be destroyed after use */ -void send_chat_message(struct ChatClientFFI *client, struct Message *message, int *error_out); +void send_chat_message(struct ChatClient *client, struct Message *message, int *error_out); /** - * Get a ptr to all messages from or to address + * Reads the message metadata of a message and returns a ptr to the metadata at the given position * * ## Arguments - * `client` - The Client pointer - * `address` - A TariAddress ptr - * `limit` - The amount of messages you want to fetch. Default to 35, max 2500 - * `page` - The page of results you'd like returned. Default to 0, maximum of u64 max + * `message` - A pointer to a Message + * `position` - The index of the array of metadata * `error_out` - Pointer to an int which will be modified * * ## Returns - * `()` - Does not return a value, equivalent to void in C + * `*mut MessageMetadata` - A pointer to to MessageMetadata * - * # Safety - * The ```address``` should be destroyed after use - * The returned pointer to ```*mut ChatMessages``` should be destroyed after use + * ## Safety + * `message` should be destroyed eventually + * the returned `MessageMetadata` should be destroyed eventually */ -struct ChatMessages *get_chat_messages(struct ChatClientFFI *client, - struct TariAddress *address, - int limit, - int page, - int *error_out); +struct MessageMetadata *chat_metadata_get_at(struct Message *message, + unsigned int position, + int *error_out); /** - * Frees memory for messages + * Returns the length of the Metadata Vector a chat Message contains * * ## Arguments - * `ptr` - The pointer of a Message + * `message` - A pointer to a Message + * `error_out` - Pointer to an int which will be modified * * ## Returns - * `()` - Does not return a value, equivalent to void in C + * `c_longlong` - The length of the metadata vector for a Message. May return -1 if something goes wrong * - * # Safety - * None + * ## Safety + * `message` should be destroyed eventually */ -void destroy_chat_messages(struct ChatMessages *ptr); +long long chat_message_metadata_len(struct Message *message, + int *error_out); /** - * Creates message metadata and appends it to a Message + * Returns a pointer to a ChatByteVector representing the data of the Message * * ## Arguments - * `message` - A pointer to a message - * `metadata_type` - An int8 that maps to MessageMetadataType enum - * '0' -> Reply - * '1' -> TokenRequest - * `data` - contents for the metadata in string format + * `message` - A pointer to a Message * `error_out` - Pointer to an int which will be modified * * ## Returns - * `()` - Does not return a value, equivalent to void in C + * `*mut ChatByteVector` - A ptr to a ChatByteVector * * ## Safety * `message` should be destroyed eventually + * the returned `ChatByteVector` should be destroyed eventually */ -void add_chat_message_metadata(struct Message *message, - int metadata_type, - struct ChatByteVector *data, - int *error_out); +struct ChatByteVector *read_chat_message_body(struct Message *message, int *error_out); /** - * Reads the message metadata of a message and returns a ptr to the metadata at the given position + * Returns a pointer to a TariAddress * * ## Arguments - * `message` - A pointer to a message - * `position` - The index of the array of metadata + * `message` - A pointer to a Message * `error_out` - Pointer to an int which will be modified * * ## Returns - * `()` - Does not return a value, equivalent to void in C + * `*mut TariAddress` - A ptr to a TariAddress * * ## Safety * `message` should be destroyed eventually - * the returned `ChatFFIMessageMetadata` should be destroyed eventually + * the returned `TariAddress` should be destroyed eventually */ -struct ChatFFIMessageMetadata *read_chat_metadata_at_position(struct ChatFFIMessage *message, - unsigned int position, - int *error_out); +struct TariAddress *read_chat_message_address(struct Message *message, int *error_out); /** - * Returns the enum int representation of a metadata type + * Returns a c_int representation of the Direction enum * * ## Arguments - * `msg_metadata` - A pointer to a message metadat + * `message` - A pointer to a Message * `error_out` - Pointer to an int which will be modified * * ## Returns - * `metadata_type` - An int8 that maps to MessageMetadataType enum - * '0' -> Reply - * '1' -> TokenRequest + * `c_int` - A c_int rep of the direction enum. May return -1 if anything goes wrong + * 0 => Inbound + * 1 => Outbound * * ## Safety - * `msg_metadata` should be destroyed eventually + * `message` should be destroyed eventually */ -int read_chat_metadata_type(struct ChatFFIMessageMetadata *msg_metadata, int *error_out); +int read_chat_message_direction(struct Message *message, int *error_out); /** - * Returns a ptr to a ByteVector + * Returns a c_ulonglong representation of the stored at timestamp as seconds since epoch * * ## Arguments - * `msg_metadata` - A pointer to a message metadata + * `message` - A pointer to a Message * `error_out` - Pointer to an int which will be modified * * ## Returns - * `*mut ` - An int8 that maps to MessageMetadataType enum - * '0' -> Reply - * '1' -> TokenRequest + * `c_ulonglong` - The stored_at timestamp, seconds since epoch. Returns 0 if message is null. * * ## Safety - * `msg_metadata` should be destroyed eventually - * the returned `ChatByteVector` should be destroyed eventually + * `message` should be destroyed eventually */ -struct ChatByteVector *read_chat_metadata_data(struct ChatFFIMessageMetadata *msg_metadata, - int *error_out); +unsigned long long read_chat_message_stored_at(struct Message *message, int *error_out); /** - * Sends a read confirmation for a given message + * Returns a c_ulonglong representation of the delivery confirmation timestamp as seconds since epoch * * ## Arguments - * `client` - The chat client - * `message` - The message that was read + * `message` - A pointer to a Message * `error_out` - Pointer to an int which will be modified * * ## Returns - * `*mut TariAddress` - A ptr to a TariAddress + * `c_ulonglong` - The delivery_confirmation_at timestamp, seconds since epoch. Returns 0 if message + * is null or if no confirmation is stored. * - * # Safety - * The ```ChatClientFFI``` When done with the client it should be destroyed - * The ```Message``` When done with the Message it should be destroyed + * ## Safety + * `message` should be destroyed eventually */ -void send_read_confirmation_for_message(struct ChatClientFFI *client, - struct Message *message, - int *error_out); +unsigned long long read_chat_message_delivery_confirmation_at(struct Message *message, + int *error_out); /** - * Creates a tor transport config + * Returns a c_ulonglong representation of the read confirmation timestamp as seconds since epoch * * ## Arguments - * `control_server_address` - The pointer to a char array - * `tor_cookie` - The pointer to a ChatByteVector containing the contents of the tor cookie file, can be null - * `tor_port` - The tor port - * `tor_proxy_bypass_for_outbound` - Whether tor will use a direct tcp connection for a given bypass address instead of - * the tor proxy if tcp is available, if not it has no effect - * `socks_password` - The pointer to a char array containing the socks password, can be null - * `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. + * `message` - A pointer to a Message + * `error_out` - Pointer to an int which will be modified * * ## Returns - * `*mut TransportConfig` - Returns a pointer to a tor TransportConfig, null on error. + * `c_ulonglong` - The read_confirmation_at timestamp, seconds since epoch. Returns 0 if message is + * null or if no confirmation is stored. * - * # Safety - * The ```destroy_chat_tor_transport_config``` method must be called when finished with a TransportConfig to prevent a - * memory leak + * ## Safety + * `message` should be destroyed eventually */ -struct TransportConfig *create_chat_tor_transport_config(const char *control_server_address, - const struct ChatByteVector *tor_cookie, - unsigned short tor_port, - bool tor_proxy_bypass_for_outbound, - const char *socks_username, - const char *socks_password, - int *error_out); +unsigned long long read_chat_message_read_confirmation_at(struct Message *message, int *error_out); /** - * Frees memory for a TransportConfig + * Returns a pointer to a ChatByteVector representation of the message_id * * ## Arguments - * `transport` - The pointer to a TransportConfig + * `message` - A pointer to a Message + * `error_out` - Pointer to an int which will be modified + * + * ## Returns + * `*mut ChatByteVector` - A ChatByteVector for the message id + * + * ## Safety + * `message` should be destroyed eventually + * The returned ```ChatByteVector``` should be destroyed eventually + */ +struct ChatByteVector *read_chat_message_id(struct Message *message, int *error_out); + +/** + * Creates message metadata and appends it to a Message + * + * ## Arguments + * `message` - A pointer to a message + * `metadata_type` - An c_uchar that maps to MessageMetadataType enum + * '0' -> Reply + * '1' -> TokenRequest + * `data` - A pointer to a byte vector containing bytes for the data field + * `error_out` - Pointer to an int which will be modified * * ## Returns * `()` - Does not return a value, equivalent to void in C * - * # Safety - * None + * ## Safety + * `message` should be destroyed eventually */ -void destroy_chat_tor_transport_config(struct TransportConfig *transport); +void add_chat_message_metadata(struct Message *message, + unsigned char metadata_type, + struct ChatByteVector *data, + int *error_out); /** - * Creates a TariAddress and returns a ptr + * Returns the c_int representation of a metadata type enum * * ## Arguments - * `receiver_c_char` - A string containing a tari address hex value + * `msg_metadata` - A pointer to a MessageMetadata * `error_out` - Pointer to an int which will be modified * * ## Returns - * `*mut TariAddress` - A ptr to a TariAddress + * `c_int` - An int8 that maps to MessageMetadataType enum. May return -1 if something goes wrong + * '0' -> Reply + * '1' -> TokenRequest * - * # Safety - * The ```destroy_tari_address``` function should be called when finished with the TariAddress + * ## Safety + * `msg_metadata` should be destroyed eventually */ -struct TariAddress *create_tari_address(const char *receiver_c_char, int *error_out); +int read_chat_metadata_type(struct MessageMetadata *msg_metadata, int *error_out); /** - * Frees memory for a TariAddress + * Returns a ptr to a ByteVector * * ## Arguments - * `address` - The pointer of a TariAddress + * `msg_metadata` - A pointer to a MessageMetadata + * `error_out` - Pointer to an int which will be modified * * ## Returns - * `()` - Does not return a value, equivalent to void in C + * `*mut ChatByteVector` - A ptr to a ChatByteVector * - * # Safety - * None + * ## Safety + * `msg_metadata` should be destroyed eventually + * the returned `ChatByteVector` should be destroyed eventually */ -void destroy_tari_address(struct TariAddress *address); +struct ChatByteVector *read_chat_metadata_data(struct MessageMetadata *msg_metadata, + int *error_out); /** - * Frees memory for a ChatFFIContactsLivenessData + * Frees memory for MessageMetadata * * ## Arguments - * `address` - The pointer of a ChatFFIContactsLivenessData + * `ptr` - The pointer of a MessageMetadata * * ## Returns * `()` - Does not return a value, equivalent to void in C @@ -477,27 +638,71 @@ void destroy_tari_address(struct TariAddress *address); * # Safety * None */ -void destroy_chat_ffi_liveness_data(struct ChatFFIContactsLivenessData *address); +void destroy_chat_message_metadata(struct MessageMetadata *ptr); /** - * Frees memory for a ChatFFIMessage + * Get a ptr to all messages from or to an address * * ## Arguments - * `address` - The pointer to a ChatFFIMessage + * `client` - The ChatClient pointer + * `address` - A TariAddress pointer + * `limit` - The amount of messages you want to fetch. Default to 35, max 2500 + * `page` - The page of results you'd like returned. Default to 0, maximum of u64 max + * `error_out` - Pointer to an int which will be modified * * ## Returns - * `()` - Does not return a value, equivalent to void in C + * `*mut MessageVector` - A pointer to a Vector of Messages * * # Safety - * None + * The returned pointer to ```MessageVector``` should be destroyed after use + * ```client``` should be destroyed after use + * ```address``` should be destroyed after use + */ +struct MessageVector *get_chat_messages(struct ChatClient *client, + struct TariAddress *address, + int limit, + int page, + int *error_out); + +/** + * Returns the length of the MessageVector + * + * ## Arguments + * `messages` - A pointer to a MessageVector + * `error_out` - Pointer to an int which will be modified + * + * ## Returns + * `c_int` - The length of the metadata vector for a Message. May return -1 if something goes wrong + * + * ## Safety + * `messages` should be destroyed eventually + */ +int message_vector_len(struct MessageVector *messages, int *error_out); + +/** + * Reads the MessageVector and returns a Message at a given position + * + * ## Arguments + * `messages` - A pointer to a MessageVector + * `position` - The index of the vector for a Message + * `error_out` - Pointer to an int which will be modified + * + * ## Returns + * `*mut ptr Message` - A pointer to a Message + * + * ## Safety + * `messages` should be destroyed eventually + * the returned `Message` should be destroyed eventually */ -void destroy_chat_ffi_message(struct ChatFFIMessage *address); +struct Message *message_vector_get_at(struct MessageVector *messages, + unsigned int position, + int *error_out); /** - * Frees memory for a ChatMessageMetadataVector + * Frees memory for MessagesVector * * ## Arguments - * `address` - The pointer to a ChatMessageMetadataVector + * `ptr` - The pointer of a MessagesVector * * ## Returns * `()` - Does not return a value, equivalent to void in C @@ -505,34 +710,41 @@ void destroy_chat_ffi_message(struct ChatFFIMessage *address); * # Safety * None */ -void destroy_chat_message_metadata_vector(struct ChatMessageMetadataVector *address); +void destroy_message_vector(struct MessageVector *ptr); /** - * Creates a ChatByteVector + * Creates a tor transport config * * ## Arguments - * `byte_array` - The pointer to the byte array - * `element_count` - The number of elements in byte_array + * `control_server_address` - The pointer to a char array + * `tor_cookie` - The pointer to a ChatByteVector containing the contents of the tor cookie file, can be null + * `tor_port` - The tor port + * `tor_proxy_bypass_for_outbound` - Whether tor will use a direct tcp connection for a given bypass address instead of + * the tor proxy if tcp is available, if not it has no effect + * `socks_password` - The pointer to a char array containing the socks password, can be null * `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 + * `*mut TransportConfig` - Returns a pointer to a tor TransportConfig, null on error. * * # Safety - * The ```byte_vector_destroy``` function must be called when finished with a ChatByteVector to prevent a memory leak + * The ```destroy_chat_tor_transport_config``` method must be called when finished with a TransportConfig to prevent a + * memory leak */ -struct ChatByteVector *chat_byte_vector_create(const unsigned char *byte_array, - unsigned int element_count, - int *error_out); +struct TransportConfig *create_chat_tor_transport_config(const char *control_server_address, + const struct ChatByteVector *tor_cookie, + unsigned short tor_port, + bool tor_proxy_bypass_for_outbound, + const char *socks_username, + const char *socks_password, + int *error_out); /** - * Frees memory for a ChatByteVector + * Frees memory for a TransportConfig * * ## Arguments - * `bytes` - The pointer to a ChatByteVector + * `ptr` - The pointer to a TransportConfig * * ## Returns * `()` - Does not return a value, equivalent to void in C @@ -540,45 +752,36 @@ struct ChatByteVector *chat_byte_vector_create(const unsigned char *byte_array, * # Safety * None */ -void chat_byte_vector_destroy(struct ChatByteVector *bytes); +void destroy_chat_tor_transport_config(struct TransportConfig *ptr); /** - * Gets a c_uchar at position in a ChatByteVector + * Creates a TariAddress and returns a ptr * * ## 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. + * `receiver_c_char` - A string containing a tari address hex value + * `error_out` - Pointer to an int which will be modified * * ## 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 + * `*mut TariAddress` - A ptr to a TariAddress * * # Safety - * None + * The ```destroy_tari_address``` function should be called when finished with the TariAddress */ -unsigned char chat_byte_vector_get_at(struct ChatByteVector *ptr, - unsigned int position, - int *error_out); +struct TariAddress *create_tari_address(const char *receiver_c_char, int *error_out); /** - * Gets the number of elements in a ChatByteVector + * Frees memory for a TariAddress * * ## 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. + * `address` - The pointer of a TariAddress * * ## Returns - * `c_uint` - Returns the integer number of elements in the ChatByteVector. Note that it will be zero - * if ptr is null + * `()` - Does not return a value, equivalent to void in C * * # Safety * None */ -unsigned int chat_byte_vector_get_length(const struct ChatByteVector *vec, - int *error_out); +void destroy_tari_address(struct TariAddress *address); #ifdef __cplusplus } // extern "C" diff --git a/base_layer/chat_ffi/src/application_config.rs b/base_layer/chat_ffi/src/application_config.rs index 84b972bf35..df2d0db6d7 100644 --- a/base_layer/chat_ffi/src/application_config.rs +++ b/base_layer/chat_ffi/src/application_config.rs @@ -28,15 +28,26 @@ use tari_chat_client::{ networking::Multiaddr, }; use tari_common::configuration::{MultiaddrList, Network, StringList}; -use tari_p2p::{PeerSeedsConfig, TransportConfig, DEFAULT_DNS_NAME_SERVER}; +use tari_p2p::{PeerSeedsConfig, TransportConfig, TransportType, DEFAULT_DNS_NAME_SERVER}; use crate::error::{InterfaceError, LibChatError}; -/// Creates a Chat Client config +/// Creates a ChatClient config /// /// ## Arguments /// `network` - The network to run on /// `public_address` - The nodes public address +/// `datastore_path` - The directory for config and db files +/// `identity_file_path` - The location of the identity file +/// `tor_transport_config` - A pointer to the TransportConfig +/// `log_path` - directory for storing log files +/// `log_verbosity` - how verbose should logging be as a c_int 0-5, or 11 +/// 0 => Off +/// 1 => Error +/// 2 => Warn +/// 3 => Info +/// 4 => Debug +/// 5 | 11 => Trace // Cranked up to 11 /// `error_out` - Pointer to an int which will be modified /// /// ## Returns @@ -168,10 +179,16 @@ pub unsafe extern "C" fn create_chat_config( }, }; + let addresses = if (*tor_transport_config).transport_type == TransportType::Tor { + MultiaddrList::default() + } else { + MultiaddrList::from(vec![address]) + }; + let mut chat_client_config = ChatClientConfig::default(); chat_client_config.network = network; chat_client_config.p2p.transport = (*tor_transport_config).clone(); - chat_client_config.p2p.public_addresses = MultiaddrList::from(vec![address]); + chat_client_config.p2p.public_addresses = addresses; chat_client_config.log_path = Some(log_path); chat_client_config.log_verbosity = Some(log_verbosity); chat_client_config.identity_file = identity_path; @@ -194,7 +211,7 @@ pub unsafe extern "C" fn create_chat_config( /// Frees memory for an ApplicationConfig /// /// ## Arguments -/// `config` - The pointer of an ApplicationConfig +/// `ptr` - The pointer of an ApplicationConfig /// /// ## Returns /// `()` - Does not return a value, equivalent to void in C @@ -202,9 +219,9 @@ pub unsafe extern "C" fn create_chat_config( /// # Safety /// None #[no_mangle] -pub unsafe extern "C" fn destroy_chat_config(config: *mut ApplicationConfig) { - if !config.is_null() { - drop(Box::from_raw(config)) +pub unsafe extern "C" fn destroy_chat_config(ptr: *mut ApplicationConfig) { + if !ptr.is_null() { + drop(Box::from_raw(ptr)) } } diff --git a/base_layer/chat_ffi/src/types/byte_vector.rs b/base_layer/chat_ffi/src/byte_vector.rs similarity index 96% rename from base_layer/chat_ffi/src/types/byte_vector.rs rename to base_layer/chat_ffi/src/byte_vector.rs index 9986fc579e..9a73239e1e 100644 --- a/base_layer/chat_ffi/src/types/byte_vector.rs +++ b/base_layer/chat_ffi/src/byte_vector.rs @@ -24,10 +24,10 @@ use std::{ptr, slice}; use libc::{c_int, c_uchar, c_uint}; -use crate::{ - error::{InterfaceError, LibChatError}, - types::ChatByteVector, -}; +use crate::error::{InterfaceError, LibChatError}; + +#[derive(Debug, PartialEq, Clone)] +pub struct ChatByteVector(pub Vec); // declared like this so that it can be exposed to external header /// Creates a ChatByteVector /// diff --git a/base_layer/chat_ffi/src/callback_handler.rs b/base_layer/chat_ffi/src/callback_handler.rs index 0251956129..1c1e0268dd 100644 --- a/base_layer/chat_ffi/src/callback_handler.rs +++ b/base_layer/chat_ffi/src/callback_handler.rs @@ -20,21 +20,19 @@ // 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, ops::Deref}; +use std::ops::Deref; -use log::{debug, error, info, trace}; +use log::{debug, info, trace}; use tari_contacts::contacts_service::{ handle::{ContactsLivenessData, ContactsLivenessEvent, ContactsServiceHandle}, types::{Confirmation, Message, MessageDispatch}, }; use tari_shutdown::ShutdownSignal; -use crate::types::{ChatFFIContactsLivenessData, ChatFFIMessage}; - 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 CallbackContactStatusChange = unsafe extern "C" fn(*mut ContactsLivenessData); +pub(crate) type CallbackMessageReceived = unsafe extern "C" fn(*mut Message); pub(crate) type CallbackDeliveryConfirmationReceived = unsafe extern "C" fn(*mut Confirmation); pub(crate) type CallbackReadConfirmationReceived = unsafe extern "C" fn(*mut Confirmation); @@ -127,13 +125,8 @@ impl CallbackHandler { data.address(), ); - match ChatFFIContactsLivenessData::try_from(data) { - Ok(data) => unsafe { - (self.callback_contact_status_change)(Box::into_raw(Box::new(data))); - }, - Err(e) => { - error!(target: LOG_TARGET, "Error processing contacts liveness data received callback: {}", e) - }, + unsafe { + (self.callback_contact_status_change)(Box::into_raw(Box::new(data))); } } @@ -144,11 +137,8 @@ impl CallbackHandler { message.address, ); - match ChatFFIMessage::try_from(message) { - Ok(message) => unsafe { - (self.callback_message_received)(Box::into_raw(Box::new(message))); - }, - Err(e) => error!(target: LOG_TARGET, "Error processing message received callback: {}", e), + unsafe { + (self.callback_message_received)(Box::into_raw(Box::new(message))); } } diff --git a/base_layer/chat_ffi/src/confirmation.rs b/base_layer/chat_ffi/src/confirmation.rs index 93c1a2bdb4..05cb2e0d06 100644 --- a/base_layer/chat_ffi/src/confirmation.rs +++ b/base_layer/chat_ffi/src/confirmation.rs @@ -20,28 +20,67 @@ // 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 std::{convert::TryFrom, os::raw::c_longlong, ptr}; use libc::{c_int, c_uint}; -use tari_contacts::contacts_service::types::Confirmation; +use tari_chat_client::ChatClient as ChatClientTrait; +use tari_contacts::contacts_service::types::{Confirmation, Message}; use crate::{ + byte_vector::{chat_byte_vector_create, ChatByteVector}, error::{InterfaceError, LibChatError}, - types::{chat_byte_vector_create, ChatByteVector}, + ChatClient, }; -/// Get a pointer to a ChatByteVector representation of a message id +/// Send a read confirmation for a given message /// /// ## Arguments -/// `confirmation` - A pointer to the Confirmation +/// `client` - Pointer to the ChatClient +/// `message` - Pointer to the Message that was read +/// `error_out` - Pointer to an int which will be modified +/// +/// ## Returns +/// `()` - Does not return a value, equivalent to void in C +/// +/// # Safety +/// The `client` When done with the ChatClient it should be destroyed +/// The `message` When done with the Message it should be destroyed +#[no_mangle] +pub unsafe extern "C" fn send_read_confirmation_for_message( + client: *mut ChatClient, + message: *mut Message, + error_out: *mut c_int, +) { + let mut error = 0; + ptr::swap(error_out, &mut error as *mut c_int); + + if client.is_null() { + error = LibChatError::from(InterfaceError::NullError("client".to_string())).code; + 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); + } + + (*client) + .runtime + .block_on((*client).client.send_read_receipt((*message).clone())); +} + +/// Get a pointer to a ChatByteVector representation of the message id associated to the confirmation +/// +/// ## Arguments +/// `confirmation` - A pointer to the Confirmation you'd like to read from /// `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 +/// `confirmation` should be destroyed when finished +/// ```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, @@ -61,35 +100,38 @@ pub unsafe extern "C" fn read_confirmation_message_id( chat_byte_vector_create(data_bytes.as_ptr(), len as c_uint, error_out) } -/// Get a c_uint timestamp for the confirmation +/// Get a c_longlong 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 +/// `c_longlong` - A uint representation of time since epoch. May return -1 on error /// /// # Safety -/// None +/// The ```confirmation``` When done with the Confirmation it should be destroyed #[no_mangle] -pub unsafe extern "C" fn read_confirmation_timestamp(confirmation: *mut Confirmation, error_out: *mut c_int) -> c_uint { +pub unsafe extern "C" fn read_confirmation_timestamp( + confirmation: *mut Confirmation, + error_out: *mut c_int, +) -> c_longlong { 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); + return -1; } - let c = &(*confirmation); - c_uint::try_from(c.timestamp).unwrap_or(0) + (*confirmation).timestamp as c_longlong } /// Frees memory for a Confirmation /// /// ## Arguments -/// `address` - The pointer of a Confirmation +/// `ptr` - The pointer of a Confirmation /// /// ## Returns /// `()` - Does not return a value, equivalent to void in C @@ -97,9 +139,9 @@ pub unsafe extern "C" fn read_confirmation_timestamp(confirmation: *mut Confirma /// # Safety /// None #[no_mangle] -pub unsafe extern "C" fn destroy_confirmation(address: *mut Confirmation) { - if !address.is_null() { - drop(Box::from_raw(address)) +pub unsafe extern "C" fn destroy_confirmation(ptr: *mut Confirmation) { + if !ptr.is_null() { + drop(Box::from_raw(ptr)) } } @@ -109,8 +151,8 @@ mod test { use tari_utilities::epoch_time::EpochTime; use crate::{ + byte_vector::{chat_byte_vector_get_at, chat_byte_vector_get_length}, confirmation::{destroy_confirmation, read_confirmation_message_id, read_confirmation_timestamp}, - types::{chat_byte_vector_get_at, chat_byte_vector_get_length}, }; #[test] @@ -139,7 +181,7 @@ mod test { unsafe { let read_timestamp = read_confirmation_timestamp(confirmation_ptr, error_out); - assert_eq!(timestamp, u64::from(read_timestamp)) + assert_eq!(timestamp, read_timestamp as u64) } unsafe { destroy_confirmation(confirmation_ptr) } diff --git a/base_layer/chat_ffi/src/contacts.rs b/base_layer/chat_ffi/src/contacts.rs index a17aa0116f..3c33082947 100644 --- a/base_layer/chat_ffi/src/contacts.rs +++ b/base_layer/chat_ffi/src/contacts.rs @@ -23,18 +23,18 @@ use std::ptr; use libc::c_int; -use tari_chat_client::ChatClient; +use tari_chat_client::ChatClient as ChatClientTrait; use tari_common_types::tari_address::TariAddress; use crate::{ error::{InterfaceError, LibChatError}, - ChatClientFFI, + ChatClient, }; /// Add a contact /// /// ## Arguments -/// `client` - The Client pointer +/// `client` - The ChatClient pointer /// `address` - A TariAddress ptr /// `error_out` - Pointer to an int which will be modified /// @@ -44,11 +44,7 @@ use crate::{ /// # Safety /// The ```receiver``` should be destroyed after use #[no_mangle] -pub unsafe extern "C" fn add_chat_contact( - client: *mut ChatClientFFI, - address: *mut TariAddress, - error_out: *mut c_int, -) { +pub unsafe extern "C" fn add_chat_contact(client: *mut ChatClient, address: *mut TariAddress, error_out: *mut c_int) { let mut error = 0; ptr::swap(error_out, &mut error as *mut c_int); @@ -68,7 +64,7 @@ pub unsafe extern "C" fn add_chat_contact( /// Check the online status of a contact /// /// ## Arguments -/// `client` - The Client pointer +/// `client` - The ChatClient pointer /// `address` - A TariAddress ptr /// `error_out` - Pointer to an int which will be modified /// @@ -83,7 +79,7 @@ pub unsafe extern "C" fn add_chat_contact( /// The ```address``` should be destroyed after use #[no_mangle] pub unsafe extern "C" fn check_online_status( - client: *mut ChatClientFFI, + client: *mut ChatClient, receiver: *mut TariAddress, error_out: *mut c_int, ) -> c_int { diff --git a/base_layer/chat_ffi/src/contacts_liveness_data.rs b/base_layer/chat_ffi/src/contacts_liveness_data.rs new file mode 100644 index 0000000000..ab27f03e35 --- /dev/null +++ b/base_layer/chat_ffi/src/contacts_liveness_data.rs @@ -0,0 +1,272 @@ +// 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; + +use libc::{c_int, c_longlong, c_uchar}; +use tari_common_types::tari_address::TariAddress; +use tari_contacts::contacts_service::handle::ContactsLivenessData; + +use crate::error::{InterfaceError, LibChatError}; + +/// Returns a pointer to a TariAddress +/// +/// ## Arguments +/// `liveness` - A pointer to a ContactsLivenessData struct +/// `error_out` - Pointer to an int which will be modified +/// +/// ## Returns +/// `*mut TariAddress` - A ptr to a TariAddress +/// +/// ## Safety +/// `liveness` should be destroyed eventually +/// the returned `TariAddress` should be destroyed eventually +#[no_mangle] +pub unsafe extern "C" fn read_liveness_data_address( + liveness: *mut ContactsLivenessData, + error_out: *mut c_int, +) -> *mut TariAddress { + let mut error = 0; + ptr::swap(error_out, &mut error as *mut c_int); + + if liveness.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 address = (*liveness).address().clone(); + Box::into_raw(Box::new(address)) +} + +/// Returns an c_uchar representation of a contacts online status +/// +/// ## Arguments +/// `liveness` - A pointer to a ContactsLivenessData struct +/// `error_out` - Pointer to an int which will be modified +/// +/// ## Returns +/// `c_uchar` - A c_uchar rep of an enum for a contacts online status. May return 0 if an error occurs +/// Online => 1 +/// Offline => 2 +/// NeverSeen => 3 +/// Banned => 4 +/// +/// ## Safety +/// `liveness` should be destroyed eventually +#[no_mangle] +pub unsafe extern "C" fn read_liveness_data_online_status( + liveness: *mut ContactsLivenessData, + error_out: *mut c_int, +) -> c_uchar { + let mut error = 0; + ptr::swap(error_out, &mut error as *mut c_int); + + if liveness.is_null() { + error = LibChatError::from(InterfaceError::NullError("message".to_string())).code; + ptr::swap(error_out, &mut error as *mut c_int); + return 0; + } + + (*liveness).online_status().as_u8() +} + +/// Returns an c_longlong representation of a timestamp when the contact was last seen +/// +/// ## Arguments +/// `liveness` - A pointer to a ContactsLivenessData struct +/// `error_out` - Pointer to an int which will be modified +/// +/// ## Returns +/// `c_longlong` - A c_longlong rep of an enum for a contacts online status. May return -1 if an error +/// occurs, or 0 if the contact has never been seen +/// +/// ## Safety +/// `liveness` should be destroyed eventually +#[no_mangle] +pub unsafe extern "C" fn read_liveness_data_last_seen( + liveness: *mut ContactsLivenessData, + error_out: *mut c_int, +) -> c_longlong { + let mut error = 0; + ptr::swap(error_out, &mut error as *mut c_int); + + if liveness.is_null() { + error = LibChatError::from(InterfaceError::NullError("message".to_string())).code; + ptr::swap(error_out, &mut error as *mut c_int); + return 0; + } + + match (*liveness).last_ping_pong_received() { + Some(last_seen) => last_seen.timestamp(), + None => 0, + } +} + +/// Frees memory for a ContactsLivenessData +/// +/// ## Arguments +/// `ptr` - The pointer of a ContactsLivenessData +/// +/// ## Returns +/// `()` - Does not return a value, equivalent to void in C +/// +/// # Safety +/// None +#[no_mangle] +pub unsafe extern "C" fn destroy_contacts_liveness_data(ptr: *mut ContactsLivenessData) { + if !ptr.is_null() { + drop(Box::from_raw(ptr)) + } +} + +#[cfg(test)] +mod test { + use std::convert::TryFrom; + + use chrono::NaiveDateTime; + use tari_contacts::contacts_service::service::{ContactMessageType, ContactOnlineStatus}; + use tari_utilities::epoch_time::EpochTime; + + use super::*; + use crate::tari_address::destroy_tari_address; + + #[test] + fn test_reading_address() { + let address = + TariAddress::from_hex("0c017c5cd01385f34ac065e3b05948326dc55d2494f120c6f459a07389011b4ec1").unwrap(); + let liveness = ContactsLivenessData::new( + address.clone(), + Default::default(), + None, + None, + ContactMessageType::Ping, + ContactOnlineStatus::Online, + ); + let liveness_ptr = Box::into_raw(Box::new(liveness)); + let error_out = Box::into_raw(Box::new(0)); + + unsafe { + let address_ptr = read_liveness_data_address(liveness_ptr, error_out); + + assert_eq!(address.to_bytes(), (*address_ptr).to_bytes()); + + destroy_contacts_liveness_data(liveness_ptr); + destroy_tari_address(address_ptr); + drop(Box::from_raw(error_out)); + } + } + + #[test] + fn test_reading_online_status() { + let statuses = [ + ContactOnlineStatus::Online, + ContactOnlineStatus::Offline, + ContactOnlineStatus::Banned("banned".to_string()), + ContactOnlineStatus::NeverSeen, + ]; + for status in statuses { + let liveness = ContactsLivenessData::new( + Default::default(), + Default::default(), + None, + None, + ContactMessageType::Ping, + status.clone(), + ); + let liveness_ptr = Box::into_raw(Box::new(liveness)); + let error_out = Box::into_raw(Box::new(0)); + + unsafe { + let status_byte = read_liveness_data_online_status(liveness_ptr, error_out); + + assert_eq!( + status.clone().as_u8(), + status_byte, + "Testing status: {} but got {}", + status, + status_byte + ); + + destroy_contacts_liveness_data(liveness_ptr); + drop(Box::from_raw(error_out)); + } + } + } + + #[test] + fn test_reading_online_status_with_no_ptr() { + let error_out = Box::into_raw(Box::new(0)); + + unsafe { + let status_byte = read_liveness_data_online_status(ptr::null_mut(), error_out); + + assert_eq!(0, status_byte); + + drop(Box::from_raw(error_out)); + } + } + + #[test] + fn test_reading_last_seen() { + let error_out = Box::into_raw(Box::new(0)); + + unsafe { + let timestamp = EpochTime::now().as_u64(); + let liveness = ContactsLivenessData::new( + Default::default(), + Default::default(), + None, + NaiveDateTime::from_timestamp_opt(i64::try_from(timestamp).unwrap(), 0), + ContactMessageType::Ping, + ContactOnlineStatus::Online, + ); + let liveness_ptr = Box::into_raw(Box::new(liveness)); + let c_timestamp = read_liveness_data_last_seen(liveness_ptr, error_out); + + assert_eq!(timestamp, c_timestamp as u64); + + destroy_contacts_liveness_data(liveness_ptr); + } + + unsafe { + let liveness = ContactsLivenessData::new( + Default::default(), + Default::default(), + None, + None, + ContactMessageType::Ping, + ContactOnlineStatus::Online, + ); + let liveness_ptr = Box::into_raw(Box::new(liveness)); + let c_timestamp = read_liveness_data_last_seen(liveness_ptr, error_out); + + assert_eq!(0, c_timestamp as u64); + + destroy_contacts_liveness_data(liveness_ptr); + } + + unsafe { + drop(Box::from_raw(error_out)); + } + } +} diff --git a/base_layer/chat_ffi/src/lib.rs b/base_layer/chat_ffi/src/lib.rs index 4f367c5d47..3c12627ce1 100644 --- a/base_layer/chat_ffi/src/lib.rs +++ b/base_layer/chat_ffi/src/lib.rs @@ -28,7 +28,7 @@ use callback_handler::CallbackContactStatusChange; use libc::c_int; use log::info; use minotari_app_utilities::identity_management::setup_node_identity; -use tari_chat_client::{config::ApplicationConfig, networking::PeerFeatures, ChatClient, Client}; +use tari_chat_client::{config::ApplicationConfig, networking::PeerFeatures, ChatClient as ChatClientTrait, Client}; use tokio::runtime::Runtime; use crate::{ @@ -43,17 +43,18 @@ use crate::{ }; mod application_config; +mod byte_vector; mod callback_handler; mod confirmation; mod contacts; +mod contacts_liveness_data; mod error; mod logging; mod message; mod message_metadata; -mod read_receipt; +mod messages; mod tansport_config; mod tari_address; -mod types; const LOG_TARGET: &str = "chat_ffi"; @@ -62,7 +63,7 @@ mod consts { include!(concat!(env!("OUT_DIR"), "/consts.rs")); } -pub struct ChatClientFFI { +pub struct ChatClient { client: Client, runtime: Runtime, } @@ -71,15 +72,22 @@ pub struct ChatClientFFI { /// /// ## Arguments /// `config` - The ApplicationConfig pointer -/// `identity_file_path` - The path to the node identity file /// `error_out` - Pointer to an int which will be modified +/// `callback_contact_status_change` - A callback function pointer. this is called whenever a +/// contacts liveness event comes in. +/// `callback_message_received` - A callback function pointer. This is called whenever a chat +/// message is received. +/// `callback_delivery_confirmation_received` - A callback function pointer. This is called when the +/// client receives a confirmation of message delivery. +/// `callback_read_confirmation_received` - A callback function pointer. This is called when the +/// client receives a confirmation of message read. /// /// ## Returns /// `*mut ChatClient` - Returns a pointer to a ChatClient, note that it returns ptr::null_mut() /// if any error was encountered or if the runtime could not be created. /// /// # Safety -/// The ```destroy_client``` method must be called when finished with a ClientFFI to prevent a memory leak +/// The ```destroy_chat_client``` method must be called when finished with a ClientFFI to prevent a memory leak #[no_mangle] pub unsafe extern "C" fn create_chat_client( config: *mut ApplicationConfig, @@ -88,7 +96,7 @@ pub unsafe extern "C" fn create_chat_client( callback_message_received: CallbackMessageReceived, callback_delivery_confirmation_received: CallbackDeliveryConfirmationReceived, callback_read_confirmation_received: CallbackReadConfirmationReceived, -) -> *mut ChatClientFFI { +) -> *mut ChatClient { let mut error = 0; ptr::swap(error_out, &mut error as *mut c_int); @@ -154,15 +162,15 @@ pub unsafe extern "C" fn create_chat_client( callback_handler.start().await; }); - let client_ffi = ChatClientFFI { client, runtime }; + let client = ChatClient { client, runtime }; - Box::into_raw(Box::new(client_ffi)) + Box::into_raw(Box::new(client)) } -/// Frees memory for a ChatClientFFI +/// Frees memory for a ChatClient /// /// ## Arguments -/// `client` - The pointer of a ChatClientFFI +/// `ptr` - The pointer of a ChatClient /// /// ## Returns /// `()` - Does not return a value, equivalent to void in C @@ -170,9 +178,9 @@ pub unsafe extern "C" fn create_chat_client( /// # Safety /// None #[no_mangle] -pub unsafe extern "C" fn destroy_chat_client_ffi(client: *mut ChatClientFFI) { - if !client.is_null() { - let mut c = Box::from_raw(client); +pub unsafe extern "C" fn destroy_chat_client(ptr: *mut ChatClient) { + if !ptr.is_null() { + let mut c = Box::from_raw(ptr); c.client.shutdown(); } } diff --git a/base_layer/chat_ffi/src/message.rs b/base_layer/chat_ffi/src/message.rs index c9ba48046b..691828f451 100644 --- a/base_layer/chat_ffi/src/message.rs +++ b/base_layer/chat_ffi/src/message.rs @@ -22,25 +22,23 @@ use std::{convert::TryFrom, ffi::CStr, ptr}; -use libc::{c_char, c_int}; -use tari_chat_client::ChatClient; +use libc::{c_char, c_int, c_longlong, c_uint, c_ulonglong}; +use tari_chat_client::ChatClient as ChatClientTrait; use tari_common_types::tari_address::TariAddress; -use tari_contacts::contacts_service::{ - handle::{DEFAULT_MESSAGE_LIMIT, DEFAULT_MESSAGE_PAGE}, - types::{Message, MessageBuilder}, -}; +use tari_contacts::contacts_service::types::{Message, MessageBuilder, MessageMetadata}; +use tari_utilities::ByteArray; use crate::{ + byte_vector::{chat_byte_vector_create, ChatByteVector}, error::{InterfaceError, LibChatError}, - types::ChatMessages, - ChatClientFFI, + ChatClient, }; -/// Creates a message and returns a ptr to it +/// Creates a message and returns a pointer to it /// /// ## Arguments -/// `receiver` - A string containing a tari address -/// `message` - The peer seeds config for the node +/// `receiver` - A pointer to a TariAddress +/// `message` - A string to send as a text message /// `error_out` - Pointer to an int which will be modified /// /// ## Returns @@ -48,6 +46,7 @@ use crate::{ /// /// # Safety /// The ```receiver``` should be destroyed after use +/// The ```Message``` received should be destroyed after use #[no_mangle] pub unsafe extern "C" fn create_chat_message( receiver: *mut TariAddress, @@ -79,10 +78,10 @@ pub unsafe extern "C" fn create_chat_message( Box::into_raw(Box::new(message_out)) } -/// Frees memory for message +/// Frees memory for Message /// /// ## Arguments -/// `messages_ptr` - The pointer of a Message +/// `ptr` - The pointer of a Message /// /// ## Returns /// `()` - Does not return a value, equivalent to void in C @@ -90,16 +89,16 @@ pub unsafe extern "C" fn create_chat_message( /// # Safety /// None #[no_mangle] -pub unsafe extern "C" fn destroy_chat_message(messages_ptr: *mut Message) { - if !messages_ptr.is_null() { - drop(Box::from_raw(messages_ptr)) +pub unsafe extern "C" fn destroy_chat_message(ptr: *mut Message) { + if !ptr.is_null() { + drop(Box::from_raw(ptr)) } } /// Sends a message over a client /// /// ## Arguments -/// `client` - The Client pointer +/// `client` - The ChatClient pointer /// `message` - Pointer to a Message struct /// `error_out` - Pointer to an int which will be modified /// @@ -109,7 +108,7 @@ pub unsafe extern "C" fn destroy_chat_message(messages_ptr: *mut Message) { /// # Safety /// The ```message``` should be destroyed after use #[no_mangle] -pub unsafe extern "C" fn send_chat_message(client: *mut ChatClientFFI, message: *mut Message, error_out: *mut c_int) { +pub unsafe extern "C" fn send_chat_message(client: *mut ChatClient, message: *mut Message, error_out: *mut c_int) { let mut error = 0; ptr::swap(error_out, &mut error as *mut c_int); @@ -128,68 +127,446 @@ pub unsafe extern "C" fn send_chat_message(client: *mut ChatClientFFI, message: .block_on((*client).client.send_message((*message).clone())); } -/// Get a ptr to all messages from or to address +/// Reads the message metadata of a message and returns a ptr to the metadata at the given position /// /// ## Arguments -/// `client` - The Client pointer -/// `address` - A TariAddress ptr -/// `limit` - The amount of messages you want to fetch. Default to 35, max 2500 -/// `page` - The page of results you'd like returned. Default to 0, maximum of u64 max +/// `message` - A pointer to a Message +/// `position` - The index of the array of metadata /// `error_out` - Pointer to an int which will be modified /// /// ## Returns -/// `()` - Does not return a value, equivalent to void in C +/// `*mut MessageMetadata` - A pointer to to MessageMetadata /// -/// # Safety -/// The ```address``` should be destroyed after use -/// The returned pointer to ```*mut ChatMessages``` should be destroyed after use +/// ## Safety +/// `message` should be destroyed eventually +/// the returned `MessageMetadata` should be destroyed eventually #[no_mangle] -pub unsafe extern "C" fn get_chat_messages( - client: *mut ChatClientFFI, - address: *mut TariAddress, - limit: c_int, - page: c_int, +pub unsafe extern "C" fn chat_metadata_get_at( + message: *mut Message, + position: c_uint, error_out: *mut c_int, -) -> *mut ChatMessages { +) -> *mut MessageMetadata { let mut error = 0; ptr::swap(error_out, &mut error as *mut c_int); - if client.is_null() { - error = LibChatError::from(InterfaceError::NullError("client".to_string())).code; + 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(); } - if address.is_null() { - error = LibChatError::from(InterfaceError::NullError("receiver".to_string())).code; + let message = &(*message); + + let len = message.metadata.len() - 1; + if position as usize > len { + error = LibChatError::from(InterfaceError::PositionInvalidError).code; ptr::swap(error_out, &mut error as *mut c_int); + return ptr::null_mut(); } - let mlimit = u64::try_from(limit).unwrap_or(DEFAULT_MESSAGE_LIMIT); - let mpage = u64::try_from(page).unwrap_or(DEFAULT_MESSAGE_PAGE); + let message_metadata_vec = &(*(message).metadata); + let message_metadata = Box::new(message_metadata_vec[position as usize].clone()); - let mut messages = Vec::new(); + Box::into_raw(message_metadata) +} - let mut retrieved_messages = (*client) - .runtime - .block_on((*client).client.get_messages(&*address, mlimit, mpage)); - messages.append(&mut retrieved_messages); +/// Returns the length of the Metadata Vector a chat Message contains +/// +/// ## Arguments +/// `message` - A pointer to a Message +/// `error_out` - Pointer to an int which will be modified +/// +/// ## Returns +/// `c_longlong` - The length of the metadata vector for a Message. May return -1 if something goes wrong +/// +/// ## Safety +/// `message` should be destroyed eventually +#[no_mangle] +pub unsafe extern "C" fn chat_message_metadata_len(message: *mut Message, error_out: *mut c_int) -> c_longlong { + 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 -1; + } - Box::into_raw(Box::new(ChatMessages(messages))) + let message = &(*message); + message.metadata.len() as c_longlong } -/// Frees memory for messages +/// Returns a pointer to a ChatByteVector representing the data of the Message /// /// ## Arguments -/// `ptr` - The pointer of a Message +/// `message` - A pointer to a Message +/// `error_out` - Pointer to an int which will be modified /// /// ## Returns -/// `()` - Does not return a value, equivalent to void in C +/// `*mut ChatByteVector` - A ptr to a ChatByteVector /// -/// # Safety -/// None +/// ## Safety +/// `message` should be destroyed eventually +/// the returned `ChatByteVector` should be destroyed eventually #[no_mangle] -pub unsafe extern "C" fn destroy_chat_messages(ptr: *mut ChatMessages) { - if !ptr.is_null() { - drop(Box::from_raw(ptr)) +pub unsafe extern "C" fn read_chat_message_body(message: *mut Message, error_out: *mut c_int) -> *mut ChatByteVector { + 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 data = (*message).body.clone(); + let data_bytes = data.as_bytes(); + let len = match c_uint::try_from(data_bytes.len()) { + Ok(num) => num, + Err(_e) => { + error = LibChatError::from(InterfaceError::AllocationError).code; + ptr::swap(error_out, &mut error as *mut c_int); + return ptr::null_mut(); + }, + }; + + chat_byte_vector_create(data_bytes.as_ptr(), len, error_out) +} + +/// Returns a pointer to a TariAddress +/// +/// ## Arguments +/// `message` - A pointer to a Message +/// `error_out` - Pointer to an int which will be modified +/// +/// ## Returns +/// `*mut TariAddress` - A ptr to a TariAddress +/// +/// ## Safety +/// `message` should be destroyed eventually +/// the returned `TariAddress` should be destroyed eventually +#[no_mangle] +pub unsafe extern "C" fn read_chat_message_address(message: *mut Message, error_out: *mut c_int) -> *mut TariAddress { + 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 address = (*message).address.clone(); + Box::into_raw(Box::new(address)) +} + +/// Returns a c_int representation of the Direction enum +/// +/// ## Arguments +/// `message` - A pointer to a Message +/// `error_out` - Pointer to an int which will be modified +/// +/// ## Returns +/// `c_int` - A c_int rep of the direction enum. May return -1 if anything goes wrong +/// 0 => Inbound +/// 1 => Outbound +/// +/// ## Safety +/// `message` should be destroyed eventually +#[no_mangle] +pub unsafe extern "C" fn read_chat_message_direction(message: *mut Message, error_out: *mut c_int) -> c_int { + 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 -1; + } + + c_int::try_from((*message).direction.as_byte()).unwrap_or(-1) +} + +/// Returns a c_ulonglong representation of the stored at timestamp as seconds since epoch +/// +/// ## Arguments +/// `message` - A pointer to a Message +/// `error_out` - Pointer to an int which will be modified +/// +/// ## Returns +/// `c_ulonglong` - The stored_at timestamp, seconds since epoch. Returns 0 if message is null. +/// +/// ## Safety +/// `message` should be destroyed eventually +#[no_mangle] +pub unsafe extern "C" fn read_chat_message_stored_at(message: *mut Message, error_out: *mut c_int) -> c_ulonglong { + 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 0; + } + + (*message).stored_at as c_ulonglong +} + +/// Returns a c_ulonglong representation of the delivery confirmation timestamp as seconds since epoch +/// +/// ## Arguments +/// `message` - A pointer to a Message +/// `error_out` - Pointer to an int which will be modified +/// +/// ## Returns +/// `c_ulonglong` - The delivery_confirmation_at timestamp, seconds since epoch. Returns 0 if message +/// is null or if no confirmation is stored. +/// +/// ## Safety +/// `message` should be destroyed eventually +#[no_mangle] +pub unsafe extern "C" fn read_chat_message_delivery_confirmation_at( + message: *mut Message, + error_out: *mut c_int, +) -> c_ulonglong { + 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 0; + } + + (*message).delivery_confirmation_at.unwrap_or(0) as c_ulonglong +} + +/// Returns a c_ulonglong representation of the read confirmation timestamp as seconds since epoch +/// +/// ## Arguments +/// `message` - A pointer to a Message +/// `error_out` - Pointer to an int which will be modified +/// +/// ## Returns +/// `c_ulonglong` - The read_confirmation_at timestamp, seconds since epoch. Returns 0 if message is +/// null or if no confirmation is stored. +/// +/// ## Safety +/// `message` should be destroyed eventually +#[no_mangle] +pub unsafe extern "C" fn read_chat_message_read_confirmation_at( + message: *mut Message, + error_out: *mut c_int, +) -> c_ulonglong { + 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 0; + } + + (*message).read_confirmation_at.unwrap_or(0) as c_ulonglong +} + +/// Returns a pointer to a ChatByteVector representation of the message_id +/// +/// ## Arguments +/// `message` - A pointer to a Message +/// `error_out` - Pointer to an int which will be modified +/// +/// ## Returns +/// `*mut ChatByteVector` - A ChatByteVector for the message id +/// +/// ## Safety +/// `message` should be destroyed eventually +/// The returned ```ChatByteVector``` should be destroyed eventually +#[no_mangle] +pub unsafe extern "C" fn read_chat_message_id(message: *mut Message, error_out: *mut c_int) -> *mut ChatByteVector { + 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 data_bytes = (*message).message_id.clone(); + let len = match c_uint::try_from(data_bytes.len()) { + Ok(num) => num, + Err(_e) => { + error = LibChatError::from(InterfaceError::PositionInvalidError).code; + ptr::swap(error_out, &mut error as *mut c_int); + return ptr::null_mut(); + }, + }; + chat_byte_vector_create(data_bytes.as_ptr(), len as c_uint, error_out) +} + +#[cfg(test)] +mod test { + use tari_contacts::contacts_service::types::{Direction, MessageBuilder}; + use tari_utilities::epoch_time::EpochTime; + + use super::*; + use crate::{ + byte_vector::{chat_byte_vector_destroy, chat_byte_vector_get_at, chat_byte_vector_get_length}, + message::read_chat_message_id, + tari_address::destroy_tari_address, + }; + + #[test] + fn test_reading_message_id() { + let message = MessageBuilder::new().build(); + + let message_ptr = Box::into_raw(Box::new(message.clone())); + let error_out = Box::into_raw(Box::new(0)); + + unsafe { + let message_byte_vector = read_chat_message_id(message_ptr, error_out); + let len = chat_byte_vector_get_length(message_byte_vector, error_out); + + let mut message_id = vec![]; + for i in 0..len { + message_id.push(chat_byte_vector_get_at(message_byte_vector, i, error_out)); + } + + assert_eq!(message.message_id, message_id); + + destroy_chat_message(message_ptr); + chat_byte_vector_destroy(message_byte_vector); + drop(Box::from_raw(error_out)); + } + } + + #[test] + fn test_reading_message_body() { + let body = "Hey there!"; + let body_bytes = body.as_bytes(); + let message = MessageBuilder::new().message(body.into()).build(); + + let message_ptr = Box::into_raw(Box::new(message)); + let error_out = Box::into_raw(Box::new(0)); + + unsafe { + let message_byte_vector = read_chat_message_body(message_ptr, error_out); + let len = chat_byte_vector_get_length(message_byte_vector, error_out); + + let mut message_body = vec![]; + for i in 0..len { + message_body.push(chat_byte_vector_get_at(message_byte_vector, i, error_out)); + } + + assert_eq!(body_bytes, message_body); + + destroy_chat_message(message_ptr); + chat_byte_vector_destroy(message_byte_vector); + drop(Box::from_raw(error_out)); + } + } + + #[test] + fn test_reading_message_address() { + let address = + TariAddress::from_hex("0c017c5cd01385f34ac065e3b05948326dc55d2494f120c6f459a07389011b4ec1").unwrap(); + let message = MessageBuilder::new().address(address.clone()).build(); + + let message_ptr = Box::into_raw(Box::new(message)); + let error_out = Box::into_raw(Box::new(0)); + + unsafe { + let address_ptr = read_chat_message_address(message_ptr, error_out); + + assert_eq!(address.to_bytes(), (*address_ptr).to_bytes()); + + destroy_chat_message(message_ptr); + destroy_tari_address(address_ptr); + drop(Box::from_raw(error_out)); + } + } + + #[test] + fn test_reading_message_direction() { + let error_out = Box::into_raw(Box::new(0)); + + unsafe { + let message = MessageBuilder::new().build(); + let message_ptr = Box::into_raw(Box::new(message)); + let direction = read_chat_message_direction(message_ptr, error_out); + assert_eq!(1, direction); // Default Outbound => 1 + destroy_chat_message(message_ptr); + }; + + unsafe { + let message = Message { + direction: Direction::Inbound, + ..Message::default() + }; + let message_ptr = Box::into_raw(Box::new(message)); + let direction = read_chat_message_direction(message_ptr, error_out); + assert_eq!(0, direction); // Default Inbound => 0 + destroy_chat_message(message_ptr); + }; + + unsafe { + drop(Box::from_raw(error_out)); + } + } + + #[test] + fn test_reading_message_timestamps() { + let error_out = Box::into_raw(Box::new(0)); + + unsafe { + let timestamp = EpochTime::now().as_u64(); + let message = Message { + stored_at: timestamp, + delivery_confirmation_at: None, + read_confirmation_at: None, + ..Message::default() + }; + + let message_ptr = Box::into_raw(Box::new(message)); + + let stored_at = read_chat_message_stored_at(message_ptr, error_out); + assert_eq!(timestamp, stored_at); + + let delivered_at = read_chat_message_delivery_confirmation_at(message_ptr, error_out); + assert_eq!(0, delivered_at); + + let read_at = read_chat_message_read_confirmation_at(message_ptr, error_out); + assert_eq!(0, read_at); + + destroy_chat_message(message_ptr); + }; + + unsafe { + let timestamp = EpochTime::now().as_u64(); + let message = Message { + stored_at: timestamp, + delivery_confirmation_at: Some(timestamp), + read_confirmation_at: Some(timestamp), + ..Message::default() + }; + + let message_ptr = Box::into_raw(Box::new(message)); + + let stored_at = read_chat_message_stored_at(message_ptr, error_out); + assert_eq!(timestamp, stored_at); + + let delivered_at = read_chat_message_delivery_confirmation_at(message_ptr, error_out); + assert_eq!(timestamp, delivered_at); + + let read_at = read_chat_message_read_confirmation_at(message_ptr, error_out); + assert_eq!(timestamp, read_at); + + destroy_chat_message(message_ptr); + }; + + unsafe { + drop(Box::from_raw(error_out)); + } } } diff --git a/base_layer/chat_ffi/src/message_metadata.rs b/base_layer/chat_ffi/src/message_metadata.rs index 2bcd44b9c0..68e4586c76 100644 --- a/base_layer/chat_ffi/src/message_metadata.rs +++ b/base_layer/chat_ffi/src/message_metadata.rs @@ -22,29 +22,23 @@ use std::{convert::TryFrom, ptr}; -use libc::{c_int, c_uint}; +use libc::{c_int, c_uchar, c_uint}; use tari_contacts::contacts_service::types::{Message, MessageMetadata, MessageMetadataType}; +use tari_utilities::ByteArray; use crate::{ + byte_vector::{chat_byte_vector_create, chat_byte_vector_get_at, chat_byte_vector_get_length, ChatByteVector}, 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: *mut ChatByteVector, - pub metadata_type: c_int, -} - /// Creates message metadata and appends it to a Message /// /// ## Arguments /// `message` - A pointer to a message -/// `metadata_type` - An int8 that maps to MessageMetadataType enum +/// `metadata_type` - An c_uchar that maps to MessageMetadataType enum /// '0' -> Reply /// '1' -> TokenRequest -/// `data` - contents for the metadata in string format +/// `data` - A pointer to a byte vector containing bytes for the data field /// `error_out` - Pointer to an int which will be modified /// /// ## Returns @@ -55,7 +49,7 @@ pub struct ChatFFIMessageMetadata { #[no_mangle] pub unsafe extern "C" fn add_chat_message_metadata( message: *mut Message, - metadata_type: c_int, + metadata_type: c_uchar, data: *mut ChatByteVector, error_out: *mut c_int, ) { @@ -68,16 +62,7 @@ pub unsafe extern "C" fn add_chat_message_metadata( return; } - let metadata_byte = match u8::try_from(metadata_type) { - Ok(byte) => byte, - Err(e) => { - error = LibChatError::from(InterfaceError::InvalidArgument(e.to_string())).code; - ptr::swap(error_out, &mut error as *mut c_int); - return; - }, - }; - - let metadata_type = match MessageMetadataType::from_byte(metadata_byte) { + let metadata_type = match MessageMetadataType::from_byte(metadata_type) { Some(t) => t, None => { error = LibChatError::from(InterfaceError::InvalidArgument( @@ -110,68 +95,21 @@ pub unsafe extern "C" fn add_chat_message_metadata( (*message).push(metadata); } -/// Reads the message metadata of a message and returns a ptr to the metadata at the given position -/// -/// ## Arguments -/// `message` - A pointer to a message -/// `position` - The index of the array of metadata -/// `error_out` - Pointer to an int which will be modified -/// -/// ## Returns -/// `()` - Does not return a value, equivalent to void in C -/// -/// ## Safety -/// `message` should be destroyed eventually -/// the returned `ChatFFIMessageMetadata` should be destroyed eventually -#[no_mangle] -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(); - } - - let md_vec = &(*(message).metadata); - - let md = Box::new(md_vec.0[len as usize].clone()); - - Box::into_raw(md) -} - -/// Returns the enum int representation of a metadata type +/// Returns the c_int representation of a metadata type enum /// /// ## Arguments -/// `msg_metadata` - A pointer to a message metadat +/// `msg_metadata` - A pointer to a MessageMetadata /// `error_out` - Pointer to an int which will be modified /// /// ## Returns -/// `metadata_type` - An int8 that maps to MessageMetadataType enum +/// `c_int` - An int8 that maps to MessageMetadataType enum. May return -1 if something goes wrong /// '0' -> Reply /// '1' -> TokenRequest /// /// ## Safety /// `msg_metadata` should be destroyed eventually #[no_mangle] -pub unsafe extern "C" fn read_chat_metadata_type( - msg_metadata: *mut ChatFFIMessageMetadata, - error_out: *mut c_int, -) -> c_int { +pub unsafe extern "C" fn read_chat_metadata_type(msg_metadata: *mut MessageMetadata, error_out: *mut c_int) -> c_int { let mut error = 0; ptr::swap(error_out, &mut error as *mut c_int); @@ -182,26 +120,24 @@ pub unsafe extern "C" fn read_chat_metadata_type( } let md = &(*msg_metadata); - md.metadata_type + c_int::try_from(md.metadata_type.as_byte()).unwrap_or(-1) } /// Returns a ptr to a ByteVector /// /// ## Arguments -/// `msg_metadata` - A pointer to a message metadata +/// `msg_metadata` - A pointer to a MessageMetadata /// `error_out` - Pointer to an int which will be modified /// /// ## Returns -/// `*mut ` - An int8 that maps to MessageMetadataType enum -/// '0' -> Reply -/// '1' -> TokenRequest +/// `*mut ChatByteVector` - A ptr to a ChatByteVector /// /// ## Safety /// `msg_metadata` should be destroyed eventually /// the returned `ChatByteVector` should be destroyed eventually #[no_mangle] pub unsafe extern "C" fn read_chat_metadata_data( - msg_metadata: *mut ChatFFIMessageMetadata, + msg_metadata: *mut MessageMetadata, error_out: *mut c_int, ) -> *mut ChatByteVector { let mut error = 0; @@ -213,19 +149,50 @@ pub unsafe extern "C" fn read_chat_metadata_data( return ptr::null_mut(); } - (*msg_metadata).data + let data = (*msg_metadata).data.clone(); + let data_bytes = data.as_bytes(); + let len = match c_uint::try_from(data_bytes.len()) { + Ok(num) => num, + Err(_e) => { + error = LibChatError::from(InterfaceError::PositionInvalidError).code; + ptr::swap(error_out, &mut error as *mut c_int); + return ptr::null_mut(); + }, + }; + + chat_byte_vector_create(data_bytes.as_ptr(), len, error_out) +} + +/// Frees memory for MessageMetadata +/// +/// ## Arguments +/// `ptr` - The pointer of a MessageMetadata +/// +/// ## Returns +/// `()` - Does not return a value, equivalent to void in C +/// +/// # Safety +/// None +#[no_mangle] +pub unsafe extern "C" fn destroy_chat_message_metadata(ptr: *mut MessageMetadata) { + if !ptr.is_null() { + drop(Box::from_raw(ptr)) + } } #[cfg(test)] mod test { use std::convert::TryFrom; - use libc::{c_int, c_uint}; + use libc::c_uint; use tari_common_types::tari_address::TariAddress; use tari_contacts::contacts_service::types::MessageBuilder; use super::*; - use crate::types::{chat_byte_vector_create, ChatFFIMessage}; + use crate::{ + byte_vector::{chat_byte_vector_create, chat_byte_vector_destroy}, + message::{chat_metadata_get_at, destroy_chat_message}, + }; #[test] fn test_metadata_adding() { @@ -237,11 +204,16 @@ mod test { 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) }; - unsafe { add_chat_message_metadata(message_ptr, 0 as c_int, data, error_out) } + unsafe { add_chat_message_metadata(message_ptr, 0, 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); + + unsafe { + chat_byte_vector_destroy(data); + drop(Box::from_raw(error_out)); + } } #[test] @@ -256,19 +228,15 @@ mod test { let error_out = Box::into_raw(Box::new(0)); unsafe { - let data = "hello".to_string(); + let data = "metadata".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 md_type = 0 as c_int; + let md_type = 0; add_chat_message_metadata(message_ptr, md_type, 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_ptr = read_chat_metadata_at_position(chat_ffi_msg_ptr, 0, error_out); + let metadata_ptr = chat_metadata_get_at(message_ptr, 0, error_out); let metadata_type = read_chat_metadata_type(metadata_ptr, error_out); let metadata_byte_vector = read_chat_metadata_data(metadata_ptr, error_out); @@ -279,8 +247,13 @@ mod test { metadata_data.push(chat_byte_vector_get_at(metadata_byte_vector, i, error_out)); } - assert_eq!(metadata_type, md_type); + assert_eq!(metadata_type, i32::from(md_type)); assert_eq!(metadata_data, data_bytes); + + destroy_chat_message_metadata(metadata_ptr); + destroy_chat_message(message_ptr); + chat_byte_vector_destroy(metadata_byte_vector); + drop(Box::from_raw(error_out)); } } } diff --git a/base_layer/chat_ffi/src/messages.rs b/base_layer/chat_ffi/src/messages.rs new file mode 100644 index 0000000000..57c25b8097 --- /dev/null +++ b/base_layer/chat_ffi/src/messages.rs @@ -0,0 +1,217 @@ +// 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_chat_client::ChatClient as ChatClientTrait; +use tari_common_types::tari_address::TariAddress; +use tari_contacts::contacts_service::{ + handle::{DEFAULT_MESSAGE_LIMIT, DEFAULT_MESSAGE_PAGE}, + types::Message, +}; + +use crate::{ + error::{InterfaceError, LibChatError}, + ChatClient, +}; + +#[derive(Clone)] +pub struct MessageVector(pub Vec); + +/// Get a ptr to all messages from or to an address +/// +/// ## Arguments +/// `client` - The ChatClient pointer +/// `address` - A TariAddress pointer +/// `limit` - The amount of messages you want to fetch. Default to 35, max 2500 +/// `page` - The page of results you'd like returned. Default to 0, maximum of u64 max +/// `error_out` - Pointer to an int which will be modified +/// +/// ## Returns +/// `*mut MessageVector` - A pointer to a Vector of Messages +/// +/// # Safety +/// The returned pointer to ```MessageVector``` should be destroyed after use +/// ```client``` should be destroyed after use +/// ```address``` should be destroyed after use +#[no_mangle] +pub unsafe extern "C" fn get_chat_messages( + client: *mut ChatClient, + address: *mut TariAddress, + limit: c_int, + page: c_int, + error_out: *mut c_int, +) -> *mut MessageVector { + let mut error = 0; + ptr::swap(error_out, &mut error as *mut c_int); + + if client.is_null() { + error = LibChatError::from(InterfaceError::NullError("client".to_string())).code; + ptr::swap(error_out, &mut error as *mut c_int); + } + + if address.is_null() { + error = LibChatError::from(InterfaceError::NullError("address".to_string())).code; + 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 messages = (*client) + .runtime + .block_on((*client).client.get_messages(&*address, mlimit, mpage)); + + Box::into_raw(Box::new(MessageVector(messages))) +} + +/// Returns the length of the MessageVector +/// +/// ## Arguments +/// `messages` - A pointer to a MessageVector +/// `error_out` - Pointer to an int which will be modified +/// +/// ## Returns +/// `c_int` - The length of the metadata vector for a Message. May return -1 if something goes wrong +/// +/// ## Safety +/// `messages` should be destroyed eventually +#[no_mangle] +pub unsafe extern "C" fn message_vector_len(messages: *mut MessageVector, error_out: *mut c_int) -> c_int { + let mut error = 0; + ptr::swap(error_out, &mut error as *mut c_int); + + if messages.is_null() { + error = LibChatError::from(InterfaceError::NullError("message".to_string())).code; + ptr::swap(error_out, &mut error as *mut c_int); + return -1; + } + + let messages = &(*messages); + c_int::try_from(messages.0.len()).unwrap_or(-1) +} + +/// Reads the MessageVector and returns a Message at a given position +/// +/// ## Arguments +/// `messages` - A pointer to a MessageVector +/// `position` - The index of the vector for a Message +/// `error_out` - Pointer to an int which will be modified +/// +/// ## Returns +/// `*mut ptr Message` - A pointer to a Message +/// +/// ## Safety +/// `messages` should be destroyed eventually +/// the returned `Message` should be destroyed eventually +#[no_mangle] +pub unsafe extern "C" fn message_vector_get_at( + messages: *mut MessageVector, + position: c_uint, + error_out: *mut c_int, +) -> *mut Message { + let mut error = 0; + ptr::swap(error_out, &mut error as *mut c_int); + + if messages.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 messages = &(*messages); + + let len = messages.0.len() - 1; + if position as usize > len { + 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(messages.0[position as usize].clone())) +} + +/// Frees memory for MessagesVector +/// +/// ## Arguments +/// `ptr` - The pointer of a MessagesVector +/// +/// ## Returns +/// `()` - Does not return a value, equivalent to void in C +/// +/// # Safety +/// None +#[no_mangle] +pub unsafe extern "C" fn destroy_message_vector(ptr: *mut MessageVector) { + if !ptr.is_null() { + drop(Box::from_raw(ptr)) + } +} + +#[cfg(test)] +mod test { + use tari_contacts::contacts_service::types::MessageBuilder; + + use super::*; + use crate::{ + byte_vector::{chat_byte_vector_destroy, chat_byte_vector_get_at, chat_byte_vector_get_length}, + message::{destroy_chat_message, read_chat_message_id}, + }; + + #[test] + fn test_retrieving_messages_from_vector() { + let m = MessageBuilder::new().message("hello 2".to_string()).build(); + let messages = MessageVector(vec![ + MessageBuilder::new().message("hello 0".to_string()).build(), + MessageBuilder::new().message("hello 1".to_string()).build(), + m.clone(), + MessageBuilder::new().message("hello 3".to_string()).build(), + MessageBuilder::new().message("hello 4".to_string()).build(), + ]); + + let messages_len = messages.0.len(); + let message_vector_ptr = Box::into_raw(Box::new(messages)); + let error_out = Box::into_raw(Box::new(0)); + + unsafe { + let message_vector_len = message_vector_len(message_vector_ptr, error_out); + assert_eq!(message_vector_len as usize, messages_len); + + let message_ptr = message_vector_get_at(message_vector_ptr, 2, error_out); + let message_byte_vector = read_chat_message_id(message_ptr, error_out); + let len = chat_byte_vector_get_length(message_byte_vector, error_out); + + let mut message_id = vec![]; + for i in 0..len { + message_id.push(chat_byte_vector_get_at(message_byte_vector, i, error_out)); + } + + assert_eq!(m.message_id, message_id); + + destroy_message_vector(message_vector_ptr); + destroy_chat_message(message_ptr); + chat_byte_vector_destroy(message_byte_vector); + drop(Box::from_raw(error_out)); + } + } +} diff --git a/base_layer/chat_ffi/src/read_receipt.rs b/base_layer/chat_ffi/src/read_receipt.rs deleted file mode 100644 index 55ae263958..0000000000 --- a/base_layer/chat_ffi/src/read_receipt.rs +++ /dev/null @@ -1,69 +0,0 @@ -// 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; - -use libc::c_int; -use tari_chat_client::ChatClient; -use tari_contacts::contacts_service::types::Message; - -use crate::{ - error::{InterfaceError, LibChatError}, - ChatClientFFI, -}; - -/// 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 -#[no_mangle] -pub unsafe extern "C" fn send_read_confirmation_for_message( - client: *mut ChatClientFFI, - message: *mut Message, - error_out: *mut c_int, -) { - let mut error = 0; - ptr::swap(error_out, &mut error as *mut c_int); - - if client.is_null() { - error = LibChatError::from(InterfaceError::NullError("client".to_string())).code; - 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); - } - - (*client) - .runtime - .block_on((*client).client.send_read_receipt((*message).clone())); -} diff --git a/base_layer/chat_ffi/src/tansport_config.rs b/base_layer/chat_ffi/src/tansport_config.rs index 280fb4efee..4aff9223c3 100644 --- a/base_layer/chat_ffi/src/tansport_config.rs +++ b/base_layer/chat_ffi/src/tansport_config.rs @@ -27,8 +27,8 @@ use tari_p2p::{SocksAuthentication, TorControlAuthentication, TorTransportConfig use tari_utilities::hex; use crate::{ + byte_vector::ChatByteVector, error::{InterfaceError, LibChatError}, - types::ChatByteVector, }; /// Creates a tor transport config @@ -160,7 +160,7 @@ pub unsafe extern "C" fn create_chat_tor_transport_config( /// Frees memory for a TransportConfig /// /// ## Arguments -/// `transport` - The pointer to a TransportConfig +/// `ptr` - The pointer to a TransportConfig /// /// ## Returns /// `()` - Does not return a value, equivalent to void in C @@ -168,9 +168,9 @@ pub unsafe extern "C" fn create_chat_tor_transport_config( /// # Safety /// None #[no_mangle] -pub unsafe extern "C" fn destroy_chat_tor_transport_config(transport: *mut TransportConfig) { - if !transport.is_null() { - drop(Box::from_raw(transport)) +pub unsafe extern "C" fn destroy_chat_tor_transport_config(ptr: *mut TransportConfig) { + if !ptr.is_null() { + drop(Box::from_raw(ptr)) } } diff --git a/base_layer/chat_ffi/src/types/chat_ffi_contacts_liveness_data.rs b/base_layer/chat_ffi/src/types/chat_ffi_contacts_liveness_data.rs deleted file mode 100644 index f411819b89..0000000000 --- a/base_layer/chat_ffi/src/types/chat_ffi_contacts_liveness_data.rs +++ /dev/null @@ -1,75 +0,0 @@ -// 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, ffi::CString}; - -use libc::c_char; -use tari_contacts::contacts_service::handle::ContactsLivenessData; - -#[repr(C)] -pub struct ChatFFIContactsLivenessData { - pub address: *const c_char, - pub last_seen: u64, - pub online_status: u8, -} - -impl TryFrom for ChatFFIContactsLivenessData { - type Error = String; - - fn try_from(v: ContactsLivenessData) -> Result { - let address = match CString::new(v.address().to_bytes()) { - Ok(s) => s, - Err(e) => return Err(e.to_string()), - }; - - let last_seen = match v.last_ping_pong_received() { - Some(ts) => match u64::try_from(ts.timestamp_micros()) { - Ok(num) => num, - Err(e) => return Err(e.to_string()), - }, - None => 0, - }; - - Ok(Self { - address: address.as_ptr(), - last_seen, - online_status: v.online_status().as_u8(), - }) - } -} - -/// Frees memory for a ChatFFIContactsLivenessData -/// -/// ## Arguments -/// `address` - The pointer of a ChatFFIContactsLivenessData -/// -/// ## Returns -/// `()` - Does not return a value, equivalent to void in C -/// -/// # Safety -/// None -#[no_mangle] -pub unsafe extern "C" fn destroy_chat_ffi_liveness_data(address: *mut ChatFFIContactsLivenessData) { - if !address.is_null() { - drop(Box::from_raw(address)) - } -} diff --git a/base_layer/chat_ffi/src/types/chat_ffi_message.rs b/base_layer/chat_ffi/src/types/chat_ffi_message.rs deleted file mode 100644 index a9a8ade771..0000000000 --- a/base_layer/chat_ffi/src/types/chat_ffi_message.rs +++ /dev/null @@ -1,123 +0,0 @@ -// 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, ffi::CString}; - -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: *mut ChatMessageMetadataVector, - pub metadata_len: c_int, -} - -impl TryFrom for ChatFFIMessage { - type Error = String; - - fn try_from(v: Message) -> Result { - let body = match CString::new(v.body) { - Ok(s) => s, - Err(e) => return Err(e.to_string()), - }; - - let address = match CString::new(v.address.to_hex()) { - Ok(s) => s, - Err(e) => return Err(e.to_string()), - }; - - let id = match CString::new(v.message_id) { - Ok(s) => s, - Err(e) => return Err(e.to_string()), - }; - - let mut chat_message_metadata_bytes = vec![]; - for md in v.metadata.clone() { - let data_ptr = Box::into_raw(Box::new(ChatByteVector( - md.data.clone().into_iter().map(|f| f as c_uchar).collect(), - ))); - chat_message_metadata_bytes.push(ChatFFIMessageMetadata { - data: data_ptr, - 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()), - }; - - let msg_md = Box::into_raw(Box::new(ChatMessageMetadataVector(chat_message_metadata_bytes))); - - Ok(Self { - body: body.as_ptr(), - from_address: address.as_ptr(), - stored_at: v.stored_at, - message_id: id.as_ptr(), - metadata: msg_md, - metadata_len: metadata_length, - }) - } -} - -/// Frees memory for a ChatFFIMessage -/// -/// ## Arguments -/// `address` - The pointer to a ChatFFIMessage -/// -/// ## Returns -/// `()` - Does not return a value, equivalent to void in C -/// -/// # Safety -/// None -#[no_mangle] -pub unsafe extern "C" fn destroy_chat_ffi_message(address: *mut ChatFFIMessage) { - if !address.is_null() { - drop(Box::from_raw(address)) - } -} - -/// Frees memory for a ChatMessageMetadataVector -/// -/// ## Arguments -/// `address` - The pointer to a ChatMessageMetadataVector -/// -/// ## Returns -/// `()` - Does not return a value, equivalent to void in C -/// -/// # Safety -/// None -#[no_mangle] -pub unsafe extern "C" fn destroy_chat_message_metadata_vector(address: *mut ChatMessageMetadataVector) { - if !address.is_null() { - drop(Box::from_raw(address)) - } -} diff --git a/base_layer/chat_ffi/src/types/mod.rs b/base_layer/chat_ffi/src/types/mod.rs deleted file mode 100644 index 6a608f86cd..0000000000 --- a/base_layer/chat_ffi/src/types/mod.rs +++ /dev/null @@ -1,35 +0,0 @@ -// 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. -mod chat_ffi_contacts_liveness_data; -pub use chat_ffi_contacts_liveness_data::{destroy_chat_ffi_liveness_data, ChatFFIContactsLivenessData}; -mod chat_ffi_message; -pub use chat_ffi_message::{destroy_chat_ffi_message, ChatFFIMessage}; -mod wrappers; -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 deleted file mode 100644 index c08a8c56d7..0000000000 --- a/base_layer/chat_ffi/src/types/wrappers.rs +++ /dev/null @@ -1,33 +0,0 @@ -// 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 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/base_layer/contacts/examples/chat_client/Cargo.toml b/base_layer/contacts/src/chat_client/Cargo.toml similarity index 100% rename from base_layer/contacts/examples/chat_client/Cargo.toml rename to base_layer/contacts/src/chat_client/Cargo.toml diff --git a/base_layer/contacts/examples/chat_client/README.md b/base_layer/contacts/src/chat_client/README.md similarity index 100% rename from base_layer/contacts/examples/chat_client/README.md rename to base_layer/contacts/src/chat_client/README.md diff --git a/base_layer/contacts/examples/chat_client/src/client.rs b/base_layer/contacts/src/chat_client/src/client.rs similarity index 100% rename from base_layer/contacts/examples/chat_client/src/client.rs rename to base_layer/contacts/src/chat_client/src/client.rs diff --git a/base_layer/contacts/examples/chat_client/src/config.rs b/base_layer/contacts/src/chat_client/src/config.rs similarity index 100% rename from base_layer/contacts/examples/chat_client/src/config.rs rename to base_layer/contacts/src/chat_client/src/config.rs diff --git a/base_layer/contacts/examples/chat_client/src/database.rs b/base_layer/contacts/src/chat_client/src/database.rs similarity index 100% rename from base_layer/contacts/examples/chat_client/src/database.rs rename to base_layer/contacts/src/chat_client/src/database.rs diff --git a/base_layer/contacts/examples/chat_client/src/lib.rs b/base_layer/contacts/src/chat_client/src/lib.rs similarity index 100% rename from base_layer/contacts/examples/chat_client/src/lib.rs rename to base_layer/contacts/src/chat_client/src/lib.rs diff --git a/base_layer/contacts/examples/chat_client/src/networking.rs b/base_layer/contacts/src/chat_client/src/networking.rs similarity index 76% rename from base_layer/contacts/examples/chat_client/src/networking.rs rename to base_layer/contacts/src/chat_client/src/networking.rs index 30cc5808dd..10d7eaaacc 100644 --- a/base_layer/contacts/examples/chat_client/src/networking.rs +++ b/base_layer/contacts/src/chat_client/src/networking.rs @@ -22,6 +22,8 @@ use std::{str::FromStr, sync::Arc, time::Duration}; +use minotari_app_utilities::{identity_management, identity_management::load_from_json}; +use tari_common::exit_codes::{ExitCode, ExitError}; // Re-exports pub use tari_comms::{ multiaddr::Multiaddr, @@ -34,6 +36,7 @@ use tari_p2p::{ initialization::{spawn_comms_using_transport, P2pInitializer}, peer_seeds::SeedPeer, services::liveness::{LivenessConfig, LivenessInitializer}, + TransportType, }; use tari_service_framework::StackBuilder; use tari_shutdown::ShutdownSignal; @@ -54,9 +57,15 @@ pub async fn start( let (publisher, subscription_factory) = pubsub_connector(100); let in_msg = Arc::new(subscription_factory); + let mut p2p_config = config.chat_client.p2p.clone(); + + let tor_identity = + load_from_json(&config.chat_client.tor_identity_file).map_err(|e| ExitError::new(ExitCode::ConfigError, e))?; + p2p_config.transport.tor.identity = tor_identity; + let fut = StackBuilder::new(shutdown_signal) .add_initializer(P2pInitializer::new( - config.chat_client.p2p.clone(), + p2p_config.clone(), config.peer_seeds.clone(), config.chat_client.network, node_identity, @@ -97,9 +106,23 @@ pub async fn start( peer_manager.add_peer(peer).await?; } - let comms = spawn_comms_using_transport(comms, config.chat_client.p2p.transport.clone()) + let comms = spawn_comms_using_transport(comms, p2p_config.transport.clone()) .await .unwrap(); + + // Save final node identity after comms has initialized. This is required because the public_address can be + // changed by comms during initialization when using tor. + match p2p_config.transport.transport_type { + TransportType::Tcp => {}, // Do not overwrite TCP public_address in the base_node_id! + _ => { + identity_management::save_as_json(&config.chat_client.identity_file, &*comms.node_identity()) + .map_err(|e| ExitError::new(ExitCode::IdentityError, e))?; + }, + }; + if let Some(hs) = comms.hidden_service() { + identity_management::save_as_json(&config.chat_client.tor_identity_file, hs.tor_identity()) + .map_err(|e| ExitError::new(ExitCode::IdentityError, e))?; + } handles.register(comms); let comms = handles.expect_handle::(); diff --git a/integration_tests/Cargo.toml b/integration_tests/Cargo.toml index 8dc78ca48b..05cd737fcd 100644 --- a/integration_tests/Cargo.toml +++ b/integration_tests/Cargo.toml @@ -11,7 +11,7 @@ minotari_app_grpc = { path = "../applications/minotari_app_grpc" } minotari_app_utilities = { path = "../applications/minotari_app_utilities" } minotari_node = { path = "../applications/minotari_node" } minotari_node_grpc_client = { path = "../clients/rust/base_node_grpc_client" } -tari_chat_client = { path = "../base_layer/contacts/examples/chat_client" } +tari_chat_client = { path = "../base_layer/contacts/src/chat_client" } minotari_chat_ffi = { path = "../base_layer/chat_ffi" } tari_crypto = { version = "0.18" } tari_common = { path = "../common" } diff --git a/integration_tests/src/chat_ffi.rs b/integration_tests/src/chat_ffi.rs index 03ea45ff50..bc17de7bb2 100644 --- a/integration_tests/src/chat_ffi.rs +++ b/integration_tests/src/chat_ffi.rs @@ -96,7 +96,7 @@ extern "C" { page: c_int, error_out: *const c_int, ) -> *mut c_void; - pub fn destroy_chat_client_ffi(client: *mut ClientFFI); + pub fn destroy_chat_client(client: *mut ClientFFI); pub fn chat_byte_vector_create( byte_array: *const c_uchar, element_count: c_uint, @@ -218,7 +218,7 @@ impl ChatClient for ChatFFI { fn shutdown(&mut self) { let client = self.ptr.lock().unwrap(); - unsafe { destroy_chat_client_ffi(client.0) } + unsafe { destroy_chat_client(client.0) } } }