core_crypto/transaction_context/conversation/
mod.rs

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