core_crypto/transaction_context/conversation/
mod.rs1pub mod external_commit;
4mod persistence;
5pub mod welcome;
6
7use std::sync::Arc;
8
9use core_crypto_keystore::{entities::PersistedMlsGroup, traits::FetchFromDatabase as _};
10use openmls::group::MlsGroup;
11
12use super::{Error, Result, TransactionContext};
13use crate::{
14 ConversationConfiguration, CredentialRef, KeystoreError, OpenMlsError, RecursiveError,
15 mls::conversation::{ConversationIdRef, ConversationMut, PendingConversation},
16};
17
18impl TransactionContext {
19 pub async fn conversation_exists(&self, id: &ConversationIdRef) -> Result<bool> {
23 let database = self.database()?.into();
24 self.mls_groups()
25 .await?
26 .exists(id, &database)
27 .await
28 .map_err(RecursiveError::context("checking for conversation existence"))
29 .map_err(Into::into)
30 }
31
32 pub async fn conversation(&self, id: &ConversationIdRef) -> Result<ConversationMut> {
36 let inner = self.inner()?;
37 let session = self.session().await?;
38 let conversation = self
39 .mls_groups()
40 .await?
41 .get_or_fetch(id, &inner.transaction, session)
42 .await
43 .map_err(RecursiveError::context("fetching conversation from mls groups by id"))?;
44
45 if let Some(conversation) = conversation {
46 return Ok(ConversationMut::new(conversation, self.clone()));
47 }
48 let pending = self.pending_conversation(id).await.map(Error::PendingConversation)?;
51 Err(pending)
52 }
53
54 pub(crate) async fn pending_conversation(&self, id: &ConversationIdRef) -> Result<PendingConversation> {
55 let inner = self.inner()?;
56 let group = inner
57 .transaction
58 .get_borrowed::<PersistedMlsGroup>(id.keystore())
59 .await
60 .map_err(KeystoreError::wrap("finding persisted mls group"))?
61 .filter(|group| group.is_pending);
62 let Some(group) = group else {
63 return Err(Error::ConversationNotFound(id.to_owned()));
64 };
65 let group = Arc::unwrap_or_clone(group);
66 Ok(PendingConversation::new(group, self.clone()))
67 }
68
69 #[cfg_attr(test, crate::dispotent)]
80 pub async fn new_conversation(
81 &self,
82 id: &ConversationIdRef,
83 credential_ref: &CredentialRef,
84 configuration: ConversationConfiguration,
85 ) -> Result<()> {
86 let database = self.database()?;
87 let provider = self.crypto_provider().await?;
88 if self.conversation_exists(id).await? || self.pending_conversation_exists(id).await? {
89 return Err(Error::ConversationAlreadyExists(id.to_owned()));
90 }
91
92 let credential = credential_ref.load(&*database).await.map_err(RecursiveError::context(
93 "loading credential from database to create new conversation",
94 ))?;
95
96 let config = configuration
97 .as_openmls_default_configuration()
98 .map_err(RecursiveError::context("converting config to openmls default"))?;
99
100 self.inner()?
107 .transaction()
108 .with_savepoint(
109 "new_conversation",
110 async || {
111 let group = MlsGroup::new_with_group_id(
112 &provider,
113 &credential.signature_key_pair,
114 &config,
115 openmls::prelude::GroupId::from_slice(id.as_ref()),
116 credential.to_mls_credential_with_key(),
117 )
118 .await
119 .map_err(OpenMlsError::wrap("creating group with id"))?;
120
121 self.persist_conversation_from_mls_group(group, configuration).await?;
122
123 Ok(())
124 },
125 Error::savepoint,
126 )
127 .await
128 }
129}