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::{
10 entities::{MlsPendingMessage, PersistedMlsGroup, StoredBufferedCommit},
11 traits::{DeletableBySearchKey, EntityDeleteBorrowed, FetchFromDatabase as _},
12};
13use openmls::group::MlsGroup;
14
15use super::{Error, Result, TransactionContext};
16use crate::{
17 ConversationConfiguration, CredentialRef, KeystoreError, OpenMlsError, RecursiveError,
18 mls::conversation::{ConversationIdRef, ConversationMut, PendingConversation},
19};
20
21impl TransactionContext {
22 pub async fn conversation_exists(&self, id: &ConversationIdRef) -> Result<bool> {
26 let database = self.database().await?.into();
27 self.mls_groups()
28 .await?
29 .exists(id, &database)
30 .await
31 .map_err(RecursiveError::context("checking for conversation existence"))
32 .map_err(Into::into)
33 }
34
35 pub async fn conversation(&self, id: &ConversationIdRef) -> Result<ConversationMut> {
39 let inner = self.inner().await?;
40 let session = self.session().await?;
41 let conversation = self
42 .mls_groups()
43 .await?
44 .get_or_fetch(id, &inner.transaction, session)
45 .await
46 .map_err(RecursiveError::context("fetching conversation from mls groups by id"))?;
47
48 if let Some(conversation) = conversation {
49 return Ok(ConversationMut::new(conversation, self.clone()));
50 }
51 let pending = self.pending_conversation(id).await.map(Error::PendingConversation)?;
54 Err(pending)
55 }
56
57 pub(crate) async fn clear_orphaned_conversation_buffers(&self, id: &ConversationIdRef) -> Result<()> {
70 let inner = self.inner().await?;
71 let tx = inner.transaction();
72
73 let group_exists = tx
74 .get_borrowed::<PersistedMlsGroup>(id.keystore())
75 .await
76 .map_err(KeystoreError::wrap("looking for a group of a removed conversation"))?
77 .is_some();
78 if group_exists {
79 return Ok(());
80 }
81
82 MlsPendingMessage::delete_all_matching(tx, id.keystore()).map_err(KeystoreError::wrap(
83 "clearing the pending messages of a removed conversation",
84 ))?;
85 StoredBufferedCommit::delete_borrowed(tx, id.as_ref()).map_err(KeystoreError::wrap(
86 "clearing the buffered commit of a removed conversation",
87 ))?;
88
89 Ok(())
90 }
91
92 pub(crate) async fn pending_conversation(&self, id: &ConversationIdRef) -> Result<PendingConversation> {
93 let inner = self.inner().await?;
94 let group = inner
95 .transaction
96 .get_borrowed::<PersistedMlsGroup>(id.keystore())
97 .await
98 .map_err(KeystoreError::wrap("finding persisted mls group"))?
99 .filter(|group| group.is_pending);
100 let Some(group) = group else {
101 return Err(Error::ConversationNotFound(id.to_owned()));
102 };
103 let group = Arc::unwrap_or_clone(group);
104 Ok(PendingConversation::new(group, self.clone()))
105 }
106
107 #[cfg_attr(test, crate::dispotent)]
118 pub async fn new_conversation(
119 &self,
120 id: &ConversationIdRef,
121 credential_ref: &CredentialRef,
122 configuration: ConversationConfiguration,
123 ) -> Result<()> {
124 let database = self.database().await?;
125 let provider = self.crypto_provider().await?;
126 if self.conversation_exists(id).await? || self.pending_conversation_exists(id).await? {
127 return Err(Error::ConversationAlreadyExists(id.to_owned()));
128 }
129
130 let credential = credential_ref.load(&*database).await.map_err(RecursiveError::context(
131 "loading credential from database to create new conversation",
132 ))?;
133
134 let config = configuration
135 .as_openmls_default_configuration()
136 .map_err(RecursiveError::context("converting config to openmls default"))?;
137
138 let group = MlsGroup::new_with_group_id(
139 &provider,
140 &credential.signature_key_pair,
141 &config,
142 openmls::prelude::GroupId::from_slice(id.as_ref()),
143 credential.to_mls_credential_with_key(),
144 )
145 .await
146 .map_err(OpenMlsError::wrap("creating group with id"))?;
147
148 self.persist_conversation_from_mls_group(group, configuration).await?;
149
150 Ok(())
151 }
152}