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 Transaction,
11 entities::{MlsPendingMessage, PersistedMlsGroup, StoredBufferedCommit},
12 traits::{DeletableBySearchKey, EntityDeleteBorrowed, FetchFromDatabase as _},
13};
14use openmls::group::MlsGroup;
15
16use super::{Error, Result, TransactionContext};
17use crate::{
18 ConversationConfiguration, CredentialRef, KeystoreError, OpenMlsError, RecursiveError,
19 mls::conversation::{ConversationIdRef, ConversationMut, PendingConversation},
20};
21
22impl TransactionContext {
23 pub async fn conversation_exists(&self, id: &ConversationIdRef) -> Result<bool> {
27 let database = self.database()?.into();
28 self.mls_groups()
29 .await?
30 .exists(id, &database)
31 .await
32 .map_err(RecursiveError::context("checking for conversation existence"))
33 .map_err(Into::into)
34 }
35
36 pub async fn conversation(&self, id: &ConversationIdRef) -> Result<ConversationMut> {
40 let inner = self.inner()?;
41 let session = self.session().await?;
42 let conversation = self
43 .mls_groups()
44 .await?
45 .get_or_fetch(id, &inner.transaction, session)
46 .await
47 .map_err(RecursiveError::context("fetching conversation from mls groups by id"))?;
48
49 if let Some(conversation) = conversation {
50 return Ok(ConversationMut::new(conversation, self.clone()));
51 }
52 let pending = self.pending_conversation(id).await.map(Error::PendingConversation)?;
55 Err(pending)
56 }
57
58 pub(crate) async fn clear_orphaned_conversation_buffers(&self, id: &ConversationIdRef) -> Result<()> {
65 let inner = self.inner()?;
66 clear_orphaned_conversation_buffers(inner.transaction(), id).await?;
67 Ok(())
68 }
69
70 pub(crate) async fn pending_conversation(&self, id: &ConversationIdRef) -> Result<PendingConversation> {
71 let inner = self.inner()?;
72 let group = inner
73 .transaction
74 .get_borrowed::<PersistedMlsGroup>(id.keystore())
75 .await
76 .map_err(KeystoreError::wrap("finding persisted mls group"))?
77 .filter(|group| group.is_pending);
78 let Some(group) = group else {
79 return Err(Error::ConversationNotFound(id.to_owned()));
80 };
81 let group = Arc::unwrap_or_clone(group);
82 Ok(PendingConversation::new(group, self.clone()))
83 }
84
85 #[cfg_attr(test, crate::dispotent)]
96 pub async fn new_conversation(
97 &self,
98 id: &ConversationIdRef,
99 credential_ref: &CredentialRef,
100 configuration: ConversationConfiguration,
101 ) -> Result<()> {
102 let database = self.database()?;
103 let provider = self.crypto_provider().await?;
104 if self.conversation_exists(id).await? || self.pending_conversation_exists(id).await? {
105 return Err(Error::ConversationAlreadyExists(id.to_owned()));
106 }
107
108 let credential = credential_ref.load(&*database).await.map_err(RecursiveError::context(
109 "loading credential from database to create new conversation",
110 ))?;
111
112 let config = configuration
113 .as_openmls_default_configuration()
114 .map_err(RecursiveError::context("converting config to openmls default"))?;
115
116 self.inner()?
123 .transaction()
124 .with_savepoint(
125 "new_conversation",
126 async || {
127 let group = MlsGroup::new_with_group_id(
128 &provider,
129 &credential.signature_key_pair,
130 &config,
131 openmls::prelude::GroupId::from_slice(id.as_ref()),
132 credential.to_mls_credential_with_key(),
133 )
134 .await
135 .map_err(OpenMlsError::wrap("creating group with id"))?;
136
137 self.persist_conversation_from_mls_group(group, configuration).await?;
138
139 Ok(())
140 },
141 Error::savepoint,
142 )
143 .await
144 }
145}
146
147pub(crate) async fn clear_orphaned_conversation_buffers(
161 tx: &Transaction,
162 id: &ConversationIdRef,
163) -> Result<(), KeystoreError> {
164 let group_exists = tx
165 .get_borrowed::<PersistedMlsGroup>(id.keystore())
166 .await
167 .map_err(KeystoreError::wrap("looking for a group of a removed conversation"))?
168 .is_some();
169 if group_exists {
170 return Ok(());
171 }
172
173 MlsPendingMessage::delete_all_matching(tx, id.keystore()).map_err(KeystoreError::wrap(
174 "clearing the pending messages of a removed conversation",
175 ))?;
176 StoredBufferedCommit::delete_borrowed(tx, id.as_ref()).map_err(KeystoreError::wrap(
177 "clearing the buffered commit of a removed conversation",
178 ))?;
179
180 Ok(())
181}