diff --git a/conversations/src/common.rs b/conversations/src/common.rs new file mode 100644 index 0000000..55916f0 --- /dev/null +++ b/conversations/src/common.rs @@ -0,0 +1,76 @@ +use std::collections::HashMap; +use std::fmt::Debug; +use std::sync::Arc; + +pub use crate::errors::ChatError; +use crate::types::{AddressedEncryptedPayload, ContentData}; + +pub type ChatId<'a> = &'a str; +pub type ChatIdOwned = Arc; + +pub trait HasChatId: Debug { + fn id(&self) -> ChatId<'_>; +} + +pub trait InboundMessageHandler { + fn handle_frame( + &mut self, + encoded_payload: &[u8], + ) -> Result<(Box, Vec), ChatError>; +} + +pub trait Chat: HasChatId + Debug { + fn send_message(&mut self, content: &[u8]) + -> Result, ChatError>; + + fn remote_id(&self) -> String; +} + +pub struct ChatStore { + chats: HashMap, Box>, + handlers: HashMap, Box>, +} + +impl ChatStore { + pub fn new() -> Self { + Self { + chats: HashMap::new(), + handlers: HashMap::new(), + } + } + + pub fn insert_chat(&mut self, conversation: impl Chat + HasChatId + 'static) -> ChatIdOwned { + let key: ChatIdOwned = Arc::from(conversation.id()); + self.chats.insert(key.clone(), Box::new(conversation)); + key + } + + pub fn register_handler( + &mut self, + handler: impl InboundMessageHandler + HasChatId + 'static, + ) -> ChatIdOwned { + let key: ChatIdOwned = Arc::from(handler.id()); + self.handlers.insert(key.clone(), Box::new(handler)); + key + } + + pub fn get_chat(&self, id: ChatId) -> Option<&(dyn Chat + '_)> { + self.chats.get(id).map(|c| c.as_ref()) + } + + pub fn get_mut_chat(&mut self, id: &str) -> Option<&mut (dyn Chat + '_)> { + Some(self.chats.get_mut(id)?.as_mut()) + } + + pub fn get_handler(&mut self, id: ChatId) -> Option<&mut (dyn InboundMessageHandler + '_)> { + Some(self.handlers.get_mut(id)?.as_mut()) + } + + pub fn chat_ids(&self) -> impl Iterator + '_ { + self.chats.keys().cloned() + } + + pub fn handler_ids(&self) -> impl Iterator + '_ { + self.handlers.keys().cloned() + } +} diff --git a/conversations/src/context.rs b/conversations/src/context.rs index f709a09..6e0f041 100644 --- a/conversations/src/context.rs +++ b/conversations/src/context.rs @@ -1,7 +1,7 @@ use std::{collections::HashMap, rc::Rc, sync::Arc}; use crate::{ - conversation::{ConversationStore, Convo, Id}, + common::{Chat, ChatStore, HasChatId}, errors::ChatError, identity::Identity, inbox::Inbox, @@ -20,9 +20,8 @@ pub type ConvoHandle = u32; // Ctx manages lifetimes of objects to process and generate payloads. pub struct Context { _identity: Rc, - store: ConversationStore, + store: ChatStore, inbox: Inbox, - buf_size: usize, convo_handle_map: HashMap>, next_convo_handle: ConvoHandle, } @@ -33,22 +32,13 @@ impl Context { let inbox = Inbox::new(Rc::clone(&identity)); // Self { _identity: identity, - store: ConversationStore::new(), + store: ChatStore::new(), inbox, - buf_size: 0, convo_handle_map: HashMap::new(), next_convo_handle: INITIAL_CONVO_HANDLE, } } - pub fn buffer_size(&self) -> usize { - self.buf_size - } - - pub fn set_buffer_size(&mut self, size: usize) { - self.buf_size = size - } - pub fn create_private_convo( &mut self, remote_bundle: &Introduction, @@ -99,17 +89,17 @@ impl Context { Ok(Introduction::from(pkb).into()) } - fn add_convo(&mut self, convo: impl Convo + Id + 'static) -> ConvoHandle { + fn add_convo(&mut self, convo: impl Chat + HasChatId + 'static) -> ConvoHandle { let handle = self.next_convo_handle; self.next_convo_handle += 1; - let convo_id = self.store.insert_convo(convo); + let convo_id = self.store.insert_chat(convo); self.convo_handle_map.insert(handle, convo_id); handle } // Returns a mutable reference to a Convo for a given ConvoHandle - fn get_convo_mut(&mut self, handle: ConvoHandle) -> Result<&mut dyn Convo, ChatError> { + fn get_convo_mut(&mut self, handle: ConvoHandle) -> Result<&mut dyn Chat, ChatError> { let convo_id = self .convo_handle_map .get(&handle) @@ -117,25 +107,7 @@ impl Context { .clone(); self.store - .get_mut(&convo_id) + .get_mut_chat(&convo_id) .ok_or_else(|| ChatError::NoConvo(handle)) } } - -#[cfg(test)] - -mod tests { - use super::*; - use crate::conversation::GroupTestConvo; - - #[test] - fn convo_store_get() { - let mut store: ConversationStore = ConversationStore::new(); - - let new_convo = GroupTestConvo::new(); - let convo_id = store.insert_convo(new_convo); - - let convo = store.get_mut(&convo_id).ok_or_else(|| 0); - convo.unwrap(); - } -} diff --git a/conversations/src/conversation.rs b/conversations/src/conversation.rs deleted file mode 100644 index 61a5d79..0000000 --- a/conversations/src/conversation.rs +++ /dev/null @@ -1,83 +0,0 @@ -use std::collections::HashMap; -use std::fmt::Debug; -use std::sync::Arc; - -pub use crate::errors::ChatError; -use crate::types::{AddressedEncryptedPayload, ContentData}; - -pub type ConversationId<'a> = &'a str; -pub type ConversationIdOwned = Arc; - -pub trait Id: Debug { - fn id(&self) -> ConversationId; -} - -pub trait ConvoFactory: Id + Debug { - fn handle_frame( - &mut self, - encoded_payload: &[u8], - ) -> Result<(Box, Vec), ChatError>; -} - -pub trait Convo: Id + Debug { - fn send_message(&mut self, content: &[u8]) - -> Result, ChatError>; - - fn remote_id(&self) -> String; -} - -pub struct ConversationStore { - conversations: HashMap, Box>, - factories: HashMap, Box>, -} - -impl ConversationStore { - pub fn new() -> Self { - Self { - conversations: HashMap::new(), - factories: HashMap::new(), - } - } - - pub fn insert_convo(&mut self, conversation: impl Convo + Id + 'static) -> ConversationIdOwned { - let key: ConversationIdOwned = Arc::from(conversation.id()); - self.conversations - .insert(key.clone(), Box::new(conversation)); - key - } - - pub fn register_factory( - &mut self, - handler: impl ConvoFactory + Id + 'static, - ) -> ConversationIdOwned { - let key: ConversationIdOwned = Arc::from(handler.id()); - self.factories.insert(key.clone(), Box::new(handler)); - key - } - - pub fn get(&self, id: ConversationId) -> Option<&(dyn Convo + '_)> { - self.conversations.get(id).map(|c| c.as_ref()) - } - - pub fn get_mut(&mut self, id: &str) -> Option<&mut (dyn Convo + '_)> { - Some(self.conversations.get_mut(id)?.as_mut()) - } - - pub fn get_factory(&mut self, id: ConversationId) -> Option<&mut (dyn ConvoFactory + '_)> { - Some(self.factories.get_mut(id)?.as_mut()) - } - - pub fn conversation_ids(&self) -> impl Iterator + '_ { - self.conversations.keys().cloned() - } - - pub fn factory_ids(&self) -> impl Iterator + '_ { - self.factories.keys().cloned() - } -} - -mod group_test; -mod privatev1; - -pub use group_test::GroupTestConvo; -pub use privatev1::PrivateV1Convo; diff --git a/conversations/src/conversation/group_test.rs b/conversations/src/conversation/group_test.rs deleted file mode 100644 index 934ab1c..0000000 --- a/conversations/src/conversation/group_test.rs +++ /dev/null @@ -1,33 +0,0 @@ -use crate::{ - conversation::{ChatError, ConversationId, Convo, Id}, - types::AddressedEncryptedPayload, -}; - -#[derive(Debug)] -pub struct GroupTestConvo {} - -impl GroupTestConvo { - pub fn new() -> Self { - Self {} - } -} - -impl Id for GroupTestConvo { - fn id(&self) -> ConversationId { - // implementation - "grouptest" - } -} - -impl Convo for GroupTestConvo { - fn send_message( - &mut self, - _content: &[u8], - ) -> Result, ChatError> { - Ok(vec![]) - } - - fn remote_id(&self) -> String { - self.id().to_string() - } -} diff --git a/conversations/src/crypto.rs b/conversations/src/crypto.rs deleted file mode 100644 index ecf0d11..0000000 --- a/conversations/src/crypto.rs +++ /dev/null @@ -1,17 +0,0 @@ -pub use blake2::Digest; -use blake2::{Blake2b, digest}; -use prost::bytes::Bytes; -pub use x25519_dalek::{PublicKey, StaticSecret}; - -pub trait CopyBytes { - fn copy_to_bytes(&self) -> Bytes; -} - -impl CopyBytes for PublicKey { - fn copy_to_bytes(&self) -> Bytes { - Bytes::copy_from_slice(self.as_bytes()) - } -} - -#[allow(dead_code)] -pub type Blake2b128 = Blake2b; diff --git a/conversations/src/dm/mod.rs b/conversations/src/dm/mod.rs new file mode 100644 index 0000000..646c64c --- /dev/null +++ b/conversations/src/dm/mod.rs @@ -0,0 +1 @@ +pub mod privatev1; diff --git a/conversations/src/conversation/privatev1.rs b/conversations/src/dm/privatev1.rs similarity index 96% rename from conversations/src/conversation/privatev1.rs rename to conversations/src/dm/privatev1.rs index 704918b..503a747 100644 --- a/conversations/src/conversation/privatev1.rs +++ b/conversations/src/dm/privatev1.rs @@ -9,8 +9,8 @@ use std::fmt::Debug; use x25519_dalek::PublicKey; use crate::{ - conversation::{ChatError, ConversationId, Convo, Id}, - errors::EncryptionError, + common::{Chat, ChatId, HasChatId}, + errors::{ChatError, EncryptionError}, proto, types::AddressedEncryptedPayload, utils::timestamp_millis, @@ -91,14 +91,14 @@ impl PrivateV1Convo { } } -impl Id for PrivateV1Convo { - fn id(&self) -> ConversationId { +impl HasChatId for PrivateV1Convo { + fn id(&self) -> ChatId<'_> { // TODO: implementation "private_v1_convo_id" } } -impl Convo for PrivateV1Convo { +impl Chat for PrivateV1Convo { fn send_message( &mut self, content: &[u8], diff --git a/conversations/src/api.rs b/conversations/src/ffi/api.rs similarity index 100% rename from conversations/src/api.rs rename to conversations/src/ffi/api.rs diff --git a/conversations/src/ffi/mod.rs b/conversations/src/ffi/mod.rs new file mode 100644 index 0000000..e5fdf85 --- /dev/null +++ b/conversations/src/ffi/mod.rs @@ -0,0 +1 @@ +pub mod api; diff --git a/conversations/src/group/mod.rs b/conversations/src/group/mod.rs new file mode 100644 index 0000000..dc53af3 --- /dev/null +++ b/conversations/src/group/mod.rs @@ -0,0 +1 @@ +// Group chat module diff --git a/conversations/src/identity.rs b/conversations/src/identity.rs index c5646a7..42316f7 100644 --- a/conversations/src/identity.rs +++ b/conversations/src/identity.rs @@ -1,7 +1,6 @@ use blake2::{Blake2b512, Digest}; use std::fmt; - -use crate::crypto::{PublicKey, StaticSecret}; +pub use x25519_dalek::{PublicKey, StaticSecret}; pub struct Identity { secret: StaticSecret, diff --git a/conversations/src/inbox/handshake.rs b/conversations/src/inbox/handshake.rs index e0576f4..a2a720b 100644 --- a/conversations/src/inbox/handshake.rs +++ b/conversations/src/inbox/handshake.rs @@ -5,7 +5,7 @@ use blake2::{ use crypto::{DomainSeparator, PrekeyBundle, SecretKey, X3Handshake}; use rand_core::{CryptoRng, RngCore}; -use crate::crypto::{PublicKey, StaticSecret}; +use crate::identity::{PublicKey, StaticSecret}; type Blake2bMac256 = Blake2bMac; diff --git a/conversations/src/inbox/inbox.rs b/conversations/src/inbox/inbox.rs index c19fd39..b3a8d1b 100644 --- a/conversations/src/inbox/inbox.rs +++ b/conversations/src/inbox/inbox.rs @@ -1,4 +1,3 @@ -use double_ratchets::InstallationKeyPair; use hex; use prost::Message; use prost::bytes::Bytes; @@ -8,12 +7,14 @@ use std::rc::Rc; use crypto::{PrekeyBundle, SecretKey}; +use crate::common::{Chat, ChatId, HasChatId, InboundMessageHandler}; use crate::context::Introduction; -use crate::conversation::{ChatError, ConversationId, Convo, ConvoFactory, Id, PrivateV1Convo}; -use crate::crypto::{Blake2b128, CopyBytes, Digest, PublicKey, StaticSecret}; +use crate::dm::privatev1::PrivateV1Convo; +use crate::errors::ChatError; use crate::identity::Identity; +use crate::identity::{PublicKey, StaticSecret}; use crate::inbox::handshake::InboxHandshake; -use crate::proto; +use crate::proto::{self, CopyBytes}; use crate::types::{AddressedEncryptedPayload, ContentData}; /// Compute the deterministic Delivery_address for an installation @@ -51,11 +52,6 @@ impl Inbox { } } - fn compute_local_convo_id(addr: &str) -> String { - let hash = Blake2b128::digest(format!("{}:{}:{}", "logoschat", "inboxV1", addr)); - hex::encode(hash) - } - pub fn create_bundle(&mut self) -> PrekeyBundle { let ephemeral = StaticSecret::random(); @@ -180,6 +176,7 @@ impl Inbox { Ok(handshake) } + #[allow(dead_code)] fn decrypt_frame( enc_payload: proto::InboxHandshakeV1, ) -> Result { @@ -196,17 +193,17 @@ impl Inbox { } } -impl Id for Inbox { - fn id(&self) -> ConversationId { +impl HasChatId for Inbox { + fn id(&self) -> ChatId<'_> { &self.local_convo_id } } -impl ConvoFactory for Inbox { +impl InboundMessageHandler for Inbox { fn handle_frame( &mut self, message: &[u8], - ) -> Result<(Box, Vec), ChatError> { + ) -> Result<(Box, Vec), ChatError> { if message.len() == 0 { return Err(ChatError::Protocol("Example error".into())); } diff --git a/conversations/src/inbox.rs b/conversations/src/inbox/mod.rs similarity index 100% rename from conversations/src/inbox.rs rename to conversations/src/inbox/mod.rs diff --git a/conversations/src/lib.rs b/conversations/src/lib.rs index 91aab32..c40aaa0 100644 --- a/conversations/src/lib.rs +++ b/conversations/src/lib.rs @@ -1,20 +1,23 @@ -mod api; +pub mod common; +pub mod dm; +pub mod ffi; +pub mod group; +pub mod inbox; + mod context; -mod conversation; -mod crypto; mod errors; mod identity; -mod inbox; mod proto; mod types; mod utils; -pub use api::*; - #[cfg(test)] mod tests { - use super::*; + use crate::ffi::api::{ + create_context, create_intro_bundle, create_new_private_convo, destroy_context, + handle_payload, + }; use std::str::FromStr; #[test] diff --git a/conversations/src/proto.rs b/conversations/src/proto.rs index 18f3119..900d1ab 100644 --- a/conversations/src/proto.rs +++ b/conversations/src/proto.rs @@ -7,3 +7,14 @@ pub use chat_proto::logoschat::invite::InvitePrivateV1; pub use prost::Message; pub use prost::bytes::Bytes; +use x25519_dalek::PublicKey; + +pub trait CopyBytes { + fn copy_to_bytes(&self) -> Bytes; +} + +impl CopyBytes for PublicKey { + fn copy_to_bytes(&self) -> Bytes { + Bytes::copy_from_slice(self.as_bytes()) + } +} diff --git a/conversations/src/types.rs b/conversations/src/types.rs index 254ab9e..da44f38 100644 --- a/conversations/src/types.rs +++ b/conversations/src/types.rs @@ -1,7 +1,5 @@ use crate::proto::{self, Message}; -// FFI Type definitions - // This struct represents Outbound data. // It wraps an encoded payload with a delivery address, so it can be handled by the delivery service. pub struct AddressedEnvelope { @@ -19,7 +17,7 @@ pub struct ContentData { // Internal type Definitions // Used by Conversations to attach addresses to outbound encrypted payloads -pub(crate) struct AddressedEncryptedPayload { +pub struct AddressedEncryptedPayload { pub delivery_address: String, pub data: proto::EncryptedPayload, }