diff --git a/openmls/src/group/mls_group/errors.rs b/openmls/src/group/mls_group/errors.rs index abf94bdbe..888f57b2a 100644 --- a/openmls/src/group/mls_group/errors.rs +++ b/openmls/src/group/mls_group/errors.rs @@ -212,6 +212,26 @@ pub enum RemoveMembersError { StorageError(StorageError), } +/// Update group membership error +#[derive(Error, Debug, PartialEq, Clone)] +pub enum UpdateGroupMembershipError { + /// See [`LibraryError`] for more details. + #[error(transparent)] + LibraryError(#[from] LibraryError), + /// See [`CreateCommitError`] for more details. + #[error(transparent)] + CreateCommitError(#[from] CreateCommitError), + /// See [`MlsGroupStateError`] for more details. + #[error(transparent)] + GroupStateError(#[from] MlsGroupStateError), + /// The member that should be removed can not be found. + #[error("The member that should be removed can not be found.")] + UnknownMember, + /// Error writing to storage + #[error("Error writing to storage: {0}")] + StorageError(StorageError), +} + /// Leave group error #[derive(Error, Debug, PartialEq, Clone)] pub enum LeaveGroupError { diff --git a/openmls/src/group/mls_group/membership.rs b/openmls/src/group/mls_group/membership.rs index b22c723ed..c1bf84b18 100644 --- a/openmls/src/group/mls_group/membership.rs +++ b/openmls/src/group/mls_group/membership.rs @@ -14,7 +14,77 @@ use crate::{ storage::OpenMlsProvider, treesync::LeafNode, }; +type UpdateResult = Result< + (MlsMessageOut, Option, Option), + UpdateGroupMembershipError<::StorageError>, +>; + impl MlsGroup { + /// Updates the group membership using only inline proposals. + /// Adds and removes members and updates the group context. + pub fn update_group_membership( + &mut self, + provider: &Provider, + signer: &impl Signer, + key_packages_to_add: &[KeyPackage], + leaf_nodes_to_remove: &[LeafNodeIndex], + new_extensions: Extensions, + ) -> UpdateResult { + self.is_operational()?; + + // Create inline add proposals from any provided key packages + let add_proposals = key_packages_to_add + .iter() + .map(|key_package| { + Proposal::Add(AddProposal { + key_package: key_package.clone(), + }) + }) + .collect::>(); + + let extensions_proposals = vec![Proposal::GroupContextExtensions( + GroupContextExtensionProposal { + extensions: new_extensions, + }, + )]; + + let mut remove_proposals = Vec::new(); + for member in leaf_nodes_to_remove.iter() { + remove_proposals.push(Proposal::Remove(RemoveProposal { removed: *member })) + } + + let proposals = [add_proposals, extensions_proposals, remove_proposals].concat(); + + let params = CreateCommitParams::builder() + .framing_parameters(self.framing_parameters()) + .proposal_store(&self.proposal_store) + .inline_proposals(proposals) + .build(); + let create_commit_result = self.group.create_commit(params, provider, signer)?; + + // Convert PublicMessage messages to MLSMessage and encrypt them if required by + // the configuration + let mls_messages = self.content_to_mls_message(create_commit_result.commit, provider)?; + + // Set the current group state to [`MlsGroupState::PendingCommit`], + // storing the current [`StagedCommit`] from the commit results + self.group_state = MlsGroupState::PendingCommit(Box::new(PendingCommitState::Member( + create_commit_result.staged_commit, + ))); + + provider + .storage() + .write_group_state(self.group_id(), &self.group_state) + .map_err(UpdateGroupMembershipError::StorageError)?; + + Ok(( + mls_messages, + create_commit_result + .welcome_option + .map(|w| MlsMessageOut::from_welcome(w, self.group.version())), + create_commit_result.group_info, + )) + } /// Adds members to the group. /// /// New members are added by providing a `KeyPackage` for each member.