adding documentations and examples and tweeks to be more rustful

This commit is contained in:
Dan Ballard 2021-09-08 00:32:56 -07:00
parent f00daf245b
commit 652526fffb
8 changed files with 137 additions and 37 deletions

View File

@ -3,8 +3,9 @@ name = "libcwtch"
version = "0.1.0" version = "0.1.0"
authors = ["Dan Ballard <dan@mindstab.net>"] authors = ["Dan Ballard <dan@mindstab.net>"]
edition = "2018" edition = "2018"
license = "MIT"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 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] [dependencies]
libc = "0.2" libc = "0.2"

View File

@ -1,6 +1,8 @@
# libCwtch-rs # 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 ## 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: 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 ### Todo

51
examples/echobot.rs Normal file
View File

@ -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();
}

View File

@ -5,12 +5,9 @@
use std::ffi::{CString}; use std::ffi::{CString};
use std::ffi::{CStr}; use std::ffi::{CStr};
use std::collections::HashMap;
use serde::{Deserialize, Serialize};
use super::{CwtchLib}; use super::{CwtchLib};
use crate::gobindings; use crate::cwtchlib_go::bindings;
struct c_str_wrap { struct c_str_wrap {
raw: *mut i8, 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 handles setting up c string arguments and freeing them
// c_bind!( $fn_name ( [ $string_args ]* ; [ $non_string_args : $type ]* ) $c_function -> $return_type? ) // c_bind!( $fn_name ( [ $string_args ]* ; [ $non_string_args : $type ]* ) $c_function -> $return_type? )
#[macro_export]
macro_rules! c_bind { macro_rules! c_bind {
// macro for returnless fns // macro for returnless fns
($func_name:ident ($($str:ident),* ; $($arg:ident: $t:ty),*) $bind_fn:ident) => { ($func_name:ident ($($str:ident),* ; $($arg:ident: $t:ty),*) $bind_fn:ident) => {
fn $func_name(&self, $($str: &str, )* $($arg: $t, )*) { fn $func_name(&self, $($str: &str, )* $($arg: $t, )*) {
$(let $str = c_str_wrap::new($str);)* $(let $str = c_str_wrap::new($str);)*
unsafe { 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 { fn $func_name(&self, $($str: &str, )* $($arg: $t, )*) -> String {
$(let $str = c_str_wrap::new($str);)* $(let $str = c_str_wrap::new($str);)*
unsafe { 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() { let result = match CStr::from_ptr(result_ptr).to_str() {
Ok(s) => s.to_owned(), Ok(s) => s.to_owned(),
Err(_) => "".to_string() Err(_) => "".to_string()
}; };
// return ownership of string memory and call the library to free it // return ownership of string memory and call the library to free it
gobindings::c_FreePointer(result_ptr); bindings::c_FreePointer(result_ptr);
result result
} }
} }
@ -67,26 +63,20 @@ macro_rules! c_bind {
fn $func_name(&self, $($str: &str, )* $($arg: $t, )*) -> $bind_fn_ty { fn $func_name(&self, $($str: &str, )* $($arg: $t, )*) -> $bind_fn_ty {
$(let $str = c_str_wrap::new($str);)* $(let $str = c_str_wrap::new($str);)*
unsafe { unsafe {
let result = gobindings::$bind_fn($( $str.raw, $str.len, )* $($arg,)* ); let result = bindings::$bind_fn($( $str.raw, $str.len, )* $($arg,)* );
result result
} }
} }
}; };
} }
#[derive(Serialize, Deserialize, Debug)] pub struct CwtchLibGo {}
struct Event {
EventType: String,
Data: HashMap<String, String>
}
pub struct GoCwtchLib {} impl CwtchLibGo {
impl GoCwtchLib {
c_bind!(send_profile_event(profile, event_json;) c_SendProfileEvent); 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!(start_cwtch(app_dir, tor_path;) c_StartCwtch -> i32);
c_bind!(send_app_event(event_json;) c_SendAppEvent); c_bind!(send_app_event(event_json;) c_SendAppEvent);
c_bind!(get_appbus_event(;) c_GetAppBusEvent -> String); 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_message(profile, contact, msg;) c_SendMessage);
c_bind!(send_invitation(profile, contact, target;) c_SendInvitation); c_bind!(send_invitation(profile, contact, target;) c_SendInvitation);
fn reset_tor(&self) { fn reset_tor(&self) {
unsafe { gobindings::c_ResetTor(); } unsafe { bindings::c_ResetTor(); }
} }
c_bind!(create_group(profile, server, name;) c_CreateGroup); c_bind!(create_group(profile, server, name;) c_CreateGroup);
c_bind!(delete_profile(profile, pass;) c_DeleteProfile); 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); c_bind!(set_group_attribute(profile, group, key, val;) c_SetGroupAttribute);
fn shutdown_cwtch(&self) { fn shutdown_cwtch(&self) {
unsafe { gobindings::c_ShutdownCwtch(); } unsafe { bindings::c_ShutdownCwtch(); }
} }
} }

6
src/cwtchlib_go/mod.rs Normal file
View File

@ -0,0 +1,6 @@
#![allow(non_upper_case_globals)]
#![allow(non_camel_case_types)]
#![allow(non_snake_case)]
#![allow(dead_code)]
pub mod bindings;

View File

@ -1,40 +1,84 @@
#![allow(non_upper_case_globals)] #![doc(html_logo_url = "https://git.openprivacy.ca/cwtch.im/cwtch-ui/media/branch/trunk/cwtch.png")]
#![allow(non_camel_case_types)]
#![allow(non_snake_case)]
#![allow(dead_code)]
mod gobindings; mod cwtchlib_go;
mod bindings_go; mod bindings_go;
pub mod structs; pub mod structs;
/// Interface to a Cwtch app with API matching libcwtch
pub trait CwtchLib { 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; 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); 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; fn get_appbus_event(&self, ) -> String;
/// Create a new profile encrypted with pass
fn create_profile(&self, nick: &str, pass: &str); fn create_profile(&self, nick: &str, pass: &str);
/// Load any profiles encrypted by pass
fn load_profiles(&self, pass: &str); fn load_profiles(&self, pass: &str);
/// Cause profile to accept contact
fn accept_contact(&self, profile: &str, contact: &str); fn accept_contact(&self, profile: &str, contact: &str);
/// Cause profile to reject contact
fn reject_invite(&self, profile: &str, contact: &str); fn reject_invite(&self, profile: &str, contact: &str);
/// Cause profile to block contact
fn block_contact(&self, profile: &str, contact: &str); 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); 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; 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; 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); 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); 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, ); fn reset_tor(&self, );
/// Cause profile to create a group on server with name
fn create_group(&self, profile: &str, server: &str, name: &str); 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); fn delete_profile(&self, profile: &str, pass: &str);
/// Cause profile to archive conversation with contact
fn archive_conversation(&self, profile: &str, contact: &str); 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); 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); 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); 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); fn set_group_attribute(&self, profile: &str, group: &str, key: &str, val: &str);
/// Shutdown the cwtch app and associated ACN
fn shutdown_cwtch(&self, ); fn shutdown_cwtch(&self, );
} }
/// Create a new CwtchLib that is backed by bindings to libcwtch-go
pub fn new_cwtchlib_go() -> impl CwtchLib { pub fn new_cwtchlib_go() -> impl CwtchLib {
bindings_go::GoCwtchLib {} bindings_go::CwtchLibGo {}
} }

View File

@ -3,39 +3,45 @@ use std::collections::HashMap;
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "PascalCase")] #[serde(rename_all = "PascalCase")]
/// Struct to serialize/deserialize events coming off the Cwtch appbus
pub struct CwtchEvent { pub struct CwtchEvent {
pub event_type: String, pub event_type: String,
pub event_ID: String, pub event_ID: String, // event_ID because golang naming converntions in libCwtch-go
pub data: HashMap<String, String>, pub data: HashMap<String, String>,
} }
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
/// Struct to serialize/deserialize contacts coming from libcwtch-go
pub struct Contact { pub struct Contact {
pub onion: String, pub onion: String,
pub name: String, pub name: String,
pub status: String, pub status: String,
pub authorization: String, pub authorization: String,
pub isGroup: bool, pub is_group: bool,
//attr: HashMap<String, String>, //attr: HashMap<String, String>,
} }
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
/// Struct to serialize/deserialize servers coming from libcwtch-go
pub struct Server { pub struct Server {
pub onion: String, pub onion: String,
pub status: String, pub status: String,
} }
#[derive(Debug)] #[derive(Debug)]
/// Struct to serialize/deserialize profiles coming from libcwtch-go
pub struct Profile { pub struct Profile {
pub onion: String, pub onion: String,
pub nick: String, pub nick: String,
pub imagePath: String, pub image_path: String,
pub attr: HashMap<String,String>, pub attr: HashMap<String,String>,
pub contacts: HashMap<String, Contact>, pub contacts: HashMap<String, Contact>,
pub servers: HashMap<String, Server>, pub servers: HashMap<String, Server>,
} }
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
/// Struct to serialize/deserialize messages sent over Cwtch between profiles / contacts
pub struct Message { pub struct Message {
pub o: i64, pub o: i64,
pub d: String 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 { pub fn new(identity: &str, name: &str, picture: &str, contacts_json: &str, server_list: &str) -> Profile {
let contacts = Profile::process_contacts(contacts_json); let contacts = Profile::process_contacts(contacts_json);
let servers = Profile::process_servers(server_list); 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<String, Contact> { fn process_contacts(constacts_json: &str) -> HashMap<String, Contact> {
let mut contacts: HashMap<String, Contact> = HashMap::new(); let mut contacts: HashMap<String, Contact> = HashMap::new();
if constacts_json == "null" { if constacts_json == "null" {
return contacts; return contacts;
@ -61,7 +67,7 @@ impl Profile {
contacts contacts
} }
pub fn process_servers(servers_json: &str) -> HashMap<String, Server> { fn process_servers(servers_json: &str) -> HashMap<String, Server> {
let mut servers: HashMap<String, Server> = HashMap::new(); let mut servers: HashMap<String, Server> = HashMap::new();
if servers_json == "null" { if servers_json == "null" {
return servers; return servers;