1 // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2 // Copyright by contributors to this project.
3 // SPDX-License-Identifier: (Apache-2.0 OR MIT)
4 
5 use alloc::vec;
6 use alloc::vec::Vec;
7 
8 use itertools::Itertools;
9 use mls_rs_core::{
10     crypto::{CipherSuite, CipherSuiteProvider, CryptoProvider},
11     identity::SigningIdentity,
12     protocol_version::ProtocolVersion,
13     psk::ExternalPskId,
14     time::MlsTime,
15 };
16 use rand::{seq::IteratorRandom, Rng, SeedableRng};
17 
18 use crate::{
19     client_builder::{ClientBuilder, MlsConfig},
20     crypto::test_utils::TestCryptoProvider,
21     group::{ClientConfig, CommitBuilder, ExportedTree},
22     identity::basic::BasicIdentityProvider,
23     key_package::KeyPackageGeneration,
24     mls_rules::CommitOptions,
25     storage_provider::in_memory::InMemoryKeyPackageStorage,
26     test_utils::{
27         all_process_message, generate_basic_client, get_test_basic_credential, get_test_groups,
28         make_test_ext_psk, TEST_EXT_PSK_ID,
29     },
30     tree_kem::Lifetime,
31     Client, Group, MlsMessage,
32 };
33 
34 const VERSION: ProtocolVersion = ProtocolVersion::MLS_10;
35 
36 const ETERNAL_LIFETIME: Lifetime = Lifetime {
37     not_before: 0,
38     not_after: u64::MAX,
39 };
40 
41 #[derive(serde::Serialize, serde::Deserialize, Debug, Default, Clone)]
42 pub struct TestCase {
43     pub cipher_suite: u16,
44 
45     pub external_psks: Vec<TestExternalPsk>,
46     #[serde(with = "hex::serde")]
47     pub key_package: Vec<u8>,
48     #[serde(with = "hex::serde")]
49     pub signature_priv: Vec<u8>,
50     #[serde(with = "hex::serde")]
51     pub encryption_priv: Vec<u8>,
52     #[serde(with = "hex::serde")]
53     pub init_priv: Vec<u8>,
54 
55     #[serde(with = "hex::serde")]
56     pub welcome: Vec<u8>,
57     pub ratchet_tree: Option<TestRatchetTree>,
58     #[serde(with = "hex::serde")]
59     pub initial_epoch_authenticator: Vec<u8>,
60 
61     pub epochs: Vec<TestEpoch>,
62 }
63 
64 #[derive(serde::Serialize, serde::Deserialize, Debug, Default, Clone)]
65 pub struct TestExternalPsk {
66     #[serde(with = "hex::serde")]
67     pub psk_id: Vec<u8>,
68     #[serde(with = "hex::serde")]
69     pub psk: Vec<u8>,
70 }
71 
72 #[derive(serde::Serialize, serde::Deserialize, Debug, Default, Clone)]
73 pub struct TestEpoch {
74     pub proposals: Vec<TestMlsMessage>,
75     #[serde(with = "hex::serde")]
76     pub commit: Vec<u8>,
77     #[serde(with = "hex::serde")]
78     pub epoch_authenticator: Vec<u8>,
79 }
80 
81 #[derive(serde::Serialize, serde::Deserialize, Debug, Default, Clone)]
82 pub struct TestMlsMessage(#[serde(with = "hex::serde")] pub Vec<u8>);
83 
84 #[derive(serde::Serialize, serde::Deserialize, Debug, Default, Clone)]
85 pub struct TestRatchetTree(#[serde(with = "hex::serde")] pub Vec<u8>);
86 
87 impl TestEpoch {
88     #[cfg_attr(coverage_nightly, coverage(off))]
new( proposals: Vec<MlsMessage>, commit: &MlsMessage, epoch_authenticator: Vec<u8>, ) -> Self89     pub fn new(
90         proposals: Vec<MlsMessage>,
91         commit: &MlsMessage,
92         epoch_authenticator: Vec<u8>,
93     ) -> Self {
94         let proposals = proposals
95             .into_iter()
96             .map(|p| TestMlsMessage(p.to_bytes().unwrap()))
97             .collect();
98 
99         Self {
100             proposals,
101             commit: commit.to_bytes().unwrap(),
102             epoch_authenticator,
103         }
104     }
105 }
106 
107 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
interop_passive_client()108 async fn interop_passive_client() {
109     // Test vectors can be found here:
110     // * https://github.com/mlswg/mls-implementations/blob/main/test-vectors/passive-client-welcome.json
111     // * https://github.com/mlswg/mls-implementations/blob/main/test-vectors/passive-client-handle-commit.json
112     // * https://github.com/mlswg/mls-implementations/blob/main/test-vectors/passive-client-random.json
113 
114     #[cfg(mls_build_async)]
115     let (test_cases_wel, test_cases_com, test_cases_rand) = {
116         let test_cases_wel: Vec<TestCase> = load_test_case_json!(
117             interop_passive_client_welcome,
118             generate_passive_client_welcome_tests().await
119         );
120 
121         let test_cases_com: Vec<TestCase> = load_test_case_json!(
122             interop_passive_client_handle_commit,
123             generate_passive_client_proposal_tests().await
124         );
125 
126         let test_cases_rand: Vec<TestCase> = load_test_case_json!(
127             interop_passive_client_random,
128             generate_passive_client_random_tests().await
129         );
130 
131         (test_cases_wel, test_cases_com, test_cases_rand)
132     };
133 
134     #[cfg(not(mls_build_async))]
135     let (test_cases_wel, test_cases_com, test_cases_rand) = {
136         let test_cases_wel: Vec<TestCase> = load_test_case_json!(
137             interop_passive_client_welcome,
138             generate_passive_client_welcome_tests()
139         );
140 
141         let test_cases_com: Vec<TestCase> = load_test_case_json!(
142             interop_passive_client_handle_commit,
143             generate_passive_client_proposal_tests()
144         );
145 
146         let test_cases_rand: Vec<TestCase> = load_test_case_json!(
147             interop_passive_client_random,
148             generate_passive_client_random_tests()
149         );
150 
151         (test_cases_wel, test_cases_com, test_cases_rand)
152     };
153 
154     for test_case in vec![]
155         .into_iter()
156         .chain(test_cases_com)
157         .chain(test_cases_wel)
158         .chain(test_cases_rand)
159     {
160         let crypto_provider = TestCryptoProvider::new();
161         let Some(cs) = crypto_provider.cipher_suite_provider(test_case.cipher_suite.into()) else {
162             continue;
163         };
164 
165         let message = MlsMessage::from_bytes(&test_case.key_package).unwrap();
166         let key_package = message.into_key_package().unwrap();
167         let id = key_package.leaf_node.signing_identity.clone();
168         let key = test_case.signature_priv.clone().into();
169 
170         let mut client_builder = ClientBuilder::new()
171             .crypto_provider(crypto_provider)
172             .identity_provider(BasicIdentityProvider::new());
173 
174         for psk in test_case.external_psks {
175             client_builder = client_builder.psk(ExternalPskId::new(psk.psk_id), psk.psk.into());
176         }
177 
178         let client = client_builder
179             .signing_identity(id, key, cs.cipher_suite())
180             .build();
181 
182         let key_pckg_gen = KeyPackageGeneration {
183             reference: key_package.to_reference(&cs).await.unwrap(),
184             key_package,
185             init_secret_key: test_case.init_priv.into(),
186             leaf_node_secret_key: test_case.encryption_priv.into(),
187         };
188 
189         let (id, pkg) = key_pckg_gen.to_storage().unwrap();
190         client.config.key_package_repo().insert(id, pkg);
191 
192         let welcome = MlsMessage::from_bytes(&test_case.welcome).unwrap();
193 
194         let tree = test_case
195             .ratchet_tree
196             .map(|t| ExportedTree::from_bytes(&t.0).unwrap());
197 
198         let (mut group, _info) = client.join_group(tree, &welcome).await.unwrap();
199 
200         assert_eq!(
201             group.epoch_authenticator().unwrap().to_vec(),
202             test_case.initial_epoch_authenticator
203         );
204 
205         for epoch in test_case.epochs {
206             for proposal in epoch.proposals.iter() {
207                 let message = MlsMessage::from_bytes(&proposal.0).unwrap();
208 
209                 group
210                     .process_incoming_message_with_time(message, MlsTime::now())
211                     .await
212                     .unwrap();
213             }
214 
215             let message = MlsMessage::from_bytes(&epoch.commit).unwrap();
216 
217             group
218                 .process_incoming_message_with_time(message, MlsTime::now())
219                 .await
220                 .unwrap();
221 
222             assert_eq!(
223                 epoch.epoch_authenticator,
224                 group.epoch_authenticator().unwrap().to_vec()
225             );
226         }
227     }
228 }
229 
230 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
231 #[cfg_attr(coverage_nightly, coverage(off))]
invite_passive_client<P: CipherSuiteProvider>( groups: &mut [Group<impl MlsConfig>], with_psk: bool, cs: &P, ) -> TestCase232 async fn invite_passive_client<P: CipherSuiteProvider>(
233     groups: &mut [Group<impl MlsConfig>],
234     with_psk: bool,
235     cs: &P,
236 ) -> TestCase {
237     let crypto_provider = TestCryptoProvider::new();
238 
239     let (secret_key, public_key) = cs.signature_key_generate().await.unwrap();
240     let credential = get_test_basic_credential(b"Arnold".to_vec());
241     let identity = SigningIdentity::new(credential, public_key);
242     let key_package_repo = InMemoryKeyPackageStorage::new();
243 
244     let client = ClientBuilder::new()
245         .crypto_provider(crypto_provider)
246         .identity_provider(BasicIdentityProvider::new())
247         .key_package_repo(key_package_repo.clone())
248         .key_package_lifetime(ETERNAL_LIFETIME.not_after - ETERNAL_LIFETIME.not_before)
249         .key_package_not_before(ETERNAL_LIFETIME.not_before)
250         .signing_identity(identity.clone(), secret_key.clone(), cs.cipher_suite())
251         .build();
252 
253     let key_pckg = client.generate_key_package_message().await.unwrap();
254 
255     let (_, key_pckg_secrets) = key_package_repo.key_packages()[0].clone();
256 
257     let mut commit_builder = groups[0]
258         .commit_builder()
259         .add_member(key_pckg.clone())
260         .unwrap();
261 
262     if with_psk {
263         commit_builder = commit_builder
264             .add_external_psk(ExternalPskId::new(TEST_EXT_PSK_ID.to_vec()))
265             .unwrap();
266     }
267 
268     let commit = commit_builder.build().await.unwrap();
269 
270     all_process_message(groups, &commit.commit_message, 0, true).await;
271 
272     let external_psk = TestExternalPsk {
273         psk_id: TEST_EXT_PSK_ID.to_vec(),
274         psk: make_test_ext_psk(),
275     };
276 
277     TestCase {
278         cipher_suite: cs.cipher_suite().into(),
279         key_package: key_pckg.to_bytes().unwrap(),
280         encryption_priv: key_pckg_secrets.leaf_node_key.to_vec(),
281         init_priv: key_pckg_secrets.init_key.to_vec(),
282         welcome: commit.welcome_messages[0].to_bytes().unwrap(),
283         initial_epoch_authenticator: groups[0].epoch_authenticator().unwrap().to_vec(),
284         epochs: vec![],
285         signature_priv: secret_key.to_vec(),
286         external_psks: if with_psk { vec![external_psk] } else { vec![] },
287         ratchet_tree: None,
288     }
289 }
290 
291 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
292 #[cfg_attr(coverage_nightly, coverage(off))]
generate_passive_client_proposal_tests() -> Vec<TestCase>293 pub async fn generate_passive_client_proposal_tests() -> Vec<TestCase> {
294     let mut test_cases: Vec<TestCase> = vec![];
295 
296     for cs in CipherSuite::all() {
297         let crypto_provider = TestCryptoProvider::new();
298         let Some(cs) = crypto_provider.cipher_suite_provider(cs) else {
299             continue;
300         };
301 
302         let mut groups =
303             get_test_groups(VERSION, cs.cipher_suite(), 7, None, false, &crypto_provider).await;
304 
305         let mut partial_test_case = invite_passive_client(&mut groups, true, &cs).await;
306 
307         // Create a new epoch s.t. the passive member can process resumption PSK from the current one
308         let commit = groups[0].commit(vec![]).await.unwrap();
309         all_process_message(&mut groups, &commit.commit_message, 0, true).await;
310 
311         partial_test_case.epochs.push(TestEpoch::new(
312             vec![],
313             &commit.commit_message,
314             groups[0].epoch_authenticator().unwrap().to_vec(),
315         ));
316 
317         let psk = ExternalPskId::new(TEST_EXT_PSK_ID.to_vec());
318         let key_pckg = create_key_package(cs.cipher_suite()).await;
319 
320         // Create by value proposals
321         let test_case = commit_by_value(
322             &mut groups[3].clone(),
323             |b| b.add_member(key_pckg.clone()).unwrap(),
324             partial_test_case.clone(),
325         )
326         .await;
327 
328         test_cases.push(test_case);
329 
330         let test_case = commit_by_value(
331             &mut groups[3].clone(),
332             |b| b.remove_member(5).unwrap(),
333             partial_test_case.clone(),
334         )
335         .await;
336 
337         test_cases.push(test_case);
338 
339         let test_case = commit_by_value(
340             &mut groups[1].clone(),
341             |b| b.add_external_psk(psk.clone()).unwrap(),
342             partial_test_case.clone(),
343         )
344         .await;
345 
346         test_cases.push(test_case);
347 
348         let test_case = commit_by_value(
349             &mut groups[5].clone(),
350             |b| b.add_resumption_psk(groups[1].current_epoch() - 1).unwrap(),
351             partial_test_case.clone(),
352         )
353         .await;
354 
355         test_cases.push(test_case);
356 
357         let test_case = commit_by_value(
358             &mut groups[2].clone(),
359             |b| b.set_group_context_ext(Default::default()).unwrap(),
360             partial_test_case.clone(),
361         )
362         .await;
363 
364         test_cases.push(test_case);
365 
366         let test_case = commit_by_value(
367             &mut groups[3].clone(),
368             |b| {
369                 b.add_member(key_pckg)
370                     .unwrap()
371                     .remove_member(5)
372                     .unwrap()
373                     .add_external_psk(psk.clone())
374                     .unwrap()
375                     .add_resumption_psk(groups[4].current_epoch() - 1)
376                     .unwrap()
377                     .set_group_context_ext(Default::default())
378                     .unwrap()
379             },
380             partial_test_case.clone(),
381         )
382         .await;
383 
384         test_cases.push(test_case);
385 
386         // Create by reference proposals
387         let add = groups[0]
388             .propose_add(create_key_package(cs.cipher_suite()).await, vec![])
389             .await
390             .unwrap();
391 
392         let add = (add, 0);
393 
394         let update = (groups[1].propose_update(vec![]).await.unwrap(), 1);
395         let remove = (groups[2].propose_remove(2, vec![]).await.unwrap(), 2);
396 
397         let ext_psk = groups[3]
398             .propose_external_psk(psk.clone(), vec![])
399             .await
400             .unwrap();
401 
402         let ext_psk = (ext_psk, 3);
403 
404         let last_ep = groups[3].current_epoch() - 1;
405 
406         let res_psk = groups[3]
407             .propose_resumption_psk(last_ep, vec![])
408             .await
409             .unwrap();
410 
411         let res_psk = (res_psk, 3);
412 
413         let grp_ext = groups[4]
414             .propose_group_context_extensions(Default::default(), vec![])
415             .await
416             .unwrap();
417 
418         let grp_ext = (grp_ext, 4);
419 
420         let proposals = [add, update, remove, ext_psk, res_psk, grp_ext];
421 
422         for (p, sender) in &proposals {
423             let mut groups = groups.clone();
424 
425             all_process_message(&mut groups, p, *sender, false).await;
426 
427             let commit = groups[5].commit(vec![]).await.unwrap().commit_message;
428 
429             groups[5].apply_pending_commit().await.unwrap();
430             let auth = groups[5].epoch_authenticator().unwrap().to_vec();
431 
432             let mut test_case = partial_test_case.clone();
433             let epoch = TestEpoch::new(vec![p.clone()], &commit, auth);
434             test_case.epochs.push(epoch);
435 
436             test_cases.push(test_case);
437         }
438 
439         let mut group = groups[4].clone();
440 
441         for (p, _) in proposals.iter().filter(|(_, i)| *i != 4) {
442             group.process_incoming_message(p.clone()).await.unwrap();
443         }
444 
445         let commit = group.commit(vec![]).await.unwrap().commit_message;
446         group.apply_pending_commit().await.unwrap();
447         let auth = group.epoch_authenticator().unwrap().to_vec();
448         let mut test_case = partial_test_case.clone();
449         let proposals = proposals.into_iter().map(|(p, _)| p).collect();
450         let epoch = TestEpoch::new(proposals, &commit, auth);
451         test_case.epochs.push(epoch);
452         test_cases.push(test_case);
453     }
454 
455     test_cases
456 }
457 
458 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
459 #[cfg_attr(coverage_nightly, coverage(off))]
commit_by_value<F, C: MlsConfig>( group: &mut Group<C>, proposal_adder: F, partial_test_case: TestCase, ) -> TestCase where F: FnOnce(CommitBuilder<C>) -> CommitBuilder<C>,460 async fn commit_by_value<F, C: MlsConfig>(
461     group: &mut Group<C>,
462     proposal_adder: F,
463     partial_test_case: TestCase,
464 ) -> TestCase
465 where
466     F: FnOnce(CommitBuilder<C>) -> CommitBuilder<C>,
467 {
468     let builder = proposal_adder(group.commit_builder());
469     let commit = builder.build().await.unwrap().commit_message;
470     group.apply_pending_commit().await.unwrap();
471     let auth = group.epoch_authenticator().unwrap().to_vec();
472     let epoch = TestEpoch::new(vec![], &commit, auth);
473     let mut test_case = partial_test_case;
474     test_case.epochs.push(epoch);
475     test_case
476 }
477 
478 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
479 #[cfg_attr(coverage_nightly, coverage(off))]
create_key_package(cs: CipherSuite) -> MlsMessage480 async fn create_key_package(cs: CipherSuite) -> MlsMessage {
481     let client = generate_basic_client(
482         cs,
483         VERSION,
484         0xbeef,
485         None,
486         false,
487         &TestCryptoProvider::new(),
488         Some(ETERNAL_LIFETIME),
489     )
490     .await;
491 
492     client.generate_key_package_message().await.unwrap()
493 }
494 
495 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
496 #[cfg_attr(coverage_nightly, coverage(off))]
generate_passive_client_welcome_tests() -> Vec<TestCase>497 pub async fn generate_passive_client_welcome_tests() -> Vec<TestCase> {
498     let mut test_cases: Vec<TestCase> = vec![];
499 
500     for cs in CipherSuite::all() {
501         let crypto_provider = TestCryptoProvider::new();
502         let Some(cs) = crypto_provider.cipher_suite_provider(cs) else {
503             continue;
504         };
505 
506         for with_tree_in_extension in [true, false] {
507             for (with_psk, with_path) in [false, true].into_iter().cartesian_product([true, false])
508             {
509                 let options = CommitOptions::new()
510                     .with_path_required(with_path)
511                     .with_ratchet_tree_extension(with_tree_in_extension);
512 
513                 let mut groups = get_test_groups(
514                     VERSION,
515                     cs.cipher_suite(),
516                     16,
517                     Some(options),
518                     false,
519                     &crypto_provider,
520                 )
521                 .await;
522 
523                 // Remove a member s.t. the passive member joins in their place
524                 let proposal = groups[0].propose_remove(7, vec![]).await.unwrap();
525                 all_process_message(&mut groups, &proposal, 0, false).await;
526 
527                 let mut test_case = invite_passive_client(&mut groups, with_psk, &cs).await;
528 
529                 if !with_tree_in_extension {
530                     let tree = groups[0].export_tree().to_bytes().unwrap();
531                     test_case.ratchet_tree = Some(TestRatchetTree(tree));
532                 }
533 
534                 test_cases.push(test_case);
535             }
536         }
537     }
538 
539     test_cases
540 }
541 
542 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
543 #[cfg_attr(coverage_nightly, coverage(off))]
generate_passive_client_random_tests() -> Vec<TestCase>544 pub async fn generate_passive_client_random_tests() -> Vec<TestCase> {
545     let mut test_cases: Vec<TestCase> = vec![];
546 
547     for cs in CipherSuite::all() {
548         let crypto = TestCryptoProvider::new();
549         let Some(csp) = crypto.cipher_suite_provider(cs) else {
550             continue;
551         };
552 
553         let creator =
554             generate_basic_client(cs, VERSION, 0, None, false, &crypto, Some(ETERNAL_LIFETIME))
555                 .await;
556 
557         let creator_group = creator.create_group(Default::default()).await.unwrap();
558 
559         let mut groups = vec![creator_group];
560 
561         let mut new_clients = Vec::new();
562 
563         for i in 0..10 {
564             new_clients.push(
565                 generate_basic_client(
566                     cs,
567                     VERSION,
568                     i + 1,
569                     None,
570                     false,
571                     &crypto,
572                     Some(ETERNAL_LIFETIME),
573                 )
574                 .await,
575             )
576         }
577 
578         add_random_members(0, &mut groups, new_clients, None).await;
579 
580         let mut test_case = invite_passive_client(&mut groups, false, &csp).await;
581 
582         let passive_client_index = 11;
583 
584         let seed: <rand::rngs::StdRng as SeedableRng>::Seed = rand::random();
585         let mut rng = rand::rngs::StdRng::from_seed(seed);
586         #[cfg(feature = "std")]
587         println!("generating random commits for seed {}", hex::encode(seed));
588 
589         let mut next_free_idx = 11;
590         for _ in 0..100 {
591             // We keep the passive client and another member to send
592             let num_removed = rng.gen_range(0..groups.len() - 2);
593             let num_added = rng.gen_range(1..30);
594 
595             let mut members = (0..groups.len())
596                 .filter(|i| groups[*i].current_member_index() != passive_client_index)
597                 .choose_multiple(&mut rng, num_removed + 1);
598 
599             let sender = members.pop().unwrap();
600 
601             remove_members(members, sender, &mut groups, Some(&mut test_case)).await;
602 
603             let sender = (0..groups.len())
604                 .filter(|i| groups[*i].current_member_index() != passive_client_index)
605                 .choose(&mut rng)
606                 .unwrap();
607 
608             let mut new_clients = Vec::new();
609 
610             for i in 0..num_added {
611                 new_clients.push(
612                     generate_basic_client(
613                         cs,
614                         VERSION,
615                         next_free_idx + i,
616                         None,
617                         false,
618                         &crypto,
619                         Some(ETERNAL_LIFETIME),
620                     )
621                     .await,
622                 );
623             }
624 
625             add_random_members(sender, &mut groups, new_clients, Some(&mut test_case)).await;
626 
627             next_free_idx += num_added;
628         }
629 
630         test_cases.push(test_case);
631     }
632 
633     test_cases
634 }
635 
636 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
637 #[cfg_attr(coverage_nightly, coverage(off))]
add_random_members<C: MlsConfig>( committer: usize, groups: &mut Vec<Group<C>>, clients: Vec<Client<C>>, test_case: Option<&mut TestCase>, )638 pub async fn add_random_members<C: MlsConfig>(
639     committer: usize,
640     groups: &mut Vec<Group<C>>,
641     clients: Vec<Client<C>>,
642     test_case: Option<&mut TestCase>,
643 ) {
644     let committer_index = groups[committer].current_member_index() as usize;
645 
646     let mut key_packages = Vec::new();
647 
648     for client in &clients {
649         let key_package = client.generate_key_package_message().await.unwrap();
650         key_packages.push(key_package);
651     }
652 
653     let mut add_proposals = Vec::new();
654 
655     let committer_group = &mut groups[committer];
656 
657     for key_package in key_packages {
658         add_proposals.push(
659             committer_group
660                 .propose_add(key_package, vec![])
661                 .await
662                 .unwrap(),
663         );
664     }
665 
666     for p in &add_proposals {
667         all_process_message(groups, p, committer_index, false).await;
668     }
669 
670     let commit_output = groups[committer].commit(vec![]).await.unwrap();
671 
672     all_process_message(groups, &commit_output.commit_message, committer_index, true).await;
673 
674     let auth = groups[committer].epoch_authenticator().unwrap().to_vec();
675     let epoch = TestEpoch::new(add_proposals, &commit_output.commit_message, auth);
676 
677     if let Some(tc) = test_case {
678         tc.epochs.push(epoch)
679     };
680 
681     let tree_data = groups[committer].export_tree().into_owned();
682 
683     for client in &clients {
684         let commit = commit_output.welcome_messages[0].clone();
685 
686         let group = client
687             .join_group(Some(tree_data.clone()), &commit)
688             .await
689             .unwrap()
690             .0;
691 
692         groups.push(group);
693     }
694 }
695 
696 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
697 #[cfg_attr(coverage_nightly, coverage(off))]
remove_members<C: MlsConfig>( removed_members: Vec<usize>, committer: usize, groups: &mut Vec<Group<C>>, test_case: Option<&mut TestCase>, )698 pub async fn remove_members<C: MlsConfig>(
699     removed_members: Vec<usize>,
700     committer: usize,
701     groups: &mut Vec<Group<C>>,
702     test_case: Option<&mut TestCase>,
703 ) {
704     let remove_indexes = removed_members
705         .iter()
706         .map(|removed| groups[*removed].current_member_index())
707         .collect::<Vec<u32>>();
708 
709     let mut commit_builder = groups[committer].commit_builder();
710 
711     for index in remove_indexes {
712         commit_builder = commit_builder.remove_member(index).unwrap();
713     }
714 
715     let commit = commit_builder.build().await.unwrap().commit_message;
716     let committer_index = groups[committer].current_member_index() as usize;
717     all_process_message(groups, &commit, committer_index, true).await;
718 
719     let auth = groups[committer].epoch_authenticator().unwrap().to_vec();
720     let epoch = TestEpoch::new(vec![], &commit, auth);
721 
722     if let Some(tc) = test_case {
723         tc.epochs.push(epoch)
724     };
725 
726     let mut index = 0;
727 
728     groups.retain(|_| {
729         index += 1;
730         !(removed_members.contains(&(index - 1)))
731     });
732 }
733