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;
4mod persistence;
5pub mod welcome;
6
7use core_crypto_keystore::{entities::PersistedMlsPendingGroup, traits::FetchFromDatabase as _};
8use openmls::group::MlsGroup;
9
10use super::{Error, Result, TransactionContext};
11use crate::{
12    ConversationConfiguration, CredentialRef, KeystoreError, LeafError, OpenMlsError, RecursiveError,
13    mls::conversation::{ConversationIdRef, ConversationMut, PendingConversation},
14};
15
16impl TransactionContext {
17    /// Checks if a given conversation id exists locally.
18    ///
19    /// Somewhat cheaper than `self.conversation(id).is_ok()`.
20    pub async fn conversation_exists(&self, id: &ConversationIdRef) -> Result<bool> {
21        let database = self.database().await?.into();
22        self.mls_groups()
23            .await?
24            .exists(id, &database)
25            .await
26            .map_err(RecursiveError::root("checking for conversation existence"))
27            .map_err(Into::into)
28    }
29
30    /// Acquire a conversation guard.
31    ///
32    /// This helper struct permits mutations on a conversation.
33    pub async fn conversation(&self, id: &ConversationIdRef) -> Result<ConversationMut> {
34        let keystore = self.database().await?;
35        let session = self.session().await?;
36        let inner = self
37            .mls_groups()
38            .await?
39            .get_or_fetch(id, &keystore, session)
40            .await
41            .map_err(RecursiveError::root("fetching conversation from mls groups by id"))?;
42
43        if let Some(inner) = inner {
44            return Ok(ConversationMut::new(inner, self.clone()));
45        }
46        // Check if there is a pending conversation with
47        // the same id
48        let pending = self.pending_conversation(id).await.map(Error::PendingConversation)?;
49        Err(pending)
50    }
51
52    pub(crate) async fn pending_conversation(&self, id: &ConversationIdRef) -> Result<PendingConversation> {
53        let keystore = self.database().await?;
54        let Some(pending_group) = keystore
55            .get_borrowed::<PersistedMlsPendingGroup>(id.as_ref())
56            .await
57            .map_err(KeystoreError::wrap("finding persisted mls pending group"))?
58        else {
59            return Err(LeafError::ConversationNotFound(id.to_owned()).into());
60        };
61        Ok(PendingConversation::new(pending_group, self.clone()))
62    }
63
64    /// Create a new empty conversation
65    ///
66    /// # Arguments
67    /// * `id` - identifier of the group/conversation (must be unique otherwise the existing group will be overridden)
68    /// * `creator_credential_type` - kind of credential the creator wants to create the group with
69    /// * `config` - configuration of the group/conversation
70    ///
71    /// # Errors
72    /// Errors can happen from the KeyStore or from OpenMls for ex if no [openmls::key_packages::KeyPackage] can
73    /// be found in the KeyStore
74    #[cfg_attr(test, crate::dispotent)]
75    pub async fn new_conversation(
76        &self,
77        id: &ConversationIdRef,
78        credential_ref: &CredentialRef,
79        configuration: ConversationConfiguration,
80    ) -> Result<()> {
81        let database = &self.database().await?;
82        let provider = &self.crypto_provider().await?;
83        if self.conversation_exists(id).await? || self.pending_conversation_exists(id).await? {
84            return Err(LeafError::ConversationAlreadyExists(id.to_owned()).into());
85        }
86
87        let credential = credential_ref
88            .load(database)
89            .await
90            .map_err(RecursiveError::mls_credential_ref(
91                "loading credential from database to create new conversation",
92            ))?;
93
94        let config = configuration
95            .as_openmls_default_configuration()
96            .map_err(RecursiveError::mls_conversation("converting config to openmls default"))?;
97
98        let group = MlsGroup::new_with_group_id(
99            provider,
100            &credential.signature_key_pair,
101            &config,
102            openmls::prelude::GroupId::from_slice(id.as_ref()),
103            credential.to_mls_credential_with_key(),
104        )
105        .await
106        .map_err(OpenMlsError::wrap("creating group with id"))?;
107
108        self.persist_conversation_from_mls_group(group, configuration).await?;
109
110        Ok(())
111    }
112}