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