Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Firefox/third_party/rust/mls-rs/src/group/   (Browser von der Mozilla Stiftung Version 136.0.1©)  Datei vom 10.2.2025 mit Größe 33 kB image not shown  

Quelle  message_processor.rs   Sprache: unbekannt

 
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// Copyright by contributors to this project.
// SPDX-License-Identifier: (Apache-2.0 OR MIT)

use super::{
    commit_sender,
    confirmation_tag::ConfirmationTag,
    framing::{
        ApplicationData, Content, ContentType, MlsMessage, MlsMessagePayload, PublicMessage, Sender,
    },
    message_signature::AuthenticatedContent,
    mls_rules::{CommitDirection, MlsRules},
    proposal_filter::ProposalBundle,
    state::GroupState,
    transcript_hash::InterimTranscriptHash,
    transcript_hashes, validate_group_info_member, GroupContext, GroupInfo, Welcome,
};
use crate::{
    client::MlsError,
    key_package::validate_key_package_properties,
    time::MlsTime,
    tree_kem::{
        leaf_node_validator::{LeafNodeValidator, ValidationContext},
        node::LeafIndex,
        path_secret::PathSecret,
        validate_update_path, TreeKemPrivate, TreeKemPublic, ValidatedUpdatePath,
    },
    CipherSuiteProvider, KeyPackage,
};
#[cfg(mls_build_async)]
use alloc::boxed::Box;
use alloc::vec::Vec;
use core::fmt::{self, Debug};
use mls_rs_core::{
    identity::IdentityProvider, protocol_version::ProtocolVersion, psk::PreSharedKeyStorage,
};

#[cfg(feature = "by_ref_proposal")]
use super::proposal_ref::ProposalRef;

#[cfg(not(feature = "by_ref_proposal"))]
use crate::group::proposal_cache::resolve_for_commit;

#[cfg(feature = "by_ref_proposal")]
use super::proposal::Proposal;

#[cfg(feature = "custom_proposal")]
use super::proposal_filter::ProposalInfo;

#[cfg(feature = "state_update")]
use mls_rs_core::{
    crypto::CipherSuite,
    group::{MemberUpdate, RosterUpdate},
};

#[cfg(all(feature = "state_update", feature = "psk"))]
use mls_rs_core::psk::ExternalPskId;

#[cfg(feature = "state_update")]
use crate::tree_kem::UpdatePath;

#[cfg(feature = "state_update")]
use super::{member_from_key_package, member_from_leaf_node};

#[cfg(all(feature = "state_update", feature = "custom_proposal"))]
use super::proposal::CustomProposal;

#[cfg(feature = "private_message")]
use crate::group::framing::PrivateMessage;

#[cfg(feature = "by_ref_proposal")]
use mls_rs_codec::{MlsDecode, MlsEncode, MlsSize};

#[derive(Debug)]
pub(crate) struct ProvisionalState {
    pub(crate) public_tree: TreeKemPublic,
    pub(crate) applied_proposals: ProposalBundle,
    pub(crate) group_context: GroupContext,
    pub(crate) external_init_index: Option<LeafIndex>,
    pub(crate) indexes_of_added_kpkgs: Vec<LeafIndex>,
    #[cfg(feature = "by_ref_proposal")]
    pub(crate) unused_proposals: Vec<crate::mls_rules::ProposalInfo<Proposal>>,
}

//By default, the path field of a Commit MUST be populated. The path field MAY be omitted if
//(a) it covers at least one proposal and (b) none of the proposals covered by the Commit are
//of "path required" types. A proposal type requires a path if it cannot change the group
//membership in a way that requires the forward secrecy and post-compromise security guarantees
//that an UpdatePath provides. The only proposal types defined in this document that do not
//require a path are:

// add
// psk
// reinit
pub(crate) fn path_update_required(proposals: &ProposalBundle) -> bool {
    let res = proposals.external_init_proposals().first().is_some();

    #[cfg(feature = "by_ref_proposal")]
    let res = res || !proposals.update_proposals().is_empty();

    res || proposals.length() == 0
        || proposals.group_context_extensions_proposal().is_some()
        || !proposals.remove_proposals().is_empty()
}

/// Representation of changes made by a [commit](crate::Group::commit).
#[cfg(feature = "state_update")]
#[derive(Clone, Debug, PartialEq)]
pub struct StateUpdate {
    pub(crate) roster_update: RosterUpdate,
    #[cfg(feature = "psk")]
    pub(crate) added_psks: Vec<ExternalPskId>,
    pub(crate) pending_reinit: Option<CipherSuite>,
    pub(crate) active: bool,
    pub(crate) epoch: u64,
    #[cfg(feature = "custom_proposal")]
    pub(crate) custom_proposals: Vec<ProposalInfo<CustomProposal>>,
    #[cfg(feature = "by_ref_proposal")]
    pub(crate) unused_proposals: Vec<crate::mls_rules::ProposalInfo<Proposal>>,
}

#[cfg(not(feature = "state_update"))]
#[non_exhaustive]
#[derive(Clone, Debug, PartialEq)]
pub struct StateUpdate {}

#[cfg(feature = "state_update")]
impl StateUpdate {
    /// Changes to the roster as a result of proposals.
    pub fn roster_update(&self) -> &RosterUpdate {
        &self.roster_update
    }

    #[cfg(feature = "psk")]
    /// Pre-shared keys that have been added to the group.
    pub fn added_psks(&self) -> &[ExternalPskId] {
        &self.added_psks
    }

    /// Flag to indicate if the group is now pending reinitialization due to
    /// receiving a [`ReInit`](crate::group::proposal::Proposal::ReInit)
    /// proposal.
    pub fn is_pending_reinit(&self) -> bool {
        self.pending_reinit.is_some()
    }

    /// Flag to indicate the group is still active. This will be false if the
    /// member processing the commit has been removed from the group.
    pub fn is_active(&self) -> bool {
        self.active
    }

    /// The new epoch of the group state.
    pub fn new_epoch(&self) -> u64 {
        self.epoch
    }

    /// Custom proposals that were committed to.
    #[cfg(feature = "custom_proposal")]
    pub fn custom_proposals(&self) -> &[ProposalInfo<CustomProposal>] {
        &self.custom_proposals
    }

    /// Proposals that were received in the prior epoch but not committed to.
    #[cfg(feature = "by_ref_proposal")]
    pub fn unused_proposals(&self) -> &[crate::mls_rules::ProposalInfo<Proposal>] {
        &self.unused_proposals
    }

    pub fn pending_reinit_ciphersuite(&self) -> Option<CipherSuite> {
        self.pending_reinit
    }
}

// #[cfg_attr(
//     all(feature = "ffi", not(test)),
//     safer_ffi_gen::ffi_type(clone, opaque)
// )]
#[derive(Debug, Clone)]
#[allow(clippy::large_enum_variant)]
/// An event generated as a result of processing a message for a group with
/// [`Group::process_incoming_message`](crate::group::Group::process_incoming_message).
pub enum ReceivedMessage {
    /// An application message was decrypted.
    ApplicationMessage(ApplicationMessageDescription),
    /// A new commit was processed creating a new group state.
    Commit(CommitMessageDescription),
    /// A proposal was received.
    Proposal(ProposalMessageDescription),
    /// Validated GroupInfo object
    GroupInfo(GroupInfo),
    /// Validated welcome message
    Welcome,
    /// Validated key package
    KeyPackage(KeyPackage),
}

impl TryFrom<ApplicationMessageDescription> for ReceivedMessage {
    type Error = MlsError;

    fn try_from(value: ApplicationMessageDescription) -> Result<Self, Self::Error> {
        Ok(ReceivedMessage::ApplicationMessage(value))
    }
}

impl From<CommitMessageDescription> for ReceivedMessage {
    fn from(value: CommitMessageDescription) -> Self {
        ReceivedMessage::Commit(value)
    }
}

impl From<ProposalMessageDescription> for ReceivedMessage {
    fn from(value: ProposalMessageDescription) -> Self {
        ReceivedMessage::Proposal(value)
    }
}

impl From<GroupInfo> for ReceivedMessage {
    fn from(value: GroupInfo) -> Self {
        ReceivedMessage::GroupInfo(value)
    }
}

impl From<Welcome> for ReceivedMessage {
    fn from(_: Welcome) -> Self {
        ReceivedMessage::Welcome
    }
}

impl From<KeyPackage> for ReceivedMessage {
    fn from(value: KeyPackage) -> Self {
        ReceivedMessage::KeyPackage(value)
    }
}

// #[cfg_attr(
//     all(feature = "ffi", not(test)),
//     safer_ffi_gen::ffi_type(clone, opaque)
// )]
#[derive(Clone, PartialEq, Eq)]
/// Description of a MLS application message.
pub struct ApplicationMessageDescription {
    /// Index of this user in the group state.
    pub sender_index: u32,
    /// Received application data.
    data: ApplicationData,
    /// Plaintext authenticated data in the received MLS packet.
    pub authenticated_data: Vec<u8>,
}

impl Debug for ApplicationMessageDescription {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_struct("ApplicationMessageDescription")
            .field("sender_index", &self.sender_index)
            .field("data", &self.data)
            .field(
                "authenticated_data",
                &mls_rs_core::debug::pretty_bytes(&self.authenticated_data),
            )
            .finish()
    }
}

// #[cfg_attr(all(feature = "ffi", not(test)), safer_ffi_gen::safer_ffi_gen)]
impl ApplicationMessageDescription {
    pub fn data(&self) -> &[u8] {
        self.data.as_bytes()
    }
}

// #[cfg_attr(
//     all(feature = "ffi", not(test)),
//     safer_ffi_gen::ffi_type(clone, opaque)
// )]
#[derive(Clone, PartialEq)]
#[non_exhaustive]
/// Description of a processed MLS commit message.
pub struct CommitMessageDescription {
    /// True if this is the result of an external commit.
    pub is_external: bool,
    /// The index in the group state of the member who performed this commit.
    pub committer: u32,
    /// A full description of group state changes as a result of this commit.
    pub state_update: StateUpdate,
    /// Plaintext authenticated data in the received MLS packet.
    pub authenticated_data: Vec<u8>,
}

impl Debug for CommitMessageDescription {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_struct("CommitMessageDescription")
            .field("is_external", &self.is_external)
            .field("committer", &self.committer)
            .field("state_update", &self.state_update)
            .field(
                "authenticated_data",
                &mls_rs_core::debug::pretty_bytes(&self.authenticated_data),
            )
            .finish()
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
/// Proposal sender type.
pub enum ProposalSender {
    /// A current member of the group by index in the group state.
    Member(u32),
    /// An external entity by index within an
    /// [`ExternalSendersExt`](crate::extension::built_in::ExternalSendersExt).
    External(u32),
    /// A new member proposing their addition to the group.
    NewMember,
}

impl TryFrom<Sender> for ProposalSender {
    type Error = MlsError;

    fn try_from(value: Sender) -> Result<Self, Self::Error> {
        match value {
            Sender::Member(index) => Ok(Self::Member(index)),
            #[cfg(feature = "by_ref_proposal")]
            Sender::External(index) => Ok(Self::External(index)),
            #[cfg(feature = "by_ref_proposal")]
            Sender::NewMemberProposal => Ok(Self::NewMember),
            Sender::NewMemberCommit => Err(MlsError::InvalidSender),
        }
    }
}

#[cfg(feature = "by_ref_proposal")]
// #[cfg_attr(
//     all(feature = "ffi", not(test)),
//     safer_ffi_gen::ffi_type(clone, opaque)
// )]
#[derive(Clone)]
#[non_exhaustive]
/// Description of a processed MLS proposal message.
pub struct ProposalMessageDescription {
    /// Sender of the proposal.
    pub sender: ProposalSender,
    /// Proposal content.
    pub proposal: Proposal,
    /// Plaintext authenticated data in the received MLS packet.
    pub authenticated_data: Vec<u8>,
    /// Proposal reference.
    pub proposal_ref: ProposalRef,
}

#[cfg(feature = "by_ref_proposal")]
impl Debug for ProposalMessageDescription {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_struct("ProposalMessageDescription")
            .field("sender", &self.sender)
            .field("proposal", &self.proposal)
            .field(
                "authenticated_data",
                &mls_rs_core::debug::pretty_bytes(&self.authenticated_data),
            )
            .field("proposal_ref", &self.proposal_ref)
            .finish()
    }
}

#[cfg(feature = "by_ref_proposal")]
#[derive(MlsSize, MlsEncode, MlsDecode)]
pub struct CachedProposal {
    pub(crate) proposal: Proposal,
    pub(crate) proposal_ref: ProposalRef,
    pub(crate) sender: Sender,
}

#[cfg(feature = "by_ref_proposal")]
impl CachedProposal {
    /// Deserialize the proposal
    pub fn from_bytes(bytes: &[u8]) -> Result<Self, MlsError> {
        Ok(Self::mls_decode(&mut &*bytes)?)
    }

    /// Serialize the proposal
    pub fn to_bytes(&self) -> Result<Vec<u8>, MlsError> {
        Ok(self.mls_encode_to_vec()?)
    }
}

#[cfg(feature = "by_ref_proposal")]
impl ProposalMessageDescription {
    pub fn cached_proposal(self) -> CachedProposal {
        let sender = match self.sender {
            ProposalSender::Member(i) => Sender::Member(i),
            ProposalSender::External(i) => Sender::External(i),
            ProposalSender::NewMember => Sender::NewMemberProposal,
        };

        CachedProposal {
            proposal: self.proposal,
            proposal_ref: self.proposal_ref,
            sender,
        }
    }

    pub fn proposal_ref(&self) -> Vec<u8> {
        self.proposal_ref.to_vec()
    }
}

#[cfg(not(feature = "by_ref_proposal"))]
// #[cfg_attr(
//     all(feature = "ffi", not(test)),
//     safer_ffi_gen::ffi_type(clone, opaque)
// )]
#[derive(Debug, Clone)]
/// Description of a processed MLS proposal message.
pub struct ProposalMessageDescription {}

#[allow(clippy::large_enum_variant)]
pub(crate) enum EventOrContent<E> {
    #[cfg_attr(
        not(all(feature = "private_message", feature = "external_client")),
        allow(dead_code)
    )]
    Event(E),
    Content(AuthenticatedContent),
}

#[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
#[cfg_attr(all(target_arch = "wasm32", mls_build_async), maybe_async::must_be_async(?Send))]
#[cfg_attr(
    all(not(target_arch = "wasm32"), mls_build_async),
    maybe_async::must_be_async
)]
pub(crate) trait MessageProcessor: Send + Sync {
    type OutputType: TryFrom<ApplicationMessageDescription, Error = MlsError>
        + From<CommitMessageDescription>
        + From<ProposalMessageDescription>
        + From<GroupInfo>
        + From<Welcome>
        + From<KeyPackage>
        + Send;

    type MlsRules: MlsRules;
    type IdentityProvider: IdentityProvider;
    type CipherSuiteProvider: CipherSuiteProvider;
    type PreSharedKeyStorage: PreSharedKeyStorage;

    async fn process_incoming_message(
        &mut self,
        message: MlsMessage,
        #[cfg(feature = "by_ref_proposal")] cache_proposal: bool,
    ) -> Result<Self::OutputType, MlsError> {
        self.process_incoming_message_with_time(
            message,
            #[cfg(feature = "by_ref_proposal")]
            cache_proposal,
            None,
        )
        .await
    }

    async fn process_incoming_message_with_time(
        &mut self,
        message: MlsMessage,
        #[cfg(feature = "by_ref_proposal")] cache_proposal: bool,
        time_sent: Option<MlsTime>,
    ) -> Result<Self::OutputType, MlsError> {
        let event_or_content = self.get_event_from_incoming_message(message).await?;

        self.process_event_or_content(
            event_or_content,
            #[cfg(feature = "by_ref_proposal")]
            cache_proposal,
            time_sent,
        )
        .await
    }

    async fn get_event_from_incoming_message(
        &mut self,
        message: MlsMessage,
    ) -> Result<EventOrContent<Self::OutputType>, MlsError> {
        self.check_metadata(&message)?;

        match message.payload {
            MlsMessagePayload::Plain(plaintext) => {
                self.verify_plaintext_authentication(plaintext).await
            }
            #[cfg(feature = "private_message")]
            MlsMessagePayload::Cipher(cipher_text) => self.process_ciphertext(&cipher_text).await,
            MlsMessagePayload::GroupInfo(group_info) => {
                validate_group_info_member(
                    self.group_state(),
                    message.version,
                    &group_info,
                    self.cipher_suite_provider(),
                )
                .await?;

                Ok(EventOrContent::Event(group_info.into()))
            }
            MlsMessagePayload::Welcome(welcome) => {
                self.validate_welcome(&welcome, message.version)?;

                Ok(EventOrContent::Event(welcome.into()))
            }
            MlsMessagePayload::KeyPackage(key_package) => {
                self.validate_key_package(&key_package, message.version)
                    .await?;

                Ok(EventOrContent::Event(key_package.into()))
            }
        }
    }

    async fn process_event_or_content(
        &mut self,
        event_or_content: EventOrContent<Self::OutputType>,
        #[cfg(feature = "by_ref_proposal")] cache_proposal: bool,
        time_sent: Option<MlsTime>,
    ) -> Result<Self::OutputType, MlsError> {
        let msg = match event_or_content {
            EventOrContent::Event(event) => event,
            EventOrContent::Content(content) => {
                self.process_auth_content(
                    content,
                    #[cfg(feature = "by_ref_proposal")]
                    cache_proposal,
                    time_sent,
                )
                .await?
            }
        };

        Ok(msg)
    }

    async fn process_auth_content(
        &mut self,
        auth_content: AuthenticatedContent,
        #[cfg(feature = "by_ref_proposal")] cache_proposal: bool,
        time_sent: Option<MlsTime>,
    ) -> Result<Self::OutputType, MlsError> {
        let event = match auth_content.content.content {
            #[cfg(feature = "private_message")]
            Content::Application(data) => {
                let authenticated_data = auth_content.content.authenticated_data;
                let sender = auth_content.content.sender;

                self.process_application_message(data, sender, authenticated_data)
                    .and_then(Self::OutputType::try_from)
            }
            Content::Commit(_) => self
                .process_commit(auth_content, time_sent)
                .await
                .map(Self::OutputType::from),
            #[cfg(feature = "by_ref_proposal")]
            Content::Proposal(ref proposal) => self
                .process_proposal(&auth_content, proposal, cache_proposal)
                .await
                .map(Self::OutputType::from),
        }?;

        Ok(event)
    }

    #[cfg(feature = "private_message")]
    fn process_application_message(
        &self,
        data: ApplicationData,
        sender: Sender,
        authenticated_data: Vec<u8>,
    ) -> Result<ApplicationMessageDescription, MlsError> {
        let Sender::Member(sender_index) = sender else {
            return Err(MlsError::InvalidSender);
        };

        Ok(ApplicationMessageDescription {
            authenticated_data,
            sender_index,
            data,
        })
    }

    #[cfg(feature = "by_ref_proposal")]
    #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
    async fn process_proposal(
        &mut self,
        auth_content: &AuthenticatedContent,
        proposal: &Proposal,
        cache_proposal: bool,
    ) -> Result<ProposalMessageDescription, MlsError> {
        let proposal_ref =
            ProposalRef::from_content(self.cipher_suite_provider(), auth_content).await?;

        let group_state = self.group_state_mut();

        if cache_proposal {
            let proposal_ref = proposal_ref.clone();

            group_state.proposals.insert(
                proposal_ref.clone(),
                proposal.clone(),
                auth_content.content.sender,
            );
        }

        Ok(ProposalMessageDescription {
            authenticated_data: auth_content.content.authenticated_data.clone(),
            proposal: proposal.clone(),
            sender: auth_content.content.sender.try_into()?,
            proposal_ref,
        })
    }

    #[cfg(feature = "state_update")]
    async fn make_state_update(
        &self,
        provisional: &ProvisionalState,
        path: Option<&UpdatePath>,
        sender: LeafIndex,
    ) -> Result<StateUpdate, MlsError> {
        let added = provisional
            .applied_proposals
            .additions
            .iter()
            .zip(provisional.indexes_of_added_kpkgs.iter())
            .map(|(p, index)| member_from_key_package(&p.proposal.key_package, *index))
            .collect::<Vec<_>>();

        let mut added = added;

        let old_tree = &self.group_state().public_tree;

        let removed = provisional
            .applied_proposals
            .removals
            .iter()
            .map(|p| {
                let index = p.proposal.to_remove;
                let node = old_tree.nodes.borrow_as_leaf(index)?;
                Ok(member_from_leaf_node(node, index))
            })
            .collect::<Result<_, MlsError>>()?;

        #[cfg(feature = "by_ref_proposal")]
        let mut updated = provisional
            .applied_proposals
            .update_senders
            .iter()
            .map(|index| {
                let prior = old_tree
                    .get_leaf_node(*index)
                    .map(|n| member_from_leaf_node(n, *index))?;

                let new = provisional
                    .public_tree
                    .get_leaf_node(*index)
                    .map(|n| member_from_leaf_node(n, *index))?;

                Ok::<_, MlsError>(MemberUpdate::new(prior, new))
            })
            .collect::<Result<Vec<_>, _>>()?;

        #[cfg(not(feature = "by_ref_proposal"))]
        let mut updated = Vec::new();

        if let Some(path) = path {
            if !provisional
                .applied_proposals
                .external_initializations
                .is_empty()
            {
                added.push(member_from_leaf_node(&path.leaf_node, sender))
            } else {
                let prior = old_tree
                    .get_leaf_node(sender)
                    .map(|n| member_from_leaf_node(n, sender))?;

                let new = member_from_leaf_node(&path.leaf_node, sender);

                updated.push(MemberUpdate::new(prior, new))
            }
        }

        #[cfg(feature = "psk")]
        let psks = provisional
            .applied_proposals
            .psks
            .iter()
            .filter_map(|psk| psk.proposal.external_psk_id().cloned())
            .collect::<Vec<_>>();

        let roster_update = RosterUpdate::new(added, removed, updated);

        let update = StateUpdate {
            roster_update,
            #[cfg(feature = "psk")]
            added_psks: psks,
            pending_reinit: provisional
                .applied_proposals
                .reinitializations
                .first()
                .map(|ri| ri.proposal.new_cipher_suite()),
            active: true,
            epoch: provisional.group_context.epoch,
            #[cfg(feature = "custom_proposal")]
            custom_proposals: provisional.applied_proposals.custom_proposals.clone(),
            #[cfg(feature = "by_ref_proposal")]
            unused_proposals: provisional.unused_proposals.clone(),
        };

        Ok(update)
    }

    async fn process_commit(
        &mut self,
        auth_content: AuthenticatedContent,
        time_sent: Option<MlsTime>,
    ) -> Result<CommitMessageDescription, MlsError> {
        if self.group_state().pending_reinit.is_some() {
            return Err(MlsError::GroupUsedAfterReInit);
        }

        // Update the new GroupContext's confirmed and interim transcript hashes using the new Commit.
        let (interim_transcript_hash, confirmed_transcript_hash) = transcript_hashes(
            self.cipher_suite_provider(),
            &self.group_state().interim_transcript_hash,
            &auth_content,
        )
        .await?;

        #[cfg(any(feature = "private_message", feature = "by_ref_proposal"))]
        let commit = match auth_content.content.content {
            Content::Commit(commit) => Ok(commit),
            _ => Err(MlsError::UnexpectedMessageType),
        }?;

        #[cfg(not(any(feature = "private_message", feature = "by_ref_proposal")))]
        let Content::Commit(commit) = auth_content.content.content;

        let group_state = self.group_state();
        let id_provider = self.identity_provider();

        #[cfg(feature = "by_ref_proposal")]
        let proposals = group_state
            .proposals
            .resolve_for_commit(auth_content.content.sender, commit.proposals)?;

        #[cfg(not(feature = "by_ref_proposal"))]
        let proposals = resolve_for_commit(auth_content.content.sender, commit.proposals)?;

        let mut provisional_state = group_state
            .apply_resolved(
                auth_content.content.sender,
                proposals,
                commit.path.as_ref().map(|path| &path.leaf_node),
                &id_provider,
                self.cipher_suite_provider(),
                &self.psk_storage(),
                &self.mls_rules(),
                time_sent,
                CommitDirection::Receive,
            )
            .await?;

        let sender = commit_sender(&auth_content.content.sender, &provisional_state)?;

        #[cfg(feature = "state_update")]
        let mut state_update = self
            .make_state_update(&provisional_state, commit.path.as_ref(), sender)
            .await?;

        #[cfg(not(feature = "state_update"))]
        let state_update = StateUpdate {};

        //Verify that the path value is populated if the proposals vector contains any Update
        // or Remove proposals, or if it's empty. Otherwise, the path value MAY be omitted.
        if path_update_required(&provisional_state.applied_proposals) && commit.path.is_none() {
            return Err(MlsError::CommitMissingPath);
        }

        if !self.can_continue_processing(&provisional_state) {
            #[cfg(feature = "state_update")]
            {
                state_update.active = false;
            }

            return Ok(CommitMessageDescription {
                is_external: matches!(auth_content.content.sender, Sender::NewMemberCommit),
                authenticated_data: auth_content.content.authenticated_data,
                committer: *sender,
                state_update,
            });
        }

        let update_path = match commit.path {
            Some(update_path) => Some(
                validate_update_path(
                    &self.identity_provider(),
                    self.cipher_suite_provider(),
                    update_path,
                    &provisional_state,
                    sender,
                    time_sent,
                )
                .await?,
            ),
            None => None,
        };

        let new_secrets = match update_path {
            Some(update_path) => {
                self.apply_update_path(sender, &update_path, &mut provisional_state)
                    .await
            }
            None => Ok(None),
        }?;

        // Update the transcript hash to get the new context.
        provisional_state.group_context.confirmed_transcript_hash = confirmed_transcript_hash;

        // Update the parent hashes in the new context
        provisional_state
            .public_tree
            .update_hashes(&[sender], self.cipher_suite_provider())
            .await?;

        // Update the tree hash in the new context
        provisional_state.group_context.tree_hash = provisional_state
            .public_tree
            .tree_hash(self.cipher_suite_provider())
            .await?;

        if let Some(reinit) = provisional_state.applied_proposals.reinitializations.pop() {
            self.group_state_mut().pending_reinit = Some(reinit.proposal);

            #[cfg(feature = "state_update")]
            {
                state_update.active = false;
            }
        }

        if let Some(confirmation_tag) = &auth_content.auth.confirmation_tag {
            // Update the key schedule to calculate new private keys
            self.update_key_schedule(
                new_secrets,
                interim_transcript_hash,
                confirmation_tag,
                provisional_state,
            )
            .await?;

            Ok(CommitMessageDescription {
                is_external: matches!(auth_content.content.sender, Sender::NewMemberCommit),
                authenticated_data: auth_content.content.authenticated_data,
                committer: *sender,
                state_update,
            })
        } else {
            Err(MlsError::InvalidConfirmationTag)
        }
    }

    fn group_state(&self) -> &GroupState;
    fn group_state_mut(&mut self) -> &mut GroupState;
    fn mls_rules(&self) -> Self::MlsRules;
    fn identity_provider(&self) -> Self::IdentityProvider;
    fn cipher_suite_provider(&self) -> &Self::CipherSuiteProvider;
    fn psk_storage(&self) -> Self::PreSharedKeyStorage;
    fn can_continue_processing(&self, provisional_state: &ProvisionalState) -> bool;

    #[cfg(feature = "private_message")]
    fn min_epoch_available(&self) -> Option<u64>;

    fn check_metadata(&self, message: &MlsMessage) -> Result<(), MlsError> {
        let context = &self.group_state().context;

        if message.version != context.protocol_version {
            return Err(MlsError::ProtocolVersionMismatch);
        }

        if let Some((group_id, epoch, content_type)) = match &message.payload {
            MlsMessagePayload::Plain(plaintext) => Some((
                &plaintext.content.group_id,
                plaintext.content.epoch,
                plaintext.content.content_type(),
            )),
            #[cfg(feature = "private_message")]
            MlsMessagePayload::Cipher(ciphertext) => Some((
                &ciphertext.group_id,
                ciphertext.epoch,
                ciphertext.content_type,
            )),
            _ => None,
        } {
            if group_id != &context.group_id {
                return Err(MlsError::GroupIdMismatch);
            }

            match content_type {
                ContentType::Commit => {
                    if context.epoch != epoch {
                        Err(MlsError::InvalidEpoch)
                    } else {
                        Ok(())
                    }
                }
                #[cfg(feature = "by_ref_proposal")]
                ContentType::Proposal => {
                    if context.epoch != epoch {
                        Err(MlsError::InvalidEpoch)
                    } else {
                        Ok(())
                    }
                }
                #[cfg(feature = "private_message")]
                ContentType::Application => {
                    if let Some(min) = self.min_epoch_available() {
                        if epoch < min {
                            Err(MlsError::InvalidEpoch)
                        } else {
                            Ok(())
                        }
                    } else {
                        Ok(())
                    }
                }
            }?;

            // Proposal and commit messages must be sent in the current epoch
            let check_epoch = content_type == ContentType::Commit;

            #[cfg(feature = "by_ref_proposal")]
            let check_epoch = check_epoch || content_type == ContentType::Proposal;

            if check_epoch && epoch != context.epoch {
                return Err(MlsError::InvalidEpoch);
            }

            // Unencrypted application messages are not allowed
            #[cfg(feature = "private_message")]
            if !matches!(&message.payload, MlsMessagePayload::Cipher(_))
                && content_type == ContentType::Application
            {
                return Err(MlsError::UnencryptedApplicationMessage);
            }
        }

        Ok(())
    }

    fn validate_welcome(
        &self,
        welcome: &Welcome,
        version: ProtocolVersion,
    ) -> Result<(), MlsError> {
        let state = self.group_state();

        (welcome.cipher_suite == state.context.cipher_suite
            && version == state.context.protocol_version)
            .then_some(())
            .ok_or(MlsError::InvalidWelcomeMessage)
    }

    async fn validate_key_package(
        &self,
        key_package: &KeyPackage,
        version: ProtocolVersion,
    ) -> Result<(), MlsError> {
        let cs = self.cipher_suite_provider();
        let id = self.identity_provider();

        validate_key_package(key_package, version, cs, &id).await
    }

    #[cfg(feature = "private_message")]
    async fn process_ciphertext(
        &mut self,
        cipher_text: &PrivateMessage,
    ) -> Result<EventOrContent<Self::OutputType>, MlsError>;

    async fn verify_plaintext_authentication(
        &self,
        message: PublicMessage,
    ) -> Result<EventOrContent<Self::OutputType>, MlsError>;

    async fn apply_update_path(
        &mut self,
        sender: LeafIndex,
        update_path: &ValidatedUpdatePath,
        provisional_state: &mut ProvisionalState,
    ) -> Result<Option<(TreeKemPrivate, PathSecret)>, MlsError> {
        provisional_state
            .public_tree
            .apply_update_path(
                sender,
                update_path,
                &provisional_state.group_context.extensions,
                self.identity_provider(),
                self.cipher_suite_provider(),
            )
            .await
            .map(|_| None)
    }

    async fn update_key_schedule(
        &mut self,
        secrets: Option<(TreeKemPrivate, PathSecret)>,
        interim_transcript_hash: InterimTranscriptHash,
        confirmation_tag: &ConfirmationTag,
        provisional_public_state: ProvisionalState,
    ) -> Result<(), MlsError>;
}

#[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
pub(crate) async fn validate_key_package<C: CipherSuiteProvider, I: IdentityProvider>(
    key_package: &KeyPackage,
    version: ProtocolVersion,
    cs: &C,
    id: &I,
) -> Result<(), MlsError> {
    let validator = LeafNodeValidator::new(cs, id, None);

    #[cfg(feature = "std")]
    let context = Some(MlsTime::now());

    #[cfg(not(feature = "std"))]
    let context = None;

    let context = ValidationContext::Add(context);

    validator
        .check_if_valid(&key_package.leaf_node, context)
        .await?;

    validate_key_package_properties(key_package, version, cs).await?;

    Ok(())
}

[ Dauer der Verarbeitung: 0.34 Sekunden  (vorverarbeitet)  ]