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, DeletePastEpochSecretsError, Extension, ExtensionType,
24 ExternalPubExtension, GroupContext, GroupEpoch, GroupId, MlsGroupJoinConfig,
25 MlsGroupStateError, OutgoingWireFormatPolicy, PublicGroup, RatchetTreeExtension,
26 RequiredCapabilitiesExtension, SetPastEpochDeletionPolicyError, 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, TreeSync,
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 treesync(&self) -> &TreeSync {
352 self.public_group.treesync()
353 }
354
355 pub fn pending_commit(&self) -> Option<&StagedCommit> {
359 match self.group_state {
360 MlsGroupState::PendingCommit(ref pending_commit_state) => {
361 Some(pending_commit_state.staged_commit())
362 }
363 MlsGroupState::Operational => None,
364 MlsGroupState::Inactive => None,
365 }
366 }
367
368 pub fn clear_pending_commit<Storage: StorageProvider>(
380 &mut self,
381 storage: &Storage,
382 ) -> Result<(), Storage::Error> {
383 match self.group_state {
384 MlsGroupState::PendingCommit(ref pending_commit_state) => {
385 if let PendingCommitState::Member(_) = **pending_commit_state {
386 self.group_state = MlsGroupState::Operational;
387 storage.write_group_state(self.group_id(), &self.group_state)
388 } else {
389 Ok(())
390 }
391 }
392 MlsGroupState::Operational | MlsGroupState::Inactive => Ok(()),
393 }
394 }
395
396 pub fn clear_pending_proposals<Storage: StorageProvider>(
403 &mut self,
404 storage: &Storage,
405 ) -> Result<(), Storage::Error> {
406 if !self.proposal_store().is_empty() {
408 self.proposal_store_mut().empty();
410
411 storage.clear_proposal_queue::<GroupId, ProposalRef>(self.group_id())?;
413 }
414
415 Ok(())
416 }
417
418 pub fn extensions(&self) -> &Extensions<GroupContext> {
420 self.public_group().group_context().extensions()
421 }
422
423 pub fn ext_commit_sender_index(
425 &self,
426 commit: &StagedCommit,
427 ) -> Result<LeafNodeIndex, LibraryError> {
428 self.public_group().ext_commit_sender_index(commit)
429 }
430
431 pub fn load<Storage: crate::storage::StorageProvider>(
435 storage: &Storage,
436 group_id: &GroupId,
437 ) -> Result<Option<MlsGroup>, Storage::Error> {
438 let public_group = PublicGroup::load(storage, group_id)?;
439 let group_epoch_secrets = storage.group_epoch_secrets(group_id)?;
440 let own_leaf_index = storage.own_leaf_index(group_id)?;
441 let message_secrets_store = storage.message_secrets(group_id)?;
442 let resumption_psk_store = storage.resumption_psk_store(group_id)?;
443 let mls_group_config = storage.mls_group_join_config(group_id)?;
444 let own_leaf_nodes = storage.own_leaf_nodes(group_id)?;
445 let group_state = storage.group_state(group_id)?;
446 #[cfg(feature = "extensions-draft-08")]
447 let application_export_tree = storage.application_export_tree(group_id)?;
448
449 let build = || -> Option<Self> {
450 Some(Self {
451 public_group: public_group?,
452 group_epoch_secrets: group_epoch_secrets?,
453 own_leaf_index: own_leaf_index?,
454 message_secrets_store: message_secrets_store?,
455 resumption_psk_store: resumption_psk_store?,
456 mls_group_config: mls_group_config?,
457 own_leaf_nodes,
458 aad: vec![],
459 group_state: group_state?,
460 #[cfg(feature = "extensions-draft-08")]
461 application_export_tree,
462 })
463 };
464
465 Ok(build())
466 }
467
468 pub fn delete<Storage: crate::storage::StorageProvider>(
472 &mut self,
473 storage: &Storage,
474 ) -> Result<(), Storage::Error> {
475 PublicGroup::delete(storage, self.group_id())?;
476 storage.delete_own_leaf_index(self.group_id())?;
477 storage.delete_group_epoch_secrets(self.group_id())?;
478 storage.delete_message_secrets(self.group_id())?;
479 storage.delete_all_resumption_psk_secrets(self.group_id())?;
480 storage.delete_group_config(self.group_id())?;
481 storage.delete_own_leaf_nodes(self.group_id())?;
482 storage.delete_group_state(self.group_id())?;
483 storage.clear_proposal_queue::<GroupId, ProposalRef>(self.group_id())?;
484
485 #[cfg(feature = "extensions-draft-08")]
486 storage.delete_application_export_tree::<_, ApplicationExportTree>(self.group_id())?;
487
488 self.proposal_store_mut().empty();
489 storage.delete_encryption_epoch_key_pairs(
490 self.group_id(),
491 &self.epoch(),
492 self.own_leaf_index().u32(),
493 )?;
494
495 Ok(())
496 }
497
498 pub fn export_ratchet_tree(&self) -> RatchetTree {
502 self.public_group().export_ratchet_tree()
503 }
504}
505
506impl MlsGroup {
508 pub(crate) fn required_capabilities(&self) -> Option<&RequiredCapabilitiesExtension> {
510 self.public_group.required_capabilities()
511 }
512
513 pub(crate) fn group_epoch_secrets(&self) -> &GroupEpochSecrets {
515 &self.group_epoch_secrets
516 }
517
518 pub(crate) fn message_secrets(&self) -> &MessageSecrets {
520 self.message_secrets_store.message_secrets()
521 }
522
523 pub(crate) fn resize_message_secrets_store(&mut self, policy: &PastEpochDeletionPolicy) {
527 self.message_secrets_store.resize(policy);
528 }
529
530 pub fn past_epoch_deletion_policy(&self) -> &PastEpochDeletionPolicy {
532 self.mls_group_config.past_epoch_deletion_policy()
533 }
534
535 pub fn set_past_epoch_deletion_policy<Provider: OpenMlsProvider>(
537 &mut self,
538 provider: &Provider,
539 policy: PastEpochDeletionPolicy,
540 ) -> Result<(), SetPastEpochDeletionPolicyError<Provider::StorageError>> {
541 self.resize_message_secrets_store(&policy);
543
544 self.mls_group_config.past_epoch_deletion_policy = policy;
546
547 provider
549 .storage()
550 .write_mls_join_config(self.group_id(), &self.mls_group_config)?;
551
552 provider
554 .storage()
555 .write_message_secrets(self.group_id(), &self.message_secrets_store)?;
556
557 Ok(())
558 }
559
560 pub(crate) fn message_secrets_for_epoch_mut(
562 &mut self,
563 epoch: GroupEpoch,
564 ) -> Result<&mut MessageSecrets, SecretTreeError> {
565 if epoch < self.context().epoch() {
566 self.message_secrets_store
567 .secrets_for_epoch_mut(epoch)
568 .ok_or(SecretTreeError::TooDistantInThePast)
569 } else {
570 Ok(self.message_secrets_store.message_secrets_mut())
571 }
572 }
573
574 pub(crate) fn message_secrets_for_epoch(
576 &self,
577 epoch: GroupEpoch,
578 ) -> Result<&MessageSecrets, SecretTreeError> {
579 if epoch < self.context().epoch() {
580 self.message_secrets_store
581 .secrets_for_epoch(epoch)
582 .ok_or(SecretTreeError::TooDistantInThePast)
583 } else {
584 Ok(self.message_secrets_store.message_secrets())
585 }
586 }
587
588 pub(crate) fn message_secrets_and_leaves(
594 &self,
595 epoch: GroupEpoch,
596 ) -> Result<(&MessageSecrets, &[Member]), SecretTreeError> {
597 if epoch < self.context().epoch() {
598 self.message_secrets_store
599 .secrets_and_leaves_for_epoch(epoch)
600 .ok_or(SecretTreeError::TooDistantInThePast)
601 } else {
602 Ok((self.message_secrets_store.message_secrets(), &[]))
605 }
606 }
607
608 pub(crate) fn create_group_context_ext_proposal<Provider: OpenMlsProvider>(
610 &self,
611 framing_parameters: FramingParameters,
612 extensions: Extensions<GroupContext>,
613 signer: &impl Signer,
614 ) -> Result<AuthenticatedContent, CreateGroupContextExtProposalError<Provider::StorageError>>
615 {
616 let required_extension = extensions
618 .iter()
619 .find(|extension| extension.extension_type() == ExtensionType::RequiredCapabilities);
620 if let Some(required_extension) = required_extension {
621 let required_capabilities = required_extension.as_required_capabilities_extension()?;
622 self.own_leaf_node()
624 .ok_or_else(|| LibraryError::custom("Tree has no own leaf."))?
625 .capabilities()
626 .supports_required_capabilities(required_capabilities)?;
627
628 self.public_group()
631 .check_extension_support(required_capabilities.extension_types())?;
632 }
633 let proposal = GroupContextExtensionProposal::new(extensions);
634 let proposal = Proposal::GroupContextExtensions(Box::new(proposal));
635 AuthenticatedContent::member_proposal(
636 framing_parameters,
637 self.own_leaf_index(),
638 proposal,
639 self.context(),
640 signer,
641 )
642 .map_err(|e| e.into())
643 }
644
645 pub(crate) fn encrypt<Provider: OpenMlsProvider>(
647 &mut self,
648 public_message: AuthenticatedContent,
649 provider: &Provider,
650 ) -> Result<PrivateMessage, MessageEncryptionError<Provider::StorageError>> {
651 let padding_size = self.configuration().padding_size();
652 let msg = PrivateMessage::try_from_authenticated_content(
653 provider.crypto(),
654 provider.rand(),
655 &public_message,
656 self.ciphersuite(),
657 self.message_secrets_store.message_secrets_mut(),
658 padding_size,
659 )?;
660
661 provider
662 .storage()
663 .write_message_secrets(self.group_id(), &self.message_secrets_store)
664 .map_err(MessageEncryptionError::StorageError)?;
665
666 Ok(msg)
667 }
668
669 pub(crate) fn framing_parameters(&self) -> FramingParameters<'_> {
671 FramingParameters::new(
672 &self.aad,
673 self.mls_group_config.wire_format_policy().outgoing(),
674 )
675 }
676
677 pub fn delete_past_epoch_secrets<Provider: OpenMlsProvider>(
681 &mut self,
682 provider: &Provider,
683 policy: PastEpochDeletion,
684 ) -> Result<(), DeletePastEpochSecretsError<Provider::StorageError>> {
685 self.message_secrets_store.delete_past_epoch_secrets(policy);
687 provider
689 .storage()
690 .write_message_secrets(self.group_id(), &self.message_secrets_store)?;
691
692 Ok(())
693 }
694
695 pub fn proposal_store(&self) -> &ProposalStore {
697 self.public_group.proposal_store()
698 }
699
700 pub(crate) fn proposal_store_mut(&mut self) -> &mut ProposalStore {
702 self.public_group.proposal_store_mut()
703 }
704
705 pub(crate) fn context(&self) -> &GroupContext {
707 self.public_group.group_context()
708 }
709
710 pub(crate) fn version(&self) -> ProtocolVersion {
712 self.public_group.version()
713 }
714
715 #[inline]
717 pub(crate) fn reset_aad(&mut self) {
718 self.aad.clear();
719 }
720
721 pub fn public_group(&self) -> &PublicGroup {
723 &self.public_group
724 }
725}
726
727impl MlsGroup {
729 pub(super) fn store_epoch_keypairs<Storage: StorageProvider>(
734 &self,
735 store: &Storage,
736 keypair_references: &[EncryptionKeyPair],
737 ) -> Result<(), Storage::Error> {
738 store.write_encryption_epoch_key_pairs(
739 self.group_id(),
740 &self.context().epoch(),
741 self.own_leaf_index().u32(),
742 keypair_references,
743 )
744 }
745
746 pub(super) fn read_epoch_keypairs<Storage: StorageProvider>(
751 &self,
752 store: &Storage,
753 ) -> Result<Vec<EncryptionKeyPair>, Storage::Error> {
754 store.encryption_epoch_key_pairs(
755 self.group_id(),
756 &self.context().epoch(),
757 self.own_leaf_index().u32(),
758 )
759 }
760
761 pub(super) fn delete_previous_epoch_keypairs<Storage: StorageProvider>(
766 &self,
767 store: &Storage,
768 ) -> Result<(), Storage::Error> {
769 store.delete_encryption_epoch_key_pairs(
770 self.group_id(),
771 &GroupEpoch::from(self.context().epoch().as_u64() - 1),
772 self.own_leaf_index().u32(),
773 )
774 }
775
776 pub(super) fn store<Storage: crate::storage::StorageProvider>(
779 &self,
780 storage: &Storage,
781 ) -> Result<(), Storage::Error> {
782 self.public_group.store(storage)?;
783 storage.write_group_epoch_secrets(self.group_id(), &self.group_epoch_secrets)?;
784 storage.write_own_leaf_index(self.group_id(), &self.own_leaf_index)?;
785 storage.write_message_secrets(self.group_id(), &self.message_secrets_store)?;
786 storage.write_resumption_psk_store(self.group_id(), &self.resumption_psk_store)?;
787 storage.write_mls_join_config(self.group_id(), &self.mls_group_config)?;
788 storage.write_group_state(self.group_id(), &self.group_state)?;
789 #[cfg(feature = "extensions-draft-08")]
790 if let Some(application_export_tree) = &self.application_export_tree {
791 storage.write_application_export_tree(self.group_id(), application_export_tree)?;
792 }
793
794 Ok(())
795 }
796
797 fn content_to_mls_message(
801 &mut self,
802 mls_auth_content: AuthenticatedContent,
803 provider: &impl OpenMlsProvider,
804 ) -> Result<MlsMessageOut, LibraryError> {
805 let msg = match self.configuration().wire_format_policy().outgoing() {
806 OutgoingWireFormatPolicy::AlwaysPlaintext => {
807 let mut plaintext: PublicMessage = mls_auth_content.into();
808 if plaintext.sender().is_member() {
810 plaintext.set_membership_tag(
811 provider.crypto(),
812 self.ciphersuite(),
813 self.message_secrets().membership_key(),
814 self.message_secrets().serialized_context(),
815 )?;
816 }
817 plaintext.into()
818 }
819 OutgoingWireFormatPolicy::AlwaysCiphertext => {
820 let ciphertext = self
821 .encrypt(mls_auth_content, provider)
822 .map_err(|_| LibraryError::custom("Malformed plaintext"))?;
824 MlsMessageOut::from_private_message(ciphertext, self.version())
825 }
826 };
827 Ok(msg)
828 }
829
830 fn is_operational(&self) -> Result<(), MlsGroupStateError> {
833 match self.group_state {
834 MlsGroupState::PendingCommit(_) => Err(MlsGroupStateError::PendingCommit),
835 MlsGroupState::Inactive => Err(MlsGroupStateError::UseAfterEviction),
836 MlsGroupState::Operational => Ok(()),
837 }
838 }
839}
840
841impl MlsGroup {
843 #[cfg(any(feature = "test-utils", test))]
844 pub fn export_group_context(&self) -> &GroupContext {
845 self.context()
846 }
847
848 #[cfg(any(feature = "test-utils", test))]
849 pub fn tree_hash(&self) -> &[u8] {
850 self.public_group().group_context().tree_hash()
851 }
852
853 #[cfg(any(feature = "test-utils", test))]
854 pub(crate) fn message_secrets_test_mut(&mut self) -> &mut MessageSecrets {
855 self.message_secrets_store.message_secrets_mut()
856 }
857
858 #[cfg(any(feature = "test-utils", test))]
859 pub fn print_ratchet_tree(&self, message: &str) {
860 println!("{}: {}", message, self.public_group().export_ratchet_tree());
861 }
862
863 #[cfg(any(feature = "test-utils", test))]
864 pub(crate) fn context_mut(&mut self) -> &mut GroupContext {
865 self.public_group.context_mut()
866 }
867
868 #[cfg(test)]
869 pub(crate) fn set_own_leaf_index(&mut self, own_leaf_index: LeafNodeIndex) {
870 self.own_leaf_index = own_leaf_index;
871 }
872
873 #[cfg(test)]
874 pub(crate) fn own_tree_position(&self) -> TreePosition {
875 TreePosition::new(self.group_id().clone(), self.own_leaf_index())
876 }
877
878 #[cfg(test)]
879 pub(crate) fn message_secrets_store(&self) -> &MessageSecretsStore {
880 &self.message_secrets_store
881 }
882
883 #[cfg(test)]
884 pub(crate) fn resumption_psk_store(&self) -> &ResumptionPskStore {
885 &self.resumption_psk_store
886 }
887
888 #[cfg(test)]
889 pub(crate) fn set_group_context(&mut self, group_context: GroupContext) {
890 self.public_group.set_group_context(group_context)
891 }
892
893 #[cfg(any(test, feature = "test-utils"))]
894 pub fn ensure_persistence(&self, storage: &impl StorageProvider) -> Result<(), LibraryError> {
895 let loaded = MlsGroup::load(storage, self.group_id())
896 .map_err(|_| LibraryError::custom("Failed to load group from storage"))?;
897 let other = loaded.ok_or_else(|| LibraryError::custom("Group not found in storage"))?;
898
899 if self != &other {
900 let mut diagnostics = Vec::new();
901
902 if self.mls_group_config != other.mls_group_config {
903 diagnostics.push(format!(
904 "mls_group_config:\n Current: {:?}\n Loaded: {:?}",
905 self.mls_group_config, other.mls_group_config
906 ));
907 }
908 if self.public_group != other.public_group {
909 diagnostics.push(format!(
910 "public_group:\n Current: {:?}\n Loaded: {:?}",
911 self.public_group, other.public_group
912 ));
913 }
914 if self.group_epoch_secrets != other.group_epoch_secrets {
915 diagnostics.push(format!(
916 "group_epoch_secrets:\n Current: {:?}\n Loaded: {:?}",
917 self.group_epoch_secrets, other.group_epoch_secrets
918 ));
919 }
920 if self.own_leaf_index != other.own_leaf_index {
921 diagnostics.push(format!(
922 "own_leaf_index:\n Current: {:?}\n Loaded: {:?}",
923 self.own_leaf_index, other.own_leaf_index
924 ));
925 }
926 if self.message_secrets_store != other.message_secrets_store {
927 diagnostics.push(format!(
928 "message_secrets_store:\n Current: {:?}\n Loaded: {:?}",
929 self.message_secrets_store, other.message_secrets_store
930 ));
931 }
932 if self.resumption_psk_store != other.resumption_psk_store {
933 diagnostics.push(format!(
934 "resumption_psk_store:\n Current: {:?}\n Loaded: {:?}",
935 self.resumption_psk_store, other.resumption_psk_store
936 ));
937 }
938 if self.own_leaf_nodes != other.own_leaf_nodes {
939 diagnostics.push(format!(
940 "own_leaf_nodes:\n Current: {:?}\n Loaded: {:?}",
941 self.own_leaf_nodes, other.own_leaf_nodes
942 ));
943 }
944 if self.aad != other.aad {
945 diagnostics.push(format!(
946 "aad:\n Current: {:?}\n Loaded: {:?}",
947 self.aad, other.aad
948 ));
949 }
950 if self.group_state != other.group_state {
951 diagnostics.push(format!(
952 "group_state:\n Current: {:?}\n Loaded: {:?}",
953 self.group_state, other.group_state
954 ));
955 }
956 #[cfg(feature = "extensions-draft-08")]
957 if self.application_export_tree != other.application_export_tree {
958 diagnostics.push(format!(
959 "application_export_tree:\n Current: {:?}\n Loaded: {:?}",
960 self.application_export_tree, other.application_export_tree
961 ));
962 }
963
964 log::error!(
965 "Loaded group does not match current group! Differing fields ({}):\n\n{}",
966 diagnostics.len(),
967 diagnostics.join("\n\n")
968 );
969
970 return Err(LibraryError::custom(
971 "Loaded group does not match current group",
972 ));
973 }
974
975 Ok(())
976 }
977}
978
979#[derive(Debug)]
982pub struct StagedWelcome {
983 mls_group_config: MlsGroupJoinConfig,
985 public_group: PublicGroup,
986 group_epoch_secrets: GroupEpochSecrets,
987 own_leaf_index: LeafNodeIndex,
988
989 message_secrets_store: MessageSecretsStore,
996
997 #[cfg(feature = "extensions-draft-08")]
1000 application_export_secret: ApplicationExportSecret,
1001
1002 resumption_psk_store: ResumptionPskStore,
1004
1005 verifiable_group_info: VerifiableGroupInfo,
1007
1008 key_package_bundle: KeyPackageBundle,
1010
1011 path_keypairs: Option<Vec<EncryptionKeyPair>>,
1013}
1014
1015pub struct ProcessedWelcome {
1022 mls_group_config: MlsGroupJoinConfig,
1024
1025 ciphersuite: Ciphersuite,
1028 group_secrets: GroupSecrets,
1029 key_schedule: crate::schedule::KeySchedule,
1030 verifiable_group_info: crate::messages::group_info::VerifiableGroupInfo,
1031 resumption_psk_store: crate::schedule::psk::store::ResumptionPskStore,
1032 key_package_bundle: KeyPackageBundle,
1033}