Add PrivateV2Convo

This commit is contained in:
Jazz Turner-Baggs 2026-06-18 13:35:11 -07:00
parent 537aa9b952
commit 9d4565c4a5
No known key found for this signature in database
7 changed files with 153 additions and 9 deletions

View File

@ -1,6 +1,7 @@
pub mod group_v1;
mod group_v2;
mod privatev1;
mod privatev2;
pub use crate::errors::ChatError;
use crate::outcomes::ConvoOutcome;
@ -9,13 +10,14 @@ use crate::service_context::{ExternalServices, ServiceContext};
pub use group_v1::GroupV1Convo;
pub use group_v2::GroupV2Convo;
pub use privatev1::PrivateV1Convo;
pub use privatev2::PrivateV2Convo;
use shared_traits::IdentIdRef;
pub type ConversationId = String;
pub type ConversationIdRef<'a> = &'a str;
/// Behaviour shared by every conversation kind.
pub(crate) trait Convo<S: ExternalServices>: Identified {
pub(crate) trait Convo<S: ExternalServices>: Identified + Send {
fn send_content(&mut self, cx: &mut ServiceContext<S>, content: &[u8])
-> Result<(), ChatError>;
@ -43,5 +45,5 @@ pub(crate) trait GroupConvo<S: ExternalServices>: Convo<S> + std::fmt::Debug + S
}
pub(crate) trait Identified {
fn id(&self) -> ConversationIdRef;
fn id(&self) -> ConversationIdRef<'_>;
}

View File

@ -0,0 +1,57 @@
use chat_proto::logoschat::encryption::EncryptedPayload;
use shared_traits::IdentIdRef;
use crate::{
ChatError, ExternalServices,
conversation::{ConversationIdRef, Convo, GroupConvo, GroupV1Convo, Identified},
service_context::ServiceContext,
};
type DelegateGroup = GroupV1Convo;
#[derive(Debug)]
pub struct PrivateV2Convo {
inner_group: DelegateGroup,
}
impl PrivateV2Convo {
pub fn new<S: ExternalServices>(
cx: &mut ServiceContext<S>,
participant: IdentIdRef,
) -> Result<Self, ChatError> {
let mut inner_group = DelegateGroup::new(cx)?;
inner_group.add_member(cx, &[participant])?;
Ok(Self { inner_group })
}
}
impl Identified for PrivateV2Convo {
fn id(&self) -> ConversationIdRef<'_> {
self.inner_group.id()
}
}
impl<S> Convo<S> for PrivateV2Convo
where
S: ExternalServices,
{
fn send_content(
&mut self,
cx: &mut ServiceContext<S>,
content: &[u8],
) -> Result<(), super::ChatError> {
self.inner_group.send_content(cx, content)
}
fn handle_frame(
&mut self,
cx: &mut ServiceContext<S>,
enc: EncryptedPayload,
) -> Result<crate::ConvoOutcome, ChatError> {
self.inner_group.handle_frame(cx, enc)
}
fn wakeup(&mut self, service_ctx: &mut ServiceContext<S>) -> Result<(), ChatError> {
self.inner_group.wakeup(service_ctx)
}
}

View File

@ -1,6 +1,6 @@
use crate::causal_history::{CausalHistoryStore, MissingMessage};
use crate::conversation::{
ConversationIdRef, GroupV1Convo, GroupV2Convo, Identified, PrivateV1Convo,
ConversationIdRef, GroupV1Convo, GroupV2Convo, Identified, PrivateV1Convo, PrivateV2Convo,
};
use crate::service_context::{ExternalServices, ServiceContext};
use crate::{DeliveryService, IdentityProvider, RegistrationService, WakeupService};
@ -16,6 +16,7 @@ use crypto::{Identity, PublicKey};
use openmls::group::GroupId;
use shared_traits::IdentIdRef;
use std::collections::HashMap;
use std::fmt::Debug;
use storage::{ChatStore, ConversationKind, ConversationStore};
use tracing::{info, instrument};
@ -188,6 +189,14 @@ impl<'a, S: ExternalServices + 'static> Core<S> {
&mut self,
remote_bundle: &Introduction,
content: &[u8],
) -> Result<ConversationId, ChatError> {
self.create_private_convo_v1(remote_bundle, content)
}
pub fn create_private_convo_v1(
&mut self,
remote_bundle: &Introduction,
content: &[u8],
) -> Result<ConversationId, ChatError> {
let (mut convo, payloads) =
self.inbox
@ -204,6 +213,17 @@ impl<'a, S: ExternalServices + 'static> Core<S> {
Ok(convo_id)
}
pub fn create_private_convo_v2(
&mut self,
pariticpant: IdentIdRef,
) -> Result<ConversationId, ChatError> {
let convo = PrivateV2Convo::new(&mut self.services, pariticpant)?;
let convo_id = convo.id().to_string();
self.register_convo(ConvoTypeOwned::Pairwise(Box::new(convo)))?;
Ok(convo_id)
}
pub fn create_group_convo(
&mut self,
participants: &[IdentIdRef],
@ -266,6 +286,10 @@ impl<'a, S: ExternalServices + 'static> Core<S> {
ConvoTypeOwned::Group(group_convo) => {
group_convo.add_member(&mut self.services, members)
}
ConvoTypeOwned::Pairwise(convo) => Err(ChatError::UnsupportedFunction(
convo.id().into(),
"Add Member".into(),
)),
}
} else {
let mut convo = self.load_group_convo(convo_id)?;
@ -397,6 +421,7 @@ impl<'a, S: ExternalServices + 'static> Core<S> {
};
let convo = match convo {
ConvoTypeOwned::Group(c) => c.as_mut(),
ConvoTypeOwned::Pairwise(c) => c.as_mut(),
};
convo.wakeup(&mut self.services)
@ -466,15 +491,24 @@ impl<'a, S: ExternalServices + 'static> Core<S> {
}
}
#[derive(Debug)]
enum ConvoTypeOwned<S: ExternalServices> {
// Pairwise(Box<dyn BaseConvo<S>>),
Pairwise(Box<dyn Convo<S>>),
Group(Box<dyn GroupConvo<S>>),
}
impl<S: ExternalServices> Debug for ConvoTypeOwned<S> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Pairwise(arg0) => f.debug_tuple("Pairwise").field(&arg0.id()).finish(),
Self::Group(arg0) => f.debug_tuple("Group").field(&arg0.id()).finish(),
}
}
}
impl<S: ExternalServices> Identified for ConvoTypeOwned<S> {
fn id(&self) -> ConversationIdRef<'_> {
match self {
ConvoTypeOwned::Pairwise(convo) => convo.id(),
ConvoTypeOwned::Group(group_convo) => group_convo.id(),
}
}
@ -488,6 +522,7 @@ impl<S: ExternalServices> Convo<S> for ConvoTypeOwned<S> {
) -> Result<(), ChatError> {
match self {
ConvoTypeOwned::Group(group_convo) => group_convo.send_content(cx, content),
ConvoTypeOwned::Pairwise(convo) => convo.send_content(cx, content),
}
}
@ -498,12 +533,14 @@ impl<S: ExternalServices> Convo<S> for ConvoTypeOwned<S> {
) -> Result<ConvoOutcome, ChatError> {
match self {
ConvoTypeOwned::Group(group_convo) => group_convo.handle_frame(cx, enc),
ConvoTypeOwned::Pairwise(convo) => convo.handle_frame(cx, enc),
}
}
fn wakeup(&mut self, service_ctx: &mut ServiceContext<S>) -> Result<(), ChatError> {
match self {
ConvoTypeOwned::Group(group_convo) => group_convo.wakeup(service_ctx),
ConvoTypeOwned::Pairwise(convo) => convo.wakeup(service_ctx),
}
}
}

View File

@ -4,6 +4,8 @@ pub use thiserror::Error;
use storage::StorageError;
use crate::ConversationId;
#[derive(Error, Debug)]
pub enum ChatError {
#[error("protocol error: {0:?}")]
@ -42,6 +44,9 @@ pub enum ChatError {
MlsError(#[from] MlsError),
#[error("demls error: {0}")]
DeMlsError(#[from] ConversationError),
// Used when a core function is called with a convo_id which is unsupported
#[error("convo:{0} does not support {1}")]
UnsupportedFunction(ConversationId, String),
}
impl ChatError {

View File

@ -87,7 +87,7 @@ fn ctx_integration() {
// Saro initiates conversation with Raya
let mut content = vec![10];
let saro_convo_id = saro.create_private_convo(&intro, &content).unwrap();
let saro_convo_id = saro.create_private_convo_v1(&intro, &content).unwrap();
// Raya receives the invite + initial message
let initial = recv_one(&mut raya);
@ -178,7 +178,7 @@ fn conversation_metadata_persistence() {
let bundle = alice.create_intro_bundle().unwrap();
let intro = Introduction::try_from(bundle.as_slice()).unwrap();
bob.create_private_convo(&intro, b"hi").unwrap();
bob.create_private_convo_v1(&intro, b"hi").unwrap();
let result = recv_one(&mut alice);
let PayloadOutcome::Inbox(io) = result else {
@ -219,7 +219,7 @@ fn conversation_full_flow() {
let bundle = alice.create_intro_bundle().unwrap();
let intro = Introduction::try_from(bundle.as_slice()).unwrap();
let bob_convo_id = bob.create_private_convo(&intro, b"hello").unwrap();
let bob_convo_id = bob.create_private_convo_v1(&intro, b"hello").unwrap();
let result = recv_one(&mut alice);
let PayloadOutcome::Inbox(io) = result else {

View File

@ -0,0 +1,43 @@
use integration_tests_core::TestHarness;
use tracing::info;
#[test]
fn happypath_roundtrip() {
let _ = tracing_subscriber::fmt()
.with_max_level(tracing::Level::INFO)
.with_test_writer()
.try_init();
const S_M1: &[u8] = b"Marco";
const R_M1: &[u8] = b"Polo";
// Initialize TestHarness with 2 clients
let mut harness = TestHarness::<2>::new(|_, _| {});
//Saro Create Convo
let particpants = harness.raya().addr();
let convo_id = harness
.saro()
.create_private_convo_v2(&particpants)
.expect("saro create group");
// Carry the invite through (commit, WelcomeReady, routing to Raya's inbox,
// accept_welcome); settle until Raya has joined.
harness.process_until_label("Saro Send", |h| h.raya().convo_count() == 1);
// Saro sends a message; settle until Raya receives it.
info!(target: "chat", "Saro -> sending: {S_M1:?}");
harness
.saro()
.send_content(&convo_id, S_M1)
.expect("saro send");
harness.process_until(|h| h.raya().check(&convo_id, S_M1));
// Raya replies; settle until Saro receives it.
info!(target: "chat", "Raya -> sending:{R_M1:?}");
harness.raya().send_content(&convo_id, R_M1).unwrap();
harness.process_until(|h| h.saro().check(&convo_id, R_M1));
assert!(harness.saro().check(&convo_id, R_M1));
}

View File

@ -168,7 +168,7 @@ where
let intro = Introduction::try_from(intro_bundle)?;
self.core
.lock()
.create_private_convo(&intro, initial_content)
.create_private_convo_v1(&intro, initial_content)
.map_err(Into::into)
}