core_crypto/transaction_context/conversation/
welcome.rs1use openmls::prelude::{MlsMessageIn, MlsMessageInBody};
4
5use super::{Error, Result, TransactionContext};
6use crate::{ConversationConfiguration, ConversationId};
7
8impl TransactionContext {
9 #[cfg_attr(test, crate::dispotent)]
22 pub async fn process_welcome_message(&self, welcome: impl Into<MlsMessageIn>) -> Result<ConversationId> {
23 let MlsMessageInBody::Welcome(welcome) = welcome.into().extract() else {
24 return Err(Error::CallerError(
25 "the message provided to process_welcome_message was not a welcome message",
26 ));
27 };
28
29 let configuration = ConversationConfiguration {
30 cipher_suite: welcome.ciphersuite().into(),
31 ..Default::default()
32 };
33
34 let inner = self.inner()?;
35 let conversation = inner
36 .with_savepoint(
37 "process_welcome_message_savepoint",
38 async || {
39 self.persist_conversation_from_welcome_message(welcome, configuration)
40 .await
41 },
42 Error::savepoint,
43 )
44 .await?;
45
46 let id = conversation.id().to_owned();
47
48 Ok(id)
49 }
50}
51
52#[cfg(test)]
53mod tests {
54 use crate::test_utils::*;
55
56 #[apply(all_cred_cipher)]
57 async fn joining_from_welcome_should_prune_local_key_material(case: TestContext) {
58 let [alice, bob] = case.sessions().await;
59 Box::pin(async move {
60 let commit_guard = case.create_conversation([&alice]).await.invite([&bob]).await;
63
64 let prev_count = bob.transaction.count_entities().await;
66 commit_guard.notify_members().await;
68
69 let next_count = bob.transaction.count_entities().await;
72 assert_eq!(next_count.key_package, prev_count.key_package - 1);
73 assert_eq!(next_count.hpke_private_key, prev_count.hpke_private_key - 1);
74 assert_eq!(next_count.encryption_keypair, prev_count.encryption_keypair - 1);
75 })
76 .await;
77 }
78
79 #[apply(all_cred_cipher)]
80 async fn process_welcome_should_fail_when_already_exists(case: TestContext) {
81 let [alice, mut bob] = case.sessions().await;
82 Box::pin(async move {
83 let credential_ref = &bob.initial_credential;
84 let commit = case.create_conversation([&alice]).await.invite([&bob]).await;
85 let conversation = commit.conversation();
86 let id = conversation.id().clone();
87 bob
89 .transaction
90 .new_conversation(&id, credential_ref, case.cfg.clone())
91 .await
92 .unwrap();
93
94 let key_package_refs_before = bob.transaction.get_key_package_refs().await.unwrap();
96
97 let welcome = conversation.transport().await.latest_welcome_message().await;
98
99 bob.commit_transaction().await;
103
104 let join_welcome = bob
105 .transaction
106 .process_welcome_message(welcome)
107 .await;
108
109 let key_package_refs_after = bob.transaction.get_key_package_refs().await.unwrap();
111
112 assert!(!key_package_refs_before.is_empty());
113 assert_eq!(key_package_refs_before, key_package_refs_after);
114 assert!(innermost_source_matches!(join_welcome.unwrap_err(), super::Error::ConversationAlreadyExists(i) if i == &id));
115 })
116 .await;
117 }
118
119 #[apply(all_cred_cipher)]
127 async fn failed_welcome_should_restore_key_material_in_same_transaction(case: TestContext) {
128 let [alice, bob] = case.sessions().await;
129 let credential_ref = &bob.initial_credential;
130 let commit = case.create_conversation([&alice]).await.invite([&bob]).await;
131 let conversation = commit.conversation();
132 let id = conversation.id().clone();
133
134 bob.transaction
137 .new_conversation(&id, credential_ref, case.cfg.clone())
138 .await
139 .unwrap();
140
141 let welcome = conversation.transport().await.latest_welcome_message().await;
142
143 let count_before = bob.transaction.count_entities().await;
144 let key_package_refs_before = bob.transaction.get_key_package_refs().await.unwrap();
145 assert!(!key_package_refs_before.is_empty());
146
147 let join_welcome = bob.transaction.process_welcome_message(welcome).await;
148 assert!(innermost_source_matches!(
149 join_welcome.unwrap_err(),
150 super::Error::ConversationAlreadyExists(i) if i == &id
151 ));
152
153 let count_after = bob.transaction.count_entities().await;
156 assert_eq!(count_before, count_after);
157 let key_package_refs_after = bob.transaction.get_key_package_refs().await.unwrap();
158 assert_eq!(key_package_refs_before, key_package_refs_after);
159 }
160
161 #[apply(all_cred_cipher)]
165 async fn restored_key_material_should_still_be_able_to_join_from_welcome(case: TestContext) {
166 let [alice, bob] = case.sessions().await;
167 let credential_ref = &bob.initial_credential;
168 let commit = case.create_conversation([&alice]).await.invite([&bob]).await;
169 let conversation = commit.conversation();
170 let id = conversation.id().clone();
171
172 bob.transaction
174 .new_conversation(&id, credential_ref, case.cfg.clone())
175 .await
176 .unwrap();
177
178 let welcome = conversation.transport().await.latest_welcome_message().await;
179
180 let join_welcome = bob.transaction.process_welcome_message(welcome.clone()).await;
181 assert!(innermost_source_matches!(
182 join_welcome.unwrap_err(),
183 super::Error::ConversationAlreadyExists(i) if i == &id
184 ));
185
186 bob.transaction.conversation(&id).await.unwrap().wipe().await.unwrap();
188
189 let joined_id = bob.transaction.process_welcome_message(welcome).await.unwrap();
191 assert_eq!(joined_id, id);
192
193 let message = bob
195 .transaction
196 .conversation(&id)
197 .await
198 .unwrap()
199 .encrypt_message(b"hello")
200 .await
201 .unwrap();
202 let decrypted = conversation
203 .guard_of(&alice)
204 .await
205 .decrypt_message(&message)
206 .await
207 .unwrap();
208 assert_eq!(decrypted.as_application_message().unwrap().plaintext, b"hello");
209 }
210}