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