diff --git a/Cargo.toml b/Cargo.toml index f426ad1..2426410 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,8 +3,9 @@ name = "libcwtch" version = "0.1.0" authors = ["Dan Ballard "] edition = "2018" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +license = "MIT" +description = "libcwtch is an interface to a Cwtch app that allows creating of profiles to communicate with contacts over the Cwtch protocol" +repository = "https://git.openprivacy.ca/cwtch.im/libcwtch-rs" [dependencies] libc = "0.2" diff --git a/README.md b/README.md index d8a39e4..81f3f5f 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ # libCwtch-rs -Rust bindings for libCwtch +Rust bindings for [libCwtch](https://git.openprivacy.ca/cwtch.im/libcwtch-go/) + +Example echobot in examples/echobot.rs (`cargo run --example echobot` -- assumes tor is on $PATH) ## Building @@ -15,7 +17,7 @@ the 'preamble from import "C"' section as it imports headers required for the C but that we don't want to create rust bindings for (like importing stdlib.h). Then: ``` -bindgen libCwtch.h -o src/gobindings/mod.rs +bindgen libCwtch.h -o src/cwtchlib_go/bindings.rs ``` ### Todo diff --git a/examples/echobot.rs b/examples/echobot.rs new file mode 100644 index 0000000..c6ec5d5 --- /dev/null +++ b/examples/echobot.rs @@ -0,0 +1,51 @@ +use std::{thread}; + +use libcwtch; +use libcwtch::CwtchLib; +use libcwtch::structs::{*}; + +fn main() { + let bot_home: String = "example_cwtch_dir".to_string(); + std::fs::remove_dir_all(&bot_home); + std::fs::create_dir_all(&bot_home).unwrap(); + + let cwtch = libcwtch::new_cwtchlib_go(); + println!("start_cwtch"); + let ret = cwtch.start_cwtch(bot_home.as_str(), ""); + println!("start_cwtch returned {}", ret); + + let event_loop_handle = thread::spawn(move || { + loop { + let event_str = cwtch.get_appbus_event(); + println!("event: {}", event_str); + + let event: CwtchEvent = serde_json::from_str(&event_str).unwrap(); + match event.event_type.as_str() { + "CwtchStarted" => { + println!("event CwtchStarted!"); + println!("Creating bot"); + cwtch.create_profile("Echobot", "be gay do crime"); + }, + "NewPeer" => { + println!("\n***** {} at {} *****\n", event.data["name"], event.data["Identity"]); + + // process json for profile, contacts and servers...else { + let profile = Profile::new(&event.data["Identity"], &event.data["name"], &event.data["picture"], &event.data["ContactsJson"], &event.data["ServerList"]); + print!("profile: {:?}", profile); + } + "NewMessageFromPeer" => { + let to = event.data["ProfileOnion"].to_string(); + let conversation = event.data["RemotePeer"].to_string(); + let message: Message = serde_json::from_str(event.data["Data"].as_str()).unwrap(); + + let response = Message{o:1, d:message.d}; + let response_json = serde_json::to_string(&response).unwrap(); + cwtch.send_message(to.as_str(), conversation.as_str(), response_json.as_str()); + } + _ => println!("unhandled event!"), + }; + }; + }); + + event_loop_handle.join().unwrap(); +} diff --git a/src/bindings_go.rs b/src/bindings_go.rs index cad9beb..c12e0fb 100644 --- a/src/bindings_go.rs +++ b/src/bindings_go.rs @@ -5,12 +5,9 @@ use std::ffi::{CString}; use std::ffi::{CStr}; -use std::collections::HashMap; - -use serde::{Deserialize, Serialize}; use super::{CwtchLib}; -use crate::gobindings; +use crate::cwtchlib_go::bindings; struct c_str_wrap { raw: *mut i8, @@ -35,14 +32,13 @@ impl Drop for c_str_wrap { // c_bind handles setting up c string arguments and freeing them // c_bind!( $fn_name ( [ $string_args ]* ; [ $non_string_args : $type ]* ) $c_function -> $return_type? ) -#[macro_export] macro_rules! c_bind { // macro for returnless fns ($func_name:ident ($($str:ident),* ; $($arg:ident: $t:ty),*) $bind_fn:ident) => { fn $func_name(&self, $($str: &str, )* $($arg: $t, )*) { $(let $str = c_str_wrap::new($str);)* unsafe { - gobindings::$bind_fn($( $str.raw, $str.len, )* $($arg,)* ); + bindings::$bind_fn($( $str.raw, $str.len, )* $($arg,)* ); } } }; @@ -51,13 +47,13 @@ macro_rules! c_bind { fn $func_name(&self, $($str: &str, )* $($arg: $t, )*) -> String { $(let $str = c_str_wrap::new($str);)* unsafe { - let result_ptr = gobindings::$bind_fn($( $str.raw, $str.len, )* $($arg,)* ); + let result_ptr = bindings::$bind_fn($( $str.raw, $str.len, )* $($arg,)* ); 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 - gobindings::c_FreePointer(result_ptr); + bindings::c_FreePointer(result_ptr); result } } @@ -67,26 +63,20 @@ macro_rules! c_bind { fn $func_name(&self, $($str: &str, )* $($arg: $t, )*) -> $bind_fn_ty { $(let $str = c_str_wrap::new($str);)* unsafe { - let result = gobindings::$bind_fn($( $str.raw, $str.len, )* $($arg,)* ); + let result = bindings::$bind_fn($( $str.raw, $str.len, )* $($arg,)* ); result } } }; } -#[derive(Serialize, Deserialize, Debug)] -struct Event { - EventType: String, - Data: HashMap -} +pub struct CwtchLibGo {} -pub struct GoCwtchLib {} - -impl GoCwtchLib { +impl CwtchLibGo { c_bind!(send_profile_event(profile, event_json;) c_SendProfileEvent); } -impl CwtchLib for GoCwtchLib { +impl CwtchLib for CwtchLibGo { c_bind!(start_cwtch(app_dir, tor_path;) c_StartCwtch -> i32); c_bind!(send_app_event(event_json;) c_SendAppEvent); c_bind!(get_appbus_event(;) c_GetAppBusEvent -> String); @@ -101,7 +91,7 @@ impl CwtchLib for GoCwtchLib { c_bind!(send_message(profile, contact, msg;) c_SendMessage); c_bind!(send_invitation(profile, contact, target;) c_SendInvitation); fn reset_tor(&self) { - unsafe { gobindings::c_ResetTor(); } + unsafe { bindings::c_ResetTor(); } } c_bind!(create_group(profile, server, name;) c_CreateGroup); c_bind!(delete_profile(profile, pass;) c_DeleteProfile); @@ -113,6 +103,6 @@ impl CwtchLib for GoCwtchLib { c_bind!(set_group_attribute(profile, group, key, val;) c_SetGroupAttribute); fn shutdown_cwtch(&self) { - unsafe { gobindings::c_ShutdownCwtch(); } + unsafe { bindings::c_ShutdownCwtch(); } } } \ No newline at end of file diff --git a/src/gobindings/mod.rs b/src/cwtchlib_go/bindings.rs similarity index 100% rename from src/gobindings/mod.rs rename to src/cwtchlib_go/bindings.rs diff --git a/src/cwtchlib_go/mod.rs b/src/cwtchlib_go/mod.rs new file mode 100644 index 0000000..b11653b --- /dev/null +++ b/src/cwtchlib_go/mod.rs @@ -0,0 +1,6 @@ +#![allow(non_upper_case_globals)] +#![allow(non_camel_case_types)] +#![allow(non_snake_case)] +#![allow(dead_code)] + +pub mod bindings; \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index eacbc37..a05d810 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,40 +1,84 @@ -#![allow(non_upper_case_globals)] -#![allow(non_camel_case_types)] -#![allow(non_snake_case)] -#![allow(dead_code)] +#![doc(html_logo_url = "https://git.openprivacy.ca/cwtch.im/cwtch-ui/media/branch/trunk/cwtch.png")] -mod gobindings; +mod cwtchlib_go; mod bindings_go; pub mod structs; +/// 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; + + /// Send json of a structs::CwtchEvent to the cwtch app bus fn send_app_event(&self, event_json: &str); + + /// Pull json of a structs::CwtchEvent off the appbus for responding to fn get_appbus_event(&self, ) -> String; + + /// Create a new profile encrypted with pass fn create_profile(&self, nick: &str, pass: &str); + + /// Load any profiles encrypted by pass fn load_profiles(&self, pass: &str); + + /// Cause profile to accept contact fn accept_contact(&self, profile: &str, contact: &str); + + /// Cause profile to reject contact fn reject_invite(&self, profile: &str, contact: &str); + + /// Cause profile to block contact fn block_contact(&self, profile: &str, contact: &str); + + /// Cause profile to update contact's message to have it's flags updated fn update_message_flags(&self, profile: &str, contact: &str, message_id: i32, message_flags: u64); + + /// Get a specific message for contact of profile by index fn get_message(&self, profile: &str, contact: &str, message_index: i32) -> String; + + /// Get a specific message for contact of profile by hash fn get_message_by_content_hash(&self, profile: &str, contact: &str, hash: &str) -> String; + + /// Send json of a structs::Message from profile to contact fn send_message(&self, profile: &str, contact: &str, msg: &str); + + /// Send profile's contact an invite for/to target fn send_invitation(&self, profile: &str, contact: &str, target: &str); + + /// Ask the ACN inside the Cwtch app to restart the tor connection fn reset_tor(&self, ); + + /// Cause profile to create a group on server with name fn create_group(&self, profile: &str, server: &str, name: &str); + + /// Delete profile with encryption/password check of pass fn delete_profile(&self, profile: &str, pass: &str); + + /// Cause profile to archive conversation with contact fn archive_conversation(&self, profile: &str, contact: &str); - fn delete_contact(&self, profile: &str, group: &str); + + /// Cause profile to delete contact/group identified by handle + fn delete_contact(&self, profile: &str, handle: &str); + + /// Cuase profile to attempt to import a contact/group/keybundle identified by bundle fn import_bundle(&self, profile: &str, bundle: &str); + + /// Set a profile attribute key to val fn set_profile_attribute(&self, profile: &str, key: &str, val: &str); + + /// Set a profile's contact's attribute of key to val fn set_contact_attribute(&self, profile: &str, contact: &str, key: &str, val: &str); + + /// Set a profile's group's attribute of key to val fn set_group_attribute(&self, profile: &str, group: &str, key: &str, val: &str); + + /// Shutdown the cwtch app and associated ACN fn shutdown_cwtch(&self, ); } +/// Create a new CwtchLib that is backed by bindings to libcwtch-go pub fn new_cwtchlib_go() -> impl CwtchLib { - bindings_go::GoCwtchLib {} + bindings_go::CwtchLibGo {} } diff --git a/src/structs.rs b/src/structs.rs index 7affa49..97ec563 100644 --- a/src/structs.rs +++ b/src/structs.rs @@ -3,39 +3,45 @@ use std::collections::HashMap; #[derive(Serialize, Deserialize, Debug)] #[serde(rename_all = "PascalCase")] +/// Struct to serialize/deserialize events coming off the Cwtch appbus pub struct CwtchEvent { pub event_type: String, - pub event_ID: String, + pub event_ID: String, // event_ID because golang naming converntions in libCwtch-go pub data: HashMap, } #[derive(Serialize, Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +/// Struct to serialize/deserialize contacts coming from libcwtch-go pub struct Contact { pub onion: String, pub name: String, pub status: String, pub authorization: String, - pub isGroup: bool, + pub is_group: bool, //attr: HashMap, } #[derive(Serialize, Deserialize, Debug)] +/// Struct to serialize/deserialize servers coming from libcwtch-go pub struct Server { pub onion: String, pub status: String, } #[derive(Debug)] +/// Struct to serialize/deserialize profiles coming from libcwtch-go pub struct Profile { pub onion: String, pub nick: String, - pub imagePath: String, + pub image_path: String, pub attr: HashMap, pub contacts: HashMap, pub servers: HashMap, } #[derive(Debug, Serialize, Deserialize)] +/// Struct to serialize/deserialize messages sent over Cwtch between profiles / contacts pub struct Message { pub o: i64, pub d: String @@ -45,10 +51,10 @@ impl Profile { pub fn new(identity: &str, name: &str, picture: &str, contacts_json: &str, server_list: &str) -> Profile { let contacts = Profile::process_contacts(contacts_json); let servers = Profile::process_servers(server_list); - Profile{ onion: identity.to_string(), nick: name.to_string(), imagePath: picture.to_string(), attr: Default::default(), contacts: contacts, servers: servers } + Profile{ onion: identity.to_string(), nick: name.to_string(), image_path: picture.to_string(), attr: Default::default(), contacts: contacts, servers: servers } } - pub fn process_contacts(constacts_json: &str) -> HashMap { + fn process_contacts(constacts_json: &str) -> HashMap { let mut contacts: HashMap = HashMap::new(); if constacts_json == "null" { return contacts; @@ -61,7 +67,7 @@ impl Profile { contacts } - pub fn process_servers(servers_json: &str) -> HashMap { + fn process_servers(servers_json: &str) -> HashMap { let mut servers: HashMap = HashMap::new(); if servers_json == "null" { return servers;