Skip to main content

core_crypto/transaction_context/conversation/
mod.rs

1//! This module contains all [super::TransactionContext] methods related to a conversation.
2
3pub mod external_commit;
4pub mod welcome;
5
6use core_crypto_keystore::{entities::PersistedMlsPendingGroup, traits::FetchFromDatabase as _};
7
8use super::{Error, Result, TransactionContext};
9use crate::{
10    CredentialRef, KeystoreError, LeafError, MlsConversation, MlsConversationConfiguration, RecursiveError,
11    mls::conversation::{ConversationGuard, ConversationIdRef, pending_conversation::PendingConversation},
12};
13
14impl TransactionContext {
15    /// Acquire a conversation guard.
16    ///
17    /// This helper struct permits mutations on a conversation.
18    pub async fn conversation(&self, id: &ConversationIdRef) -> Result<ConversationGuard> {
19        let keystore = self.database().await?;
20        let inner = self
21            .mls_groups()
22            .await?
23            .get_fetch(id, &keystore, None)
24            .await
25            .map_err(RecursiveError::root("fetching conversation from mls groups by id"))?;
26
27        if let Some(inner) = inner {
28            return Ok(ConversationGuard::new(inner, self.clone()));
29        }
30        // Check if there is a pending conversation with
31        // the same id
32        let pending = self.pending_conversation(id).await.map(Error::PendingConversation)?;
33        Err(pending)
34    }
35
36    pub(crate) async fn pending_conversation(&self, id: &ConversationIdRef) -> Result<PendingConversation> {
37        let keystore = self.database().await?;
38        let Some(pending_group) = keystore
39            .get_borrowed::<PersistedMlsPendingGroup>(id.as_ref())
40            .await
41            .map_err(KeystoreError::wrap("finding persisted mls pending group"))?
42        else {
43            return Err(LeafError::ConversationNotFound(id.to_owned()).into());
44        };
45        Ok(PendingConversation::new(pending_group, self.clone()))
46    }
47
48    /// Create a new empty conversation
49    ///
50    /// # Arguments
51    /// * `id` - identifier of the group/conversation (must be unique otherwise the existing group will be overridden)
52    /// * `creator_credential_type` - kind of credential the creator wants to create the group with
53    /// * `config` - configuration of the group/conversation
54    ///
55    /// # Errors
56    /// Errors can happen from the KeyStore or from OpenMls for ex if no [openmls::key_packages::KeyPackage] can
57    /// be found in the KeyStore
58    #[cfg_attr(test, crate::dispotent)]
59    pub async fn new_conversation(
60        &self,
61        id: &ConversationIdRef,
62        credential_ref: &CredentialRef,
63        config: MlsConversationConfiguration,
64    ) -> Result<()> {
65        let database = &self.database().await?;
66        let provider = &self.mls_provider().await?;
67        if self.conversation_exists(id).await? || self.pending_conversation_exists(id).await? {
68            return Err(LeafError::ConversationAlreadyExists(id.to_owned()).into());
69        }
70        let conversation = MlsConversation::create(id.to_owned(), provider, database, credential_ref, config)
71            .await
72            .map_err(RecursiveError::mls_conversation("creating conversation"))?;
73
74        self.mls_groups().await?.insert(id, conversation);
75
76        Ok(())
77    }
78
79    /// Checks if a given conversation id exists locally
80    pub async fn conversation_exists(&self, id: &ConversationIdRef) -> Result<bool> {
81        self.mls_groups()
82            .await?
83            .get_fetch(id, &self.database().await?, None)
84            .await
85            .map(|option| option.is_some())
86            .map_err(RecursiveError::root("fetching conversation from mls groups by id"))
87            .map_err(Into::into)
88    }
89}