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 extensions::Extensions,
21 framing::{mls_auth_content::AuthenticatedContent, *},
22 group::{
23 CreateGroupContextExtProposalError, Extension, ExtensionType, ExternalPubExtension,
24 GroupContext, GroupEpoch, GroupId, MlsGroupJoinConfig, MlsGroupStateError,
25 OutgoingWireFormatPolicy, PublicGroup, RatchetTreeExtension, RequiredCapabilitiesExtension,
26 StagedCommit,
27 },
28 key_packages::KeyPackageBundle,
29 messages::{
30 group_info::{GroupInfo, GroupInfoTBS, VerifiableGroupInfo},
31 proposals::*,
32 ConfirmationTag, GroupSecrets, Welcome,
33 },
34 schedule::{
35 message_secrets::MessageSecrets,
36 psk::{load_psks, store::ResumptionPskStore, PskSecret},
37 GroupEpochSecrets, JoinerSecret, KeySchedule,
38 },
39 storage::{OpenMlsProvider, StorageProvider},
40 treesync::{
41 node::{encryption_keys::EncryptionKeyPair, leaf_node::LeafNode},
42 RatchetTree,
43 },
44 versions::ProtocolVersion,
45};
46use openmls_traits::{signatures::Signer, storage::StorageProvider as _, types::Ciphersuite};
47
48#[cfg(feature = "extensions-draft-08")]
49use crate::schedule::{application_export_tree::ApplicationExportTree, ApplicationExportSecret};
50
51mod application;
53mod exporting;
54mod updates;
55
56use config::*;
57
58pub(crate) mod builder;
60pub(crate) mod commit_builder;
61pub(crate) mod config;
62pub(crate) mod creation;
63pub(crate) mod errors;
64pub(crate) mod membership;
65pub(crate) mod past_secrets;
66pub(crate) mod processing;
67pub(crate) mod proposal;
68pub(crate) mod proposal_store;
69pub(crate) mod staged_commit;
70
71#[cfg(feature = "extensions-draft-08")]
72pub(crate) mod app_ephemeral;
73
74#[cfg(test)]
76pub(crate) mod tests_and_kats;
77
78#[derive(Debug)]
79pub(crate) struct CreateCommitResult {
80 pub(crate) commit: AuthenticatedContent,
81 pub(crate) welcome_option: Option<Welcome>,
82 pub(crate) staged_commit: StagedCommit,
83 pub(crate) group_info: Option<GroupInfo>,
84}
85
86#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
88pub struct Member {
89 pub index: LeafNodeIndex,
91 pub credential: Credential,
93 pub encryption_key: Vec<u8>,
95 pub signature_key: Vec<u8>,
97}
98
99impl Member {
100 pub fn new(
102 index: LeafNodeIndex,
103 encryption_key: Vec<u8>,
104 signature_key: Vec<u8>,
105 credential: Credential,
106 ) -> Self {
107 Self {
108 index,
109 encryption_key,
110 signature_key,
111 credential,
112 }
113 }
114}
115
116#[derive(Debug, Serialize, Deserialize)]
119#[cfg_attr(any(test, feature = "test-utils"), derive(Clone, PartialEq))]
120pub enum PendingCommitState {
121 Member(StagedCommit),
123 External(StagedCommit),
125}
126
127impl PendingCommitState {
128 pub(crate) fn staged_commit(&self) -> &StagedCommit {
131 match self {
132 PendingCommitState::Member(pc) => pc,
133 PendingCommitState::External(pc) => pc,
134 }
135 }
136}
137
138impl From<PendingCommitState> for StagedCommit {
139 fn from(pcs: PendingCommitState) -> Self {
140 match pcs {
141 PendingCommitState::Member(pc) => pc,
142 PendingCommitState::External(pc) => pc,
143 }
144 }
145}
146
147#[derive(Debug, Serialize, Deserialize)]
192#[cfg_attr(any(test, feature = "test-utils"), derive(Clone, PartialEq))]
193pub enum MlsGroupState {
194 PendingCommit(Box<PendingCommitState>),
196 Operational,
198 Inactive,
200}
201
202#[derive(Debug)]
227#[cfg_attr(feature = "test-utils", derive(Clone, PartialEq))]
228pub struct MlsGroup {
229 mls_group_config: MlsGroupJoinConfig,
231 public_group: PublicGroup,
233 group_epoch_secrets: GroupEpochSecrets,
235 own_leaf_index: LeafNodeIndex,
237 message_secrets_store: MessageSecretsStore,
244 resumption_psk_store: ResumptionPskStore,
246 own_leaf_nodes: Vec<LeafNode>,
250 aad: Vec<u8>,
254 group_state: MlsGroupState,
257 #[cfg(feature = "extensions-draft-08")]
261 application_export_tree: Option<ApplicationExportTree>,
262}
263
264impl MlsGroup {
265 pub fn configuration(&self) -> &MlsGroupJoinConfig {
269 &self.mls_group_config
270 }
271
272 pub fn set_configuration<Storage: StorageProvider>(
274 &mut self,
275 storage: &Storage,
276 mls_group_config: &MlsGroupJoinConfig,
277 ) -> Result<(), Storage::Error> {
278 self.mls_group_config = mls_group_config.clone();
279 storage.write_mls_join_config(self.group_id(), mls_group_config)
280 }
281
282 pub fn set_aad(&mut self, aad: Vec<u8>) {
286 self.aad = aad;
287 }
288
289 pub fn aad(&self) -> &[u8] {
292 &self.aad
293 }
294
295 pub fn ciphersuite(&self) -> Ciphersuite {
299 self.public_group.ciphersuite()
300 }
301
302 pub fn confirmation_tag(&self) -> &ConfirmationTag {
304 self.public_group.confirmation_tag()
305 }
306
307 pub fn is_active(&self) -> bool {
310 !matches!(self.group_state, MlsGroupState::Inactive)
311 }
312
313 pub fn credential(&self) -> Result<&Credential, MlsGroupStateError> {
316 if !self.is_active() {
317 return Err(MlsGroupStateError::UseAfterEviction);
318 }
319 self.public_group
320 .leaf(self.own_leaf_index())
321 .map(|node| node.credential())
322 .ok_or_else(|| LibraryError::custom("Own leaf node missing").into())
323 }
324
325 pub fn own_leaf_index(&self) -> LeafNodeIndex {
327 self.own_leaf_index
328 }
329
330 pub fn own_leaf_node(&self) -> Option<&LeafNode> {
332 self.public_group().leaf(self.own_leaf_index())
333 }
334
335 pub fn group_id(&self) -> &GroupId {
337 self.public_group.group_id()
338 }
339
340 pub fn epoch(&self) -> GroupEpoch {
342 self.public_group.group_context().epoch()
343 }
344
345 pub fn pending_proposals(&self) -> impl Iterator<Item = &QueuedProposal> {
347 self.proposal_store().proposals()
348 }
349
350 pub fn pending_commit(&self) -> Option<&StagedCommit> {
354 match self.group_state {
355 MlsGroupState::PendingCommit(ref pending_commit_state) => {
356 Some(pending_commit_state.staged_commit())
357 }
358 MlsGroupState::Operational => None,
359 MlsGroupState::Inactive => None,
360 }
361 }
362
363 pub fn clear_pending_commit<Storage: StorageProvider>(
375 &mut self,
376 storage: &Storage,
377 ) -> Result<(), Storage::Error> {
378 match self.group_state {
379 MlsGroupState::PendingCommit(ref pending_commit_state) => {
380 if let PendingCommitState::Member(_) = **pending_commit_state {
381 self.group_state = MlsGroupState::Operational;
382 storage.write_group_state(self.group_id(), &self.group_state)
383 } else {
384 Ok(())
385 }
386 }
387 MlsGroupState::Operational | MlsGroupState::Inactive => Ok(()),
388 }
389 }
390
391 pub fn clear_pending_proposals<Storage: StorageProvider>(
398 &mut self,
399 storage: &Storage,
400 ) -> Result<(), Storage::Error> {
401 if !self.proposal_store().is_empty() {
403 self.proposal_store_mut().empty();
405
406 storage.clear_proposal_queue::<GroupId, ProposalRef>(self.group_id())?;
408 }
409
410 Ok(())
411 }
412
413 pub fn extensions(&self) -> &Extensions<GroupContext> {
415 self.public_group().group_context().extensions()
416 }
417
418 pub fn ext_commit_sender_index(
420 &self,
421 commit: &StagedCommit,
422 ) -> Result<LeafNodeIndex, LibraryError> {
423 self.public_group().ext_commit_sender_index(commit)
424 }
425
426 pub fn load<Storage: crate::storage::StorageProvider>(
430 storage: &Storage,
431 group_id: &GroupId,
432 ) -> Result<Option<MlsGroup>, Storage::Error> {
433 let public_group = PublicGroup::load(storage, group_id)?;
434 let group_epoch_secrets = storage.group_epoch_secrets(group_id)?;
435 let own_leaf_index = storage.own_leaf_index(group_id)?;
436 let message_secrets_store = storage.message_secrets(group_id)?;
437 let resumption_psk_store = storage.resumption_psk_store(group_id)?;
438 let mls_group_config = storage.mls_group_join_config(group_id)?;
439 let own_leaf_nodes = storage.own_leaf_nodes(group_id)?;
440 let group_state = storage.group_state(group_id)?;
441 #[cfg(feature = "extensions-draft-08")]
442 let application_export_tree = storage.application_export_tree(group_id)?;
443
444 let build = || -> Option<Self> {
445 Some(Self {
446 public_group: public_group?,
447 group_epoch_secrets: group_epoch_secrets?,
448 own_leaf_index: own_leaf_index?,
449 message_secrets_store: message_secrets_store?,
450 resumption_psk_store: resumption_psk_store?,
451 mls_group_config: mls_group_config?,
452 own_leaf_nodes,
453 aad: vec![],
454 group_state: group_state?,
455 #[cfg(feature = "extensions-draft-08")]
456 application_export_tree,
457 })
458 };
459
460 Ok(build())
461 }
462
463 pub fn delete<Storage: crate::storage::StorageProvider>(
467 &mut self,
468 storage: &Storage,
469 ) -> Result<(), Storage::Error> {
470 PublicGroup::delete(storage, self.group_id())?;
471 storage.delete_own_leaf_index(self.group_id())?;
472 storage.delete_group_epoch_secrets(self.group_id())?;
473 storage.delete_message_secrets(self.group_id())?;
474 storage.delete_all_resumption_psk_secrets(self.group_id())?;
475 storage.delete_group_config(self.group_id())?;
476 storage.delete_own_leaf_nodes(self.group_id())?;
477 storage.delete_group_state(self.group_id())?;
478 storage.clear_proposal_queue::<GroupId, ProposalRef>(self.group_id())?;
479
480 #[cfg(feature = "extensions-draft-08")]
481 storage.delete_application_export_tree::<_, ApplicationExportTree>(self.group_id())?;
482
483 self.proposal_store_mut().empty();
484 storage.delete_encryption_epoch_key_pairs(
485 self.group_id(),
486 &self.epoch(),
487 self.own_leaf_index().u32(),
488 )?;
489
490 Ok(())
491 }
492
493 pub fn export_ratchet_tree(&self) -> RatchetTree {
497 self.public_group().export_ratchet_tree()
498 }
499}
500
501impl MlsGroup {
503 pub(crate) fn required_capabilities(&self) -> Option<&RequiredCapabilitiesExtension> {
505 self.public_group.required_capabilities()
506 }
507
508 pub(crate) fn group_epoch_secrets(&self) -> &GroupEpochSecrets {
510 &self.group_epoch_secrets
511 }
512
513 pub(crate) fn message_secrets(&self) -> &MessageSecrets {
515 self.message_secrets_store.message_secrets()
516 }
517
518 pub(crate) fn set_max_past_epochs(&mut self, max_past_epochs: usize) {
522 self.message_secrets_store.resize(max_past_epochs);
523 }
524
525 pub(crate) fn message_secrets_mut(
527 &mut self,
528 epoch: GroupEpoch,
529 ) -> Result<&mut MessageSecrets, SecretTreeError> {
530 if epoch < self.context().epoch() {
531 self.message_secrets_store
532 .secrets_for_epoch_mut(epoch)
533 .ok_or(SecretTreeError::TooDistantInThePast)
534 } else {
535 Ok(self.message_secrets_store.message_secrets_mut())
536 }
537 }
538
539 pub(crate) fn message_secrets_for_epoch(
541 &self,
542 epoch: GroupEpoch,
543 ) -> Result<&MessageSecrets, SecretTreeError> {
544 if epoch < self.context().epoch() {
545 self.message_secrets_store
546 .secrets_for_epoch(epoch)
547 .ok_or(SecretTreeError::TooDistantInThePast)
548 } else {
549 Ok(self.message_secrets_store.message_secrets())
550 }
551 }
552
553 pub(crate) fn message_secrets_and_leaves_mut(
559 &mut self,
560 epoch: GroupEpoch,
561 ) -> Result<(&mut MessageSecrets, &[Member]), SecretTreeError> {
562 if epoch < self.context().epoch() {
563 self.message_secrets_store
564 .secrets_and_leaves_for_epoch_mut(epoch)
565 .ok_or(SecretTreeError::TooDistantInThePast)
566 } else {
567 Ok((self.message_secrets_store.message_secrets_mut(), &[]))
570 }
571 }
572
573 pub(crate) fn create_group_context_ext_proposal<Provider: OpenMlsProvider>(
575 &self,
576 framing_parameters: FramingParameters,
577 extensions: Extensions<GroupContext>,
578 signer: &impl Signer,
579 ) -> Result<AuthenticatedContent, CreateGroupContextExtProposalError<Provider::StorageError>>
580 {
581 let required_extension = extensions
583 .iter()
584 .find(|extension| extension.extension_type() == ExtensionType::RequiredCapabilities);
585 if let Some(required_extension) = required_extension {
586 let required_capabilities = required_extension.as_required_capabilities_extension()?;
587 self.own_leaf_node()
589 .ok_or_else(|| LibraryError::custom("Tree has no own leaf."))?
590 .capabilities()
591 .supports_required_capabilities(required_capabilities)?;
592
593 self.public_group()
596 .check_extension_support(required_capabilities.extension_types())?;
597 }
598 let proposal = GroupContextExtensionProposal::new(extensions);
599 let proposal = Proposal::GroupContextExtensions(Box::new(proposal));
600 AuthenticatedContent::member_proposal(
601 framing_parameters,
602 self.own_leaf_index(),
603 proposal,
604 self.context(),
605 signer,
606 )
607 .map_err(|e| e.into())
608 }
609
610 pub(crate) fn encrypt<Provider: OpenMlsProvider>(
612 &mut self,
613 public_message: AuthenticatedContent,
614 provider: &Provider,
615 ) -> Result<PrivateMessage, MessageEncryptionError<Provider::StorageError>> {
616 let padding_size = self.configuration().padding_size();
617 let msg = PrivateMessage::try_from_authenticated_content(
618 provider.crypto(),
619 provider.rand(),
620 &public_message,
621 self.ciphersuite(),
622 self.message_secrets_store.message_secrets_mut(),
623 padding_size,
624 )?;
625
626 provider
627 .storage()
628 .write_message_secrets(self.group_id(), &self.message_secrets_store)
629 .map_err(MessageEncryptionError::StorageError)?;
630
631 Ok(msg)
632 }
633
634 pub(crate) fn framing_parameters(&self) -> FramingParameters<'_> {
636 FramingParameters::new(
637 &self.aad,
638 self.mls_group_config.wire_format_policy().outgoing(),
639 )
640 }
641
642 pub fn proposal_store(&self) -> &ProposalStore {
644 self.public_group.proposal_store()
645 }
646
647 pub(crate) fn proposal_store_mut(&mut self) -> &mut ProposalStore {
649 self.public_group.proposal_store_mut()
650 }
651
652 pub(crate) fn context(&self) -> &GroupContext {
654 self.public_group.group_context()
655 }
656
657 pub(crate) fn version(&self) -> ProtocolVersion {
659 self.public_group.version()
660 }
661
662 #[inline]
664 pub(crate) fn reset_aad(&mut self) {
665 self.aad.clear();
666 }
667
668 pub(crate) fn public_group(&self) -> &PublicGroup {
670 &self.public_group
671 }
672}
673
674impl MlsGroup {
676 pub(super) fn store_epoch_keypairs<Storage: StorageProvider>(
681 &self,
682 store: &Storage,
683 keypair_references: &[EncryptionKeyPair],
684 ) -> Result<(), Storage::Error> {
685 store.write_encryption_epoch_key_pairs(
686 self.group_id(),
687 &self.context().epoch(),
688 self.own_leaf_index().u32(),
689 keypair_references,
690 )
691 }
692
693 pub(super) fn read_epoch_keypairs<Storage: StorageProvider>(
698 &self,
699 store: &Storage,
700 ) -> Result<Vec<EncryptionKeyPair>, Storage::Error> {
701 store.encryption_epoch_key_pairs(
702 self.group_id(),
703 &self.context().epoch(),
704 self.own_leaf_index().u32(),
705 )
706 }
707
708 pub(super) fn delete_previous_epoch_keypairs<Storage: StorageProvider>(
713 &self,
714 store: &Storage,
715 ) -> Result<(), Storage::Error> {
716 store.delete_encryption_epoch_key_pairs(
717 self.group_id(),
718 &GroupEpoch::from(self.context().epoch().as_u64() - 1),
719 self.own_leaf_index().u32(),
720 )
721 }
722
723 pub(super) fn store<Storage: crate::storage::StorageProvider>(
726 &self,
727 storage: &Storage,
728 ) -> Result<(), Storage::Error> {
729 self.public_group.store(storage)?;
730 storage.write_group_epoch_secrets(self.group_id(), &self.group_epoch_secrets)?;
731 storage.write_own_leaf_index(self.group_id(), &self.own_leaf_index)?;
732 storage.write_message_secrets(self.group_id(), &self.message_secrets_store)?;
733 storage.write_resumption_psk_store(self.group_id(), &self.resumption_psk_store)?;
734 storage.write_mls_join_config(self.group_id(), &self.mls_group_config)?;
735 storage.write_group_state(self.group_id(), &self.group_state)?;
736 #[cfg(feature = "extensions-draft-08")]
737 if let Some(application_export_tree) = &self.application_export_tree {
738 storage.write_application_export_tree(self.group_id(), application_export_tree)?;
739 }
740
741 Ok(())
742 }
743
744 fn content_to_mls_message(
748 &mut self,
749 mls_auth_content: AuthenticatedContent,
750 provider: &impl OpenMlsProvider,
751 ) -> Result<MlsMessageOut, LibraryError> {
752 let msg = match self.configuration().wire_format_policy().outgoing() {
753 OutgoingWireFormatPolicy::AlwaysPlaintext => {
754 let mut plaintext: PublicMessage = mls_auth_content.into();
755 if plaintext.sender().is_member() {
757 plaintext.set_membership_tag(
758 provider.crypto(),
759 self.ciphersuite(),
760 self.message_secrets().membership_key(),
761 self.message_secrets().serialized_context(),
762 )?;
763 }
764 plaintext.into()
765 }
766 OutgoingWireFormatPolicy::AlwaysCiphertext => {
767 let ciphertext = self
768 .encrypt(mls_auth_content, provider)
769 .map_err(|_| LibraryError::custom("Malformed plaintext"))?;
771 MlsMessageOut::from_private_message(ciphertext, self.version())
772 }
773 };
774 Ok(msg)
775 }
776
777 fn is_operational(&self) -> Result<(), MlsGroupStateError> {
780 match self.group_state {
781 MlsGroupState::PendingCommit(_) => Err(MlsGroupStateError::PendingCommit),
782 MlsGroupState::Inactive => Err(MlsGroupStateError::UseAfterEviction),
783 MlsGroupState::Operational => Ok(()),
784 }
785 }
786}
787
788impl MlsGroup {
790 #[cfg(any(feature = "test-utils", test))]
791 pub fn export_group_context(&self) -> &GroupContext {
792 self.context()
793 }
794
795 #[cfg(any(feature = "test-utils", test))]
796 pub fn tree_hash(&self) -> &[u8] {
797 self.public_group().group_context().tree_hash()
798 }
799
800 #[cfg(any(feature = "test-utils", test))]
801 pub(crate) fn message_secrets_test_mut(&mut self) -> &mut MessageSecrets {
802 self.message_secrets_store.message_secrets_mut()
803 }
804
805 #[cfg(any(feature = "test-utils", test))]
806 pub fn print_ratchet_tree(&self, message: &str) {
807 println!("{}: {}", message, self.public_group().export_ratchet_tree());
808 }
809
810 #[cfg(any(feature = "test-utils", test))]
811 pub(crate) fn context_mut(&mut self) -> &mut GroupContext {
812 self.public_group.context_mut()
813 }
814
815 #[cfg(test)]
816 pub(crate) fn set_own_leaf_index(&mut self, own_leaf_index: LeafNodeIndex) {
817 self.own_leaf_index = own_leaf_index;
818 }
819
820 #[cfg(test)]
821 pub(crate) fn own_tree_position(&self) -> TreePosition {
822 TreePosition::new(self.group_id().clone(), self.own_leaf_index())
823 }
824
825 #[cfg(test)]
826 pub(crate) fn message_secrets_store(&self) -> &MessageSecretsStore {
827 &self.message_secrets_store
828 }
829
830 #[cfg(test)]
831 pub(crate) fn resumption_psk_store(&self) -> &ResumptionPskStore {
832 &self.resumption_psk_store
833 }
834
835 #[cfg(test)]
836 pub(crate) fn set_group_context(&mut self, group_context: GroupContext) {
837 self.public_group.set_group_context(group_context)
838 }
839
840 #[cfg(any(test, feature = "test-utils"))]
841 pub fn ensure_persistence(&self, storage: &impl StorageProvider) -> Result<(), LibraryError> {
842 let loaded = MlsGroup::load(storage, self.group_id())
843 .map_err(|_| LibraryError::custom("Failed to load group from storage"))?;
844 let other = loaded.ok_or_else(|| LibraryError::custom("Group not found in storage"))?;
845
846 if self != &other {
847 let mut diagnostics = Vec::new();
848
849 if self.mls_group_config != other.mls_group_config {
850 diagnostics.push(format!(
851 "mls_group_config:\n Current: {:?}\n Loaded: {:?}",
852 self.mls_group_config, other.mls_group_config
853 ));
854 }
855 if self.public_group != other.public_group {
856 diagnostics.push(format!(
857 "public_group:\n Current: {:?}\n Loaded: {:?}",
858 self.public_group, other.public_group
859 ));
860 }
861 if self.group_epoch_secrets != other.group_epoch_secrets {
862 diagnostics.push(format!(
863 "group_epoch_secrets:\n Current: {:?}\n Loaded: {:?}",
864 self.group_epoch_secrets, other.group_epoch_secrets
865 ));
866 }
867 if self.own_leaf_index != other.own_leaf_index {
868 diagnostics.push(format!(
869 "own_leaf_index:\n Current: {:?}\n Loaded: {:?}",
870 self.own_leaf_index, other.own_leaf_index
871 ));
872 }
873 if self.message_secrets_store != other.message_secrets_store {
874 diagnostics.push(format!(
875 "message_secrets_store:\n Current: {:?}\n Loaded: {:?}",
876 self.message_secrets_store, other.message_secrets_store
877 ));
878 }
879 if self.resumption_psk_store != other.resumption_psk_store {
880 diagnostics.push(format!(
881 "resumption_psk_store:\n Current: {:?}\n Loaded: {:?}",
882 self.resumption_psk_store, other.resumption_psk_store
883 ));
884 }
885 if self.own_leaf_nodes != other.own_leaf_nodes {
886 diagnostics.push(format!(
887 "own_leaf_nodes:\n Current: {:?}\n Loaded: {:?}",
888 self.own_leaf_nodes, other.own_leaf_nodes
889 ));
890 }
891 if self.aad != other.aad {
892 diagnostics.push(format!(
893 "aad:\n Current: {:?}\n Loaded: {:?}",
894 self.aad, other.aad
895 ));
896 }
897 if self.group_state != other.group_state {
898 diagnostics.push(format!(
899 "group_state:\n Current: {:?}\n Loaded: {:?}",
900 self.group_state, other.group_state
901 ));
902 }
903 #[cfg(feature = "extensions-draft-08")]
904 if self.application_export_tree != other.application_export_tree {
905 diagnostics.push(format!(
906 "application_export_tree:\n Current: {:?}\n Loaded: {:?}",
907 self.application_export_tree, other.application_export_tree
908 ));
909 }
910
911 log::error!(
912 "Loaded group does not match current group! Differing fields ({}):\n\n{}",
913 diagnostics.len(),
914 diagnostics.join("\n\n")
915 );
916
917 return Err(LibraryError::custom(
918 "Loaded group does not match current group",
919 ));
920 }
921
922 Ok(())
923 }
924}
925
926#[derive(Debug)]
929pub struct StagedWelcome {
930 mls_group_config: MlsGroupJoinConfig,
932 public_group: PublicGroup,
933 group_epoch_secrets: GroupEpochSecrets,
934 own_leaf_index: LeafNodeIndex,
935
936 message_secrets_store: MessageSecretsStore,
943
944 #[cfg(feature = "extensions-draft-08")]
947 application_export_secret: ApplicationExportSecret,
948
949 resumption_psk_store: ResumptionPskStore,
951
952 verifiable_group_info: VerifiableGroupInfo,
954
955 key_package_bundle: KeyPackageBundle,
957
958 path_keypairs: Option<Vec<EncryptionKeyPair>>,
960}
961
962pub struct ProcessedWelcome {
969 mls_group_config: MlsGroupJoinConfig,
971
972 ciphersuite: Ciphersuite,
975 group_secrets: GroupSecrets,
976 key_schedule: crate::schedule::KeySchedule,
977 verifiable_group_info: crate::messages::group_info::VerifiableGroupInfo,
978 resumption_psk_store: crate::schedule::psk::store::ResumptionPskStore,
979 key_package_bundle: KeyPackageBundle,
980}