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(feature = "extensions-draft-08")]
71pub(crate) mod app_ephemeral;
72
73#[cfg(test)]
75pub(crate) mod tests_and_kats;
76
77#[derive(Debug)]
78pub(crate) struct CreateCommitResult {
79 pub(crate) commit: AuthenticatedContent,
80 pub(crate) welcome_option: Option<Welcome>,
81 pub(crate) staged_commit: StagedCommit,
82 pub(crate) group_info: Option<GroupInfo>,
83}
84
85#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
87pub struct Member {
88 pub index: LeafNodeIndex,
90 pub credential: Credential,
92 pub encryption_key: Vec<u8>,
94 pub signature_key: Vec<u8>,
96}
97
98impl Member {
99 pub fn new(
101 index: LeafNodeIndex,
102 encryption_key: Vec<u8>,
103 signature_key: Vec<u8>,
104 credential: Credential,
105 ) -> Self {
106 Self {
107 index,
108 encryption_key,
109 signature_key,
110 credential,
111 }
112 }
113}
114
115#[derive(Debug, Serialize, Deserialize)]
118#[cfg_attr(any(test, feature = "test-utils"), derive(Clone, PartialEq))]
119pub enum PendingCommitState {
120 Member(StagedCommit),
122 External(StagedCommit),
124}
125
126impl PendingCommitState {
127 pub(crate) fn staged_commit(&self) -> &StagedCommit {
130 match self {
131 PendingCommitState::Member(pc) => pc,
132 PendingCommitState::External(pc) => pc,
133 }
134 }
135}
136
137impl From<PendingCommitState> for StagedCommit {
138 fn from(pcs: PendingCommitState) -> Self {
139 match pcs {
140 PendingCommitState::Member(pc) => pc,
141 PendingCommitState::External(pc) => pc,
142 }
143 }
144}
145
146#[derive(Debug, Serialize, Deserialize)]
191#[cfg_attr(any(test, feature = "test-utils"), derive(Clone, PartialEq))]
192pub enum MlsGroupState {
193 PendingCommit(Box<PendingCommitState>),
195 Operational,
197 Inactive,
199}
200
201#[derive(Debug)]
226#[cfg_attr(feature = "test-utils", derive(Clone, PartialEq))]
227pub struct MlsGroup {
228 mls_group_config: MlsGroupJoinConfig,
230 public_group: PublicGroup,
232 group_epoch_secrets: GroupEpochSecrets,
234 own_leaf_index: LeafNodeIndex,
236 message_secrets_store: MessageSecretsStore,
243 resumption_psk_store: ResumptionPskStore,
245 own_leaf_nodes: Vec<LeafNode>,
249 aad: Vec<u8>,
253 group_state: MlsGroupState,
256 #[cfg(feature = "extensions-draft-08")]
260 application_export_tree: Option<ApplicationExportTree>,
261}
262
263impl MlsGroup {
264 pub fn configuration(&self) -> &MlsGroupJoinConfig {
268 &self.mls_group_config
269 }
270
271 pub fn set_configuration<Storage: StorageProvider>(
273 &mut self,
274 storage: &Storage,
275 mls_group_config: &MlsGroupJoinConfig,
276 ) -> Result<(), Storage::Error> {
277 self.mls_group_config = mls_group_config.clone();
278 storage.write_mls_join_config(self.group_id(), mls_group_config)
279 }
280
281 pub fn set_aad(&mut self, aad: Vec<u8>) {
285 self.aad = aad;
286 }
287
288 pub fn aad(&self) -> &[u8] {
291 &self.aad
292 }
293
294 pub fn ciphersuite(&self) -> Ciphersuite {
298 self.public_group.ciphersuite()
299 }
300
301 pub fn confirmation_tag(&self) -> &ConfirmationTag {
303 self.public_group.confirmation_tag()
304 }
305
306 pub fn is_active(&self) -> bool {
309 !matches!(self.group_state, MlsGroupState::Inactive)
310 }
311
312 pub fn credential(&self) -> Result<&Credential, MlsGroupStateError> {
315 if !self.is_active() {
316 return Err(MlsGroupStateError::UseAfterEviction);
317 }
318 self.public_group
319 .leaf(self.own_leaf_index())
320 .map(|node| node.credential())
321 .ok_or_else(|| LibraryError::custom("Own leaf node missing").into())
322 }
323
324 pub fn own_leaf_index(&self) -> LeafNodeIndex {
326 self.own_leaf_index
327 }
328
329 pub fn own_leaf_node(&self) -> Option<&LeafNode> {
331 self.public_group().leaf(self.own_leaf_index())
332 }
333
334 pub fn group_id(&self) -> &GroupId {
336 self.public_group.group_id()
337 }
338
339 pub fn epoch(&self) -> GroupEpoch {
341 self.public_group.group_context().epoch()
342 }
343
344 pub fn pending_proposals(&self) -> impl Iterator<Item = &QueuedProposal> {
346 self.proposal_store().proposals()
347 }
348
349 pub fn pending_commit(&self) -> Option<&StagedCommit> {
353 match self.group_state {
354 MlsGroupState::PendingCommit(ref pending_commit_state) => {
355 Some(pending_commit_state.staged_commit())
356 }
357 MlsGroupState::Operational => None,
358 MlsGroupState::Inactive => None,
359 }
360 }
361
362 pub fn clear_pending_commit<Storage: StorageProvider>(
374 &mut self,
375 storage: &Storage,
376 ) -> Result<(), Storage::Error> {
377 match self.group_state {
378 MlsGroupState::PendingCommit(ref pending_commit_state) => {
379 if let PendingCommitState::Member(_) = **pending_commit_state {
380 self.group_state = MlsGroupState::Operational;
381 storage.write_group_state(self.group_id(), &self.group_state)
382 } else {
383 Ok(())
384 }
385 }
386 MlsGroupState::Operational | MlsGroupState::Inactive => Ok(()),
387 }
388 }
389
390 pub fn clear_pending_proposals<Storage: StorageProvider>(
397 &mut self,
398 storage: &Storage,
399 ) -> Result<(), Storage::Error> {
400 if !self.proposal_store().is_empty() {
402 self.proposal_store_mut().empty();
404
405 storage.clear_proposal_queue::<GroupId, ProposalRef>(self.group_id())?;
407 }
408
409 Ok(())
410 }
411
412 pub fn extensions(&self) -> &Extensions {
414 self.public_group().group_context().extensions()
415 }
416
417 pub fn ext_commit_sender_index(
419 &self,
420 commit: &StagedCommit,
421 ) -> Result<LeafNodeIndex, LibraryError> {
422 self.public_group().ext_commit_sender_index(commit)
423 }
424
425 pub fn load<Storage: crate::storage::StorageProvider>(
429 storage: &Storage,
430 group_id: &GroupId,
431 ) -> Result<Option<MlsGroup>, Storage::Error> {
432 let public_group = PublicGroup::load(storage, group_id)?;
433 let group_epoch_secrets = storage.group_epoch_secrets(group_id)?;
434 let own_leaf_index = storage.own_leaf_index(group_id)?;
435 let message_secrets_store = storage.message_secrets(group_id)?;
436 let resumption_psk_store = storage.resumption_psk_store(group_id)?;
437 let mls_group_config = storage.mls_group_join_config(group_id)?;
438 let own_leaf_nodes = storage.own_leaf_nodes(group_id)?;
439 let group_state = storage.group_state(group_id)?;
440 #[cfg(feature = "extensions-draft-08")]
441 let application_export_tree = storage.application_export_tree(group_id)?;
442
443 let build = || -> Option<Self> {
444 Some(Self {
445 public_group: public_group?,
446 group_epoch_secrets: group_epoch_secrets?,
447 own_leaf_index: own_leaf_index?,
448 message_secrets_store: message_secrets_store?,
449 resumption_psk_store: resumption_psk_store?,
450 mls_group_config: mls_group_config?,
451 own_leaf_nodes,
452 aad: vec![],
453 group_state: group_state?,
454 #[cfg(feature = "extensions-draft-08")]
455 application_export_tree,
456 })
457 };
458
459 Ok(build())
460 }
461
462 pub fn delete<Storage: crate::storage::StorageProvider>(
466 &mut self,
467 storage: &Storage,
468 ) -> Result<(), Storage::Error> {
469 PublicGroup::delete(storage, self.group_id())?;
470 storage.delete_own_leaf_index(self.group_id())?;
471 storage.delete_group_epoch_secrets(self.group_id())?;
472 storage.delete_message_secrets(self.group_id())?;
473 storage.delete_all_resumption_psk_secrets(self.group_id())?;
474 storage.delete_group_config(self.group_id())?;
475 storage.delete_own_leaf_nodes(self.group_id())?;
476 storage.delete_group_state(self.group_id())?;
477 storage.clear_proposal_queue::<GroupId, ProposalRef>(self.group_id())?;
478
479 #[cfg(feature = "extensions-draft-08")]
480 storage.delete_application_export_tree::<_, ApplicationExportTree>(self.group_id())?;
481
482 self.proposal_store_mut().empty();
483 storage.delete_encryption_epoch_key_pairs(
484 self.group_id(),
485 &self.epoch(),
486 self.own_leaf_index().u32(),
487 )?;
488
489 Ok(())
490 }
491
492 pub fn export_ratchet_tree(&self) -> RatchetTree {
496 self.public_group().export_ratchet_tree()
497 }
498}
499
500impl MlsGroup {
502 pub(crate) fn required_capabilities(&self) -> Option<&RequiredCapabilitiesExtension> {
504 self.public_group.required_capabilities()
505 }
506
507 pub(crate) fn group_epoch_secrets(&self) -> &GroupEpochSecrets {
509 &self.group_epoch_secrets
510 }
511
512 pub(crate) fn message_secrets(&self) -> &MessageSecrets {
514 self.message_secrets_store.message_secrets()
515 }
516
517 pub(crate) fn set_max_past_epochs(&mut self, max_past_epochs: usize) {
521 self.message_secrets_store.resize(max_past_epochs);
522 }
523
524 pub(crate) fn message_secrets_mut(
526 &mut self,
527 epoch: GroupEpoch,
528 ) -> Result<&mut MessageSecrets, SecretTreeError> {
529 if epoch < self.context().epoch() {
530 self.message_secrets_store
531 .secrets_for_epoch_mut(epoch)
532 .ok_or(SecretTreeError::TooDistantInThePast)
533 } else {
534 Ok(self.message_secrets_store.message_secrets_mut())
535 }
536 }
537
538 pub(crate) fn message_secrets_for_epoch(
540 &self,
541 epoch: GroupEpoch,
542 ) -> Result<&MessageSecrets, SecretTreeError> {
543 if epoch < self.context().epoch() {
544 self.message_secrets_store
545 .secrets_for_epoch(epoch)
546 .ok_or(SecretTreeError::TooDistantInThePast)
547 } else {
548 Ok(self.message_secrets_store.message_secrets())
549 }
550 }
551
552 pub(crate) fn message_secrets_and_leaves_mut(
558 &mut self,
559 epoch: GroupEpoch,
560 ) -> Result<(&mut MessageSecrets, &[Member]), SecretTreeError> {
561 if epoch < self.context().epoch() {
562 self.message_secrets_store
563 .secrets_and_leaves_for_epoch_mut(epoch)
564 .ok_or(SecretTreeError::TooDistantInThePast)
565 } else {
566 Ok((self.message_secrets_store.message_secrets_mut(), &[]))
569 }
570 }
571
572 pub(crate) fn create_group_context_ext_proposal<Provider: OpenMlsProvider>(
574 &self,
575 framing_parameters: FramingParameters,
576 extensions: Extensions,
577 signer: &impl Signer,
578 ) -> Result<AuthenticatedContent, CreateGroupContextExtProposalError<Provider::StorageError>>
579 {
580 let required_extension = extensions
582 .iter()
583 .find(|extension| extension.extension_type() == ExtensionType::RequiredCapabilities);
584 if let Some(required_extension) = required_extension {
585 let required_capabilities = required_extension.as_required_capabilities_extension()?;
586 self.own_leaf_node()
588 .ok_or_else(|| LibraryError::custom("Tree has no own leaf."))?
589 .capabilities()
590 .supports_required_capabilities(required_capabilities)?;
591
592 self.public_group()
595 .check_extension_support(required_capabilities.extension_types())?;
596 }
597 let proposal = GroupContextExtensionProposal::new(extensions);
598 let proposal = Proposal::GroupContextExtensions(Box::new(proposal));
599 AuthenticatedContent::member_proposal(
600 framing_parameters,
601 self.own_leaf_index(),
602 proposal,
603 self.context(),
604 signer,
605 )
606 .map_err(|e| e.into())
607 }
608
609 pub(crate) fn encrypt<Provider: OpenMlsProvider>(
611 &mut self,
612 public_message: AuthenticatedContent,
613 provider: &Provider,
614 ) -> Result<PrivateMessage, MessageEncryptionError<Provider::StorageError>> {
615 let padding_size = self.configuration().padding_size();
616 let msg = PrivateMessage::try_from_authenticated_content(
617 provider.crypto(),
618 provider.rand(),
619 &public_message,
620 self.ciphersuite(),
621 self.message_secrets_store.message_secrets_mut(),
622 padding_size,
623 )?;
624
625 provider
626 .storage()
627 .write_message_secrets(self.group_id(), &self.message_secrets_store)
628 .map_err(MessageEncryptionError::StorageError)?;
629
630 Ok(msg)
631 }
632
633 pub(crate) fn framing_parameters(&self) -> FramingParameters<'_> {
635 FramingParameters::new(
636 &self.aad,
637 self.mls_group_config.wire_format_policy().outgoing(),
638 )
639 }
640
641 pub(crate) fn proposal_store(&self) -> &ProposalStore {
643 self.public_group.proposal_store()
644 }
645
646 pub(crate) fn proposal_store_mut(&mut self) -> &mut ProposalStore {
648 self.public_group.proposal_store_mut()
649 }
650
651 pub(crate) fn context(&self) -> &GroupContext {
653 self.public_group.group_context()
654 }
655
656 pub(crate) fn version(&self) -> ProtocolVersion {
658 self.public_group.version()
659 }
660
661 #[inline]
663 pub(crate) fn reset_aad(&mut self) {
664 self.aad.clear();
665 }
666
667 pub(crate) fn public_group(&self) -> &PublicGroup {
669 &self.public_group
670 }
671}
672
673impl MlsGroup {
675 pub(super) fn store_epoch_keypairs<Storage: StorageProvider>(
680 &self,
681 store: &Storage,
682 keypair_references: &[EncryptionKeyPair],
683 ) -> Result<(), Storage::Error> {
684 store.write_encryption_epoch_key_pairs(
685 self.group_id(),
686 &self.context().epoch(),
687 self.own_leaf_index().u32(),
688 keypair_references,
689 )
690 }
691
692 pub(super) fn read_epoch_keypairs<Storage: StorageProvider>(
697 &self,
698 store: &Storage,
699 ) -> Result<Vec<EncryptionKeyPair>, Storage::Error> {
700 store.encryption_epoch_key_pairs(
701 self.group_id(),
702 &self.context().epoch(),
703 self.own_leaf_index().u32(),
704 )
705 }
706
707 pub(super) fn delete_previous_epoch_keypairs<Storage: StorageProvider>(
712 &self,
713 store: &Storage,
714 ) -> Result<(), Storage::Error> {
715 store.delete_encryption_epoch_key_pairs(
716 self.group_id(),
717 &GroupEpoch::from(self.context().epoch().as_u64() - 1),
718 self.own_leaf_index().u32(),
719 )
720 }
721
722 pub(super) fn store<Storage: crate::storage::StorageProvider>(
725 &self,
726 storage: &Storage,
727 ) -> Result<(), Storage::Error> {
728 self.public_group.store(storage)?;
729 storage.write_group_epoch_secrets(self.group_id(), &self.group_epoch_secrets)?;
730 storage.write_own_leaf_index(self.group_id(), &self.own_leaf_index)?;
731 storage.write_message_secrets(self.group_id(), &self.message_secrets_store)?;
732 storage.write_resumption_psk_store(self.group_id(), &self.resumption_psk_store)?;
733 storage.write_mls_join_config(self.group_id(), &self.mls_group_config)?;
734 storage.write_group_state(self.group_id(), &self.group_state)?;
735 #[cfg(feature = "extensions-draft-08")]
736 if let Some(application_export_tree) = &self.application_export_tree {
737 storage.write_application_export_tree(self.group_id(), application_export_tree)?;
738 }
739
740 Ok(())
741 }
742
743 fn content_to_mls_message(
747 &mut self,
748 mls_auth_content: AuthenticatedContent,
749 provider: &impl OpenMlsProvider,
750 ) -> Result<MlsMessageOut, LibraryError> {
751 let msg = match self.configuration().wire_format_policy().outgoing() {
752 OutgoingWireFormatPolicy::AlwaysPlaintext => {
753 let mut plaintext: PublicMessage = mls_auth_content.into();
754 if plaintext.sender().is_member() {
756 plaintext.set_membership_tag(
757 provider.crypto(),
758 self.ciphersuite(),
759 self.message_secrets().membership_key(),
760 self.message_secrets().serialized_context(),
761 )?;
762 }
763 plaintext.into()
764 }
765 OutgoingWireFormatPolicy::AlwaysCiphertext => {
766 let ciphertext = self
767 .encrypt(mls_auth_content, provider)
768 .map_err(|_| LibraryError::custom("Malformed plaintext"))?;
770 MlsMessageOut::from_private_message(ciphertext, self.version())
771 }
772 };
773 Ok(msg)
774 }
775
776 fn is_operational(&self) -> Result<(), MlsGroupStateError> {
779 match self.group_state {
780 MlsGroupState::PendingCommit(_) => Err(MlsGroupStateError::PendingCommit),
781 MlsGroupState::Inactive => Err(MlsGroupStateError::UseAfterEviction),
782 MlsGroupState::Operational => Ok(()),
783 }
784 }
785}
786
787impl MlsGroup {
789 #[cfg(any(feature = "test-utils", test))]
790 pub fn export_group_context(&self) -> &GroupContext {
791 self.context()
792 }
793
794 #[cfg(any(feature = "test-utils", test))]
795 pub fn tree_hash(&self) -> &[u8] {
796 self.public_group().group_context().tree_hash()
797 }
798
799 #[cfg(any(feature = "test-utils", test))]
800 pub(crate) fn message_secrets_test_mut(&mut self) -> &mut MessageSecrets {
801 self.message_secrets_store.message_secrets_mut()
802 }
803
804 #[cfg(any(feature = "test-utils", test))]
805 pub fn print_ratchet_tree(&self, message: &str) {
806 println!("{}: {}", message, self.public_group().export_ratchet_tree());
807 }
808
809 #[cfg(any(feature = "test-utils", test))]
810 pub(crate) fn context_mut(&mut self) -> &mut GroupContext {
811 self.public_group.context_mut()
812 }
813
814 #[cfg(test)]
815 pub(crate) fn set_own_leaf_index(&mut self, own_leaf_index: LeafNodeIndex) {
816 self.own_leaf_index = own_leaf_index;
817 }
818
819 #[cfg(test)]
820 pub(crate) fn own_tree_position(&self) -> TreePosition {
821 TreePosition::new(self.group_id().clone(), self.own_leaf_index())
822 }
823
824 #[cfg(test)]
825 pub(crate) fn message_secrets_store(&self) -> &MessageSecretsStore {
826 &self.message_secrets_store
827 }
828
829 #[cfg(test)]
830 pub(crate) fn resumption_psk_store(&self) -> &ResumptionPskStore {
831 &self.resumption_psk_store
832 }
833
834 #[cfg(test)]
835 pub(crate) fn set_group_context(&mut self, group_context: GroupContext) {
836 self.public_group.set_group_context(group_context)
837 }
838
839 #[cfg(any(test, feature = "test-utils"))]
840 pub fn ensure_persistence(&self, storage: &impl StorageProvider) -> Result<(), LibraryError> {
841 let loaded = MlsGroup::load(storage, self.group_id())
842 .map_err(|_| LibraryError::custom("Failed to load group from storage"))?;
843 let other = loaded.ok_or_else(|| LibraryError::custom("Group not found in storage"))?;
844
845 if self != &other {
846 let mut diagnostics = Vec::new();
847
848 if self.mls_group_config != other.mls_group_config {
849 diagnostics.push(format!(
850 "mls_group_config:\n Current: {:?}\n Loaded: {:?}",
851 self.mls_group_config, other.mls_group_config
852 ));
853 }
854 if self.public_group != other.public_group {
855 diagnostics.push(format!(
856 "public_group:\n Current: {:?}\n Loaded: {:?}",
857 self.public_group, other.public_group
858 ));
859 }
860 if self.group_epoch_secrets != other.group_epoch_secrets {
861 diagnostics.push(format!(
862 "group_epoch_secrets:\n Current: {:?}\n Loaded: {:?}",
863 self.group_epoch_secrets, other.group_epoch_secrets
864 ));
865 }
866 if self.own_leaf_index != other.own_leaf_index {
867 diagnostics.push(format!(
868 "own_leaf_index:\n Current: {:?}\n Loaded: {:?}",
869 self.own_leaf_index, other.own_leaf_index
870 ));
871 }
872 if self.message_secrets_store != other.message_secrets_store {
873 diagnostics.push(format!(
874 "message_secrets_store:\n Current: {:?}\n Loaded: {:?}",
875 self.message_secrets_store, other.message_secrets_store
876 ));
877 }
878 if self.resumption_psk_store != other.resumption_psk_store {
879 diagnostics.push(format!(
880 "resumption_psk_store:\n Current: {:?}\n Loaded: {:?}",
881 self.resumption_psk_store, other.resumption_psk_store
882 ));
883 }
884 if self.own_leaf_nodes != other.own_leaf_nodes {
885 diagnostics.push(format!(
886 "own_leaf_nodes:\n Current: {:?}\n Loaded: {:?}",
887 self.own_leaf_nodes, other.own_leaf_nodes
888 ));
889 }
890 if self.aad != other.aad {
891 diagnostics.push(format!(
892 "aad:\n Current: {:?}\n Loaded: {:?}",
893 self.aad, other.aad
894 ));
895 }
896 if self.group_state != other.group_state {
897 diagnostics.push(format!(
898 "group_state:\n Current: {:?}\n Loaded: {:?}",
899 self.group_state, other.group_state
900 ));
901 }
902 #[cfg(feature = "extensions-draft-08")]
903 if self.application_export_tree != other.application_export_tree {
904 diagnostics.push(format!(
905 "application_export_tree:\n Current: {:?}\n Loaded: {:?}",
906 self.application_export_tree, other.application_export_tree
907 ));
908 }
909
910 log::error!(
911 "Loaded group does not match current group! Differing fields ({}):\n\n{}",
912 diagnostics.len(),
913 diagnostics.join("\n\n")
914 );
915
916 return Err(LibraryError::custom(
917 "Loaded group does not match current group",
918 ));
919 }
920
921 Ok(())
922 }
923}
924
925#[derive(Debug)]
928pub struct StagedWelcome {
929 mls_group_config: MlsGroupJoinConfig,
931 public_group: PublicGroup,
932 group_epoch_secrets: GroupEpochSecrets,
933 own_leaf_index: LeafNodeIndex,
934
935 message_secrets_store: MessageSecretsStore,
942
943 #[cfg(feature = "extensions-draft-08")]
946 application_export_secret: ApplicationExportSecret,
947
948 resumption_psk_store: ResumptionPskStore,
950
951 verifiable_group_info: VerifiableGroupInfo,
953
954 key_package_bundle: KeyPackageBundle,
956
957 path_keypairs: Option<Vec<EncryptionKeyPair>>,
959}
960
961pub struct ProcessedWelcome {
968 mls_group_config: MlsGroupJoinConfig,
970
971 ciphersuite: Ciphersuite,
974 group_secrets: GroupSecrets,
975 key_schedule: crate::schedule::KeySchedule,
976 verifiable_group_info: crate::messages::group_info::VerifiableGroupInfo,
977 resumption_psk_store: crate::schedule::psk::store::ResumptionPskStore,
978 key_package_bundle: KeyPackageBundle,
979}