1use core_crypto_keystore::entities::MlsEncryptionKeyPair;
2use openmls::prelude::{LeafNode, LeafNodeIndex, Proposal, QueuedProposal, Sender, StagedCommit};
3use openmls_traits::OpenMlsCryptoProvider;
4
5use mls_crypto_provider::MlsCryptoProvider;
6
7use crate::prelude::{Client, CryptoError, CryptoResult, MlsConversation, MlsProposalBundle};
8
9pub(crate) struct Renew;
12
13impl Renew {
14 pub(crate) fn renew<'a>(
26 self_index: &LeafNodeIndex,
27 pending_proposals: impl Iterator<Item = QueuedProposal> + 'a,
28 pending_commit: Option<&'a StagedCommit>,
29 valid_commit: &'a StagedCommit,
30 ) -> (Vec<QueuedProposal>, bool) {
31 let mut needs_update = false;
35
36 let renewed_pending_proposals = if let Some(pending_commit) = pending_commit {
37 let commit_proposals = pending_commit.queued_proposals().cloned().collect::<Vec<_>>();
39
40 let empty_commit = commit_proposals.is_empty();
42
43 let valid_commit_has_own_update_proposal = valid_commit.update_proposals().any(|p| match p.sender() {
45 Sender::Member(sender_index) => self_index == sender_index,
46 _ => false,
47 });
48
49 needs_update = !valid_commit_has_own_update_proposal && empty_commit;
51
52 commit_proposals
54 .into_iter()
55 .filter_map(|p| Self::is_proposal_renewable(p, Some(valid_commit)))
56 .collect::<Vec<_>>()
57 } else {
58 pending_proposals
60 .filter_map(|p| Self::is_proposal_renewable(p, Some(valid_commit)))
61 .collect::<Vec<_>>()
62 };
63 (renewed_pending_proposals, needs_update)
64 }
65
66 fn is_proposal_renewable(proposal: QueuedProposal, commit: Option<&StagedCommit>) -> Option<QueuedProposal> {
68 if let Some(commit) = commit {
69 let in_commit = match proposal.proposal() {
70 Proposal::Add(ref add) => commit.add_proposals().any(|p| {
71 let commits_identity = p.add_proposal().key_package().leaf_node().credential().identity();
72 let proposal_identity = add.key_package().leaf_node().credential().identity();
73 commits_identity == proposal_identity
74 }),
75 Proposal::Remove(ref remove) => commit
76 .remove_proposals()
77 .any(|p| p.remove_proposal().removed() == remove.removed()),
78 Proposal::Update(ref update) => commit
79 .update_proposals()
80 .any(|p| p.update_proposal().leaf_node() == update.leaf_node()),
81 _ => true,
82 };
83 if in_commit {
84 None
85 } else {
86 Some(proposal)
87 }
88 } else {
89 Some(proposal)
91 }
92 }
93}
94
95impl MlsConversation {
96 pub(crate) async fn renew_proposals_for_current_epoch(
99 &mut self,
100 client: &Client,
101 backend: &MlsCryptoProvider,
102 proposals: impl Iterator<Item = QueuedProposal>,
103 needs_update: bool,
104 ) -> CryptoResult<Vec<MlsProposalBundle>> {
105 let mut bundle = vec![];
106 let is_external = |p: &QueuedProposal| matches!(p.sender(), Sender::External(_) | Sender::NewMemberProposal);
107 let proposals = proposals.filter(|p| !is_external(p));
108 for proposal in proposals {
109 let msg = match proposal.proposal {
110 Proposal::Add(add) => self.propose_add_member(client, backend, add.key_package.into()).await?,
111 Proposal::Remove(remove) => self.propose_remove_member(client, backend, remove.removed()).await?,
112 Proposal::Update(update) => self.renew_update(client, backend, Some(update.leaf_node())).await?,
113 _ => return Err(CryptoError::ImplementationError),
114 };
115 bundle.push(msg);
116 }
117 if needs_update {
118 let proposal = self.renew_update(client, backend, None).await?;
119 bundle.push(proposal);
120 }
121 Ok(bundle)
122 }
123
124 async fn renew_update(
128 &mut self,
129 client: &Client,
130 backend: &MlsCryptoProvider,
131 leaf_node: Option<&LeafNode>,
132 ) -> CryptoResult<MlsProposalBundle> {
133 if let Some(leaf_node) = leaf_node {
134 backend
137 .key_store()
138 .remove::<MlsEncryptionKeyPair, _>(leaf_node.encryption_key().as_slice())
139 .await?;
140 }
141
142 let mut leaf_node = leaf_node
143 .or_else(|| self.group.own_leaf())
144 .cloned()
145 .ok_or(CryptoError::InternalMlsError)?;
146
147 let sc = self.signature_scheme();
148 let ct = self.own_credential_type()?;
149 let cb = client.find_most_recent_credential_bundle(sc, ct).await?;
150
151 leaf_node.set_credential_with_key(cb.to_mls_credential_with_key());
152
153 self.propose_explicit_self_update(client, backend, Some(leaf_node))
154 .await
155 }
156
157 pub(crate) fn self_pending_proposals(&self) -> impl Iterator<Item = &QueuedProposal> {
158 self.group
159 .pending_proposals()
160 .filter(|&p| matches!(p.sender(), Sender::Member(i) if i == &self.group.own_leaf_index()))
161 }
162}
163
164#[cfg(test)]
165mod tests {
166 use wasm_bindgen_test::*;
167
168 use crate::test_utils::*;
169
170 mod update {
171 use super::*;
172
173 #[apply(all_cred_cipher)]
174 #[wasm_bindgen_test]
175 pub async fn renewable_when_created_by_self(case: TestCase) {
176 run_test_with_client_ids(
177 case.clone(),
178 ["alice", "bob"],
179 move |[mut alice_central, bob_central]| {
180 Box::pin(async move {
181 let id = conversation_id();
182 alice_central
183 .context
184 .new_conversation(&id, case.credential_type, case.cfg.clone())
185 .await
186 .unwrap();
187 alice_central.invite_all(&case, &id, [&bob_central]).await.unwrap();
188
189 assert!(alice_central.pending_proposals(&id).await.is_empty());
190 alice_central.context.new_update_proposal(&id).await.unwrap();
191 assert_eq!(alice_central.pending_proposals(&id).await.len(), 1);
192
193 let commit = bob_central.context.update_keying_material(&id).await.unwrap().commit;
195 bob_central.context.commit_accepted(&id).await.unwrap();
196
197 let proposals = alice_central
198 .context
199 .decrypt_message(&id, commit.to_bytes().unwrap())
200 .await
201 .unwrap()
202 .proposals;
203 assert_eq!(alice_central.pending_proposals(&id).await.len(), 1);
205 assert_eq!(proposals.len(), alice_central.pending_proposals(&id).await.len());
206
207 alice_central.context.commit_pending_proposals(&id).await.unwrap();
209 assert!(alice_central.pending_commit(&id).await.is_some());
210 let commit = bob_central.context.update_keying_material(&id).await.unwrap().commit;
211 let proposals = alice_central
212 .context
213 .decrypt_message(&id, commit.to_bytes().unwrap())
214 .await
215 .unwrap()
216 .proposals;
217 assert_eq!(alice_central.pending_proposals(&id).await.len(), 1);
220 assert_eq!(proposals.len(), alice_central.pending_proposals(&id).await.len());
221 })
222 },
223 )
224 .await
225 }
226
227 #[apply(all_cred_cipher)]
228 #[wasm_bindgen_test]
229 pub async fn renews_pending_commit_when_created_by_self(case: TestCase) {
230 run_test_with_client_ids(
231 case.clone(),
232 ["alice", "bob"],
233 move |[mut alice_central, bob_central]| {
234 Box::pin(async move {
235 let id = conversation_id();
236 alice_central
237 .context
238 .new_conversation(&id, case.credential_type, case.cfg.clone())
239 .await
240 .unwrap();
241 alice_central.invite_all(&case, &id, [&bob_central]).await.unwrap();
242
243 alice_central.context.update_keying_material(&id).await.unwrap();
244 assert!(alice_central.pending_commit(&id).await.is_some());
245
246 let commit = bob_central.context.update_keying_material(&id).await.unwrap().commit;
248
249 let proposals = alice_central
250 .context
251 .decrypt_message(&id, commit.to_bytes().unwrap())
252 .await
253 .unwrap()
254 .proposals;
255 assert_eq!(alice_central.pending_proposals(&id).await.len(), 1);
257 assert_eq!(proposals.len(), alice_central.pending_proposals(&id).await.len());
258 })
259 },
260 )
261 .await
262 }
263
264 #[apply(all_cred_cipher)]
265 #[wasm_bindgen_test]
266 pub async fn not_renewable_when_in_valid_commit(case: TestCase) {
267 run_test_with_client_ids(
268 case.clone(),
269 ["alice", "bob"],
270 move |[mut alice_central, bob_central]| {
271 Box::pin(async move {
272 let id = conversation_id();
273 alice_central
274 .context
275 .new_conversation(&id, case.credential_type, case.cfg.clone())
276 .await
277 .unwrap();
278 alice_central.invite_all(&case, &id, [&bob_central]).await.unwrap();
279
280 assert!(alice_central.pending_proposals(&id).await.is_empty());
281 let proposal = alice_central.context.new_update_proposal(&id).await.unwrap().proposal;
282 assert_eq!(alice_central.pending_proposals(&id).await.len(), 1);
283
284 bob_central
286 .context
287 .decrypt_message(&id, proposal.to_bytes().unwrap())
288 .await
289 .unwrap();
290
291 let commit = bob_central.context.update_keying_material(&id).await.unwrap().commit;
292 bob_central.context.commit_accepted(&id).await.unwrap();
293
294 let proposals = alice_central
296 .context
297 .decrypt_message(&id, commit.to_bytes().unwrap())
298 .await
299 .unwrap()
300 .proposals;
301 assert!(alice_central.pending_proposals(&id).await.is_empty());
303 assert_eq!(proposals.len(), alice_central.pending_proposals(&id).await.len());
304
305 let proposal = alice_central.context.new_update_proposal(&id).await.unwrap().proposal;
307 alice_central.context.commit_pending_proposals(&id).await.unwrap();
308 assert_eq!(alice_central.pending_proposals(&id).await.len(), 1);
309 assert!(alice_central.pending_commit(&id).await.is_some());
310 bob_central
311 .context
312 .decrypt_message(&id, proposal.to_bytes().unwrap())
313 .await
314 .unwrap();
315 let commit = bob_central.context.update_keying_material(&id).await.unwrap().commit;
316 let proposals = alice_central
317 .context
318 .decrypt_message(&id, commit.to_bytes().unwrap())
319 .await
320 .unwrap()
321 .proposals;
322 assert!(alice_central.pending_proposals(&id).await.is_empty());
324 assert_eq!(proposals.len(), alice_central.pending_proposals(&id).await.len());
325 })
326 },
327 )
328 .await
329 }
330
331 #[apply(all_cred_cipher)]
332 #[wasm_bindgen_test]
333 pub async fn not_renewable_by_ref(case: TestCase) {
334 run_test_with_client_ids(
335 case.clone(),
336 ["alice", "bob", "charlie"],
337 move |[mut alice_central, bob_central, charlie_central]| {
338 Box::pin(async move {
339 let id = conversation_id();
340 alice_central
341 .context
342 .new_conversation(&id, case.credential_type, case.cfg.clone())
343 .await
344 .unwrap();
345 alice_central
346 .invite_all(&case, &id, [&bob_central, &charlie_central])
347 .await
348 .unwrap();
349
350 let proposal = bob_central.context.new_update_proposal(&id).await.unwrap().proposal;
351 assert!(alice_central.pending_proposals(&id).await.is_empty());
352 alice_central
353 .context
354 .decrypt_message(&id, proposal.to_bytes().unwrap())
355 .await
356 .unwrap();
357 assert_eq!(alice_central.pending_proposals(&id).await.len(), 1);
358
359 let commit = charlie_central
361 .context
362 .update_keying_material(&id)
363 .await
364 .unwrap()
365 .commit;
366 let proposals = alice_central
367 .context
368 .decrypt_message(&id, commit.to_bytes().unwrap())
369 .await
370 .unwrap()
371 .proposals;
372 assert!(alice_central.pending_proposals(&id).await.is_empty());
374 assert_eq!(proposals.len(), alice_central.pending_proposals(&id).await.len());
375 })
376 },
377 )
378 .await
379 }
380 }
381
382 mod add {
383 use super::*;
384
385 #[apply(all_cred_cipher)]
386 #[wasm_bindgen_test]
387 pub async fn not_renewable_when_valid_commit_adds_same(case: TestCase) {
388 run_test_with_client_ids(
389 case.clone(),
390 ["alice", "bob", "charlie"],
391 move |[mut alice_central, bob_central, charlie_central]| {
392 Box::pin(async move {
393 let id = conversation_id();
394 alice_central
395 .context
396 .new_conversation(&id, case.credential_type, case.cfg.clone())
397 .await
398 .unwrap();
399 alice_central.invite_all(&case, &id, [&bob_central]).await.unwrap();
400
401 let charlie_kp = charlie_central.get_one_key_package(&case).await;
402 assert!(alice_central.pending_proposals(&id).await.is_empty());
403 alice_central.context.new_add_proposal(&id, charlie_kp).await.unwrap();
404 assert_eq!(alice_central.pending_proposals(&id).await.len(), 1);
405
406 let charlie = charlie_central.rand_key_package(&case).await;
407 let commit = bob_central
408 .context
409 .add_members_to_conversation(&id, vec![charlie])
410 .await
411 .unwrap()
412 .commit;
413 let proposals = alice_central
414 .context
415 .decrypt_message(&id, commit.to_bytes().unwrap())
416 .await
417 .unwrap()
418 .proposals;
419 assert!(alice_central.pending_proposals(&id).await.is_empty());
421 assert_eq!(proposals.len(), alice_central.pending_proposals(&id).await.len());
422 })
423 },
424 )
425 .await
426 }
427
428 #[apply(all_cred_cipher)]
429 #[wasm_bindgen_test]
430 pub async fn not_renewable_in_pending_commit_when_valid_commit_adds_same(case: TestCase) {
431 run_test_with_client_ids(
432 case.clone(),
433 ["alice", "bob", "charlie"],
434 move |[mut alice_central, bob_central, charlie_central]| {
435 Box::pin(async move {
436 let id = conversation_id();
437 alice_central
438 .context
439 .new_conversation(&id, case.credential_type, case.cfg.clone())
440 .await
441 .unwrap();
442 alice_central.invite_all(&case, &id, [&bob_central]).await.unwrap();
443
444 let charlie_kp = charlie_central.get_one_key_package(&case).await;
445 assert!(alice_central.pending_proposals(&id).await.is_empty());
446 alice_central.context.new_add_proposal(&id, charlie_kp).await.unwrap();
447 assert_eq!(alice_central.pending_proposals(&id).await.len(), 1);
448
449 alice_central.context.commit_pending_proposals(&id).await.unwrap();
451 assert!(alice_central.pending_commit(&id).await.is_some());
452
453 let charlie = charlie_central.rand_key_package(&case).await;
454 let commit = bob_central
455 .context
456 .add_members_to_conversation(&id, vec![charlie])
457 .await
458 .unwrap()
459 .commit;
460 let proposals = alice_central
461 .context
462 .decrypt_message(&id, commit.to_bytes().unwrap())
463 .await
464 .unwrap()
465 .proposals;
466 assert!(alice_central.pending_proposals(&id).await.is_empty());
468 assert_eq!(proposals.len(), alice_central.pending_proposals(&id).await.len());
469 })
470 },
471 )
472 .await
473 }
474
475 #[apply(all_cred_cipher)]
476 #[wasm_bindgen_test]
477 pub async fn not_renewable_by_ref(case: TestCase) {
478 run_test_with_client_ids(
479 case.clone(),
480 ["alice", "bob", "charlie", "debbie"],
481 move |[mut alice_central, bob_central, charlie_central, debbie_central]| {
482 Box::pin(async move {
483 let id = conversation_id();
484 alice_central
485 .context
486 .new_conversation(&id, case.credential_type, case.cfg.clone())
487 .await
488 .unwrap();
489 alice_central
490 .invite_all(&case, &id, [&bob_central, &charlie_central])
491 .await
492 .unwrap();
493
494 let debbie_kp = debbie_central.get_one_key_package(&case).await;
496 let proposal = bob_central
497 .context
498 .new_add_proposal(&id, debbie_kp)
499 .await
500 .unwrap()
501 .proposal;
502 alice_central
503 .context
504 .decrypt_message(&id, proposal.to_bytes().unwrap())
505 .await
506 .unwrap();
507 assert_eq!(alice_central.pending_proposals(&id).await.len(), 1);
508
509 let commit = charlie_central
511 .context
512 .update_keying_material(&id)
513 .await
514 .unwrap()
515 .commit;
516 let proposals = alice_central
517 .context
518 .decrypt_message(&id, commit.to_bytes().unwrap())
519 .await
520 .unwrap()
521 .proposals;
522 assert!(alice_central.pending_proposals(&id).await.is_empty());
524 assert_eq!(proposals.len(), alice_central.pending_proposals(&id).await.len());
525 })
526 },
527 )
528 .await
529 }
530
531 #[apply(all_cred_cipher)]
532 #[wasm_bindgen_test]
533 pub async fn renewable_when_valid_commit_doesnt_adds_same(case: TestCase) {
534 run_test_with_client_ids(
535 case.clone(),
536 ["alice", "bob", "charlie"],
537 move |[mut alice_central, bob_central, charlie_central]| {
538 Box::pin(async move {
539 let id = conversation_id();
540 alice_central
541 .context
542 .new_conversation(&id, case.credential_type, case.cfg.clone())
543 .await
544 .unwrap();
545 alice_central.invite_all(&case, &id, [&bob_central]).await.unwrap();
546
547 let charlie_kp = charlie_central.get_one_key_package(&case).await;
549 assert!(alice_central.pending_proposals(&id).await.is_empty());
550 alice_central.context.new_add_proposal(&id, charlie_kp).await.unwrap();
551 assert_eq!(alice_central.pending_proposals(&id).await.len(), 1);
552
553 let commit = bob_central.context.update_keying_material(&id).await.unwrap().commit;
555 bob_central.context.commit_accepted(&id).await.unwrap();
556 let proposals = alice_central
557 .context
558 .decrypt_message(&id, commit.to_bytes().unwrap())
559 .await
560 .unwrap()
561 .proposals;
562 assert_eq!(alice_central.pending_proposals(&id).await.len(), 1);
564 assert_eq!(proposals.len(), alice_central.pending_proposals(&id).await.len());
565
566 alice_central.context.commit_pending_proposals(&id).await.unwrap();
568 assert!(alice_central.pending_commit(&id).await.is_some());
569 let commit = bob_central.context.update_keying_material(&id).await.unwrap().commit;
570 let proposals = alice_central
571 .context
572 .decrypt_message(&id, commit.to_bytes().unwrap())
573 .await
574 .unwrap()
575 .proposals;
576 assert_eq!(alice_central.pending_proposals(&id).await.len(), 1);
579 assert_eq!(proposals.len(), alice_central.pending_proposals(&id).await.len());
580 })
581 },
582 )
583 .await
584 }
585
586 #[apply(all_cred_cipher)]
587 #[wasm_bindgen_test]
588 pub async fn renews_pending_commit_when_valid_commit_doesnt_add_same(case: TestCase) {
589 run_test_with_client_ids(
590 case.clone(),
591 ["alice", "bob", "charlie"],
592 move |[mut alice_central, bob_central, charlie_central]| {
593 Box::pin(async move {
594 let id = conversation_id();
595 alice_central
596 .context
597 .new_conversation(&id, case.credential_type, case.cfg.clone())
598 .await
599 .unwrap();
600 alice_central.invite_all(&case, &id, [&bob_central]).await.unwrap();
601
602 let charlie = charlie_central.rand_key_package(&case).await;
604 alice_central
605 .context
606 .add_members_to_conversation(&id, vec![charlie])
607 .await
608 .unwrap();
609 assert!(alice_central.pending_commit(&id).await.is_some());
610
611 let commit = bob_central.context.update_keying_material(&id).await.unwrap().commit;
613 let proposals = alice_central
614 .context
615 .decrypt_message(&id, commit.to_bytes().unwrap())
616 .await
617 .unwrap()
618 .proposals;
619 assert_eq!(alice_central.pending_proposals(&id).await.len(), 1);
621 assert_eq!(proposals.len(), alice_central.pending_proposals(&id).await.len());
622 })
623 },
624 )
625 .await
626 }
627 }
628
629 mod remove {
630 use super::*;
631
632 #[apply(all_cred_cipher)]
633 #[wasm_bindgen_test]
634 pub async fn not_renewable_when_valid_commit_removes_same(case: TestCase) {
635 run_test_with_client_ids(
636 case.clone(),
637 ["alice", "bob", "charlie"],
638 move |[mut alice_central, bob_central, charlie_central]| {
639 Box::pin(async move {
640 let id = conversation_id();
641 alice_central
642 .context
643 .new_conversation(&id, case.credential_type, case.cfg.clone())
644 .await
645 .unwrap();
646 alice_central
647 .invite_all(&case, &id, [&bob_central, &charlie_central])
648 .await
649 .unwrap();
650
651 assert!(alice_central.pending_proposals(&id).await.is_empty());
652 alice_central
653 .context
654 .new_remove_proposal(&id, charlie_central.get_client_id().await)
655 .await
656 .unwrap();
657 assert_eq!(alice_central.pending_proposals(&id).await.len(), 1);
658
659 let commit = bob_central
660 .context
661 .remove_members_from_conversation(&id, &[charlie_central.get_client_id().await])
662 .await
663 .unwrap()
664 .commit;
665 let proposals = alice_central
666 .context
667 .decrypt_message(&id, commit.to_bytes().unwrap())
668 .await
669 .unwrap()
670 .proposals;
671 assert!(alice_central.pending_proposals(&id).await.is_empty());
673 assert_eq!(proposals.len(), alice_central.pending_proposals(&id).await.len());
674 })
675 },
676 )
677 .await
678 }
679
680 #[apply(all_cred_cipher)]
681 #[wasm_bindgen_test]
682 pub async fn not_renewable_by_ref(case: TestCase) {
683 run_test_with_client_ids(
684 case.clone(),
685 ["alice", "bob", "charlie"],
686 move |[mut alice_central, bob_central, charlie_central]| {
687 Box::pin(async move {
688 let id = conversation_id();
689 alice_central
690 .context
691 .new_conversation(&id, case.credential_type, case.cfg.clone())
692 .await
693 .unwrap();
694 alice_central
695 .invite_all(&case, &id, [&bob_central, &charlie_central])
696 .await
697 .unwrap();
698
699 let proposal = bob_central
700 .context
701 .new_remove_proposal(&id, charlie_central.get_client_id().await)
702 .await
703 .unwrap()
704 .proposal;
705 assert!(alice_central.pending_proposals(&id).await.is_empty());
706 alice_central
707 .context
708 .decrypt_message(&id, proposal.to_bytes().unwrap())
709 .await
710 .unwrap();
711 assert_eq!(alice_central.pending_proposals(&id).await.len(), 1);
712
713 let commit = charlie_central
714 .context
715 .update_keying_material(&id)
716 .await
717 .unwrap()
718 .commit;
719 let proposals = alice_central
720 .context
721 .decrypt_message(&id, commit.to_bytes().unwrap())
722 .await
723 .unwrap()
724 .proposals;
725 assert!(alice_central.pending_proposals(&id).await.is_empty());
727 assert_eq!(proposals.len(), alice_central.pending_proposals(&id).await.len());
728 })
729 },
730 )
731 .await
732 }
733
734 #[apply(all_cred_cipher)]
735 #[wasm_bindgen_test]
736 pub async fn renewable_when_valid_commit_doesnt_remove_same(case: TestCase) {
737 run_test_with_client_ids(
738 case.clone(),
739 ["alice", "bob", "charlie", "debbie"],
740 move |[mut alice_central, bob_central, charlie_central, debbie_central]| {
741 Box::pin(async move {
742 let id = conversation_id();
743 alice_central
744 .context
745 .new_conversation(&id, case.credential_type, case.cfg.clone())
746 .await
747 .unwrap();
748 alice_central
749 .invite_all(&case, &id, [&bob_central, &charlie_central, &debbie_central])
750 .await
751 .unwrap();
752
753 assert!(alice_central.pending_proposals(&id).await.is_empty());
755 alice_central
756 .context
757 .new_remove_proposal(&id, charlie_central.get_client_id().await)
758 .await
759 .unwrap();
760 assert_eq!(alice_central.pending_proposals(&id).await.len(), 1);
761
762 let commit = bob_central
764 .context
765 .remove_members_from_conversation(&id, &[debbie_central.get_client_id().await])
766 .await
767 .unwrap()
768 .commit;
769 let proposals = alice_central
770 .context
771 .decrypt_message(&id, commit.to_bytes().unwrap())
772 .await
773 .unwrap()
774 .proposals;
775 assert_eq!(alice_central.pending_proposals(&id).await.len(), 1);
777 assert_eq!(proposals.len(), alice_central.pending_proposals(&id).await.len());
778 })
779 },
780 )
781 .await
782 }
783
784 #[apply(all_cred_cipher)]
785 #[wasm_bindgen_test]
786 pub async fn renews_pending_commit_when_commit_doesnt_remove_same(case: TestCase) {
787 run_test_with_client_ids(
788 case.clone(),
789 ["alice", "bob", "charlie", "debbie"],
790 move |[mut alice_central, bob_central, charlie_central, debbie_central]| {
791 Box::pin(async move {
792 let id = conversation_id();
793 alice_central
794 .context
795 .new_conversation(&id, case.credential_type, case.cfg.clone())
796 .await
797 .unwrap();
798 alice_central
799 .invite_all(&case, &id, [&bob_central, &charlie_central, &debbie_central])
800 .await
801 .unwrap();
802
803 alice_central
805 .context
806 .remove_members_from_conversation(&id, &[charlie_central.get_client_id().await])
807 .await
808 .unwrap();
809 assert!(alice_central.pending_commit(&id).await.is_some());
810
811 let commit = bob_central
813 .context
814 .remove_members_from_conversation(&id, &[debbie_central.get_client_id().await])
815 .await
816 .unwrap()
817 .commit;
818 let proposals = alice_central
819 .context
820 .decrypt_message(&id, commit.to_bytes().unwrap())
821 .await
822 .unwrap()
823 .proposals;
824 assert_eq!(alice_central.pending_proposals(&id).await.len(), 1);
826 assert_eq!(proposals.len(), alice_central.pending_proposals(&id).await.len());
827 })
828 },
829 )
830 .await
831 }
832
833 #[apply(all_cred_cipher)]
834 #[wasm_bindgen_test]
835 pub async fn renews_pending_commit_from_proposal_when_commit_doesnt_remove_same(case: TestCase) {
836 run_test_with_client_ids(
837 case.clone(),
838 ["alice", "bob", "charlie", "debbie"],
839 move |[mut alice_central, bob_central, charlie_central, debbie_central]| {
840 Box::pin(async move {
841 let id = conversation_id();
842 alice_central
843 .context
844 .new_conversation(&id, case.credential_type, case.cfg.clone())
845 .await
846 .unwrap();
847 alice_central
848 .invite_all(&case, &id, [&bob_central, &charlie_central, &debbie_central])
849 .await
850 .unwrap();
851
852 alice_central
854 .context
855 .new_remove_proposal(&id, charlie_central.get_client_id().await)
856 .await
857 .unwrap();
858 alice_central.context.commit_pending_proposals(&id).await.unwrap();
859 assert_eq!(alice_central.pending_proposals(&id).await.len(), 1);
860 assert!(alice_central.pending_commit(&id).await.is_some());
861
862 let commit = bob_central
864 .context
865 .remove_members_from_conversation(&id, &[debbie_central.get_client_id().await])
866 .await
867 .unwrap()
868 .commit;
869 let proposals = alice_central
870 .context
871 .decrypt_message(&id, commit.to_bytes().unwrap())
872 .await
873 .unwrap()
874 .proposals;
875 assert_eq!(alice_central.pending_proposals(&id).await.len(), 1);
877 assert_eq!(proposals.len(), alice_central.pending_proposals(&id).await.len());
878 })
879 },
880 )
881 .await
882 }
883 }
884}