1#[cfg(all(
6 feature = "by_ref_proposal",
7 feature = "custom_proposal",
8 feature = "self_remove_proposal"
9))]
10use super::SelfRemoveProposal;
11use super::{
12 commit_sender,
13 confirmation_tag::ConfirmationTag,
14 framing::{
15 ApplicationData, Content, ContentType, MlsMessage, MlsMessagePayload, PublicMessage, Sender,
16 },
17 message_signature::AuthenticatedContent,
18 mls_rules::{CommitDirection, MlsRules},
19 proposal_filter::ProposalBundle,
20 state::GroupState,
21 transcript_hash::InterimTranscriptHash,
22 transcript_hashes, validate_group_info_member, GroupContext, GroupInfo, ReInitProposal,
23 RemoveProposal, Welcome,
24};
25use crate::{
26 client::MlsError,
27 key_package::validate_key_package_properties,
28 time::MlsTime,
29 tree_kem::{
30 leaf_node_validator::{LeafNodeValidator, ValidationContext},
31 node::LeafIndex,
32 path_secret::PathSecret,
33 validate_update_path, TreeKemPrivate, TreeKemPublic, ValidatedUpdatePath,
34 },
35 CipherSuiteProvider, KeyPackage,
36};
37use itertools::Itertools;
38use mls_rs_codec::{MlsDecode, MlsEncode, MlsSize};
39
40use alloc::boxed::Box;
41use alloc::vec::Vec;
42use core::fmt::{self, Debug};
43use mls_rs_core::{
44 identity::{IdentityProvider, MemberValidationContext},
45 protocol_version::ProtocolVersion,
46 psk::PreSharedKeyStorage,
47};
48
49#[cfg(feature = "by_ref_proposal")]
50use super::proposal_ref::ProposalRef;
51
52#[cfg(not(feature = "by_ref_proposal"))]
53use crate::group::proposal_cache::resolve_for_commit;
54
55use super::proposal::Proposal;
56use super::proposal_filter::ProposalInfo;
57
58#[cfg(feature = "private_message")]
59use crate::group::framing::PrivateMessage;
60
61#[derive(Debug)]
62pub(crate) struct ProvisionalState {
63 pub(crate) public_tree: TreeKemPublic,
64 pub(crate) applied_proposals: ProposalBundle,
65 pub(crate) group_context: GroupContext,
66 pub(crate) external_init_index: Option<LeafIndex>,
67 pub(crate) indexes_of_added_kpkgs: Vec<LeafIndex>,
68 pub(crate) unused_proposals: Vec<ProposalInfo<Proposal>>,
69}
70
71pub(crate) fn path_update_required(proposals: &ProposalBundle) -> bool {
82 let res = !proposals.external_init_proposals().is_empty();
83
84 #[cfg(feature = "by_ref_proposal")]
85 let res = res || !proposals.update_proposals().is_empty();
86
87 #[cfg(all(
88 feature = "by_ref_proposal",
89 feature = "custom_proposal",
90 feature = "self_remove_proposal"
91 ))]
92 let res = res || !proposals.self_removes.is_empty();
93
94 res || proposals.length() == 0
95 || proposals.group_context_extensions_proposal().is_some()
96 || !proposals.remove_proposals().is_empty()
97}
98
99#[cfg_attr(
100 all(feature = "ffi", not(test)),
101 safer_ffi_gen::ffi_type(clone, opaque)
102)]
103#[derive(Clone, Debug, PartialEq, MlsSize, MlsEncode, MlsDecode)]
104#[non_exhaustive]
105pub struct NewEpoch {
106 pub epoch: u64,
107 pub prior_state: GroupState,
108 pub applied_proposals: Vec<ProposalInfo<Proposal>>,
109 pub unused_proposals: Vec<ProposalInfo<Proposal>>,
110}
111
112impl NewEpoch {
113 pub(crate) fn new(prior_state: GroupState, provisional_state: &ProvisionalState) -> NewEpoch {
114 NewEpoch {
115 epoch: provisional_state.group_context.epoch,
116 prior_state,
117 unused_proposals: provisional_state.unused_proposals.clone(),
118 applied_proposals: provisional_state
119 .applied_proposals
120 .clone()
121 .into_proposals()
122 .collect_vec(),
123 }
124 }
125}
126
127#[cfg(all(feature = "ffi", not(test)))]
128#[safer_ffi_gen::safer_ffi_gen]
129impl NewEpoch {
130 pub fn epoch(&self) -> u64 {
131 self.epoch
132 }
133
134 pub fn prior_state(&self) -> &GroupState {
135 &self.prior_state
136 }
137
138 pub fn applied_proposals(&self) -> &[ProposalInfo<Proposal>] {
139 &self.applied_proposals
140 }
141
142 pub fn unused_proposals(&self) -> &[ProposalInfo<Proposal>] {
143 &self.unused_proposals
144 }
145}
146
147#[cfg_attr(
148 all(feature = "ffi", not(test)),
149 safer_ffi_gen::ffi_type(clone, opaque)
150)]
151#[derive(Clone, Debug, PartialEq)]
152pub enum CommitEffect {
153 NewEpoch(Box<NewEpoch>),
154 Removed {
155 new_epoch: Box<NewEpoch>,
156 remover: Sender,
157 },
158 ReInit(ProposalInfo<ReInitProposal>),
159}
160
161impl MlsSize for CommitEffect {
162 fn mls_encoded_len(&self) -> usize {
163 0u8.mls_encoded_len()
164 + match self {
165 Self::NewEpoch(e) => e.mls_encoded_len(),
166 Self::Removed { new_epoch, remover } => {
167 new_epoch.mls_encoded_len() + remover.mls_encoded_len()
168 }
169 Self::ReInit(r) => r.mls_encoded_len(),
170 }
171 }
172}
173
174impl MlsEncode for CommitEffect {
175 fn mls_encode(&self, writer: &mut Vec<u8>) -> Result<(), mls_rs_codec::Error> {
176 match self {
177 Self::NewEpoch(e) => {
178 1u8.mls_encode(writer)?;
179 e.mls_encode(writer)?;
180 }
181 Self::Removed { new_epoch, remover } => {
182 2u8.mls_encode(writer)?;
183 new_epoch.mls_encode(writer)?;
184 remover.mls_encode(writer)?;
185 }
186 Self::ReInit(r) => {
187 3u8.mls_encode(writer)?;
188 r.mls_encode(writer)?;
189 }
190 }
191
192 Ok(())
193 }
194}
195
196impl MlsDecode for CommitEffect {
197 fn mls_decode(reader: &mut &[u8]) -> Result<Self, mls_rs_codec::Error> {
198 match u8::mls_decode(reader)? {
199 1u8 => Ok(Self::NewEpoch(NewEpoch::mls_decode(reader)?.into())),
200 2u8 => Ok(Self::Removed {
201 new_epoch: NewEpoch::mls_decode(reader)?.into(),
202 remover: Sender::mls_decode(reader)?,
203 }),
204 3u8 => Ok(Self::ReInit(ProposalInfo::mls_decode(reader)?)),
205 _ => Err(mls_rs_codec::Error::UnsupportedEnumDiscriminant),
206 }
207 }
208}
209
210#[cfg_attr(
211 all(feature = "ffi", not(test)),
212 safer_ffi_gen::ffi_type(clone, opaque)
213)]
214#[derive(Debug, Clone)]
215#[allow(clippy::large_enum_variant)]
216pub enum ReceivedMessage {
219 ApplicationMessage(ApplicationMessageDescription),
221 Commit(CommitMessageDescription),
223 Proposal(ProposalMessageDescription),
225 GroupInfo(GroupInfo),
227 Welcome,
229 KeyPackage(KeyPackage),
231}
232
233impl TryFrom<ApplicationMessageDescription> for ReceivedMessage {
234 type Error = MlsError;
235
236 fn try_from(value: ApplicationMessageDescription) -> Result<Self, Self::Error> {
237 Ok(ReceivedMessage::ApplicationMessage(value))
238 }
239}
240
241impl From<CommitMessageDescription> for ReceivedMessage {
242 fn from(value: CommitMessageDescription) -> Self {
243 ReceivedMessage::Commit(value)
244 }
245}
246
247impl From<ProposalMessageDescription> for ReceivedMessage {
248 fn from(value: ProposalMessageDescription) -> Self {
249 ReceivedMessage::Proposal(value)
250 }
251}
252
253impl From<GroupInfo> for ReceivedMessage {
254 fn from(value: GroupInfo) -> Self {
255 ReceivedMessage::GroupInfo(value)
256 }
257}
258
259impl From<Welcome> for ReceivedMessage {
260 fn from(_: Welcome) -> Self {
261 ReceivedMessage::Welcome
262 }
263}
264
265impl From<KeyPackage> for ReceivedMessage {
266 fn from(value: KeyPackage) -> Self {
267 ReceivedMessage::KeyPackage(value)
268 }
269}
270
271#[cfg_attr(
272 all(feature = "ffi", not(test)),
273 safer_ffi_gen::ffi_type(clone, opaque)
274)]
275#[derive(Clone, PartialEq, Eq)]
276pub struct ApplicationMessageDescription {
278 pub sender_index: u32,
280 data: ApplicationData,
282 pub authenticated_data: Vec<u8>,
284 #[cfg(all(feature = "export_key_generation", feature = "private_message"))]
287 pub unauthenticated_key_generation: Option<u32>,
288}
289
290impl Debug for ApplicationMessageDescription {
291 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
292 let mut res = f.debug_struct("ApplicationMessageDescription");
293 res.field("sender_index", &self.sender_index)
294 .field("data", &self.data)
295 .field(
296 "authenticated_data",
297 &mls_rs_core::debug::pretty_bytes(&self.authenticated_data),
298 );
299 #[cfg(all(feature = "export_key_generation", feature = "private_message"))]
300 res.field(
301 "unauthenticated_key_generation",
302 &self.unauthenticated_key_generation,
303 );
304 res.finish()
305 }
306}
307
308#[cfg_attr(all(feature = "ffi", not(test)), safer_ffi_gen::safer_ffi_gen)]
309impl ApplicationMessageDescription {
310 pub fn data(&self) -> &[u8] {
311 self.data.as_bytes()
312 }
313}
314
315#[cfg_attr(
316 all(feature = "ffi", not(test)),
317 safer_ffi_gen::ffi_type(clone, opaque)
318)]
319#[derive(Clone, PartialEq, MlsSize, MlsEncode, MlsDecode)]
320#[non_exhaustive]
321pub struct CommitMessageDescription {
323 pub is_external: bool,
325 pub committer: u32,
327 pub effect: CommitEffect,
329 #[mls_codec(with = "mls_rs_codec::byte_vec")]
331 pub authenticated_data: Vec<u8>,
332}
333
334impl Debug for CommitMessageDescription {
335 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
336 f.debug_struct("CommitMessageDescription")
337 .field("is_external", &self.is_external)
338 .field("committer", &self.committer)
339 .field("effect", &self.effect)
340 .field(
341 "authenticated_data",
342 &mls_rs_core::debug::pretty_bytes(&self.authenticated_data),
343 )
344 .finish()
345 }
346}
347
348#[derive(Debug, Clone, Copy, PartialEq, Eq, MlsEncode, MlsDecode, MlsSize)]
349#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
350#[repr(u8)]
351pub enum ProposalSender {
353 Member(u32) = 1u8,
355 External(u32) = 2u8,
358 NewMember = 3u8,
360}
361
362impl TryFrom<Sender> for ProposalSender {
363 type Error = MlsError;
364
365 fn try_from(value: Sender) -> Result<Self, Self::Error> {
366 match value {
367 Sender::Member(index) => Ok(Self::Member(index)),
368 #[cfg(feature = "by_ref_proposal")]
369 Sender::External(index) => Ok(Self::External(index)),
370 #[cfg(feature = "by_ref_proposal")]
371 Sender::NewMemberProposal => Ok(Self::NewMember),
372 Sender::NewMemberCommit => Err(MlsError::InvalidSender),
373 }
374 }
375}
376
377#[cfg(feature = "by_ref_proposal")]
378#[cfg_attr(
379 all(feature = "ffi", not(test)),
380 safer_ffi_gen::ffi_type(clone, opaque)
381)]
382#[derive(Clone, MlsEncode, MlsDecode, MlsSize, PartialEq)]
383#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
384#[non_exhaustive]
385pub struct ProposalMessageDescription {
387 pub sender: ProposalSender,
389 pub proposal: Proposal,
391 pub authenticated_data: Vec<u8>,
393 pub proposal_ref: ProposalRef,
395}
396
397#[cfg(feature = "by_ref_proposal")]
398impl Debug for ProposalMessageDescription {
399 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
400 f.debug_struct("ProposalMessageDescription")
401 .field("sender", &self.sender)
402 .field("proposal", &self.proposal)
403 .field(
404 "authenticated_data",
405 &mls_rs_core::debug::pretty_bytes(&self.authenticated_data),
406 )
407 .field("proposal_ref", &self.proposal_ref)
408 .finish()
409 }
410}
411
412#[cfg(feature = "by_ref_proposal")]
413#[derive(MlsSize, MlsEncode, MlsDecode)]
414pub struct CachedProposal {
415 pub(crate) proposal: Proposal,
416 pub(crate) proposal_ref: ProposalRef,
417 pub(crate) sender: Sender,
418}
419
420#[cfg(feature = "by_ref_proposal")]
421impl CachedProposal {
422 pub fn from_bytes(bytes: &[u8]) -> Result<Self, MlsError> {
424 Ok(Self::mls_decode(&mut &*bytes)?)
425 }
426
427 pub fn to_bytes(&self) -> Result<Vec<u8>, MlsError> {
429 Ok(self.mls_encode_to_vec()?)
430 }
431}
432
433#[cfg(feature = "by_ref_proposal")]
434impl ProposalMessageDescription {
435 pub fn cached_proposal(self) -> CachedProposal {
436 let sender = match self.sender {
437 ProposalSender::Member(i) => Sender::Member(i),
438 ProposalSender::External(i) => Sender::External(i),
439 ProposalSender::NewMember => Sender::NewMemberProposal,
440 };
441
442 CachedProposal {
443 proposal: self.proposal,
444 proposal_ref: self.proposal_ref,
445 sender,
446 }
447 }
448
449 pub fn proposal_ref(&self) -> Vec<u8> {
450 self.proposal_ref.to_vec()
451 }
452
453 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
454 pub(crate) async fn new<C: CipherSuiteProvider>(
455 cs: &C,
456 content: &AuthenticatedContent,
457 proposal: Proposal,
458 ) -> Result<Self, MlsError> {
459 Ok(ProposalMessageDescription {
460 authenticated_data: content.content.authenticated_data.clone(),
461 proposal,
462 sender: content.content.sender.try_into()?,
463 proposal_ref: ProposalRef::from_content(cs, content).await?,
464 })
465 }
466}
467
468#[cfg(not(feature = "by_ref_proposal"))]
469#[cfg_attr(
470 all(feature = "ffi", not(test)),
471 safer_ffi_gen::ffi_type(clone, opaque)
472)]
473#[derive(Debug, Clone)]
474pub struct ProposalMessageDescription {}
476
477#[allow(clippy::large_enum_variant)]
478pub(crate) enum EventOrContent<E> {
479 #[cfg_attr(
480 not(all(feature = "private_message", feature = "external_client")),
481 allow(dead_code)
482 )]
483 Event(E),
484 Content(AuthenticatedContent),
485}
486
487#[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
488#[cfg_attr(all(target_arch = "wasm32", mls_build_async), maybe_async::must_be_async(?Send))]
489#[cfg_attr(
490 all(not(target_arch = "wasm32"), mls_build_async),
491 maybe_async::must_be_async
492)]
493pub(crate) trait MessageProcessor: Send + Sync {
494 type OutputType: TryFrom<ApplicationMessageDescription, Error = MlsError>
495 + From<CommitMessageDescription>
496 + From<ProposalMessageDescription>
497 + From<GroupInfo>
498 + From<Welcome>
499 + From<KeyPackage>
500 + Send;
501
502 type MlsRules: MlsRules;
503 type IdentityProvider: IdentityProvider;
504 type CipherSuiteProvider: CipherSuiteProvider;
505 type PreSharedKeyStorage: PreSharedKeyStorage;
506
507 async fn process_incoming_message(
508 &mut self,
509 message: MlsMessage,
510 #[cfg(feature = "by_ref_proposal")] cache_proposal: bool,
511 ) -> Result<Self::OutputType, MlsError> {
512 self.process_incoming_message_with_time(
513 message,
514 #[cfg(feature = "by_ref_proposal")]
515 cache_proposal,
516 None,
517 )
518 .await
519 }
520
521 async fn process_incoming_message_with_time(
522 &mut self,
523 message: MlsMessage,
524 #[cfg(feature = "by_ref_proposal")] cache_proposal: bool,
525 time_sent: Option<MlsTime>,
526 ) -> Result<Self::OutputType, MlsError> {
527 #[cfg(all(feature = "export_key_generation", feature = "private_message"))]
528 let unauthn_key_gen_in_app_msg: Option<u32> = match message.payload {
534 MlsMessagePayload::Cipher(ref cipher_text) => self
535 .get_unauthenticated_key_generation_from_sender_data(cipher_text)
536 .unwrap_or_default(),
537 _ => None,
538 };
539
540 let event_or_content = self
541 .get_event_from_incoming_message(message, time_sent)
542 .await?;
543
544 self.process_event_or_content(
545 event_or_content,
546 #[cfg(feature = "by_ref_proposal")]
547 cache_proposal,
548 #[cfg(all(feature = "export_key_generation", feature = "private_message"))]
549 unauthn_key_gen_in_app_msg,
550 time_sent,
551 )
552 .await
553 }
554
555 async fn get_event_from_incoming_message(
556 &mut self,
557 message: MlsMessage,
558 time: Option<MlsTime>,
559 ) -> Result<EventOrContent<Self::OutputType>, MlsError> {
560 self.check_metadata(&message)?;
561
562 match message.payload {
563 MlsMessagePayload::Plain(plaintext) => {
564 self.verify_plaintext_authentication(plaintext).await
565 }
566 #[cfg(feature = "private_message")]
567 MlsMessagePayload::Cipher(cipher_text) => self.process_ciphertext(&cipher_text).await,
568 MlsMessagePayload::GroupInfo(group_info) => {
569 validate_group_info_member(
570 self.group_state(),
571 message.version,
572 &group_info,
573 self.cipher_suite_provider(),
574 )
575 .await?;
576
577 Ok(EventOrContent::Event(group_info.into()))
578 }
579 MlsMessagePayload::Welcome(welcome) => {
580 self.validate_welcome(&welcome, message.version)?;
581
582 Ok(EventOrContent::Event(welcome.into()))
583 }
584 MlsMessagePayload::KeyPackage(key_package) => {
585 self.validate_key_package(&key_package, message.version, time)
586 .await?;
587
588 Ok(EventOrContent::Event(key_package.into()))
589 }
590 }
591 }
592
593 async fn process_event_or_content(
594 &mut self,
595 event_or_content: EventOrContent<Self::OutputType>,
596 #[cfg(feature = "by_ref_proposal")] cache_proposal: bool,
597 #[cfg(all(feature = "export_key_generation", feature = "private_message"))]
598 unauthn_key_gen_in_app_msg: Option<u32>,
599 time_sent: Option<MlsTime>,
600 ) -> Result<Self::OutputType, MlsError> {
601 let msg = match event_or_content {
602 EventOrContent::Event(event) => event,
603 EventOrContent::Content(content) => {
604 self.process_auth_content(
605 content,
606 #[cfg(feature = "by_ref_proposal")]
607 cache_proposal,
608 #[cfg(all(feature = "export_key_generation", feature = "private_message"))]
609 unauthn_key_gen_in_app_msg,
610 time_sent,
611 )
612 .await?
613 }
614 };
615
616 Ok(msg)
617 }
618
619 async fn process_auth_content(
620 &mut self,
621 auth_content: AuthenticatedContent,
622 #[cfg(feature = "by_ref_proposal")] cache_proposal: bool,
623 #[cfg(all(feature = "export_key_generation", feature = "private_message"))]
624 unauthn_key_gen_in_app_msg: Option<u32>,
625 time_sent: Option<MlsTime>,
626 ) -> Result<Self::OutputType, MlsError> {
627 let event = match auth_content.content.content {
628 #[cfg(feature = "private_message")]
629 Content::Application(data) => {
630 let authenticated_data = auth_content.content.authenticated_data;
631 let sender = auth_content.content.sender;
632
633 self.process_application_message(
634 data,
635 sender,
636 authenticated_data,
637 #[cfg(all(feature = "export_key_generation", feature = "private_message"))]
638 unauthn_key_gen_in_app_msg,
639 )
640 .and_then(Self::OutputType::try_from)
641 }
642 Content::Commit(_) => self
643 .process_commit(auth_content, time_sent)
644 .await
645 .map(Self::OutputType::from),
646 #[cfg(feature = "by_ref_proposal")]
647 Content::Proposal(ref proposal) => self
648 .process_proposal(&auth_content, proposal, cache_proposal)
649 .await
650 .map(Self::OutputType::from),
651 }?;
652
653 Ok(event)
654 }
655
656 #[cfg(feature = "private_message")]
657 fn process_application_message(
658 &self,
659 data: ApplicationData,
660 sender: Sender,
661 authenticated_data: Vec<u8>,
662 #[cfg(all(feature = "export_key_generation", feature = "private_message"))]
663 unauthenticated_key_generation: Option<u32>,
664 ) -> Result<ApplicationMessageDescription, MlsError> {
665 let Sender::Member(sender_index) = sender else {
666 return Err(MlsError::InvalidSender);
667 };
668
669 Ok(ApplicationMessageDescription {
670 authenticated_data,
671 sender_index,
672 data,
673 #[cfg(all(feature = "export_key_generation", feature = "private_message"))]
674 unauthenticated_key_generation,
675 })
676 }
677
678 #[cfg(feature = "by_ref_proposal")]
679 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
680 async fn process_proposal(
681 &mut self,
682 auth_content: &AuthenticatedContent,
683 proposal: &Proposal,
684 cache_proposal: bool,
685 ) -> Result<ProposalMessageDescription, MlsError> {
686 let proposal = ProposalMessageDescription::new(
687 self.cipher_suite_provider(),
688 auth_content,
689 proposal.clone(),
690 )
691 .await?;
692
693 let group_state = self.group_state_mut();
694
695 if cache_proposal {
696 group_state.proposals.insert(
697 proposal.proposal_ref.clone(),
698 proposal.proposal.clone(),
699 auth_content.content.sender,
700 );
701 }
702
703 Ok(proposal)
704 }
705
706 async fn process_commit(
707 &mut self,
708 auth_content: AuthenticatedContent,
709 time_sent: Option<MlsTime>,
710 ) -> Result<CommitMessageDescription, MlsError> {
711 if self.group_state().pending_reinit.is_some() {
712 return Err(MlsError::GroupUsedAfterReInit);
713 }
714
715 let (interim_transcript_hash, confirmed_transcript_hash) = transcript_hashes(
717 self.cipher_suite_provider(),
718 &self.group_state().interim_transcript_hash,
719 &auth_content,
720 )
721 .await?;
722
723 #[cfg(any(feature = "private_message", feature = "by_ref_proposal"))]
724 let commit = match auth_content.content.content {
725 Content::Commit(commit) => Ok(commit),
726 _ => Err(MlsError::UnexpectedMessageType),
727 }?;
728
729 #[cfg(not(any(feature = "private_message", feature = "by_ref_proposal")))]
730 let Content::Commit(commit) = auth_content.content.content;
731
732 let group_state = self.group_state();
733 let id_provider = self.identity_provider();
734
735 #[cfg(feature = "by_ref_proposal")]
736 let proposals = group_state
737 .proposals
738 .resolve_for_commit(auth_content.content.sender, commit.proposals)?;
739
740 #[cfg(not(feature = "by_ref_proposal"))]
741 let proposals = resolve_for_commit(auth_content.content.sender, commit.proposals)?;
742
743 let mut provisional_state = group_state
744 .apply_resolved(
745 auth_content.content.sender,
746 proposals,
747 commit.path.as_ref().map(|path| &path.leaf_node),
748 &id_provider,
749 self.cipher_suite_provider(),
750 &self.psk_storage(),
751 &self.mls_rules(),
752 time_sent,
753 CommitDirection::Receive,
754 )
755 .await?;
756
757 let sender = commit_sender(&auth_content.content.sender, &provisional_state)?;
758
759 if path_update_required(&provisional_state.applied_proposals) && commit.path.is_none() {
762 return Err(MlsError::CommitMissingPath);
763 }
764
765 let self_removed = self.removal_proposal(&provisional_state);
766 #[cfg(all(
767 feature = "by_ref_proposal",
768 feature = "custom_proposal",
769 feature = "self_remove_proposal"
770 ))]
771 let self_removed_by_self = self.self_removal_proposal(&provisional_state);
772
773 let is_self_removed = self_removed.is_some();
774 #[cfg(all(
775 feature = "by_ref_proposal",
776 feature = "custom_proposal",
777 feature = "self_remove_proposal"
778 ))]
779 let is_self_removed = is_self_removed || self_removed_by_self.is_some();
780
781 let update_path = match commit.path {
782 Some(update_path) => Some(
783 validate_update_path(
784 &self.identity_provider(),
785 self.cipher_suite_provider(),
786 update_path,
787 &provisional_state,
788 sender,
789 time_sent,
790 &group_state.context,
791 )
792 .await?,
793 ),
794 None => None,
795 };
796
797 let commit_effect =
798 if let Some(reinit) = provisional_state.applied_proposals.reinitializations.pop() {
799 self.group_state_mut().pending_reinit = Some(reinit.proposal.clone());
800 CommitEffect::ReInit(reinit)
801 } else if let Some(remove_proposal) = self_removed {
802 let new_epoch = NewEpoch::new(self.group_state().clone(), &provisional_state);
803 CommitEffect::Removed {
804 remover: remove_proposal.sender,
805 new_epoch: Box::new(new_epoch),
806 }
807 } else {
808 CommitEffect::NewEpoch(Box::new(NewEpoch::new(
809 self.group_state().clone(),
810 &provisional_state,
811 )))
812 };
813
814 #[cfg(all(
815 feature = "by_ref_proposal",
816 feature = "custom_proposal",
817 feature = "self_remove_proposal"
818 ))]
819 let commit_effect = if let Some(self_remove_proposal) = self_removed_by_self {
820 let new_epoch = NewEpoch::new(self.group_state().clone(), &provisional_state);
821 CommitEffect::Removed {
822 remover: self_remove_proposal.sender,
823 new_epoch: Box::new(new_epoch),
824 }
825 } else {
826 commit_effect
827 };
828
829 let new_secrets = match update_path {
830 Some(update_path) if !is_self_removed => {
831 self.apply_update_path(sender, &update_path, &mut provisional_state)
832 .await
833 }
834 _ => Ok(None),
835 }?;
836
837 provisional_state.group_context.confirmed_transcript_hash = confirmed_transcript_hash;
839
840 provisional_state
842 .public_tree
843 .update_hashes(&[sender], self.cipher_suite_provider())
844 .await?;
845
846 provisional_state.group_context.tree_hash = provisional_state
848 .public_tree
849 .tree_hash(self.cipher_suite_provider())
850 .await?;
851
852 if let Some(confirmation_tag) = &auth_content.auth.confirmation_tag {
853 if !is_self_removed {
854 self.update_key_schedule(
856 new_secrets,
857 interim_transcript_hash,
858 confirmation_tag,
859 provisional_state,
860 )
861 .await?;
862 }
863 Ok(CommitMessageDescription {
864 is_external: matches!(auth_content.content.sender, Sender::NewMemberCommit),
865 authenticated_data: auth_content.content.authenticated_data,
866 committer: *sender,
867 effect: commit_effect,
868 })
869 } else {
870 Err(MlsError::InvalidConfirmationTag)
871 }
872 }
873
874 fn group_state(&self) -> &GroupState;
875 fn group_state_mut(&mut self) -> &mut GroupState;
876 fn mls_rules(&self) -> Self::MlsRules;
877 fn identity_provider(&self) -> Self::IdentityProvider;
878 fn cipher_suite_provider(&self) -> &Self::CipherSuiteProvider;
879 fn psk_storage(&self) -> Self::PreSharedKeyStorage;
880
881 fn removal_proposal(
882 &self,
883 provisional_state: &ProvisionalState,
884 ) -> Option<ProposalInfo<RemoveProposal>>;
885
886 #[cfg(all(
887 feature = "by_ref_proposal",
888 feature = "custom_proposal",
889 feature = "self_remove_proposal"
890 ))]
891 #[cfg_attr(feature = "ffi", safer_ffi_gen::safer_ffi_gen_ignore)]
892 fn self_removal_proposal(
893 &self,
894 provisional_state: &ProvisionalState,
895 ) -> Option<ProposalInfo<SelfRemoveProposal>>;
896
897 #[cfg(feature = "private_message")]
898 fn min_epoch_available(&self) -> Option<u64>;
899
900 fn check_metadata(&self, message: &MlsMessage) -> Result<(), MlsError> {
901 let context = &self.group_state().context;
902
903 if message.version != context.protocol_version {
904 return Err(MlsError::ProtocolVersionMismatch);
905 }
906
907 if let Some((group_id, epoch, content_type)) = match &message.payload {
908 MlsMessagePayload::Plain(plaintext) => Some((
909 &plaintext.content.group_id,
910 plaintext.content.epoch,
911 plaintext.content.content_type(),
912 )),
913 #[cfg(feature = "private_message")]
914 MlsMessagePayload::Cipher(ciphertext) => Some((
915 &ciphertext.group_id,
916 ciphertext.epoch,
917 ciphertext.content_type,
918 )),
919 _ => None,
920 } {
921 if group_id != &context.group_id {
922 return Err(MlsError::GroupIdMismatch);
923 }
924
925 match content_type {
926 ContentType::Commit => {
927 if context.epoch != epoch {
928 Err(MlsError::InvalidEpoch)
929 } else {
930 Ok(())
931 }
932 }
933 #[cfg(feature = "by_ref_proposal")]
934 ContentType::Proposal => {
935 if context.epoch != epoch {
936 Err(MlsError::InvalidEpoch)
937 } else {
938 Ok(())
939 }
940 }
941 #[cfg(feature = "private_message")]
942 ContentType::Application => {
943 if let Some(min) = self.min_epoch_available() {
944 if epoch < min {
945 Err(MlsError::InvalidEpoch)
946 } else {
947 Ok(())
948 }
949 } else {
950 Ok(())
951 }
952 }
953 }?;
954
955 let check_epoch = content_type == ContentType::Commit;
957
958 #[cfg(feature = "by_ref_proposal")]
959 let check_epoch = check_epoch || content_type == ContentType::Proposal;
960
961 if check_epoch && epoch != context.epoch {
962 return Err(MlsError::InvalidEpoch);
963 }
964
965 #[cfg(feature = "private_message")]
967 if !matches!(&message.payload, MlsMessagePayload::Cipher(_))
968 && content_type == ContentType::Application
969 {
970 return Err(MlsError::UnencryptedApplicationMessage);
971 }
972 }
973
974 Ok(())
975 }
976
977 fn validate_welcome(
978 &self,
979 welcome: &Welcome,
980 version: ProtocolVersion,
981 ) -> Result<(), MlsError> {
982 let state = self.group_state();
983
984 (welcome.cipher_suite == state.context.cipher_suite
985 && version == state.context.protocol_version)
986 .then_some(())
987 .ok_or(MlsError::InvalidWelcomeMessage)
988 }
989
990 async fn validate_key_package(
991 &self,
992 key_package: &KeyPackage,
993 version: ProtocolVersion,
994 time: Option<MlsTime>,
995 ) -> Result<(), MlsError> {
996 let cs = self.cipher_suite_provider();
997 let id = self.identity_provider();
998
999 validate_key_package(key_package, version, cs, &id, time).await
1000 }
1001
1002 #[cfg(feature = "private_message")]
1003 async fn process_ciphertext(
1004 &mut self,
1005 cipher_text: &PrivateMessage,
1006 ) -> Result<EventOrContent<Self::OutputType>, MlsError>;
1007
1008 async fn verify_plaintext_authentication(
1009 &self,
1010 message: PublicMessage,
1011 ) -> Result<EventOrContent<Self::OutputType>, MlsError>;
1012
1013 #[cfg(all(feature = "export_key_generation", feature = "private_message"))]
1014 async fn get_unauthenticated_key_generation_from_sender_data(
1016 &mut self,
1017 cipher_text: &PrivateMessage,
1018 ) -> Result<Option<u32>, MlsError>;
1019
1020 async fn apply_update_path(
1021 &mut self,
1022 sender: LeafIndex,
1023 update_path: &ValidatedUpdatePath,
1024 provisional_state: &mut ProvisionalState,
1025 ) -> Result<Option<(TreeKemPrivate, PathSecret)>, MlsError> {
1026 provisional_state
1027 .public_tree
1028 .apply_update_path(
1029 sender,
1030 update_path,
1031 &provisional_state.group_context.extensions,
1032 self.identity_provider(),
1033 self.cipher_suite_provider(),
1034 )
1035 .await
1036 .map(|_| None)
1037 }
1038
1039 async fn update_key_schedule(
1040 &mut self,
1041 secrets: Option<(TreeKemPrivate, PathSecret)>,
1042 interim_transcript_hash: InterimTranscriptHash,
1043 confirmation_tag: &ConfirmationTag,
1044 provisional_public_state: ProvisionalState,
1045 ) -> Result<(), MlsError>;
1046}
1047
1048#[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
1049pub(crate) async fn validate_key_package<C: CipherSuiteProvider, I: IdentityProvider>(
1050 key_package: &KeyPackage,
1051 version: ProtocolVersion,
1052 cs: &C,
1053 id: &I,
1054 time: Option<MlsTime>,
1055) -> Result<(), MlsError> {
1056 let validator = LeafNodeValidator::new(cs, id, MemberValidationContext::None);
1057
1058 #[cfg(feature = "std")]
1059 let context = Some(MlsTime::now());
1060
1061 #[cfg(not(feature = "std"))]
1062 let context = None;
1063
1064 let context = if time.is_some() { time } else { context };
1065
1066 let context = ValidationContext::Add(context);
1067
1068 validator
1069 .check_if_valid(&key_package.leaf_node, context)
1070 .await?;
1071
1072 validate_key_package_properties(key_package, version, cs).await?;
1073
1074 Ok(())
1075}
1076
1077#[cfg(test)]
1078mod tests {
1079 use alloc::{vec, vec::Vec};
1080 use mls_rs_codec::{MlsDecode, MlsEncode};
1081
1082 use crate::{
1083 client::test_utils::TEST_PROTOCOL_VERSION,
1084 group::{test_utils::get_test_group_context, GroupState, Sender},
1085 };
1086
1087 use super::{CommitEffect, NewEpoch};
1088
1089 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
1090 async fn commit_effect_codec() {
1091 let epoch = NewEpoch {
1092 epoch: 7,
1093 prior_state: GroupState {
1094 #[cfg(feature = "by_ref_proposal")]
1095 proposals: crate::group::ProposalCache::new(TEST_PROTOCOL_VERSION, vec![]),
1096 context: get_test_group_context(7, 7.into()).await,
1097 public_tree: Default::default(),
1098 interim_transcript_hash: vec![].into(),
1099 pending_reinit: None,
1100 confirmation_tag: Default::default(),
1101 },
1102 applied_proposals: vec![],
1103 unused_proposals: vec![],
1104 };
1105
1106 let effects = vec![
1107 CommitEffect::NewEpoch(epoch.clone().into()),
1108 CommitEffect::Removed {
1109 new_epoch: epoch.into(),
1110 remover: Sender::Member(0),
1111 },
1112 ];
1113
1114 let bytes = effects.mls_encode_to_vec().unwrap();
1115
1116 assert_eq!(
1117 effects,
1118 Vec::<CommitEffect>::mls_decode(&mut &*bytes).unwrap()
1119 );
1120 }
1121}