diff --git a/conversations/src/api.rs b/conversations/src/api.rs index 2b127d4..a3a8fd5 100644 --- a/conversations/src/api.rs +++ b/conversations/src/api.rs @@ -112,7 +112,15 @@ pub fn send_content( convo_handle: ConvoHandle, content: c_slice::Ref<'_, u8>, ) -> PayloadResult { - let payloads = ctx.0.send_content(convo_handle, &content); + let payloads = match ctx.0.send_content(convo_handle, &content) { + Ok(p) => p, + Err(_) => { + return PayloadResult { + error_code: ErrorCode::UnknownError as i32, + payloads: safer_ffi::Vec::EMPTY, + }; + } + }; let ffi_payloads: Vec = payloads .into_iter() diff --git a/conversations/src/context.rs b/conversations/src/context.rs index b8e845e..f709a09 100644 --- a/conversations/src/context.rs +++ b/conversations/src/context.rs @@ -1,11 +1,11 @@ use std::{collections::HashMap, rc::Rc, sync::Arc}; use crate::{ - conversation::{ConversationId, ConversationStore, Convo, Id}, + conversation::{ConversationStore, Convo, Id}, errors::ChatError, identity::Identity, inbox::Inbox, - types::{ContentData, PayloadData}, + types::{AddressedEnvelope, ContentData}, }; pub use crate::inbox::Introduction; @@ -53,22 +53,37 @@ impl Context { &mut self, remote_bundle: &Introduction, content: String, - ) -> (ConvoHandle, Vec) { + ) -> (ConvoHandle, Vec) { let (convo, payloads) = self .inbox .invite_to_private_convo(remote_bundle, content) .unwrap_or_else(|_| todo!("Log/Surface Error")); + let payload_bytes = payloads + .into_iter() + .map(|p| p.to_envelope(convo.id().to_string())) + .collect(); + let convo_handle = self.add_convo(convo); - (convo_handle, payloads) + (convo_handle, payload_bytes) } - pub fn send_content(&mut self, convo_id: ConvoHandle, _content: &[u8]) -> Vec { - // !TODO Replace Mock - vec![PayloadData { - delivery_address: format!("addr-for-{convo_id}"), - data: vec![40, 30, 20, 10], - }] + pub fn send_content( + &mut self, + convo_handle: ConvoHandle, + content: &[u8], + ) -> Result, ChatError> { + // Lookup convo from handle + let convo = self.get_convo_mut(convo_handle)?; + + // Generate encrypted payloads + let payloads = convo.send_message(content)?; + + // Attach conversation_ids to Envelopes + Ok(payloads + .into_iter() + .map(|p| p.to_envelope(convo.remote_id())) + .collect()) } pub fn handle_payload(&mut self, _payload: &[u8]) -> Option { @@ -92,6 +107,19 @@ impl Context { 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> { + let convo_id = self + .convo_handle_map + .get(&handle) + .ok_or_else(|| ChatError::NoConvo(handle))? + .clone(); + + self.store + .get_mut(&convo_id) + .ok_or_else(|| ChatError::NoConvo(handle)) + } } #[cfg(test)] diff --git a/conversations/src/conversation.rs b/conversations/src/conversation.rs index 84327b7..61a5d79 100644 --- a/conversations/src/conversation.rs +++ b/conversations/src/conversation.rs @@ -3,7 +3,7 @@ use std::fmt::Debug; use std::sync::Arc; pub use crate::errors::ChatError; -use crate::types::ContentData; +use crate::types::{AddressedEncryptedPayload, ContentData}; pub type ConversationId<'a> = &'a str; pub type ConversationIdOwned = Arc; @@ -20,7 +20,10 @@ pub trait ConvoFactory: Id + Debug { } pub trait Convo: Id + Debug { - fn send_message(&mut self, content: &[u8]) -> Result, ChatError>; + fn send_message(&mut self, content: &[u8]) + -> Result, ChatError>; + + fn remote_id(&self) -> String; } pub struct ConversationStore { @@ -76,6 +79,5 @@ impl ConversationStore { mod group_test; mod privatev1; -use crate::proto::EncryptedPayload; 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 index 077f240..934ab1c 100644 --- a/conversations/src/conversation/group_test.rs +++ b/conversations/src/conversation/group_test.rs @@ -1,6 +1,7 @@ -use chat_proto::logoschat::encryption::EncryptedPayload; - -use crate::conversation::{ChatError, ConversationId, Convo, Id}; +use crate::{ + conversation::{ChatError, ConversationId, Convo, Id}, + types::AddressedEncryptedPayload, +}; #[derive(Debug)] pub struct GroupTestConvo {} @@ -19,7 +20,14 @@ impl Id for GroupTestConvo { } impl Convo for GroupTestConvo { - fn send_message(&mut self, _content: &[u8]) -> Result, ChatError> { + 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/conversation/privatev1.rs b/conversations/src/conversation/privatev1.rs index baa2266..c40c18e 100644 --- a/conversations/src/conversation/privatev1.rs +++ b/conversations/src/conversation/privatev1.rs @@ -7,6 +7,7 @@ use prost::{Message, bytes::Bytes}; use crate::{ conversation::{ChatError, ConversationId, Convo, Id}, + types::AddressedEncryptedPayload, utils::timestamp_millis, }; @@ -41,7 +42,10 @@ impl Id for PrivateV1Convo { } impl Convo for PrivateV1Convo { - fn send_message(&mut self, content: &[u8]) -> Result, ChatError> { + fn send_message( + &mut self, + content: &[u8], + ) -> Result, ChatError> { let frame = PrivateV1Frame { conversation_id: self.id().into(), sender: "delete".into(), @@ -49,8 +53,16 @@ impl Convo for PrivateV1Convo { frame_type: Some(FrameType::Content(content.to_vec().into())), }; - let ef = self.encrypt(frame); + let data = self.encrypt(frame); - Ok(vec![ef]) + Ok(vec![AddressedEncryptedPayload { + delivery_address: "delivery_address".into(), + data, + }]) + } + + fn remote_id(&self) -> String { + //TODO: Implement as per spec + self.id().into() } } diff --git a/conversations/src/errors.rs b/conversations/src/errors.rs index fc57a8b..19bfbe2 100644 --- a/conversations/src/errors.rs +++ b/conversations/src/errors.rs @@ -18,4 +18,6 @@ pub enum ChatError { InvalidKeyLength, #[error("bytes provided to {0} failed")] BadParsing(&'static str), + #[error("convo with handle: {0} was not found")] + NoConvo(u32), } diff --git a/conversations/src/inbox/inbox.rs b/conversations/src/inbox/inbox.rs index b9febef..f117a3c 100644 --- a/conversations/src/inbox/inbox.rs +++ b/conversations/src/inbox/inbox.rs @@ -1,4 +1,3 @@ -use chat_proto::logoschat::inbox::InboxV1Frame; use hex; use prost::Message; use prost::bytes::Bytes; @@ -13,8 +12,8 @@ use crate::conversation::{ChatError, ConversationId, Convo, ConvoFactory, Id, Pr use crate::crypto::{Blake2b128, CopyBytes, Digest, PublicKey, StaticSecret}; use crate::identity::Identity; use crate::inbox::handshake::InboxHandshake; -use crate::proto::{self}; -use crate::types::{ContentData, PayloadData}; +use crate::proto; +use crate::types::{AddressedEncryptedPayload, ContentData}; /// Compute the deterministic Delivery_address for an installation fn delivery_address_for_installation(_: PublicKey) -> String { @@ -75,7 +74,7 @@ impl Inbox { &self, remote_bundle: &Introduction, initial_message: String, - ) -> Result<(PrivateV1Convo, Vec), ChatError> { + ) -> Result<(PrivateV1Convo, Vec), ChatError> { let mut rng = OsRng; // TODO: Include signature in introduction bundle. Manaully fill for now @@ -91,12 +90,12 @@ impl Inbox { let mut convo = PrivateV1Convo::new(seed_key); - let mut initial_payloads = convo.send_message(initial_message.as_bytes())?; + let mut payloads = convo.send_message(initial_message.as_bytes())?; // Wrap First payload in Invite - if let Some(first_message) = initial_payloads.get_mut(0) { - let old = first_message.clone(); - let frame = Self::wrap_in_invite(old); + if let Some(first_message) = payloads.get_mut(0) { + // Take the the value of .data - it's being replaced at the end of this block + let frame = Self::wrap_in_invite(std::mem::take(&mut first_message.data)); // TODO: Encrypt frame let ciphertext = frame.encode_to_vec(); @@ -113,21 +112,16 @@ impl Inbox { payload: Bytes::from_owner(ciphertext), }; - *first_message = proto::EncryptedPayload { + // Update the address field with the Inbox delivery_Address + first_message.delivery_address = + delivery_address_for_installation(remote_bundle.installation_key); + // Update the data field with new Payload + first_message.data = proto::EncryptedPayload { encryption: Some(proto::Encryption::InboxHandshake(handshake)), }; } - // Convert Encrypted Payloads to PayloadData - let payload_data = initial_payloads - .iter() - .map(|p| PayloadData { - delivery_address: delivery_address_for_installation(remote_bundle.installation_key), - data: p.encode_to_vec(), - }) - .collect(); - - Ok((convo, payload_data)) + Ok((convo, payloads)) } fn wrap_in_invite(payload: proto::EncryptedPayload) -> proto::InboxV1Frame { @@ -174,7 +168,7 @@ impl Inbox { ); // TODO: Decrypt Content - let frame = InboxV1Frame::decode(handshake.payload)?; + let frame = proto::InboxV1Frame::decode(handshake.payload)?; Ok((seed_key, frame)) } diff --git a/conversations/src/proto.rs b/conversations/src/proto.rs index af9b43f..1697de5 100644 --- a/conversations/src/proto.rs +++ b/conversations/src/proto.rs @@ -1,5 +1,9 @@ pub use chat_proto::logoschat::encryption::encrypted_payload::Encryption; pub use chat_proto::logoschat::encryption::inbox_handshake_v1::InboxHeaderV1; pub use chat_proto::logoschat::encryption::{EncryptedPayload, InboxHandshakeV1}; +pub use chat_proto::logoschat::envelope::EnvelopeV1; pub use chat_proto::logoschat::inbox::InboxV1Frame; pub use chat_proto::logoschat::invite::InvitePrivateV1; + +pub use prost::Message; +pub use prost::bytes::Bytes; diff --git a/conversations/src/types.rs b/conversations/src/types.rs index 0717065..254ab9e 100644 --- a/conversations/src/types.rs +++ b/conversations/src/types.rs @@ -1,6 +1,10 @@ +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 PayloadData { +pub struct AddressedEnvelope { pub delivery_address: String, pub data: Vec, } @@ -11,3 +15,28 @@ pub struct ContentData { pub conversation_id: String, pub data: Vec, } + +// Internal type Definitions + +// Used by Conversations to attach addresses to outbound encrypted payloads +pub(crate) struct AddressedEncryptedPayload { + pub delivery_address: String, + pub data: proto::EncryptedPayload, +} + +impl AddressedEncryptedPayload { + // Wrap in an envelope and prepare for transmission + pub fn to_envelope(self, convo_id: String) -> AddressedEnvelope { + let envelope = proto::EnvelopeV1 { + // TODO: conversation_id should be obscured + conversation_hint: convo_id, + salt: 0, + payload: proto::Bytes::copy_from_slice(self.data.encode_to_vec().as_slice()), + }; + + AddressedEnvelope { + delivery_address: self.delivery_address, + data: envelope.encode_to_vec(), + } + } +}