use std::collections::HashSet;
use crate::{
binary_tree::LeafNodeIndex,
error::LibraryError,
extensions::Extensions,
framing::Sender,
group::proposal_store::ProposalQueue,
messages::proposals::{AddProposal, ExternalInitProposal, Proposal, ProposalType},
schedule::psk::PreSharedKeyId,
};
use super::*;
#[cfg(feature = "extensions-draft-08")]
use crate::{
extensions::{AppDataDictionaryExtension, Extension},
prelude::processing::AppDataUpdates,
};
#[derive(Debug)]
pub(crate) struct ApplyProposalsValues {
pub(crate) path_required: bool,
pub(crate) self_removed: bool,
pub(crate) invitation_list: Vec<(LeafNodeIndex, AddProposal)>,
pub(crate) presharedkeys: Vec<PreSharedKeyId>,
pub(crate) external_init_proposal_option: Option<ExternalInitProposal>,
pub(crate) extensions: Option<Extensions<GroupContext>>,
}
impl ApplyProposalsValues {
pub(crate) fn exclusion_list(&self) -> HashSet<&LeafNodeIndex> {
let new_leaves_indexes: HashSet<&LeafNodeIndex> = self
.invitation_list
.iter()
.map(|(index, _)| index)
.collect();
new_leaves_indexes
}
}
impl PublicGroupDiff<'_> {
pub(crate) fn apply_proposals(
&mut self,
proposal_queue: &ProposalQueue,
own_leaf_index: impl Into<Option<LeafNodeIndex>>,
) -> Result<ApplyProposalsValues, LibraryError> {
log::debug!("Applying proposal");
let mut self_removed = false;
let external_init_proposal_option = proposal_queue
.filtered_by_type(ProposalType::ExternalInit)
.next()
.and_then(|queued_proposal| {
if let Proposal::ExternalInit(external_init_proposal) = queued_proposal.proposal() {
Some(*(*external_init_proposal).clone())
} else {
None
}
});
for queued_proposal in proposal_queue.filtered_by_type(ProposalType::Update) {
if let Proposal::Update(update_proposal) = queued_proposal.proposal() {
let sender = queued_proposal.sender();
let sender_index = match sender {
Sender::Member(sender_index) => *sender_index,
_ => return Err(LibraryError::custom("Update proposal from non-member")),
};
let leaf_node = update_proposal.leaf_node().clone();
self.diff.update_leaf(leaf_node, sender_index);
}
}
let own_leaf_index = own_leaf_index.into();
for queued_proposal in proposal_queue.filtered_by_type(ProposalType::Remove) {
if let Proposal::Remove(remove_proposal) = queued_proposal.proposal() {
match own_leaf_index {
Some(leaf_index) if remove_proposal.removed() == leaf_index => {
self_removed = true
}
_ => (),
};
self.diff.blank_leaf(remove_proposal.removed());
}
}
for queued_proposal in proposal_queue.filtered_by_type(ProposalType::SelfRemove) {
if let Proposal::SelfRemove = queued_proposal.proposal() {
let Sender::Member(removed) = queued_proposal.sender() else {
return Err(LibraryError::custom("SelfRemove proposal from non-member"));
};
if Some(*removed) == own_leaf_index {
self_removed = true;
}
self.diff.blank_leaf(*removed);
}
}
let add_proposals = proposal_queue
.filtered_by_type(ProposalType::Add)
.filter_map(|queued_proposal| {
if let Proposal::Add(add_proposal) = queued_proposal.proposal() {
Some(add_proposal)
} else {
None
}
});
let mut invitation_list = Vec::new();
for add_proposal in add_proposals {
let leaf_node = add_proposal.key_package.leaf_node();
let leaf_index = self
.diff
.add_leaf(leaf_node.clone())
.map_err(|_| LibraryError::custom("Tree full: cannot add more members"))?;
invitation_list.push((leaf_index, *(*add_proposal).clone()))
}
self.diff.trim_tree();
let presharedkeys: Vec<PreSharedKeyId> = proposal_queue
.filtered_by_type(ProposalType::PreSharedKey)
.filter_map(|queued_proposal| {
if let Proposal::PreSharedKey(psk_proposal) = queued_proposal.proposal() {
Some(psk_proposal.clone().into_psk_id())
} else {
None
}
})
.collect();
let updated_group_context_extensions = proposal_queue
.filtered_by_type(ProposalType::GroupContextExtensions)
.find_map(|queued_proposal| match queued_proposal.proposal() {
Proposal::GroupContextExtensions(extensions) => {
Some(extensions.extensions().clone())
}
_ => None,
});
let proposals_require_path = proposal_queue
.queued_proposals()
.any(|p| p.proposal().is_path_required());
let path_required = proposals_require_path || proposal_queue.is_empty();
Ok(ApplyProposalsValues {
path_required,
self_removed,
invitation_list,
presharedkeys,
external_init_proposal_option,
extensions: updated_group_context_extensions,
})
}
#[cfg(feature = "extensions-draft-08")]
pub(crate) fn apply_proposals_with_app_data_updates(
&mut self,
proposal_queue: &ProposalQueue,
own_leaf_index: impl Into<Option<LeafNodeIndex>>,
app_data_dict_updates: Option<AppDataUpdates>,
) -> Result<ApplyProposalsValues, ApplyAppDataUpdateError> {
let ApplyProposalsValues {
path_required,
self_removed,
invitation_list,
presharedkeys,
external_init_proposal_option,
extensions,
} = self.apply_proposals(proposal_queue, own_leaf_index)?;
#[cfg(feature = "extensions-draft-08")]
let mut extensions = extensions;
#[cfg(feature = "extensions-draft-08")]
self.apply_app_data_update_proposals(
&mut extensions,
proposal_queue,
app_data_dict_updates,
)?;
Ok(ApplyProposalsValues {
path_required,
self_removed,
invitation_list,
presharedkeys,
external_init_proposal_option,
extensions,
})
}
#[cfg(feature = "extensions-draft-08")]
pub(crate) fn apply_app_data_update_proposals(
&self,
updated_group_context_extensions: &mut Option<Extensions<GroupContext>>,
proposal_queue: &ProposalQueue,
app_data_dict_updates: Option<AppDataUpdates>,
) -> Result<(), ApplyAppDataUpdateError> {
let has_app_data_update_proposals = proposal_queue
.queued_proposals()
.any(|proposal| proposal.proposal().is_type(ProposalType::AppDataUpdate));
let updates = match (has_app_data_update_proposals, app_data_dict_updates) {
(true, Some(updates)) => updates,
(false, None) => return Ok(()),
(true, None) => {
return Err(ApplyAppDataUpdateError::MissingAppDataUpdates);
}
(false, Some(_)) => {
return Err(ApplyAppDataUpdateError::SuperfluousAppDataUpdates);
}
};
let mut dictionary = self
.group_context
.app_data_dict()
.cloned()
.unwrap_or_default();
for (id, data) in updates.into_iter() {
if let Some(data) = data {
let _ = dictionary.insert(id, data);
} else {
let _ = dictionary.remove(&id);
}
}
let extensions_to_update = updated_group_context_extensions
.get_or_insert_with(|| self.group_context.extensions().clone());
extensions_to_update.add_or_replace(Extension::AppDataDictionary(
AppDataDictionaryExtension::new(dictionary),
));
Ok(())
}
}