use std::{borrow::BorrowMut, marker::PhantomData};
use openmls_traits::{
crypto::OpenMlsCrypto, random::OpenMlsRand, signatures::Signer, storage::StorageProvider as _,
};
use tls_codec::Serialize as _;
use crate::{
binary_tree::LeafNodeIndex,
ciphersuite::{signable::Signable as _, Secret},
extensions::Extensions,
framing::{FramingParameters, WireFormat},
group::{
diff::compute_path::{CommitType, PathComputationResult},
CommitBuilderStageError, CreateCommitError, Extension, ExternalPubExtension, GroupContext,
ProposalQueue, ProposalQueueError, QueuedProposal, RatchetTreeExtension, StagedCommit,
WireFormatPolicy,
},
key_packages::KeyPackage,
messages::{
group_info::{GroupInfo, GroupInfoTBS},
Commit, Welcome,
},
prelude::{
CredentialWithKey, InvalidExtensionError, LeafNodeParameters, LibraryError, NewSignerBundle,
},
schedule::{
psk::{load_psks, PskSecret},
EpochSecretsResult, JoinerSecret, KeySchedule, PreSharedKeyId,
},
storage::{OpenMlsProvider, StorageProvider},
treesync::errors::LeafNodeValidationError,
versions::ProtocolVersion,
};
#[cfg(feature = "extensions-draft-08")]
use crate::{
messages::proposals::AppDataUpdateProposal,
prelude::processing::{AppDataDictionaryUpdater, AppDataUpdates},
schedule::application_export_tree::ApplicationExportTree,
};
pub(crate) mod external_commits;
pub use external_commits::{ExternalCommitBuilder, ExternalCommitBuilderError};
#[cfg(doc)]
use super::MlsGroupJoinConfig;
use super::{
mls_auth_content::AuthenticatedContent,
staged_commit::{MemberStagedCommitState, StagedCommitState},
AddProposal, CreateCommitResult, GroupContextExtensionProposal, MlsGroup, MlsGroupState,
MlsMessageOut, PendingCommitState, Proposal, RemoveProposal, Sender,
};
#[derive(Debug)]
struct ExternalCommitInfo {
aad: Vec<u8>,
credential: CredentialWithKey,
wire_format_policy: WireFormatPolicy,
}
#[derive(Debug, Default)]
struct GroupInfoConfig {
create_group_info: bool,
use_ratchet_tree_extension: bool,
other_extensions: Vec<Extension>,
}
#[derive(Debug)]
pub struct Initial {
own_proposals: Vec<Proposal>,
force_self_update: bool,
leaf_node_parameters: LeafNodeParameters,
external_commit_info: Option<ExternalCommitInfo>,
consume_proposal_store: bool,
}
impl Default for Initial {
fn default() -> Self {
Initial {
consume_proposal_store: true,
force_self_update: false,
leaf_node_parameters: LeafNodeParameters::default(),
own_proposals: vec![],
external_commit_info: None,
}
}
}
pub struct LoadedPsks {
own_proposals: Vec<Proposal>,
force_self_update: bool,
leaf_node_parameters: LeafNodeParameters,
external_commit_info: Option<ExternalCommitInfo>,
consume_proposal_store: bool,
psks: Vec<(PreSharedKeyId, Secret)>,
group_info_config: GroupInfoConfig,
#[cfg(feature = "extensions-draft-08")]
app_data_dictionary_updates: Option<AppDataUpdates>,
}
#[derive(Debug)]
pub struct Complete {
result: CreateCommitResult,
original_wire_format_policy: Option<WireFormatPolicy>,
}
#[derive(Debug)]
pub struct CommitBuilder<'a, T, G: BorrowMut<MlsGroup> = &'a mut MlsGroup> {
group: G,
stage: T,
pd: PhantomData<&'a ()>,
}
impl<'a, T, G: BorrowMut<MlsGroup>> CommitBuilder<'a, T, G> {
pub(crate) fn replace_stage<NextStage>(
self,
next_stage: NextStage,
) -> (T, CommitBuilder<'a, NextStage, G>) {
self.map_stage(|prev_stage| (prev_stage, next_stage))
}
pub(crate) fn into_stage<NextStage>(
self,
next_stage: NextStage,
) -> CommitBuilder<'a, NextStage, G> {
self.replace_stage(next_stage).1
}
fn take_stage(self) -> (T, CommitBuilder<'a, (), G>) {
self.replace_stage(())
}
fn map_stage<NextStage, Aux, F: FnOnce(T) -> (Aux, NextStage)>(
self,
f: F,
) -> (Aux, CommitBuilder<'a, NextStage, G>) {
let Self {
group,
stage,
pd: PhantomData,
} = self;
let (aux, stage) = f(stage);
(
aux,
CommitBuilder {
group,
stage,
pd: PhantomData,
},
)
}
#[cfg(feature = "fork-resolution")]
pub(crate) fn stage(&self) -> &T {
&self.stage
}
}
impl MlsGroup {
pub fn commit_builder(&mut self) -> CommitBuilder<'_, Initial> {
CommitBuilder::<'_, Initial, &mut MlsGroup>::new(self)
}
}
impl<'a> CommitBuilder<'a, Initial, &mut MlsGroup> {
pub fn consume_proposal_store(mut self, consume_proposal_store: bool) -> Self {
self.stage.consume_proposal_store = consume_proposal_store;
self
}
pub fn force_self_update(mut self, force_self_update: bool) -> Self {
self.stage.force_self_update = force_self_update;
self
}
pub fn propose_adds(mut self, key_packages: impl IntoIterator<Item = KeyPackage>) -> Self {
self.stage.own_proposals.extend(
key_packages
.into_iter()
.map(|key_package| Proposal::add(AddProposal { key_package })),
);
self
}
pub fn propose_removals(mut self, removed: impl IntoIterator<Item = LeafNodeIndex>) -> Self {
self.stage.own_proposals.extend(
removed
.into_iter()
.map(|removed| Proposal::remove(RemoveProposal { removed })),
);
self
}
pub fn propose_group_context_extensions(
mut self,
extensions: Extensions<GroupContext>,
) -> Result<Self, CreateCommitError> {
let proposal = GroupContextExtensionProposal::new(extensions);
self.stage
.own_proposals
.push(Proposal::group_context_extensions(proposal));
Ok(self)
}
pub fn add_proposal(mut self, proposal: Proposal) -> Self {
self.stage.own_proposals.push(proposal);
self
}
pub fn add_proposals(mut self, proposals: impl IntoIterator<Item = Proposal>) -> Self {
self.stage.own_proposals.extend(proposals);
self
}
}
impl<'a, G: BorrowMut<MlsGroup>> CommitBuilder<'a, Initial, G> {
pub fn new(group: G) -> CommitBuilder<'a, Initial, G> {
let stage = Initial {
..Default::default()
};
CommitBuilder {
group,
stage,
pd: PhantomData,
}
}
pub fn leaf_node_parameters(mut self, leaf_node_parameters: LeafNodeParameters) -> Self {
self.stage.leaf_node_parameters = leaf_node_parameters;
self
}
pub fn load_psks<Storage: StorageProvider>(
self,
storage: &'a Storage,
) -> Result<CommitBuilder<'a, LoadedPsks, G>, CreateCommitError> {
let psk_ids: Vec<_> = self
.stage
.own_proposals
.iter()
.chain(
self.group
.borrow()
.proposal_store()
.proposals()
.map(|queued_proposal| queued_proposal.proposal()),
)
.filter_map(|proposal| match proposal {
Proposal::PreSharedKey(psk_proposal) => Some(psk_proposal.clone().into_psk_id()),
_ => None,
})
.collect();
let psks = load_psks(storage, &self.group.borrow().resumption_psk_store, &psk_ids)?
.into_iter()
.map(|(psk_id_ref, key)| (psk_id_ref.clone(), key))
.collect();
let use_ratchet_tree_extension = self
.group
.borrow()
.configuration()
.use_ratchet_tree_extension;
let group_info_config = GroupInfoConfig {
use_ratchet_tree_extension,
create_group_info: use_ratchet_tree_extension,
other_extensions: vec![],
};
Ok(self
.map_stage(|stage| {
(
(),
LoadedPsks {
own_proposals: stage.own_proposals,
psks,
force_self_update: stage.force_self_update,
leaf_node_parameters: stage.leaf_node_parameters,
consume_proposal_store: stage.consume_proposal_store,
group_info_config,
external_commit_info: stage.external_commit_info,
#[cfg(feature = "extensions-draft-08")]
app_data_dictionary_updates: None,
},
)
})
.1)
}
}
impl<'a, G: BorrowMut<MlsGroup>> CommitBuilder<'a, LoadedPsks, G> {
pub fn create_group_info(mut self, create_group_info: bool) -> Self {
self.stage.group_info_config.create_group_info = create_group_info;
self
}
pub fn use_ratchet_tree_extension(mut self, use_ratchet_tree_extension: bool) -> Self {
if use_ratchet_tree_extension {
self.stage.group_info_config.create_group_info = true;
}
self.stage.group_info_config.use_ratchet_tree_extension = use_ratchet_tree_extension;
self
}
pub fn create_group_info_with_extensions(
mut self,
extensions: impl IntoIterator<Item = Extension>,
) -> Result<Self, InvalidExtensionError> {
self.stage.group_info_config.create_group_info = true;
self.stage.group_info_config.other_extensions = extensions
.into_iter()
.map(|extension| {
if extension.as_ratchet_tree_extension().is_ok()
|| extension.as_external_pub_extension().is_ok()
{
Err(InvalidExtensionError::CannotAddDirectlyToGroupInfo)
} else {
Ok(extension)
}
})
.collect::<Result<Vec<_>, _>>()?;
Ok(self)
}
pub fn build<S: Signer>(
self,
rand: &impl OpenMlsRand,
crypto: &impl OpenMlsCrypto,
signer: &S,
f: impl FnMut(&QueuedProposal) -> bool,
) -> Result<CommitBuilder<'a, Complete, G>, CreateCommitError> {
self.build_internal(rand, crypto, signer, None::<NewSignerBundle<'_, S>>, f)
}
pub fn build_with_new_signer<S: Signer>(
self,
rand: &impl OpenMlsRand,
crypto: &impl OpenMlsCrypto,
old_signer: &impl Signer,
new_signer: NewSignerBundle<'_, S>,
f: impl FnMut(&QueuedProposal) -> bool,
) -> Result<CommitBuilder<'a, Complete, G>, CreateCommitError> {
self.build_internal(rand, crypto, old_signer, Some(new_signer), f)
}
fn build_internal<S: Signer>(
self,
rand: &impl OpenMlsRand,
crypto: &impl OpenMlsCrypto,
old_signer: &impl Signer,
new_signer: Option<NewSignerBundle<'_, S>>,
f: impl FnMut(&QueuedProposal) -> bool,
) -> Result<CommitBuilder<'a, Complete, G>, CreateCommitError> {
let (mut cur_stage, builder) = self.take_stage();
let GroupInfoConfig {
create_group_info,
use_ratchet_tree_extension,
other_extensions,
} = cur_stage.group_info_config;
let group = builder.group.borrow();
let ciphersuite = group.ciphersuite();
let own_leaf_index = group.own_leaf_index();
let (sender, is_external_commit) = match cur_stage.external_commit_info {
None => (Sender::build_member(own_leaf_index), false),
Some(_) => (Sender::NewMemberCommit, true),
};
let psks = cur_stage.psks;
let own_proposals: Vec<_> = cur_stage
.own_proposals
.into_iter()
.map(|proposal| {
QueuedProposal::from_proposal_and_sender(ciphersuite, crypto, proposal, &sender)
})
.collect::<Result<_, _>>()?;
let group_proposal_store_queue = group
.pending_proposals()
.filter(|_| cur_stage.consume_proposal_store)
.cloned();
let proposal_queue = group_proposal_store_queue.chain(own_proposals).filter(f);
let (proposal_queue, contains_own_updates) =
ProposalQueue::filter_proposals(proposal_queue, group.own_leaf_index).map_err(|e| {
match e {
ProposalQueueError::LibraryError(e) => e.into(),
ProposalQueueError::ProposalNotFound => CreateCommitError::MissingProposal,
ProposalQueueError::UpdateFromExternalSender
| ProposalQueueError::SelfRemoveFromNonMember => {
CreateCommitError::WrongProposalSenderType
}
}
})?;
group
.public_group
.validate_proposal_type_support(&proposal_queue)?;
group
.public_group
.validate_key_uniqueness(&proposal_queue, None)?;
group.public_group.validate_add_proposals(&proposal_queue)?;
group.public_group.validate_capabilities(&proposal_queue)?;
group
.public_group
.validate_remove_proposals(&proposal_queue)?;
group
.public_group
.validate_pre_shared_key_proposals(&proposal_queue)?;
group
.public_group
.validate_update_proposals(&proposal_queue, own_leaf_index)?;
group
.public_group
.validate_group_context_extensions_proposal(&proposal_queue)?;
#[cfg(feature = "extensions-draft-08")]
group
.public_group
.validate_app_data_update_proposals_and_group_context(&proposal_queue)?;
if is_external_commit {
group
.public_group
.validate_external_commit(&proposal_queue)?;
}
let proposal_reference_list = proposal_queue.commit_list();
let mut diff = group.public_group.empty_diff();
#[cfg(feature = "extensions-draft-08")]
let apply_proposals_values = diff.apply_proposals_with_app_data_updates(
&proposal_queue,
own_leaf_index,
cur_stage.app_data_dictionary_updates,
)?;
#[cfg(not(feature = "extensions-draft-08"))]
let apply_proposals_values = diff.apply_proposals(&proposal_queue, own_leaf_index)?;
if apply_proposals_values.self_removed && !is_external_commit {
return Err(CreateCommitError::CannotRemoveSelf);
}
let path_computation_result =
if apply_proposals_values.path_required
|| contains_own_updates
|| cur_stage.force_self_update
|| !cur_stage.leaf_node_parameters.is_empty()
{
let commit_type = match &cur_stage.external_commit_info {
Some(ExternalCommitInfo { credential , ..}) => {
CommitType::External(credential.clone())
}
None => CommitType::Member,
};
if let Some(new_signer) = new_signer {
if let Some(credential_with_key) =
cur_stage.leaf_node_parameters.credential_with_key()
{
if credential_with_key != &new_signer.credential_with_key {
return Err(CreateCommitError::InvalidLeafNodeParameters);
}
}
cur_stage.leaf_node_parameters.set_credential_with_key(
new_signer.credential_with_key,
);
diff.compute_path(
rand,
crypto,
own_leaf_index,
apply_proposals_values.exclusion_list(),
&commit_type,
&cur_stage.leaf_node_parameters,
new_signer.signer,
apply_proposals_values.extensions.clone()
)?
} else {
diff.compute_path(
rand,
crypto,
own_leaf_index,
apply_proposals_values.exclusion_list(),
&commit_type,
&cur_stage.leaf_node_parameters,
old_signer,
apply_proposals_values.extensions.clone()
)?
}
} else {
diff.update_group_context(crypto, apply_proposals_values.extensions.clone())?;
PathComputationResult::default()
};
let update_path_leaf_node = path_computation_result
.encrypted_path
.as_ref()
.map(|path| path.leaf_node().clone());
if let Some(ref leaf_node) = update_path_leaf_node {
if !diff
.group_context()
.extensions()
.iter()
.map(Extension::extension_type)
.all(|ext_type| leaf_node.supports_extension(&ext_type))
{
return Err(CreateCommitError::LeafNodeValidation(
LeafNodeValidationError::UnsupportedExtensions,
));
}
if let Some(required_capabilities) =
diff.group_context().extensions().required_capabilities()
{
leaf_node
.capabilities()
.supports_required_capabilities(required_capabilities)?
}
}
let commit = Commit {
proposals: proposal_reference_list,
path: path_computation_result.encrypted_path,
};
let framing_parameters =
if let Some(ExternalCommitInfo { aad, .. }) = &cur_stage.external_commit_info {
FramingParameters::new(aad, WireFormat::PublicMessage)
} else {
group.framing_parameters()
};
let mut authenticated_content = AuthenticatedContent::commit(
framing_parameters,
sender,
commit,
group.public_group.group_context(),
old_signer,
)?;
diff.update_confirmed_transcript_hash(crypto, &authenticated_content)?;
let serialized_provisional_group_context = diff
.group_context()
.tls_serialize_detached()
.map_err(LibraryError::missing_bound_check)?;
let joiner_secret = JoinerSecret::new(
crypto,
ciphersuite,
path_computation_result.commit_secret,
group.group_epoch_secrets().init_secret(),
&serialized_provisional_group_context,
)
.map_err(LibraryError::unexpected_crypto_error)?;
let psk_secret = { PskSecret::new(crypto, ciphersuite, psks)? };
let mut key_schedule = KeySchedule::init(ciphersuite, crypto, &joiner_secret, psk_secret)?;
let serialized_provisional_group_context = diff
.group_context()
.tls_serialize_detached()
.map_err(LibraryError::missing_bound_check)?;
let welcome_secret = key_schedule
.welcome(crypto, ciphersuite)
.map_err(|_| LibraryError::custom("Using the key schedule in the wrong state"))?;
key_schedule
.add_context(crypto, &serialized_provisional_group_context)
.map_err(|_| LibraryError::custom("Using the key schedule in the wrong state"))?;
let EpochSecretsResult {
epoch_secrets: provisional_epoch_secrets,
#[cfg(feature = "extensions-draft-08")]
application_exporter,
} = key_schedule
.epoch_secrets(crypto, ciphersuite)
.map_err(|_| LibraryError::custom("Using the key schedule in the wrong state"))?;
let confirmation_tag = provisional_epoch_secrets
.confirmation_key()
.tag(
crypto,
ciphersuite,
diff.group_context().confirmed_transcript_hash(),
)
.map_err(LibraryError::unexpected_crypto_error)?;
authenticated_content.set_confirmation_tag(confirmation_tag.clone());
diff.update_interim_transcript_hash(ciphersuite, crypto, confirmation_tag.clone())?;
let needs_welcome = !apply_proposals_values.invitation_list.is_empty();
let needs_group_info = needs_welcome || create_group_info;
let (welcome_option, group_info) = if !needs_group_info {
(None, None)
} else {
let mut extensions_list = vec![];
if use_ratchet_tree_extension {
extensions_list.push(Extension::RatchetTree(RatchetTreeExtension::new(
diff.export_ratchet_tree(),
)));
};
extensions_list.extend(other_extensions);
let mut extensions = Extensions::from_vec(extensions_list)?;
let welcome_option = needs_welcome
.then(|| -> Result<_, CreateCommitError> {
let group_info_tbs = {
GroupInfoTBS::new(
diff.group_context().clone(),
extensions.clone(),
confirmation_tag.clone(),
own_leaf_index,
)?
};
let group_info = group_info_tbs.sign(old_signer)?;
let (welcome_key, welcome_nonce) = welcome_secret
.derive_welcome_key_nonce(crypto, ciphersuite)
.map_err(LibraryError::unexpected_crypto_error)?;
let encrypted_group_info = welcome_key
.aead_seal(
crypto,
group_info
.tls_serialize_detached()
.map_err(LibraryError::missing_bound_check)?
.as_slice(),
&[],
&welcome_nonce,
)
.map_err(LibraryError::unexpected_crypto_error)?;
let encrypted_secrets = diff.encrypt_group_secrets(
&joiner_secret,
apply_proposals_values.invitation_list,
path_computation_result.plain_path.as_deref(),
&apply_proposals_values.presharedkeys,
&encrypted_group_info,
crypto,
own_leaf_index,
)?;
let welcome =
Welcome::new(ciphersuite, encrypted_secrets, encrypted_group_info);
Ok(welcome)
})
.transpose()?;
let exported_group_info = create_group_info
.then(|| -> Result<_, CreateCommitError> {
let external_pub = provisional_epoch_secrets
.external_secret()
.derive_external_keypair(crypto, ciphersuite)
.map_err(LibraryError::unexpected_crypto_error)?
.public;
let external_pub_extension =
Extension::ExternalPub(ExternalPubExtension::new(external_pub.into()));
extensions.add(external_pub_extension)?;
let group_info_tbs = {
GroupInfoTBS::new(
diff.group_context().clone(),
extensions,
confirmation_tag.clone(),
own_leaf_index,
)?
};
Ok(group_info_tbs.sign(old_signer)?)
})
.transpose()?;
(welcome_option, exported_group_info)
};
let (provisional_group_epoch_secrets, provisional_message_secrets) =
provisional_epoch_secrets.split_secrets(
serialized_provisional_group_context,
diff.tree_size(),
own_leaf_index,
);
#[cfg(feature = "extensions-draft-08")]
let application_export_tree = ApplicationExportTree::new(application_exporter);
let staged_commit_state = MemberStagedCommitState::new(
provisional_group_epoch_secrets,
provisional_message_secrets,
diff.into_staged_diff(crypto, ciphersuite)?,
path_computation_result.new_keypairs,
None,
update_path_leaf_node,
#[cfg(feature = "extensions-draft-08")]
application_export_tree,
);
let staged_commit = StagedCommit::new(
proposal_queue,
StagedCommitState::GroupMember(Box::new(staged_commit_state)),
);
Ok(builder.into_stage(Complete {
result: CreateCommitResult {
commit: authenticated_content,
welcome_option,
staged_commit,
group_info: group_info.filter(|_| create_group_info),
},
original_wire_format_policy: cur_stage
.external_commit_info
.as_ref()
.map(|info| info.wire_format_policy),
}))
}
#[cfg(feature = "extensions-draft-08")]
pub fn app_data_dictionary_updater(&self) -> AppDataDictionaryUpdater<'_> {
AppDataDictionaryUpdater::new(self.group.borrow().context().app_data_dict())
}
#[cfg(feature = "extensions-draft-08")]
pub fn with_app_data_dictionary_updates(
&mut self,
app_data_dictionary_updates: Option<AppDataUpdates>,
) {
self.stage.app_data_dictionary_updates = app_data_dictionary_updates;
}
#[cfg(feature = "extensions-draft-08")]
pub fn app_data_update_proposals(&self) -> impl Iterator<Item = &AppDataUpdateProposal> {
let proposal_store_proposals = self
.group
.borrow()
.proposal_store()
.proposals()
.map(|queued_proposal| queued_proposal.proposal());
let all_proposals = proposal_store_proposals.chain(self.stage.own_proposals.iter());
let mut app_data_update_proposals: Vec<&AppDataUpdateProposal> = all_proposals
.filter_map(|proposal| match proposal {
Proposal::AppDataUpdate(proposal) => Some(proposal.as_ref()),
_ => None,
})
.collect();
app_data_update_proposals.sort_by_key(|prop| prop.component_id());
app_data_update_proposals.into_iter()
}
}
impl CommitBuilder<'_, Complete, &mut MlsGroup> {
#[cfg(test)]
pub(crate) fn commit_result(self) -> CreateCommitResult {
self.stage.result
}
pub fn stage_commit<Provider: OpenMlsProvider>(
self,
provider: &Provider,
) -> Result<CommitMessageBundle, CommitBuilderStageError<Provider::StorageError>> {
let Self {
group,
stage:
Complete {
result: create_commit_result,
original_wire_format_policy: _,
},
..
} = self;
group.group_state = MlsGroupState::PendingCommit(Box::new(PendingCommitState::Member(
create_commit_result.staged_commit,
)));
provider
.storage()
.write_group_state(group.group_id(), &group.group_state)
.map_err(CommitBuilderStageError::KeyStoreError)?;
group.reset_aad();
let mls_message = group.content_to_mls_message(create_commit_result.commit, provider)?;
Ok(CommitMessageBundle {
version: group.version(),
commit: mls_message,
welcome: create_commit_result.welcome_option,
group_info: create_commit_result.group_info,
})
}
}
#[derive(Debug, Clone)]
pub struct CommitMessageBundle {
version: ProtocolVersion,
commit: MlsMessageOut,
welcome: Option<Welcome>,
group_info: Option<GroupInfo>,
}
pub struct WelcomeCommitMessages {
pub commit: MlsMessageOut,
pub welcome: MlsMessageOut,
pub group_info: Option<MlsMessageOut>,
}
impl TryFrom<CommitMessageBundle> for WelcomeCommitMessages {
type Error = LibraryError;
fn try_from(value: CommitMessageBundle) -> Result<Self, Self::Error> {
let (commit, welcome_opt, group_info) = value.into_messages();
Ok(Self {
commit,
welcome: welcome_opt.ok_or(LibraryError::custom(
"WelcomeCommitMessages must only be used with commits that produce a welcome.",
))?,
group_info,
})
}
}
#[cfg(test)]
impl CommitMessageBundle {
pub fn new(
version: ProtocolVersion,
commit: MlsMessageOut,
welcome: Option<Welcome>,
group_info: Option<GroupInfo>,
) -> Self {
Self {
version,
commit,
welcome,
group_info,
}
}
}
impl CommitMessageBundle {
pub fn commit(&self) -> &MlsMessageOut {
&self.commit
}
pub fn welcome(&self) -> Option<&Welcome> {
self.welcome.as_ref()
}
pub fn to_welcome_msg(&self) -> Option<MlsMessageOut> {
self.welcome
.as_ref()
.map(|welcome| MlsMessageOut::from_welcome(welcome.clone(), self.version))
}
pub fn group_info(&self) -> Option<&GroupInfo> {
self.group_info.as_ref()
}
pub fn contents(&self) -> (&MlsMessageOut, Option<&Welcome>, Option<&GroupInfo>) {
(
&self.commit,
self.welcome.as_ref(),
self.group_info.as_ref(),
)
}
pub fn into_commit(self) -> MlsMessageOut {
self.commit
}
pub fn into_welcome(self) -> Option<Welcome> {
self.welcome
}
pub fn into_welcome_msg(self) -> Option<MlsMessageOut> {
self.welcome
.map(|welcome| MlsMessageOut::from_welcome(welcome, self.version))
}
pub fn into_group_info(self) -> Option<GroupInfo> {
self.group_info
}
pub fn into_group_info_msg(self) -> Option<MlsMessageOut> {
self.group_info.map(|group_info| group_info.into())
}
pub fn into_contents(self) -> (MlsMessageOut, Option<Welcome>, Option<GroupInfo>) {
(self.commit, self.welcome, self.group_info)
}
pub fn into_messages(self) -> (MlsMessageOut, Option<MlsMessageOut>, Option<MlsMessageOut>) {
(
self.commit,
self.welcome
.map(|welcome| MlsMessageOut::from_welcome(welcome, self.version)),
self.group_info.map(|group_info| group_info.into()),
)
}
}
impl IntoIterator for CommitMessageBundle {
type Item = MlsMessageOut;
type IntoIter = core::iter::Chain<
core::iter::Chain<
core::option::IntoIter<MlsMessageOut>,
core::option::IntoIter<MlsMessageOut>,
>,
core::option::IntoIter<MlsMessageOut>,
>;
fn into_iter(self) -> Self::IntoIter {
let welcome = self.to_welcome_msg();
let group_info = self.group_info.map(|group_info| group_info.into());
Some(self.commit)
.into_iter()
.chain(welcome)
.chain(group_info)
}
}