1use past_secrets::MessageSecretsStore;
7use proposal_store::ProposalQueue;
8use serde::{Deserialize, Serialize};
9use tls_codec::Serialize as _;
10
11#[cfg(test)]
12use crate::treesync::node::leaf_node::TreePosition;
13
14use super::proposal_store::{ProposalStore, QueuedProposal};
15use crate::{
16 binary_tree::array_representation::LeafNodeIndex,
17 ciphersuite::{hash_ref::ProposalRef, signable::Signable},
18 credentials::Credential,
19 error::LibraryError,
20 framing::{mls_auth_content::AuthenticatedContent, *},
21 group::{
22 CreateGroupContextExtProposalError, Extension, ExtensionType, Extensions,
23 ExternalPubExtension, GroupContext, GroupEpoch, GroupId, MlsGroupJoinConfig,
24 MlsGroupStateError, OutgoingWireFormatPolicy, PublicGroup, RatchetTreeExtension,
25 RequiredCapabilitiesExtension, StagedCommit,
26 },
27 key_packages::KeyPackageBundle,
28 messages::{
29 group_info::{GroupInfo, GroupInfoTBS, VerifiableGroupInfo},
30 proposals::*,
31 ConfirmationTag, GroupSecrets, Welcome,
32 },
33 schedule::{
34 message_secrets::MessageSecrets,
35 psk::{load_psks, store::ResumptionPskStore, PskSecret},
36 GroupEpochSecrets, JoinerSecret, KeySchedule,
37 },
38 storage::{OpenMlsProvider, StorageProvider},
39 treesync::{
40 node::{encryption_keys::EncryptionKeyPair, leaf_node::LeafNode},
41 RatchetTree,
42 },
43 versions::ProtocolVersion,
44};
45use openmls_traits::{signatures::Signer, storage::StorageProvider as _, types::Ciphersuite};
46
47#[cfg(feature = "extensions-draft-08")]
48use crate::schedule::{application_export_tree::ApplicationExportTree, ApplicationExportSecret};
49
50mod application;
52mod exporting;
53mod updates;
54
55use config::*;
56
57pub(crate) mod builder;
59pub(crate) mod commit_builder;
60pub(crate) mod config;
61pub(crate) mod creation;
62pub(crate) mod errors;
63pub(crate) mod membership;
64pub(crate) mod past_secrets;
65pub(crate) mod processing;
66pub(crate) mod proposal;
67pub(crate) mod proposal_store;
68pub(crate) mod staged_commit;
69
70#[cfg(test)]
72pub(crate) mod tests_and_kats;
73
74#[derive(Debug)]
75pub(crate) struct CreateCommitResult {
76 pub(crate) commit: AuthenticatedContent,
77 pub(crate) welcome_option: Option<Welcome>,
78 pub(crate) staged_commit: StagedCommit,
79 pub(crate) group_info: Option<GroupInfo>,
80}
81
82#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
84pub struct Member {
85 pub index: LeafNodeIndex,
87 pub credential: Credential,
89 pub encryption_key: Vec<u8>,
91 pub signature_key: Vec<u8>,
93}
94
95impl Member {
96 pub fn new(
98 index: LeafNodeIndex,
99 encryption_key: Vec<u8>,
100 signature_key: Vec<u8>,
101 credential: Credential,
102 ) -> Self {
103 Self {
104 index,
105 encryption_key,
106 signature_key,
107 credential,
108 }
109 }
110}
111
112#[derive(Debug, Serialize, Deserialize)]
115#[cfg_attr(any(test, feature = "test-utils"), derive(Clone, PartialEq))]
116pub enum PendingCommitState {
117 Member(StagedCommit),
119 External(StagedCommit),
121}
122
123impl PendingCommitState {
124 pub(crate) fn staged_commit(&self) -> &StagedCommit {
127 match self {
128 PendingCommitState::Member(pc) => pc,
129 PendingCommitState::External(pc) => pc,
130 }
131 }
132}
133
134impl From<PendingCommitState> for StagedCommit {
135 fn from(pcs: PendingCommitState) -> Self {
136 match pcs {
137 PendingCommitState::Member(pc) => pc,
138 PendingCommitState::External(pc) => pc,
139 }
140 }
141}
142
143#[derive(Debug, Serialize, Deserialize)]
188#[cfg_attr(any(test, feature = "test-utils"), derive(Clone, PartialEq))]
189pub enum MlsGroupState {
190 PendingCommit(Box<PendingCommitState>),
192 Operational,
194 Inactive,
196}
197
198#[derive(Debug)]
223#[cfg_attr(feature = "test-utils", derive(Clone, PartialEq))]
224pub struct MlsGroup {
225 mls_group_config: MlsGroupJoinConfig,
227 public_group: PublicGroup,
229 group_epoch_secrets: GroupEpochSecrets,
231 own_leaf_index: LeafNodeIndex,
233 message_secrets_store: MessageSecretsStore,
240 resumption_psk_store: ResumptionPskStore,
242 own_leaf_nodes: Vec<LeafNode>,
246 aad: Vec<u8>,
250 group_state: MlsGroupState,
253 #[cfg(feature = "extensions-draft-08")]
257 application_export_tree: Option<ApplicationExportTree>,
258}
259
260impl MlsGroup {
261 pub fn configuration(&self) -> &MlsGroupJoinConfig {
265 &self.mls_group_config
266 }
267
268 pub fn set_configuration<Storage: StorageProvider>(
270 &mut self,
271 storage: &Storage,
272 mls_group_config: &MlsGroupJoinConfig,
273 ) -> Result<(), Storage::Error> {
274 self.mls_group_config = mls_group_config.clone();
275 storage.write_mls_join_config(self.group_id(), mls_group_config)
276 }
277
278 pub fn set_aad(&mut self, aad: Vec<u8>) {
282 self.aad = aad;
283 }
284
285 pub fn aad(&self) -> &[u8] {
288 &self.aad
289 }
290
291 pub fn ciphersuite(&self) -> Ciphersuite {
295 self.public_group.ciphersuite()
296 }
297
298 pub fn confirmation_tag(&self) -> &ConfirmationTag {
300 self.public_group.confirmation_tag()
301 }
302
303 pub fn is_active(&self) -> bool {
306 !matches!(self.group_state, MlsGroupState::Inactive)
307 }
308
309 pub fn credential(&self) -> Result<&Credential, MlsGroupStateError> {
312 if !self.is_active() {
313 return Err(MlsGroupStateError::UseAfterEviction);
314 }
315 self.public_group
316 .leaf(self.own_leaf_index())
317 .map(|node| node.credential())
318 .ok_or_else(|| LibraryError::custom("Own leaf node missing").into())
319 }
320
321 pub fn own_leaf_index(&self) -> LeafNodeIndex {
323 self.own_leaf_index
324 }
325
326 pub fn own_leaf_node(&self) -> Option<&LeafNode> {
328 self.public_group().leaf(self.own_leaf_index())
329 }
330
331 pub fn group_id(&self) -> &GroupId {
333 self.public_group.group_id()
334 }
335
336 pub fn epoch(&self) -> GroupEpoch {
338 self.public_group.group_context().epoch()
339 }
340
341 pub fn pending_proposals(&self) -> impl Iterator<Item = &QueuedProposal> {
343 self.proposal_store().proposals()
344 }
345
346 pub fn pending_commit(&self) -> Option<&StagedCommit> {
350 match self.group_state {
351 MlsGroupState::PendingCommit(ref pending_commit_state) => {
352 Some(pending_commit_state.staged_commit())
353 }
354 MlsGroupState::Operational => None,
355 MlsGroupState::Inactive => None,
356 }
357 }
358
359 pub fn clear_pending_commit<Storage: StorageProvider>(
371 &mut self,
372 storage: &Storage,
373 ) -> Result<(), Storage::Error> {
374 match self.group_state {
375 MlsGroupState::PendingCommit(ref pending_commit_state) => {
376 if let PendingCommitState::Member(_) = **pending_commit_state {
377 self.group_state = MlsGroupState::Operational;
378 storage.write_group_state(self.group_id(), &self.group_state)
379 } else {
380 Ok(())
381 }
382 }
383 MlsGroupState::Operational | MlsGroupState::Inactive => Ok(()),
384 }
385 }
386
387 pub fn clear_pending_proposals<Storage: StorageProvider>(
394 &mut self,
395 storage: &Storage,
396 ) -> Result<(), Storage::Error> {
397 if !self.proposal_store().is_empty() {
399 self.proposal_store_mut().empty();
401
402 storage.clear_proposal_queue::<GroupId, ProposalRef>(self.group_id())?;
404 }
405
406 Ok(())
407 }
408
409 pub fn extensions(&self) -> &Extensions {
411 self.public_group().group_context().extensions()
412 }
413
414 pub fn ext_commit_sender_index(
416 &self,
417 commit: &StagedCommit,
418 ) -> Result<LeafNodeIndex, LibraryError> {
419 self.public_group().ext_commit_sender_index(commit)
420 }
421
422 pub fn load<Storage: crate::storage::StorageProvider>(
426 storage: &Storage,
427 group_id: &GroupId,
428 ) -> Result<Option<MlsGroup>, Storage::Error> {
429 let public_group = PublicGroup::load(storage, group_id)?;
430 let group_epoch_secrets = storage.group_epoch_secrets(group_id)?;
431 let own_leaf_index = storage.own_leaf_index(group_id)?;
432 let message_secrets_store = storage.message_secrets(group_id)?;
433 let resumption_psk_store = storage.resumption_psk_store(group_id)?;
434 let mls_group_config = storage.mls_group_join_config(group_id)?;
435 let own_leaf_nodes = storage.own_leaf_nodes(group_id)?;
436 let group_state = storage.group_state(group_id)?;
437 #[cfg(feature = "extensions-draft-08")]
438 let application_export_tree = storage.application_export_tree(group_id)?;
439
440 let build = || -> Option<Self> {
441 Some(Self {
442 public_group: public_group?,
443 group_epoch_secrets: group_epoch_secrets?,
444 own_leaf_index: own_leaf_index?,
445 message_secrets_store: message_secrets_store?,
446 resumption_psk_store: resumption_psk_store?,
447 mls_group_config: mls_group_config?,
448 own_leaf_nodes,
449 aad: vec![],
450 group_state: group_state?,
451 #[cfg(feature = "extensions-draft-08")]
452 application_export_tree,
453 })
454 };
455
456 Ok(build())
457 }
458
459 pub fn delete<Storage: crate::storage::StorageProvider>(
463 &mut self,
464 storage: &Storage,
465 ) -> Result<(), Storage::Error> {
466 PublicGroup::delete(storage, self.group_id())?;
467 storage.delete_own_leaf_index(self.group_id())?;
468 storage.delete_group_epoch_secrets(self.group_id())?;
469 storage.delete_message_secrets(self.group_id())?;
470 storage.delete_all_resumption_psk_secrets(self.group_id())?;
471 storage.delete_group_config(self.group_id())?;
472 storage.delete_own_leaf_nodes(self.group_id())?;
473 storage.delete_group_state(self.group_id())?;
474 storage.clear_proposal_queue::<GroupId, ProposalRef>(self.group_id())?;
475
476 #[cfg(feature = "extensions-draft-08")]
477 storage.delete_application_export_tree::<_, ApplicationExportTree>(self.group_id())?;
478
479 self.proposal_store_mut().empty();
480 storage.delete_encryption_epoch_key_pairs(
481 self.group_id(),
482 &self.epoch(),
483 self.own_leaf_index().u32(),
484 )?;
485
486 Ok(())
487 }
488
489 pub fn export_ratchet_tree(&self) -> RatchetTree {
493 self.public_group().export_ratchet_tree()
494 }
495}
496
497impl MlsGroup {
499 pub(crate) fn required_capabilities(&self) -> Option<&RequiredCapabilitiesExtension> {
501 self.public_group.required_capabilities()
502 }
503
504 pub(crate) fn group_epoch_secrets(&self) -> &GroupEpochSecrets {
506 &self.group_epoch_secrets
507 }
508
509 pub(crate) fn message_secrets(&self) -> &MessageSecrets {
511 self.message_secrets_store.message_secrets()
512 }
513
514 pub(crate) fn set_max_past_epochs(&mut self, max_past_epochs: usize) {
518 self.message_secrets_store.resize(max_past_epochs);
519 }
520
521 pub(crate) fn message_secrets_mut(
523 &mut self,
524 epoch: GroupEpoch,
525 ) -> Result<&mut MessageSecrets, SecretTreeError> {
526 if epoch < self.context().epoch() {
527 self.message_secrets_store
528 .secrets_for_epoch_mut(epoch)
529 .ok_or(SecretTreeError::TooDistantInThePast)
530 } else {
531 Ok(self.message_secrets_store.message_secrets_mut())
532 }
533 }
534
535 pub(crate) fn message_secrets_for_epoch(
537 &self,
538 epoch: GroupEpoch,
539 ) -> Result<&MessageSecrets, SecretTreeError> {
540 if epoch < self.context().epoch() {
541 self.message_secrets_store
542 .secrets_for_epoch(epoch)
543 .ok_or(SecretTreeError::TooDistantInThePast)
544 } else {
545 Ok(self.message_secrets_store.message_secrets())
546 }
547 }
548
549 pub(crate) fn message_secrets_and_leaves_mut(
555 &mut self,
556 epoch: GroupEpoch,
557 ) -> Result<(&mut MessageSecrets, &[Member]), SecretTreeError> {
558 if epoch < self.context().epoch() {
559 self.message_secrets_store
560 .secrets_and_leaves_for_epoch_mut(epoch)
561 .ok_or(SecretTreeError::TooDistantInThePast)
562 } else {
563 Ok((self.message_secrets_store.message_secrets_mut(), &[]))
566 }
567 }
568
569 pub(crate) fn create_group_context_ext_proposal<Provider: OpenMlsProvider>(
571 &self,
572 framing_parameters: FramingParameters,
573 extensions: Extensions,
574 signer: &impl Signer,
575 ) -> Result<AuthenticatedContent, CreateGroupContextExtProposalError<Provider::StorageError>>
576 {
577 let required_extension = extensions
579 .iter()
580 .find(|extension| extension.extension_type() == ExtensionType::RequiredCapabilities);
581 if let Some(required_extension) = required_extension {
582 let required_capabilities = required_extension.as_required_capabilities_extension()?;
583 self.own_leaf_node()
585 .ok_or_else(|| LibraryError::custom("Tree has no own leaf."))?
586 .capabilities()
587 .supports_required_capabilities(required_capabilities)?;
588
589 self.public_group()
592 .check_extension_support(required_capabilities.extension_types())?;
593 }
594 let proposal = GroupContextExtensionProposal::new(extensions);
595 let proposal = Proposal::GroupContextExtensions(Box::new(proposal));
596 AuthenticatedContent::member_proposal(
597 framing_parameters,
598 self.own_leaf_index(),
599 proposal,
600 self.context(),
601 signer,
602 )
603 .map_err(|e| e.into())
604 }
605
606 pub(crate) fn encrypt<Provider: OpenMlsProvider>(
608 &mut self,
609 public_message: AuthenticatedContent,
610 provider: &Provider,
611 ) -> Result<PrivateMessage, MessageEncryptionError<Provider::StorageError>> {
612 let padding_size = self.configuration().padding_size();
613 let msg = PrivateMessage::try_from_authenticated_content(
614 provider.crypto(),
615 provider.rand(),
616 &public_message,
617 self.ciphersuite(),
618 self.message_secrets_store.message_secrets_mut(),
619 padding_size,
620 )?;
621
622 provider
623 .storage()
624 .write_message_secrets(self.group_id(), &self.message_secrets_store)
625 .map_err(MessageEncryptionError::StorageError)?;
626
627 Ok(msg)
628 }
629
630 pub(crate) fn framing_parameters(&self) -> FramingParameters<'_> {
632 FramingParameters::new(
633 &self.aad,
634 self.mls_group_config.wire_format_policy().outgoing(),
635 )
636 }
637
638 pub(crate) fn proposal_store(&self) -> &ProposalStore {
640 self.public_group.proposal_store()
641 }
642
643 pub(crate) fn proposal_store_mut(&mut self) -> &mut ProposalStore {
645 self.public_group.proposal_store_mut()
646 }
647
648 pub(crate) fn context(&self) -> &GroupContext {
650 self.public_group.group_context()
651 }
652
653 pub(crate) fn version(&self) -> ProtocolVersion {
655 self.public_group.version()
656 }
657
658 #[inline]
660 pub(crate) fn reset_aad(&mut self) {
661 self.aad.clear();
662 }
663
664 pub(crate) fn public_group(&self) -> &PublicGroup {
666 &self.public_group
667 }
668}
669
670impl MlsGroup {
672 pub(super) fn store_epoch_keypairs<Storage: StorageProvider>(
677 &self,
678 store: &Storage,
679 keypair_references: &[EncryptionKeyPair],
680 ) -> Result<(), Storage::Error> {
681 store.write_encryption_epoch_key_pairs(
682 self.group_id(),
683 &self.context().epoch(),
684 self.own_leaf_index().u32(),
685 keypair_references,
686 )
687 }
688
689 pub(super) fn read_epoch_keypairs<Storage: StorageProvider>(
694 &self,
695 store: &Storage,
696 ) -> Result<Vec<EncryptionKeyPair>, Storage::Error> {
697 store.encryption_epoch_key_pairs(
698 self.group_id(),
699 &self.context().epoch(),
700 self.own_leaf_index().u32(),
701 )
702 }
703
704 pub(super) fn delete_previous_epoch_keypairs<Storage: StorageProvider>(
709 &self,
710 store: &Storage,
711 ) -> Result<(), Storage::Error> {
712 store.delete_encryption_epoch_key_pairs(
713 self.group_id(),
714 &GroupEpoch::from(self.context().epoch().as_u64() - 1),
715 self.own_leaf_index().u32(),
716 )
717 }
718
719 pub(super) fn store<Storage: crate::storage::StorageProvider>(
722 &self,
723 storage: &Storage,
724 ) -> Result<(), Storage::Error> {
725 self.public_group.store(storage)?;
726 storage.write_group_epoch_secrets(self.group_id(), &self.group_epoch_secrets)?;
727 storage.write_own_leaf_index(self.group_id(), &self.own_leaf_index)?;
728 storage.write_message_secrets(self.group_id(), &self.message_secrets_store)?;
729 storage.write_resumption_psk_store(self.group_id(), &self.resumption_psk_store)?;
730 storage.write_mls_join_config(self.group_id(), &self.mls_group_config)?;
731 storage.write_group_state(self.group_id(), &self.group_state)?;
732
733 Ok(())
734 }
735
736 fn content_to_mls_message(
740 &mut self,
741 mls_auth_content: AuthenticatedContent,
742 provider: &impl OpenMlsProvider,
743 ) -> Result<MlsMessageOut, LibraryError> {
744 let msg = match self.configuration().wire_format_policy().outgoing() {
745 OutgoingWireFormatPolicy::AlwaysPlaintext => {
746 let mut plaintext: PublicMessage = mls_auth_content.into();
747 if plaintext.sender().is_member() {
749 plaintext.set_membership_tag(
750 provider.crypto(),
751 self.ciphersuite(),
752 self.message_secrets().membership_key(),
753 self.message_secrets().serialized_context(),
754 )?;
755 }
756 plaintext.into()
757 }
758 OutgoingWireFormatPolicy::AlwaysCiphertext => {
759 let ciphertext = self
760 .encrypt(mls_auth_content, provider)
761 .map_err(|_| LibraryError::custom("Malformed plaintext"))?;
763 MlsMessageOut::from_private_message(ciphertext, self.version())
764 }
765 };
766 Ok(msg)
767 }
768
769 fn is_operational(&self) -> Result<(), MlsGroupStateError> {
772 match self.group_state {
773 MlsGroupState::PendingCommit(_) => Err(MlsGroupStateError::PendingCommit),
774 MlsGroupState::Inactive => Err(MlsGroupStateError::UseAfterEviction),
775 MlsGroupState::Operational => Ok(()),
776 }
777 }
778}
779
780impl MlsGroup {
782 #[cfg(any(feature = "test-utils", test))]
783 pub fn export_group_context(&self) -> &GroupContext {
784 self.context()
785 }
786
787 #[cfg(any(feature = "test-utils", test))]
788 pub fn tree_hash(&self) -> &[u8] {
789 self.public_group().group_context().tree_hash()
790 }
791
792 #[cfg(any(feature = "test-utils", test))]
793 pub(crate) fn message_secrets_test_mut(&mut self) -> &mut MessageSecrets {
794 self.message_secrets_store.message_secrets_mut()
795 }
796
797 #[cfg(any(feature = "test-utils", test))]
798 pub fn print_ratchet_tree(&self, message: &str) {
799 println!("{}: {}", message, self.public_group().export_ratchet_tree());
800 }
801
802 #[cfg(any(feature = "test-utils", test))]
803 pub(crate) fn context_mut(&mut self) -> &mut GroupContext {
804 self.public_group.context_mut()
805 }
806
807 #[cfg(test)]
808 pub(crate) fn set_own_leaf_index(&mut self, own_leaf_index: LeafNodeIndex) {
809 self.own_leaf_index = own_leaf_index;
810 }
811
812 #[cfg(test)]
813 pub(crate) fn own_tree_position(&self) -> TreePosition {
814 TreePosition::new(self.group_id().clone(), self.own_leaf_index())
815 }
816
817 #[cfg(test)]
818 pub(crate) fn message_secrets_store(&self) -> &MessageSecretsStore {
819 &self.message_secrets_store
820 }
821
822 #[cfg(test)]
823 pub(crate) fn resumption_psk_store(&self) -> &ResumptionPskStore {
824 &self.resumption_psk_store
825 }
826
827 #[cfg(test)]
828 pub(crate) fn set_group_context(&mut self, group_context: GroupContext) {
829 self.public_group.set_group_context(group_context)
830 }
831
832 #[cfg(any(test, feature = "test-utils"))]
833 pub fn ensure_persistence(&self, storage: &impl StorageProvider) -> Result<(), LibraryError> {
834 let loaded = MlsGroup::load(storage, self.group_id())
835 .map_err(|_| LibraryError::custom("Failed to load group from storage"))?;
836 let other = loaded.ok_or_else(|| LibraryError::custom("Group not found in storage"))?;
837
838 if self != &other {
839 let mut diagnostics = Vec::new();
840
841 if self.mls_group_config != other.mls_group_config {
842 diagnostics.push(format!(
843 "mls_group_config:\n Current: {:?}\n Loaded: {:?}",
844 self.mls_group_config, other.mls_group_config
845 ));
846 }
847 if self.public_group != other.public_group {
848 diagnostics.push(format!(
849 "public_group:\n Current: {:?}\n Loaded: {:?}",
850 self.public_group, other.public_group
851 ));
852 }
853 if self.group_epoch_secrets != other.group_epoch_secrets {
854 diagnostics.push(format!(
855 "group_epoch_secrets:\n Current: {:?}\n Loaded: {:?}",
856 self.group_epoch_secrets, other.group_epoch_secrets
857 ));
858 }
859 if self.own_leaf_index != other.own_leaf_index {
860 diagnostics.push(format!(
861 "own_leaf_index:\n Current: {:?}\n Loaded: {:?}",
862 self.own_leaf_index, other.own_leaf_index
863 ));
864 }
865 if self.message_secrets_store != other.message_secrets_store {
866 diagnostics.push(format!(
867 "message_secrets_store:\n Current: {:?}\n Loaded: {:?}",
868 self.message_secrets_store, other.message_secrets_store
869 ));
870 }
871 if self.resumption_psk_store != other.resumption_psk_store {
872 diagnostics.push(format!(
873 "resumption_psk_store:\n Current: {:?}\n Loaded: {:?}",
874 self.resumption_psk_store, other.resumption_psk_store
875 ));
876 }
877 if self.own_leaf_nodes != other.own_leaf_nodes {
878 diagnostics.push(format!(
879 "own_leaf_nodes:\n Current: {:?}\n Loaded: {:?}",
880 self.own_leaf_nodes, other.own_leaf_nodes
881 ));
882 }
883 if self.aad != other.aad {
884 diagnostics.push(format!(
885 "aad:\n Current: {:?}\n Loaded: {:?}",
886 self.aad, other.aad
887 ));
888 }
889 if self.group_state != other.group_state {
890 diagnostics.push(format!(
891 "group_state:\n Current: {:?}\n Loaded: {:?}",
892 self.group_state, other.group_state
893 ));
894 }
895 #[cfg(feature = "extensions-draft-08")]
896 if self.application_export_tree != other.application_export_tree {
897 diagnostics.push(format!(
898 "application_export_tree:\n Current: {:?}\n Loaded: {:?}",
899 self.application_export_tree, other.application_export_tree
900 ));
901 }
902
903 log::error!(
904 "Loaded group does not match current group! Differing fields ({}):\n\n{}",
905 diagnostics.len(),
906 diagnostics.join("\n\n")
907 );
908
909 return Err(LibraryError::custom(
910 "Loaded group does not match current group",
911 ));
912 }
913
914 Ok(())
915 }
916}
917
918#[derive(Debug)]
921pub struct StagedWelcome {
922 mls_group_config: MlsGroupJoinConfig,
924 public_group: PublicGroup,
925 group_epoch_secrets: GroupEpochSecrets,
926 own_leaf_index: LeafNodeIndex,
927
928 message_secrets_store: MessageSecretsStore,
935
936 #[cfg(feature = "extensions-draft-08")]
939 application_export_secret: ApplicationExportSecret,
940
941 resumption_psk_store: ResumptionPskStore,
943
944 verifiable_group_info: VerifiableGroupInfo,
946
947 key_package_bundle: KeyPackageBundle,
949
950 path_keypairs: Option<Vec<EncryptionKeyPair>>,
952}
953
954pub struct ProcessedWelcome {
961 mls_group_config: MlsGroupJoinConfig,
963
964 ciphersuite: Ciphersuite,
967 group_secrets: GroupSecrets,
968 key_schedule: crate::schedule::KeySchedule,
969 verifiable_group_info: crate::messages::group_info::VerifiableGroupInfo,
970 resumption_psk_store: crate::schedule::psk::store::ResumptionPskStore,
971 key_package_bundle: KeyPackageBundle,
972}