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