1use openmls::prelude::MlsMessageOut;
9
10use mls_crypto_provider::MlsCryptoProvider;
11
12use super::{Error, Result};
13use crate::{
14 mls::MlsConversation,
15 prelude::{MlsError, MlsGroupInfoBundle, Session},
16};
17
18impl MlsConversation {
19 #[cfg_attr(test, crate::durable)]
21 pub(crate) async fn commit_pending_proposals(
22 &mut self,
23 client: &Session,
24 backend: &MlsCryptoProvider,
25 ) -> Result<Option<MlsCommitBundle>> {
26 if self.group.pending_proposals().count() == 0 {
27 return Ok(None);
28 }
29 let signer = &self.find_most_recent_credential_bundle(client).await?.signature_key;
30
31 let (commit, welcome, gi) = self
32 .group
33 .commit_to_pending_proposals(backend, signer)
34 .await
35 .map_err(MlsError::wrap("group commit to pending proposals"))?;
36 let group_info = MlsGroupInfoBundle::try_new_full_plaintext(gi.unwrap())?;
37
38 self.persist_group_when_changed(&backend.keystore(), false).await?;
39
40 Ok(Some(MlsCommitBundle {
41 welcome,
42 commit,
43 group_info,
44 }))
45 }
46}
47
48#[derive(Debug, Clone)]
50pub struct MlsCommitBundle {
51 pub welcome: Option<MlsMessageOut>,
53 pub commit: MlsMessageOut,
55 pub group_info: MlsGroupInfoBundle,
57}
58
59impl MlsCommitBundle {
60 #[allow(clippy::type_complexity)]
65 pub fn to_bytes_triple(self) -> Result<(Option<Vec<u8>>, Vec<u8>, MlsGroupInfoBundle)> {
66 use openmls::prelude::TlsSerializeTrait as _;
67 let welcome = self
68 .welcome
69 .as_ref()
70 .map(|w| {
71 w.tls_serialize_detached()
72 .map_err(Error::tls_serialize("serialize welcome"))
73 })
74 .transpose()?;
75 let commit = self
76 .commit
77 .tls_serialize_detached()
78 .map_err(Error::tls_serialize("serialize commit"))?;
79 Ok((welcome, commit, self.group_info))
80 }
81}
82
83#[cfg(test)]
84mod tests {
85 use itertools::Itertools;
86 use openmls::prelude::SignaturePublicKey;
87 use wasm_bindgen_test::*;
88
89 use crate::test_utils::*;
90 use crate::transaction_context::Error as TransactionError;
91
92 use super::{Error, *};
93
94 wasm_bindgen_test_configure!(run_in_browser);
95
96 mod transport {
97 use super::*;
98 use std::sync::Arc;
99
100 #[apply(all_cred_cipher)]
101 #[wasm_bindgen_test]
102 async fn retry_should_work(case: TestContext) {
103 use crate::mls::conversation::Conversation as _;
104
105 let [alice, bob, charlie] = case.sessions().await;
106 Box::pin(async move {
107 let conversation = case.create_conversation([&alice, &bob]).await;
109
110 let commit = conversation.update_guarded_with(&bob).await;
112 let bob_epoch = commit.conversation().guard_of(&bob).await.epoch().await;
113 assert_eq!(2, bob_epoch);
114 let alice_epoch = commit.conversation().guard_of(&alice).await.epoch().await;
115 assert_eq!(1, alice_epoch);
116 let intermediate_commit = commit.message();
117 let retry_provider = Arc::new(
119 CoreCryptoTransportRetrySuccessProvider::default().with_intermediate_commits(
120 alice.clone(),
121 &[intermediate_commit],
122 commit.conversation().id(),
123 ),
124 );
125
126 alice.replace_transport(retry_provider.clone()).await;
127
128 let id = commit.finish().advance_epoch().await.invite([&charlie]).await.id;
132
133 assert_eq!(retry_provider.retry_count().await, 2);
135 assert_eq!(retry_provider.success_count().await, 2);
137
138 assert!(alice.try_talk_to(&id, &bob).await.is_ok());
140 })
141 .await;
142 }
143 }
144
145 mod add_members {
146 use super::*;
147 use std::sync::Arc;
148
149 #[apply(all_cred_cipher)]
150 #[wasm_bindgen_test]
151 async fn can_add_members_to_conversation(case: TestContext) {
152 let [alice_central, bob_central] = case.sessions().await;
153 Box::pin(async move {
154 let id = conversation_id();
155
156 alice_central
157 .transaction
158 .new_conversation(&id, case.credential_type, case.cfg.clone())
159 .await
160 .unwrap();
161 let bob = bob_central.rand_key_package(&case).await;
162 alice_central
164 .transaction
165 .set_transport_callbacks(Some(Arc::<CoreCryptoTransportAbortProvider>::default()))
166 .await
167 .unwrap();
168 alice_central
169 .transaction
170 .conversation(&id)
171 .await
172 .unwrap()
173 .add_members(vec![bob.clone()])
174 .await
175 .unwrap_err();
176
177 assert_eq!(alice_central.get_conversation_unchecked(&id).await.members().len(), 1);
179
180 let success_provider = Arc::<CoreCryptoTransportSuccessProvider>::default();
181 alice_central
182 .transaction
183 .set_transport_callbacks(Some(success_provider.clone()))
184 .await
185 .unwrap();
186 alice_central
187 .transaction
188 .conversation(&id)
189 .await
190 .unwrap()
191 .add_members(vec![bob])
192 .await
193 .unwrap();
194
195 assert_eq!(alice_central.get_conversation_unchecked(&id).await.id, id);
196 assert_eq!(
197 alice_central
198 .get_conversation_unchecked(&id)
199 .await
200 .group
201 .group_id()
202 .as_slice(),
203 id
204 );
205 assert_eq!(alice_central.get_conversation_unchecked(&id).await.members().len(), 2);
206 let commit = success_provider.latest_commit_bundle().await;
207 bob_central
208 .transaction
209 .process_welcome_message(commit.welcome.unwrap().into(), case.custom_cfg())
210 .await
211 .unwrap();
212 assert_eq!(
213 alice_central.get_conversation_unchecked(&id).await.id(),
214 bob_central.get_conversation_unchecked(&id).await.id()
215 );
216 assert_eq!(bob_central.get_conversation_unchecked(&id).await.members().len(), 2);
217 assert!(alice_central.try_talk_to(&id, &bob_central).await.is_ok());
218 })
219 .await
220 }
221
222 #[apply(all_cred_cipher)]
223 #[wasm_bindgen_test]
224 async fn should_return_valid_welcome(case: TestContext) {
225 let [alice_central, bob_central] = case.sessions().await;
226 Box::pin(async move {
227 let id = conversation_id();
228 alice_central
229 .transaction
230 .new_conversation(&id, case.credential_type, case.cfg.clone())
231 .await
232 .unwrap();
233
234 let bob = bob_central.rand_key_package(&case).await;
235 alice_central
236 .transaction
237 .conversation(&id)
238 .await
239 .unwrap()
240 .add_members(vec![bob])
241 .await
242 .unwrap();
243
244 let welcome = alice_central
245 .mls_transport()
246 .await
247 .latest_commit_bundle()
248 .await
249 .welcome
250 .unwrap();
251
252 bob_central
253 .transaction
254 .process_welcome_message(welcome.into(), case.custom_cfg())
255 .await
256 .unwrap();
257 assert!(alice_central.try_talk_to(&id, &bob_central).await.is_ok());
258 })
259 .await
260 }
261
262 #[apply(all_cred_cipher)]
263 #[wasm_bindgen_test]
264 async fn should_return_valid_group_info(case: TestContext) {
265 let [alice_central, bob_central, mut guest_central] = case.sessions().await;
266 Box::pin(async move {
267 let id = conversation_id();
268 alice_central
269 .transaction
270 .new_conversation(&id, case.credential_type, case.cfg.clone())
271 .await
272 .unwrap();
273
274 let bob = bob_central.rand_key_package(&case).await;
275 alice_central
276 .transaction
277 .conversation(&id)
278 .await
279 .unwrap()
280 .add_members(vec![bob])
281 .await
282 .unwrap();
283 let commit_bundle = alice_central.mls_transport().await.latest_commit_bundle().await;
284 let group_info = commit_bundle.group_info.get_group_info();
285
286 assert!(
287 guest_central
288 .try_join_from_group_info(&case, &id, group_info, vec![&alice_central])
289 .await
290 .is_ok()
291 );
292 })
293 .await
294 }
295 }
296
297 mod remove_members {
298 use super::*;
299
300 #[apply(all_cred_cipher)]
301 #[wasm_bindgen_test]
302 async fn alice_can_remove_bob_from_conversation(case: TestContext) {
303 let [alice, bob] = case.sessions().await;
304 Box::pin(async move {
305 let conversation = case.create_conversation([&alice, &bob]).await;
306 let id = conversation.remove(&bob).await.id;
307
308 let MlsCommitBundle { welcome, .. } = alice.mls_transport().await.latest_commit_bundle().await;
309 assert!(welcome.is_none());
310
311 assert_eq!(alice.get_conversation_unchecked(&id).await.members().len(), 1);
312
313 assert!(matches!(
315 bob.transaction.conversation(&id).await.unwrap_err(),
316 TransactionError::Leaf(crate::LeafError::ConversationNotFound(ref i))
317 if i == &id
318 ));
319 assert!(alice.try_talk_to(&id, &bob).await.is_err());
320 })
321 .await;
322 }
323
324 #[apply(all_cred_cipher)]
325 #[wasm_bindgen_test]
326 async fn should_return_valid_welcome(case: TestContext) {
327 let [alice_central, bob_central, mut guest_central] = case.sessions().await;
328 Box::pin(async move {
329 let id = conversation_id();
330
331 alice_central
332 .transaction
333 .new_conversation(&id, case.credential_type, case.cfg.clone())
334 .await
335 .unwrap();
336 alice_central.invite_all(&case, &id, [&bob_central]).await.unwrap();
337
338 let proposal = alice_central
339 .transaction
340 .new_add_proposal(&id, guest_central.get_one_key_package(&case).await)
341 .await
342 .unwrap();
343 bob_central
344 .transaction
345 .conversation(&id)
346 .await
347 .unwrap()
348 .decrypt_message(proposal.proposal.to_bytes().unwrap())
349 .await
350 .unwrap();
351
352 alice_central
353 .transaction
354 .conversation(&id)
355 .await
356 .unwrap()
357 .remove_members(&[bob_central.get_client_id().await])
358 .await
359 .unwrap();
360
361 let welcome = alice_central.mls_transport().await.latest_welcome_message().await;
362
363 assert!(
364 guest_central
365 .try_join_from_welcome(&id, welcome.into(), case.custom_cfg(), vec![&alice_central])
366 .await
367 .is_ok()
368 );
369 assert!(guest_central.try_talk_to(&id, &bob_central).await.is_err());
371 })
372 .await;
373 }
374
375 #[apply(all_cred_cipher)]
376 #[wasm_bindgen_test]
377 async fn should_return_valid_group_info(case: TestContext) {
378 let [alice_central, bob_central, mut guest_central] = case.sessions().await;
379 Box::pin(async move {
380 let id = conversation_id();
381
382 alice_central
383 .transaction
384 .new_conversation(&id, case.credential_type, case.cfg.clone())
385 .await
386 .unwrap();
387 alice_central.invite_all(&case, &id, [&bob_central]).await.unwrap();
388
389 alice_central
390 .transaction
391 .conversation(&id)
392 .await
393 .unwrap()
394 .remove_members(&[bob_central.get_client_id().await])
395 .await
396 .unwrap();
397
398 let commit_bundle = alice_central.mls_transport().await.latest_commit_bundle().await;
399
400 let group_info = commit_bundle.group_info.get_group_info();
401
402 assert!(
403 guest_central
404 .try_join_from_group_info(&case, &id, group_info, vec![&alice_central])
405 .await
406 .is_ok()
407 );
408 assert!(guest_central.try_talk_to(&id, &bob_central).await.is_err());
410 })
411 .await;
412 }
413 }
414
415 mod update_keying_material {
416 use super::*;
417
418 #[apply(all_cred_cipher)]
419 #[wasm_bindgen_test]
420 async fn should_succeed(case: TestContext) {
421 let [alice_central, bob_central] = case.sessions().await;
422 Box::pin(async move {
423 let id = conversation_id();
424 alice_central
425 .transaction
426 .new_conversation(&id, case.credential_type, case.cfg.clone())
427 .await
428 .unwrap();
429 alice_central.invite_all(&case, &id, [&bob_central]).await.unwrap();
430
431 let init_count = alice_central.transaction.count_entities().await;
432
433 let bob_keys = bob_central
434 .get_conversation_unchecked(&id)
435 .await
436 .encryption_keys()
437 .collect::<Vec<Vec<u8>>>();
438 let alice_keys = alice_central
439 .get_conversation_unchecked(&id)
440 .await
441 .encryption_keys()
442 .collect::<Vec<Vec<u8>>>();
443 assert!(alice_keys.iter().all(|a_key| bob_keys.contains(a_key)));
444
445 let alice_key = alice_central
446 .encryption_key_of(&id, alice_central.get_client_id().await)
447 .await;
448
449 alice_central
451 .transaction
452 .conversation(&id)
453 .await
454 .unwrap()
455 .update_key_material()
456 .await
457 .unwrap();
458 let MlsCommitBundle { commit, welcome, .. } =
459 alice_central.mls_transport().await.latest_commit_bundle().await;
460 assert!(welcome.is_none());
461
462 assert!(
463 !alice_central
464 .get_conversation_unchecked(&id)
465 .await
466 .encryption_keys()
467 .contains(&alice_key)
468 );
469
470 let alice_new_keys = alice_central
471 .get_conversation_unchecked(&id)
472 .await
473 .encryption_keys()
474 .collect::<Vec<_>>();
475 assert!(!alice_new_keys.contains(&alice_key));
476
477 bob_central
479 .transaction
480 .conversation(&id)
481 .await
482 .unwrap()
483 .decrypt_message(&commit.to_bytes().unwrap())
484 .await
485 .unwrap();
486
487 let bob_new_keys = bob_central
488 .get_conversation_unchecked(&id)
489 .await
490 .encryption_keys()
491 .collect::<Vec<_>>();
492 assert!(alice_new_keys.iter().all(|a_key| bob_new_keys.contains(a_key)));
493
494 assert!(alice_central.try_talk_to(&id, &bob_central).await.is_ok());
496
497 let final_count = alice_central.transaction.count_entities().await;
500 assert_eq!(init_count, final_count);
501 })
502 .await;
503 }
504
505 #[apply(all_cred_cipher)]
506 #[wasm_bindgen_test]
507 async fn should_create_welcome_for_pending_add_proposals(case: TestContext) {
508 let [alice_central, bob_central, charlie_central] = case.sessions().await;
509 Box::pin(async move {
510 let id = conversation_id();
511 alice_central
512 .transaction
513 .new_conversation(&id, case.credential_type, case.cfg.clone())
514 .await
515 .unwrap();
516 alice_central.invite_all(&case, &id, [&bob_central]).await.unwrap();
517
518 let bob_keys = bob_central
519 .get_conversation_unchecked(&id)
520 .await
521 .signature_keys()
522 .collect::<Vec<SignaturePublicKey>>();
523 let alice_keys = alice_central
524 .get_conversation_unchecked(&id)
525 .await
526 .signature_keys()
527 .collect::<Vec<SignaturePublicKey>>();
528
529 assert!(alice_keys.iter().all(|a_key| bob_keys.contains(a_key)));
531
532 let alice_key = alice_central
533 .encryption_key_of(&id, alice_central.get_client_id().await)
534 .await;
535
536 let charlie_kp = charlie_central.get_one_key_package(&case).await;
538 let add_charlie_proposal = alice_central
539 .transaction
540 .new_add_proposal(&id, charlie_kp)
541 .await
542 .unwrap();
543
544 bob_central
546 .transaction
547 .conversation(&id)
548 .await
549 .unwrap()
550 .decrypt_message(add_charlie_proposal.proposal.to_bytes().unwrap())
551 .await
552 .unwrap();
553
554 assert!(
555 alice_central
556 .get_conversation_unchecked(&id)
557 .await
558 .encryption_keys()
559 .contains(&alice_key)
560 );
561
562 alice_central
564 .transaction
565 .conversation(&id)
566 .await
567 .unwrap()
568 .update_key_material()
569 .await
570 .unwrap();
571 let MlsCommitBundle { commit, welcome, .. } =
572 alice_central.mls_transport().await.latest_commit_bundle().await;
573 assert!(welcome.is_some());
574 assert!(
575 !alice_central
576 .get_conversation_unchecked(&id)
577 .await
578 .encryption_keys()
579 .contains(&alice_key)
580 );
581
582 charlie_central
584 .transaction
585 .process_welcome_message(welcome.unwrap().into(), case.custom_cfg())
586 .await
587 .unwrap();
588
589 assert_eq!(alice_central.get_conversation_unchecked(&id).await.members().len(), 3);
590 assert_eq!(charlie_central.get_conversation_unchecked(&id).await.members().len(), 3);
591 assert_eq!(bob_central.get_conversation_unchecked(&id).await.members().len(), 2);
593
594 let alice_new_keys = alice_central
595 .get_conversation_unchecked(&id)
596 .await
597 .encryption_keys()
598 .collect::<Vec<Vec<u8>>>();
599 assert!(!alice_new_keys.contains(&alice_key));
600
601 bob_central
603 .transaction
604 .conversation(&id)
605 .await
606 .unwrap()
607 .decrypt_message(&commit.to_bytes().unwrap())
608 .await
609 .unwrap();
610 assert_eq!(bob_central.get_conversation_unchecked(&id).await.members().len(), 3);
611
612 let bob_new_keys = bob_central
613 .get_conversation_unchecked(&id)
614 .await
615 .encryption_keys()
616 .collect::<Vec<Vec<u8>>>();
617 assert!(alice_new_keys.iter().all(|a_key| bob_new_keys.contains(a_key)));
618
619 assert!(alice_central.try_talk_to(&id, &bob_central).await.is_ok());
621 assert!(bob_central.try_talk_to(&id, &charlie_central).await.is_ok());
622 assert!(charlie_central.try_talk_to(&id, &alice_central).await.is_ok());
623 })
624 .await;
625 }
626
627 #[apply(all_cred_cipher)]
628 #[wasm_bindgen_test]
629 async fn should_return_valid_welcome(case: TestContext) {
630 let [alice_central, bob_central, mut guest_central] = case.sessions().await;
631 Box::pin(async move {
632 let id = conversation_id();
633 alice_central
634 .transaction
635 .new_conversation(&id, case.credential_type, case.cfg.clone())
636 .await
637 .unwrap();
638 alice_central.invite_all(&case, &id, [&bob_central]).await.unwrap();
639
640 let proposal = alice_central
641 .transaction
642 .new_add_proposal(&id, guest_central.get_one_key_package(&case).await)
643 .await
644 .unwrap()
645 .proposal;
646 bob_central
647 .transaction
648 .conversation(&id)
649 .await
650 .unwrap()
651 .decrypt_message(proposal.to_bytes().unwrap())
652 .await
653 .unwrap();
654
655 alice_central
656 .transaction
657 .conversation(&id)
658 .await
659 .unwrap()
660 .update_key_material()
661 .await
662 .unwrap();
663 let MlsCommitBundle { commit, welcome, .. } =
664 alice_central.mls_transport().await.latest_commit_bundle().await;
665
666 bob_central
667 .transaction
668 .conversation(&id)
669 .await
670 .unwrap()
671 .decrypt_message(commit.to_bytes().unwrap())
672 .await
673 .unwrap();
674
675 assert!(
676 guest_central
677 .try_join_from_welcome(
678 &id,
679 welcome.unwrap().into(),
680 case.custom_cfg(),
681 vec![&alice_central, &bob_central]
682 )
683 .await
684 .is_ok()
685 );
686 })
687 .await;
688 }
689
690 #[apply(all_cred_cipher)]
691 #[wasm_bindgen_test]
692 async fn should_return_valid_group_info(case: TestContext) {
693 let [alice_central, bob_central, mut guest_central] = case.sessions().await;
694 Box::pin(async move {
695 let id = conversation_id();
696 alice_central
697 .transaction
698 .new_conversation(&id, case.credential_type, case.cfg.clone())
699 .await
700 .unwrap();
701 alice_central.invite_all(&case, &id, [&bob_central]).await.unwrap();
702
703 alice_central
704 .transaction
705 .conversation(&id)
706 .await
707 .unwrap()
708 .update_key_material()
709 .await
710 .unwrap();
711 let group_info = alice_central.mls_transport().await.latest_group_info().await;
712 let group_info = group_info.get_group_info();
713
714 assert!(
715 guest_central
716 .try_join_from_group_info(&case, &id, group_info, vec![&alice_central])
717 .await
718 .is_ok()
719 );
720 })
721 .await;
722 }
723 }
724
725 mod commit_pending_proposals {
726 use super::*;
727
728 #[apply(all_cred_cipher)]
729 #[wasm_bindgen_test]
730 async fn should_create_a_commit_out_of_self_pending_proposals(case: TestContext) {
731 let [mut alice_central, bob_central] = case.sessions().await;
732 Box::pin(async move {
733 let id = conversation_id();
734 alice_central
735 .transaction
736 .new_conversation(&id, case.credential_type, case.cfg.clone())
737 .await
738 .unwrap();
739 alice_central
740 .transaction
741 .new_add_proposal(&id, bob_central.get_one_key_package(&case).await)
742 .await
743 .unwrap();
744 assert!(!alice_central.pending_proposals(&id).await.is_empty());
745 assert_eq!(alice_central.get_conversation_unchecked(&id).await.members().len(), 1);
746 alice_central
747 .transaction
748 .conversation(&id)
749 .await
750 .unwrap()
751 .commit_pending_proposals()
752 .await
753 .unwrap();
754 assert_eq!(alice_central.get_conversation_unchecked(&id).await.members().len(), 2);
755
756 let welcome = alice_central.mls_transport().await.latest_commit_bundle().await.welcome;
757 bob_central
758 .transaction
759 .process_welcome_message(welcome.unwrap().into(), case.custom_cfg())
760 .await
761 .unwrap();
762 assert!(alice_central.try_talk_to(&id, &bob_central).await.is_ok());
763 })
764 .await;
765 }
766
767 #[apply(all_cred_cipher)]
768 #[wasm_bindgen_test]
769 async fn should_create_a_commit_out_of_pending_proposals_by_ref(case: TestContext) {
770 let [alice_central, mut bob_central, charlie_central] = case.sessions().await;
771 Box::pin(async move {
772 let id = conversation_id();
773 alice_central
774 .transaction
775 .new_conversation(&id, case.credential_type, case.cfg.clone())
776 .await
777 .unwrap();
778 alice_central.invite_all(&case, &id, [&bob_central]).await.unwrap();
779 let proposal = bob_central
780 .transaction
781 .new_add_proposal(&id, charlie_central.get_one_key_package(&case).await)
782 .await
783 .unwrap();
784 assert!(!bob_central.pending_proposals(&id).await.is_empty());
785 assert_eq!(bob_central.get_conversation_unchecked(&id).await.members().len(), 2);
786 alice_central
787 .transaction
788 .conversation(&id)
789 .await
790 .unwrap()
791 .decrypt_message(proposal.proposal.to_bytes().unwrap())
792 .await
793 .unwrap();
794
795 alice_central
796 .transaction
797 .conversation(&id)
798 .await
799 .unwrap()
800 .commit_pending_proposals()
801 .await
802 .unwrap();
803 let commit = alice_central.mls_transport().await.latest_commit_bundle().await.commit;
804 assert_eq!(alice_central.get_conversation_unchecked(&id).await.members().len(), 3);
805
806 bob_central
807 .transaction
808 .conversation(&id)
809 .await
810 .unwrap()
811 .decrypt_message(commit.to_bytes().unwrap())
812 .await
813 .unwrap();
814 assert_eq!(bob_central.get_conversation_unchecked(&id).await.members().len(), 3);
815 assert!(alice_central.try_talk_to(&id, &bob_central).await.is_ok());
816 })
817 .await;
818 }
819
820 #[apply(all_cred_cipher)]
821 #[wasm_bindgen_test]
822 async fn should_return_valid_welcome(case: TestContext) {
823 let [alice_central, bob_central] = case.sessions().await;
824 Box::pin(async move {
825 let id = conversation_id();
826 alice_central
827 .transaction
828 .new_conversation(&id, case.credential_type, case.cfg.clone())
829 .await
830 .unwrap();
831 alice_central
832 .transaction
833 .new_add_proposal(&id, bob_central.get_one_key_package(&case).await)
834 .await
835 .unwrap();
836 alice_central
837 .transaction
838 .conversation(&id)
839 .await
840 .unwrap()
841 .commit_pending_proposals()
842 .await
843 .unwrap();
844
845 let welcome = alice_central.mls_transport().await.latest_commit_bundle().await.welcome;
846
847 bob_central
848 .transaction
849 .process_welcome_message(welcome.unwrap().into(), case.custom_cfg())
850 .await
851 .unwrap();
852 assert!(alice_central.try_talk_to(&id, &bob_central).await.is_ok());
853 })
854 .await;
855 }
856
857 #[apply(all_cred_cipher)]
858 #[wasm_bindgen_test]
859 async fn should_return_valid_group_info(case: TestContext) {
860 let [alice_central, bob_central, mut guest_central] = case.sessions().await;
861 Box::pin(async move {
862 let id = conversation_id();
863 alice_central
864 .transaction
865 .new_conversation(&id, case.credential_type, case.cfg.clone())
866 .await
867 .unwrap();
868 alice_central
869 .transaction
870 .new_add_proposal(&id, bob_central.get_one_key_package(&case).await)
871 .await
872 .unwrap();
873 alice_central
874 .transaction
875 .conversation(&id)
876 .await
877 .unwrap()
878 .commit_pending_proposals()
879 .await
880 .unwrap();
881 let commit_bundle = alice_central.mls_transport().await.latest_commit_bundle().await;
882 let group_info = commit_bundle.group_info.get_group_info();
883
884 assert!(
885 guest_central
886 .try_join_from_group_info(&case, &id, group_info, vec![&alice_central])
887 .await
888 .is_ok()
889 );
890 })
891 .await;
892 }
893 }
894
895 mod delivery_semantics {
896 use super::*;
897
898 #[apply(all_cred_cipher)]
899 #[wasm_bindgen_test]
900 async fn should_prevent_out_of_order_commits(case: TestContext) {
901 let [alice_central, bob_central] = case.sessions().await;
902 Box::pin(async move {
903 let id = conversation_id();
904 alice_central
905 .transaction
906 .new_conversation(&id, case.credential_type, case.cfg.clone())
907 .await
908 .unwrap();
909 alice_central.invite_all(&case, &id, [&bob_central]).await.unwrap();
910
911 alice_central
912 .transaction
913 .conversation(&id)
914 .await
915 .unwrap()
916 .update_key_material()
917 .await
918 .unwrap();
919 let commit1 = alice_central.mls_transport().await.latest_commit().await;
920 let commit1 = commit1.to_bytes().unwrap();
921 alice_central
922 .transaction
923 .conversation(&id)
924 .await
925 .unwrap()
926 .update_key_material()
927 .await
928 .unwrap();
929 let commit2 = alice_central.mls_transport().await.latest_commit().await;
930 let commit2 = commit2.to_bytes().unwrap();
931
932 let out_of_order = bob_central
934 .transaction
935 .conversation(&id)
936 .await
937 .unwrap()
938 .decrypt_message(&commit2)
939 .await;
940 assert!(matches!(out_of_order.unwrap_err(), Error::BufferedFutureMessage { .. }));
941
942 bob_central
945 .transaction
946 .conversation(&id)
947 .await
948 .unwrap()
949 .decrypt_message(&commit1)
950 .await
951 .unwrap();
952
953 let past_commit = bob_central
955 .transaction
956 .conversation(&id)
957 .await
958 .unwrap()
959 .decrypt_message(&commit1)
960 .await;
961 assert!(matches!(past_commit.unwrap_err(), Error::StaleCommit));
962 })
963 .await;
964 }
965
966 #[apply(all_cred_cipher)]
967 #[wasm_bindgen_test]
968 async fn should_prevent_replayed_encrypted_handshake_messages(case: TestContext) {
969 if !case.is_pure_ciphertext() {
970 return;
971 }
972
973 let [alice_central, bob_central] = case.sessions().await;
974 Box::pin(async move {
975 let id = conversation_id();
976 alice_central
977 .transaction
978 .new_conversation(&id, case.credential_type, case.cfg.clone())
979 .await
980 .unwrap();
981 alice_central.invite_all(&case, &id, [&bob_central]).await.unwrap();
982
983 let proposal1 = alice_central
984 .transaction
985 .new_update_proposal(&id)
986 .await
987 .unwrap()
988 .proposal;
989 let proposal2 = proposal1.clone();
990 alice_central
991 .get_conversation_unchecked(&id)
992 .await
993 .group
994 .clear_pending_proposals();
995
996 alice_central
997 .transaction
998 .conversation(&id)
999 .await
1000 .unwrap()
1001 .update_key_material()
1002 .await
1003 .unwrap();
1004 let commit1 = alice_central.mls_transport().await.latest_commit().await;
1005 let commit2 = commit1.clone();
1006
1007 bob_central
1009 .transaction
1010 .conversation(&id)
1011 .await
1012 .unwrap()
1013 .decrypt_message(proposal1.to_bytes().unwrap())
1014 .await
1015 .unwrap();
1016 assert!(matches!(
1017 bob_central
1018 .transaction
1019 .conversation(&id)
1020 .await
1021 .unwrap()
1022 .decrypt_message(proposal2.to_bytes().unwrap())
1023 .await
1024 .unwrap_err(),
1025 Error::DuplicateMessage
1026 ));
1027 bob_central
1028 .get_conversation_unchecked(&id)
1029 .await
1030 .group
1031 .clear_pending_proposals();
1032
1033 bob_central
1035 .transaction
1036 .conversation(&id)
1037 .await
1038 .unwrap()
1039 .decrypt_message(commit1.to_bytes().unwrap())
1040 .await
1041 .unwrap();
1042 assert!(matches!(
1043 bob_central
1044 .transaction
1045 .conversation(&id)
1046 .await
1047 .unwrap()
1048 .decrypt_message(commit2.to_bytes().unwrap())
1049 .await
1050 .unwrap_err(),
1051 Error::StaleCommit
1052 ));
1053 })
1054 .await;
1055 }
1056 }
1057}