core_crypto/transaction_context/conversation/
welcome.rs1use std::borrow::BorrowMut as _;
4
5use openmls::prelude::{MlsMessageIn, MlsMessageInBody};
6use tls_codec::Deserialize as _;
7
8use super::{Error, Result, TransactionContext};
9use crate::{
10 MlsConversation, MlsConversationConfiguration, RecursiveError, WelcomeBundle,
11 mls::credential::crl::{extract_crl_uris_from_group, get_new_crl_distribution_points},
12};
13
14impl TransactionContext {
15 #[cfg_attr(test, crate::dispotent)]
28 pub async fn process_raw_welcome_message(&self, welcome: &[u8]) -> Result<WelcomeBundle> {
29 let mut cursor = std::io::Cursor::new(welcome);
30 let welcome =
31 MlsMessageIn::tls_deserialize(&mut cursor).map_err(Error::tls_deserialize("mls message in (welcome)"))?;
32 self.process_welcome_message(welcome).await
33 }
34
35 #[cfg_attr(test, crate::dispotent)]
49 pub async fn process_welcome_message(&self, welcome: MlsMessageIn) -> Result<WelcomeBundle> {
50 let MlsMessageInBody::Welcome(welcome) = welcome.extract() else {
51 return Err(Error::CallerError(
52 "the message provided to process_welcome_message was not a welcome message",
53 ));
54 };
55 let cs = welcome.ciphersuite().into();
56 let configuration = MlsConversationConfiguration {
57 ciphersuite: cs,
58 ..Default::default()
59 };
60 let mls_provider = self
61 .mls_provider()
62 .await
63 .map_err(RecursiveError::transaction("getting mls provider"))?;
64 let mut mls_groups = self
65 .mls_groups()
66 .await
67 .map_err(RecursiveError::transaction("getting mls groups"))?;
68 let conversation =
69 MlsConversation::from_welcome_message(welcome, configuration, &mls_provider, mls_groups.borrow_mut())
70 .await
71 .map_err(RecursiveError::mls_conversation("creating conversation from welcome"))?;
72
73 let crl_new_distribution_points = get_new_crl_distribution_points(
75 &mls_provider,
76 extract_crl_uris_from_group(&conversation.group)
77 .map_err(RecursiveError::mls_credential("extracting crl uris from group"))?,
78 )
79 .await
80 .map_err(RecursiveError::mls_credential("getting new crl distribution points"))?;
81
82 let id = conversation.id.clone();
83 mls_groups.insert(&id, conversation);
84
85 Ok(WelcomeBundle {
86 id,
87 crl_new_distribution_points,
88 })
89 }
90}
91
92#[cfg(test)]
93mod tests {
94 use super::*;
95 use crate::test_utils::*;
96
97 #[apply(all_cred_cipher)]
98 async fn joining_from_welcome_should_prune_local_key_material(case: TestContext) {
99 let [alice, bob] = case.sessions().await;
100 Box::pin(async move {
101 let commit_guard = case.create_conversation([&alice]).await.invite([&bob]).await;
104
105 let prev_count = bob.transaction.count_entities().await;
107 commit_guard.notify_members().await;
109
110 let next_count = bob.transaction.count_entities().await;
113 assert_eq!(next_count.key_package, prev_count.key_package - 1);
114 assert_eq!(next_count.hpke_private_key, prev_count.hpke_private_key - 1);
115 assert_eq!(next_count.encryption_keypair, prev_count.encryption_keypair - 1);
116 })
117 .await;
118 }
119
120 #[apply(all_cred_cipher)]
121 async fn process_welcome_should_fail_when_already_exists(case: TestContext) {
122 use crate::LeafError;
123
124 let [alice, bob] = case.sessions().await;
125 Box::pin(async move {
126 let credential_ref = &bob.initial_credential;
127 let commit = case.create_conversation([&alice]).await.invite([&bob]).await;
128 let conversation = commit.conversation();
129 let id = conversation.id().clone();
130 bob
132 .transaction
133 .new_conversation(&id, credential_ref, case.cfg.clone())
134 .await
135 .unwrap();
136
137 let welcome = conversation.transport().await.latest_welcome_message().await;
138 let join_welcome = bob
139 .transaction
140 .process_welcome_message(welcome.into())
141 .await;
142 assert!(
143 matches!(join_welcome.unwrap_err(),
144 Error::Recursive(crate::RecursiveError::MlsConversation { source, .. })
145 if matches!(*source, crate::mls::conversation::Error::Leaf(LeafError::ConversationAlreadyExists(ref i)) if i == &id
146 )
147 )
148 );
149 })
150 .await;
151 }
152}