| // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. |
| // Copyright by contributors to this project. |
| // SPDX-License-Identifier: (Apache-2.0 OR MIT) |
| |
| use assert_matches::assert_matches; |
| use cfg_if::cfg_if; |
| use mls_rs::client_builder::MlsConfig; |
| use mls_rs::error::MlsError; |
| use mls_rs::group::proposal::Proposal; |
| use mls_rs::group::ReceivedMessage; |
| use mls_rs::identity::SigningIdentity; |
| use mls_rs::mls_rules::CommitOptions; |
| use mls_rs::ExtensionList; |
| use mls_rs::MlsMessage; |
| use mls_rs::ProtocolVersion; |
| use mls_rs::{CipherSuite, Group}; |
| use mls_rs::{Client, CryptoProvider}; |
| use mls_rs_core::crypto::CipherSuiteProvider; |
| use rand::prelude::SliceRandom; |
| use rand::RngCore; |
| |
| use mls_rs::test_utils::{all_process_message, get_test_basic_credential}; |
| |
| #[cfg(mls_build_async)] |
| use futures::Future; |
| |
| cfg_if! { |
| if #[cfg(target_arch = "wasm32")] { |
| use mls_rs_crypto_webcrypto::WebCryptoProvider as TestCryptoProvider; |
| } else { |
| use mls_rs_crypto_openssl::OpensslCryptoProvider as TestCryptoProvider; |
| } |
| } |
| |
| #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)] |
| async fn generate_client( |
| cipher_suite: CipherSuite, |
| protocol_version: ProtocolVersion, |
| id: usize, |
| encrypt_controls: bool, |
| ) -> Client<impl MlsConfig> { |
| mls_rs::test_utils::generate_basic_client( |
| cipher_suite, |
| protocol_version, |
| id, |
| None, |
| encrypt_controls, |
| &TestCryptoProvider::default(), |
| None, |
| ) |
| .await |
| } |
| |
| #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)] |
| pub async fn get_test_groups( |
| version: ProtocolVersion, |
| cipher_suite: CipherSuite, |
| num_participants: usize, |
| encrypt_controls: bool, |
| ) -> Vec<Group<impl MlsConfig>> { |
| mls_rs::test_utils::get_test_groups( |
| version, |
| cipher_suite, |
| num_participants, |
| None, |
| encrypt_controls, |
| &TestCryptoProvider::default(), |
| ) |
| .await |
| } |
| |
| use rand::seq::IteratorRandom; |
| |
| #[cfg(target_arch = "wasm32")] |
| wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser); |
| |
| #[cfg(target_arch = "wasm32")] |
| use wasm_bindgen_test::wasm_bindgen_test as futures_test; |
| |
| #[cfg(all(mls_build_async, not(target_arch = "wasm32")))] |
| use futures_test::test as futures_test; |
| |
| #[cfg(feature = "private_message")] |
| #[cfg(mls_build_async)] |
| async fn test_on_all_params<F, Fut>(test: F) |
| where |
| F: Fn(ProtocolVersion, CipherSuite, usize, bool) -> Fut, |
| Fut: Future<Output = ()>, |
| { |
| for version in ProtocolVersion::all() { |
| for cs in TestCryptoProvider::all_supported_cipher_suites() { |
| for encrypt_controls in [true, false] { |
| test(version, cs, 10, encrypt_controls).await; |
| } |
| } |
| } |
| } |
| |
| #[cfg(feature = "private_message")] |
| #[cfg(not(mls_build_async))] |
| fn test_on_all_params<F>(test: F) |
| where |
| F: Fn(ProtocolVersion, CipherSuite, usize, bool), |
| { |
| for version in ProtocolVersion::all() { |
| for cs in TestCryptoProvider::all_supported_cipher_suites() { |
| for encrypt_controls in [true, false] { |
| test(version, cs, 10, encrypt_controls); |
| } |
| } |
| } |
| } |
| |
| #[cfg(not(feature = "private_message"))] |
| #[cfg(mls_build_async)] |
| async fn test_on_all_params<F, Fut>(test: F) |
| where |
| F: Fn(ProtocolVersion, CipherSuite, usize, bool) -> Fut, |
| Fut: Future<Output = ()>, |
| { |
| test_on_all_params_plaintext(test).await; |
| } |
| |
| #[cfg(not(feature = "private_message"))] |
| #[cfg(not(mls_build_async))] |
| fn test_on_all_params<F>(test: F) |
| where |
| F: Fn(ProtocolVersion, CipherSuite, usize, bool), |
| { |
| test_on_all_params_plaintext(test); |
| } |
| |
| #[cfg(mls_build_async)] |
| async fn test_on_all_params_plaintext<F, Fut>(test: F) |
| where |
| F: Fn(ProtocolVersion, CipherSuite, usize, bool) -> Fut, |
| Fut: Future<Output = ()>, |
| { |
| for version in ProtocolVersion::all() { |
| for cs in TestCryptoProvider::all_supported_cipher_suites() { |
| test(version, cs, 10, false).await; |
| } |
| } |
| } |
| |
| #[cfg(not(mls_build_async))] |
| fn test_on_all_params_plaintext<F>(test: F) |
| where |
| F: Fn(ProtocolVersion, CipherSuite, usize, bool), |
| { |
| for version in ProtocolVersion::all() { |
| for cs in TestCryptoProvider::all_supported_cipher_suites() { |
| test(version, cs, 10, false); |
| } |
| } |
| } |
| |
| #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)] |
| async fn test_create( |
| protocol_version: ProtocolVersion, |
| cipher_suite: CipherSuite, |
| _n_participants: usize, |
| encrypt_controls: bool, |
| ) { |
| let alice = generate_client(cipher_suite, protocol_version, 0, encrypt_controls).await; |
| let bob = generate_client(cipher_suite, protocol_version, 1, encrypt_controls).await; |
| let bob_key_pkg = bob.generate_key_package_message().await.unwrap(); |
| |
| // Alice creates a group and adds bob |
| let mut alice_group = alice |
| .create_group_with_id(b"group".to_vec(), ExtensionList::default()) |
| .await |
| .unwrap(); |
| |
| let welcome = &alice_group |
| .commit_builder() |
| .add_member(bob_key_pkg) |
| .unwrap() |
| .build() |
| .await |
| .unwrap() |
| .welcome_messages[0]; |
| |
| // Upon server confirmation, alice applies the commit to her own state |
| alice_group.apply_pending_commit().await.unwrap(); |
| |
| // Bob receives the welcome message and joins the group |
| let (bob_group, _) = bob.join_group(None, welcome).await.unwrap(); |
| |
| assert!(Group::equal_group_state(&alice_group, &bob_group)); |
| } |
| |
| #[maybe_async::test(not(mls_build_async), async(mls_build_async, futures_test))] |
| async fn test_create_group() { |
| test_on_all_params(test_create).await; |
| } |
| |
| #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)] |
| async fn test_empty_commits( |
| protocol_version: ProtocolVersion, |
| cipher_suite: CipherSuite, |
| participants: usize, |
| encrypt_controls: bool, |
| ) { |
| let mut groups = get_test_groups( |
| protocol_version, |
| cipher_suite, |
| participants, |
| encrypt_controls, |
| ) |
| .await; |
| |
| // Loop through each participant and send a path update |
| |
| for i in 0..groups.len() { |
| // Create the commit |
| let commit_output = groups[i].commit(Vec::new()).await.unwrap(); |
| |
| assert!(commit_output.welcome_messages.is_empty()); |
| |
| let index = groups[i].current_member_index() as usize; |
| all_process_message(&mut groups, &commit_output.commit_message, index, true).await; |
| |
| for other_group in groups.iter() { |
| assert!(Group::equal_group_state(other_group, &groups[i])); |
| } |
| } |
| } |
| |
| #[maybe_async::test(not(mls_build_async), async(mls_build_async, futures_test))] |
| async fn test_group_path_updates() { |
| test_on_all_params(test_empty_commits).await; |
| } |
| |
| #[cfg(feature = "by_ref_proposal")] |
| #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)] |
| async fn test_update_proposals( |
| protocol_version: ProtocolVersion, |
| cipher_suite: CipherSuite, |
| participants: usize, |
| encrypt_controls: bool, |
| ) { |
| let mut groups = get_test_groups( |
| protocol_version, |
| cipher_suite, |
| participants, |
| encrypt_controls, |
| ) |
| .await; |
| |
| // Create an update from the ith member, have the ith + 1 member commit it |
| for i in 0..groups.len() - 1 { |
| let update_proposal_msg = groups[i].propose_update(Vec::new()).await.unwrap(); |
| |
| let sender = groups[i].current_member_index() as usize; |
| all_process_message(&mut groups, &update_proposal_msg, sender, false).await; |
| |
| // Everyone receives the commit |
| let committer_index = i + 1; |
| |
| let commit_output = groups[committer_index].commit(Vec::new()).await.unwrap(); |
| |
| assert!(commit_output.welcome_messages.is_empty()); |
| |
| let commit = commit_output.commit_message; |
| |
| all_process_message(&mut groups, &commit, committer_index, true).await; |
| |
| groups |
| .iter() |
| .for_each(|g| assert!(Group::equal_group_state(g, &groups[0]))); |
| } |
| } |
| |
| #[cfg(feature = "by_ref_proposal")] |
| #[maybe_async::test(not(mls_build_async), async(mls_build_async, futures_test))] |
| async fn test_group_update_proposals() { |
| test_on_all_params(test_update_proposals).await; |
| } |
| |
| #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)] |
| async fn test_remove_proposals( |
| protocol_version: ProtocolVersion, |
| cipher_suite: CipherSuite, |
| participants: usize, |
| encrypt_controls: bool, |
| ) { |
| let mut groups = get_test_groups( |
| protocol_version, |
| cipher_suite, |
| participants, |
| encrypt_controls, |
| ) |
| .await; |
| |
| // Remove people from the group one at a time |
| while groups.len() > 1 { |
| let removed_and_committer = (0..groups.len()).choose_multiple(&mut rand::thread_rng(), 2); |
| |
| let to_remove = removed_and_committer[0]; |
| let committer = removed_and_committer[1]; |
| let to_remove_index = groups[to_remove].current_member_index(); |
| |
| let epoch_before_remove = groups[committer].current_epoch(); |
| |
| let commit_output = groups[committer] |
| .commit_builder() |
| .remove_member(to_remove_index) |
| .unwrap() |
| .build() |
| .await |
| .unwrap(); |
| |
| assert!(commit_output.welcome_messages.is_empty()); |
| |
| let commit = commit_output.commit_message; |
| let committer_index = groups[committer].current_member_index() as usize; |
| all_process_message(&mut groups, &commit, committer_index, true).await; |
| |
| // Check that remove was effective |
| for (i, group) in groups.iter().enumerate() { |
| if i == to_remove { |
| assert_eq!(group.current_epoch(), epoch_before_remove); |
| } else { |
| assert_eq!(group.current_epoch(), epoch_before_remove + 1); |
| assert!(group.roster().member_with_index(to_remove_index).is_err()); |
| } |
| } |
| |
| groups.retain(|group| group.current_member_index() != to_remove_index); |
| |
| for one_group in groups.iter() { |
| assert!(Group::equal_group_state(one_group, &groups[0])) |
| } |
| } |
| } |
| |
| #[maybe_async::test(not(mls_build_async), async(mls_build_async, futures_test))] |
| async fn test_group_remove_proposals() { |
| test_on_all_params(test_remove_proposals).await; |
| } |
| |
| #[cfg(feature = "private_message")] |
| #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)] |
| async fn test_application_messages( |
| protocol_version: ProtocolVersion, |
| cipher_suite: CipherSuite, |
| participants: usize, |
| encrypt_controls: bool, |
| ) { |
| let message_count = 20; |
| |
| let mut groups = get_test_groups( |
| protocol_version, |
| cipher_suite, |
| participants, |
| encrypt_controls, |
| ) |
| .await; |
| |
| // Loop through each participant and send application messages |
| for i in 0..groups.len() { |
| let mut test_message = vec![0; 1024]; |
| rand::thread_rng().fill_bytes(&mut test_message); |
| |
| for _ in 0..message_count { |
| // Encrypt the application message |
| let ciphertext = groups[i] |
| .encrypt_application_message(&test_message, Vec::new()) |
| .await |
| .unwrap(); |
| |
| let sender_index = groups[i].current_member_index(); |
| |
| for g in groups.iter_mut() { |
| if g.current_member_index() != sender_index { |
| let decrypted = g |
| .process_incoming_message(ciphertext.clone()) |
| .await |
| .unwrap(); |
| |
| assert_matches!(decrypted, ReceivedMessage::ApplicationMessage(m) if m.data() == test_message); |
| } |
| } |
| } |
| } |
| } |
| |
| #[cfg(all(feature = "private_message", feature = "out_of_order"))] |
| #[maybe_async::test(not(mls_build_async), async(mls_build_async, futures_test))] |
| async fn test_out_of_order_application_messages() { |
| let mut groups = |
| get_test_groups(ProtocolVersion::MLS_10, CipherSuite::P256_AES128, 2, false).await; |
| |
| let mut alice_group = groups[0].clone(); |
| let bob_group = &mut groups[1]; |
| |
| let ciphertext = alice_group |
| .encrypt_application_message(&[0], Vec::new()) |
| .await |
| .unwrap(); |
| |
| let mut ciphertexts = vec![ciphertext]; |
| |
| ciphertexts.push( |
| alice_group |
| .encrypt_application_message(&[1], Vec::new()) |
| .await |
| .unwrap(), |
| ); |
| |
| let commit = alice_group.commit(Vec::new()).await.unwrap().commit_message; |
| |
| alice_group.apply_pending_commit().await.unwrap(); |
| |
| bob_group.process_incoming_message(commit).await.unwrap(); |
| |
| ciphertexts.push( |
| alice_group |
| .encrypt_application_message(&[2], Vec::new()) |
| .await |
| .unwrap(), |
| ); |
| |
| ciphertexts.push( |
| alice_group |
| .encrypt_application_message(&[3], Vec::new()) |
| .await |
| .unwrap(), |
| ); |
| |
| for i in [3, 2, 1, 0] { |
| let res = bob_group |
| .process_incoming_message(ciphertexts[i].clone()) |
| .await |
| .unwrap(); |
| |
| assert_matches!( |
| res, |
| ReceivedMessage::ApplicationMessage(m) if m.data() == [i as u8] |
| ); |
| } |
| } |
| |
| #[cfg(feature = "private_message")] |
| #[maybe_async::test(not(mls_build_async), async(mls_build_async, futures_test))] |
| async fn test_group_application_messages() { |
| test_on_all_params(test_application_messages).await |
| } |
| |
| #[cfg(feature = "private_message")] |
| #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)] |
| async fn processing_message_from_self_returns_error( |
| protocol_version: ProtocolVersion, |
| cipher_suite: CipherSuite, |
| _n_participants: usize, |
| encrypt_controls: bool, |
| ) { |
| let mut creator_group = |
| get_test_groups(protocol_version, cipher_suite, 1, encrypt_controls).await; |
| let creator_group = &mut creator_group[0]; |
| |
| let msg = creator_group |
| .encrypt_application_message(b"hello self", vec![]) |
| .await |
| .unwrap(); |
| |
| let error = creator_group |
| .process_incoming_message(msg) |
| .await |
| .unwrap_err(); |
| |
| assert_matches!(error, MlsError::CantProcessMessageFromSelf); |
| } |
| |
| #[cfg(feature = "private_message")] |
| #[maybe_async::test(not(mls_build_async), async(mls_build_async, futures_test))] |
| async fn test_processing_message_from_self_returns_error() { |
| test_on_all_params(processing_message_from_self_returns_error).await; |
| } |
| |
| #[cfg(feature = "by_ref_proposal")] |
| #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)] |
| async fn external_commits_work( |
| protocol_version: ProtocolVersion, |
| cipher_suite: CipherSuite, |
| _n_participants: usize, |
| _encrypt_controls: bool, |
| ) { |
| let creator = generate_client(cipher_suite, protocol_version, 0, false).await; |
| |
| let creator_group = creator |
| .create_group_with_id(b"group".to_vec(), ExtensionList::default()) |
| .await |
| .unwrap(); |
| |
| const PARTICIPANT_COUNT: usize = 10; |
| |
| let mut others = Vec::new(); |
| |
| for i in 1..PARTICIPANT_COUNT { |
| others.push(generate_client(cipher_suite, protocol_version, i, Default::default()).await) |
| } |
| |
| let mut groups = vec![creator_group]; |
| |
| for client in &others { |
| let existing_group = groups.choose_mut(&mut rand::thread_rng()).unwrap(); |
| |
| let group_info = existing_group |
| .group_info_message_allowing_ext_commit(true) |
| .await |
| .unwrap(); |
| |
| let (new_group, commit) = client |
| .external_commit_builder() |
| .unwrap() |
| .build(group_info) |
| .await |
| .unwrap(); |
| |
| for group in groups.iter_mut() { |
| group |
| .process_incoming_message(commit.clone()) |
| .await |
| .unwrap(); |
| } |
| |
| groups.push(new_group); |
| } |
| |
| assert!(groups |
| .iter() |
| .all(|group| group.roster().members_iter().count() == PARTICIPANT_COUNT)); |
| |
| for i in 0..groups.len() { |
| let message = groups[i].propose_remove(0, Vec::new()).await.unwrap(); |
| |
| for (_, group) in groups.iter_mut().enumerate().filter(|&(j, _)| i != j) { |
| let processed = group |
| .process_incoming_message(message.clone()) |
| .await |
| .unwrap(); |
| |
| if let ReceivedMessage::Proposal(p) = &processed { |
| if let Proposal::Remove(r) = &p.proposal { |
| if r.to_remove() == 0 { |
| continue; |
| } |
| } |
| } |
| |
| panic!("expected a proposal, got {processed:?}"); |
| } |
| } |
| } |
| |
| #[cfg(feature = "by_ref_proposal")] |
| #[maybe_async::test(not(mls_build_async), async(mls_build_async, futures_test))] |
| async fn test_external_commits() { |
| test_on_all_params_plaintext(external_commits_work).await |
| } |
| |
| #[maybe_async::test(not(mls_build_async), async(mls_build_async, futures_test))] |
| async fn test_remove_nonexisting_leaf() { |
| let mut groups = |
| get_test_groups(ProtocolVersion::MLS_10, CipherSuite::P256_AES128, 10, false).await; |
| |
| groups[0] |
| .commit_builder() |
| .remove_member(5) |
| .unwrap() |
| .build() |
| .await |
| .unwrap(); |
| groups[0].apply_pending_commit().await.unwrap(); |
| |
| // Leaf index out of bounds |
| assert!(groups[0].commit_builder().remove_member(13).is_err()); |
| |
| // Removing blank leaf causes error |
| assert!(groups[0].commit_builder().remove_member(5).is_err()); |
| } |
| |
| #[cfg(feature = "psk")] |
| #[maybe_async::test(not(mls_build_async), async(mls_build_async, futures_test))] |
| async fn reinit_works() { |
| let suite1 = CipherSuite::P256_AES128; |
| |
| let Some(suite2) = CipherSuite::all() |
| .find(|cs| cs != &suite1 && TestCryptoProvider::all_supported_cipher_suites().contains(cs)) |
| else { |
| return; |
| }; |
| |
| let version = ProtocolVersion::MLS_10; |
| |
| let alice1 = generate_client(suite1, version, 1, Default::default()).await; |
| let bob1 = generate_client(suite1, version, 2, Default::default()).await; |
| |
| // Create a group with 2 parties |
| let mut alice_group = alice1.create_group(ExtensionList::new()).await.unwrap(); |
| let kp = bob1.generate_key_package_message().await.unwrap(); |
| |
| let welcome = &alice_group |
| .commit_builder() |
| .add_member(kp) |
| .unwrap() |
| .build() |
| .await |
| .unwrap() |
| .welcome_messages[0]; |
| |
| alice_group.apply_pending_commit().await.unwrap(); |
| |
| let (mut bob_group, _) = bob1.join_group(None, welcome).await.unwrap(); |
| |
| // Alice proposes reinit |
| let reinit_proposal_message = alice_group |
| .propose_reinit( |
| None, |
| ProtocolVersion::MLS_10, |
| suite2, |
| ExtensionList::default(), |
| Vec::new(), |
| ) |
| .await |
| .unwrap(); |
| |
| // Bob commits the reinit |
| bob_group |
| .process_incoming_message(reinit_proposal_message) |
| .await |
| .unwrap(); |
| |
| let commit = bob_group.commit(Vec::new()).await.unwrap().commit_message; |
| |
| // Both process Bob's commit |
| |
| #[cfg(feature = "state_update")] |
| { |
| let state_update = bob_group.apply_pending_commit().await.unwrap().state_update; |
| assert!(!state_update.is_active() && state_update.is_pending_reinit()); |
| } |
| |
| #[cfg(not(feature = "state_update"))] |
| bob_group.apply_pending_commit().await.unwrap(); |
| |
| let message = alice_group.process_incoming_message(commit).await.unwrap(); |
| |
| #[cfg(feature = "state_update")] |
| if let ReceivedMessage::Commit(commit_description) = message { |
| assert!( |
| !commit_description.state_update.is_active() |
| && commit_description.state_update.is_pending_reinit() |
| ); |
| } |
| |
| #[cfg(not(feature = "state_update"))] |
| assert_matches!(message, ReceivedMessage::Commit(_)); |
| |
| // They can't create new epochs anymore |
| let res = alice_group.commit(Vec::new()).await; |
| assert!(res.is_err()); |
| |
| let res = bob_group.commit(Vec::new()).await; |
| assert!(res.is_err()); |
| |
| // Get reinit clients for alice and bob |
| let (secret_key, public_key) = TestCryptoProvider::new() |
| .cipher_suite_provider(suite2) |
| .unwrap() |
| .signature_key_generate() |
| .await |
| .unwrap(); |
| |
| let identity = SigningIdentity::new(get_test_basic_credential(b"bob".to_vec()), public_key); |
| |
| let bob2 = bob_group |
| .get_reinit_client(Some(secret_key), Some(identity)) |
| .unwrap(); |
| |
| let (secret_key, public_key) = TestCryptoProvider::new() |
| .cipher_suite_provider(suite2) |
| .unwrap() |
| .signature_key_generate() |
| .await |
| .unwrap(); |
| |
| let identity = SigningIdentity::new(get_test_basic_credential(b"alice".to_vec()), public_key); |
| |
| let alice2 = alice_group |
| .get_reinit_client(Some(secret_key), Some(identity)) |
| .unwrap(); |
| |
| // Bob produces key package, alice commits, bob joins |
| let kp = bob2.generate_key_package().await.unwrap(); |
| let (mut alice_group, welcome) = alice2.commit(vec![kp]).await.unwrap(); |
| let (mut bob_group, _) = bob2.join(&welcome[0], None).await.unwrap(); |
| |
| assert!(bob_group.cipher_suite() == suite2); |
| |
| // They can talk |
| let carol = generate_client(suite2, version, 3, Default::default()).await; |
| |
| let kp = carol.generate_key_package_message().await.unwrap(); |
| |
| let commit_output = alice_group |
| .commit_builder() |
| .add_member(kp) |
| .unwrap() |
| .build() |
| .await |
| .unwrap(); |
| |
| alice_group.apply_pending_commit().await.unwrap(); |
| |
| bob_group |
| .process_incoming_message(commit_output.commit_message) |
| .await |
| .unwrap(); |
| |
| carol |
| .join_group(None, &commit_output.welcome_messages[0]) |
| .await |
| .unwrap(); |
| } |
| |
| #[cfg(feature = "by_ref_proposal")] |
| #[maybe_async::test(not(mls_build_async), async(mls_build_async, futures_test))] |
| async fn external_joiner_can_process_siblings_update() { |
| let mut groups = |
| get_test_groups(ProtocolVersion::MLS_10, CipherSuite::P256_AES128, 3, false).await; |
| |
| // Remove leaf 1 s.t. the external joiner joins in its place |
| let c = groups[0] |
| .commit_builder() |
| .remove_member(1) |
| .unwrap() |
| .build() |
| .await |
| .unwrap(); |
| |
| all_process_message(&mut groups, &c.commit_message, 0, true).await; |
| |
| let info = groups[0] |
| .group_info_message_allowing_ext_commit(true) |
| .await |
| .unwrap(); |
| |
| // Create the external joiner and join |
| let new_client = generate_client( |
| CipherSuite::P256_AES128, |
| ProtocolVersion::MLS_10, |
| 0xabba, |
| false, |
| ) |
| .await; |
| |
| let (mut group, commit) = new_client.commit_external(info).await.unwrap(); |
| |
| all_process_message(&mut groups, &commit, 1, false).await; |
| groups.remove(1); |
| |
| // New client's sibling proposes an update to blank their common parent |
| let p = groups[0].propose_update(Vec::new()).await.unwrap(); |
| all_process_message(&mut groups, &p, 0, false).await; |
| group.process_incoming_message(p).await.unwrap(); |
| |
| // Some other member commits |
| let c = groups[1].commit(Vec::new()).await.unwrap().commit_message; |
| all_process_message(&mut groups, &c, 2, true).await; |
| group.process_incoming_message(c).await.unwrap(); |
| } |
| |
| #[maybe_async::test(not(mls_build_async), async(mls_build_async, futures_test))] |
| async fn weird_tree_scenario() { |
| let mut groups = |
| get_test_groups(ProtocolVersion::MLS_10, CipherSuite::P256_AES128, 17, false).await; |
| |
| let to_remove = [0u32, 2, 5, 7, 8, 9, 15]; |
| |
| let mut builder = groups[14].commit_builder(); |
| |
| for idx in to_remove.iter() { |
| builder = builder.remove_member(*idx).unwrap(); |
| } |
| |
| let commit = builder.build().await.unwrap(); |
| |
| for idx in to_remove.into_iter().rev() { |
| groups.remove(idx as usize); |
| } |
| |
| all_process_message(&mut groups, &commit.commit_message, 14, true).await; |
| |
| let mut builder = groups.last_mut().unwrap().commit_builder(); |
| |
| for idx in 0..7 { |
| builder = builder |
| .add_member(fake_key_package(5555555 + idx).await) |
| .unwrap() |
| } |
| |
| let commit = builder.remove_member(1).unwrap().build().await.unwrap(); |
| |
| let idx = groups.last().unwrap().current_member_index() as usize; |
| |
| all_process_message(&mut groups, &commit.commit_message, idx, true).await; |
| } |
| |
| #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)] |
| async fn fake_key_package(id: usize) -> MlsMessage { |
| generate_client(CipherSuite::P256_AES128, ProtocolVersion::MLS_10, id, false) |
| .await |
| .generate_key_package_message() |
| .await |
| .unwrap() |
| } |
| |
| #[maybe_async::test(not(mls_build_async), async(mls_build_async, futures_test))] |
| async fn external_info_from_commit_allows_to_join() { |
| let cs = CipherSuite::P256_AES128; |
| let version = ProtocolVersion::MLS_10; |
| |
| let mut alice = mls_rs::test_utils::get_test_groups( |
| version, |
| cs, |
| 1, |
| Some(CommitOptions::new().with_allow_external_commit(true)), |
| false, |
| &TestCryptoProvider::default(), |
| ) |
| .await |
| .remove(0); |
| |
| let commit = alice.commit(vec![]).await.unwrap(); |
| alice.apply_pending_commit().await.unwrap(); |
| let bob = generate_client(cs, version, 0xdead, false).await; |
| |
| let (_bob, commit) = bob |
| .commit_external(commit.external_commit_group_info.unwrap()) |
| .await |
| .unwrap(); |
| |
| alice.process_incoming_message(commit).await.unwrap(); |
| } |