core_crypto/mls/conversation/immutable/
mod.rs1mod 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 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#[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 pub fn id(&self) -> &ConversationIdRef {
60 ConversationIdRef::new(&self.id)
61 }
62
63 pub(crate) async fn group(&self) -> RwLockReadGuard<'_, MlsGroupState> {
65 self.group.read().await
66 }
67
68 pub fn configuration(&self) -> &ConversationConfiguration {
70 &self.configuration
71 }
72
73 pub async fn epoch(&self) -> u64 {
75 self.group().await.epoch().as_u64()
76 }
77
78 pub fn cipher_suite(&self) -> CipherSuite {
80 self.configuration.cipher_suite
81 }
82
83 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 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 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}