1use std::{borrow::BorrowMut, marker::PhantomData};
5
6use openmls_traits::{
7 crypto::OpenMlsCrypto, random::OpenMlsRand, signatures::Signer, storage::StorageProvider as _,
8};
9use tls_codec::Serialize as _;
10
11#[cfg(feature = "extensions-draft-08")]
12use crate::schedule::application_export_tree::ApplicationExportTree;
13use crate::{
14 binary_tree::LeafNodeIndex,
15 ciphersuite::{signable::Signable as _, Secret},
16 framing::{FramingParameters, WireFormat},
17 group::{
18 diff::compute_path::{CommitType, PathComputationResult},
19 CommitBuilderStageError, CreateCommitError, Extension, Extensions, ExternalPubExtension,
20 ProposalQueue, ProposalQueueError, QueuedProposal, RatchetTreeExtension, StagedCommit,
21 WireFormatPolicy,
22 },
23 key_packages::KeyPackage,
24 messages::{
25 group_info::{GroupInfo, GroupInfoTBS},
26 Commit, Welcome,
27 },
28 prelude::{CredentialWithKey, LeafNodeParameters, LibraryError, NewSignerBundle},
29 schedule::{
30 psk::{load_psks, PskSecret},
31 EpochSecretsResult, JoinerSecret, KeySchedule, PreSharedKeyId,
32 },
33 storage::{OpenMlsProvider, StorageProvider},
34 versions::ProtocolVersion,
35};
36
37pub(crate) mod external_commits;
38
39pub use external_commits::{ExternalCommitBuilder, ExternalCommitBuilderError};
40
41#[cfg(doc)]
42use super::MlsGroupJoinConfig;
43
44use super::{
45 mls_auth_content::AuthenticatedContent,
46 staged_commit::{MemberStagedCommitState, StagedCommitState},
47 AddProposal, CreateCommitResult, GroupContextExtensionProposal, MlsGroup, MlsGroupState,
48 MlsMessageOut, PendingCommitState, Proposal, RemoveProposal, Sender,
49};
50
51#[derive(Debug)]
52struct ExternalCommitInfo {
53 aad: Vec<u8>,
54 credential: CredentialWithKey,
55 wire_format_policy: WireFormatPolicy,
56}
57
58#[derive(Debug)]
60pub struct Initial {
61 own_proposals: Vec<Proposal>,
62 force_self_update: bool,
63 leaf_node_parameters: LeafNodeParameters,
64 create_group_info: bool,
65 external_commit_info: Option<ExternalCommitInfo>,
66
67 consume_proposal_store: bool,
70}
71
72impl Default for Initial {
73 fn default() -> Self {
74 Initial {
75 consume_proposal_store: true,
76 force_self_update: false,
77 leaf_node_parameters: LeafNodeParameters::default(),
78 create_group_info: false,
79 own_proposals: vec![],
80 external_commit_info: None,
81 }
82 }
83}
84
85pub struct LoadedPsks {
87 own_proposals: Vec<Proposal>,
88 force_self_update: bool,
89 leaf_node_parameters: LeafNodeParameters,
90 create_group_info: bool,
91 external_commit_info: Option<ExternalCommitInfo>,
92
93 consume_proposal_store: bool,
96 psks: Vec<(PreSharedKeyId, Secret)>,
97}
98
99pub struct Complete {
101 result: CreateCommitResult,
102 original_wire_format_policy: Option<WireFormatPolicy>,
104}
105
106#[derive(Debug)]
144pub struct CommitBuilder<'a, T, G: BorrowMut<MlsGroup> = &'a mut MlsGroup> {
145 group: G,
148
149 stage: T,
151
152 pd: PhantomData<&'a ()>,
153}
154
155impl<'a, T, G: BorrowMut<MlsGroup>> CommitBuilder<'a, T, G> {
156 pub(crate) fn replace_stage<NextStage>(
157 self,
158 next_stage: NextStage,
159 ) -> (T, CommitBuilder<'a, NextStage, G>) {
160 self.map_stage(|prev_stage| (prev_stage, next_stage))
161 }
162
163 pub(crate) fn into_stage<NextStage>(
164 self,
165 next_stage: NextStage,
166 ) -> CommitBuilder<'a, NextStage, G> {
167 self.replace_stage(next_stage).1
168 }
169
170 fn take_stage(self) -> (T, CommitBuilder<'a, (), G>) {
171 self.replace_stage(())
172 }
173
174 fn map_stage<NextStage, Aux, F: FnOnce(T) -> (Aux, NextStage)>(
175 self,
176 f: F,
177 ) -> (Aux, CommitBuilder<'a, NextStage, G>) {
178 let Self {
179 group,
180 stage,
181 pd: PhantomData,
182 } = self;
183
184 let (aux, stage) = f(stage);
185
186 (
187 aux,
188 CommitBuilder {
189 group,
190 stage,
191 pd: PhantomData,
192 },
193 )
194 }
195
196 #[cfg(feature = "fork-resolution")]
197 pub(crate) fn stage(&self) -> &T {
198 &self.stage
199 }
200}
201
202impl MlsGroup {
203 pub fn commit_builder(&mut self) -> CommitBuilder<'_, Initial> {
205 CommitBuilder::<'_, Initial, &mut MlsGroup>::new(self)
206 }
207}
208
209impl<'a> CommitBuilder<'a, Initial, &mut MlsGroup> {
211 pub fn consume_proposal_store(mut self, consume_proposal_store: bool) -> Self {
214 self.stage.consume_proposal_store = consume_proposal_store;
215 self
216 }
217
218 pub fn force_self_update(mut self, force_self_update: bool) -> Self {
220 self.stage.force_self_update = force_self_update;
221 self
222 }
223
224 pub fn propose_adds(mut self, key_packages: impl IntoIterator<Item = KeyPackage>) -> Self {
227 self.stage.own_proposals.extend(
228 key_packages
229 .into_iter()
230 .map(|key_package| Proposal::add(AddProposal { key_package })),
231 );
232 self
233 }
234
235 pub fn propose_removals(mut self, removed: impl IntoIterator<Item = LeafNodeIndex>) -> Self {
238 self.stage.own_proposals.extend(
239 removed
240 .into_iter()
241 .map(|removed| Proposal::remove(RemoveProposal { removed })),
242 );
243 self
244 }
245
246 pub fn propose_group_context_extensions(mut self, extensions: Extensions) -> Self {
249 self.stage
250 .own_proposals
251 .push(Proposal::group_context_extensions(
252 GroupContextExtensionProposal::new(extensions),
253 ));
254 self
255 }
256
257 pub fn add_proposal(mut self, proposal: Proposal) -> Self {
260 self.stage.own_proposals.push(proposal);
261 self
262 }
263
264 pub fn add_proposals(mut self, proposals: impl IntoIterator<Item = Proposal>) -> Self {
266 self.stage.own_proposals.extend(proposals);
267 self
268 }
269}
270
271impl<'a, G: BorrowMut<MlsGroup>> CommitBuilder<'a, Initial, G> {
273 pub fn new(group: G) -> CommitBuilder<'a, Initial, G> {
275 let stage = Initial {
276 create_group_info: group.borrow().configuration().use_ratchet_tree_extension,
277 ..Default::default()
278 };
279 CommitBuilder {
280 group,
281 stage,
282 pd: PhantomData,
283 }
284 }
285
286 pub fn create_group_info(mut self, create_group_info: bool) -> Self {
289 self.stage.create_group_info = create_group_info;
290 self
291 }
292
293 pub fn leaf_node_parameters(mut self, leaf_node_parameters: LeafNodeParameters) -> Self {
296 self.stage.leaf_node_parameters = leaf_node_parameters;
297 self
298 }
299
300 pub fn load_psks<Storage: StorageProvider>(
302 self,
303 storage: &'a Storage,
304 ) -> Result<CommitBuilder<'a, LoadedPsks, G>, CreateCommitError> {
305 let psk_ids: Vec<_> = self
306 .stage
307 .own_proposals
308 .iter()
309 .chain(
310 self.group
311 .borrow()
312 .proposal_store()
313 .proposals()
314 .map(|queued_proposal| queued_proposal.proposal()),
315 )
316 .filter_map(|proposal| match proposal {
317 Proposal::PreSharedKey(psk_proposal) => Some(psk_proposal.clone().into_psk_id()),
318 _ => None,
319 })
320 .collect();
321
322 let psks = load_psks(storage, &self.group.borrow().resumption_psk_store, &psk_ids)?
324 .into_iter()
325 .map(|(psk_id_ref, key)| (psk_id_ref.clone(), key))
326 .collect();
327
328 Ok(self
329 .map_stage(|stage| {
330 (
331 (),
332 LoadedPsks {
333 own_proposals: stage.own_proposals,
334 psks,
335 force_self_update: stage.force_self_update,
336 leaf_node_parameters: stage.leaf_node_parameters,
337 consume_proposal_store: stage.consume_proposal_store,
338 create_group_info: stage.create_group_info,
339 external_commit_info: stage.external_commit_info,
340 },
341 )
342 })
343 .1)
344 }
345}
346
347impl<'a, G: BorrowMut<MlsGroup>> CommitBuilder<'a, LoadedPsks, G> {
348 pub fn build<S: Signer>(
352 self,
353 rand: &impl OpenMlsRand,
354 crypto: &impl OpenMlsCrypto,
355 signer: &S,
356 f: impl FnMut(&QueuedProposal) -> bool,
357 ) -> Result<CommitBuilder<'a, Complete, G>, CreateCommitError> {
358 self.build_internal(rand, crypto, signer, None::<NewSignerBundle<'_, S>>, f)
359 }
360
361 pub fn build_with_new_signer<S: Signer>(
369 self,
370 rand: &impl OpenMlsRand,
371 crypto: &impl OpenMlsCrypto,
372 old_signer: &impl Signer,
373 new_signer: NewSignerBundle<'_, S>,
374 f: impl FnMut(&QueuedProposal) -> bool,
375 ) -> Result<CommitBuilder<'a, Complete, G>, CreateCommitError> {
376 self.build_internal(rand, crypto, old_signer, Some(new_signer), f)
377 }
378
379 fn build_internal<S: Signer>(
380 self,
381 rand: &impl OpenMlsRand,
382 crypto: &impl OpenMlsCrypto,
383 old_signer: &impl Signer,
384 new_signer: Option<NewSignerBundle<'_, S>>,
385 f: impl FnMut(&QueuedProposal) -> bool,
386 ) -> Result<CommitBuilder<'a, Complete, G>, CreateCommitError> {
387 let (mut cur_stage, builder) = self.take_stage();
388 let group = builder.group.borrow();
389 let ciphersuite = group.ciphersuite();
390 let own_leaf_index = group.own_leaf_index();
391 let (sender, is_external_commit) = match cur_stage.external_commit_info {
392 None => (Sender::build_member(own_leaf_index), false),
393 Some(_) => (Sender::NewMemberCommit, true),
394 };
395 let psks = cur_stage.psks;
396
397 let own_proposals: Vec<_> = cur_stage
400 .own_proposals
401 .into_iter()
402 .map(|proposal| {
403 QueuedProposal::from_proposal_and_sender(ciphersuite, crypto, proposal, &sender)
404 })
405 .collect::<Result<_, _>>()?;
406
407 let group_proposal_store_queue = group
410 .pending_proposals()
411 .filter(|_| cur_stage.consume_proposal_store)
412 .cloned();
413
414 let proposal_queue = group_proposal_store_queue.chain(own_proposals).filter(f);
418
419 let (proposal_queue, contains_own_updates) =
420 ProposalQueue::filter_proposals(proposal_queue, group.own_leaf_index).map_err(|e| {
421 match e {
422 ProposalQueueError::LibraryError(e) => e.into(),
423 ProposalQueueError::ProposalNotFound => CreateCommitError::MissingProposal,
424 ProposalQueueError::UpdateFromExternalSender
425 | ProposalQueueError::SelfRemoveFromNonMember => {
426 CreateCommitError::WrongProposalSenderType
427 }
428 }
429 })?;
430
431 group
436 .public_group
437 .validate_proposal_type_support(&proposal_queue)?;
438 group
443 .public_group
444 .validate_key_uniqueness(&proposal_queue, None)?;
445 group.public_group.validate_add_proposals(&proposal_queue)?;
447 group.public_group.validate_capabilities(&proposal_queue)?;
450 group
453 .public_group
454 .validate_remove_proposals(&proposal_queue)?;
455 group
456 .public_group
457 .validate_pre_shared_key_proposals(&proposal_queue)?;
458 group
463 .public_group
464 .validate_update_proposals(&proposal_queue, own_leaf_index)?;
465
466 group
469 .public_group
470 .validate_group_context_extensions_proposal(&proposal_queue)?;
471
472 if is_external_commit {
473 group
474 .public_group
475 .validate_external_commit(&proposal_queue)?;
476 }
477
478 let proposal_reference_list = proposal_queue.commit_list();
479
480 let mut diff = group.public_group.empty_diff();
482
483 let apply_proposals_values = diff.apply_proposals(&proposal_queue, own_leaf_index)?;
485 if apply_proposals_values.self_removed && !is_external_commit {
486 return Err(CreateCommitError::CannotRemoveSelf);
487 }
488
489 let path_computation_result =
490 if apply_proposals_values.path_required
492 || contains_own_updates
493 || cur_stage.force_self_update
494 || !cur_stage.leaf_node_parameters.is_empty()
495 {
496 let commit_type = match &cur_stage.external_commit_info {
497 Some(ExternalCommitInfo { credential , ..}) => {
498 CommitType::External(credential.clone())
499 }
500 None => CommitType::Member,
501 };
502 if let Some(new_signer) = new_signer {
506 if let Some(credential_with_key) =
507 cur_stage.leaf_node_parameters.credential_with_key()
508 {
509 if credential_with_key != &new_signer.credential_with_key {
510 return Err(CreateCommitError::InvalidLeafNodeParameters);
511 }
512 }
513 cur_stage.leaf_node_parameters.set_credential_with_key(
514 new_signer.credential_with_key,
515 );
516 diff.compute_path(
517 rand,
518 crypto,
519 own_leaf_index,
520 apply_proposals_values.exclusion_list(),
521 &commit_type,
522 &cur_stage.leaf_node_parameters,
523 new_signer.signer,
524 apply_proposals_values.extensions.clone()
525 )?
526 } else {
527 diff.compute_path(
528 rand,
529 crypto,
530 own_leaf_index,
531 apply_proposals_values.exclusion_list(),
532 &commit_type,
533 &cur_stage.leaf_node_parameters,
534 old_signer,
535 apply_proposals_values.extensions.clone()
536 )?
537 }
538 } else {
539 diff.update_group_context(crypto, apply_proposals_values.extensions.clone())?;
542 PathComputationResult::default()
543 };
544
545 let update_path_leaf_node = path_computation_result
546 .encrypted_path
547 .as_ref()
548 .map(|path| path.leaf_node().clone());
549
550 let commit = Commit {
552 proposals: proposal_reference_list,
553 path: path_computation_result.encrypted_path,
554 };
555
556 let framing_parameters =
557 if let Some(ExternalCommitInfo { aad, .. }) = &cur_stage.external_commit_info {
558 FramingParameters::new(aad, WireFormat::PublicMessage)
559 } else {
560 group.framing_parameters()
561 };
562
563 let mut authenticated_content = AuthenticatedContent::commit(
565 framing_parameters,
566 sender,
567 commit,
568 group.public_group.group_context(),
569 old_signer,
570 )?;
571
572 diff.update_confirmed_transcript_hash(crypto, &authenticated_content)?;
574
575 let serialized_provisional_group_context = diff
576 .group_context()
577 .tls_serialize_detached()
578 .map_err(LibraryError::missing_bound_check)?;
579
580 let joiner_secret = JoinerSecret::new(
581 crypto,
582 ciphersuite,
583 path_computation_result.commit_secret,
584 group.group_epoch_secrets().init_secret(),
585 &serialized_provisional_group_context,
586 )
587 .map_err(LibraryError::unexpected_crypto_error)?;
588
589 let psk_secret = { PskSecret::new(crypto, ciphersuite, psks)? };
591
592 let mut key_schedule = KeySchedule::init(ciphersuite, crypto, &joiner_secret, psk_secret)?;
594
595 let serialized_provisional_group_context = diff
596 .group_context()
597 .tls_serialize_detached()
598 .map_err(LibraryError::missing_bound_check)?;
599
600 let welcome_secret = key_schedule
601 .welcome(crypto, ciphersuite)
602 .map_err(|_| LibraryError::custom("Using the key schedule in the wrong state"))?;
603 key_schedule
604 .add_context(crypto, &serialized_provisional_group_context)
605 .map_err(|_| LibraryError::custom("Using the key schedule in the wrong state"))?;
606 let EpochSecretsResult {
607 epoch_secrets: provisional_epoch_secrets,
608 #[cfg(feature = "extensions-draft-08")]
609 application_exporter,
610 } = key_schedule
611 .epoch_secrets(crypto, ciphersuite)
612 .map_err(|_| LibraryError::custom("Using the key schedule in the wrong state"))?;
613
614 let confirmation_tag = provisional_epoch_secrets
616 .confirmation_key()
617 .tag(
618 crypto,
619 ciphersuite,
620 diff.group_context().confirmed_transcript_hash(),
621 )
622 .map_err(LibraryError::unexpected_crypto_error)?;
623
624 authenticated_content.set_confirmation_tag(confirmation_tag.clone());
626
627 diff.update_interim_transcript_hash(ciphersuite, crypto, confirmation_tag.clone())?;
628
629 let needs_welcome = !apply_proposals_values.invitation_list.is_empty();
631
632 let needs_group_info = needs_welcome || cur_stage.create_group_info;
636
637 let (welcome_option, group_info) = if !needs_group_info {
638 (None, None)
639 } else {
640 let mut extensions = Extensions::empty();
642 if group.configuration().use_ratchet_tree_extension {
643 extensions.add(Extension::RatchetTree(RatchetTreeExtension::new(
644 diff.export_ratchet_tree(),
645 )))?;
646 };
647
648 let welcome_option = needs_welcome
649 .then(|| -> Result<_, CreateCommitError> {
650 let group_info_tbs = {
651 GroupInfoTBS::new(
652 diff.group_context().clone(),
653 extensions.clone(),
654 confirmation_tag.clone(),
655 own_leaf_index,
656 )
657 };
658 let group_info = group_info_tbs.sign(old_signer)?;
660
661 let (welcome_key, welcome_nonce) = welcome_secret
663 .derive_welcome_key_nonce(crypto, ciphersuite)
664 .map_err(LibraryError::unexpected_crypto_error)?;
665 let encrypted_group_info = welcome_key
666 .aead_seal(
667 crypto,
668 group_info
669 .tls_serialize_detached()
670 .map_err(LibraryError::missing_bound_check)?
671 .as_slice(),
672 &[],
673 &welcome_nonce,
674 )
675 .map_err(LibraryError::unexpected_crypto_error)?;
676
677 let encrypted_secrets = diff.encrypt_group_secrets(
680 &joiner_secret,
681 apply_proposals_values.invitation_list,
682 path_computation_result.plain_path.as_deref(),
683 &apply_proposals_values.presharedkeys,
684 &encrypted_group_info,
685 crypto,
686 own_leaf_index,
687 )?;
688
689 let welcome =
691 Welcome::new(ciphersuite, encrypted_secrets, encrypted_group_info);
692 Ok(welcome)
693 })
694 .transpose()?;
695
696 let exported_group_info = cur_stage
699 .create_group_info
700 .then(|| -> Result<_, CreateCommitError> {
701 let external_pub = provisional_epoch_secrets
702 .external_secret()
703 .derive_external_keypair(crypto, ciphersuite)
704 .map_err(LibraryError::unexpected_crypto_error)?
705 .public;
706 let external_pub_extension =
707 Extension::ExternalPub(ExternalPubExtension::new(external_pub.into()));
708 extensions.add(external_pub_extension)?;
709 let group_info_tbs = {
710 GroupInfoTBS::new(
711 diff.group_context().clone(),
712 extensions,
713 confirmation_tag.clone(),
714 own_leaf_index,
715 )
716 };
717 Ok(group_info_tbs.sign(old_signer)?)
719 })
720 .transpose()?;
721
722 (welcome_option, exported_group_info)
723 };
724
725 let (provisional_group_epoch_secrets, provisional_message_secrets) =
726 provisional_epoch_secrets.split_secrets(
727 serialized_provisional_group_context,
728 diff.tree_size(),
729 own_leaf_index,
730 );
731
732 #[cfg(feature = "extensions-draft-08")]
733 let application_export_tree = ApplicationExportTree::new(application_exporter);
734 let staged_commit_state = MemberStagedCommitState::new(
735 provisional_group_epoch_secrets,
736 provisional_message_secrets,
737 diff.into_staged_diff(crypto, ciphersuite)?,
738 path_computation_result.new_keypairs,
739 None,
742 update_path_leaf_node,
743 #[cfg(feature = "extensions-draft-08")]
744 application_export_tree,
745 );
746 let staged_commit = StagedCommit::new(
747 proposal_queue,
748 StagedCommitState::GroupMember(Box::new(staged_commit_state)),
749 );
750
751 Ok(builder.into_stage(Complete {
752 result: CreateCommitResult {
753 commit: authenticated_content,
754 welcome_option,
755 staged_commit,
756 group_info: group_info.filter(|_| cur_stage.create_group_info),
757 },
758 original_wire_format_policy: cur_stage
759 .external_commit_info
760 .as_ref()
761 .map(|info| info.wire_format_policy),
762 }))
763 }
764}
765
766impl CommitBuilder<'_, Complete, &mut MlsGroup> {
768 #[cfg(test)]
769 pub(crate) fn commit_result(self) -> CreateCommitResult {
770 self.stage.result
771 }
772
773 pub fn stage_commit<Provider: OpenMlsProvider>(
775 self,
776 provider: &Provider,
777 ) -> Result<CommitMessageBundle, CommitBuilderStageError<Provider::StorageError>> {
778 let Self {
779 group,
780 stage:
781 Complete {
782 result: create_commit_result,
783 original_wire_format_policy: _,
784 },
785 ..
786 } = self;
787
788 group.group_state = MlsGroupState::PendingCommit(Box::new(PendingCommitState::Member(
791 create_commit_result.staged_commit,
792 )));
793
794 provider
795 .storage()
796 .write_group_state(group.group_id(), &group.group_state)
797 .map_err(CommitBuilderStageError::KeyStoreError)?;
798
799 group.reset_aad();
800
801 let mls_message = group.content_to_mls_message(create_commit_result.commit, provider)?;
807
808 Ok(CommitMessageBundle {
809 version: group.version(),
810 commit: mls_message,
811 welcome: create_commit_result.welcome_option,
812 group_info: create_commit_result.group_info,
813 })
814 }
815}
816
817#[derive(Debug, Clone)]
820pub struct CommitMessageBundle {
821 version: ProtocolVersion,
822 commit: MlsMessageOut,
823 welcome: Option<Welcome>,
824 group_info: Option<GroupInfo>,
825}
826
827pub struct WelcomeCommitMessages {
832 pub commit: MlsMessageOut,
834
835 pub welcome: MlsMessageOut,
837
838 pub group_info: Option<MlsMessageOut>,
840}
841
842impl TryFrom<CommitMessageBundle> for WelcomeCommitMessages {
843 type Error = LibraryError;
844
845 fn try_from(value: CommitMessageBundle) -> Result<Self, Self::Error> {
846 let (commit, welcome_opt, group_info) = value.into_messages();
847 Ok(Self {
848 commit,
849 welcome: welcome_opt.ok_or(LibraryError::custom(
850 "WelcomeCommitMessages must only be used with commits that produce a welcome.",
851 ))?,
852 group_info,
853 })
854 }
855}
856
857#[cfg(test)]
858impl CommitMessageBundle {
859 pub fn new(
860 version: ProtocolVersion,
861 commit: MlsMessageOut,
862 welcome: Option<Welcome>,
863 group_info: Option<GroupInfo>,
864 ) -> Self {
865 Self {
866 version,
867 commit,
868 welcome,
869 group_info,
870 }
871 }
872}
873
874impl CommitMessageBundle {
875 pub fn commit(&self) -> &MlsMessageOut {
879 &self.commit
880 }
881
882 pub fn welcome(&self) -> Option<&Welcome> {
885 self.welcome.as_ref()
886 }
887
888 pub fn to_welcome_msg(&self) -> Option<MlsMessageOut> {
891 self.welcome
892 .as_ref()
893 .map(|welcome| MlsMessageOut::from_welcome(welcome.clone(), self.version))
894 }
895
896 pub fn group_info(&self) -> Option<&GroupInfo> {
900 self.group_info.as_ref()
901 }
902
903 pub fn contents(&self) -> (&MlsMessageOut, Option<&Welcome>, Option<&GroupInfo>) {
906 (
907 &self.commit,
908 self.welcome.as_ref(),
909 self.group_info.as_ref(),
910 )
911 }
912
913 pub fn into_commit(self) -> MlsMessageOut {
917 self.commit
918 }
919
920 pub fn into_welcome(self) -> Option<Welcome> {
924 self.welcome
925 }
926
927 pub fn into_welcome_msg(self) -> Option<MlsMessageOut> {
930 self.welcome
931 .map(|welcome| MlsMessageOut::from_welcome(welcome, self.version))
932 }
933
934 pub fn into_group_info(self) -> Option<GroupInfo> {
939 self.group_info
940 }
941
942 pub fn into_group_info_msg(self) -> Option<MlsMessageOut> {
944 self.group_info.map(|group_info| group_info.into())
945 }
946
947 pub fn into_contents(self) -> (MlsMessageOut, Option<Welcome>, Option<GroupInfo>) {
950 (self.commit, self.welcome, self.group_info)
951 }
952
953 pub fn into_messages(self) -> (MlsMessageOut, Option<MlsMessageOut>, Option<MlsMessageOut>) {
956 (
957 self.commit,
958 self.welcome
959 .map(|welcome| MlsMessageOut::from_welcome(welcome, self.version)),
960 self.group_info.map(|group_info| group_info.into()),
961 )
962 }
963}
964
965impl IntoIterator for CommitMessageBundle {
966 type Item = MlsMessageOut;
967
968 type IntoIter = core::iter::Chain<
969 core::iter::Chain<
970 core::option::IntoIter<MlsMessageOut>,
971 core::option::IntoIter<MlsMessageOut>,
972 >,
973 core::option::IntoIter<MlsMessageOut>,
974 >;
975
976 fn into_iter(self) -> Self::IntoIter {
977 let welcome = self.to_welcome_msg();
978 let group_info = self.group_info.map(|group_info| group_info.into());
979
980 Some(self.commit)
981 .into_iter()
982 .chain(welcome)
983 .chain(group_info)
984 }
985}