From 577d8999cfda2946b60f3f69fda37a960d90faa1 Mon Sep 17 00:00:00 2001 From: Dan Ballard Date: Wed, 6 Jul 2022 23:52:30 -0700 Subject: [PATCH] add events wrapping enum --- Cargo.lock | 78 ++++++++ Cargo.toml | 1 + src/bindings_go.rs | 9 +- src/event.rs | 476 +++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 5 +- src/structs.rs | 4 +- 6 files changed, 569 insertions(+), 4 deletions(-) create mode 100644 src/event.rs diff --git a/Cargo.lock b/Cargo.lock index 91faa43..8e7149e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,12 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + [[package]] name = "block-buffer" version = "0.10.2" @@ -17,6 +23,19 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "chrono" +version = "0.4.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" +dependencies = [ + "libc", + "num-integer", + "num-traits", + "time", + "winapi", +] + [[package]] name = "cpufeatures" version = "0.2.1" @@ -125,6 +144,7 @@ checksum = "1b03d17f364a3a042d5e5d46b053bbbf82c92c9430c592dd4c064dc6ee997125" name = "libcwtch" version = "0.3.2" dependencies = [ + "chrono", "hex-literal", "libc", "serde", @@ -133,6 +153,25 @@ dependencies = [ "sha2", ] +[[package]] +name = "num-integer" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +dependencies = [ + "autocfg", +] + [[package]] name = "proc-macro2" version = "1.0.36" @@ -245,6 +284,17 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "time" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" +dependencies = [ + "libc", + "wasi", + "winapi", +] + [[package]] name = "typenum" version = "1.15.0" @@ -262,3 +312,31 @@ name = "version_check" version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "wasi" +version = "0.10.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/Cargo.toml b/Cargo.toml index fc9b081..c63047d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,3 +18,4 @@ libc = "0.2" serde_json = "1.0" serde = { version = "1.0.127", features = ["derive"] } serde_with = { version = "1.10.0" } +chrono = "0.4.19" diff --git a/src/bindings_go.rs b/src/bindings_go.rs index f3ad8f7..d6b584b 100644 --- a/src/bindings_go.rs +++ b/src/bindings_go.rs @@ -9,6 +9,7 @@ use std::ffi::CString; use super::CwtchLib; use crate::cwtchlib_go::bindings; use crate::{CwtchError, structs::*}; +use crate::event::Event; struct c_str_wrap { raw: *mut i8, @@ -86,6 +87,7 @@ pub struct CwtchLibGo {} 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); + c_bind!(_get_appbus_event(;;) c_GetAppBusEvent -> String); } impl CwtchLib for CwtchLibGo { @@ -93,7 +95,6 @@ impl CwtchLib for CwtchLibGo { 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); c_bind!(create_profile(nick: &str, pass: &str;;) c_CreateProfile); c_bind!(load_profiles(pass: &str;;) c_LoadProfiles); c_bind!(accept_conversation(profile: &str ; conversation_id: i32 ;) c_AcceptConversation); @@ -164,4 +165,10 @@ impl CwtchLib for CwtchLibGo { c_bind!(destroy_servers(;;) c_DestroyServers); c_bind!(set_server_attribute(onion: &str, key: &str, val: &str;;) c_SetServerAttribute); 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) + } } diff --git a/src/event.rs b/src/event.rs new file mode 100644 index 0000000..d1602bd --- /dev/null +++ b/src/event.rs @@ -0,0 +1,476 @@ +use std::collections::HashMap; + +use chrono::{DateTime, FixedOffset}; +use chrono::format::Fixed::TimezoneOffset; +use chrono::prelude::*; +use chrono::offset::LocalResult; + +use crate::structs::{ACL, ConnectionState, CwtchEvent}; + +#[derive(Debug)] +pub enum MessageNotification { + // NotificationNone enum for message["notification"] that means no notification + None, + // NotificationEvent enum for message["notification"] that means emit a notification that a message event happened only + SimpleEvent, + // NotificationConversation enum for message["notification"] that means emit a notification event with Conversation handle included + ContactInfo, +} + +impl From for MessageNotification { + fn from(str: String) -> MessageNotification { + match str.as_str() { + "None" => MessageNotification::None, + "SimpleEvent" => MessageNotification::SimpleEvent, + "ContactInfo" => MessageNotification::ContactInfo, + _ => MessageNotification::None, + } + } +} + +#[derive(Debug)] +pub enum NetworkCheckStatus { + Error, + Success +} + +impl From for NetworkCheckStatus { + fn from(str: String) -> NetworkCheckStatus { + match str.as_str() { + "Error" => NetworkCheckStatus::Error, + "Success" => NetworkCheckStatus::Success, + _ => NetworkCheckStatus::Error, + } + } +} + +#[derive(Debug)] +pub enum ServerStorageType { + DefaultPassword, + Password, + NoPassword +} + +impl From for ServerStorageType { + fn from(str: String) -> ServerStorageType { + match str.as_str() { + "storage-default-password" => ServerStorageType::DefaultPassword, + "storage-password" => ServerStorageType::Password, + "storage-no-password" => ServerStorageType::NoPassword, + _ => ServerStorageType::DefaultPassword, + } + } +} + +#[derive(Debug)] +pub enum ServerIntent { + Running, + Stopped +} + +impl From for ServerIntent { + fn from(str: String) -> ServerIntent { + match str.as_str() { + "running" => ServerIntent::Running, + "stopped" => ServerIntent::Stopped, + _ => ServerIntent::Stopped, + } + } +} + +#[derive(Debug)] +pub enum Event { + + // App events + + CwtchStarted, + NewPeer { + identity: String, + tag: String, + created: bool, + name: String, + default_picture: String, + picture: String, + online: String, + contacts_json: String, + server_json: String, //ServerList + }, + AppError { + error: String + }, + UpdateGlobalSettings { + settings: HashMap, + }, + ErrUnhandled { + name: String, + data: HashMap, + }, + PeerError { + error: String + }, + PeerDeleted { + identity: String + }, + Shutdown, + ACNStatus { + progress: i8, + status: String + }, + ACNVersion { + verstion: String, + }, + StartingStorageMiragtion, + DoneStorageMigration, + + // app server events + + NewServer { + onion: String, + server_bundle: String, + description: String, + storage_type: ServerStorageType, + autostart: bool, + running: bool, + }, + ServerIntentUpdate { + identity: String, + intent: ServerIntent + }, + ServerDeleted { + identity: String, + success: bool, + error: Option, + }, + ServerStatsUpdate { + identity: String, + total_messages: i32, + connections: i32, + }, + + // profile events + + NewMessageFromPeer { + conversation_id: i32, + handle: String, + nick: String, + timestamp_received: DateTime, + message: String, + notification: MessageNotification, + picture: String, + }, + ContactCreated { + conversation_id: i32, + remote_peer: String, + nick: String, + status: ConnectionState, + unread: i32, + picture: String, + default_picture: String, + num_messages: i32, + accepted: bool, + access_control_list: ACL, + blocked: bool, + loading: bool, + last_msg_time: DateTime, + + }, + PeerStateChange { + remote_peer: String, + connection_state: ConnectionState, + }, + NetworkStatus { + address: String, + error: String, + status: NetworkCheckStatus, + }, + ACNInfo { + handle: String, + key: String, + data: String, + }, + UpdatedProfileAttribute { + key: String, + value: String, + }, + IndexedAcknowledgement { + conversation_id: i32, + index: i32, + }, + IndexedFailure { + conversation_id: i32, + index: i32, + handle: String, + error: String + }, + PeerAcknowledgement { + event_id: String, + remote_peer: String, + conversation_id: i32, + }, + NewMessageFromGroup { + conversation_id: i32, + timestamp_sent: DateTime, + remote_peer: String, + index: i32, + message: String, + content_hash: String, + picture: String, + nick: String, + notification: MessageNotification, + }, + GroupCreated { + conversation_id: i32, + group_id: String, + group_server: String, + group_name: String, + picture: String, + access_control_list: ACL, + }, + NewGroup { + conversation_id: i32, + group_id: String, + group_server: String, + group_invite: String, + group_name: String, + picture: String, + access_control_list: ACL, + }, + ServerStateChange { + group_server: String, + state: ConnectionState + }, + NewRetValMessageFromPeer { + remote_peer: String, + scope: String, + path: String, + exists: bool, + data: String, + file_path: Option + }, + ShareManifest { + filekey: String, + serializedManifest: String, + }, + ManifestSizeReceived { + filekey: String, + manifest_size: i32, + handle: String, + }, + ManifestError { + handle: String, + filekey: String, + }, + ManifestReceived { + handle: String, + filekey: String, + serialized_manifest: String, + }, + ManifestSaved { + handle: String, + filekey: String, + serialized_manifest: String, + temp_file: String, + name_suggestion: String, + }, + FileDownloadProgressUpdate { + filekey: String, + progress: i32, + filesize_in_chunks: i32, + name_suggestion: String, + }, + // FileDownloaded, ?? +} + +impl From<&CwtchEvent> for Event { + fn from(cwtch_event: &CwtchEvent) -> Self { + match cwtch_event.event_type.as_str() { + "CwtchStarted" => Event::CwtchStarted, + "NewPeer" => Event::NewPeer { + identity: cwtch_event.data["Identity"].clone(), + tag: cwtch_event.data["tag"].clone(), + created: cwtch_event.data["Created"] == "true", + name: cwtch_event.data["name"].clone(), + default_picture: cwtch_event.data["defaultPicture"].clone(), + picture: cwtch_event.data["picture"].clone(), + online: cwtch_event.data["Online"].clone(), + contacts_json: cwtch_event.data["ContactsJson"].clone(), + server_json: cwtch_event.data["ServerList"].clone(), + }, + "NewMessageFromPeer" => Event::NewMessageFromPeer { + conversation_id: cwtch_event.data["ConversationID"].parse().unwrap_or(-2), + handle: cwtch_event.data["RemotePeer"].clone(), + nick: cwtch_event.data["Nick"].clone(), + timestamp_received: DateTime::parse_from_rfc3339(cwtch_event.data["TimestampReceived"].as_str()).unwrap_or( DateTime::from(Utc::now())), + message: cwtch_event.data["Data"].clone(), + notification: MessageNotification::from(cwtch_event.data["notification"].clone()), + picture: cwtch_event.data["Picture"].clone(), + }, + "PeerError" => Event::PeerError { error: cwtch_event.data["Error"].clone() }, + "AppError" => Event::AppError { + error: cwtch_event.data["Error"].clone(), + }, + "ContactCreated" => Event::ContactCreated { + conversation_id: cwtch_event.data["ConversationID"].clone().parse().unwrap_or(-2), + remote_peer: cwtch_event.data["RemotePeer"].clone(), + unread: cwtch_event.data["unread"].clone().parse().unwrap_or(0), + picture: cwtch_event.data["picture"].clone(), + default_picture: cwtch_event.data["defaultPicture"].clone(), + num_messages: cwtch_event.data["numMessages"].clone().parse().unwrap_or(0), + nick: cwtch_event.data["nick"].clone(), + accepted: cwtch_event.data["accepted"].clone().parse().unwrap_or(false), + status: ConnectionState::from(cwtch_event.data["status"].as_str()), + access_control_list: serde_json::from_str(cwtch_event.data["accessControlList"].as_str()).unwrap_or(ACL::new()), + blocked: cwtch_event.data["blocked"].clone().parse().unwrap_or(false), + loading: cwtch_event.data["loading"].clone().parse().unwrap_or(false), + last_msg_time: DateTime::parse_from_rfc3339(cwtch_event.data["lastMsgTime"].as_str()).unwrap_or(DateTime::from(Utc::now())), + }, + "PeerStateChange" => Event::PeerStateChange { + remote_peer: cwtch_event.data["RemotePeer"].clone(), + connection_state: ConnectionState::from(cwtch_event.data["ConnectionState"].as_str()), + }, + "UpdateGlobalSettings" => Event::UpdateGlobalSettings { + settings: serde_json::from_str(cwtch_event.data["Data"].as_str()).unwrap_or(HashMap::new()), + }, + "PeerDeleted" => Event::PeerDeleted { identity: cwtch_event.data["Identity"].clone() }, + + "ACNStatus" => Event::ACNStatus { + progress: cwtch_event.data["Progress"].parse().unwrap_or(0), + status: cwtch_event.data["Status"].clone(), + }, + "ACNVersion" => Event::ACNVersion { verstion: cwtch_event.data["Data"].clone()}, + "NetworkError" => Event::NetworkStatus { + address: cwtch_event.data["Onion"].clone(), + error: cwtch_event.data["Error"].clone(), + status: NetworkCheckStatus::from(cwtch_event.data["Status"].clone()) + }, + "ACNInfo" => Event::ACNInfo { + handle: cwtch_event.data["Handle"].clone(), + key: cwtch_event.data["Key"].clone(), + data: cwtch_event.data["Data"].clone(), + }, + "UpdatedProfileAttribute" => Event::UpdatedProfileAttribute { + key: cwtch_event.data["Key"].clone(), + value: cwtch_event.data["Data"].clone(), + }, + "IndexedAcknowledgement" => Event::IndexedAcknowledgement { + conversation_id: cwtch_event.data["ConversationID"].parse().unwrap_or(-1), + index: cwtch_event.data["Index"].parse().unwrap_or(-1), + }, + "IndexedAcknowledgement" => Event::IndexedFailure { + conversation_id: cwtch_event.data["ConversationID"].parse().unwrap_or(-1), + index: cwtch_event.data["Index"].parse().unwrap_or(-1), + handle: cwtch_event.data["Handle"].clone(), + error: cwtch_event.data["Error"].clone(), + }, + "ShareManifest" => Event::ShareManifest { + filekey: cwtch_event.data["FileKey"].clone(), + serializedManifest: cwtch_event.data["SerializedManifest"].clone(), + }, + "NewServer" => Event::NewServer { + onion: cwtch_event.data["Onion"].clone(), + server_bundle: cwtch_event.data["ServerBundle"].clone(), + description: cwtch_event.data["Description"].clone(), + storage_type: ServerStorageType::from(cwtch_event.data["StorageType"].clone()), + autostart: cwtch_event.data["Autostart"].parse().unwrap_or(false), + running: cwtch_event.data["Running"].parse().unwrap_or(false), + }, + "ServerIntentUpdate" => Event::ServerIntentUpdate { + identity: cwtch_event.data["Identity"].clone(), + intent: ServerIntent::from(cwtch_event.data["Intent"].clone()) + }, + "ServerDeleted" => Event::ServerDeleted { + identity: cwtch_event.data["Identity"].clone(), + success: cwtch_event.data["Status"].clone() == "success", + error: match cwtch_event.data.get("Error") { + Some(e) => Some(e.clone()), + None => None, + } + }, + "ServerStatsUpdate" => Event::ServerStatsUpdate { + identity: cwtch_event.data["Identity"].clone(), + total_messages: cwtch_event.data["TotalMessages"].parse().unwrap_or(0), + connections: cwtch_event.data["Connections"].parse().unwrap_or(0), + }, + "ManifestSizeReceived" => Event::ManifestSizeReceived { + filekey: cwtch_event.data["FileKey"].clone(), + manifest_size: cwtch_event.data["ManifestSize"].parse().unwrap_or(0), + handle: cwtch_event.data["Handle"].clone(), + }, + "ManifestError" => Event::ManifestError { + handle: cwtch_event.data["Handle"].clone(), + filekey: cwtch_event.data["FileKey"].clone(), + }, + "ManifestReceived" => Event::ManifestReceived { + handle: cwtch_event.data["Handle"].clone(), + filekey: cwtch_event.data["FileKey"].clone(), + serialized_manifest: cwtch_event.data["SerializedManifest"].clone(), + }, + "ManifestSaved" => Event::ManifestSaved { + handle: cwtch_event.data["Handle"].clone(), + filekey: cwtch_event.data["FileKey"].clone(), + serialized_manifest: cwtch_event.data["SerializedManifest"].clone(), + temp_file: cwtch_event.data["TempFile"].clone(), + name_suggestion: cwtch_event.data["NameSuggestion"].clone(), + }, + "FileDownloadProgressUpdate" => Event::FileDownloadProgressUpdate { + filekey: cwtch_event.data["FileKey"].clone(), + progress: cwtch_event.data["Progress"].parse().unwrap_or(0), + filesize_in_chunks: cwtch_event.data["FileSizeInChunks"].parse().unwrap_or(0), + name_suggestion: cwtch_event.data["NameSuggestion"].clone(), + }, + "PeerAcknowledgement" => Event::PeerAcknowledgement { + event_id: cwtch_event.data["EventId"].clone(), + remote_peer: cwtch_event.data["RemotePeer"].clone(), + conversation_id: cwtch_event.data["ConversationID"].parse().unwrap_or(0) + }, + "NewMessageFromGroup" => Event::NewMessageFromGroup { + timestamp_sent: DateTime::parse_from_rfc3339(cwtch_event.data["TimestampSent"].as_str()).unwrap_or( DateTime::from(Utc::now())), + index: cwtch_event.data["RemotePeer"].parse().unwrap_or(-1), + content_hash: cwtch_event.data["ContentHash"].clone(), + conversation_id: cwtch_event.data["ConversationID"].parse().unwrap_or(-2), + remote_peer: cwtch_event.data["RemotePeer"].clone(), + nick: cwtch_event.data["Nick"].clone(), + message: cwtch_event.data["Data"].clone(), + notification: MessageNotification::from(cwtch_event.data["notification"].clone()), + picture: cwtch_event.data["Picture"].clone(), + }, + "GroupCreated" => Event::GroupCreated { + conversation_id: cwtch_event.data["ConversationID"].parse().unwrap_or(-2), + group_id: cwtch_event.data["GroupID"].clone(), + group_server: cwtch_event.data["GroupServer"].clone(), + group_name: cwtch_event.data["GroupName"].clone(), + picture: cwtch_event.data["picture"].clone(), + access_control_list: serde_json::from_str(cwtch_event.data["accessControlList"].as_str()).unwrap_or(ACL::new()), + }, + "NewGroup" => Event::NewGroup { + conversation_id: cwtch_event.data["ConversationID"].parse().unwrap_or(-2), + group_id: cwtch_event.data["GroupID"].clone(), + group_server: cwtch_event.data["GroupServer"].clone(), + group_invite: cwtch_event.data["GroupInvite"].clone(), + group_name: cwtch_event.data["GroupName"].clone(), + picture: cwtch_event.data["picture"].clone(), + access_control_list: serde_json::from_str(cwtch_event.data["accessControlList"].as_str()).unwrap_or(ACL::new()), + }, + "ServerStateChange" => Event::ServerStateChange { + group_server: cwtch_event.data["GroupServer"].clone(), + state: ConnectionState::from(cwtch_event.data["ConnectionState"].as_str()), + }, + "NewRetValMessageFromPeer" => Event::NewRetValMessageFromPeer { + remote_peer: cwtch_event.data["RemotePeer"].clone(), + scope: cwtch_event.data["Scope"].clone(), + path: cwtch_event.data["Path"].clone(), + exists: cwtch_event.data["Exists"].parse().unwrap_or(false), + data: cwtch_event.data["Data"].clone(), + file_path: match cwtch_event.data.get("FilePath") { + Some(fp) => Some(fp.to_string()), + None => None, + }, + }, + + _ => Event::ErrUnhandled { + name: cwtch_event.event_type.to_string(), + data: cwtch_event.data.clone(), + }, + } + } +} diff --git a/src/lib.rs b/src/lib.rs index b84271e..7c1e53c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,11 +3,14 @@ #![doc(html_root_url = "https://git.openprivacy.ca/cwtch.im/libcwtch-rs")] #![deny(missing_docs)] +use crate::event::Event; + mod bindings_go; mod cwtchlib_go; /// Basic structs using data from Cwtch and for deserializing JSON and serializing to JSON to communicate with Cwtch pub mod structs; +pub mod event; /// Error type for Cwtch lib related errors, intended for use with Result pub type CwtchError = String; @@ -27,7 +30,7 @@ pub trait CwtchLib { fn send_profile_event(&self, profile: &str, 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) -> Event; /// Create a new profile encrypted with pass fn create_profile(&self, nick: &str, pass: &str); diff --git a/src/structs.rs b/src/structs.rs index 91b41e6..e3c9b93 100644 --- a/src/structs.rs +++ b/src/structs.rs @@ -29,9 +29,9 @@ impl Default for ConnectionState { } } -impl ConnectionState { +impl From<&str> for ConnectionState { /// Creates a ConnectionState from a string sent from libcwtch-go - pub fn new(name: &str) -> Self { + fn from(name: &str) -> Self { match name { "Disconnected" => ConnectionState::Disconnected, "Connecting" => ConnectionState::Connecting,