core_crypto/mls/conversation/conversation_guard/
merge.rs1use openmls::prelude::MlsGroupStateError;
15
16use super::{ConversationGuard, Result};
17use crate::{
18 MlsError,
19 mls::conversation::{ConversationWithMls as _, Error},
20 prelude::{MlsProposalRef, Obfuscated},
21};
22
23impl ConversationGuard {
24 pub async fn clear_pending_proposal(&mut self, proposal_ref: MlsProposalRef) -> Result<()> {
39 let keystore = self.crypto_provider().await?.keystore();
40 let mut conversation = self.conversation_mut().await;
41 conversation
42 .group
43 .remove_pending_proposal(&keystore, &proposal_ref)
44 .await
45 .map_err(|mls_group_state_error| match mls_group_state_error {
46 MlsGroupStateError::PendingProposalNotFound => Error::PendingProposalNotFound(proposal_ref),
47 _ => MlsError::wrap("removing pending proposal")(mls_group_state_error).into(),
48 })?;
49 conversation.persist_group_when_changed(&keystore, true).await?;
50 Ok(())
51 }
52
53 pub(crate) async fn clear_pending_commit(&mut self) -> Result<()> {
65 let keystore = self.crypto_provider().await?.keystore();
66 let mut conversation = self.conversation_mut().await;
67 if conversation.group.pending_commit().is_some() {
68 conversation.group.clear_pending_commit();
69 conversation.persist_group_when_changed(&keystore, true).await?;
70 log::info!(group_id = Obfuscated::from(conversation.id()); "Cleared pending commit.");
71 Ok(())
72 } else {
73 Err(Error::PendingCommitNotFound)
74 }
75 }
76
77 pub(crate) async fn ensure_no_pending_commit(&mut self) -> Result<()> {
80 match self.clear_pending_commit().await {
81 Err(Error::PendingCommitNotFound) => Ok(()),
82 result => result,
83 }
84 }
85}
86
87#[cfg(test)]
88mod tests {
89 use openmls::prelude::Proposal;
90 use wasm_bindgen_test::*;
91
92 use crate::test_utils::*;
93
94 use super::*;
95
96 wasm_bindgen_test_configure!(run_in_browser);
97
98 mod clear_pending_proposal {
99 use super::*;
100
101 #[apply(all_cred_cipher)]
102 #[wasm_bindgen_test]
103 pub async fn should_remove_proposal(case: TestContext) {
104 let [mut alice_central, bob_central, charlie_central] = case.sessions().await;
105 Box::pin(async move {
106 let id = conversation_id();
107 alice_central
108 .transaction
109 .new_conversation(&id, case.credential_type, case.cfg.clone())
110 .await
111 .unwrap();
112 alice_central.invite_all(&case, &id, [&bob_central]).await.unwrap();
113 assert!(alice_central.pending_proposals(&id).await.is_empty());
114
115 let charlie_kp = charlie_central.get_one_key_package(&case).await;
116 let add_ref = alice_central
117 .transaction
118 .new_add_proposal(&id, charlie_kp)
119 .await
120 .unwrap()
121 .proposal_ref;
122
123 let remove_ref = alice_central
124 .transaction
125 .new_remove_proposal(&id, bob_central.get_client_id().await)
126 .await
127 .unwrap()
128 .proposal_ref;
129
130 let update_ref = alice_central
131 .transaction
132 .new_update_proposal(&id)
133 .await
134 .unwrap()
135 .proposal_ref;
136
137 assert_eq!(alice_central.pending_proposals(&id).await.len(), 3);
138 alice_central
139 .transaction
140 .conversation(&id)
141 .await
142 .unwrap()
143 .clear_pending_proposal(add_ref)
144 .await
145 .unwrap();
146 assert_eq!(alice_central.pending_proposals(&id).await.len(), 2);
147 assert!(
148 !alice_central
149 .pending_proposals(&id)
150 .await
151 .into_iter()
152 .any(|p| matches!(p.proposal(), Proposal::Add(_)))
153 );
154
155 alice_central
156 .transaction
157 .conversation(&id)
158 .await
159 .unwrap()
160 .clear_pending_proposal(remove_ref)
161 .await
162 .unwrap();
163 assert_eq!(alice_central.pending_proposals(&id).await.len(), 1);
164 assert!(
165 !alice_central
166 .pending_proposals(&id)
167 .await
168 .into_iter()
169 .any(|p| matches!(p.proposal(), Proposal::Remove(_)))
170 );
171
172 alice_central
173 .transaction
174 .conversation(&id)
175 .await
176 .unwrap()
177 .clear_pending_proposal(update_ref)
178 .await
179 .unwrap();
180 assert!(alice_central.pending_proposals(&id).await.is_empty());
181 assert!(
182 !alice_central
183 .pending_proposals(&id)
184 .await
185 .into_iter()
186 .any(|p| matches!(p.proposal(), Proposal::Update(_)))
187 );
188 })
189 .await
190 }
191
192 #[apply(all_cred_cipher)]
193 #[wasm_bindgen_test]
194 pub async fn should_fail_when_proposal_ref_not_found(case: TestContext) {
195 let [mut alice_central] = case.sessions().await;
196 Box::pin(async move {
197 let id = conversation_id();
198 alice_central
199 .transaction
200 .new_conversation(&id, case.credential_type, case.cfg.clone())
201 .await
202 .unwrap();
203 assert!(alice_central.pending_proposals(&id).await.is_empty());
204 let any_ref = MlsProposalRef::from(vec![0; case.ciphersuite().hash_length()]);
205 let clear = alice_central
206 .transaction
207 .conversation(&id)
208 .await
209 .unwrap()
210 .clear_pending_proposal(any_ref.clone())
211 .await;
212 assert!(matches!(clear.unwrap_err(), Error::PendingProposalNotFound(prop_ref) if prop_ref == any_ref))
213 })
214 .await
215 }
216
217 #[apply(all_cred_cipher)]
218 #[wasm_bindgen_test]
219 pub async fn should_clean_associated_key_material(case: TestContext) {
220 let [mut cc] = case.sessions().await;
221 Box::pin(async move {
222 let id = conversation_id();
223 cc.transaction
224 .new_conversation(&id, case.credential_type, case.cfg.clone())
225 .await
226 .unwrap();
227 assert!(cc.pending_proposals(&id).await.is_empty());
228
229 let init = cc.transaction.count_entities().await;
230
231 let proposal_ref = cc.transaction.new_update_proposal(&id).await.unwrap().proposal_ref;
232 assert_eq!(cc.pending_proposals(&id).await.len(), 1);
233
234 cc.transaction
235 .conversation(&id)
236 .await
237 .unwrap()
238 .clear_pending_proposal(proposal_ref)
239 .await
240 .unwrap();
241 assert!(cc.pending_proposals(&id).await.is_empty());
242
243 let after_clear_proposal = cc.transaction.count_entities().await;
247 assert_eq!(init, after_clear_proposal);
248 })
249 .await
250 }
251 }
252
253 mod clear_pending_commit {
254 use super::*;
255
256 #[apply(all_cred_cipher)]
257 #[wasm_bindgen_test]
258 pub async fn should_remove_commit(case: TestContext) {
259 let [alice_central] = case.sessions().await;
260 Box::pin(async move {
261 let id = conversation_id();
262 alice_central
263 .transaction
264 .new_conversation(&id, case.credential_type, case.cfg.clone())
265 .await
266 .unwrap();
267 assert!(alice_central.pending_commit(&id).await.is_none());
268
269 alice_central.create_unmerged_commit(&id).await;
270 assert!(alice_central.pending_commit(&id).await.is_some());
271 alice_central
272 .transaction
273 .conversation(&id)
274 .await
275 .unwrap()
276 .clear_pending_commit()
277 .await
278 .unwrap();
279 assert!(alice_central.pending_commit(&id).await.is_none());
280 })
281 .await
282 }
283
284 #[apply(all_cred_cipher)]
285 #[wasm_bindgen_test]
286 pub async fn should_fail_when_pending_commit_absent(case: TestContext) {
287 let [alice_central] = case.sessions().await;
288 Box::pin(async move {
289 let id = conversation_id();
290 alice_central
291 .transaction
292 .new_conversation(&id, case.credential_type, case.cfg.clone())
293 .await
294 .unwrap();
295 assert!(alice_central.pending_commit(&id).await.is_none());
296 let clear = alice_central
297 .transaction
298 .conversation(&id)
299 .await
300 .unwrap()
301 .clear_pending_commit()
302 .await;
303 assert!(matches!(clear.unwrap_err(), Error::PendingCommitNotFound))
304 })
305 .await
306 }
307
308 #[apply(all_cred_cipher)]
309 #[wasm_bindgen_test]
310 pub async fn should_clean_associated_key_material(case: TestContext) {
311 let [cc] = case.sessions().await;
312 Box::pin(async move {
313 let id = conversation_id();
314 cc.transaction
315 .new_conversation(&id, case.credential_type, case.cfg.clone())
316 .await
317 .unwrap();
318 assert!(cc.pending_commit(&id).await.is_none());
319
320 let init = cc.transaction.count_entities().await;
321
322 cc.create_unmerged_commit(&id).await;
323 assert!(cc.pending_commit(&id).await.is_some());
324
325 cc.transaction
326 .conversation(&id)
327 .await
328 .unwrap()
329 .clear_pending_commit()
330 .await
331 .unwrap();
332 assert!(cc.pending_commit(&id).await.is_none());
333
334 let after_clear_commit = cc.transaction.count_entities().await;
338 assert_eq!(init, after_clear_commit);
339 })
340 .await
341 }
342 }
343}