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
51#[cfg(feature = "virtual-clients-draft")]
52use errors::VcEmulationStateError;
53
54mod application;
56mod exporting;
57mod updates;
58
59use config::*;
60
61pub(crate) mod builder;
63pub(crate) mod commit_builder;
64pub(crate) mod config;
65pub(crate) mod creation;
66pub(crate) mod errors;
67pub(crate) mod membership;
68pub(crate) mod past_secrets;
69pub(crate) mod processing;
70pub(crate) mod proposal;
71pub(crate) mod proposal_store;
72pub(crate) mod staged_commit;
73
74#[cfg(feature = "extensions-draft-08")]
75pub(crate) mod app_ephemeral;
76
77#[cfg(test)]
79pub(crate) mod tests_and_kats;
80
81#[derive(Debug)]
82pub(crate) struct CreateCommitResult {
83 pub(crate) commit: AuthenticatedContent,
84 pub(crate) welcome_option: Option<Welcome>,
85 pub(crate) staged_commit: StagedCommit,
86 pub(crate) group_info: Option<GroupInfo>,
87}
88
89#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
91pub struct Member {
92 pub index: LeafNodeIndex,
94 pub credential: Credential,
96 pub encryption_key: Vec<u8>,
98 pub signature_key: Vec<u8>,
100}
101
102impl Member {
103 pub fn new(
105 index: LeafNodeIndex,
106 encryption_key: Vec<u8>,
107 signature_key: Vec<u8>,
108 credential: Credential,
109 ) -> Self {
110 Self {
111 index,
112 encryption_key,
113 signature_key,
114 credential,
115 }
116 }
117}
118
119#[derive(Debug, Serialize, Deserialize)]
122#[cfg_attr(any(test, feature = "test-utils"), derive(Clone, PartialEq))]
123pub enum PendingCommitState {
124 Member(StagedCommit),
126 External(StagedCommit),
128}
129
130impl PendingCommitState {
131 pub(crate) fn staged_commit(&self) -> &StagedCommit {
134 match self {
135 PendingCommitState::Member(pc) => pc,
136 PendingCommitState::External(pc) => pc,
137 }
138 }
139}
140
141impl From<PendingCommitState> for StagedCommit {
142 fn from(pcs: PendingCommitState) -> Self {
143 match pcs {
144 PendingCommitState::Member(pc) => pc,
145 PendingCommitState::External(pc) => pc,
146 }
147 }
148}
149
150#[derive(Debug, Serialize, Deserialize)]
195#[cfg_attr(any(test, feature = "test-utils"), derive(Clone, PartialEq))]
196pub enum MlsGroupState {
197 PendingCommit(Box<PendingCommitState>),
199 Operational,
201 Inactive,
203}
204
205#[derive(Debug)]
230#[cfg_attr(feature = "test-utils", derive(Clone, PartialEq))]
231pub struct MlsGroup {
232 mls_group_config: MlsGroupJoinConfig,
234 public_group: PublicGroup,
236 group_epoch_secrets: GroupEpochSecrets,
238 own_leaf_index: LeafNodeIndex,
240 message_secrets_store: MessageSecretsStore,
247 resumption_psk_store: ResumptionPskStore,
249 own_leaf_nodes: Vec<LeafNode>,
253 aad: Vec<u8>,
257 #[cfg(feature = "extensions-draft-08")]
261 safe_aad: SafeAad,
262 group_state: MlsGroupState,
265 #[cfg(feature = "extensions-draft-08")]
269 application_export_tree: Option<ApplicationExportTree>,
270}
271
272impl MlsGroup {
273 pub fn configuration(&self) -> &MlsGroupJoinConfig {
277 &self.mls_group_config
278 }
279
280 pub fn set_configuration<Storage: StorageProvider>(
282 &mut self,
283 storage: &Storage,
284 mls_group_config: &MlsGroupJoinConfig,
285 ) -> Result<(), Storage::Error> {
286 self.mls_group_config = mls_group_config.clone();
287 storage.write_mls_join_config(self.group_id(), mls_group_config)
288 }
289
290 pub fn set_aad(&mut self, aad: Vec<u8>) {
294 self.aad = aad;
295 }
296
297 pub fn aad(&self) -> &[u8] {
300 &self.aad
301 }
302
303 #[cfg(feature = "extensions-draft-08")]
313 pub fn set_safe_aad(&mut self, items: Vec<SafeAadItem>) -> Result<(), SafeAadError> {
314 self.safe_aad = SafeAad::from_items(items)?;
315 Ok(())
316 }
317
318 #[cfg(feature = "extensions-draft-08")]
321 pub fn safe_aad_items(&self) -> &[SafeAadItem] {
322 self.safe_aad.items()
323 }
324
325 pub fn ciphersuite(&self) -> Ciphersuite {
329 self.public_group.ciphersuite()
330 }
331
332 pub fn confirmation_tag(&self) -> &ConfirmationTag {
334 self.public_group.confirmation_tag()
335 }
336
337 pub fn is_active(&self) -> bool {
340 !matches!(self.group_state, MlsGroupState::Inactive)
341 }
342
343 pub fn credential(&self) -> Result<&Credential, MlsGroupStateError> {
346 if !self.is_active() {
347 return Err(MlsGroupStateError::UseAfterEviction);
348 }
349 self.public_group
350 .leaf(self.own_leaf_index())
351 .map(|node| node.credential())
352 .ok_or_else(|| LibraryError::custom("Own leaf node missing").into())
353 }
354
355 pub fn own_leaf_index(&self) -> LeafNodeIndex {
357 self.own_leaf_index
358 }
359
360 pub fn own_leaf_node(&self) -> Option<&LeafNode> {
362 self.public_group().leaf(self.own_leaf_index())
363 }
364
365 pub fn group_id(&self) -> &GroupId {
367 self.public_group.group_id()
368 }
369
370 pub fn epoch(&self) -> GroupEpoch {
372 self.public_group.group_context().epoch()
373 }
374
375 pub fn pending_proposals(&self) -> impl Iterator<Item = &QueuedProposal> {
377 self.proposal_store().proposals()
378 }
379
380 pub fn treesync(&self) -> &TreeSync {
382 self.public_group.treesync()
383 }
384
385 pub fn pending_commit(&self) -> Option<&StagedCommit> {
389 match self.group_state {
390 MlsGroupState::PendingCommit(ref pending_commit_state) => {
391 Some(pending_commit_state.staged_commit())
392 }
393 MlsGroupState::Operational => None,
394 MlsGroupState::Inactive => None,
395 }
396 }
397
398 pub fn clear_pending_commit<Storage: StorageProvider>(
410 &mut self,
411 storage: &Storage,
412 ) -> Result<(), Storage::Error> {
413 match self.group_state {
414 MlsGroupState::PendingCommit(ref pending_commit_state) => {
415 if let PendingCommitState::Member(_) = **pending_commit_state {
416 self.group_state = MlsGroupState::Operational;
417 storage.write_group_state(self.group_id(), &self.group_state)
418 } else {
419 Ok(())
420 }
421 }
422 MlsGroupState::Operational | MlsGroupState::Inactive => Ok(()),
423 }
424 }
425
426 pub fn clear_pending_proposals<Storage: StorageProvider>(
433 &mut self,
434 storage: &Storage,
435 ) -> Result<(), Storage::Error> {
436 if !self.proposal_store().is_empty() {
438 self.proposal_store_mut().empty();
440
441 storage.clear_proposal_queue::<GroupId, ProposalRef>(self.group_id())?;
443 }
444
445 Ok(())
446 }
447
448 pub fn extensions(&self) -> &Extensions<GroupContext> {
450 self.public_group().group_context().extensions()
451 }
452
453 pub fn ext_commit_sender_index(
455 &self,
456 commit: &StagedCommit,
457 ) -> Result<LeafNodeIndex, LibraryError> {
458 self.public_group().ext_commit_sender_index(commit)
459 }
460
461 pub fn load<Storage: crate::storage::StorageProvider>(
465 storage: &Storage,
466 group_id: &GroupId,
467 ) -> Result<Option<MlsGroup>, Storage::Error> {
468 let public_group = PublicGroup::load(storage, group_id)?;
469 let group_epoch_secrets = storage.group_epoch_secrets(group_id)?;
470 let own_leaf_index = storage.own_leaf_index(group_id)?;
471 let message_secrets_store = storage.message_secrets(group_id)?;
472 let resumption_psk_store = storage.resumption_psk_store(group_id)?;
473 let mls_group_config = storage.mls_group_join_config(group_id)?;
474 let own_leaf_nodes = storage.own_leaf_nodes(group_id)?;
475 let group_state = storage.group_state(group_id)?;
476 #[cfg(feature = "extensions-draft-08")]
477 let application_export_tree = storage.application_export_tree(group_id)?;
478
479 let build = || -> Option<Self> {
480 Some(Self {
481 public_group: public_group?,
482 group_epoch_secrets: group_epoch_secrets?,
483 own_leaf_index: own_leaf_index?,
484 message_secrets_store: message_secrets_store?,
485 resumption_psk_store: resumption_psk_store?,
486 mls_group_config: mls_group_config?,
487 own_leaf_nodes,
488 aad: vec![],
489 #[cfg(feature = "extensions-draft-08")]
490 safe_aad: SafeAad::empty(),
491 group_state: group_state?,
492 #[cfg(feature = "extensions-draft-08")]
493 application_export_tree,
494 })
495 };
496
497 Ok(build())
498 }
499
500 pub fn delete<Storage: crate::storage::StorageProvider>(
511 &mut self,
512 storage: &Storage,
513 ) -> Result<(), Storage::Error> {
514 PublicGroup::delete(storage, self.group_id())?;
515 storage.delete_own_leaf_index(self.group_id())?;
516 storage.delete_group_epoch_secrets(self.group_id())?;
517 storage.delete_message_secrets(self.group_id())?;
518 storage.delete_all_resumption_psk_secrets(self.group_id())?;
519 storage.delete_group_config(self.group_id())?;
520 storage.delete_own_leaf_nodes(self.group_id())?;
521 storage.delete_group_state(self.group_id())?;
522 storage.clear_proposal_queue::<GroupId, ProposalRef>(self.group_id())?;
523
524 #[cfg(feature = "extensions-draft-08")]
525 storage.delete_application_export_tree::<_, ApplicationExportTree>(self.group_id())?;
526
527 #[cfg(feature = "virtual-clients-draft")]
532 storage.delete_vc_emulation_bindings(self.group_id())?;
533
534 self.proposal_store_mut().empty();
535 storage.delete_encryption_epoch_key_pairs(
536 self.group_id(),
537 &self.epoch(),
538 self.own_leaf_index().u32(),
539 )?;
540
541 Ok(())
542 }
543
544 pub fn export_ratchet_tree(&self) -> RatchetTree {
548 self.public_group().export_ratchet_tree()
549 }
550}
551
552impl MlsGroup {
554 pub(crate) fn required_capabilities(&self) -> Option<&RequiredCapabilitiesExtension> {
556 self.public_group.required_capabilities()
557 }
558
559 pub(crate) fn group_epoch_secrets(&self) -> &GroupEpochSecrets {
561 &self.group_epoch_secrets
562 }
563
564 pub(crate) fn message_secrets(&self) -> &MessageSecrets {
566 self.message_secrets_store.message_secrets()
567 }
568
569 pub(crate) fn resize_message_secrets_store(&mut self, policy: &PastEpochDeletionPolicy) {
573 self.message_secrets_store.resize(policy);
574 }
575
576 pub fn past_epoch_deletion_policy(&self) -> &PastEpochDeletionPolicy {
578 self.mls_group_config.past_epoch_deletion_policy()
579 }
580
581 pub fn set_past_epoch_deletion_policy<Provider: OpenMlsProvider>(
583 &mut self,
584 provider: &Provider,
585 policy: PastEpochDeletionPolicy,
586 ) -> Result<(), SetPastEpochDeletionPolicyError<Provider::StorageError>> {
587 self.resize_message_secrets_store(&policy);
589
590 self.mls_group_config.past_epoch_deletion_policy = policy;
592
593 provider
595 .storage()
596 .write_mls_join_config(self.group_id(), &self.mls_group_config)?;
597
598 provider
600 .storage()
601 .write_message_secrets(self.group_id(), &self.message_secrets_store)?;
602
603 Ok(())
604 }
605
606 pub(crate) fn message_secrets_for_epoch_mut(
608 &mut self,
609 epoch: GroupEpoch,
610 ) -> Result<&mut MessageSecrets, SecretTreeError> {
611 if epoch < self.context().epoch() {
612 self.message_secrets_store
613 .secrets_for_epoch_mut(epoch)
614 .ok_or(SecretTreeError::TooDistantInThePast)
615 } else {
616 Ok(self.message_secrets_store.message_secrets_mut())
617 }
618 }
619
620 pub(crate) fn message_secrets_for_epoch(
622 &self,
623 epoch: GroupEpoch,
624 ) -> Result<&MessageSecrets, SecretTreeError> {
625 if epoch < self.context().epoch() {
626 self.message_secrets_store
627 .secrets_for_epoch(epoch)
628 .ok_or(SecretTreeError::TooDistantInThePast)
629 } else {
630 Ok(self.message_secrets_store.message_secrets())
631 }
632 }
633
634 pub(crate) fn message_secrets_and_leaves(
640 &self,
641 epoch: GroupEpoch,
642 ) -> Result<(&MessageSecrets, &[Member]), SecretTreeError> {
643 if epoch < self.context().epoch() {
644 self.message_secrets_store
645 .secrets_and_leaves_for_epoch(epoch)
646 .ok_or(SecretTreeError::TooDistantInThePast)
647 } else {
648 Ok((self.message_secrets_store.message_secrets(), &[]))
651 }
652 }
653
654 pub(crate) fn create_group_context_ext_proposal<Provider: OpenMlsProvider>(
656 &self,
657 framing_parameters: FramingParameters,
658 extensions: Extensions<GroupContext>,
659 signer: &impl Signer,
660 ) -> Result<AuthenticatedContent, CreateGroupContextExtProposalError<Provider::StorageError>>
661 {
662 let required_extension = extensions
664 .iter()
665 .find(|extension| extension.extension_type() == ExtensionType::RequiredCapabilities);
666 if let Some(required_extension) = required_extension {
667 let required_capabilities = required_extension.as_required_capabilities_extension()?;
668 self.own_leaf_node()
670 .ok_or_else(|| LibraryError::custom("Tree has no own leaf."))?
671 .capabilities()
672 .supports_required_capabilities(required_capabilities)?;
673
674 self.public_group()
677 .check_extension_support(required_capabilities.extension_types())?;
678 }
679 let proposal = GroupContextExtensionProposal::new(extensions);
680 let proposal = Proposal::GroupContextExtensions(Box::new(proposal));
681 AuthenticatedContent::member_proposal(
682 framing_parameters,
683 self.own_leaf_index(),
684 proposal,
685 self.context(),
686 signer,
687 )
688 .map_err(|e| e.into())
689 }
690
691 #[cfg(feature = "virtual-clients-draft")]
700 pub(crate) fn vc_emulation_state_at_epoch<Storage: StorageProvider>(
701 &self,
702 storage: &Storage,
703 epoch: GroupEpoch,
704 ) -> Result<
705 Option<crate::components::vc_derivation_info::EmulationEpochState>,
706 VcEmulationStateError<Storage::Error>,
707 > {
708 let bindings: Option<crate::components::vc_derivation_info::VcEmulationBindings> = storage
709 .vc_emulation_bindings(self.group_id())
710 .map_err(VcEmulationStateError::Storage)?;
711 let Some(epoch_id) = bindings.and_then(|bindings| bindings.get(epoch).cloned()) else {
712 return Ok(None);
713 };
714 let state = storage
715 .vc_emulation_epoch_state(&epoch_id)
716 .map_err(VcEmulationStateError::Storage)?
717 .ok_or_else(|| {
718 log::error!("vc: group is bound to emulation epoch, but state is missing");
719 VcEmulationStateError::MissingEmulationEpochState
720 })?;
721 Ok(Some(state))
722 }
723
724 pub(crate) fn encrypt<Provider: OpenMlsProvider>(
726 &mut self,
727 public_message: AuthenticatedContent,
728 provider: &Provider,
729 ) -> Result<EncryptionOutput, MessageEncryptionError<Provider::StorageError>> {
730 let padding_size = self.configuration().padding_size();
731
732 #[cfg(feature = "virtual-clients-draft")]
736 let emulation_state = self
737 .vc_emulation_state_at_epoch(provider.storage(), self.epoch())
738 .map_err(|e| match e {
739 VcEmulationStateError::Storage(e) => MessageEncryptionError::StorageError(e),
740 VcEmulationStateError::MissingEmulationEpochState => {
741 MessageEncryptionError::VirtualClientsError(
742 crate::components::vc_derivation_info::VirtualClientsError::MissingEmulationEpochState,
743 )
744 }
745 })?;
746 #[cfg(feature = "virtual-clients-draft")]
747 let emulator_ctx: Option<crate::framing::EmulatorReuseGuardCtx<'_>> = emulation_state
748 .as_ref()
749 .map(|state| state.reuse_guard_inputs());
750
751 let msg = PrivateMessage::try_from_authenticated_content(
752 provider.crypto(),
753 provider.rand(),
754 &public_message,
755 self.ciphersuite(),
756 self.message_secrets_store.message_secrets_mut(),
757 padding_size,
758 #[cfg(feature = "virtual-clients-draft")]
759 emulator_ctx.as_ref(),
760 )?;
761
762 provider
763 .storage()
764 .write_message_secrets(self.group_id(), &self.message_secrets_store)
765 .map_err(MessageEncryptionError::StorageError)?;
766
767 Ok(msg)
768 }
769
770 pub(crate) fn outgoing_wire_format(&self) -> WireFormat {
772 self.mls_group_config.wire_format_policy().outgoing().into()
773 }
774
775 pub(crate) fn outgoing_authenticated_data(&self) -> Result<Vec<u8>, LibraryError> {
781 #[cfg(feature = "extensions-draft-08")]
782 {
783 self.assembled_authenticated_data()
784 }
785 #[cfg(not(feature = "extensions-draft-08"))]
786 {
787 Ok(self.aad.clone())
788 }
789 }
790
791 #[cfg(feature = "extensions-draft-08")]
796 pub(crate) fn assembled_authenticated_data(&self) -> Result<Vec<u8>, LibraryError> {
797 if !self.context().safe_aad_required() {
798 return Ok(self.aad.clone());
799 }
800 crate::framing::safe_aad::assemble_authenticated_data(&self.safe_aad, &self.aad)
801 .map_err(|_| LibraryError::custom("SafeAad serialization failed"))
802 }
803
804 pub fn delete_past_epoch_secrets<Provider: OpenMlsProvider>(
808 &mut self,
809 provider: &Provider,
810 policy: PastEpochDeletion,
811 ) -> Result<(), DeletePastEpochSecretsError<Provider::StorageError>> {
812 self.message_secrets_store.delete_past_epoch_secrets(policy);
814 provider
816 .storage()
817 .write_message_secrets(self.group_id(), &self.message_secrets_store)?;
818
819 Ok(())
820 }
821
822 pub fn proposal_store(&self) -> &ProposalStore {
824 self.public_group.proposal_store()
825 }
826
827 pub(crate) fn proposal_store_mut(&mut self) -> &mut ProposalStore {
829 self.public_group.proposal_store_mut()
830 }
831
832 pub(crate) fn context(&self) -> &GroupContext {
834 self.public_group.group_context()
835 }
836
837 pub(crate) fn version(&self) -> ProtocolVersion {
839 self.public_group.version()
840 }
841
842 #[inline]
844 pub(crate) fn reset_aad(&mut self) {
845 self.aad.clear();
846 #[cfg(feature = "extensions-draft-08")]
847 {
848 self.safe_aad = SafeAad::empty();
849 }
850 }
851
852 pub fn public_group(&self) -> &PublicGroup {
854 &self.public_group
855 }
856}
857
858impl MlsGroup {
860 pub(super) fn store_epoch_keypairs<Storage: StorageProvider>(
865 &self,
866 store: &Storage,
867 keypair_references: &[EncryptionKeyPair],
868 ) -> Result<(), Storage::Error> {
869 store.write_encryption_epoch_key_pairs(
870 self.group_id(),
871 &self.context().epoch(),
872 self.own_leaf_index().u32(),
873 keypair_references,
874 )
875 }
876
877 pub(super) fn read_epoch_keypairs<Storage: StorageProvider>(
882 &self,
883 store: &Storage,
884 ) -> Result<Vec<EncryptionKeyPair>, Storage::Error> {
885 store.encryption_epoch_key_pairs(
886 self.group_id(),
887 &self.context().epoch(),
888 self.own_leaf_index().u32(),
889 )
890 }
891
892 pub(super) fn delete_previous_epoch_keypairs<Storage: StorageProvider>(
904 &self,
905 store: &Storage,
906 #[cfg(feature = "virtual-clients-draft")] previous_own_leaf_index: LeafNodeIndex,
907 ) -> Result<(), Storage::Error> {
908 #[cfg(feature = "virtual-clients-draft")]
909 let leaf_index = previous_own_leaf_index.u32();
910 #[cfg(not(feature = "virtual-clients-draft"))]
911 let leaf_index = self.own_leaf_index().u32();
912 store.delete_encryption_epoch_key_pairs(
913 self.group_id(),
914 &GroupEpoch::from(self.context().epoch().as_u64() - 1),
915 leaf_index,
916 )
917 }
918
919 pub(super) fn store<Storage: crate::storage::StorageProvider>(
922 &self,
923 storage: &Storage,
924 ) -> Result<(), Storage::Error> {
925 self.public_group.store(storage)?;
926 storage.write_group_epoch_secrets(self.group_id(), &self.group_epoch_secrets)?;
927 storage.write_own_leaf_index(self.group_id(), &self.own_leaf_index)?;
928 storage.write_message_secrets(self.group_id(), &self.message_secrets_store)?;
929 storage.write_resumption_psk_store(self.group_id(), &self.resumption_psk_store)?;
930 storage.write_mls_join_config(self.group_id(), &self.mls_group_config)?;
931 storage.write_group_state(self.group_id(), &self.group_state)?;
932 #[cfg(feature = "extensions-draft-08")]
933 if let Some(application_export_tree) = &self.application_export_tree {
934 storage.write_application_export_tree(self.group_id(), application_export_tree)?;
935 }
936
937 Ok(())
938 }
939
940 fn content_to_mls_message(
944 &mut self,
945 mls_auth_content: AuthenticatedContent,
946 provider: &impl OpenMlsProvider,
947 ) -> Result<MlsMessageOut, LibraryError> {
948 let msg = match self.configuration().wire_format_policy().outgoing() {
949 OutgoingWireFormatPolicy::AlwaysPlaintext => {
950 let mut plaintext: PublicMessage = mls_auth_content.into();
951 if plaintext.sender().is_member() {
953 plaintext.set_membership_tag(
954 provider.crypto(),
955 self.ciphersuite(),
956 self.message_secrets().membership_key(),
957 self.message_secrets().serialized_context(),
958 )?;
959 }
960 plaintext.into()
961 }
962 OutgoingWireFormatPolicy::AlwaysCiphertext => {
963 let EncryptionOutput {
966 private_message, ..
967 } = self
968 .encrypt(mls_auth_content, provider)
969 .map_err(|_| LibraryError::custom("Malformed plaintext"))?;
971 MlsMessageOut::from_private_message(private_message, self.version())
972 }
973 };
974 Ok(msg)
975 }
976
977 fn is_operational(&self) -> Result<(), MlsGroupStateError> {
980 match self.group_state {
981 MlsGroupState::PendingCommit(_) => Err(MlsGroupStateError::PendingCommit),
982 MlsGroupState::Inactive => Err(MlsGroupStateError::UseAfterEviction),
983 MlsGroupState::Operational => Ok(()),
984 }
985 }
986}
987
988impl MlsGroup {
990 #[cfg(any(feature = "test-utils", test))]
991 pub fn export_group_context(&self) -> &GroupContext {
992 self.context()
993 }
994
995 #[cfg(any(feature = "test-utils", test))]
996 pub fn tree_hash(&self) -> &[u8] {
997 self.public_group().group_context().tree_hash()
998 }
999
1000 #[cfg(any(feature = "test-utils", test))]
1001 pub(crate) fn message_secrets_test_mut(&mut self) -> &mut MessageSecrets {
1002 self.message_secrets_store.message_secrets_mut()
1003 }
1004
1005 #[cfg(any(feature = "test-utils", test))]
1006 pub fn print_ratchet_tree(&self, message: &str) {
1007 println!("{}: {}", message, self.public_group().export_ratchet_tree());
1008 }
1009
1010 #[cfg(any(feature = "test-utils", test))]
1011 pub(crate) fn context_mut(&mut self) -> &mut GroupContext {
1012 self.public_group.context_mut()
1013 }
1014
1015 #[cfg(test)]
1016 pub(crate) fn set_own_leaf_index(&mut self, own_leaf_index: LeafNodeIndex) {
1017 self.own_leaf_index = own_leaf_index;
1018 }
1019
1020 #[cfg(test)]
1021 pub(crate) fn own_tree_position(&self) -> TreePosition {
1022 TreePosition::new(self.group_id().clone(), self.own_leaf_index())
1023 }
1024
1025 #[cfg(test)]
1026 pub(crate) fn message_secrets_store(&self) -> &MessageSecretsStore {
1027 &self.message_secrets_store
1028 }
1029
1030 #[cfg(test)]
1031 pub(crate) fn resumption_psk_store(&self) -> &ResumptionPskStore {
1032 &self.resumption_psk_store
1033 }
1034
1035 #[cfg(test)]
1036 pub(crate) fn set_group_context(&mut self, group_context: GroupContext) {
1037 self.public_group.set_group_context(group_context)
1038 }
1039
1040 #[cfg(any(test, feature = "test-utils"))]
1041 pub fn ensure_persistence(&self, storage: &impl StorageProvider) -> Result<(), LibraryError> {
1042 let loaded = MlsGroup::load(storage, self.group_id())
1043 .map_err(|_| LibraryError::custom("Failed to load group from storage"))?;
1044 let other = loaded.ok_or_else(|| LibraryError::custom("Group not found in storage"))?;
1045
1046 if self != &other {
1047 let mut diagnostics = Vec::new();
1048
1049 if self.mls_group_config != other.mls_group_config {
1050 diagnostics.push(format!(
1051 "mls_group_config:\n Current: {:?}\n Loaded: {:?}",
1052 self.mls_group_config, other.mls_group_config
1053 ));
1054 }
1055 if self.public_group != other.public_group {
1056 diagnostics.push(format!(
1057 "public_group:\n Current: {:?}\n Loaded: {:?}",
1058 self.public_group, other.public_group
1059 ));
1060 }
1061 if self.group_epoch_secrets != other.group_epoch_secrets {
1062 diagnostics.push(format!(
1063 "group_epoch_secrets:\n Current: {:?}\n Loaded: {:?}",
1064 self.group_epoch_secrets, other.group_epoch_secrets
1065 ));
1066 }
1067 if self.own_leaf_index != other.own_leaf_index {
1068 diagnostics.push(format!(
1069 "own_leaf_index:\n Current: {:?}\n Loaded: {:?}",
1070 self.own_leaf_index, other.own_leaf_index
1071 ));
1072 }
1073 if self.message_secrets_store != other.message_secrets_store {
1074 diagnostics.push(format!(
1075 "message_secrets_store:\n Current: {:?}\n Loaded: {:?}",
1076 self.message_secrets_store, other.message_secrets_store
1077 ));
1078 }
1079 if self.resumption_psk_store != other.resumption_psk_store {
1080 diagnostics.push(format!(
1081 "resumption_psk_store:\n Current: {:?}\n Loaded: {:?}",
1082 self.resumption_psk_store, other.resumption_psk_store
1083 ));
1084 }
1085 if self.own_leaf_nodes != other.own_leaf_nodes {
1086 diagnostics.push(format!(
1087 "own_leaf_nodes:\n Current: {:?}\n Loaded: {:?}",
1088 self.own_leaf_nodes, other.own_leaf_nodes
1089 ));
1090 }
1091 if self.aad != other.aad {
1092 diagnostics.push(format!(
1093 "aad:\n Current: {:?}\n Loaded: {:?}",
1094 self.aad, other.aad
1095 ));
1096 }
1097 if self.group_state != other.group_state {
1098 diagnostics.push(format!(
1099 "group_state:\n Current: {:?}\n Loaded: {:?}",
1100 self.group_state, other.group_state
1101 ));
1102 }
1103 #[cfg(feature = "extensions-draft-08")]
1104 if self.application_export_tree != other.application_export_tree {
1105 diagnostics.push(format!(
1106 "application_export_tree:\n Current: {:?}\n Loaded: {:?}",
1107 self.application_export_tree, other.application_export_tree
1108 ));
1109 }
1110
1111 log::error!(
1112 "Loaded group does not match current group! Differing fields ({}):\n\n{}",
1113 diagnostics.len(),
1114 diagnostics.join("\n\n")
1115 );
1116
1117 return Err(LibraryError::custom(
1118 "Loaded group does not match current group",
1119 ));
1120 }
1121
1122 Ok(())
1123 }
1124}
1125
1126#[derive(Debug)]
1129pub struct StagedWelcome {
1130 mls_group_config: MlsGroupJoinConfig,
1132 public_group: PublicGroup,
1133 group_epoch_secrets: GroupEpochSecrets,
1134 own_leaf_index: LeafNodeIndex,
1135
1136 message_secrets_store: MessageSecretsStore,
1143
1144 #[cfg(feature = "extensions-draft-08")]
1147 application_export_secret: ApplicationExportSecret,
1148
1149 resumption_psk_store: ResumptionPskStore,
1151
1152 verifiable_group_info: VerifiableGroupInfo,
1154
1155 key_package_bundle: KeyPackageBundle,
1157
1158 path_keypairs: Option<Vec<EncryptionKeyPair>>,
1160}
1161
1162pub struct ProcessedWelcome {
1169 mls_group_config: MlsGroupJoinConfig,
1171
1172 ciphersuite: Ciphersuite,
1175 group_secrets: GroupSecrets,
1176 key_schedule: crate::schedule::KeySchedule,
1177 verifiable_group_info: crate::messages::group_info::VerifiableGroupInfo,
1178 resumption_psk_store: crate::schedule::psk::store::ResumptionPskStore,
1179 key_package_bundle: KeyPackageBundle,
1180}