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 #[cfg(feature = "extensions-draft-08")]
258 safe_aad: SafeAad,
259 group_state: MlsGroupState,
262 #[cfg(feature = "extensions-draft-08")]
266 application_export_tree: Option<ApplicationExportTree>,
267}
268
269impl MlsGroup {
270 pub fn configuration(&self) -> &MlsGroupJoinConfig {
274 &self.mls_group_config
275 }
276
277 pub fn set_configuration<Storage: StorageProvider>(
279 &mut self,
280 storage: &Storage,
281 mls_group_config: &MlsGroupJoinConfig,
282 ) -> Result<(), Storage::Error> {
283 self.mls_group_config = mls_group_config.clone();
284 storage.write_mls_join_config(self.group_id(), mls_group_config)
285 }
286
287 pub fn set_aad(&mut self, aad: Vec<u8>) {
291 self.aad = aad;
292 }
293
294 pub fn aad(&self) -> &[u8] {
297 &self.aad
298 }
299
300 #[cfg(feature = "extensions-draft-08")]
310 pub fn set_safe_aad(&mut self, items: Vec<SafeAadItem>) -> Result<(), SafeAadError> {
311 self.safe_aad = SafeAad::from_items(items)?;
312 Ok(())
313 }
314
315 #[cfg(feature = "extensions-draft-08")]
318 pub fn safe_aad_items(&self) -> &[SafeAadItem] {
319 self.safe_aad.items()
320 }
321
322 pub fn ciphersuite(&self) -> Ciphersuite {
326 self.public_group.ciphersuite()
327 }
328
329 pub fn confirmation_tag(&self) -> &ConfirmationTag {
331 self.public_group.confirmation_tag()
332 }
333
334 pub fn is_active(&self) -> bool {
337 !matches!(self.group_state, MlsGroupState::Inactive)
338 }
339
340 pub fn credential(&self) -> Result<&Credential, MlsGroupStateError> {
343 if !self.is_active() {
344 return Err(MlsGroupStateError::UseAfterEviction);
345 }
346 self.public_group
347 .leaf(self.own_leaf_index())
348 .map(|node| node.credential())
349 .ok_or_else(|| LibraryError::custom("Own leaf node missing").into())
350 }
351
352 pub fn own_leaf_index(&self) -> LeafNodeIndex {
354 self.own_leaf_index
355 }
356
357 pub fn own_leaf_node(&self) -> Option<&LeafNode> {
359 self.public_group().leaf(self.own_leaf_index())
360 }
361
362 pub fn group_id(&self) -> &GroupId {
364 self.public_group.group_id()
365 }
366
367 pub fn epoch(&self) -> GroupEpoch {
369 self.public_group.group_context().epoch()
370 }
371
372 pub fn pending_proposals(&self) -> impl Iterator<Item = &QueuedProposal> {
374 self.proposal_store().proposals()
375 }
376
377 pub fn treesync(&self) -> &TreeSync {
379 self.public_group.treesync()
380 }
381
382 pub fn pending_commit(&self) -> Option<&StagedCommit> {
386 match self.group_state {
387 MlsGroupState::PendingCommit(ref pending_commit_state) => {
388 Some(pending_commit_state.staged_commit())
389 }
390 MlsGroupState::Operational => None,
391 MlsGroupState::Inactive => None,
392 }
393 }
394
395 pub fn clear_pending_commit<Storage: StorageProvider>(
407 &mut self,
408 storage: &Storage,
409 ) -> Result<(), Storage::Error> {
410 match self.group_state {
411 MlsGroupState::PendingCommit(ref pending_commit_state) => {
412 if let PendingCommitState::Member(_) = **pending_commit_state {
413 self.group_state = MlsGroupState::Operational;
414 storage.write_group_state(self.group_id(), &self.group_state)
415 } else {
416 Ok(())
417 }
418 }
419 MlsGroupState::Operational | MlsGroupState::Inactive => Ok(()),
420 }
421 }
422
423 pub fn clear_pending_proposals<Storage: StorageProvider>(
430 &mut self,
431 storage: &Storage,
432 ) -> Result<(), Storage::Error> {
433 if !self.proposal_store().is_empty() {
435 self.proposal_store_mut().empty();
437
438 storage.clear_proposal_queue::<GroupId, ProposalRef>(self.group_id())?;
440 }
441
442 Ok(())
443 }
444
445 pub fn extensions(&self) -> &Extensions<GroupContext> {
447 self.public_group().group_context().extensions()
448 }
449
450 pub fn ext_commit_sender_index(
452 &self,
453 commit: &StagedCommit,
454 ) -> Result<LeafNodeIndex, LibraryError> {
455 self.public_group().ext_commit_sender_index(commit)
456 }
457
458 pub fn load<Storage: crate::storage::StorageProvider>(
462 storage: &Storage,
463 group_id: &GroupId,
464 ) -> Result<Option<MlsGroup>, Storage::Error> {
465 let public_group = PublicGroup::load(storage, group_id)?;
466 let group_epoch_secrets = storage.group_epoch_secrets(group_id)?;
467 let own_leaf_index = storage.own_leaf_index(group_id)?;
468 let message_secrets_store = storage.message_secrets(group_id)?;
469 let resumption_psk_store = storage.resumption_psk_store(group_id)?;
470 let mls_group_config = storage.mls_group_join_config(group_id)?;
471 let own_leaf_nodes = storage.own_leaf_nodes(group_id)?;
472 let group_state = storage.group_state(group_id)?;
473 #[cfg(feature = "extensions-draft-08")]
474 let application_export_tree = storage.application_export_tree(group_id)?;
475
476 let build = || -> Option<Self> {
477 Some(Self {
478 public_group: public_group?,
479 group_epoch_secrets: group_epoch_secrets?,
480 own_leaf_index: own_leaf_index?,
481 message_secrets_store: message_secrets_store?,
482 resumption_psk_store: resumption_psk_store?,
483 mls_group_config: mls_group_config?,
484 own_leaf_nodes,
485 aad: vec![],
486 #[cfg(feature = "extensions-draft-08")]
487 safe_aad: SafeAad::empty(),
488 group_state: group_state?,
489 #[cfg(feature = "extensions-draft-08")]
490 application_export_tree,
491 })
492 };
493
494 Ok(build())
495 }
496
497 pub fn delete<Storage: crate::storage::StorageProvider>(
501 &mut self,
502 storage: &Storage,
503 ) -> Result<(), Storage::Error> {
504 PublicGroup::delete(storage, self.group_id())?;
505 storage.delete_own_leaf_index(self.group_id())?;
506 storage.delete_group_epoch_secrets(self.group_id())?;
507 storage.delete_message_secrets(self.group_id())?;
508 storage.delete_all_resumption_psk_secrets(self.group_id())?;
509 storage.delete_group_config(self.group_id())?;
510 storage.delete_own_leaf_nodes(self.group_id())?;
511 storage.delete_group_state(self.group_id())?;
512 storage.clear_proposal_queue::<GroupId, ProposalRef>(self.group_id())?;
513
514 #[cfg(feature = "extensions-draft-08")]
515 storage.delete_application_export_tree::<_, ApplicationExportTree>(self.group_id())?;
516
517 self.proposal_store_mut().empty();
518 storage.delete_encryption_epoch_key_pairs(
519 self.group_id(),
520 &self.epoch(),
521 self.own_leaf_index().u32(),
522 )?;
523
524 Ok(())
525 }
526
527 pub fn export_ratchet_tree(&self) -> RatchetTree {
531 self.public_group().export_ratchet_tree()
532 }
533}
534
535impl MlsGroup {
537 pub(crate) fn required_capabilities(&self) -> Option<&RequiredCapabilitiesExtension> {
539 self.public_group.required_capabilities()
540 }
541
542 pub(crate) fn group_epoch_secrets(&self) -> &GroupEpochSecrets {
544 &self.group_epoch_secrets
545 }
546
547 pub(crate) fn message_secrets(&self) -> &MessageSecrets {
549 self.message_secrets_store.message_secrets()
550 }
551
552 pub(crate) fn resize_message_secrets_store(&mut self, policy: &PastEpochDeletionPolicy) {
556 self.message_secrets_store.resize(policy);
557 }
558
559 pub fn past_epoch_deletion_policy(&self) -> &PastEpochDeletionPolicy {
561 self.mls_group_config.past_epoch_deletion_policy()
562 }
563
564 pub fn set_past_epoch_deletion_policy<Provider: OpenMlsProvider>(
566 &mut self,
567 provider: &Provider,
568 policy: PastEpochDeletionPolicy,
569 ) -> Result<(), SetPastEpochDeletionPolicyError<Provider::StorageError>> {
570 self.resize_message_secrets_store(&policy);
572
573 self.mls_group_config.past_epoch_deletion_policy = policy;
575
576 provider
578 .storage()
579 .write_mls_join_config(self.group_id(), &self.mls_group_config)?;
580
581 provider
583 .storage()
584 .write_message_secrets(self.group_id(), &self.message_secrets_store)?;
585
586 Ok(())
587 }
588
589 pub(crate) fn message_secrets_for_epoch_mut(
591 &mut self,
592 epoch: GroupEpoch,
593 ) -> Result<&mut MessageSecrets, SecretTreeError> {
594 if epoch < self.context().epoch() {
595 self.message_secrets_store
596 .secrets_for_epoch_mut(epoch)
597 .ok_or(SecretTreeError::TooDistantInThePast)
598 } else {
599 Ok(self.message_secrets_store.message_secrets_mut())
600 }
601 }
602
603 pub(crate) fn message_secrets_for_epoch(
605 &self,
606 epoch: GroupEpoch,
607 ) -> Result<&MessageSecrets, SecretTreeError> {
608 if epoch < self.context().epoch() {
609 self.message_secrets_store
610 .secrets_for_epoch(epoch)
611 .ok_or(SecretTreeError::TooDistantInThePast)
612 } else {
613 Ok(self.message_secrets_store.message_secrets())
614 }
615 }
616
617 pub(crate) fn message_secrets_and_leaves(
623 &self,
624 epoch: GroupEpoch,
625 ) -> Result<(&MessageSecrets, &[Member]), SecretTreeError> {
626 if epoch < self.context().epoch() {
627 self.message_secrets_store
628 .secrets_and_leaves_for_epoch(epoch)
629 .ok_or(SecretTreeError::TooDistantInThePast)
630 } else {
631 Ok((self.message_secrets_store.message_secrets(), &[]))
634 }
635 }
636
637 pub(crate) fn create_group_context_ext_proposal<Provider: OpenMlsProvider>(
639 &self,
640 framing_parameters: FramingParameters,
641 extensions: Extensions<GroupContext>,
642 signer: &impl Signer,
643 ) -> Result<AuthenticatedContent, CreateGroupContextExtProposalError<Provider::StorageError>>
644 {
645 let required_extension = extensions
647 .iter()
648 .find(|extension| extension.extension_type() == ExtensionType::RequiredCapabilities);
649 if let Some(required_extension) = required_extension {
650 let required_capabilities = required_extension.as_required_capabilities_extension()?;
651 self.own_leaf_node()
653 .ok_or_else(|| LibraryError::custom("Tree has no own leaf."))?
654 .capabilities()
655 .supports_required_capabilities(required_capabilities)?;
656
657 self.public_group()
660 .check_extension_support(required_capabilities.extension_types())?;
661 }
662 let proposal = GroupContextExtensionProposal::new(extensions);
663 let proposal = Proposal::GroupContextExtensions(Box::new(proposal));
664 AuthenticatedContent::member_proposal(
665 framing_parameters,
666 self.own_leaf_index(),
667 proposal,
668 self.context(),
669 signer,
670 )
671 .map_err(|e| e.into())
672 }
673
674 pub(crate) fn encrypt<Provider: OpenMlsProvider>(
676 &mut self,
677 public_message: AuthenticatedContent,
678 provider: &Provider,
679 ) -> Result<EncryptionOutput, MessageEncryptionError<Provider::StorageError>> {
680 let padding_size = self.configuration().padding_size();
681 let msg = PrivateMessage::try_from_authenticated_content(
682 provider.crypto(),
683 provider.rand(),
684 &public_message,
685 self.ciphersuite(),
686 self.message_secrets_store.message_secrets_mut(),
687 padding_size,
688 )?;
689
690 provider
691 .storage()
692 .write_message_secrets(self.group_id(), &self.message_secrets_store)
693 .map_err(MessageEncryptionError::StorageError)?;
694
695 Ok(msg)
696 }
697
698 pub(crate) fn outgoing_wire_format(&self) -> WireFormat {
700 self.mls_group_config.wire_format_policy().outgoing().into()
701 }
702
703 pub(crate) fn outgoing_authenticated_data(&self) -> Result<Vec<u8>, LibraryError> {
709 #[cfg(feature = "extensions-draft-08")]
710 {
711 self.assembled_authenticated_data()
712 }
713 #[cfg(not(feature = "extensions-draft-08"))]
714 {
715 Ok(self.aad.clone())
716 }
717 }
718
719 #[cfg(feature = "extensions-draft-08")]
724 pub(crate) fn assembled_authenticated_data(&self) -> Result<Vec<u8>, LibraryError> {
725 if !self.context().safe_aad_required() {
726 return Ok(self.aad.clone());
727 }
728 crate::framing::safe_aad::assemble_authenticated_data(&self.safe_aad, &self.aad)
729 .map_err(|_| LibraryError::custom("SafeAad serialization failed"))
730 }
731
732 pub fn delete_past_epoch_secrets<Provider: OpenMlsProvider>(
736 &mut self,
737 provider: &Provider,
738 policy: PastEpochDeletion,
739 ) -> Result<(), DeletePastEpochSecretsError<Provider::StorageError>> {
740 self.message_secrets_store.delete_past_epoch_secrets(policy);
742 provider
744 .storage()
745 .write_message_secrets(self.group_id(), &self.message_secrets_store)?;
746
747 Ok(())
748 }
749
750 pub fn proposal_store(&self) -> &ProposalStore {
752 self.public_group.proposal_store()
753 }
754
755 pub(crate) fn proposal_store_mut(&mut self) -> &mut ProposalStore {
757 self.public_group.proposal_store_mut()
758 }
759
760 pub(crate) fn context(&self) -> &GroupContext {
762 self.public_group.group_context()
763 }
764
765 pub(crate) fn version(&self) -> ProtocolVersion {
767 self.public_group.version()
768 }
769
770 #[inline]
772 pub(crate) fn reset_aad(&mut self) {
773 self.aad.clear();
774 #[cfg(feature = "extensions-draft-08")]
775 {
776 self.safe_aad = SafeAad::empty();
777 }
778 }
779
780 pub fn public_group(&self) -> &PublicGroup {
782 &self.public_group
783 }
784}
785
786impl MlsGroup {
788 pub(super) fn store_epoch_keypairs<Storage: StorageProvider>(
793 &self,
794 store: &Storage,
795 keypair_references: &[EncryptionKeyPair],
796 ) -> Result<(), Storage::Error> {
797 store.write_encryption_epoch_key_pairs(
798 self.group_id(),
799 &self.context().epoch(),
800 self.own_leaf_index().u32(),
801 keypair_references,
802 )
803 }
804
805 pub(super) fn read_epoch_keypairs<Storage: StorageProvider>(
810 &self,
811 store: &Storage,
812 ) -> Result<Vec<EncryptionKeyPair>, Storage::Error> {
813 store.encryption_epoch_key_pairs(
814 self.group_id(),
815 &self.context().epoch(),
816 self.own_leaf_index().u32(),
817 )
818 }
819
820 pub(super) fn delete_previous_epoch_keypairs<Storage: StorageProvider>(
825 &self,
826 store: &Storage,
827 ) -> Result<(), Storage::Error> {
828 store.delete_encryption_epoch_key_pairs(
829 self.group_id(),
830 &GroupEpoch::from(self.context().epoch().as_u64() - 1),
831 self.own_leaf_index().u32(),
832 )
833 }
834
835 pub(super) fn store<Storage: crate::storage::StorageProvider>(
838 &self,
839 storage: &Storage,
840 ) -> Result<(), Storage::Error> {
841 self.public_group.store(storage)?;
842 storage.write_group_epoch_secrets(self.group_id(), &self.group_epoch_secrets)?;
843 storage.write_own_leaf_index(self.group_id(), &self.own_leaf_index)?;
844 storage.write_message_secrets(self.group_id(), &self.message_secrets_store)?;
845 storage.write_resumption_psk_store(self.group_id(), &self.resumption_psk_store)?;
846 storage.write_mls_join_config(self.group_id(), &self.mls_group_config)?;
847 storage.write_group_state(self.group_id(), &self.group_state)?;
848 #[cfg(feature = "extensions-draft-08")]
849 if let Some(application_export_tree) = &self.application_export_tree {
850 storage.write_application_export_tree(self.group_id(), application_export_tree)?;
851 }
852
853 Ok(())
854 }
855
856 fn content_to_mls_message(
860 &mut self,
861 mls_auth_content: AuthenticatedContent,
862 provider: &impl OpenMlsProvider,
863 ) -> Result<MlsMessageOut, LibraryError> {
864 let msg = match self.configuration().wire_format_policy().outgoing() {
865 OutgoingWireFormatPolicy::AlwaysPlaintext => {
866 let mut plaintext: PublicMessage = mls_auth_content.into();
867 if plaintext.sender().is_member() {
869 plaintext.set_membership_tag(
870 provider.crypto(),
871 self.ciphersuite(),
872 self.message_secrets().membership_key(),
873 self.message_secrets().serialized_context(),
874 )?;
875 }
876 plaintext.into()
877 }
878 OutgoingWireFormatPolicy::AlwaysCiphertext => {
879 let EncryptionOutput {
882 private_message, ..
883 } = self
884 .encrypt(mls_auth_content, provider)
885 .map_err(|_| LibraryError::custom("Malformed plaintext"))?;
887 MlsMessageOut::from_private_message(private_message, self.version())
888 }
889 };
890 Ok(msg)
891 }
892
893 fn is_operational(&self) -> Result<(), MlsGroupStateError> {
896 match self.group_state {
897 MlsGroupState::PendingCommit(_) => Err(MlsGroupStateError::PendingCommit),
898 MlsGroupState::Inactive => Err(MlsGroupStateError::UseAfterEviction),
899 MlsGroupState::Operational => Ok(()),
900 }
901 }
902}
903
904impl MlsGroup {
906 #[cfg(any(feature = "test-utils", test))]
907 pub fn export_group_context(&self) -> &GroupContext {
908 self.context()
909 }
910
911 #[cfg(any(feature = "test-utils", test))]
912 pub fn tree_hash(&self) -> &[u8] {
913 self.public_group().group_context().tree_hash()
914 }
915
916 #[cfg(any(feature = "test-utils", test))]
917 pub(crate) fn message_secrets_test_mut(&mut self) -> &mut MessageSecrets {
918 self.message_secrets_store.message_secrets_mut()
919 }
920
921 #[cfg(any(feature = "test-utils", test))]
922 pub fn print_ratchet_tree(&self, message: &str) {
923 println!("{}: {}", message, self.public_group().export_ratchet_tree());
924 }
925
926 #[cfg(any(feature = "test-utils", test))]
927 pub(crate) fn context_mut(&mut self) -> &mut GroupContext {
928 self.public_group.context_mut()
929 }
930
931 #[cfg(test)]
932 pub(crate) fn set_own_leaf_index(&mut self, own_leaf_index: LeafNodeIndex) {
933 self.own_leaf_index = own_leaf_index;
934 }
935
936 #[cfg(test)]
937 pub(crate) fn own_tree_position(&self) -> TreePosition {
938 TreePosition::new(self.group_id().clone(), self.own_leaf_index())
939 }
940
941 #[cfg(test)]
942 pub(crate) fn message_secrets_store(&self) -> &MessageSecretsStore {
943 &self.message_secrets_store
944 }
945
946 #[cfg(test)]
947 pub(crate) fn resumption_psk_store(&self) -> &ResumptionPskStore {
948 &self.resumption_psk_store
949 }
950
951 #[cfg(test)]
952 pub(crate) fn set_group_context(&mut self, group_context: GroupContext) {
953 self.public_group.set_group_context(group_context)
954 }
955
956 #[cfg(any(test, feature = "test-utils"))]
957 pub fn ensure_persistence(&self, storage: &impl StorageProvider) -> Result<(), LibraryError> {
958 let loaded = MlsGroup::load(storage, self.group_id())
959 .map_err(|_| LibraryError::custom("Failed to load group from storage"))?;
960 let other = loaded.ok_or_else(|| LibraryError::custom("Group not found in storage"))?;
961
962 if self != &other {
963 let mut diagnostics = Vec::new();
964
965 if self.mls_group_config != other.mls_group_config {
966 diagnostics.push(format!(
967 "mls_group_config:\n Current: {:?}\n Loaded: {:?}",
968 self.mls_group_config, other.mls_group_config
969 ));
970 }
971 if self.public_group != other.public_group {
972 diagnostics.push(format!(
973 "public_group:\n Current: {:?}\n Loaded: {:?}",
974 self.public_group, other.public_group
975 ));
976 }
977 if self.group_epoch_secrets != other.group_epoch_secrets {
978 diagnostics.push(format!(
979 "group_epoch_secrets:\n Current: {:?}\n Loaded: {:?}",
980 self.group_epoch_secrets, other.group_epoch_secrets
981 ));
982 }
983 if self.own_leaf_index != other.own_leaf_index {
984 diagnostics.push(format!(
985 "own_leaf_index:\n Current: {:?}\n Loaded: {:?}",
986 self.own_leaf_index, other.own_leaf_index
987 ));
988 }
989 if self.message_secrets_store != other.message_secrets_store {
990 diagnostics.push(format!(
991 "message_secrets_store:\n Current: {:?}\n Loaded: {:?}",
992 self.message_secrets_store, other.message_secrets_store
993 ));
994 }
995 if self.resumption_psk_store != other.resumption_psk_store {
996 diagnostics.push(format!(
997 "resumption_psk_store:\n Current: {:?}\n Loaded: {:?}",
998 self.resumption_psk_store, other.resumption_psk_store
999 ));
1000 }
1001 if self.own_leaf_nodes != other.own_leaf_nodes {
1002 diagnostics.push(format!(
1003 "own_leaf_nodes:\n Current: {:?}\n Loaded: {:?}",
1004 self.own_leaf_nodes, other.own_leaf_nodes
1005 ));
1006 }
1007 if self.aad != other.aad {
1008 diagnostics.push(format!(
1009 "aad:\n Current: {:?}\n Loaded: {:?}",
1010 self.aad, other.aad
1011 ));
1012 }
1013 if self.group_state != other.group_state {
1014 diagnostics.push(format!(
1015 "group_state:\n Current: {:?}\n Loaded: {:?}",
1016 self.group_state, other.group_state
1017 ));
1018 }
1019 #[cfg(feature = "extensions-draft-08")]
1020 if self.application_export_tree != other.application_export_tree {
1021 diagnostics.push(format!(
1022 "application_export_tree:\n Current: {:?}\n Loaded: {:?}",
1023 self.application_export_tree, other.application_export_tree
1024 ));
1025 }
1026
1027 log::error!(
1028 "Loaded group does not match current group! Differing fields ({}):\n\n{}",
1029 diagnostics.len(),
1030 diagnostics.join("\n\n")
1031 );
1032
1033 return Err(LibraryError::custom(
1034 "Loaded group does not match current group",
1035 ));
1036 }
1037
1038 Ok(())
1039 }
1040}
1041
1042#[derive(Debug)]
1045pub struct StagedWelcome {
1046 mls_group_config: MlsGroupJoinConfig,
1048 public_group: PublicGroup,
1049 group_epoch_secrets: GroupEpochSecrets,
1050 own_leaf_index: LeafNodeIndex,
1051
1052 message_secrets_store: MessageSecretsStore,
1059
1060 #[cfg(feature = "extensions-draft-08")]
1063 application_export_secret: ApplicationExportSecret,
1064
1065 resumption_psk_store: ResumptionPskStore,
1067
1068 verifiable_group_info: VerifiableGroupInfo,
1070
1071 key_package_bundle: KeyPackageBundle,
1073
1074 path_keypairs: Option<Vec<EncryptionKeyPair>>,
1076}
1077
1078pub struct ProcessedWelcome {
1085 mls_group_config: MlsGroupJoinConfig,
1087
1088 ciphersuite: Ciphersuite,
1091 group_secrets: GroupSecrets,
1092 key_schedule: crate::schedule::KeySchedule,
1093 verifiable_group_info: crate::messages::group_info::VerifiableGroupInfo,
1094 resumption_psk_store: crate::schedule::psk::store::ResumptionPskStore,
1095 key_package_bundle: KeyPackageBundle,
1096}