Skip to main content

core_crypto/mls/conversation/immutable/
mod.rs

1mod clients;
2mod commit_delay;
3mod credential;
4mod duplicate;
5mod e2ei;
6mod history_sharing;
7mod persistence;
8
9use async_lock::{RwLock, RwLockReadGuard};
10use openmls::group::MlsGroup;
11
12use super::{ConversationIdRef, Error, Result, SecretKey};
13use crate::{
14    CipherSuite, ConversationConfiguration, ConversationId, CredentialRef, ExternalSender, OpenMlsError, Session,
15    mls::TntMessageCounter,
16};
17
18#[derive(derive_more::Constructor, derive_more::Deref, derive_more::DerefMut, derive_more::Debug)]
19pub(crate) struct MlsGroupState {
20    #[deref]
21    #[deref_mut]
22    group: MlsGroup,
23    #[debug(skip)]
24    tnt_message_counter: TntMessageCounter,
25}
26
27impl MlsGroupState {
28    pub(in crate::mls::conversation) fn mls_group(&self) -> &MlsGroup {
29        &self.group
30    }
31
32    pub(in crate::mls::conversation) fn mls_group_mut(&mut self) -> &mut MlsGroup {
33        &mut self.group
34    }
35
36    #[expect(unused)]
37    pub(in crate::mls::conversation) fn tnt_message_counter(&self) -> TntMessageCounter {
38        self.tnt_message_counter
39    }
40
41    /// Get the tnt message counter bound to this conversation after incrementing it.
42    pub(in crate::mls::conversation) fn obtain_tnt_message_counter(&mut self) -> Result<TntMessageCounter> {
43        self.tnt_message_counter.increment()?;
44        self.group.set_state(openmls::group::InnerState::Changed);
45        Ok(self.tnt_message_counter)
46    }
47
48    pub(in crate::mls::conversation) fn reset_tnt_message_counter(&mut self) {
49        self.tnt_message_counter = Default::default()
50    }
51}
52
53/// A Conversation exposes the read-only interface of an MLS conversation.
54#[derive(Debug, derive_more::Constructor)]
55pub struct Conversation {
56    pub(in crate::mls::conversation) id: ConversationId,
57    pub(in crate::mls::conversation) group: RwLock<MlsGroupState>,
58    pub(in crate::mls::conversation) configuration: ConversationConfiguration,
59    session: Session,
60}
61
62impl Conversation {
63    /// Returns the conversation's ID
64    pub fn id(&self) -> &ConversationIdRef {
65        ConversationIdRef::new(&self.id)
66    }
67
68    /// Returns an immutable guard over the underlying MLS group
69    pub(crate) async fn group(&self) -> RwLockReadGuard<'_, MlsGroupState> {
70        self.group.read().await
71    }
72
73    /// Returns the conversation's configuration
74    pub fn configuration(&self) -> &ConversationConfiguration {
75        &self.configuration
76    }
77
78    /// Returns current epoch of the MLS group
79    pub async fn epoch(&self) -> u64 {
80        self.group().await.epoch().as_u64()
81    }
82
83    /// Returns this conversation's cipher suite
84    pub fn cipher_suite(&self) -> CipherSuite {
85        self.configuration.cipher_suite
86    }
87
88    /// Returns a reference to the credential used in this conversation
89    pub async fn credential_ref(&self) -> Result<CredentialRef> {
90        let credential = self
91            .find_current_credential()
92            .await
93            .map_err(|_| Error::IdentityInitializationError)?;
94        Ok(CredentialRef::from_credential(&credential))
95    }
96
97    /// Derives a new key from the one in the group, to be used elsewhere.
98    ///
99    /// # Arguments
100    /// * `key_length` - the length of the key to be derived. If the value is higher than the bounds of `u16` or the
101    ///   context hash * 255, an error will be returned
102    ///
103    /// # Errors
104    /// OpenMls secret generation error
105    pub async fn export_secret_key(&self, key_length: usize) -> Result<SecretKey> {
106        const EXPORTER_LABEL: &str = "exporter";
107        const EXPORTER_CONTEXT: &[u8] = &[];
108        self.group()
109            .await
110            .export_secret(
111                &self.session.crypto_provider,
112                EXPORTER_LABEL,
113                EXPORTER_CONTEXT,
114                key_length,
115            )
116            .map(Into::into)
117            .map_err(OpenMlsError::wrap("exporting secret key"))
118            .map_err(Into::into)
119    }
120
121    /// Returns the first external sender present in this group.
122    ///
123    /// This should be used to initialize a subconversation
124    pub async fn get_external_sender(&self) -> Result<ExternalSender> {
125        let group = self.group().await;
126        let ext_senders = group
127            .group_context_extensions()
128            .external_senders()
129            .ok_or(Error::MissingExternalSenderExtension)?;
130        let ext_sender = ext_senders.first().ok_or(Error::MissingExternalSenderExtension)?;
131        Ok(ext_sender.clone().into())
132    }
133}
134
135#[cfg(test)]
136mod test_utils {
137    use openmls::prelude::SignaturePublicKey;
138
139    use super::*;
140
141    impl Conversation {
142        pub async fn signature_keys(&self) -> Vec<SignaturePublicKey> {
143            let group = self.group().await;
144            group
145                .members()
146                .map(|m| m.signature_key)
147                .map(|mpk| SignaturePublicKey::from(mpk.as_slice()))
148                .collect()
149        }
150
151        pub async fn encryption_keys(&self) -> Vec<Vec<u8>> {
152            let group = self.group().await;
153            group.members().map(|m| m.encryption_key).collect()
154        }
155
156        pub async fn extensions(&self) -> openmls::prelude::Extensions {
157            let group = self.group().await;
158            group.export_group_context().extensions().to_owned()
159        }
160    }
161}