diff --git a/build.rs b/build.rs index 0267d14..6e52ea4 100644 --- a/build.rs +++ b/build.rs @@ -15,9 +15,9 @@ fn main() { println!("cargo:rerun-if-changed=libCwtch.h"); let lib_cwtch_path = Path::new(&out_dir).join("libCwtch.so"); - // https://git.openprivacy.ca/cwtch.im/libcwtch-go/releases v1.5.4 + // https://git.openprivacy.ca/cwtch.im/libcwtch-go/releases v1.7.0 Command::new("wget") - .arg("https://git.openprivacy.ca/attachments/dd3c6b41-98e4-4e7b-81af-d21893bfe389") + .arg("https://git.openprivacy.ca/attachments/390d383b-ab02-489b-b5c9-e62267d8b3fd") .arg("-O") .arg(lib_cwtch_path) .output() @@ -29,5 +29,5 @@ fn main() { io::copy(&mut file, &mut hasher).expect("failed to copy file into hasher"); let hash_bytes = hasher.finalize(); - assert_eq!(hash_bytes[..], hex!("776a26076dfad3370d1b2edec9ad954187584f54483ec201163be0dc356c10b0fe74168e8e95f2116f458e5676e1fb07fbd0357cab1e4389ac762fe03bd5ef67")[..]); + assert_eq!(hash_bytes[..], hex!("271c281bad59696fc4ea5e559b5d3fe5c1949384c26dd891dde91b0af0a012e30bdbc3b16781ec5de795d2945e2f42415a8985451b49394b3c85c412ab4769d3")[..]); } diff --git a/libCwtch.h b/libCwtch.h index de34689..0730fb6 100644 --- a/libCwtch.h +++ b/libCwtch.h @@ -61,6 +61,7 @@ extern "C" { #endif extern int c_StartCwtch(char* dir_c, int len, char* tor_c, int torLen); +extern int c_Started(); extern void c_ReconnectCwtchForeground(); // A generic method for Rebroadcasting App Events from a UI @@ -86,11 +87,14 @@ extern char* c_GetMessageByID(char* profile_ptr, int profile_len, int conversati // the pointer returned from this function **must** be freed by calling c_Free extern char* c_GetMessagesByContentHash(char* profile_ptr, int profile_len, int conversation_id, char* contenthash_ptr, int contenthash_len); +// the pointer returned from this function **must** be Freed by c_Free +extern char* c_GetMessages(char* profile_ptr, int profile_len, int conversation_id, int message_index, int count); + // Dangerous function. Should only be used as documented in `MEMORY.md` extern void c_FreePointer(char* ptr); -extern void c_SendMessage(char* profile_ptr, int profile_len, int conversation_id, char* msg_ptr, int msg_len); -extern void c_SendInvitation(char* profile_ptr, int profile_len, int conversation_id, int target_id); -extern void c_ShareFile(char* profile_ptr, int profile_len, int conversation_id, char* filepath_ptr, int filepath_len); +extern char* c_SendMessage(char* profile_ptr, int profile_len, int conversation_id, char* msg_ptr, int msg_len); +extern char* c_SendInvitation(char* profile_ptr, int profile_len, int conversation_id, int target_id); +extern char* c_ShareFile(char* profile_ptr, int profile_len, int conversation_id, char* filepath_ptr, int filepath_len); extern void c_DownloadFile(char* profile_ptr, int profile_len, int conversation_id, char* filepath_ptr, int filepath_len, char* manifestpath_ptr, int manifestpath_len, char* filekey_ptr, int filekey_len); extern void c_CheckDownloadStatus(char* profilePtr, int profileLen, char* fileKeyPtr, int fileKeyLen); extern void c_VerifyOrResumeDownload(char* profile_ptr, int profile_len, int conversation_id, char* filekey_ptr, int filekey_len); @@ -101,9 +105,13 @@ extern void c_ArchiveConversation(char* profile_ptr, int profile_len, int conver extern void c_DeleteContact(char* profile_ptr, int profile_len, int conversation_id); extern void c_ImportBundle(char* profile_ptr, int profile_len, char* bundle_ptr, int bundle_len); extern void c_SetProfileAttribute(char* profile_ptr, int profile_len, char* key_ptr, int key_len, char* val_ptr, int val_len); +extern char* c_GetProfileAttribute(char* profile_ptr, int profile_len, char* key_ptr, int key_len); extern void c_SetConversationAttribute(char* profile_ptr, int profile_len, int conversation_id, char* key_ptr, int key_len, char* val_ptr, int val_len); +extern char* c_GetConversationAttribute(char* profile_ptr, int profile_len, int conversation_id, char* key_ptr, int key_len); extern void c_SetMessageAttribute(char* profile_ptr, int profile_len, int conversation_id, int channel_id, int message_id, char* key_ptr, int key_len, char* val_ptr, int val_len); extern void c_ChangePassword(char* profile_ptr, int profile_len, char* oldpassword_ptr, int oldpassword_len, char* newpassword_ptr, int newpassword_len, char* newpassword_again_ptr, int newpassword_again_len); +extern void c_ExportProfile(char* profile_ptr, int profile_len, char* file_ptr, int file_len); +extern char* c_ImportProfile(char* file_ptr, int file_len, char* passwordPtr, int passwordLen); extern void c_ShutdownCwtch(); extern void c_LoadServers(char* passwordPtr, int passwordLen); extern void c_CreateServer(char* passwordPtr, int passwordLen, char* descPtr, int descLen, char autostart); diff --git a/src/bindings_go.rs b/src/bindings_go.rs index ed1610d..2a36a1d 100644 --- a/src/bindings_go.rs +++ b/src/bindings_go.rs @@ -8,6 +8,7 @@ use std::ffi::CString; use super::CwtchLib; use crate::cwtchlib_go::bindings; +use crate::{CwtchError, structs::*}; struct c_str_wrap { raw: *mut i8, @@ -80,8 +81,16 @@ macro_rules! c_bind { pub struct CwtchLibGo {} +// Some bindings are going to be wrapped so we can handle their returns and give most rust idiomatic returns (esp for json returning apis) +// so we pre define the real binding here as a _helper function and in the impl for CwtchLib define the wrapper +impl CwtchLibGo { + c_bind!(_get_profile_attribute(profile: &str, key: &str;;) c_GetProfileAttribute -> String); + c_bind!(_get_conversation_attribute(profile: &str; conversation_id: i32; key: &str) c_GetConversationAttribute -> String); +} + impl CwtchLib for CwtchLibGo { c_bind!(start_cwtch(app_dir: &str, tor_path: &str;;) c_StartCwtch -> i32); + c_bind!(started(;;) c_Started -> i32); c_bind!(send_app_event(event_json: &str;;) c_SendAppEvent); c_bind!(send_profile_event(profile: &str, event_jason: &str;;) c_SendProfileEvent); c_bind!(get_appbus_event(;;) c_GetAppBusEvent -> String); @@ -90,12 +99,13 @@ impl CwtchLib for CwtchLibGo { c_bind!(accept_conversation(profile: &str ; conversation_id: i32 ;) c_AcceptConversation); c_bind!(block_contact(profile: &str ; conversation_id: i32; ) c_BlockContact); c_bind!(unblock_contact(profile: &str ; conversation_id: i32; ) c_UnblockContact); - c_bind!(get_message(profile: &str; conversation_id: i32, message_index: i32 ;) c_GetMessage -> String); + c_bind!(get_message(profile: &str; conversation_id: i32, message_index: i32 ;) c_GetMessage -> String); c_bind!(get_message_by_id(profile: &str ; conversation_id: i32, message_id: i32 ;) c_GetMessageByID -> String); c_bind!(get_message_by_content_hash(profile: &str ; conversation_id: i32 ; hash: &str) c_GetMessagesByContentHash -> String); - c_bind!(send_message(profile: &str; conversation_id: i32; msg: &str) c_SendMessage); - c_bind!(send_invitation(profile: &str; conversation_id: i32, target_id: i32;) c_SendInvitation); - c_bind!(share_file(profile: &str; conversation_id: i32; file_path: &str) c_ShareFile); + c_bind!(get_messages(profile: &str; conversation_id: i32, message_index: i32, count: i32 ;) c_GetMessages -> String); + c_bind!(send_message(profile: &str; conversation_id: i32; msg: &str) c_SendMessage -> String); + c_bind!(send_invitation(profile: &str; conversation_id: i32, target_id: i32;) c_SendInvitation -> String); + c_bind!(share_file(profile: &str; conversation_id: i32; file_path: &str) c_ShareFile -> String); c_bind!(download_file(profile: &str; conversation_id: i32; file_path: &str, manifest_path: &str, file_key: &str) c_DownloadFile); c_bind!(check_download_status(profile: &str, file_key: &str;;) c_CheckDownloadStatus); c_bind!(verify_or_resume_download(profile: &str; conversation_id: i32; file_key: &str) c_VerifyOrResumeDownload); @@ -110,9 +120,33 @@ impl CwtchLib for CwtchLibGo { c_bind!(delete_contact(profile: &str; conversation_id: i32;) c_DeleteContact); c_bind!(import_bundle(profile: &str, bundle: &str;;) c_ImportBundle); c_bind!(set_profile_attribute(profile: &str, key: &str, val: &str;;) c_SetProfileAttribute); + fn get_profile_attribute(&self, profile: &str, key: &str) -> Result, CwtchError> { + let resp = self._get_profile_attribute(profile, key); + let attr: Attribute = match serde_json::from_str(&resp) { + Ok(attr) => attr, + Err(e) => return Err(e.to_string()), + }; + match attr.exists { + true => Ok(Some(attr.value)), + false => Ok(None), + } + } c_bind!(set_conversation_attribute(profile: &str; conversation_id: i32; key: &str, val: &str) c_SetConversationAttribute); + fn get_conversation_attribute(&self, profile: &str, conversation_id: i32, key: &str) -> Result, CwtchError> { + let resp = self._get_conversation_attribute(profile, conversation_id, key); + let attr: Attribute = match serde_json::from_str(&resp) { + Ok(attr) => attr, + Err(e) => return Err(e.to_string()), + }; + match attr.exists { + true => Ok(Some(attr.value)), + false => Ok(None), + } + } c_bind!(set_message_attribute(profile: &str; conversation_id: i32, channel_id: i32, message_id: i32; key: &str, val: &str) c_SetMessageAttribute); c_bind!(change_password(profile: &str, old_pass: &str, new_pass: &str, new_pass_again: &str;;) c_ChangePassword); + c_bind!(export_profile(profile: &str, filename: &str;;) c_ExportProfile); + c_bind!(import_profile(filename: &str, password: &str;;) c_ImportProfile -> String); fn shutdown_cwtch(&self) { unsafe { diff --git a/src/cwtchlib_go/bindings.rs b/src/cwtchlib_go/bindings.rs index cd4ab52..64d0189 100644 --- a/src/cwtchlib_go/bindings.rs +++ b/src/cwtchlib_go/bindings.rs @@ -208,6 +208,9 @@ extern "C" { torLen: ::std::os::raw::c_int, ) -> ::std::os::raw::c_int; } +extern "C" { + pub fn c_Started() -> ::std::os::raw::c_int; +} extern "C" { pub fn c_ReconnectCwtchForeground(); } @@ -285,6 +288,15 @@ extern "C" { contenthash_len: ::std::os::raw::c_int, ) -> *mut ::std::os::raw::c_char; } +extern "C" { + pub fn c_GetMessages( + profile_ptr: *mut ::std::os::raw::c_char, + profile_len: ::std::os::raw::c_int, + conversation_id: ::std::os::raw::c_int, + message_index: ::std::os::raw::c_int, + count: ::std::os::raw::c_int, + ) -> *mut ::std::os::raw::c_char; +} extern "C" { pub fn c_FreePointer(ptr: *mut ::std::os::raw::c_char); } @@ -295,7 +307,7 @@ extern "C" { conversation_id: ::std::os::raw::c_int, msg_ptr: *mut ::std::os::raw::c_char, msg_len: ::std::os::raw::c_int, - ); + ) -> *mut ::std::os::raw::c_char; } extern "C" { pub fn c_SendInvitation( @@ -303,7 +315,7 @@ extern "C" { profile_len: ::std::os::raw::c_int, conversation_id: ::std::os::raw::c_int, target_id: ::std::os::raw::c_int, - ); + ) -> *mut ::std::os::raw::c_char; } extern "C" { pub fn c_ShareFile( @@ -312,7 +324,7 @@ extern "C" { conversation_id: ::std::os::raw::c_int, filepath_ptr: *mut ::std::os::raw::c_char, filepath_len: ::std::os::raw::c_int, - ); + ) -> *mut ::std::os::raw::c_char; } extern "C" { pub fn c_DownloadFile( @@ -397,6 +409,14 @@ extern "C" { val_len: ::std::os::raw::c_int, ); } +extern "C" { + pub fn c_GetProfileAttribute( + profile_ptr: *mut ::std::os::raw::c_char, + profile_len: ::std::os::raw::c_int, + key_ptr: *mut ::std::os::raw::c_char, + key_len: ::std::os::raw::c_int, + ) -> *mut ::std::os::raw::c_char; +} extern "C" { pub fn c_SetConversationAttribute( profile_ptr: *mut ::std::os::raw::c_char, @@ -408,6 +428,15 @@ extern "C" { val_len: ::std::os::raw::c_int, ); } +extern "C" { + pub fn c_GetConversationAttribute( + profile_ptr: *mut ::std::os::raw::c_char, + profile_len: ::std::os::raw::c_int, + conversation_id: ::std::os::raw::c_int, + key_ptr: *mut ::std::os::raw::c_char, + key_len: ::std::os::raw::c_int, + ) -> *mut ::std::os::raw::c_char; +} extern "C" { pub fn c_SetMessageAttribute( profile_ptr: *mut ::std::os::raw::c_char, @@ -433,6 +462,22 @@ extern "C" { newpassword_again_len: ::std::os::raw::c_int, ); } +extern "C" { + pub fn c_ExportProfile( + profile_ptr: *mut ::std::os::raw::c_char, + profile_len: ::std::os::raw::c_int, + file_ptr: *mut ::std::os::raw::c_char, + file_len: ::std::os::raw::c_int, + ); +} +extern "C" { + pub fn c_ImportProfile( + file_ptr: *mut ::std::os::raw::c_char, + file_len: ::std::os::raw::c_int, + passwordPtr: *mut ::std::os::raw::c_char, + passwordLen: ::std::os::raw::c_int, + ) -> *mut ::std::os::raw::c_char; +} extern "C" { pub fn c_ShutdownCwtch(); } diff --git a/src/lib.rs b/src/lib.rs index 2bd6cbc..5f11af9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,11 +9,17 @@ mod cwtchlib_go; /// Basic structs using data from Cwtch and for deserializing JSON and serializing to JSON to communicate with Cwtch pub mod structs; +/// Error type for Cwtch lib related errors, intended for use with Result +pub type CwtchError = String; + /// Interface to a Cwtch app with API matching libcwtch pub trait CwtchLib { /// Start a cwtch application using app_dir to store all user profile data and looking to tor_path to find tor to run fn start_cwtch(&self, app_dir: &str, tor_path: &str) -> i32; + /// Return 1 if cwtch has been started and 0 if not + fn started(&self) -> i32; + /// Send json of a structs::CwtchEvent to the cwtch app bus fn send_app_event(&self, event_json: &str); @@ -52,14 +58,17 @@ pub trait CwtchLib { hash: &str, ) -> String; - /// Send json of a structs::Message from profile to contact - fn send_message(&self, profile: &str, conversation_id: i32, msg: &str); + /// Bulk get messages starting at message index and of count amoung + fn get_messages(&self, profile: &str, conversation_id: i32, message_index: i32, count: i32) -> String; - /// Send profile's contact an invite for/to target - fn send_invitation(&self, profile: &str, conversation_id: i32, target_id: i32); + /// Send json of a structs::Message from profile to contact. Returns computed sent message (including index and hash values) + fn send_message(&self, profile: &str, conversation_id: i32, msg: &str) -> String; - /// share a file file_path with a conersation - fn share_file(&self, profile: &str, conversation_id: i32, file_path: &str); + /// Send profile's contact an invite for/to target. Returns computed sent message (including index and hash values) + fn send_invitation(&self, profile: &str, conversation_id: i32, target_id: i32) -> String; + + /// share a file file_path with a conersation. Returns computed sent message (including index and hash values) + fn share_file(&self, profile: &str, conversation_id: i32, file_path: &str) -> String; /// download a file from a conversation to the file_path fn download_file( @@ -98,6 +107,9 @@ pub trait CwtchLib { /// Set a profile attribute key to val fn set_profile_attribute(&self, profile: &str, key: &str, val: &str); + /// Get a profile attribute + fn get_profile_attribute(&self, profile: &str, key: &str) -> Result, CwtchError>; + /// Set a profile's contact's attribute of key to val fn set_conversation_attribute(&self, profile: &str, conversation_id: i32, key: &str, val: &str); @@ -112,9 +124,18 @@ pub trait CwtchLib { attribute_value: &str, ); + /// Get an attribute for a conversation + fn get_conversation_attribute(&self, profile: &str, conversation_id: i32, key: &str) -> Result, CwtchError>; + /// Change a profile's password to new_pass if old_pass is correct fn change_password(&self, profile: &str, old_pass: &str, new_pass: &str, new_pass_again: &str); + /// Export a profile to filename + fn export_profile(&self, profile: &str, filename: &str); + + /// Import a profile from a file with supplied password. Json of a profile struct returned on success + fn import_profile(&self, filename: &str, password: &str) -> String; + /// Shutdown the cwtch app and associated ACN fn shutdown_cwtch(&self); diff --git a/src/structs.rs b/src/structs.rs index 5f48f02..6c8875a 100644 --- a/src/structs.rs +++ b/src/structs.rs @@ -57,6 +57,17 @@ pub enum ContactAuthorization { Blocked, } +#[derive(Serialize, Deserialize, Debug)] +#[serde(rename_all = "PascalCase")] +#[allow(non_snake_case)] +/// Struct to deserialize the results of Get*Attribute requests into +pub struct Attribute { + /// Was the attribute value found in storage + pub exists: bool, + /// The value of the requested attribute + pub value: String, +} + #[derive(Serialize, Deserialize, Debug)] #[serde(rename_all = "PascalCase")] #[allow(non_snake_case)] @@ -70,6 +81,22 @@ pub struct CwtchEvent { pub data: HashMap, } +#[derive(Serialize, Deserialize, Debug)] +#[serde(rename_all = "PascalCase")] +#[allow(non_snake_case)] +/// AccessControl is a type determining client assigned authorization to a peer +pub struct AccessControl { + /// Any attempts from this handle to connect are blocked + pub blocked: bool, + /// Allows a handle to access the conversation + pub read: bool, + /// Allows a handle to append new messages to the conversation + pub append: bool, +} + +/// represents an access control list for a conversation. Mapping handles to conversation functions +pub type ACL = HashMap; + #[serde_as] #[derive(Serialize, Deserialize, Debug)] #[serde(rename_all = "camelCase")] @@ -88,6 +115,8 @@ pub struct Conversation { pub status: ConnectionState, /// has the conversation been manually accpted pub accepted: bool, + ///represents an access control list for a conversation. Mapping handles to conversation functions + pub access_control_list: ACL, /// has the conversation been manually blocked pub blocked: bool, /// is this conversation a group? if so "onion" will be a group ID @@ -105,11 +134,13 @@ pub struct Server { pub status: ConnectionState, } -#[derive(Debug)] +#[serde_as] +#[derive(Debug, Serialize, Deserialize)] /// Struct to serialize/deserialize profiles coming from libcwtch-go pub struct Profile { /// onion address / ID of the profile - pub onion: String, + #[serde(alias = "onion")] + pub handle: String, /// nick name of the onion as supplied by libcwtch-go based on "name" attribute pub nick: String, /// path to a profile image, controled by "picture" attribute @@ -249,7 +280,7 @@ impl Profile { Err(e) => return Err(e), }; Ok(Profile { - onion: identity.to_string(), + handle: identity.to_string(), nick: name.to_string(), image_path: picture.to_string(), attr: Default::default(),