1use std::mem;
4
5#[cfg(any(not(feature = "virtual-clients-draft"), feature = "test-utils", test))]
6use errors::CommitToPendingProposalsError;
7use errors::MergePendingCommitError;
8#[cfg(feature = "extensions-draft")]
9use errors::ResolveAppDataCommitError;
10#[cfg(any(not(feature = "virtual-clients-draft"), feature = "test-utils", test))]
11use openmls_traits::signatures::Signer;
12use openmls_traits::{crypto::OpenMlsCrypto, storage::StorageProvider as _};
13
14#[cfg(any(not(feature = "virtual-clients-draft"), feature = "test-utils", test))]
15use crate::messages::group_info::GroupInfo;
16use crate::{
17 framing::mls_content::FramedContentBody,
18 group::{errors::MergeCommitError, StageCommitError, ValidationError},
19 storage::OpenMlsProvider,
20 tree::sender_ratchet::SenderRatchetConfiguration,
21};
22
23#[cfg(feature = "extensions-draft")]
26use crate::messages::Commit;
27
28#[cfg(feature = "extensions-draft")]
29use crate::{
30 component::{ComponentData, ComponentId},
31 extensions::AppDataDictionary,
32 messages::proposals::AppDataUpdateProposal,
33};
34
35#[cfg(feature = "extensions-draft")]
36use std::collections::BTreeMap;
37
38use super::{errors::ProcessMessageError, *};
39
40pub(crate) enum UnprotectedMessage {
42 Unverified(Box<UnverifiedMessage>),
45 OwnPrivateMessage {
50 epoch: GroupEpoch,
51 authenticated_data: Vec<u8>,
52 },
53}
54
55#[cfg(feature = "extensions-draft")]
56pub struct AppDataDictionaryUpdater<'a> {
58 old_dict: Option<&'a AppDataDictionary>,
59 new_entries: Option<AppDataUpdates>,
60}
61
62#[cfg(feature = "extensions-draft")]
66#[derive(Default, Debug)]
67pub struct AppDataUpdates(BTreeMap<ComponentId, Option<Vec<u8>>>);
68
69#[cfg(feature = "extensions-draft")]
70impl IntoIterator for AppDataUpdates {
71 type Item = (ComponentId, Option<Vec<u8>>);
72
73 type IntoIter = <BTreeMap<ComponentId, Option<Vec<u8>>> as IntoIterator>::IntoIter;
74
75 fn into_iter(self) -> Self::IntoIter {
76 self.0.into_iter()
77 }
78}
79
80#[cfg(feature = "extensions-draft")]
81impl AppDataUpdates {
82 pub fn len(&self) -> usize {
84 self.0.len()
85 }
86
87 pub fn is_empty(&self) -> bool {
89 self.0.is_empty()
90 }
91}
92
93#[cfg(feature = "extensions-draft")]
94impl<'a> AppDataDictionaryUpdater<'a> {
95 pub fn new(old_dict: Option<&'a AppDataDictionary>) -> Self {
97 Self {
98 old_dict,
99 new_entries: None,
100 }
101 }
102
103 pub fn old_value(&self, component_id: ComponentId) -> Option<&[u8]> {
105 self.old_dict?.get(&component_id)
106 }
107
108 fn new_entries_mut(&mut self) -> &mut AppDataUpdates {
111 self.new_entries
112 .get_or_insert_with(|| AppDataUpdates(BTreeMap::new()))
113 }
114
115 pub fn set(&mut self, component_data: ComponentData) {
118 let (id, data) = component_data.into_parts();
119
120 self.new_entries_mut().0.insert(id, Some(data.into()));
121 }
122
123 pub fn remove(&mut self, id: &ComponentId) {
125 self.new_entries_mut().0.insert(*id, None);
126 }
127
128 pub fn changes(self) -> Option<AppDataUpdates> {
135 self.new_entries
136 }
137}
138
139#[cfg(feature = "extensions-draft")]
164pub struct UnresolvedAppDataCommit {
165 content: AuthenticatedContent,
166 proposals: Vec<AppDataUpdateProposal>,
170 #[cfg(feature = "virtual-clients-draft")]
171 vc_commit_material: Option<crate::components::vc_derivation_info::VcCommitMaterial>,
172}
173
174#[cfg(feature = "extensions-draft")]
175impl UnresolvedAppDataCommit {
176 pub(crate) fn new(
180 content: AuthenticatedContent,
181 proposals: Vec<AppDataUpdateProposal>,
182 ) -> Self {
183 Self {
184 content,
185 proposals,
186 #[cfg(feature = "virtual-clients-draft")]
187 vc_commit_material: None,
188 }
189 }
190
191 pub(crate) fn into_content(self) -> AuthenticatedContent {
196 self.content
197 }
198
199 pub fn app_data_update_proposals(&self) -> impl Iterator<Item = &AppDataUpdateProposal> {
203 self.proposals.iter()
204 }
205}
206
207#[cfg(feature = "extensions-draft")]
208impl core::fmt::Debug for UnresolvedAppDataCommit {
209 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
210 let mut debug_struct = f.debug_struct("UnresolvedAppDataCommit");
211 debug_struct
212 .field("content", &self.content)
213 .field("proposals", &self.proposals);
214 #[cfg(feature = "virtual-clients-draft")]
217 debug_struct.field(
218 "vc_emulation_epoch_id",
219 &self
220 .vc_commit_material
221 .as_ref()
222 .map(|material| &material.epoch_id),
223 );
224 debug_struct.finish_non_exhaustive()
225 }
226}
227
228impl MlsGroup {
229 #[cfg_attr(
236 feature = "extensions-draft",
237 doc = "A commit covering AppDataUpdate proposals is returned as\n\
238 [`ProcessedMessageContent::UnresolvedAppDataCommit`], since the\n\
239 application has to interpret the proposals before the commit can be\n\
240 staged via [`MlsGroup::stage_app_data_commit()`].\n"
241 )]
242 pub fn process_message<Provider: OpenMlsProvider>(
246 &mut self,
247 provider: &Provider,
248 message: impl Into<ProtocolMessage>,
249 ) -> Result<ProcessedMessage, ProcessMessageError<Provider::StorageError>> {
250 match self.unprotect_message(provider, message)? {
251 UnprotectedMessage::Unverified(m) => self.process_unverified_message(provider, *m),
252 UnprotectedMessage::OwnPrivateMessage {
255 epoch,
256 authenticated_data,
257 } => {
258 let credential = self.credential()?.clone();
259 #[cfg_attr(not(feature = "extensions-draft"), allow(unused_mut))]
260 let mut processed = ProcessedMessage::new(
261 self.group_id().clone(),
262 epoch,
263 Sender::Member(self.own_leaf_index()),
264 authenticated_data,
265 ProcessedMessageContent::OwnPrivateMessage,
266 credential,
267 #[cfg(feature = "virtual-clients-draft")]
268 None,
269 );
270 #[cfg(feature = "extensions-draft")]
271 if self.context().safe_aad_required() {
272 processed
273 .try_attach_safe_aad()
274 .map_err(|_| ProcessMessageError::MalformedSafeAad)?;
275 }
276 Ok(processed)
277 }
278 }
279 }
280
281 #[cfg(feature = "extensions-draft")]
282 pub fn app_data_dictionary_updater<'a>(&'a self) -> AppDataDictionaryUpdater<'a> {
284 AppDataDictionaryUpdater::new(self.context().app_data_dict())
285 }
286
287 pub(crate) fn unprotect_message<Provider: OpenMlsProvider>(
290 &mut self,
291 provider: &Provider,
292 message: impl Into<ProtocolMessage>,
293 ) -> Result<UnprotectedMessage, ProcessMessageError<Provider::StorageError>> {
294 if !self.is_active() {
296 return Err(ProcessMessageError::GroupStateError(
297 MlsGroupStateError::UseAfterEviction,
298 ));
299 }
300 let message = message.into();
301
302 if !message.is_external()
304 && message.is_handshake_message()
305 && !self
306 .configuration()
307 .wire_format_policy()
308 .incoming()
309 .is_compatible_with(message.wire_format())
310 {
311 return Err(ProcessMessageError::IncompatibleWireFormat);
312 }
313
314 let sender_ratchet_configuration = *self.configuration().sender_ratchet_configuration();
316
317 let will_modify_secret_tree = matches!(message, ProtocolMessage::PrivateMessage(_));
320
321 #[cfg(feature = "virtual-clients-draft")]
329 let emulation_state = if let ProtocolMessage::PrivateMessage(private_message) = &message {
330 self.vc_emulation_state_at_epoch(provider.storage(), private_message.epoch())
331 .map_err(|e| match e {
332 super::VcEmulationStateError::Storage(e) => {
333 ProcessMessageError::StorageError(e)
334 }
335 super::VcEmulationStateError::MissingEmulationEpochState => {
336 ProcessMessageError::ValidationError(
337 crate::group::ValidationError::UnableToDecrypt(
338 crate::framing::errors::MessageDecryptionError::VirtualClientsError(
339 crate::components::vc_derivation_info::VirtualClientsError::MissingEmulationEpochState,
340 ),
341 ),
342 )
343 }
344 })?
345 } else {
346 None
347 };
348 #[cfg(feature = "virtual-clients-draft")]
349 let emulator_ctx: Option<crate::framing::EmulatorReuseGuardCtx<'_>> = emulation_state
350 .as_ref()
351 .map(|state| state.reuse_guard_inputs());
352
353 let decrypt_result = self.decrypt_message(
359 provider.crypto(),
360 message,
361 &sender_ratchet_configuration,
362 #[cfg(feature = "virtual-clients-draft")]
363 emulator_ctx.as_ref(),
364 )?;
365
366 if will_modify_secret_tree {
368 provider
369 .storage()
370 .write_message_secrets(self.group_id(), &self.message_secrets_store)
371 .map_err(ProcessMessageError::StorageError)?;
372 }
373
374 let decrypted_message = match decrypt_result {
375 InboundDecryptionResult::Decrypted(decrypted_message) => decrypted_message,
376 InboundDecryptionResult::OwnPrivateMessage {
379 epoch,
380 authenticated_data,
381 } => {
382 return Ok(UnprotectedMessage::OwnPrivateMessage {
383 epoch,
384 authenticated_data,
385 });
386 }
387 };
388
389 let unverified_message = self
390 .public_group
391 .parse_message(decrypted_message, &self.message_secrets_store)
392 .map_err(ProcessMessageError::from)?;
393
394 Ok(UnprotectedMessage::Unverified(Box::new(unverified_message)))
395 }
396
397 pub fn store_pending_proposal<Storage: StorageProvider>(
399 &mut self,
400 storage: &Storage,
401 proposal: QueuedProposal,
402 ) -> Result<(), Storage::Error> {
403 storage.queue_proposal(self.group_id(), &proposal.proposal_reference(), &proposal)?;
404 self.proposal_store_mut().add(proposal);
406
407 Ok(())
408 }
409
410 pub fn has_pending_proposals(&self) -> bool {
412 !self.proposal_store().is_empty()
413 }
414
415 #[cfg(any(not(feature = "virtual-clients-draft"), feature = "test-utils", test))]
432 #[allow(clippy::type_complexity)]
433 pub fn commit_to_pending_proposals<Provider: OpenMlsProvider>(
434 &mut self,
435 provider: &Provider,
436 signer: &impl Signer,
437 ) -> Result<
438 (MlsMessageOut, Option<MlsMessageOut>, Option<GroupInfo>),
439 CommitToPendingProposalsError<Provider::StorageError>,
440 > {
441 self.is_operational()?;
442
443 let (commit, welcome, group_info) = self
446 .commit_builder()
447 .consume_proposal_store(true)
449 .load_psks(provider.storage())?
450 .build(provider.rand(), provider.crypto(), signer, |_| true)?
451 .stage_commit(provider)?
452 .into_contents();
453
454 Ok((
455 commit,
456 welcome.map(|welcome| MlsMessageOut::from_welcome(welcome, self.version())),
458 group_info,
459 ))
460 }
461
462 pub fn merge_staged_commit<Provider: OpenMlsProvider>(
465 &mut self,
466 provider: &Provider,
467 staged_commit: StagedCommit,
468 ) -> Result<(), MergeCommitError<Provider::StorageError>> {
469 if staged_commit.self_removed() {
471 self.group_state = MlsGroupState::Inactive;
472 }
473 provider
474 .storage()
475 .write_group_state(self.group_id(), &self.group_state)
476 .map_err(MergeCommitError::StorageError)?;
477
478 #[cfg(feature = "virtual-clients-draft")]
485 if staged_commit.self_removed() {
486 provider
487 .storage()
488 .delete_vc_emulation_bindings(self.group_id())
489 .map_err(|e| {
490 log::error!("vc: drop emulation bindings on self-removal failed: {e:?}");
491 MergeCommitError::StorageError(e)
492 })?;
493 provider
494 .storage()
495 .delete_registered_vc_emulation_epoch(self.group_id())
496 .map_err(|e| {
497 log::error!(
498 "vc: drop registered emulation epoch on self-removal failed: {e:?}"
499 );
500 MergeCommitError::StorageError(e)
501 })?;
502 } else {
503 let mut bindings: crate::components::vc_derivation_info::VcEmulationBindings = provider
504 .storage()
505 .vc_emulation_bindings(self.group_id())
506 .map_err(MergeCommitError::StorageError)?
507 .unwrap_or_default();
508 let epoch_id = staged_commit
509 .vc_emulation_epoch_id
510 .clone()
511 .or_else(|| bindings.get(self.epoch()).cloned());
512 if let Some(epoch_id) = epoch_id {
513 let max_entries = self.message_secrets_store.max_epochs.saturating_add(1);
517 bindings.insert(staged_commit.epoch(), epoch_id, max_entries);
518 provider
519 .storage()
520 .write_vc_emulation_bindings(self.group_id(), &bindings)
521 .map_err(|e| {
522 log::error!("vc: persist emulation bindings at merge failed: {e:?}");
523 MergeCommitError::StorageError(e)
524 })?;
525 }
526 }
527
528 self.merge_commit(provider, staged_commit)?;
530
531 let resumption_psk = self.group_epoch_secrets().resumption_psk();
533 self.resumption_psk_store
534 .add(self.context().epoch(), resumption_psk.clone());
535 provider
536 .storage()
537 .write_resumption_psk_store(self.group_id(), &self.resumption_psk_store)
538 .map_err(MergeCommitError::StorageError)?;
539
540 self.own_leaf_nodes.clear();
542 provider
543 .storage()
544 .delete_own_leaf_nodes(self.group_id())
545 .map_err(MergeCommitError::StorageError)?;
546
547 self.clear_pending_commit(provider.storage())
549 .map_err(MergeCommitError::StorageError)?;
550
551 Ok(())
552 }
553
554 pub fn merge_pending_commit<Provider: OpenMlsProvider>(
557 &mut self,
558 provider: &Provider,
559 ) -> Result<(), MergePendingCommitError<Provider::StorageError>> {
560 match &self.group_state {
561 MlsGroupState::PendingCommit(_) => {
562 let old_state = mem::replace(&mut self.group_state, MlsGroupState::Operational);
563 if let MlsGroupState::PendingCommit(pending_commit_state) = old_state {
564 self.merge_staged_commit(provider, (*pending_commit_state).into())?;
565 }
566 Ok(())
567 }
568 MlsGroupState::Inactive => Err(MlsGroupStateError::UseAfterEviction)?,
569 MlsGroupState::Operational => Ok(()),
570 }
571 }
572
573 #[cfg(feature = "virtual-clients-draft")]
605 pub(super) fn load_vc_commit_material<Provider: OpenMlsProvider>(
606 &self,
607 provider: &Provider,
608 commit: &Commit,
609 ) -> Result<Option<crate::components::vc_derivation_info::VcCommitMaterial>, StageCommitError>
610 {
611 use tls_codec::{DeserializeBytes, Serialize as _};
612
613 use crate::{
614 components::vc_derivation_info::{
615 DerivationInfo, EmulationEpochState, VirtualClientOperationType,
616 VirtualClientsError, VC_COMPONENT_ID,
617 },
618 components::vc_operation_tree::OperationSecretTree,
619 treesync::node::leaf_node::LeafNodeSource,
620 };
621
622 let Some(path) = commit.path.as_ref() else {
623 return Ok(None);
624 };
625 let Some(app_data_dict) = path.leaf_node().extensions().app_data_dictionary() else {
626 return Ok(None);
627 };
628 let Some(derivation_info_bytes) = app_data_dict.dictionary().get(&VC_COMPONENT_ID) else {
629 return Ok(None);
630 };
631 let derivation_info = DerivationInfo::tls_deserialize_exact_bytes(derivation_info_bytes)
632 .map_err(|e| {
633 log::error!("vc: derivation info deserialize failed: {e:?}");
634 VirtualClientsError::DerivationInfoMalformed
635 })?;
636
637 let epoch_id = derivation_info.epoch_id();
638 let storage = provider.storage();
639 let state: EmulationEpochState = storage
640 .vc_emulation_epoch_state(epoch_id)
641 .map_err(|e| {
642 log::error!("vc: load emulation epoch state failed: {e:?}");
643 VirtualClientsError::StorageError
644 })?
645 .ok_or(VirtualClientsError::MissingEmulationEpochState)?;
646 let mut operation_tree: OperationSecretTree = storage
647 .vc_operation_tree(epoch_id)
648 .map_err(|e| {
649 log::error!("vc: load operation tree failed: {e:?}");
650 VirtualClientsError::StorageError
651 })?
652 .ok_or(VirtualClientsError::MissingOperationTree)?;
653 let (_state_leaf_index, epoch_encryption_key, emulation_ciphersuite) = state.into_parts();
657
658 let crypto = provider.crypto();
659 let leaf_encryption_key = path
660 .leaf_node()
661 .encryption_key()
662 .tls_serialize_detached()
663 .map_err(VirtualClientsError::from)?;
664 let operation_type = match path.leaf_node().leaf_node_source() {
671 LeafNodeSource::KeyPackage(_) => {
672 log::error!("vc: key-package leaf on an update path");
673 return Err(VirtualClientsError::DerivationInfoMalformed.into());
674 }
675 LeafNodeSource::Update | LeafNodeSource::Commit(_) => {
676 VirtualClientOperationType::LeafNode
677 }
678 };
679 let tbe = derivation_info.decrypt(
680 crypto,
681 emulation_ciphersuite,
682 &epoch_encryption_key,
683 &leaf_encryption_key,
684 operation_type,
685 )?;
686 let external_init_secret = tbe.external_init_secret().cloned();
691 let operation_context = self.group_id().as_slice().to_vec();
694
695 let operation_secret = operation_tree.derive_operation_secret(
699 crypto,
700 emulation_ciphersuite,
701 epoch_id,
702 tbe.leaf_index(),
703 operation_type,
704 tbe.generation(),
705 &operation_context,
706 )?;
707 storage
710 .write_vc_operation_tree(epoch_id, &operation_tree)
711 .map_err(|e| {
712 log::error!("vc: persist advanced operation tree failed: {e:?}");
713 VirtualClientsError::StorageError
714 })?;
715
716 Ok(Some(
717 crate::components::vc_derivation_info::VcCommitMaterial {
718 epoch_id: epoch_id.clone(),
719 operation_secret,
720 external_init_secret,
721 },
722 ))
723 }
724
725 pub(super) fn read_decryption_keypairs(
727 &self,
728 provider: &impl OpenMlsProvider,
729 own_leaf_nodes: &[LeafNode],
730 ) -> Result<(Vec<EncryptionKeyPair>, Vec<EncryptionKeyPair>), StageCommitError> {
731 let old_epoch_keypairs = self.read_epoch_keypairs(provider.storage()).map_err(|e| {
733 log::error!("Error reading epoch keypairs: {e:?}");
734 StageCommitError::MissingDecryptionKey
735 })?;
736
737 let leaf_node_keypairs = own_leaf_nodes
741 .iter()
742 .map(|leaf_node| {
743 EncryptionKeyPair::read(provider, leaf_node.encryption_key())
744 .ok_or(StageCommitError::MissingDecryptionKey)
745 })
746 .collect::<Result<Vec<EncryptionKeyPair>, StageCommitError>>()?;
747
748 Ok((old_epoch_keypairs, leaf_node_keypairs))
749 }
750
751 #[cfg(feature = "extensions-draft")]
758 pub fn stage_app_data_commit<Provider: OpenMlsProvider>(
759 &self,
760 provider: &Provider,
761 unresolved_commit: UnresolvedAppDataCommit,
762 app_data_dict_updates: Option<AppDataUpdates>,
763 ) -> Result<StagedCommit, StageCommitError> {
764 let content = unresolved_commit.content;
765 #[cfg(feature = "virtual-clients-draft")]
766 let vc_commit_material = unresolved_commit.vc_commit_material;
767
768 let (old_epoch_keypairs, leaf_node_keypairs) =
769 self.read_decryption_keypairs(provider, &self.own_leaf_nodes)?;
770
771 self.stage_commit_with_app_data_updates(
772 &content,
773 old_epoch_keypairs,
774 leaf_node_keypairs,
775 app_data_dict_updates,
776 provider,
777 #[cfg(feature = "virtual-clients-draft")]
778 vc_commit_material,
779 )
780 }
781
782 #[cfg(feature = "extensions-draft")]
797 pub fn resolve_app_data_commit<Provider: OpenMlsProvider>(
798 &self,
799 provider: &Provider,
800 processed_message: ProcessedMessage,
801 app_data_dict_updates: Option<AppDataUpdates>,
802 ) -> Result<ProcessedMessage, ResolveAppDataCommitError> {
803 processed_message.resolve_app_data_commit(|unresolved_commit| {
804 self.stage_app_data_commit(provider, unresolved_commit, app_data_dict_updates)
805 })
806 }
807
808 pub(crate) fn process_unverified_message<Provider: OpenMlsProvider>(
833 &self,
834 provider: &Provider,
835 unverified_message: UnverifiedMessage,
836 ) -> Result<ProcessedMessage, ProcessMessageError<Provider::StorageError>> {
837 let verified =
843 unverified_message.verify(self.ciphersuite(), provider.crypto(), self.version())?;
844
845 #[cfg_attr(not(feature = "extensions-draft"), allow(unused_mut))]
846 let mut processed = match verified.content.sender() {
847 Sender::Member(_) | Sender::NewMemberProposal | Sender::NewMemberCommit => self
848 .process_internal_authenticated_content(
849 provider,
850 verified.content,
851 verified.credential,
852 #[cfg(feature = "virtual-clients-draft")]
853 verified.emulator_sender_leaf_index,
854 )?,
855 Sender::External(_) => self.process_external_authenticated_content(
856 provider,
857 verified.content,
858 verified.credential,
859 )?,
860 };
861 #[cfg(feature = "extensions-draft")]
862 if self.context().safe_aad_required() {
863 processed
864 .try_attach_safe_aad()
865 .map_err(|_| ProcessMessageError::MalformedSafeAad)?;
866 }
867 Ok(processed)
868 }
869
870 fn process_internal_authenticated_content<Provider: OpenMlsProvider>(
871 &self,
872 provider: &Provider,
873 content: AuthenticatedContent,
874 credential: Credential,
875 #[cfg(feature = "virtual-clients-draft")] emulator_sender_leaf_index: Option<LeafNodeIndex>,
876 ) -> Result<ProcessedMessage, ProcessMessageError<Provider::StorageError>> {
877 let sender = content.sender().clone();
878 let authenticated_data = content.authenticated_data().to_owned();
879 let epoch = content.epoch();
880
881 let content = match content.content() {
882 FramedContentBody::Application(application_message) => {
883 ProcessedMessageContent::ApplicationMessage(ApplicationMessage::new(
884 application_message.as_slice().to_owned(),
885 ))
886 }
887 FramedContentBody::Proposal(_) => {
888 let proposal = Box::new(QueuedProposal::from_authenticated_content_by_ref(
889 self.ciphersuite(),
890 provider.crypto(),
891 content,
892 )?);
893
894 if matches!(sender, Sender::NewMemberProposal) {
895 ProcessedMessageContent::ExternalJoinProposalMessage(proposal)
896 } else {
897 ProcessedMessageContent::ProposalMessage(proposal)
898 }
899 }
900 FramedContentBody::Commit(commit) => {
901 let is_own_commit =
902 matches!(&sender, Sender::Member(member) if member == &self.own_leaf_index());
903
904 if is_own_commit {
905 let received_tag = content
906 .confirmation_tag()
907 .ok_or(StageCommitError::ConfirmationTagMissing)?;
908 if self.matches_pending_commit(received_tag) {
909 return Ok(ProcessedMessage::new(
914 self.group_id().clone(),
915 epoch,
916 sender,
917 authenticated_data,
918 ProcessedMessageContent::OwnPendingCommit,
919 credential,
920 #[cfg(feature = "virtual-clients-draft")]
921 emulator_sender_leaf_index,
922 ));
923 }
924 }
925
926 #[cfg(feature = "virtual-clients-draft")]
944 let (vc_commit_material, is_own_commit) = {
945 let vc_commit_material =
946 if is_sibling_vc_commit(commit, &sender, self.own_leaf_index()) {
947 self.load_vc_commit_material(provider, commit)?
948 } else {
949 None
950 };
951
952 let is_own_commit = is_own_commit && vc_commit_material.is_none();
953
954 (vc_commit_material, is_own_commit)
955 };
956
957 if is_own_commit && commit.path.is_some() {
965 return Err(StageCommitError::OwnCommitMismatch.into());
966 }
967
968 #[cfg(feature = "extensions-draft")]
975 {
976 let app_data_update_proposals =
977 committed_app_data_update_proposals(commit, self.proposal_store());
978 if !app_data_update_proposals.is_empty() {
979 let unresolved_commit = UnresolvedAppDataCommit {
980 content,
981 proposals: app_data_update_proposals,
982 #[cfg(feature = "virtual-clients-draft")]
983 vc_commit_material,
984 };
985 return Ok(ProcessedMessage::new(
986 self.group_id().clone(),
987 epoch,
988 sender,
989 authenticated_data,
990 ProcessedMessageContent::UnresolvedAppDataCommit(Box::new(
991 unresolved_commit,
992 )),
993 credential,
994 #[cfg(feature = "virtual-clients-draft")]
995 emulator_sender_leaf_index,
996 ));
997 }
998 }
999
1000 let (old_epoch_keypairs, leaf_node_keypairs) =
1002 self.read_decryption_keypairs(provider, &self.own_leaf_nodes)?;
1003
1004 let staged_commit = self.stage_commit(
1005 &content,
1006 old_epoch_keypairs,
1007 leaf_node_keypairs,
1008 provider,
1009 #[cfg(feature = "virtual-clients-draft")]
1010 vc_commit_material,
1011 )?;
1012
1013 ProcessedMessageContent::StagedCommitMessage(Box::new(staged_commit))
1014 }
1015 };
1016
1017 Ok(ProcessedMessage::new(
1018 self.group_id().clone(),
1019 epoch,
1020 sender,
1021 authenticated_data,
1022 content,
1023 credential,
1024 #[cfg(feature = "virtual-clients-draft")]
1025 emulator_sender_leaf_index,
1026 ))
1027 }
1028
1029 fn process_external_authenticated_content<Provider: OpenMlsProvider>(
1035 &self,
1036 provider: &Provider,
1037 content: AuthenticatedContent,
1038 credential: Credential,
1039 ) -> Result<ProcessedMessage, ProcessMessageError<Provider::StorageError>> {
1040 #[cfg(feature = "virtual-clients-draft")]
1041 let emulator_sender_leaf_index: Option<crate::binary_tree::LeafNodeIndex> = None;
1042 let sender = content.sender().clone();
1043 let data = content.authenticated_data().to_owned();
1044
1045 debug_assert!(matches!(sender, Sender::External(_)));
1046
1047 match content.content() {
1049 FramedContentBody::Application(_) => {
1050 Err(ProcessMessageError::UnauthorizedExternalApplicationMessage)
1051 }
1052 FramedContentBody::Proposal(Proposal::GroupContextExtensions(_)) => {
1054 let content = ProcessedMessageContent::ProposalMessage(Box::new(
1055 QueuedProposal::from_authenticated_content_by_ref(
1056 self.ciphersuite(),
1057 provider.crypto(),
1058 content,
1059 )?,
1060 ));
1061 Ok(ProcessedMessage::new(
1062 self.group_id().clone(),
1063 self.context().epoch(),
1064 sender,
1065 data,
1066 content,
1067 credential,
1068 #[cfg(feature = "virtual-clients-draft")]
1069 emulator_sender_leaf_index,
1070 ))
1071 }
1072
1073 FramedContentBody::Proposal(Proposal::Remove(_)) => {
1074 let content = ProcessedMessageContent::ProposalMessage(Box::new(
1075 QueuedProposal::from_authenticated_content_by_ref(
1076 self.ciphersuite(),
1077 provider.crypto(),
1078 content,
1079 )?,
1080 ));
1081 Ok(ProcessedMessage::new(
1082 self.group_id().clone(),
1083 self.context().epoch(),
1084 sender,
1085 data,
1086 content,
1087 credential,
1088 #[cfg(feature = "virtual-clients-draft")]
1089 emulator_sender_leaf_index,
1090 ))
1091 }
1092 FramedContentBody::Proposal(Proposal::Add(_)) => {
1093 let content = ProcessedMessageContent::ProposalMessage(Box::new(
1094 QueuedProposal::from_authenticated_content_by_ref(
1095 self.ciphersuite(),
1096 provider.crypto(),
1097 content,
1098 )?,
1099 ));
1100 Ok(ProcessedMessage::new(
1101 self.group_id().clone(),
1102 self.context().epoch(),
1103 sender,
1104 data,
1105 content,
1106 credential,
1107 #[cfg(feature = "virtual-clients-draft")]
1108 emulator_sender_leaf_index,
1109 ))
1110 }
1111 FramedContentBody::Proposal(_) => Err(ProcessMessageError::UnsupportedProposalType),
1113 FramedContentBody::Commit(_) => {
1114 Err(ProcessMessageError::UnauthorizedExternalCommitMessage)
1115 }
1116 }
1117 }
1118
1119 pub(crate) fn decrypt_message(
1131 &mut self,
1132 crypto: &impl OpenMlsCrypto,
1133 message: ProtocolMessage,
1134 sender_ratchet_configuration: &SenderRatchetConfiguration,
1135 #[cfg(feature = "virtual-clients-draft")] emulator_ctx: Option<
1136 &crate::framing::EmulatorReuseGuardCtx<'_>,
1137 >,
1138 ) -> Result<InboundDecryptionResult, ValidationError> {
1139 self.public_group.validate_framing(&message)?;
1143
1144 let epoch = message.epoch();
1145
1146 match message {
1150 ProtocolMessage::PublicMessage(public_message) => {
1151 let message_secrets =
1153 self.message_secrets_for_epoch(epoch).map_err(|e| match e {
1154 SecretTreeError::TooDistantInThePast => ValidationError::NoPastEpochData,
1155 _ => LibraryError::custom(
1156 "Unexpected error while retrieving message secrets for epoch.",
1157 )
1158 .into(),
1159 })?;
1160 DecryptedMessage::from_inbound_public_message(
1161 *public_message,
1162 message_secrets,
1163 message_secrets.serialized_context().to_vec(),
1164 crypto,
1165 self.ciphersuite(),
1166 )
1167 .map(InboundDecryptionResult::Decrypted)
1168 }
1169 ProtocolMessage::PrivateMessage(ciphertext) => {
1170 DecryptedMessage::from_inbound_ciphertext(
1172 ciphertext,
1173 crypto,
1174 self,
1175 sender_ratchet_configuration,
1176 #[cfg(feature = "virtual-clients-draft")]
1177 emulator_ctx,
1178 )
1179 }
1180 }
1181 }
1182}
1183
1184#[cfg(feature = "extensions-draft")]
1192pub(crate) fn committed_app_data_update_proposals(
1193 commit: &Commit,
1194 proposal_store: &ProposalStore,
1195) -> Vec<AppDataUpdateProposal> {
1196 use crate::messages::proposals::ProposalOrRef;
1197
1198 let mut proposals: Vec<AppDataUpdateProposal> = commit
1199 .proposals
1200 .iter()
1201 .filter_map(|proposal_or_ref| match proposal_or_ref {
1202 ProposalOrRef::Proposal(proposal) => match proposal.as_ref() {
1203 Proposal::AppDataUpdate(proposal) => Some(proposal.as_ref().clone()),
1204 _ => None,
1205 },
1206 ProposalOrRef::Reference(reference) => proposal_store
1207 .proposals()
1208 .find(|queued_proposal| {
1209 queued_proposal.proposal_reference_ref() == reference.as_ref()
1210 })
1211 .and_then(|queued_proposal| match queued_proposal.proposal() {
1212 Proposal::AppDataUpdate(proposal) => Some(proposal.as_ref().clone()),
1213 _ => None,
1214 }),
1215 })
1216 .collect();
1217
1218 proposals.sort_by_key(|proposal| proposal.component_id());
1219 proposals
1220}
1221
1222#[cfg(feature = "virtual-clients-draft")]
1233fn is_sibling_vc_commit(
1234 commit: &Commit,
1235 sender: &super::Sender,
1236 own_leaf_index: crate::binary_tree::LeafNodeIndex,
1237) -> bool {
1238 use crate::messages::proposals::{Proposal, ProposalOrRef};
1239
1240 match sender {
1241 super::Sender::Member(idx) => *idx == own_leaf_index,
1242 super::Sender::NewMemberCommit => commit.proposals.iter().any(|p| {
1243 matches!(
1244 p,
1245 ProposalOrRef::Proposal(boxed)
1246 if matches!(boxed.as_ref(), Proposal::Remove(r) if r.removed() == own_leaf_index)
1247 )
1248 }),
1249 _ => false,
1250 }
1251}