openmls/test_utils/test_framework/
mod.rs

1//! This crate provides a framework to set up clients and groups using the
2//! OpenMLS mls_group API. To use the framework, start by creating a new
3//! `TestSetup` with a number of clients. After that, `create_clients` has to be
4//! called before the the `TestSetup` can be used.
5//!
6//! Note that due to lifetime issues, no new clients can be created after
7//! initialization.
8//!
9//! After initialization, the `TestSetup` enables the creation of groups using
10//! `create_group`, which simply creates a one-member group, or
11//! `create_random_group`, which creates a group of the given size with random
12//! clients.
13//!
14//! Existing groups are represented by the `groups` field of the `TestSetup` and
15//! initial members can be instructed to either propose or commit adds, removes
16//! or updates via the corresponding functions of `TestSetup`. Note, that these
17//! functions require a `&Group` reference, which can be obtained via the
18//! `groups` field. When using these functions, the `TestSetup` fills the role
19//! of the DS and automatically distributes the required KeyPackages and
20//! resulting mls messages to the individual clients. Alternatively, the clients
21//! can be manipulated manually via the `Client` struct, which contains their
22//! group states.
23
24use crate::storage::OpenMlsProvider;
25use crate::test_utils::OpenMlsRustCrypto;
26use crate::treesync::LeafNodeParameters;
27use crate::{
28    binary_tree::array_representation::LeafNodeIndex,
29    ciphersuite::{hash_ref::KeyPackageRef, *},
30    credentials::*,
31    framing::*,
32    group::*,
33    key_packages::*,
34    messages::*,
35    treesync::{node::Node, LeafNode, RatchetTree, RatchetTreeIn},
36};
37use ::rand::{rngs::OsRng, RngCore};
38use openmls_basic_credential::SignatureKeyPair;
39use openmls_traits::{
40    crypto::OpenMlsCrypto,
41    types::{Ciphersuite, HpkeKeyPair, SignatureScheme},
42    OpenMlsProvider as _,
43};
44
45use std::{collections::HashMap, sync::RwLock};
46use tls_codec::*;
47
48pub mod client;
49pub mod errors;
50
51use self::client::*;
52use self::errors::*;
53
54#[derive(Clone)]
55/// The `Group` struct represents the "global" shared state of the group. Note,
56/// that this state is only consistent if operations are conducted as per spec
57/// and messages are distributed correctly to all clients via the
58/// `distribute_to_members` function of `TestSetup`, which also updates the
59/// `public_tree` field.
60pub struct Group {
61    pub group_id: GroupId,
62    pub members: Vec<(usize, Vec<u8>)>,
63    pub ciphersuite: Ciphersuite,
64    pub group_config: MlsGroupJoinConfig,
65    pub public_tree: RatchetTree,
66    pub exporter_secret: Vec<u8>,
67}
68
69impl Group {
70    /// Return the identity of a random member of the group.
71    pub fn random_group_member(&self) -> (u32, Vec<u8>) {
72        let index = (OsRng.next_u32() as usize) % self.members.len();
73        let (i, identity) = self.members[index].clone();
74        (i as u32, identity)
75    }
76    pub fn group_id(&self) -> &GroupId {
77        &self.group_id
78    }
79
80    pub fn members(&self) -> impl Iterator<Item = (u32, Vec<u8>)> + '_ {
81        self.members
82            .clone()
83            .into_iter()
84            .map(|(index, id)| (index as u32, id))
85    }
86}
87
88#[derive(Debug)]
89pub enum ActionType {
90    Commit,
91    Proposal,
92}
93
94#[derive(Debug, PartialEq, Eq)]
95pub enum CodecUse {
96    SerializedMessages,
97    StructMessages,
98}
99
100/// `MlsGroupTestSetup` is the main struct of the framework. It contains the
101/// state of all clients. The `waiting_for_welcome` field acts as a temporary
102/// store for `KeyPackage`s that are used to add new members to groups. Note,
103/// that the `MlsGroupTestSetup` can only be initialized with a fixed number of
104/// clients and that `create_clients` has to be called before it can be
105/// otherwise used.
106pub struct MlsGroupTestSetup<Provider: OpenMlsProvider> {
107    // The clients identity is its position in the vector in be_bytes.
108    pub clients: RwLock<HashMap<Vec<u8>, RwLock<Client<Provider>>>>,
109    pub groups: RwLock<HashMap<GroupId, Group>>,
110    // This maps key package hashes to client ids.
111    pub waiting_for_welcome: RwLock<HashMap<Vec<u8>, Vec<u8>>>,
112    pub default_mgp: MlsGroupCreateConfig,
113    /// Flag to indicate if messages should be serialized and de-serialized as
114    /// part of message distribution
115    pub use_codec: CodecUse,
116}
117
118// Some notes regarding the layout of the `MlsGroupTestSetup` implementation
119// below: The references to the credentials in the MlsGroups (in the
120// clients) are essentially references to the keystore, which in turns means
121// they are references to the top-level object, i.e. the setup.
122//
123// As a result, we can't have mutable references to the setup, because we have
124// living (immutable) references to the keystore floating around. As a follow-up
125// result, we need to populate the keystore _completely_ before distributing the
126// references, as we can't mutably reference the keystore.
127//
128// Finally, this means we have to initialize the KeyStore before we create the
129// MlsGroupTestSetup object and we can create the clients (which contain the
130// references to the KeyStore) only after we do that. This has to happen in the
131// context that the `MlsGroupTestSetup` lives in, because otherwise the
132// references don't live long enough.
133
134impl<Provider: OpenMlsProvider + Default> MlsGroupTestSetup<Provider> {
135    /// Create a new `MlsGroupTestSetup` with the given default
136    /// `MlsGroupCreateConfig` and the given number of clients. For lifetime
137    /// reasons, `create_clients` has to be called in addition with the same
138    /// number of clients.
139    pub fn new(
140        default_mgp: MlsGroupCreateConfig,
141        number_of_clients: usize,
142        use_codec: CodecUse,
143    ) -> Self {
144        let mut clients = HashMap::new();
145        for i in 0..number_of_clients {
146            let identity = i.to_be_bytes().to_vec();
147            let provider = Provider::default();
148            let mut credentials = HashMap::new();
149            for ciphersuite in provider.crypto().supported_ciphersuites().iter() {
150                let credential = BasicCredential::new(identity.clone());
151                let signature_keys =
152                    SignatureKeyPair::new(ciphersuite.signature_algorithm()).unwrap();
153                signature_keys.store(provider.storage()).unwrap();
154                let signature_key = OpenMlsSignaturePublicKey::new(
155                    signature_keys.public().into(),
156                    signature_keys.signature_scheme(),
157                )
158                .unwrap();
159
160                credentials.insert(
161                    *ciphersuite,
162                    CredentialWithKey {
163                        credential: credential.into(),
164                        signature_key: signature_key.into(),
165                    },
166                );
167            }
168            let client = Client {
169                identity: identity.clone(),
170                credentials,
171                provider,
172                groups: RwLock::new(HashMap::new()),
173            };
174            clients.insert(identity, RwLock::new(client));
175        }
176        let groups = RwLock::new(HashMap::new());
177        let waiting_for_welcome = RwLock::new(HashMap::new());
178        MlsGroupTestSetup {
179            clients: RwLock::new(clients),
180            groups,
181            waiting_for_welcome,
182            default_mgp,
183            use_codec,
184        }
185    }
186
187    /// Create a fresh `KeyPackage` for client `client` for use when adding it
188    /// to a group. The `KeyPackageBundle` will be fetched automatically when
189    /// delivering the `Welcome` via `deliver_welcome`. This function throws an
190    /// error if the client does not support the given ciphersuite.
191    pub fn get_fresh_key_package(
192        &self,
193        client: &Client<Provider>,
194        ciphersuite: Ciphersuite,
195    ) -> Result<KeyPackage, SetupError<Provider::StorageError>> {
196        let key_package = client.get_fresh_key_package(ciphersuite)?;
197        self.waiting_for_welcome
198            .write()
199            .expect("An unexpected error occurred.")
200            .insert(
201                key_package
202                    .hash_ref(client.provider.crypto())?
203                    .as_slice()
204                    .to_vec(),
205                client.identity.clone(),
206            );
207        Ok(key_package)
208    }
209
210    /// Convert an index in the tree into the corresponding identity.
211    pub fn identity_by_index(&self, index: usize, group: &Group) -> Option<Vec<u8>> {
212        let (_, id) = group
213            .members
214            .iter()
215            .find(|(leaf_index, _)| index == *leaf_index)
216            .expect("Couldn't find member at leaf index");
217        let clients = self.clients.read().expect("An unexpected error occurred.");
218        let client = clients
219            .get(id)
220            .expect("An unexpected error occurred.")
221            .read()
222            .expect("An unexpected error occurred.");
223        client.identity(&group.group_id)
224    }
225
226    /// Convert an identity in the tree into the corresponding key package reference.
227    pub fn identity_by_id(&self, id: &[u8], group: &Group) -> Option<Vec<u8>> {
228        let (_, id) = group
229            .members
230            .iter()
231            .find(|(_, leaf_id)| id == leaf_id)
232            .expect("Couldn't find member at leaf index");
233        let clients = self.clients.read().expect("An unexpected error occurred.");
234        let client = clients
235            .get(id)
236            .expect("An unexpected error occurred.")
237            .read()
238            .expect("An unexpected error occurred.");
239        client.identity(&group.group_id)
240    }
241
242    /// Deliver a Welcome message to the intended recipients. It uses the given
243    /// group `group` to obtain the current public tree of the group. Note, that
244    /// this tree only exists if `distribute_to_members` was previously used to
245    /// distribute the commit adding the members to the group. This function
246    /// will throw an error if no key package was previously created for the
247    /// client by `get_fresh_key_package`.
248    pub fn deliver_welcome(
249        &self,
250        welcome: Welcome,
251        group: &Group,
252    ) -> Result<(), SetupError<Provider::StorageError>> {
253        // Serialize and de-serialize the Welcome if the bit was set.
254        let welcome = match self.use_codec {
255            CodecUse::SerializedMessages => {
256                let serialized_welcome = welcome
257                    .tls_serialize_detached()
258                    .map_err(ClientError::TlsCodecError)?;
259                Welcome::tls_deserialize(&mut serialized_welcome.as_slice())
260                    .map_err(ClientError::TlsCodecError)?
261            }
262            CodecUse::StructMessages => welcome,
263        };
264        let clients = self.clients.read().expect("An unexpected error occurred.");
265        for egs in welcome.secrets() {
266            let client_id = self
267                .waiting_for_welcome
268                .write()
269                .expect("An unexpected error occurred.")
270                .remove(egs.new_member().as_slice())
271                .ok_or(SetupError::NoFreshKeyPackage)?;
272            let client = clients
273                .get(&client_id)
274                .expect("An unexpected error occurred.")
275                .read()
276                .expect("An unexpected error occurred.");
277            client.join_group(
278                group.group_config.clone(),
279                welcome.clone(),
280                Some(group.public_tree.clone().into()),
281            )?;
282        }
283        Ok(())
284    }
285
286    /// Distribute a set of messages sent by the sender with identity
287    /// `sender_id` to their intended recipients in group `Group`. This function
288    /// also verifies that all members of that group agree on the public tree.
289    pub fn distribute_to_members<AS: Fn(&Credential) -> bool>(
290        &self,
291        // We need the sender to know a group member that we know can not have
292        // been removed from the group.
293        sender_id: &[u8],
294        group: &mut Group,
295        message: &MlsMessageIn,
296        authentication_service: &AS,
297    ) -> Result<(), ClientError<Provider::StorageError>> {
298        // Test serialization if mandated by config
299        let message: ProtocolMessage = match self.use_codec {
300            CodecUse::SerializedMessages => {
301                let mls_message_out: MlsMessageOut = message.clone().into();
302                let serialized_message = mls_message_out
303                    .tls_serialize_detached()
304                    .map_err(ClientError::TlsCodecError)?;
305
306                MlsMessageIn::tls_deserialize(&mut serialized_message.as_slice())
307                    .map_err(ClientError::TlsCodecError)?
308            }
309            CodecUse::StructMessages => message.clone(),
310        }
311        .into_protocol_message()
312        .expect("Unexptected message type.");
313        let clients = self.clients.read().expect("An unexpected error occurred.");
314        // Distribute message to all members, except to the sender in the case of application messages
315        let results: Result<Vec<_>, _> = group
316            .members
317            .iter()
318            .filter_map(|(_index, member_id)| {
319                if message.content_type() == ContentType::Application && member_id == sender_id {
320                    None
321                } else {
322                    Some(member_id)
323                }
324            })
325            .map(|member_id| {
326                let member = clients
327                    .get(member_id)
328                    .expect("An unexpected error occurred.")
329                    .read()
330                    .expect("An unexpected error occurred.");
331                member.receive_messages_for_group(&message, sender_id, &authentication_service)
332            })
333            .collect();
334
335        // Check if we received an error
336        results?;
337        // Get the current tree and figure out who's still in the group.
338        let sender = clients
339            .get(sender_id)
340            .expect("An unexpected error occurred.")
341            .read()
342            .expect("An unexpected error occurred.");
343        let sender_groups = sender.groups.read().expect("An unexpected error occurred.");
344        let sender_group = sender_groups
345            .get(&group.group_id)
346            .expect("An unexpected error occurred.");
347        group.members = sender
348            .get_members_of_group(&group.group_id)?
349            .iter()
350            .map(
351                |Member {
352                     index, credential, ..
353                 }| { (index.usize(), credential.serialized_content().to_vec()) },
354            )
355            .collect();
356        group.public_tree = sender_group.export_ratchet_tree();
357        group.exporter_secret = sender_group
358            .export_secret(sender.provider.crypto(), "test", &[], 32)
359            .map_err(ClientError::ExportSecretError)?;
360        Ok(())
361    }
362
363    /// Check if the public tree and the exporter secret with label "test" and
364    /// length of the given group is the same for each group member. It also has
365    /// each group member encrypt an application message and delivers all of
366    /// these messages to all other members. This function panics if any of the
367    /// above tests fail.
368    pub fn check_group_states<AS: Fn(&Credential) -> bool>(
369        &self,
370        group: &mut Group,
371        authentication_service: AS,
372    ) {
373        let clients = self.clients.read().expect("An unexpected error occurred.");
374
375        let group_members = group.members.iter();
376
377        let messages = group_members
378            .filter_map(|(_, m_id)| {
379                let m = clients
380                    .get(m_id)
381                    .expect("An unexpected error occurred.")
382                    .read()
383                    .expect("An unexpected error occurred.");
384                let mut group_states = m.groups.write().expect("An unexpected error occurred.");
385                // Some group members may not have received their welcome messages yet.
386                if let Some(group_state) = group_states.get_mut(&group.group_id) {
387                    assert_eq!(group_state.export_ratchet_tree(), group.public_tree);
388                    assert_eq!(
389                        group_state
390                            .export_secret(m.provider.crypto(), "test", &[], 32)
391                            .expect("An unexpected error occurred."),
392                        group.exporter_secret
393                    );
394                    // Get the signature public key to read the signer from the
395                    // key store.
396                    let signature_pk = group_state.own_leaf().unwrap().signature_key();
397                    let signer = SignatureKeyPair::read(
398                        m.provider.storage(),
399                        signature_pk.as_slice(),
400                        group_state.ciphersuite().signature_algorithm(),
401                    )
402                    .unwrap();
403                    let message = group_state
404                        .create_message(&m.provider, &signer, "Hello World!".as_bytes())
405                        .expect("Error composing message while checking group states.");
406                    Some((m_id.to_vec(), message))
407                } else {
408                    None
409                }
410            })
411            .collect::<Vec<(Vec<u8>, MlsMessageOut)>>();
412        drop(clients);
413        for (sender_id, message) in messages {
414            self.distribute_to_members(&sender_id, group, &message.into(), &authentication_service)
415                .expect("Error sending messages to clients while checking group states.");
416        }
417    }
418
419    /// Get `number_of_members` new members for the given group `group` for use
420    /// with the `add_members` function. If not enough clients are left that are
421    /// not already members of this group, this function will return an error.
422    /// TODO #310: Make this function ensure that the given members support the
423    /// ciphersuite of the group.
424    pub fn random_new_members_for_group(
425        &self,
426        group: &Group,
427        number_of_members: usize,
428    ) -> Result<Vec<Vec<u8>>, SetupError<Provider::StorageError>> {
429        let clients = self.clients.read().expect("An unexpected error occurred.");
430        if number_of_members + group.members.len() > clients.len() {
431            return Err(SetupError::NotEnoughClients);
432        }
433        let mut new_member_ids: Vec<Vec<u8>> = Vec::new();
434
435        for _ in 0..number_of_members {
436            let is_in_new_members = |client_id| {
437                new_member_ids
438                    .iter()
439                    .any(|new_member_id| client_id == new_member_id)
440            };
441            let is_in_group = |client_id| {
442                group
443                    .members
444                    .iter()
445                    .any(|(_, member_id)| client_id == member_id)
446            };
447            let new_member_id = clients
448                .keys()
449                .find(|&client_id| !is_in_group(client_id) && !is_in_new_members(client_id))
450                .expect("An unexpected error occurred.");
451            new_member_ids.push(new_member_id.clone());
452        }
453        Ok(new_member_ids)
454    }
455
456    /// Have a random client create a new group with ciphersuite `ciphersuite`
457    /// and return the `GroupId`. Only works reliably if all clients support all
458    /// ciphersuites, as it will throw an error if the randomly chosen client
459    /// does not support the given ciphersuite. TODO #310: Fix to always work
460    /// reliably, probably by introducing a mapping from ciphersuite to the set
461    /// of client ids supporting it.
462    pub fn create_group(
463        &self,
464        ciphersuite: Ciphersuite,
465    ) -> Result<GroupId, SetupError<Provider::StorageError>> {
466        // Pick a random group creator.
467        let clients = self.clients.read().expect("An unexpected error occurred.");
468        let group_creator_id = ((OsRng.next_u32() as usize) % clients.len())
469            .to_be_bytes()
470            .to_vec();
471        let group_creator = clients
472            .get(&group_creator_id)
473            .expect("An unexpected error occurred.")
474            .read()
475            .expect("An unexpected error occurred.");
476        let mut groups = self.groups.write().expect("An unexpected error occurred.");
477        let group_id = group_creator.create_group(self.default_mgp.clone(), ciphersuite)?;
478        let creator_groups = group_creator
479            .groups
480            .read()
481            .expect("An unexpected error occurred.");
482        let group = creator_groups
483            .get(&group_id)
484            .expect("An unexpected error occurred.");
485        let public_tree = group.export_ratchet_tree();
486        let exporter_secret =
487            group.export_secret(group_creator.provider.crypto(), "test", &[], 32)?;
488        let member_ids = vec![(0, group_creator_id)];
489        let group = Group {
490            group_id: group_id.clone(),
491            members: member_ids,
492            ciphersuite,
493            group_config: self.default_mgp.join_config.clone(),
494            public_tree,
495            exporter_secret,
496        };
497        groups.insert(group_id.clone(), group);
498        Ok(group_id)
499    }
500
501    /// Create a random group of size `group_size` and return the `GroupId`
502    pub fn create_random_group<AS: Fn(&Credential) -> bool>(
503        &self,
504        target_group_size: usize,
505        ciphersuite: Ciphersuite,
506        authentication_service: AS,
507    ) -> Result<GroupId, SetupError<Provider::StorageError>> {
508        // Create the initial group.
509        let group_id = self.create_group(ciphersuite)?;
510
511        let mut groups = self.groups.write().expect("An unexpected error occurred.");
512        let group = groups
513            .get_mut(&group_id)
514            .expect("An unexpected error occurred.");
515
516        // Get new members to add to the group.
517        let mut new_members = self.random_new_members_for_group(group, target_group_size - 1)?;
518
519        // Add new members bit by bit.
520        while !new_members.is_empty() {
521            // Pick a random adder.
522            let adder_id = group.random_group_member();
523            // Add between 1 and 5 new members.
524            let number_of_adds = ((OsRng.next_u32() as usize) % 5 % new_members.len()) + 1;
525            let members_to_add = new_members.drain(0..number_of_adds).collect();
526            self.add_clients(
527                ActionType::Commit,
528                group,
529                &adder_id.1,
530                members_to_add,
531                &authentication_service,
532            )?;
533        }
534        Ok(group_id)
535    }
536
537    /// Have the client with identity `client_id` either propose or commit
538    /// (depending on `action_type`) a self update in group `group`. Will throw
539    /// an error if the client is not actually a member of group `group`.
540    pub fn self_update<AS: Fn(&Credential) -> bool>(
541        &self,
542        action_type: ActionType,
543        group: &mut Group,
544        client_id: &[u8],
545        leaf_node_parameters: LeafNodeParameters,
546        authentication_service: &AS,
547    ) -> Result<(), SetupError<Provider::StorageError>> {
548        let clients = self.clients.read().expect("An unexpected error occurred.");
549        let client = clients
550            .get(client_id)
551            .ok_or(SetupError::UnknownClientId)?
552            .read()
553            .expect("An unexpected error occurred.");
554        let (messages, welcome_option, _) =
555            client.self_update(action_type, &group.group_id, leaf_node_parameters)?;
556        self.distribute_to_members(
557            &client.identity,
558            group,
559            &messages.into(),
560            authentication_service,
561        )?;
562        if let Some(welcome) = welcome_option {
563            self.deliver_welcome(welcome, group)?;
564        }
565        Ok(())
566    }
567
568    /// Has the `adder` either propose or commit (depending on the
569    /// `action_type`) an add of the `addee` to the Group `group`. Returns an
570    /// error if
571    /// * the `adder` is not part of the group
572    /// * the `addee` is already part of the group
573    /// * the `addee` doesn't support the group's ciphersuite.
574    pub fn add_clients<AS: Fn(&Credential) -> bool>(
575        &self,
576        action_type: ActionType,
577        group: &mut Group,
578        adder_id: &[u8],
579        addees: Vec<Vec<u8>>,
580        authentication_service: &AS,
581    ) -> Result<(), SetupError<Provider::StorageError>> {
582        let clients = self.clients.read().expect("An unexpected error occurred.");
583        let adder = clients
584            .get(adder_id)
585            .ok_or(SetupError::UnknownClientId)?
586            .read()
587            .expect("An unexpected error occurred.");
588        if group
589            .members
590            .iter()
591            .any(|(_, id)| addees.iter().any(|client_id| client_id == id))
592        {
593            return Err(SetupError::ClientAlreadyInGroup);
594        }
595        let mut key_packages = Vec::new();
596        for addee_id in &addees {
597            let addee = clients
598                .get(addee_id)
599                .ok_or(SetupError::UnknownClientId)?
600                .read()
601                .expect("An unexpected error occurred.");
602            let key_package = self.get_fresh_key_package(&addee, group.ciphersuite)?;
603            key_packages.push(key_package);
604        }
605        let (messages, welcome_option, _) =
606            adder.add_members(action_type, &group.group_id, &key_packages)?;
607        for message in messages {
608            self.distribute_to_members(adder_id, group, &message.into(), authentication_service)?;
609        }
610        if let Some(welcome) = welcome_option {
611            self.deliver_welcome(welcome, group)?;
612        }
613        Ok(())
614    }
615
616    /// Has the `remover` propose or commit (depending on the `action_type`) the
617    /// removal the `target_members` from the Group `group`. If the `remover` or
618    /// one of the `target_members` is not part of the group, it returns an
619    /// error.
620    pub fn remove_clients<AS: Fn(&Credential) -> bool>(
621        &self,
622        action_type: ActionType,
623        group: &mut Group,
624        remover_id: &[u8],
625        target_members: &[LeafNodeIndex],
626        authentication_service: AS,
627    ) -> Result<(), SetupError<Provider::StorageError>> {
628        let clients = self.clients.read().expect("An unexpected error occurred.");
629        let remover = clients
630            .get(remover_id)
631            .ok_or(SetupError::UnknownClientId)?
632            .read()
633            .expect("An unexpected error occurred.");
634        let (messages, welcome_option, _) =
635            remover.remove_members(action_type, &group.group_id, target_members)?;
636        for message in messages {
637            self.distribute_to_members(
638                remover_id,
639                group,
640                &message.into(),
641                &authentication_service,
642            )?;
643        }
644        if let Some(welcome) = welcome_option {
645            self.deliver_welcome(welcome, group)?;
646        }
647        Ok(())
648    }
649
650    /// This function picks a random member of group `group` and has them
651    /// perform a random commit- or proposal action. TODO #133: This won't work
652    /// yet due to the missing proposal validation.
653    pub fn perform_random_operation<AS: Fn(&Credential) -> bool>(
654        &self,
655        group: &mut Group,
656        authentication_service: &AS,
657    ) -> Result<(), SetupError<Provider::StorageError>> {
658        // Who's going to do it?
659        let member_id = group.random_group_member();
660        println!("Member performing the operation: {member_id:?}");
661
662        // TODO: Do both things.
663        let action_type = match (OsRng.next_u32() as usize) % 2 {
664            0 => ActionType::Proposal,
665            1 => ActionType::Commit,
666            _ => return Err(SetupError::Unknown),
667        };
668
669        // TODO: Do multiple things.
670        let operation_type = (OsRng.next_u32() as usize) % 3;
671        match operation_type {
672            0 => {
673                println!("Performing a self-update with action type: {action_type:?}");
674                self.self_update(
675                    action_type,
676                    group,
677                    &member_id.1,
678                    LeafNodeParameters::default(),
679                    authentication_service,
680                )?;
681            }
682            1 => {
683                // If it's a single-member group, don't remove anyone.
684                if group.members.len() > 1 {
685                    // How many members?
686                    let number_of_removals =
687                        (((OsRng.next_u32() as usize) % group.members.len()) % 5) + 1;
688
689                    let (own_index, _) = group
690                        .members
691                        .iter()
692                        .find(|(_, identity)| identity == &member_id.1)
693                        .expect("An unexpected error occurred.")
694                        .clone();
695                    println!("Index of the member performing the {action_type:?}: {own_index:?}");
696
697                    let mut target_member_leaf_indices = Vec::new();
698                    let mut target_member_identities = Vec::new();
699                    let clients = self.clients.read().expect("An unexpected error occurred.");
700                    // Get the client references, as opposed to just the member indices.
701                    println!("Removing members:");
702                    for _ in 0..number_of_removals {
703                        // Get a random index.
704                        let mut member_list_index =
705                            (OsRng.next_u32() as usize) % group.members.len();
706                        // Re-sample until the index is not our own index and
707                        // not one that is not already being removed.
708                        let (mut leaf_index, mut identity) =
709                            group.members[member_list_index].clone();
710                        while leaf_index == own_index
711                            || target_member_identities.contains(&identity)
712                        {
713                            member_list_index = (OsRng.next_u32() as usize) % group.members.len();
714                            let (new_leaf_index, new_identity) =
715                                group.members[member_list_index].clone();
716                            leaf_index = new_leaf_index;
717                            identity = new_identity;
718                        }
719                        let client = clients
720                            .get(&identity)
721                            .expect("An unexpected error occurred.")
722                            .read()
723                            .expect("An unexpected error occurred.");
724                        let client_group =
725                            client.groups.read().expect("An unexpected error occurred.");
726                        let client_group = client_group
727                            .get(&group.group_id)
728                            .expect("An unexpected error occurred.");
729                        target_member_leaf_indices.push(client_group.own_leaf_index());
730                        target_member_identities.push(identity);
731                    }
732                    self.remove_clients(
733                        action_type,
734                        group,
735                        &member_id.1,
736                        &target_member_leaf_indices,
737                        authentication_service,
738                    )?
739                };
740            }
741            2 => {
742                // First, figure out if there are clients left to add.
743                let clients_left = self
744                    .clients
745                    .read()
746                    .expect("An unexpected error occurred.")
747                    .len()
748                    - group.members.len();
749                if clients_left > 0 {
750                    let number_of_adds = (((OsRng.next_u32() as usize) % clients_left) % 5) + 1;
751                    let new_member_ids = self
752                        .random_new_members_for_group(group, number_of_adds)
753                        .expect("An unexpected error occurred.");
754                    println!("{action_type:?}: Adding new clients: {new_member_ids:?}");
755                    // Have the adder add them to the group.
756                    self.add_clients(
757                        action_type,
758                        group,
759                        &member_id.1,
760                        new_member_ids,
761                        authentication_service,
762                    )?;
763                }
764            }
765            _ => return Err(SetupError::Unknown),
766        };
767        Ok(())
768    }
769}
770
771// This function signature is used in the callback for credential validation.
772// This particular function just accepts any credential. If you want to validate
773// credentials in your test, don't use this.
774pub fn noop_authentication_service(_cred: &Credential) -> bool {
775    true
776}