pub(crate) mod bundle;
mod filtering_common;
#[cfg(feature = "by_ref_proposal")]
mod filtering;
#[cfg(not(feature = "by_ref_proposal"))]
pub mod filtering_lite;
#[cfg(all(feature = "custom_proposal", not(feature = "by_ref_proposal")))]
use filtering_lite as filtering;
pub use bundle::{ProposalBundle, ProposalInfo, ProposalSource};
pub(crate) use filtering_common::{prepare_proposals_for_mls_rules, ProposalApplier};
#[cfg(all(feature = "by_ref_proposal", test))]
pub(crate) use filtering::proposer_can_propose;
use crate::{group::proposal_filter::bundle::Proposable, MlsRules};
pub(crate) fn path_update_required<R: MlsRules>(
proposals: &ProposalBundle,
#[cfg(feature = "custom_proposal")] mls_rules: &R,
#[cfg(not(feature = "custom_proposal"))] _mls_rules: &R,
) -> bool {
#[cfg(feature = "custom_proposal")]
if proposals
.custom_proposals
.iter()
.any(|p| mls_rules.custom_proposal_requires_update_path(p.proposal.proposal_type()))
{
return true;
}
#[cfg(feature = "by_ref_proposal")]
if has_non_local_proposal(proposals.update_proposals()) {
return true;
}
#[cfg(all(
feature = "by_ref_proposal",
feature = "custom_proposal",
feature = "self_remove_proposal"
))]
if has_non_local_proposal(&proposals.self_removes) {
return true;
}
proposals.length() == 0
|| has_non_local_proposal(proposals.external_init_proposals())
|| has_non_local_proposal(&proposals.group_context_extensions)
|| has_non_local_proposal(proposals.remove_proposals())
}
fn has_non_local_proposal<T: Proposable>(proposals: &[ProposalInfo<T>]) -> bool {
proposals.iter().any(|p| p.source != ProposalSource::Local)
}
#[cfg(test)]
mod tests {
use alloc::vec;
use super::*;
use crate::group::{
framing::Sender,
mls_rules::DefaultMlsRules,
proposal::{ExternalInit, ReInitProposal, RemoveProposal},
proposal_filter::bundle::ProposalSource,
};
use crate::tree_kem::node::LeafIndex;
use mls_rs_core::group::ProposalType;
#[cfg(feature = "custom_proposal")]
use crate::group::proposal::CustomProposal;
fn member_sender() -> Sender {
Sender::Member(0)
}
fn dummy_leaf_node() -> crate::tree_kem::leaf_node::LeafNode {
use crate::tree_kem::leaf_node::{LeafNode, LeafNodeSource};
use mls_rs_core::identity::{BasicCredential, Credential, SigningIdentity};
LeafNode {
public_key: vec![].into(),
signing_identity: SigningIdentity::new(
Credential::Basic(BasicCredential::new(b"test".to_vec())),
vec![].into(),
),
capabilities: Default::default(),
leaf_node_source: LeafNodeSource::Update,
extensions: Default::default(),
signature: vec![],
}
}
#[test]
fn empty_bundle_requires_path() {
let bundle = ProposalBundle::default();
assert!(path_update_required(&bundle, &DefaultMlsRules::new()));
}
#[test]
fn remove_requires_path() {
let mut bundle = ProposalBundle::default();
bundle.removals.push(ProposalInfo {
proposal: RemoveProposal {
to_remove: LeafIndex::unchecked(1),
},
sender: member_sender(),
source: ProposalSource::ByValue,
});
assert!(path_update_required(&bundle, &DefaultMlsRules::new()));
}
#[test]
fn reinit_only_does_not_require_path() {
let mut bundle = ProposalBundle::default();
bundle.reinitializations.push(ProposalInfo {
proposal: ReInitProposal {
group_id: vec![],
version: crate::ProtocolVersion::MLS_10,
cipher_suite: crate::CipherSuite::CURVE25519_AES128,
extensions: Default::default(),
},
sender: member_sender(),
source: ProposalSource::ByValue,
});
assert!(!path_update_required(&bundle, &DefaultMlsRules::new()));
}
#[test]
fn external_init_requires_path() {
let mut bundle = ProposalBundle::default();
bundle.external_initializations.push(ProposalInfo {
proposal: ExternalInit { kem_output: vec![] },
sender: member_sender(),
source: ProposalSource::ByValue,
});
assert!(path_update_required(&bundle, &DefaultMlsRules::new()));
}
#[test]
fn group_context_extensions_requires_path() {
let mut bundle = ProposalBundle::default();
bundle.group_context_extensions.push(ProposalInfo {
proposal: Default::default(),
sender: member_sender(),
source: ProposalSource::ByValue,
});
assert!(path_update_required(&bundle, &DefaultMlsRules::new()));
}
#[test]
fn local_remove_does_not_require_path_by_itself() {
let mut bundle = ProposalBundle::default();
bundle.removals.push(ProposalInfo {
proposal: RemoveProposal {
to_remove: LeafIndex::unchecked(1),
},
sender: member_sender(),
source: ProposalSource::Local,
});
assert!(!path_update_required(&bundle, &DefaultMlsRules::new()));
}
#[cfg(feature = "by_ref_proposal")]
#[test]
fn update_requires_path() {
use crate::group::proposal::UpdateProposal;
let mut bundle = ProposalBundle::default();
bundle.updates.push(ProposalInfo {
proposal: UpdateProposal {
leaf_node: dummy_leaf_node(),
},
sender: member_sender(),
source: ProposalSource::ByValue,
});
bundle.update_senders.push(LeafIndex::unchecked(0));
assert!(path_update_required(&bundle, &DefaultMlsRules::new()));
}
#[cfg(all(
feature = "by_ref_proposal",
feature = "custom_proposal",
feature = "self_remove_proposal"
))]
#[test]
fn self_remove_requires_path() {
use crate::group::proposal::SelfRemoveProposal;
let mut bundle = ProposalBundle::default();
bundle.self_removes.push(ProposalInfo {
proposal: SelfRemoveProposal {},
sender: member_sender(),
source: ProposalSource::ByValue,
});
assert!(path_update_required(&bundle, &DefaultMlsRules::new()));
}
#[cfg(feature = "custom_proposal")]
#[test]
fn custom_proposal_requires_path_when_rules_say_so() {
let custom_type = ProposalType::from(42);
let rules = DefaultMlsRules::new()
.with_custom_proposals_that_require_update_path(vec![custom_type]);
let mut bundle = ProposalBundle::default();
bundle.custom_proposals.push(ProposalInfo {
proposal: CustomProposal::new(custom_type, vec![]),
sender: member_sender(),
source: ProposalSource::ByValue,
});
assert!(path_update_required(&bundle, &rules));
}
#[cfg(feature = "custom_proposal")]
#[test]
fn custom_proposal_does_not_require_path_when_rules_say_no() {
let custom_type = ProposalType::from(42);
let rules = DefaultMlsRules::new();
let mut bundle = ProposalBundle::default();
bundle.custom_proposals.push(ProposalInfo {
proposal: CustomProposal::new(custom_type, vec![]),
sender: member_sender(),
source: ProposalSource::ByValue,
});
bundle.removals.push(ProposalInfo {
proposal: RemoveProposal {
to_remove: LeafIndex::unchecked(1),
},
sender: member_sender(),
source: ProposalSource::Local,
});
assert!(!path_update_required(&bundle, &rules));
}
}