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 super::{Error, Result};
8use crate::{
9 KeystoreError, RecursiveError,
10 prelude::{MlsConversation, MlsProposalBundle, Session},
11};
12
13pub(crate) struct Renew;
16
17impl Renew {
18 pub(crate) fn renew<'a>(
30 self_index: &LeafNodeIndex,
31 pending_proposals: impl Iterator<Item = QueuedProposal> + 'a,
32 pending_commit: Option<&'a StagedCommit>,
33 valid_commit: &'a StagedCommit,
34 ) -> (Vec<QueuedProposal>, bool) {
35 let mut needs_update = false;
39
40 let renewed_pending_proposals = if let Some(pending_commit) = pending_commit {
41 let commit_proposals = pending_commit.queued_proposals().cloned().collect::<Vec<_>>();
43
44 let empty_commit = commit_proposals.is_empty();
46
47 let valid_commit_has_own_update_proposal = valid_commit.update_proposals().any(|p| match p.sender() {
49 Sender::Member(sender_index) => self_index == sender_index,
50 _ => false,
51 });
52
53 needs_update = !valid_commit_has_own_update_proposal && empty_commit;
55
56 commit_proposals
58 .into_iter()
59 .filter_map(|p| Self::is_proposal_renewable(p, Some(valid_commit)))
60 .collect::<Vec<_>>()
61 } else {
62 pending_proposals
64 .filter_map(|p| Self::is_proposal_renewable(p, Some(valid_commit)))
65 .collect::<Vec<_>>()
66 };
67 (renewed_pending_proposals, needs_update)
68 }
69
70 fn is_proposal_renewable(proposal: QueuedProposal, commit: Option<&StagedCommit>) -> Option<QueuedProposal> {
72 if let Some(commit) = commit {
73 let in_commit = match proposal.proposal() {
74 Proposal::Add(add) => commit.add_proposals().any(|p| {
75 let commits_identity = p.add_proposal().key_package().leaf_node().credential().identity();
76 let proposal_identity = add.key_package().leaf_node().credential().identity();
77 commits_identity == proposal_identity
78 }),
79 Proposal::Remove(remove) => commit
80 .remove_proposals()
81 .any(|p| p.remove_proposal().removed() == remove.removed()),
82 Proposal::Update(update) => commit
83 .update_proposals()
84 .any(|p| p.update_proposal().leaf_node() == update.leaf_node()),
85 _ => true,
86 };
87 if in_commit { None } else { Some(proposal) }
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: &Session,
101 backend: &MlsCryptoProvider,
102 proposals: impl Iterator<Item = QueuedProposal>,
103 needs_update: bool,
104 ) -> Result<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(Error::ProposalVariantCannotBeRenewed),
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: &Session,
130 backend: &MlsCryptoProvider,
131 leaf_node: Option<&LeafNode>,
132 ) -> Result<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 .map_err(KeystoreError::wrap("removing mls encryption keypair"))?;
141 }
142
143 let mut leaf_node = leaf_node
144 .or_else(|| self.group.own_leaf())
145 .cloned()
146 .ok_or(Error::MlsGroupInvalidState("own_leaf is None"))?;
147
148 let sc = self.signature_scheme();
149 let ct = self.own_credential_type()?;
150 let cb = client
151 .find_most_recent_credential_bundle(sc, ct)
152 .await
153 .map_err(RecursiveError::mls_client("finding most recent credential bundle"))?;
154
155 leaf_node.set_credential_with_key(cb.to_mls_credential_with_key());
156
157 self.propose_explicit_self_update(client, backend, Some(leaf_node))
158 .await
159 }
160
161 pub(crate) fn self_pending_proposals(&self) -> impl Iterator<Item = &QueuedProposal> {
162 self.group
163 .pending_proposals()
164 .filter(|&p| matches!(p.sender(), Sender::Member(i) if i == &self.group.own_leaf_index()))
165 }
166}
167
168#[cfg(test)]
169mod tests {
170 use wasm_bindgen_test::*;
171
172 use crate::test_utils::*;
173
174 mod update {
175 use super::*;
176
177 #[apply(all_cred_cipher)]
178 #[wasm_bindgen_test]
179 pub async fn renewable_when_created_by_self(case: TestContext) {
180 let [mut alice_central, bob_central] = case.sessions().await;
181 Box::pin(async move {
182 let id = conversation_id();
183 alice_central
184 .transaction
185 .new_conversation(&id, case.credential_type, case.cfg.clone())
186 .await
187 .unwrap();
188 alice_central.invite_all(&case, &id, [&bob_central]).await.unwrap();
189
190 assert!(alice_central.pending_proposals(&id).await.is_empty());
191 alice_central.transaction.new_update_proposal(&id).await.unwrap();
192 assert_eq!(alice_central.pending_proposals(&id).await.len(), 1);
193
194 bob_central
196 .transaction
197 .conversation(&id)
198 .await
199 .unwrap()
200 .update_key_material()
201 .await
202 .unwrap();
203 let commit = bob_central.mls_transport().await.latest_commit().await;
204
205 let proposals = alice_central
206 .transaction
207 .conversation(&id)
208 .await
209 .unwrap()
210 .decrypt_message(commit.to_bytes().unwrap())
211 .await
212 .unwrap()
213 .proposals;
214 assert_eq!(alice_central.pending_proposals(&id).await.len(), 1);
216 assert_eq!(proposals.len(), alice_central.pending_proposals(&id).await.len());
217
218 bob_central
220 .transaction
221 .conversation(&id)
222 .await
223 .unwrap()
224 .update_key_material()
225 .await
226 .unwrap();
227 let commit = bob_central.mls_transport().await.latest_commit().await;
228 let proposals = alice_central
229 .transaction
230 .conversation(&id)
231 .await
232 .unwrap()
233 .decrypt_message(commit.to_bytes().unwrap())
234 .await
235 .unwrap()
236 .proposals;
237 assert_eq!(alice_central.pending_proposals(&id).await.len(), 1);
240 assert_eq!(proposals.len(), alice_central.pending_proposals(&id).await.len());
241 })
242 .await
243 }
244
245 #[apply(all_cred_cipher)]
246 #[wasm_bindgen_test]
247 pub async fn not_renewable_when_in_valid_commit(case: TestContext) {
248 let [mut alice_central, bob_central] = case.sessions().await;
249 Box::pin(async move {
250 let id = conversation_id();
251 alice_central
252 .transaction
253 .new_conversation(&id, case.credential_type, case.cfg.clone())
254 .await
255 .unwrap();
256 alice_central.invite_all(&case, &id, [&bob_central]).await.unwrap();
257
258 assert!(alice_central.pending_proposals(&id).await.is_empty());
259 let proposal = alice_central
260 .transaction
261 .new_update_proposal(&id)
262 .await
263 .unwrap()
264 .proposal;
265 assert_eq!(alice_central.pending_proposals(&id).await.len(), 1);
266
267 bob_central
269 .transaction
270 .conversation(&id)
271 .await
272 .unwrap()
273 .decrypt_message(proposal.to_bytes().unwrap())
274 .await
275 .unwrap();
276
277 bob_central
278 .transaction
279 .conversation(&id)
280 .await
281 .unwrap()
282 .update_key_material()
283 .await
284 .unwrap();
285 let commit = bob_central.mls_transport().await.latest_commit().await;
286
287 let proposals = alice_central
289 .transaction
290 .conversation(&id)
291 .await
292 .unwrap()
293 .decrypt_message(commit.to_bytes().unwrap())
294 .await
295 .unwrap()
296 .proposals;
297 assert!(alice_central.pending_proposals(&id).await.is_empty());
299 assert_eq!(proposals.len(), alice_central.pending_proposals(&id).await.len());
300
301 let proposal = alice_central
302 .transaction
303 .new_update_proposal(&id)
304 .await
305 .unwrap()
306 .proposal;
307 bob_central
308 .transaction
309 .conversation(&id)
310 .await
311 .unwrap()
312 .decrypt_message(proposal.to_bytes().unwrap())
313 .await
314 .unwrap();
315 bob_central
316 .transaction
317 .conversation(&id)
318 .await
319 .unwrap()
320 .update_key_material()
321 .await
322 .unwrap();
323 let commit = bob_central.mls_transport().await.latest_commit().await;
324 let proposals = alice_central
325 .transaction
326 .conversation(&id)
327 .await
328 .unwrap()
329 .decrypt_message(commit.to_bytes().unwrap())
330 .await
331 .unwrap()
332 .proposals;
333 assert!(alice_central.pending_proposals(&id).await.is_empty());
335 assert_eq!(proposals.len(), alice_central.pending_proposals(&id).await.len());
336 })
337 .await
338 }
339
340 #[apply(all_cred_cipher)]
341 #[wasm_bindgen_test]
342 pub async fn not_renewable_by_ref(case: TestContext) {
343 let [mut alice_central, bob_central, charlie_central] = case.sessions().await;
344 Box::pin(async move {
345 let id = conversation_id();
346 alice_central
347 .transaction
348 .new_conversation(&id, case.credential_type, case.cfg.clone())
349 .await
350 .unwrap();
351 alice_central
352 .invite_all(&case, &id, [&bob_central, &charlie_central])
353 .await
354 .unwrap();
355
356 let proposal = bob_central.transaction.new_update_proposal(&id).await.unwrap().proposal;
357 assert!(alice_central.pending_proposals(&id).await.is_empty());
358 alice_central
359 .transaction
360 .conversation(&id)
361 .await
362 .unwrap()
363 .decrypt_message(proposal.to_bytes().unwrap())
364 .await
365 .unwrap();
366 assert_eq!(alice_central.pending_proposals(&id).await.len(), 1);
367
368 charlie_central
370 .transaction
371 .conversation(&id)
372 .await
373 .unwrap()
374 .update_key_material()
375 .await
376 .unwrap();
377 let commit = charlie_central.mls_transport().await.latest_commit().await;
378 let proposals = alice_central
379 .transaction
380 .conversation(&id)
381 .await
382 .unwrap()
383 .decrypt_message(commit.to_bytes().unwrap())
384 .await
385 .unwrap()
386 .proposals;
387 assert!(alice_central.pending_proposals(&id).await.is_empty());
389 assert_eq!(proposals.len(), alice_central.pending_proposals(&id).await.len());
390 })
391 .await
392 }
393 }
394
395 mod add {
396 use super::*;
397
398 #[apply(all_cred_cipher)]
399 #[wasm_bindgen_test]
400 pub async fn not_renewable_when_valid_commit_adds_same(case: TestContext) {
401 let [mut alice_central, bob_central, charlie_central] = case.sessions().await;
402 Box::pin(async move {
403 let id = conversation_id();
404 alice_central
405 .transaction
406 .new_conversation(&id, case.credential_type, case.cfg.clone())
407 .await
408 .unwrap();
409 alice_central.invite_all(&case, &id, [&bob_central]).await.unwrap();
410
411 let charlie_kp = charlie_central.get_one_key_package(&case).await;
412 assert!(alice_central.pending_proposals(&id).await.is_empty());
413 alice_central
414 .transaction
415 .new_add_proposal(&id, charlie_kp)
416 .await
417 .unwrap();
418 assert_eq!(alice_central.pending_proposals(&id).await.len(), 1);
419
420 let charlie = charlie_central.rand_key_package(&case).await;
421 bob_central
422 .transaction
423 .conversation(&id)
424 .await
425 .unwrap()
426 .add_members(vec![charlie])
427 .await
428 .unwrap();
429 let commit = bob_central.mls_transport().await.latest_commit().await;
430 let proposals = alice_central
431 .transaction
432 .conversation(&id)
433 .await
434 .unwrap()
435 .decrypt_message(commit.to_bytes().unwrap())
436 .await
437 .unwrap()
438 .proposals;
439 assert!(alice_central.pending_proposals(&id).await.is_empty());
441 assert_eq!(proposals.len(), alice_central.pending_proposals(&id).await.len());
442 })
443 .await
444 }
445
446 #[apply(all_cred_cipher)]
447 #[wasm_bindgen_test]
448 pub async fn not_renewable_in_pending_commit_when_valid_commit_adds_same(case: TestContext) {
449 let [mut alice_central, bob_central, charlie_central] = case.sessions().await;
450 Box::pin(async move {
451 let id = conversation_id();
452 alice_central
453 .transaction
454 .new_conversation(&id, case.credential_type, case.cfg.clone())
455 .await
456 .unwrap();
457 alice_central.invite_all(&case, &id, [&bob_central]).await.unwrap();
458
459 let charlie_kp = charlie_central.get_one_key_package(&case).await;
460 assert!(alice_central.pending_proposals(&id).await.is_empty());
461 alice_central
462 .transaction
463 .new_add_proposal(&id, charlie_kp)
464 .await
465 .unwrap();
466 assert_eq!(alice_central.pending_proposals(&id).await.len(), 1);
467
468 alice_central.commit_pending_proposals_unmerged(&id).await;
470 assert!(alice_central.pending_commit(&id).await.is_some());
471
472 let charlie = charlie_central.rand_key_package(&case).await;
473 bob_central
474 .transaction
475 .conversation(&id)
476 .await
477 .unwrap()
478 .add_members(vec![charlie])
479 .await
480 .unwrap();
481 let commit = bob_central.mls_transport().await.latest_commit().await;
482 let proposals = alice_central
483 .transaction
484 .conversation(&id)
485 .await
486 .unwrap()
487 .decrypt_message(commit.to_bytes().unwrap())
488 .await
489 .unwrap()
490 .proposals;
491 assert!(alice_central.pending_proposals(&id).await.is_empty());
493 assert_eq!(proposals.len(), alice_central.pending_proposals(&id).await.len());
494 })
495 .await
496 }
497
498 #[apply(all_cred_cipher)]
499 #[wasm_bindgen_test]
500 pub async fn not_renewable_by_ref(case: TestContext) {
501 let [mut alice_central, bob_central, charlie_central, debbie_central] = case.sessions().await;
502 Box::pin(async move {
503 let id = conversation_id();
504 alice_central
505 .transaction
506 .new_conversation(&id, case.credential_type, case.cfg.clone())
507 .await
508 .unwrap();
509 alice_central
510 .invite_all(&case, &id, [&bob_central, &charlie_central])
511 .await
512 .unwrap();
513
514 let debbie_kp = debbie_central.get_one_key_package(&case).await;
516 let proposal = bob_central
517 .transaction
518 .new_add_proposal(&id, debbie_kp)
519 .await
520 .unwrap()
521 .proposal;
522 alice_central
523 .transaction
524 .conversation(&id)
525 .await
526 .unwrap()
527 .decrypt_message(proposal.to_bytes().unwrap())
528 .await
529 .unwrap();
530 assert_eq!(alice_central.pending_proposals(&id).await.len(), 1);
531
532 charlie_central
534 .transaction
535 .conversation(&id)
536 .await
537 .unwrap()
538 .update_key_material()
539 .await
540 .unwrap();
541 let commit = charlie_central.mls_transport().await.latest_commit().await;
542 let proposals = alice_central
543 .transaction
544 .conversation(&id)
545 .await
546 .unwrap()
547 .decrypt_message(commit.to_bytes().unwrap())
548 .await
549 .unwrap()
550 .proposals;
551 assert!(alice_central.pending_proposals(&id).await.is_empty());
553 assert_eq!(proposals.len(), alice_central.pending_proposals(&id).await.len());
554 })
555 .await
556 }
557
558 #[apply(all_cred_cipher)]
559 #[wasm_bindgen_test]
560 pub async fn renewable_when_valid_commit_doesnt_adds_same(case: TestContext) {
561 let [mut alice_central, bob_central, charlie_central] = case.sessions().await;
562 Box::pin(async move {
563 let id = conversation_id();
564 alice_central
565 .transaction
566 .new_conversation(&id, case.credential_type, case.cfg.clone())
567 .await
568 .unwrap();
569 alice_central.invite_all(&case, &id, [&bob_central]).await.unwrap();
570
571 let charlie_kp = charlie_central.get_one_key_package(&case).await;
573 assert!(alice_central.pending_proposals(&id).await.is_empty());
574 alice_central
575 .transaction
576 .new_add_proposal(&id, charlie_kp)
577 .await
578 .unwrap();
579 assert_eq!(alice_central.pending_proposals(&id).await.len(), 1);
580
581 bob_central
583 .transaction
584 .conversation(&id)
585 .await
586 .unwrap()
587 .update_key_material()
588 .await
589 .unwrap();
590 let commit = bob_central.mls_transport().await.latest_commit().await;
591 let proposals = alice_central
592 .transaction
593 .conversation(&id)
594 .await
595 .unwrap()
596 .decrypt_message(commit.to_bytes().unwrap())
597 .await
598 .unwrap()
599 .proposals;
600 assert_eq!(alice_central.pending_proposals(&id).await.len(), 1);
602 assert_eq!(proposals.len(), alice_central.pending_proposals(&id).await.len());
603
604 alice_central.commit_pending_proposals_unmerged(&id).await;
606 assert!(alice_central.pending_commit(&id).await.is_some());
607 bob_central
608 .transaction
609 .conversation(&id)
610 .await
611 .unwrap()
612 .update_key_material()
613 .await
614 .unwrap();
615 let commit = bob_central.mls_transport().await.latest_commit().await;
616 let proposals = alice_central
617 .transaction
618 .conversation(&id)
619 .await
620 .unwrap()
621 .decrypt_message(commit.to_bytes().unwrap())
622 .await
623 .unwrap()
624 .proposals;
625 assert_eq!(alice_central.pending_proposals(&id).await.len(), 1);
628 assert_eq!(proposals.len(), alice_central.pending_proposals(&id).await.len());
629 })
630 .await
631 }
632
633 #[apply(all_cred_cipher)]
634 #[wasm_bindgen_test]
635 pub async fn renews_pending_commit_when_valid_commit_doesnt_add_same(case: TestContext) {
636 let [mut alice_central, bob_central] = case.sessions().await;
637 Box::pin(async move {
638 let id = conversation_id();
639 alice_central
640 .transaction
641 .new_conversation(&id, case.credential_type, case.cfg.clone())
642 .await
643 .unwrap();
644 alice_central.invite_all(&case, &id, [&bob_central]).await.unwrap();
645
646 alice_central.create_unmerged_commit(&id).await;
648 assert!(alice_central.pending_commit(&id).await.is_some());
649
650 bob_central
652 .transaction
653 .conversation(&id)
654 .await
655 .unwrap()
656 .update_key_material()
657 .await
658 .unwrap();
659 let commit = bob_central.mls_transport().await.latest_commit().await;
660 let proposals = alice_central
661 .transaction
662 .conversation(&id)
663 .await
664 .unwrap()
665 .decrypt_message(commit.to_bytes().unwrap())
666 .await
667 .unwrap()
668 .proposals;
669 assert_eq!(alice_central.pending_proposals(&id).await.len(), 1);
671 assert_eq!(proposals.len(), alice_central.pending_proposals(&id).await.len());
672 })
673 .await
674 }
675 }
676
677 mod remove {
678 use super::*;
679
680 #[apply(all_cred_cipher)]
681 #[wasm_bindgen_test]
682 pub async fn not_renewable_when_valid_commit_removes_same(case: TestContext) {
683 let [mut alice_central, bob_central, charlie_central] = case.sessions().await;
684 Box::pin(async move {
685 let id = conversation_id();
686 alice_central
687 .transaction
688 .new_conversation(&id, case.credential_type, case.cfg.clone())
689 .await
690 .unwrap();
691 alice_central
692 .invite_all(&case, &id, [&bob_central, &charlie_central])
693 .await
694 .unwrap();
695
696 assert!(alice_central.pending_proposals(&id).await.is_empty());
697 alice_central
698 .transaction
699 .new_remove_proposal(&id, charlie_central.get_client_id().await)
700 .await
701 .unwrap();
702 assert_eq!(alice_central.pending_proposals(&id).await.len(), 1);
703
704 bob_central
705 .transaction
706 .conversation(&id)
707 .await
708 .unwrap()
709 .remove_members(&[charlie_central.get_client_id().await])
710 .await
711 .unwrap();
712 let commit = bob_central.mls_transport().await.latest_commit().await;
713 let proposals = alice_central
714 .transaction
715 .conversation(&id)
716 .await
717 .unwrap()
718 .decrypt_message(commit.to_bytes().unwrap())
719 .await
720 .unwrap()
721 .proposals;
722 assert!(alice_central.pending_proposals(&id).await.is_empty());
724 assert_eq!(proposals.len(), alice_central.pending_proposals(&id).await.len());
725 })
726 .await
727 }
728
729 #[apply(all_cred_cipher)]
730 #[wasm_bindgen_test]
731 pub async fn not_renewable_by_ref(case: TestContext) {
732 let [mut alice_central, bob_central, charlie_central] = case.sessions().await;
733 Box::pin(async move {
734 let id = conversation_id();
735 alice_central
736 .transaction
737 .new_conversation(&id, case.credential_type, case.cfg.clone())
738 .await
739 .unwrap();
740 alice_central
741 .invite_all(&case, &id, [&bob_central, &charlie_central])
742 .await
743 .unwrap();
744
745 let proposal = bob_central
746 .transaction
747 .new_remove_proposal(&id, charlie_central.get_client_id().await)
748 .await
749 .unwrap()
750 .proposal;
751 assert!(alice_central.pending_proposals(&id).await.is_empty());
752 alice_central
753 .transaction
754 .conversation(&id)
755 .await
756 .unwrap()
757 .decrypt_message(proposal.to_bytes().unwrap())
758 .await
759 .unwrap();
760 assert_eq!(alice_central.pending_proposals(&id).await.len(), 1);
761
762 charlie_central
763 .transaction
764 .conversation(&id)
765 .await
766 .unwrap()
767 .update_key_material()
768 .await
769 .unwrap();
770 let commit = charlie_central.mls_transport().await.latest_commit().await;
771 let proposals = alice_central
772 .transaction
773 .conversation(&id)
774 .await
775 .unwrap()
776 .decrypt_message(commit.to_bytes().unwrap())
777 .await
778 .unwrap()
779 .proposals;
780 assert!(alice_central.pending_proposals(&id).await.is_empty());
782 assert_eq!(proposals.len(), alice_central.pending_proposals(&id).await.len());
783 })
784 .await
785 }
786
787 #[apply(all_cred_cipher)]
788 #[wasm_bindgen_test]
789 pub async fn renewable_when_valid_commit_doesnt_remove_same(case: TestContext) {
790 let [mut alice_central, bob_central, charlie_central, debbie_central] = case.sessions().await;
791 Box::pin(async move {
792 let id = conversation_id();
793 alice_central
794 .transaction
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 assert!(alice_central.pending_proposals(&id).await.is_empty());
805 alice_central
806 .transaction
807 .new_remove_proposal(&id, charlie_central.get_client_id().await)
808 .await
809 .unwrap();
810 assert_eq!(alice_central.pending_proposals(&id).await.len(), 1);
811
812 bob_central
814 .transaction
815 .conversation(&id)
816 .await
817 .unwrap()
818 .remove_members(&[debbie_central.get_client_id().await])
819 .await
820 .unwrap();
821 let commit = bob_central.mls_transport().await.latest_commit().await;
822 let proposals = alice_central
823 .transaction
824 .conversation(&id)
825 .await
826 .unwrap()
827 .decrypt_message(commit.to_bytes().unwrap())
828 .await
829 .unwrap()
830 .proposals;
831 assert_eq!(alice_central.pending_proposals(&id).await.len(), 1);
833 assert_eq!(proposals.len(), alice_central.pending_proposals(&id).await.len());
834 })
835 .await
836 }
837
838 #[apply(all_cred_cipher)]
839 #[wasm_bindgen_test]
840 pub async fn renews_pending_commit_when_commit_doesnt_remove_same(case: TestContext) {
841 let [mut alice_central, bob_central, charlie_central, debbie_central] = case.sessions().await;
842 Box::pin(async move {
843 let id = conversation_id();
844 alice_central
845 .transaction
846 .new_conversation(&id, case.credential_type, case.cfg.clone())
847 .await
848 .unwrap();
849 alice_central
850 .invite_all(&case, &id, [&bob_central, &charlie_central, &debbie_central])
851 .await
852 .unwrap();
853
854 alice_central
856 .transaction
857 .new_remove_proposal(&id, charlie_central.get_client_id().await)
858 .await
859 .unwrap();
860 alice_central.commit_pending_proposals_unmerged(&id).await;
861 assert!(alice_central.pending_commit(&id).await.is_some());
862
863 bob_central
865 .transaction
866 .conversation(&id)
867 .await
868 .unwrap()
869 .remove_members(&[debbie_central.get_client_id().await])
870 .await
871 .unwrap();
872 let commit = bob_central.mls_transport().await.latest_commit().await;
873 let proposals = alice_central
874 .transaction
875 .conversation(&id)
876 .await
877 .unwrap()
878 .decrypt_message(commit.to_bytes().unwrap())
879 .await
880 .unwrap()
881 .proposals;
882 assert_eq!(alice_central.pending_proposals(&id).await.len(), 1);
884 assert_eq!(proposals.len(), alice_central.pending_proposals(&id).await.len());
885 })
886 .await
887 }
888
889 #[apply(all_cred_cipher)]
890 #[wasm_bindgen_test]
891 pub async fn renews_pending_commit_from_proposal_when_commit_doesnt_remove_same(case: TestContext) {
892 let [mut alice_central, bob_central, charlie_central, debbie_central] = case.sessions().await;
893 Box::pin(async move {
894 let id = conversation_id();
895 alice_central
896 .transaction
897 .new_conversation(&id, case.credential_type, case.cfg.clone())
898 .await
899 .unwrap();
900 alice_central
901 .invite_all(&case, &id, [&bob_central, &charlie_central, &debbie_central])
902 .await
903 .unwrap();
904
905 alice_central
907 .transaction
908 .new_remove_proposal(&id, charlie_central.get_client_id().await)
909 .await
910 .unwrap();
911 alice_central.commit_pending_proposals_unmerged(&id).await;
912
913 bob_central
915 .transaction
916 .conversation(&id)
917 .await
918 .unwrap()
919 .remove_members(&[debbie_central.get_client_id().await])
920 .await
921 .unwrap();
922 let commit = bob_central.mls_transport().await.latest_commit().await;
923 let proposals = alice_central
924 .transaction
925 .conversation(&id)
926 .await
927 .unwrap()
928 .decrypt_message(commit.to_bytes().unwrap())
929 .await
930 .unwrap()
931 .proposals;
932 assert_eq!(alice_central.pending_proposals(&id).await.len(), 1);
934 assert_eq!(proposals.len(), alice_central.pending_proposals(&id).await.len());
935 })
936 .await
937 }
938 }
939}