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