core_crypto/mls/conversation/conversation_guard/
mod.rs

1mod commit;
2pub(crate) mod decrypt;
3mod encrypt;
4mod merge;
5
6use super::{ConversationWithMls, Error, MlsConversation, Result};
7use crate::mls::credential::CredentialBundle;
8use crate::prelude::ConversationId;
9use crate::{
10    KeystoreError, LeafError, RecursiveError, context::CentralContext, group_store::GroupStoreValue,
11    prelude::MlsGroupInfoBundle,
12};
13use async_lock::{RwLockReadGuard, RwLockWriteGuard};
14use core_crypto_keystore::CryptoKeystoreMls;
15use openmls::prelude::group_info::GroupInfo;
16use openmls_traits::OpenMlsCryptoProvider;
17use std::sync::Arc;
18
19/// A Conversation Guard wraps a `GroupStoreValue<MlsConversation>`.
20///
21/// By doing so, it permits mutable accesses to the conversation. This in turn
22/// means that we don't have to duplicate the entire `MlsConversation` API
23/// on `CentralContext`.
24#[derive(Debug)]
25pub struct ConversationGuard {
26    inner: GroupStoreValue<MlsConversation>,
27    central_context: CentralContext,
28}
29
30#[cfg_attr(target_family = "wasm", async_trait::async_trait(?Send))]
31#[cfg_attr(not(target_family = "wasm"), async_trait::async_trait)]
32impl<'inner> ConversationWithMls<'inner> for ConversationGuard {
33    type Central = CentralContext;
34    type Conversation = RwLockReadGuard<'inner, MlsConversation>;
35
36    async fn central(&self) -> Result<CentralContext> {
37        Ok(self.central_context.clone())
38    }
39
40    async fn conversation(&'inner self) -> RwLockReadGuard<'inner, MlsConversation> {
41        self.inner.read().await
42    }
43}
44
45impl ConversationGuard {
46    pub(crate) fn new(inner: GroupStoreValue<MlsConversation>, central_context: CentralContext) -> Self {
47        Self { inner, central_context }
48    }
49
50    pub(crate) async fn conversation_mut(&mut self) -> RwLockWriteGuard<MlsConversation> {
51        self.inner.write().await
52    }
53
54    /// Destroys a group locally
55    ///
56    /// # Errors
57    /// KeyStore errors, such as IO
58    pub async fn wipe(&mut self) -> Result<()> {
59        let provider = self.mls_provider().await?;
60        let mut group_store = self
61            .central_context
62            .mls_groups()
63            .await
64            .map_err(RecursiveError::root("getting mls groups"))?;
65        let mut conversation = self.conversation_mut().await;
66        conversation.wipe_associated_entities(&provider).await?;
67        provider
68            .key_store()
69            .mls_group_delete(conversation.id())
70            .await
71            .map_err(KeystoreError::wrap("deleting mls group"))?;
72        let _ = group_store.remove(conversation.id());
73        Ok(())
74    }
75
76    /// This is not used right now, just like the entire mechanism of parent and
77    /// child conversations. When our threat model requires it, we can re-enable this, or we remove
78    /// it, along with all other code related to parent-child conversations.
79    #[expect(dead_code)]
80    pub(crate) async fn get_parent(&self) -> Result<Option<Self>> {
81        let conversation_lock = self.conversation().await;
82        let Some(parent_id) = conversation_lock.parent_id.as_ref() else {
83            return Ok(None);
84        };
85        self.central_context
86            .conversation(parent_id)
87            .await
88            .map(Some)
89            .map_err(|_| Error::ParentGroupNotFound)
90    }
91
92    /// Marks this conversation as child of another.
93    /// Prerequisite: Must be a member of the parent group, and it must exist in the keystore
94    pub async fn mark_as_child_of(&mut self, parent_id: &ConversationId) -> Result<()> {
95        let backend = self.mls_provider().await?;
96        let keystore = &backend.keystore();
97        let mut conversation = self.conversation_mut().await;
98        if keystore.mls_group_exists(parent_id).await {
99            conversation.parent_id = Some(parent_id.clone());
100            conversation.persist_group_when_changed(keystore, true).await?;
101            Ok(())
102        } else {
103            Err(Error::ParentGroupNotFound)
104        }
105    }
106
107    async fn credential_bundle(&self) -> Result<Arc<CredentialBundle>> {
108        let client = self.mls_client().await?;
109        let inner = self.conversation().await;
110        inner
111            .find_current_credential_bundle(&client)
112            .await
113            .map_err(|_| Error::IdentityInitializationError)
114    }
115
116    fn group_info(group_info: Option<GroupInfo>) -> Result<MlsGroupInfoBundle> {
117        let group_info = group_info.ok_or(LeafError::MissingGroupInfo)?;
118        MlsGroupInfoBundle::try_new_full_plaintext(group_info)
119    }
120}