#![allow(non_upper_case_globals)] #![allow(non_camel_case_types)] #![allow(non_snake_case)] #![allow(dead_code)] use std::ffi::CStr; use std::ffi::CString; use super::CwtchLib; use crate::cwtchlib_go::bindings; use crate::{ConversationID, CwtchError, FileKey, ProfileIdentity, ServerIdentity, structs::*}; use crate::event::Event; type c_bool = core::ffi::c_char; fn from_c_bool(b: c_bool) -> bool { b == 1 } fn to_c_bool(b: bool) -> c_bool { if b { 1 } else { 0 } } struct c_str_wrap { raw: *mut core::ffi::c_char, len: i32, } impl c_str_wrap { pub fn new(str: &str) -> c_str_wrap { let cs = match CString::new(str) { Ok(s) => s, Err(_) => CString::new("").unwrap(), }; c_str_wrap { len: cs.as_bytes().len() as i32, raw: cs.into_raw(), } } } impl Drop for c_str_wrap { fn drop(&mut self) { unsafe { drop(CString::from_raw(self.raw)); } } } // c_bind handles setting up c string arguments and freeing them // c_bind!( $fn_name ( [ $string_args: &str],* [ $non_string_args : $type ],* ) $c_function -> $return_type? ) macro_rules! c_bind { // No return ($func_name:ident ($($str1:ident: &str),* ; $($args:ident: $t:ty),* ; $($str2:ident: &str),*) $bind_fn:ident) => { fn $func_name(&self, $($str1: &str, )* $($args: $t, )* $($str2: &str, )*) { $(let $str1 = c_str_wrap::new($str1);)* $(let $str2 = c_str_wrap::new($str2);)* unsafe { bindings::$bind_fn($( $str1.raw, $str1.len, )* $($args,)* $( $str2.raw, $str2.len, )*); } } }; // String return ($func_name:ident ($($str1:ident: &str),* ; $($args:ident: $t:ty),* ; $($str2:ident: &str),*) $bind_fn:ident -> String) => { fn $func_name(&self, $($str1: &str, )* $($args: $t, )* $($str2: &str, )*) -> String { $(let $str1 = c_str_wrap::new($str1);)* $(let $str2 = c_str_wrap::new($str2);)* unsafe { let result_ptr = bindings::$bind_fn($( $str1.raw, $str1.len, )* $($args,)* $( $str2.raw, $str2.len, )*); let result = match CStr::from_ptr(result_ptr).to_str() { Ok(s) => s.to_owned(), Err(_) => "".to_string() }; // return ownership of string memory and call the library to free it bindings::c_FreePointer(result_ptr); result } } }; // Non String return ($func_name:ident ($($str1:ident: &str),* ; $($args:ident: $t:ty),* ; $($str2:ident: &str),*) $bind_fn:ident -> $bind_fn_ty:ty) => { fn $func_name(&self, $($str1: &str, )* $($args: t, )* $($str2: &str, )*) -> $bind_fn_ty { $(let $str1 = c_str_wrap::new($str1);)* $(let $str2 = c_str_wrap::new($str2);)* unsafe { let result = bindings::$bind_fn($( $str1.raw, $str1.len, )* $($args,)* $( $str2.raw, $str2.len, )*); result } } }; } 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!(_peer_with(profile: &str, new_peer: &str;;) c_PeerWithOnion); c_bind!(_update_settings(settings_json: &str;;) c_UpdateSettings); 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); c_bind!(_get_appbus_event(;;) c_GetAppBusEvent -> String); c_bind!(_configure_connections(profile: &str; listen: c_bool, peers: c_bool, servers: c_bool;) c_ConfigureConnections); c_bind!(_create_profile(nick: &str, pass: &str; autostart: c_bool;) c_CreateProfile); c_bind!(_activate_peer_engine(profile: &str;;) c_ActivatePeerEngine); c_bind!(_deactivate_peer_engine(profile: &str;;) c_DeactivatePeerEngine); c_bind!(_accept_conversation(profile: &str ; conversation_id: i32; ) c_AcceptConversation); c_bind!(_block_conversation(profile: &str ; conversation_id: i32; ) c_BlockConversation); c_bind!(_unblock_conversation(profile: &str ; conversation_id: i32; ) c_UnblockConversation); c_bind!(_disconnect_from_peer(profile: &str, peer_address: &str;;) c_DisconnectFromPeer); c_bind!(_search_conversations(profile: &str, pattern: &str;;) c_SearchConversations -> String); c_bind!(_get_conversation_access_control_list(profile: &str; conversation_id: i32;) c_GetConversationAccessControlList -> String); c_bind!(_update_conversation_access_control_list(profile: &str; conversation_id: i32; acl: &str) c_UpdateConversationAccessControlList); 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_GetMessageByContentHash -> String); c_bind!(_get_messages(profile: &str; conversation_id: i32, message_index: i32, count: u32 ;) c_GetMessages -> String); c_bind!(_send_message(profile: &str; conversation_id: i32; msg: &str) c_SendMessage -> String); c_bind!(_send_invite_message(profile: &str; conversation_id: i32, target_id: i32;) c_SendInviteMessage -> String); c_bind!(_share_file(profile: &str; conversation_id: i32; file_path: &str) c_ShareFile -> String); c_bind!(_get_shared_files(profile: &str; conversaion_id: i32;) c_GetSharedFiles -> String); c_bind!(_restart_fileshare(profile: &str, file_key: &str;;) c_RestartFileShare); c_bind!(_stop_fileshare(profile: &str, file_key: &str;;) c_StopFileShare); c_bind!(_download_file_default_limit(profile: &str; conversation_id: i32; file_path: &str, manifest_path: &str, file_key: &str) c_DownloadFileDefaultLimit); 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_VerifyOrResumeDownloadDefaultLimit); c_bind!(_start_group(profile: &str, name: &str, server: &str;;) c_StartGroup); c_bind!(_queue_join_server(profile: &str, server: &str;;) c_QueueJoinServer); c_bind!(_disconnect_from_server(profile: &str, server: &str;;) c_DisconnectFromServer); c_bind!(_publish_server_update(profile: &str;;) c_PublishServerUpdate); c_bind!(_get_server_info_list(profile: &str;;) c_GetServerInfoList); c_bind!(_delete_server_info(profile: &str, server: &str;;) c_DeleteServerInfo); c_bind!(_delete_profile(profile: &str, pass: &str;;) c_DeleteProfile); c_bind!(_archive_conversation(profile: &str; conversation_id: i32;) c_ArchiveConversation); c_bind!(_delete_conversation(profile: &str; conversation_id: i32;) c_DeleteConversation); c_bind!(_import_bundle(profile: &str, bundle: &str;;) c_ImportBundle); c_bind!(_set_profile_attribute(profile: &str, key: &str, val: &str;;) c_SetProfileAttribute); c_bind!(_set_conversation_attribute(profile: &str; conversation_id: i32; key: &str, val: &str) c_SetConversationAttribute); c_bind!(_update_message_attribute(profile: &str; conversation_id: i32, channel_id: i32, message_id: i32; key: &str, val: &str) c_UpdateMessageAttribute); 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!(_create_server(password: &str, description: &str; autostart: c_bool;) c_CreateServer); c_bind!(_delete_server(server: &str, current_password: &str;;) c_DeleteServer); c_bind!(_launch_server(server: &str;;) c_LaunchServer); c_bind!(_stop_server(server: &str;;) c_StopServer); c_bind!(_set_server_attribute(server: &str, key: &str, val: &str;;) c_SetServerAttribute); } impl CwtchLib for CwtchLibGo { c_bind!(start_cwtch(app_dir: &str, tor_path: &str;;) c_StartCwtch -> i32); c_bind!(started(;;) c_Started -> i32); fn update_settings(&self, settings: &Settings) { let settings_json = match serde_json::to_string(settings) { Ok(s) => s, Err(_) => return, }; self._update_settings(&settings_json) } c_bind!(reconnect_cwtch_foreground(;;) c_ReconnectCwtchForeground); fn create_profile(&self, nick: &str, pass: &str, autostart: bool) { self._create_profile(nick, pass, to_c_bool(autostart)) } fn activate_peer_engine(&self, profile: &ProfileIdentity) { self._activate_peer_engine(profile.as_str()) } fn deactivate_peer_engine(&self, profile: &ProfileIdentity) { self._deactivate_peer_engine(profile.as_str()) } fn configure_connections(&self, profile: &ProfileIdentity, listen: bool, peers: bool, servers: bool) { self._configure_connections(profile.as_str(), to_c_bool(listen), to_c_bool(peers), to_c_bool(servers)) } fn search_conversations(&self, profile: &ProfileIdentity, pattern: &str) -> String { self._search_conversations(profile.as_str(), pattern) } fn get_conversation_access_control_list(&self, profile: &ProfileIdentity, conversation_id: ConversationID) -> Result { let json = self._get_conversation_access_control_list(profile.as_str(), conversation_id.into()); match serde_json::from_str(&json) { Ok(acl) => Ok(acl), Err(e) => Err(e.to_string()), } } fn update_conversation_access_control_list(&self, profile: &ProfileIdentity, conversation_id: ConversationID, acl: ACL) { match serde_json::to_string(&acl) { Ok(acl_json) => self._update_conversation_access_control_list(profile.as_str(), conversation_id.into(), &acl_json), Err(_) => return, }; } c_bind!(load_profiles(pass: &str;;) c_LoadProfiles); fn accept_conversation(&self, profile: &ProfileIdentity, conversation_id: ConversationID) { self._accept_conversation(profile.as_str(), conversation_id.into()) } fn peer_with(&self, profile: &ProfileIdentity, new_peer_address: &str) { self._peer_with(profile.as_str(), new_peer_address) } fn block_conversation(&self, profile: &ProfileIdentity, conversation_id: ConversationID) { self._block_conversation(String::from(profile).as_str(), conversation_id.into()) } fn unblock_conversation(&self, profile: &ProfileIdentity, conversation_id: ConversationID) { self._unblock_conversation(String::from(profile).as_str(), conversation_id.into()) } fn disconnect_from_peer(&self, profile: &ProfileIdentity, peer_id: &str) { self._disconnect_from_peer(profile.as_str(), peer_id) } fn get_message_by_id(&self, profile: &ProfileIdentity, conversation_id: ConversationID, message_id: i32) -> String { self._get_message_by_id(String::from(profile).as_str(), conversation_id.into(), message_id) } fn get_message_by_content_hash(&self, profile: &ProfileIdentity, conversation_id: ConversationID, hash: &str) -> String { self._get_message_by_content_hash(String::from(profile).as_str(), conversation_id.into(), hash) } fn get_messages(&self, profile: &ProfileIdentity, conversation_id: ConversationID, message_index: i32, count: u32) -> String { self._get_messages(String::from(profile).as_str(), conversation_id.into(), message_index, count) } fn send_message_raw(&self, profile: &ProfileIdentity, conversation_id: ConversationID, msg: &str) -> String { self._send_message(String::from(profile).as_str(), conversation_id.into(), msg) } fn send_message(&self, profile: &ProfileIdentity, conversation_id: ConversationID, message: &MessageWrapper) -> Result { match serde_json::to_string(&message) { Ok(message_json) => Ok(self._send_message(&String::from(profile), conversation_id.into(), &message_json)), Err(e) => Err(format!("Error parsing json response: {}", e.to_string())) } } fn send_invite_message(&self, profile: &ProfileIdentity, conversation_id: ConversationID, target_id: i32) -> String { self._send_invite_message(String::from(profile).as_str(), conversation_id.into(), target_id) } fn share_file(&self, profile: &ProfileIdentity, conversation_id: ConversationID, file_path: &str) -> String { self._share_file(String::from(profile).as_str(), conversation_id.into(), file_path) } fn get_shared_files(&self, profile: &ProfileIdentity, conversaion_id: ConversationID) -> Vec { let json = self._get_shared_files(profile.as_str(), conversaion_id.into()); match serde_json::from_str(&json) { Ok(l) => l, Err(_) => vec!(), } } fn download_file_default_limit(&self, profile: &ProfileIdentity, conversation_id: ConversationID, file_path: &str, manifest_path: &str, file_key: &FileKey) { self._download_file_default_limit(String::from(profile).as_str(), conversation_id.into(), file_path, manifest_path, String::from(file_key).as_str()) } fn restart_fileshare(&self, profile: &ProfileIdentity, file_key: &FileKey) { self._restart_fileshare(profile.as_str(), file_key.as_str()) } fn stop_fileshare(&self, profile: &ProfileIdentity, file_key: &FileKey) { self._stop_fileshare(profile.as_str(), file_key.as_str()) } fn check_download_status(&self, profile: &ProfileIdentity, file_key: &FileKey) { self._check_download_status(String::from(profile).as_str(), String::from(file_key).as_str()) } fn verify_or_resume_download(&self, profile: &ProfileIdentity, conversation_id: ConversationID, file_key: &FileKey) { self._verify_or_resume_download(String::from(profile).as_str(), conversation_id.into(), String::from(file_key).as_str()) } fn reset_tor(&self) { unsafe { bindings::c_ResetTor(); } } fn start_group(&self, profile: &ProfileIdentity, server: &str, name: &str) { self._start_group(String::from(profile).as_str(), server, name) } fn queue_join_server(&self, profile: &ProfileIdentity, server: &ServerIdentity) { self._queue_join_server(profile.as_str(), server.as_str()) } fn disconnect_from_server(&self, profile: &ProfileIdentity, server: &ServerIdentity) { self._disconnect_from_server(profile.as_str(), server.as_str()) } fn publish_server_update(&self, profile: &ProfileIdentity) { self._publish_server_update(profile.as_str()) } fn get_server_info_list(&self, profile: &ProfileIdentity) { self._get_server_info_list(profile.as_str()) } fn delete_server_info(&self, profile: &ProfileIdentity, server: &ServerIdentity) { self._delete_server_info(profile.as_str(), server.as_str()) } fn delete_profile(&self, profile: &ProfileIdentity, pass: &str) { self._delete_profile(String::from(profile).as_str(), pass) } fn archive_conversation(&self, profile: &ProfileIdentity, conversation_id: ConversationID) { self._archive_conversation(String::from(profile).as_str(), conversation_id.into()) } fn delete_conversation(&self, profile: &ProfileIdentity, conversation_id: ConversationID) { self._delete_conversation(profile.as_str(), conversation_id.into()) } fn import_bundle(&self, profile: &ProfileIdentity, bundle: &str) { self._import_bundle(String::from(profile).as_str(), bundle) } fn set_profile_attribute(&self, profile: &ProfileIdentity, key: &str, val: &str) { self._set_profile_attribute(String::from(profile).as_str(), key, val) } fn get_profile_attribute(&self, profile: &ProfileIdentity, key: &str) -> Result, CwtchError> { let resp = self._get_profile_attribute(String::from(profile).as_str(), 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), } } fn set_conversation_attribute(&self, profile: &ProfileIdentity, conversation_id: ConversationID, key: &str, val: &str) { self._set_conversation_attribute(String::from(profile).as_str(), conversation_id.into(), key, val) } fn get_conversation_attribute(&self, profile: &ProfileIdentity, conversation_id: ConversationID, key: &str) -> Result, CwtchError> { let resp = self._get_conversation_attribute(String::from(profile).as_str(), conversation_id.into(), 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), } } fn update_message_attribute(&self, profile: &ProfileIdentity, conversation_id: ConversationID, channel_id: i32, message_id: i32, key: &str, val: &str) { self._update_message_attribute(String::from(profile).as_str(), conversation_id.into(), channel_id, message_id, key, val) } fn change_password(&self, profile: &ProfileIdentity, old_pass: &str, new_pass: &str, new_pass_again: &str) { self._change_password(String::from(profile).as_str(), old_pass, new_pass, new_pass_again) } fn export_profile(&self, profile: &ProfileIdentity, filename: &str) { self._export_profile(String::from(profile).as_str(), filename) } c_bind!(import_profile(filename: &str, password: &str;;) c_ImportProfile -> String); fn shutdown_cwtch(&self) { unsafe { bindings::c_ShutdownCwtch(); } } c_bind!(load_servers(password: &str;;) c_LoadServers); fn create_server(&self, password: &str, description: &str , autostart: bool) { self._create_server(password, description, to_c_bool(autostart)) } fn delete_server(&self, server: ServerIdentity, current_password: &str) { self._delete_server(String::from(server).as_str(), current_password) } c_bind!(launch_servers(;;) c_LaunchServers); fn launch_server(&self, server: ServerIdentity) { self._launch_server(String::from(server).as_str()) } fn stop_server(&self, server: ServerIdentity) { self._stop_server(String::from(server).as_str()) } c_bind!(stop_servers(;;) c_StopServers); c_bind!(destroy_servers(;;) c_DestroyServers); fn set_server_attribute(&self, server: ServerIdentity, key: &str, val: &str) { self._set_server_attribute(String::from(server).as_str(), key, val) } c_bind!(get_debug_info(;;) c_GetDebugInfo -> String); fn get_appbus_event(&self) -> Event { let event_json = self._get_appbus_event(); let cwtch_event: CwtchEvent = serde_json::from_str(&event_json).expect("Error parsing Cwtch event"); Event::from(&cwtch_event) } }