1 // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2 // Copyright by contributors to this project.
3 // SPDX-License-Identifier: (Apache-2.0 OR MIT)
4
5 use alloc::vec::Vec;
6
7 use super::{
8 message_processor::ProvisionalState,
9 mls_rules::{CommitDirection, CommitSource, MlsRules},
10 GroupState, ProposalOrRef,
11 };
12 use crate::{
13 client::MlsError,
14 group::{
15 proposal_filter::{ProposalApplier, ProposalBundle, ProposalSource},
16 Proposal, Sender,
17 },
18 time::MlsTime,
19 };
20
21 #[cfg(feature = "by_ref_proposal")]
22 use crate::group::{proposal_filter::FilterStrategy, ProposalRef, ProtocolVersion};
23
24 use crate::tree_kem::leaf_node::LeafNode;
25
26 #[cfg(all(feature = "std", feature = "by_ref_proposal"))]
27 use std::collections::HashMap;
28
29 #[cfg(feature = "by_ref_proposal")]
30 use mls_rs_codec::{MlsDecode, MlsEncode, MlsSize};
31
32 use mls_rs_core::{
33 crypto::CipherSuiteProvider, error::IntoAnyError, identity::IdentityProvider,
34 psk::PreSharedKeyStorage,
35 };
36
37 #[cfg(feature = "by_ref_proposal")]
38 use core::fmt::{self, Debug};
39
40 #[cfg(feature = "by_ref_proposal")]
41 #[derive(Debug, Clone, MlsSize, MlsEncode, MlsDecode, PartialEq)]
42 #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
43 pub struct CachedProposal {
44 pub(crate) proposal: Proposal,
45 pub(crate) sender: Sender,
46 }
47
48 #[cfg(feature = "by_ref_proposal")]
49 #[derive(Clone, PartialEq)]
50 pub(crate) struct ProposalCache {
51 protocol_version: ProtocolVersion,
52 group_id: Vec<u8>,
53 #[cfg(feature = "std")]
54 pub(crate) proposals: HashMap<ProposalRef, CachedProposal>,
55 #[cfg(not(feature = "std"))]
56 pub(crate) proposals: Vec<(ProposalRef, CachedProposal)>,
57 }
58
59 #[cfg(feature = "by_ref_proposal")]
60 impl Debug for ProposalCache {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result61 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
62 f.debug_struct("ProposalCache")
63 .field("protocol_version", &self.protocol_version)
64 .field(
65 "group_id",
66 &mls_rs_core::debug::pretty_group_id(&self.group_id),
67 )
68 .field("proposals", &self.proposals)
69 .finish()
70 }
71 }
72
73 #[cfg(feature = "by_ref_proposal")]
74 impl ProposalCache {
new(protocol_version: ProtocolVersion, group_id: Vec<u8>) -> Self75 pub fn new(protocol_version: ProtocolVersion, group_id: Vec<u8>) -> Self {
76 Self {
77 protocol_version,
78 group_id,
79 proposals: Default::default(),
80 }
81 }
82
import( protocol_version: ProtocolVersion, group_id: Vec<u8>, #[cfg(feature = "std")] proposals: HashMap<ProposalRef, CachedProposal>, #[cfg(not(feature = "std"))] proposals: Vec<(ProposalRef, CachedProposal)>, ) -> Self83 pub fn import(
84 protocol_version: ProtocolVersion,
85 group_id: Vec<u8>,
86 #[cfg(feature = "std")] proposals: HashMap<ProposalRef, CachedProposal>,
87 #[cfg(not(feature = "std"))] proposals: Vec<(ProposalRef, CachedProposal)>,
88 ) -> Self {
89 Self {
90 protocol_version,
91 group_id,
92 proposals,
93 }
94 }
95
96 #[inline]
clear(&mut self)97 pub fn clear(&mut self) {
98 self.proposals.clear();
99 }
100
101 #[cfg(feature = "private_message")]
102 #[inline]
is_empty(&self) -> bool103 pub fn is_empty(&self) -> bool {
104 self.proposals.is_empty()
105 }
106
insert(&mut self, proposal_ref: ProposalRef, proposal: Proposal, sender: Sender)107 pub fn insert(&mut self, proposal_ref: ProposalRef, proposal: Proposal, sender: Sender) {
108 let cached_proposal = CachedProposal { proposal, sender };
109
110 #[cfg(feature = "std")]
111 self.proposals.insert(proposal_ref, cached_proposal);
112
113 #[cfg(not(feature = "std"))]
114 // This may result in dups but it does not matter
115 self.proposals.push((proposal_ref, cached_proposal));
116 }
117
prepare_commit( &self, sender: Sender, additional_proposals: Vec<Proposal>, ) -> ProposalBundle118 pub fn prepare_commit(
119 &self,
120 sender: Sender,
121 additional_proposals: Vec<Proposal>,
122 ) -> ProposalBundle {
123 self.proposals
124 .iter()
125 .map(|(r, p)| {
126 (
127 p.proposal.clone(),
128 p.sender,
129 ProposalSource::ByReference(r.clone()),
130 )
131 })
132 .chain(
133 additional_proposals
134 .into_iter()
135 .map(|p| (p, sender, ProposalSource::ByValue)),
136 )
137 .collect()
138 }
139
resolve_for_commit( &self, sender: Sender, proposal_list: Vec<ProposalOrRef>, ) -> Result<ProposalBundle, MlsError>140 pub fn resolve_for_commit(
141 &self,
142 sender: Sender,
143 proposal_list: Vec<ProposalOrRef>,
144 ) -> Result<ProposalBundle, MlsError> {
145 let mut proposals = ProposalBundle::default();
146
147 for p in proposal_list {
148 match p {
149 ProposalOrRef::Proposal(p) => proposals.add(*p, sender, ProposalSource::ByValue),
150 ProposalOrRef::Reference(r) => {
151 #[cfg(feature = "std")]
152 let p = self
153 .proposals
154 .get(&r)
155 .ok_or(MlsError::ProposalNotFound)?
156 .clone();
157 #[cfg(not(feature = "std"))]
158 let p = self
159 .proposals
160 .iter()
161 .find_map(|(rr, p)| (rr == &r).then_some(p))
162 .ok_or(MlsError::ProposalNotFound)?
163 .clone();
164
165 proposals.add(p.proposal, p.sender, ProposalSource::ByReference(r));
166 }
167 };
168 }
169
170 Ok(proposals)
171 }
172 }
173
174 #[cfg(not(feature = "by_ref_proposal"))]
prepare_commit( sender: Sender, additional_proposals: Vec<Proposal>, ) -> ProposalBundle175 pub(crate) fn prepare_commit(
176 sender: Sender,
177 additional_proposals: Vec<Proposal>,
178 ) -> ProposalBundle {
179 let mut proposals = ProposalBundle::default();
180
181 for p in additional_proposals.into_iter() {
182 proposals.add(p, sender, ProposalSource::ByValue);
183 }
184
185 proposals
186 }
187
188 #[cfg(not(feature = "by_ref_proposal"))]
resolve_for_commit( sender: Sender, proposal_list: Vec<ProposalOrRef>, ) -> Result<ProposalBundle, MlsError>189 pub(crate) fn resolve_for_commit(
190 sender: Sender,
191 proposal_list: Vec<ProposalOrRef>,
192 ) -> Result<ProposalBundle, MlsError> {
193 let mut proposals = ProposalBundle::default();
194
195 for p in proposal_list {
196 let ProposalOrRef::Proposal(p) = p;
197 proposals.add(*p, sender, ProposalSource::ByValue);
198 }
199
200 Ok(proposals)
201 }
202
203 impl GroupState {
204 #[inline(never)]
205 #[allow(clippy::too_many_arguments)]
206 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
apply_resolved<C, F, P, CSP>( &self, sender: Sender, mut proposals: ProposalBundle, external_leaf: Option<&LeafNode>, identity_provider: &C, cipher_suite_provider: &CSP, psk_storage: &P, user_rules: &F, commit_time: Option<MlsTime>, direction: CommitDirection, ) -> Result<ProvisionalState, MlsError> where C: IdentityProvider, F: MlsRules, P: PreSharedKeyStorage, CSP: CipherSuiteProvider,207 pub(crate) async fn apply_resolved<C, F, P, CSP>(
208 &self,
209 sender: Sender,
210 mut proposals: ProposalBundle,
211 external_leaf: Option<&LeafNode>,
212 identity_provider: &C,
213 cipher_suite_provider: &CSP,
214 psk_storage: &P,
215 user_rules: &F,
216 commit_time: Option<MlsTime>,
217 direction: CommitDirection,
218 ) -> Result<ProvisionalState, MlsError>
219 where
220 C: IdentityProvider,
221 F: MlsRules,
222 P: PreSharedKeyStorage,
223 CSP: CipherSuiteProvider,
224 {
225 let roster = self.public_tree.roster();
226 let group_extensions = &self.context.extensions;
227
228 #[cfg(feature = "by_ref_proposal")]
229 let all_proposals = proposals.clone();
230
231 let origin = match sender {
232 Sender::Member(index) => Ok::<_, MlsError>(CommitSource::ExistingMember(
233 roster.member_with_index(index)?,
234 )),
235 #[cfg(feature = "by_ref_proposal")]
236 Sender::NewMemberProposal => Err(MlsError::InvalidSender),
237 #[cfg(feature = "by_ref_proposal")]
238 Sender::External(_) => Err(MlsError::InvalidSender),
239 Sender::NewMemberCommit => Ok(CommitSource::NewMember(
240 external_leaf
241 .map(|l| l.signing_identity.clone())
242 .ok_or(MlsError::ExternalCommitMustHaveNewLeaf)?,
243 )),
244 }?;
245
246 proposals = user_rules
247 .filter_proposals(direction, origin, &roster, group_extensions, proposals)
248 .await
249 .map_err(|e| MlsError::MlsRulesError(e.into_any_error()))?;
250
251 let applier = ProposalApplier::new(
252 &self.public_tree,
253 self.context.protocol_version,
254 cipher_suite_provider,
255 group_extensions,
256 external_leaf,
257 identity_provider,
258 psk_storage,
259 #[cfg(feature = "by_ref_proposal")]
260 &self.context.group_id,
261 );
262
263 #[cfg(feature = "by_ref_proposal")]
264 let applier_output = match direction {
265 CommitDirection::Send => {
266 applier
267 .apply_proposals(FilterStrategy::IgnoreByRef, &sender, proposals, commit_time)
268 .await?
269 }
270 CommitDirection::Receive => {
271 applier
272 .apply_proposals(FilterStrategy::IgnoreNone, &sender, proposals, commit_time)
273 .await?
274 }
275 };
276
277 #[cfg(not(feature = "by_ref_proposal"))]
278 let applier_output = applier
279 .apply_proposals(&sender, &proposals, commit_time)
280 .await?;
281
282 #[cfg(feature = "by_ref_proposal")]
283 let unused_proposals = unused_proposals(
284 match direction {
285 CommitDirection::Send => all_proposals,
286 CommitDirection::Receive => self.proposals.proposals.iter().collect(),
287 },
288 &applier_output.applied_proposals,
289 );
290
291 let mut group_context = self.context.clone();
292 group_context.epoch += 1;
293
294 if let Some(ext) = applier_output.new_context_extensions {
295 group_context.extensions = ext;
296 }
297
298 #[cfg(feature = "by_ref_proposal")]
299 let proposals = applier_output.applied_proposals;
300
301 Ok(ProvisionalState {
302 public_tree: applier_output.new_tree,
303 group_context,
304 applied_proposals: proposals,
305 external_init_index: applier_output.external_init_index,
306 indexes_of_added_kpkgs: applier_output.indexes_of_added_kpkgs,
307 #[cfg(feature = "by_ref_proposal")]
308 unused_proposals,
309 })
310 }
311 }
312
313 #[cfg(feature = "by_ref_proposal")]
314 impl Extend<(ProposalRef, CachedProposal)> for ProposalCache {
extend<T>(&mut self, iter: T) where T: IntoIterator<Item = (ProposalRef, CachedProposal)>,315 fn extend<T>(&mut self, iter: T)
316 where
317 T: IntoIterator<Item = (ProposalRef, CachedProposal)>,
318 {
319 self.proposals.extend(iter);
320 }
321 }
322
323 #[cfg(feature = "by_ref_proposal")]
has_ref(proposals: &ProposalBundle, reference: &ProposalRef) -> bool324 fn has_ref(proposals: &ProposalBundle, reference: &ProposalRef) -> bool {
325 proposals
326 .iter_proposals()
327 .any(|p| matches!(&p.source, ProposalSource::ByReference(r) if r == reference))
328 }
329
330 #[cfg(feature = "by_ref_proposal")]
unused_proposals( all_proposals: ProposalBundle, accepted_proposals: &ProposalBundle, ) -> Vec<crate::mls_rules::ProposalInfo<Proposal>>331 fn unused_proposals(
332 all_proposals: ProposalBundle,
333 accepted_proposals: &ProposalBundle,
334 ) -> Vec<crate::mls_rules::ProposalInfo<Proposal>> {
335 all_proposals
336 .into_proposals()
337 .filter(|p| {
338 matches!(p.source, ProposalSource::ByReference(ref r) if !has_ref(accepted_proposals, r)
339 )
340 })
341 .collect()
342 }
343
344 // TODO add tests for lite version of filtering
345 #[cfg(all(feature = "by_ref_proposal", test))]
346 pub(crate) mod test_utils {
347 use mls_rs_core::{
348 crypto::CipherSuiteProvider, extension::ExtensionList, identity::IdentityProvider,
349 psk::PreSharedKeyStorage,
350 };
351
352 use crate::{
353 client::test_utils::TEST_PROTOCOL_VERSION,
354 group::{
355 confirmation_tag::ConfirmationTag,
356 mls_rules::{CommitDirection, DefaultMlsRules, MlsRules},
357 proposal::{Proposal, ProposalOrRef},
358 proposal_ref::ProposalRef,
359 state::GroupState,
360 test_utils::{get_test_group_context, TEST_GROUP},
361 GroupContext, LeafIndex, LeafNode, ProvisionalState, Sender, TreeKemPublic,
362 },
363 identity::{basic::BasicIdentityProvider, test_utils::BasicWithCustomProvider},
364 psk::AlwaysFoundPskStorage,
365 };
366
367 use super::{CachedProposal, MlsError, ProposalCache};
368
369 use alloc::vec::Vec;
370
371 impl CachedProposal {
new(proposal: Proposal, sender: Sender) -> Self372 pub fn new(proposal: Proposal, sender: Sender) -> Self {
373 Self { proposal, sender }
374 }
375 }
376
377 #[derive(Debug)]
378 pub(crate) struct CommitReceiver<'a, C, F, P, CSP> {
379 tree: &'a TreeKemPublic,
380 sender: Sender,
381 receiver: LeafIndex,
382 cache: ProposalCache,
383 identity_provider: C,
384 cipher_suite_provider: CSP,
385 group_context_extensions: ExtensionList,
386 user_rules: F,
387 with_psk_storage: P,
388 }
389
390 impl<'a, CSP>
391 CommitReceiver<'a, BasicWithCustomProvider, DefaultMlsRules, AlwaysFoundPskStorage, CSP>
392 {
new<S>( tree: &'a TreeKemPublic, sender: S, receiver: LeafIndex, cipher_suite_provider: CSP, ) -> Self where S: Into<Sender>,393 pub fn new<S>(
394 tree: &'a TreeKemPublic,
395 sender: S,
396 receiver: LeafIndex,
397 cipher_suite_provider: CSP,
398 ) -> Self
399 where
400 S: Into<Sender>,
401 {
402 Self {
403 tree,
404 sender: sender.into(),
405 receiver,
406 cache: make_proposal_cache(),
407 identity_provider: BasicWithCustomProvider::new(BasicIdentityProvider),
408 group_context_extensions: Default::default(),
409 user_rules: pass_through_rules(),
410 with_psk_storage: AlwaysFoundPskStorage,
411 cipher_suite_provider,
412 }
413 }
414 }
415
416 impl<'a, C, F, P, CSP> CommitReceiver<'a, C, F, P, CSP>
417 where
418 C: IdentityProvider,
419 F: MlsRules,
420 P: PreSharedKeyStorage,
421 CSP: CipherSuiteProvider,
422 {
423 #[cfg(feature = "by_ref_proposal")]
with_identity_provider<V>(self, validator: V) -> CommitReceiver<'a, V, F, P, CSP> where V: IdentityProvider,424 pub fn with_identity_provider<V>(self, validator: V) -> CommitReceiver<'a, V, F, P, CSP>
425 where
426 V: IdentityProvider,
427 {
428 CommitReceiver {
429 tree: self.tree,
430 sender: self.sender,
431 receiver: self.receiver,
432 cache: self.cache,
433 identity_provider: validator,
434 group_context_extensions: self.group_context_extensions,
435 user_rules: self.user_rules,
436 with_psk_storage: self.with_psk_storage,
437 cipher_suite_provider: self.cipher_suite_provider,
438 }
439 }
440
with_user_rules<G>(self, f: G) -> CommitReceiver<'a, C, G, P, CSP> where G: MlsRules,441 pub fn with_user_rules<G>(self, f: G) -> CommitReceiver<'a, C, G, P, CSP>
442 where
443 G: MlsRules,
444 {
445 CommitReceiver {
446 tree: self.tree,
447 sender: self.sender,
448 receiver: self.receiver,
449 cache: self.cache,
450 identity_provider: self.identity_provider,
451 group_context_extensions: self.group_context_extensions,
452 user_rules: f,
453 with_psk_storage: self.with_psk_storage,
454 cipher_suite_provider: self.cipher_suite_provider,
455 }
456 }
457
with_psk_storage<V>(self, v: V) -> CommitReceiver<'a, C, F, V, CSP> where V: PreSharedKeyStorage,458 pub fn with_psk_storage<V>(self, v: V) -> CommitReceiver<'a, C, F, V, CSP>
459 where
460 V: PreSharedKeyStorage,
461 {
462 CommitReceiver {
463 tree: self.tree,
464 sender: self.sender,
465 receiver: self.receiver,
466 cache: self.cache,
467 identity_provider: self.identity_provider,
468 group_context_extensions: self.group_context_extensions,
469 user_rules: self.user_rules,
470 with_psk_storage: v,
471 cipher_suite_provider: self.cipher_suite_provider,
472 }
473 }
474
475 #[cfg(feature = "by_ref_proposal")]
with_extensions(self, extensions: ExtensionList) -> Self476 pub fn with_extensions(self, extensions: ExtensionList) -> Self {
477 Self {
478 group_context_extensions: extensions,
479 ..self
480 }
481 }
482
cache<S>(mut self, r: ProposalRef, p: Proposal, proposer: S) -> Self where S: Into<Sender>,483 pub fn cache<S>(mut self, r: ProposalRef, p: Proposal, proposer: S) -> Self
484 where
485 S: Into<Sender>,
486 {
487 self.cache.insert(r, p, proposer.into());
488 self
489 }
490
491 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
receive<I>(&self, proposals: I) -> Result<ProvisionalState, MlsError> where I: IntoIterator, I::Item: Into<ProposalOrRef>,492 pub async fn receive<I>(&self, proposals: I) -> Result<ProvisionalState, MlsError>
493 where
494 I: IntoIterator,
495 I::Item: Into<ProposalOrRef>,
496 {
497 self.cache
498 .resolve_for_commit_default(
499 self.sender,
500 proposals.into_iter().map(Into::into).collect(),
501 None,
502 &self.group_context_extensions,
503 &self.identity_provider,
504 &self.cipher_suite_provider,
505 self.tree,
506 &self.with_psk_storage,
507 &self.user_rules,
508 )
509 .await
510 }
511 }
512
make_proposal_cache() -> ProposalCache513 pub(crate) fn make_proposal_cache() -> ProposalCache {
514 ProposalCache::new(TEST_PROTOCOL_VERSION, TEST_GROUP.to_vec())
515 }
516
pass_through_rules() -> DefaultMlsRules517 pub fn pass_through_rules() -> DefaultMlsRules {
518 DefaultMlsRules::new()
519 }
520
521 impl ProposalCache {
522 #[allow(clippy::too_many_arguments)]
523 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
resolve_for_commit_default<C, F, P, CSP>( &self, sender: Sender, proposal_list: Vec<ProposalOrRef>, external_leaf: Option<&LeafNode>, group_extensions: &ExtensionList, identity_provider: &C, cipher_suite_provider: &CSP, public_tree: &TreeKemPublic, psk_storage: &P, user_rules: F, ) -> Result<ProvisionalState, MlsError> where C: IdentityProvider, F: MlsRules, P: PreSharedKeyStorage, CSP: CipherSuiteProvider,524 pub async fn resolve_for_commit_default<C, F, P, CSP>(
525 &self,
526 sender: Sender,
527 proposal_list: Vec<ProposalOrRef>,
528 external_leaf: Option<&LeafNode>,
529 group_extensions: &ExtensionList,
530 identity_provider: &C,
531 cipher_suite_provider: &CSP,
532 public_tree: &TreeKemPublic,
533 psk_storage: &P,
534 user_rules: F,
535 ) -> Result<ProvisionalState, MlsError>
536 where
537 C: IdentityProvider,
538 F: MlsRules,
539 P: PreSharedKeyStorage,
540 CSP: CipherSuiteProvider,
541 {
542 let mut context =
543 get_test_group_context(123, cipher_suite_provider.cipher_suite()).await;
544
545 context.extensions = group_extensions.clone();
546
547 let mut state = GroupState::new(
548 context,
549 public_tree.clone(),
550 Vec::new().into(),
551 ConfirmationTag::empty(cipher_suite_provider).await,
552 );
553
554 state.proposals.proposals = self.proposals.clone();
555 let proposals = self.resolve_for_commit(sender, proposal_list)?;
556
557 state
558 .apply_resolved(
559 sender,
560 proposals,
561 external_leaf,
562 identity_provider,
563 cipher_suite_provider,
564 psk_storage,
565 &user_rules,
566 None,
567 CommitDirection::Receive,
568 )
569 .await
570 }
571
572 #[allow(clippy::too_many_arguments)]
573 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
prepare_commit_default<C, F, P, CSP>( &self, sender: Sender, additional_proposals: Vec<Proposal>, context: &GroupContext, identity_provider: &C, cipher_suite_provider: &CSP, public_tree: &TreeKemPublic, external_leaf: Option<&LeafNode>, psk_storage: &P, user_rules: F, ) -> Result<ProvisionalState, MlsError> where C: IdentityProvider, F: MlsRules, P: PreSharedKeyStorage, CSP: CipherSuiteProvider,574 pub async fn prepare_commit_default<C, F, P, CSP>(
575 &self,
576 sender: Sender,
577 additional_proposals: Vec<Proposal>,
578 context: &GroupContext,
579 identity_provider: &C,
580 cipher_suite_provider: &CSP,
581 public_tree: &TreeKemPublic,
582 external_leaf: Option<&LeafNode>,
583 psk_storage: &P,
584 user_rules: F,
585 ) -> Result<ProvisionalState, MlsError>
586 where
587 C: IdentityProvider,
588 F: MlsRules,
589 P: PreSharedKeyStorage,
590 CSP: CipherSuiteProvider,
591 {
592 let state = GroupState::new(
593 context.clone(),
594 public_tree.clone(),
595 Vec::new().into(),
596 ConfirmationTag::empty(cipher_suite_provider).await,
597 );
598
599 let proposals = self.prepare_commit(sender, additional_proposals);
600
601 state
602 .apply_resolved(
603 sender,
604 proposals,
605 external_leaf,
606 identity_provider,
607 cipher_suite_provider,
608 psk_storage,
609 &user_rules,
610 None,
611 CommitDirection::Send,
612 )
613 .await
614 }
615 }
616 }
617
618 // TODO add tests for lite version of filtering
619 #[cfg(all(feature = "by_ref_proposal", test))]
620 mod tests {
621 use alloc::{boxed::Box, vec, vec::Vec};
622
623 use super::test_utils::{make_proposal_cache, pass_through_rules, CommitReceiver};
624 use super::{CachedProposal, ProposalCache};
625 use crate::client::MlsError;
626 use crate::group::message_processor::ProvisionalState;
627 use crate::group::mls_rules::{CommitDirection, CommitSource, EncryptionOptions};
628 use crate::group::proposal_filter::{ProposalBundle, ProposalInfo, ProposalSource};
629 use crate::group::proposal_ref::test_utils::auth_content_from_proposal;
630 use crate::group::proposal_ref::ProposalRef;
631 use crate::group::{
632 AddProposal, AuthenticatedContent, Content, ExternalInit, Proposal, ProposalOrRef,
633 ReInitProposal, RemoveProposal, Roster, Sender, UpdateProposal,
634 };
635 use crate::key_package::test_utils::test_key_package_with_signer;
636 use crate::signer::Signable;
637 use crate::tree_kem::leaf_node::LeafNode;
638 use crate::tree_kem::node::LeafIndex;
639 use crate::tree_kem::TreeKemPublic;
640 use crate::{
641 client::test_utils::{TEST_CIPHER_SUITE, TEST_PROTOCOL_VERSION},
642 crypto::{self, test_utils::test_cipher_suite_provider},
643 extension::test_utils::TestExtension,
644 group::{
645 message_processor::path_update_required,
646 proposal_filter::proposer_can_propose,
647 test_utils::{get_test_group_context, random_bytes, test_group, TEST_GROUP},
648 },
649 identity::basic::BasicIdentityProvider,
650 identity::test_utils::{get_test_signing_identity, BasicWithCustomProvider},
651 key_package::{test_utils::test_key_package, KeyPackageGenerator},
652 mls_rules::{CommitOptions, DefaultMlsRules},
653 psk::AlwaysFoundPskStorage,
654 tree_kem::{
655 leaf_node::{
656 test_utils::{
657 default_properties, get_basic_test_node, get_basic_test_node_capabilities,
658 get_basic_test_node_sig_key, get_test_capabilities,
659 },
660 ConfigProperties, LeafNodeSigningContext, LeafNodeSource,
661 },
662 Lifetime,
663 },
664 };
665 use crate::{KeyPackage, MlsRules};
666
667 use crate::extension::RequiredCapabilitiesExt;
668
669 #[cfg(feature = "by_ref_proposal")]
670 use crate::{
671 extension::ExternalSendersExt,
672 tree_kem::leaf_node_validator::test_utils::FailureIdentityProvider,
673 };
674
675 #[cfg(feature = "psk")]
676 use crate::{
677 group::proposal::PreSharedKeyProposal,
678 psk::{
679 ExternalPskId, JustPreSharedKeyID, PreSharedKeyID, PskGroupId, PskNonce,
680 ResumptionPSKUsage, ResumptionPsk,
681 },
682 };
683
684 #[cfg(feature = "custom_proposal")]
685 use crate::group::proposal::CustomProposal;
686
687 use assert_matches::assert_matches;
688 use core::convert::Infallible;
689 use itertools::Itertools;
690 use mls_rs_core::crypto::{CipherSuite, CipherSuiteProvider};
691 use mls_rs_core::extension::ExtensionList;
692 use mls_rs_core::group::{Capabilities, ProposalType};
693 use mls_rs_core::identity::IdentityProvider;
694 use mls_rs_core::protocol_version::ProtocolVersion;
695 use mls_rs_core::psk::{PreSharedKey, PreSharedKeyStorage};
696 use mls_rs_core::{
697 extension::MlsExtension,
698 identity::{Credential, CredentialType, CustomCredential},
699 };
700
test_sender() -> u32701 fn test_sender() -> u32 {
702 1
703 }
704
705 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
new_tree_custom_proposals( name: &str, proposal_types: Vec<ProposalType>, ) -> (LeafIndex, TreeKemPublic)706 async fn new_tree_custom_proposals(
707 name: &str,
708 proposal_types: Vec<ProposalType>,
709 ) -> (LeafIndex, TreeKemPublic) {
710 let (leaf, secret, _) = get_basic_test_node_capabilities(
711 TEST_CIPHER_SUITE,
712 name,
713 Capabilities {
714 proposals: proposal_types,
715 ..get_test_capabilities()
716 },
717 )
718 .await;
719
720 let (pub_tree, priv_tree) =
721 TreeKemPublic::derive(leaf, secret, &BasicIdentityProvider, &Default::default())
722 .await
723 .unwrap();
724
725 (priv_tree.self_index, pub_tree)
726 }
727
728 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
new_tree(name: &str) -> (LeafIndex, TreeKemPublic)729 async fn new_tree(name: &str) -> (LeafIndex, TreeKemPublic) {
730 new_tree_custom_proposals(name, vec![]).await
731 }
732
733 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
add_member(tree: &mut TreeKemPublic, name: &str) -> LeafIndex734 async fn add_member(tree: &mut TreeKemPublic, name: &str) -> LeafIndex {
735 let test_node = get_basic_test_node(TEST_CIPHER_SUITE, name).await;
736
737 tree.add_leaves(
738 vec![test_node],
739 &BasicIdentityProvider,
740 &test_cipher_suite_provider(TEST_CIPHER_SUITE),
741 )
742 .await
743 .unwrap()[0]
744 }
745
746 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
update_leaf_node(name: &str, leaf_index: u32) -> LeafNode747 async fn update_leaf_node(name: &str, leaf_index: u32) -> LeafNode {
748 let (mut leaf, _, signer) = get_basic_test_node_sig_key(TEST_CIPHER_SUITE, name).await;
749
750 leaf.update(
751 &test_cipher_suite_provider(TEST_CIPHER_SUITE),
752 TEST_GROUP,
753 leaf_index,
754 default_properties(),
755 None,
756 &signer,
757 )
758 .await
759 .unwrap();
760
761 leaf
762 }
763
764 struct TestProposals {
765 test_sender: u32,
766 test_proposals: Vec<AuthenticatedContent>,
767 expected_effects: ProvisionalState,
768 tree: TreeKemPublic,
769 }
770
771 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
test_proposals( protocol_version: ProtocolVersion, cipher_suite: CipherSuite, ) -> TestProposals772 async fn test_proposals(
773 protocol_version: ProtocolVersion,
774 cipher_suite: CipherSuite,
775 ) -> TestProposals {
776 let cipher_suite_provider = test_cipher_suite_provider(cipher_suite);
777
778 let (sender_leaf, sender_leaf_secret, _) =
779 get_basic_test_node_sig_key(cipher_suite, "alice").await;
780
781 let sender = LeafIndex(0);
782
783 let (mut tree, _) = TreeKemPublic::derive(
784 sender_leaf,
785 sender_leaf_secret,
786 &BasicIdentityProvider,
787 &Default::default(),
788 )
789 .await
790 .unwrap();
791
792 let add_package = test_key_package(protocol_version, cipher_suite, "dave").await;
793
794 let remove_leaf_index = add_member(&mut tree, "carol").await;
795
796 let add = Proposal::Add(Box::new(AddProposal {
797 key_package: add_package.clone(),
798 }));
799
800 let remove = Proposal::Remove(RemoveProposal {
801 to_remove: remove_leaf_index,
802 });
803
804 let extensions = Proposal::GroupContextExtensions(ExtensionList::new());
805
806 let proposals = vec![add, remove, extensions];
807
808 let test_node = get_basic_test_node(cipher_suite, "charlie").await;
809
810 let test_sender = *tree
811 .add_leaves(
812 vec![test_node],
813 &BasicIdentityProvider,
814 &cipher_suite_provider,
815 )
816 .await
817 .unwrap()[0];
818
819 let mut expected_tree = tree.clone();
820
821 let mut bundle = ProposalBundle::default();
822
823 let plaintext = proposals
824 .iter()
825 .cloned()
826 .map(|p| auth_content_from_proposal(p, sender))
827 .collect_vec();
828
829 for i in 0..proposals.len() {
830 let pref = ProposalRef::from_content(&cipher_suite_provider, &plaintext[i])
831 .await
832 .unwrap();
833
834 bundle.add(
835 proposals[i].clone(),
836 Sender::Member(test_sender),
837 ProposalSource::ByReference(pref),
838 )
839 }
840
841 expected_tree
842 .batch_edit(
843 &mut bundle,
844 &Default::default(),
845 &BasicIdentityProvider,
846 &cipher_suite_provider,
847 true,
848 )
849 .await
850 .unwrap();
851
852 let expected_effects = ProvisionalState {
853 public_tree: expected_tree,
854 group_context: get_test_group_context(1, cipher_suite).await,
855 external_init_index: None,
856 indexes_of_added_kpkgs: vec![LeafIndex(1)],
857 #[cfg(feature = "state_update")]
858 unused_proposals: vec![],
859 applied_proposals: bundle,
860 };
861
862 TestProposals {
863 test_sender,
864 test_proposals: plaintext,
865 expected_effects,
866 tree,
867 }
868 }
869
870 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
filter_proposals( cipher_suite: CipherSuite, proposals: Vec<AuthenticatedContent>, ) -> Vec<(ProposalRef, CachedProposal)>871 async fn filter_proposals(
872 cipher_suite: CipherSuite,
873 proposals: Vec<AuthenticatedContent>,
874 ) -> Vec<(ProposalRef, CachedProposal)> {
875 let mut contents = Vec::new();
876
877 for p in proposals {
878 if let Content::Proposal(proposal) = &p.content.content {
879 let proposal_ref =
880 ProposalRef::from_content(&test_cipher_suite_provider(cipher_suite), &p)
881 .await
882 .unwrap();
883 contents.push((
884 proposal_ref,
885 CachedProposal::new(proposal.as_ref().clone(), p.content.sender),
886 ));
887 }
888 }
889
890 contents
891 }
892
893 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
make_proposal_ref<S>(p: &Proposal, sender: S) -> ProposalRef where S: Into<Sender>,894 async fn make_proposal_ref<S>(p: &Proposal, sender: S) -> ProposalRef
895 where
896 S: Into<Sender>,
897 {
898 ProposalRef::from_content(
899 &test_cipher_suite_provider(TEST_CIPHER_SUITE),
900 &auth_content_from_proposal(p.clone(), sender),
901 )
902 .await
903 .unwrap()
904 }
905
906 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
make_proposal_info<S>(p: &Proposal, sender: S) -> ProposalInfo<Proposal> where S: Into<Sender> + Clone,907 async fn make_proposal_info<S>(p: &Proposal, sender: S) -> ProposalInfo<Proposal>
908 where
909 S: Into<Sender> + Clone,
910 {
911 ProposalInfo {
912 proposal: p.clone(),
913 sender: sender.clone().into(),
914 source: ProposalSource::ByReference(make_proposal_ref(p, sender).await),
915 }
916 }
917
918 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
test_proposal_cache_setup(proposals: Vec<AuthenticatedContent>) -> ProposalCache919 async fn test_proposal_cache_setup(proposals: Vec<AuthenticatedContent>) -> ProposalCache {
920 let mut cache = make_proposal_cache();
921 cache.extend(filter_proposals(TEST_CIPHER_SUITE, proposals).await);
922 cache
923 }
924
assert_matches(mut expected_state: ProvisionalState, state: ProvisionalState)925 fn assert_matches(mut expected_state: ProvisionalState, state: ProvisionalState) {
926 let expected_proposals = expected_state.applied_proposals.into_proposals_or_refs();
927 let proposals = state.applied_proposals.into_proposals_or_refs();
928
929 assert_eq!(proposals.len(), expected_proposals.len());
930
931 // Determine there are no duplicates in the proposals returned
932 assert!(!proposals.iter().enumerate().any(|(i, p1)| proposals
933 .iter()
934 .enumerate()
935 .any(|(j, p2)| p1 == p2 && i != j)),);
936
937 // Proposal order may change so we just compare the length and contents are the same
938 expected_proposals
939 .iter()
940 .for_each(|p| assert!(proposals.contains(p)));
941
942 assert_eq!(
943 expected_state.external_init_index,
944 state.external_init_index
945 );
946
947 // We don't compare the epoch in this test.
948 expected_state.group_context.epoch = state.group_context.epoch;
949 assert_eq!(expected_state.group_context, state.group_context);
950
951 assert_eq!(
952 expected_state.indexes_of_added_kpkgs,
953 state.indexes_of_added_kpkgs
954 );
955
956 assert_eq!(expected_state.public_tree, state.public_tree);
957
958 #[cfg(feature = "state_update")]
959 assert_eq!(expected_state.unused_proposals, state.unused_proposals);
960 }
961
962 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
test_proposal_cache_commit_all_cached()963 async fn test_proposal_cache_commit_all_cached() {
964 let cipher_suite_provider = test_cipher_suite_provider(TEST_CIPHER_SUITE);
965
966 let TestProposals {
967 test_sender,
968 test_proposals,
969 expected_effects,
970 tree,
971 ..
972 } = test_proposals(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE).await;
973
974 let cache = test_proposal_cache_setup(test_proposals.clone()).await;
975
976 let provisional_state = cache
977 .prepare_commit_default(
978 Sender::Member(test_sender),
979 vec![],
980 &get_test_group_context(0, TEST_CIPHER_SUITE).await,
981 &BasicIdentityProvider,
982 &cipher_suite_provider,
983 &tree,
984 None,
985 &AlwaysFoundPskStorage,
986 pass_through_rules(),
987 )
988 .await
989 .unwrap();
990
991 assert_matches(expected_effects, provisional_state)
992 }
993
994 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
test_proposal_cache_commit_additional()995 async fn test_proposal_cache_commit_additional() {
996 let cipher_suite_provider = test_cipher_suite_provider(TEST_CIPHER_SUITE);
997
998 let TestProposals {
999 test_sender,
1000 test_proposals,
1001 mut expected_effects,
1002 tree,
1003 ..
1004 } = test_proposals(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE).await;
1005
1006 let additional_key_package =
1007 test_key_package(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE, "frank").await;
1008
1009 let additional = AddProposal {
1010 key_package: additional_key_package.clone(),
1011 };
1012
1013 let cache = test_proposal_cache_setup(test_proposals.clone()).await;
1014
1015 let provisional_state = cache
1016 .prepare_commit_default(
1017 Sender::Member(test_sender),
1018 vec![Proposal::Add(Box::new(additional.clone()))],
1019 &get_test_group_context(0, TEST_CIPHER_SUITE).await,
1020 &BasicIdentityProvider,
1021 &cipher_suite_provider,
1022 &tree,
1023 None,
1024 &AlwaysFoundPskStorage,
1025 pass_through_rules(),
1026 )
1027 .await
1028 .unwrap();
1029
1030 expected_effects.applied_proposals.add(
1031 Proposal::Add(Box::new(additional.clone())),
1032 Sender::Member(test_sender),
1033 ProposalSource::ByValue,
1034 );
1035
1036 let leaf = vec![additional_key_package.leaf_node.clone()];
1037
1038 expected_effects
1039 .public_tree
1040 .add_leaves(leaf, &BasicIdentityProvider, &cipher_suite_provider)
1041 .await
1042 .unwrap();
1043
1044 expected_effects.indexes_of_added_kpkgs.push(LeafIndex(3));
1045
1046 assert_matches(expected_effects, provisional_state);
1047 }
1048
1049 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
test_proposal_cache_update_filter()1050 async fn test_proposal_cache_update_filter() {
1051 let cipher_suite_provider = test_cipher_suite_provider(TEST_CIPHER_SUITE);
1052
1053 let TestProposals {
1054 test_proposals,
1055 tree,
1056 ..
1057 } = test_proposals(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE).await;
1058
1059 let update_proposal = make_update_proposal("foo").await;
1060
1061 let additional = vec![Proposal::Update(update_proposal)];
1062
1063 let cache = test_proposal_cache_setup(test_proposals).await;
1064
1065 let res = cache
1066 .prepare_commit_default(
1067 Sender::Member(test_sender()),
1068 additional,
1069 &get_test_group_context(0, TEST_CIPHER_SUITE).await,
1070 &BasicIdentityProvider,
1071 &cipher_suite_provider,
1072 &tree,
1073 None,
1074 &AlwaysFoundPskStorage,
1075 pass_through_rules(),
1076 )
1077 .await;
1078
1079 assert_matches!(res, Err(MlsError::InvalidProposalTypeForSender));
1080 }
1081
1082 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
test_proposal_cache_removal_override_update()1083 async fn test_proposal_cache_removal_override_update() {
1084 let cipher_suite_provider = test_cipher_suite_provider(TEST_CIPHER_SUITE);
1085
1086 let TestProposals {
1087 test_sender,
1088 test_proposals,
1089 tree,
1090 ..
1091 } = test_proposals(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE).await;
1092
1093 let update = Proposal::Update(make_update_proposal("foo").await);
1094 let update_proposal_ref = make_proposal_ref(&update, LeafIndex(1)).await;
1095 let mut cache = test_proposal_cache_setup(test_proposals).await;
1096
1097 cache.insert(update_proposal_ref.clone(), update, Sender::Member(1));
1098
1099 let provisional_state = cache
1100 .prepare_commit_default(
1101 Sender::Member(test_sender),
1102 vec![],
1103 &get_test_group_context(0, TEST_CIPHER_SUITE).await,
1104 &BasicIdentityProvider,
1105 &cipher_suite_provider,
1106 &tree,
1107 None,
1108 &AlwaysFoundPskStorage,
1109 pass_through_rules(),
1110 )
1111 .await
1112 .unwrap();
1113
1114 assert!(provisional_state
1115 .applied_proposals
1116 .removals
1117 .iter()
1118 .any(|p| *p.proposal.to_remove == 1));
1119
1120 assert!(!provisional_state
1121 .applied_proposals
1122 .into_proposals_or_refs()
1123 .contains(&ProposalOrRef::Reference(update_proposal_ref)))
1124 }
1125
1126 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
test_proposal_cache_filter_duplicates_insert()1127 async fn test_proposal_cache_filter_duplicates_insert() {
1128 let cipher_suite_provider = test_cipher_suite_provider(TEST_CIPHER_SUITE);
1129
1130 let TestProposals {
1131 test_sender,
1132 test_proposals,
1133 expected_effects,
1134 tree,
1135 ..
1136 } = test_proposals(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE).await;
1137
1138 let mut cache = test_proposal_cache_setup(test_proposals.clone()).await;
1139 cache.extend(filter_proposals(TEST_CIPHER_SUITE, test_proposals.clone()).await);
1140
1141 let provisional_state = cache
1142 .prepare_commit_default(
1143 Sender::Member(test_sender),
1144 vec![],
1145 &get_test_group_context(0, TEST_CIPHER_SUITE).await,
1146 &BasicIdentityProvider,
1147 &cipher_suite_provider,
1148 &tree,
1149 None,
1150 &AlwaysFoundPskStorage,
1151 pass_through_rules(),
1152 )
1153 .await
1154 .unwrap();
1155
1156 assert_matches(expected_effects, provisional_state)
1157 }
1158
1159 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
test_proposal_cache_filter_duplicates_additional()1160 async fn test_proposal_cache_filter_duplicates_additional() {
1161 let cipher_suite_provider = test_cipher_suite_provider(TEST_CIPHER_SUITE);
1162
1163 let TestProposals {
1164 test_proposals,
1165 expected_effects,
1166 tree,
1167 ..
1168 } = test_proposals(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE).await;
1169
1170 let mut cache = test_proposal_cache_setup(test_proposals.clone()).await;
1171
1172 // Updates from different senders will be allowed so we test duplicates for add / remove
1173 let additional = test_proposals
1174 .clone()
1175 .into_iter()
1176 .filter_map(|plaintext| match plaintext.content.content {
1177 Content::Proposal(p) if p.proposal_type() == ProposalType::UPDATE => None,
1178 Content::Proposal(_) => Some(plaintext),
1179 _ => None,
1180 })
1181 .collect::<Vec<_>>();
1182
1183 cache.extend(filter_proposals(TEST_CIPHER_SUITE, additional).await);
1184
1185 let provisional_state = cache
1186 .prepare_commit_default(
1187 Sender::Member(2),
1188 Vec::new(),
1189 &get_test_group_context(0, TEST_CIPHER_SUITE).await,
1190 &BasicIdentityProvider,
1191 &cipher_suite_provider,
1192 &tree,
1193 None,
1194 &AlwaysFoundPskStorage,
1195 pass_through_rules(),
1196 )
1197 .await
1198 .unwrap();
1199
1200 assert_matches(expected_effects, provisional_state)
1201 }
1202
1203 #[cfg(feature = "private_message")]
1204 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
test_proposal_cache_is_empty()1205 async fn test_proposal_cache_is_empty() {
1206 let mut cache = make_proposal_cache();
1207 assert!(cache.is_empty());
1208
1209 let test_proposal = Proposal::Remove(RemoveProposal {
1210 to_remove: LeafIndex(test_sender()),
1211 });
1212
1213 let proposer = test_sender();
1214 let test_proposal_ref = make_proposal_ref(&test_proposal, LeafIndex(proposer)).await;
1215 cache.insert(test_proposal_ref, test_proposal, Sender::Member(proposer));
1216
1217 assert!(!cache.is_empty())
1218 }
1219
1220 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
test_proposal_cache_resolve()1221 async fn test_proposal_cache_resolve() {
1222 let cipher_suite_provider = test_cipher_suite_provider(TEST_CIPHER_SUITE);
1223
1224 let TestProposals {
1225 test_sender,
1226 test_proposals,
1227 tree,
1228 ..
1229 } = test_proposals(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE).await;
1230
1231 let cache = test_proposal_cache_setup(test_proposals).await;
1232
1233 let proposal = Proposal::Add(Box::new(AddProposal {
1234 key_package: test_key_package(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE, "frank").await,
1235 }));
1236
1237 let additional = vec![proposal];
1238
1239 let expected_effects = cache
1240 .prepare_commit_default(
1241 Sender::Member(test_sender),
1242 additional,
1243 &get_test_group_context(0, TEST_CIPHER_SUITE).await,
1244 &BasicIdentityProvider,
1245 &cipher_suite_provider,
1246 &tree,
1247 None,
1248 &AlwaysFoundPskStorage,
1249 pass_through_rules(),
1250 )
1251 .await
1252 .unwrap();
1253
1254 let proposals = expected_effects
1255 .applied_proposals
1256 .clone()
1257 .into_proposals_or_refs();
1258
1259 let resolution = cache
1260 .resolve_for_commit_default(
1261 Sender::Member(test_sender),
1262 proposals,
1263 None,
1264 &ExtensionList::new(),
1265 &BasicIdentityProvider,
1266 &cipher_suite_provider,
1267 &tree,
1268 &AlwaysFoundPskStorage,
1269 pass_through_rules(),
1270 )
1271 .await
1272 .unwrap();
1273
1274 assert_matches(expected_effects, resolution);
1275 }
1276
1277 #[cfg(feature = "psk")]
1278 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
proposal_cache_filters_duplicate_psk_ids()1279 async fn proposal_cache_filters_duplicate_psk_ids() {
1280 let cipher_suite_provider = test_cipher_suite_provider(TEST_CIPHER_SUITE);
1281
1282 let (alice, tree) = new_tree("alice").await;
1283 let cache = make_proposal_cache();
1284
1285 let proposal = Proposal::Psk(make_external_psk(
1286 b"ted",
1287 crate::psk::PskNonce::random(&test_cipher_suite_provider(TEST_CIPHER_SUITE)).unwrap(),
1288 ));
1289
1290 let res = cache
1291 .prepare_commit_default(
1292 Sender::Member(*alice),
1293 vec![proposal.clone(), proposal],
1294 &get_test_group_context(0, TEST_CIPHER_SUITE).await,
1295 &BasicIdentityProvider,
1296 &cipher_suite_provider,
1297 &tree,
1298 None,
1299 &AlwaysFoundPskStorage,
1300 pass_through_rules(),
1301 )
1302 .await;
1303
1304 assert_matches!(res, Err(MlsError::DuplicatePskIds));
1305 }
1306
1307 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
test_node() -> LeafNode1308 async fn test_node() -> LeafNode {
1309 let (mut leaf_node, _, signer) =
1310 get_basic_test_node_sig_key(TEST_CIPHER_SUITE, "foo").await;
1311
1312 leaf_node
1313 .commit(
1314 &test_cipher_suite_provider(TEST_CIPHER_SUITE),
1315 TEST_GROUP,
1316 0,
1317 default_properties(),
1318 None,
1319 &signer,
1320 )
1321 .await
1322 .unwrap();
1323
1324 leaf_node
1325 }
1326
1327 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
external_commit_must_have_new_leaf()1328 async fn external_commit_must_have_new_leaf() {
1329 let cache = make_proposal_cache();
1330 let cipher_suite_provider = test_cipher_suite_provider(TEST_CIPHER_SUITE);
1331
1332 let kem_output = vec![0; cipher_suite_provider.kdf_extract_size()];
1333 let group = test_group(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE).await;
1334 let public_tree = &group.group.state.public_tree;
1335
1336 let res = cache
1337 .resolve_for_commit_default(
1338 Sender::NewMemberCommit,
1339 vec![ProposalOrRef::Proposal(Box::new(Proposal::ExternalInit(
1340 ExternalInit { kem_output },
1341 )))],
1342 None,
1343 &group.group.context().extensions,
1344 &BasicIdentityProvider,
1345 &cipher_suite_provider,
1346 public_tree,
1347 &AlwaysFoundPskStorage,
1348 pass_through_rules(),
1349 )
1350 .await;
1351
1352 assert_matches!(res, Err(MlsError::ExternalCommitMustHaveNewLeaf));
1353 }
1354
1355 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
proposal_cache_rejects_proposals_by_ref_for_new_member()1356 async fn proposal_cache_rejects_proposals_by_ref_for_new_member() {
1357 let mut cache = make_proposal_cache();
1358 let cipher_suite_provider = test_cipher_suite_provider(TEST_CIPHER_SUITE);
1359
1360 let proposal = {
1361 let kem_output = vec![0; cipher_suite_provider.kdf_extract_size()];
1362 Proposal::ExternalInit(ExternalInit { kem_output })
1363 };
1364
1365 let proposal_ref = make_proposal_ref(&proposal, test_sender()).await;
1366
1367 cache.insert(
1368 proposal_ref.clone(),
1369 proposal,
1370 Sender::Member(test_sender()),
1371 );
1372
1373 let group = test_group(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE).await;
1374 let public_tree = &group.group.state.public_tree;
1375
1376 let res = cache
1377 .resolve_for_commit_default(
1378 Sender::NewMemberCommit,
1379 vec![ProposalOrRef::Reference(proposal_ref)],
1380 Some(&test_node().await),
1381 &group.group.context().extensions,
1382 &BasicIdentityProvider,
1383 &cipher_suite_provider,
1384 public_tree,
1385 &AlwaysFoundPskStorage,
1386 pass_through_rules(),
1387 )
1388 .await;
1389
1390 assert_matches!(res, Err(MlsError::OnlyMembersCanCommitProposalsByRef));
1391 }
1392
1393 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
proposal_cache_rejects_multiple_external_init_proposals_in_commit()1394 async fn proposal_cache_rejects_multiple_external_init_proposals_in_commit() {
1395 let cache = make_proposal_cache();
1396 let cipher_suite_provider = test_cipher_suite_provider(TEST_CIPHER_SUITE);
1397 let kem_output = vec![0; cipher_suite_provider.kdf_extract_size()];
1398 let group = test_group(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE).await;
1399 let public_tree = &group.group.state.public_tree;
1400
1401 let res = cache
1402 .resolve_for_commit_default(
1403 Sender::NewMemberCommit,
1404 [
1405 Proposal::ExternalInit(ExternalInit {
1406 kem_output: kem_output.clone(),
1407 }),
1408 Proposal::ExternalInit(ExternalInit { kem_output }),
1409 ]
1410 .into_iter()
1411 .map(|p| ProposalOrRef::Proposal(Box::new(p)))
1412 .collect(),
1413 Some(&test_node().await),
1414 &group.group.context().extensions,
1415 &BasicIdentityProvider,
1416 &cipher_suite_provider,
1417 public_tree,
1418 &AlwaysFoundPskStorage,
1419 pass_through_rules(),
1420 )
1421 .await;
1422
1423 assert_matches!(
1424 res,
1425 Err(MlsError::ExternalCommitMustHaveExactlyOneExternalInit)
1426 );
1427 }
1428
1429 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
new_member_commits_proposal(proposal: Proposal) -> Result<ProvisionalState, MlsError>1430 async fn new_member_commits_proposal(proposal: Proposal) -> Result<ProvisionalState, MlsError> {
1431 let cache = make_proposal_cache();
1432 let cipher_suite_provider = test_cipher_suite_provider(TEST_CIPHER_SUITE);
1433 let kem_output = vec![0; cipher_suite_provider.kdf_extract_size()];
1434 let group = test_group(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE).await;
1435 let public_tree = &group.group.state.public_tree;
1436
1437 cache
1438 .resolve_for_commit_default(
1439 Sender::NewMemberCommit,
1440 [
1441 Proposal::ExternalInit(ExternalInit { kem_output }),
1442 proposal,
1443 ]
1444 .into_iter()
1445 .map(|p| ProposalOrRef::Proposal(Box::new(p)))
1446 .collect(),
1447 Some(&test_node().await),
1448 &group.group.context().extensions,
1449 &BasicIdentityProvider,
1450 &cipher_suite_provider,
1451 public_tree,
1452 &AlwaysFoundPskStorage,
1453 pass_through_rules(),
1454 )
1455 .await
1456 }
1457
1458 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
new_member_cannot_commit_add_proposal()1459 async fn new_member_cannot_commit_add_proposal() {
1460 let res = new_member_commits_proposal(Proposal::Add(Box::new(AddProposal {
1461 key_package: test_key_package(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE, "frank").await,
1462 })))
1463 .await;
1464
1465 assert_matches!(
1466 res,
1467 Err(MlsError::InvalidProposalTypeInExternalCommit(
1468 ProposalType::ADD
1469 ))
1470 );
1471 }
1472
1473 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
new_member_cannot_commit_more_than_one_remove_proposal()1474 async fn new_member_cannot_commit_more_than_one_remove_proposal() {
1475 let cache = make_proposal_cache();
1476 let cipher_suite_provider = test_cipher_suite_provider(TEST_CIPHER_SUITE);
1477 let kem_output = vec![0; cipher_suite_provider.kdf_extract_size()];
1478 let group = test_group(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE).await;
1479 let group_extensions = group.group.context().extensions.clone();
1480 let mut public_tree = group.group.state.public_tree;
1481
1482 let foo = get_basic_test_node(TEST_CIPHER_SUITE, "foo").await;
1483
1484 let bar = get_basic_test_node(TEST_CIPHER_SUITE, "bar").await;
1485
1486 let test_leaf_nodes = vec![foo, bar];
1487
1488 let test_leaf_node_indexes = public_tree
1489 .add_leaves(
1490 test_leaf_nodes,
1491 &BasicIdentityProvider,
1492 &cipher_suite_provider,
1493 )
1494 .await
1495 .unwrap();
1496
1497 let proposals = vec![
1498 Proposal::ExternalInit(ExternalInit { kem_output }),
1499 Proposal::Remove(RemoveProposal {
1500 to_remove: test_leaf_node_indexes[0],
1501 }),
1502 Proposal::Remove(RemoveProposal {
1503 to_remove: test_leaf_node_indexes[1],
1504 }),
1505 ];
1506
1507 let res = cache
1508 .resolve_for_commit_default(
1509 Sender::NewMemberCommit,
1510 proposals
1511 .into_iter()
1512 .map(|p| ProposalOrRef::Proposal(Box::new(p)))
1513 .collect(),
1514 Some(&test_node().await),
1515 &group_extensions,
1516 &BasicIdentityProvider,
1517 &cipher_suite_provider,
1518 &public_tree,
1519 &AlwaysFoundPskStorage,
1520 pass_through_rules(),
1521 )
1522 .await;
1523
1524 assert_matches!(res, Err(MlsError::ExternalCommitWithMoreThanOneRemove));
1525 }
1526
1527 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
new_member_remove_proposal_invalid_credential()1528 async fn new_member_remove_proposal_invalid_credential() {
1529 let cache = make_proposal_cache();
1530 let cipher_suite_provider = test_cipher_suite_provider(TEST_CIPHER_SUITE);
1531 let kem_output = vec![0; cipher_suite_provider.kdf_extract_size()];
1532 let group = test_group(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE).await;
1533 let group_extensions = group.group.context().extensions.clone();
1534 let mut public_tree = group.group.state.public_tree;
1535
1536 let node = get_basic_test_node(TEST_CIPHER_SUITE, "bar").await;
1537
1538 let test_leaf_nodes = vec![node];
1539
1540 let test_leaf_node_indexes = public_tree
1541 .add_leaves(
1542 test_leaf_nodes,
1543 &BasicIdentityProvider,
1544 &cipher_suite_provider,
1545 )
1546 .await
1547 .unwrap();
1548
1549 let proposals = vec![
1550 Proposal::ExternalInit(ExternalInit { kem_output }),
1551 Proposal::Remove(RemoveProposal {
1552 to_remove: test_leaf_node_indexes[0],
1553 }),
1554 ];
1555
1556 let res = cache
1557 .resolve_for_commit_default(
1558 Sender::NewMemberCommit,
1559 proposals
1560 .into_iter()
1561 .map(|p| ProposalOrRef::Proposal(Box::new(p)))
1562 .collect(),
1563 Some(&test_node().await),
1564 &group_extensions,
1565 &BasicIdentityProvider,
1566 &cipher_suite_provider,
1567 &public_tree,
1568 &AlwaysFoundPskStorage,
1569 pass_through_rules(),
1570 )
1571 .await;
1572
1573 assert_matches!(res, Err(MlsError::ExternalCommitRemovesOtherIdentity));
1574 }
1575
1576 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
new_member_remove_proposal_valid_credential()1577 async fn new_member_remove_proposal_valid_credential() {
1578 let cache = make_proposal_cache();
1579 let cipher_suite_provider = test_cipher_suite_provider(TEST_CIPHER_SUITE);
1580 let kem_output = vec![0; cipher_suite_provider.kdf_extract_size()];
1581 let group = test_group(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE).await;
1582 let group_extensions = group.group.context().extensions.clone();
1583 let mut public_tree = group.group.state.public_tree;
1584
1585 let node = get_basic_test_node(TEST_CIPHER_SUITE, "foo").await;
1586
1587 let test_leaf_nodes = vec![node];
1588
1589 let test_leaf_node_indexes = public_tree
1590 .add_leaves(
1591 test_leaf_nodes,
1592 &BasicIdentityProvider,
1593 &cipher_suite_provider,
1594 )
1595 .await
1596 .unwrap();
1597
1598 let proposals = vec![
1599 Proposal::ExternalInit(ExternalInit { kem_output }),
1600 Proposal::Remove(RemoveProposal {
1601 to_remove: test_leaf_node_indexes[0],
1602 }),
1603 ];
1604
1605 let res = cache
1606 .resolve_for_commit_default(
1607 Sender::NewMemberCommit,
1608 proposals
1609 .into_iter()
1610 .map(|p| ProposalOrRef::Proposal(Box::new(p)))
1611 .collect(),
1612 Some(&test_node().await),
1613 &group_extensions,
1614 &BasicIdentityProvider,
1615 &cipher_suite_provider,
1616 &public_tree,
1617 &AlwaysFoundPskStorage,
1618 pass_through_rules(),
1619 )
1620 .await;
1621
1622 assert_matches!(res, Ok(_));
1623 }
1624
1625 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
new_member_cannot_commit_update_proposal()1626 async fn new_member_cannot_commit_update_proposal() {
1627 let res = new_member_commits_proposal(Proposal::Update(UpdateProposal {
1628 leaf_node: get_basic_test_node(TEST_CIPHER_SUITE, "foo").await,
1629 }))
1630 .await;
1631
1632 assert_matches!(
1633 res,
1634 Err(MlsError::InvalidProposalTypeInExternalCommit(
1635 ProposalType::UPDATE
1636 ))
1637 );
1638 }
1639
1640 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
new_member_cannot_commit_group_extensions_proposal()1641 async fn new_member_cannot_commit_group_extensions_proposal() {
1642 let res =
1643 new_member_commits_proposal(Proposal::GroupContextExtensions(ExtensionList::new()))
1644 .await;
1645
1646 assert_matches!(
1647 res,
1648 Err(MlsError::InvalidProposalTypeInExternalCommit(
1649 ProposalType::GROUP_CONTEXT_EXTENSIONS,
1650 ))
1651 );
1652 }
1653
1654 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
new_member_cannot_commit_reinit_proposal()1655 async fn new_member_cannot_commit_reinit_proposal() {
1656 let res = new_member_commits_proposal(Proposal::ReInit(ReInitProposal {
1657 group_id: b"foo".to_vec(),
1658 version: TEST_PROTOCOL_VERSION,
1659 cipher_suite: TEST_CIPHER_SUITE,
1660 extensions: ExtensionList::new(),
1661 }))
1662 .await;
1663
1664 assert_matches!(
1665 res,
1666 Err(MlsError::InvalidProposalTypeInExternalCommit(
1667 ProposalType::RE_INIT
1668 ))
1669 );
1670 }
1671
1672 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
new_member_commit_must_contain_an_external_init_proposal()1673 async fn new_member_commit_must_contain_an_external_init_proposal() {
1674 let cache = make_proposal_cache();
1675 let cipher_suite_provider = test_cipher_suite_provider(TEST_CIPHER_SUITE);
1676 let group = test_group(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE).await;
1677 let public_tree = &group.group.state.public_tree;
1678
1679 let res = cache
1680 .resolve_for_commit_default(
1681 Sender::NewMemberCommit,
1682 Vec::new(),
1683 Some(&test_node().await),
1684 &group.group.context().extensions,
1685 &BasicIdentityProvider,
1686 &cipher_suite_provider,
1687 public_tree,
1688 &AlwaysFoundPskStorage,
1689 pass_through_rules(),
1690 )
1691 .await;
1692
1693 assert_matches!(
1694 res,
1695 Err(MlsError::ExternalCommitMustHaveExactlyOneExternalInit)
1696 );
1697 }
1698
1699 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
test_path_update_required_empty()1700 async fn test_path_update_required_empty() {
1701 let cache = make_proposal_cache();
1702 let cipher_suite_provider = test_cipher_suite_provider(TEST_CIPHER_SUITE);
1703
1704 let mut tree = TreeKemPublic::new();
1705 add_member(&mut tree, "alice").await;
1706 add_member(&mut tree, "bob").await;
1707
1708 let effects = cache
1709 .prepare_commit_default(
1710 Sender::Member(test_sender()),
1711 vec![],
1712 &get_test_group_context(1, TEST_CIPHER_SUITE).await,
1713 &BasicIdentityProvider,
1714 &cipher_suite_provider,
1715 &tree,
1716 None,
1717 &AlwaysFoundPskStorage,
1718 pass_through_rules(),
1719 )
1720 .await
1721 .unwrap();
1722
1723 assert!(path_update_required(&effects.applied_proposals))
1724 }
1725
1726 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
test_path_update_required_updates()1727 async fn test_path_update_required_updates() {
1728 let mut cache = make_proposal_cache();
1729 let update = Proposal::Update(make_update_proposal("bar").await);
1730 let cipher_suite_provider = test_cipher_suite_provider(TEST_CIPHER_SUITE);
1731
1732 cache.insert(
1733 make_proposal_ref(&update, LeafIndex(2)).await,
1734 update,
1735 Sender::Member(2),
1736 );
1737
1738 let mut tree = TreeKemPublic::new();
1739 add_member(&mut tree, "alice").await;
1740 add_member(&mut tree, "bob").await;
1741 add_member(&mut tree, "carol").await;
1742
1743 let effects = cache
1744 .prepare_commit_default(
1745 Sender::Member(test_sender()),
1746 Vec::new(),
1747 &get_test_group_context(1, TEST_CIPHER_SUITE).await,
1748 &BasicIdentityProvider,
1749 &cipher_suite_provider,
1750 &tree,
1751 None,
1752 &AlwaysFoundPskStorage,
1753 pass_through_rules(),
1754 )
1755 .await
1756 .unwrap();
1757
1758 assert!(path_update_required(&effects.applied_proposals))
1759 }
1760
1761 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
test_path_update_required_removes()1762 async fn test_path_update_required_removes() {
1763 let cache = make_proposal_cache();
1764 let cipher_suite_provider = test_cipher_suite_provider(TEST_CIPHER_SUITE);
1765
1766 let (alice_leaf, alice_secret, _) =
1767 get_basic_test_node_sig_key(TEST_CIPHER_SUITE, "alice").await;
1768 let alice = 0;
1769
1770 let (mut tree, _) = TreeKemPublic::derive(
1771 alice_leaf,
1772 alice_secret,
1773 &BasicIdentityProvider,
1774 &Default::default(),
1775 )
1776 .await
1777 .unwrap();
1778
1779 let bob_node = get_basic_test_node(TEST_CIPHER_SUITE, "bob").await;
1780
1781 let bob = tree
1782 .add_leaves(
1783 vec![bob_node],
1784 &BasicIdentityProvider,
1785 &cipher_suite_provider,
1786 )
1787 .await
1788 .unwrap()[0];
1789
1790 let remove = Proposal::Remove(RemoveProposal { to_remove: bob });
1791
1792 let effects = cache
1793 .prepare_commit_default(
1794 Sender::Member(alice),
1795 vec![remove],
1796 &get_test_group_context(1, TEST_CIPHER_SUITE).await,
1797 &BasicIdentityProvider,
1798 &cipher_suite_provider,
1799 &tree,
1800 None,
1801 &AlwaysFoundPskStorage,
1802 pass_through_rules(),
1803 )
1804 .await
1805 .unwrap();
1806
1807 assert!(path_update_required(&effects.applied_proposals))
1808 }
1809
1810 #[cfg(feature = "psk")]
1811 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
test_path_update_not_required()1812 async fn test_path_update_not_required() {
1813 let (alice, tree) = new_tree("alice").await;
1814 let cipher_suite_provider = test_cipher_suite_provider(TEST_CIPHER_SUITE);
1815 let cache = make_proposal_cache();
1816
1817 let psk = Proposal::Psk(PreSharedKeyProposal {
1818 psk: PreSharedKeyID::new(
1819 JustPreSharedKeyID::External(ExternalPskId::new(vec![])),
1820 &test_cipher_suite_provider(TEST_CIPHER_SUITE),
1821 )
1822 .unwrap(),
1823 });
1824
1825 let add = Proposal::Add(Box::new(AddProposal {
1826 key_package: test_key_package(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE, "bob").await,
1827 }));
1828
1829 let effects = cache
1830 .prepare_commit_default(
1831 Sender::Member(*alice),
1832 vec![psk, add],
1833 &get_test_group_context(1, TEST_CIPHER_SUITE).await,
1834 &BasicIdentityProvider,
1835 &cipher_suite_provider,
1836 &tree,
1837 None,
1838 &AlwaysFoundPskStorage,
1839 pass_through_rules(),
1840 )
1841 .await
1842 .unwrap();
1843
1844 assert!(!path_update_required(&effects.applied_proposals))
1845 }
1846
1847 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
path_update_is_not_required_for_re_init()1848 async fn path_update_is_not_required_for_re_init() {
1849 let cipher_suite_provider = test_cipher_suite_provider(TEST_CIPHER_SUITE);
1850 let (alice, tree) = new_tree("alice").await;
1851 let cache = make_proposal_cache();
1852
1853 let reinit = Proposal::ReInit(ReInitProposal {
1854 group_id: vec![],
1855 version: TEST_PROTOCOL_VERSION,
1856 cipher_suite: TEST_CIPHER_SUITE,
1857 extensions: Default::default(),
1858 });
1859
1860 let effects = cache
1861 .prepare_commit_default(
1862 Sender::Member(*alice),
1863 vec![reinit],
1864 &get_test_group_context(1, TEST_CIPHER_SUITE).await,
1865 &BasicIdentityProvider,
1866 &cipher_suite_provider,
1867 &tree,
1868 None,
1869 &AlwaysFoundPskStorage,
1870 pass_through_rules(),
1871 )
1872 .await
1873 .unwrap();
1874
1875 assert!(!path_update_required(&effects.applied_proposals))
1876 }
1877
1878 #[derive(Debug)]
1879 struct CommitSender<'a, C, F, P, CSP> {
1880 cipher_suite_provider: CSP,
1881 tree: &'a TreeKemPublic,
1882 sender: LeafIndex,
1883 cache: ProposalCache,
1884 additional_proposals: Vec<Proposal>,
1885 identity_provider: C,
1886 user_rules: F,
1887 psk_storage: P,
1888 }
1889
1890 impl<'a, CSP>
1891 CommitSender<'a, BasicWithCustomProvider, DefaultMlsRules, AlwaysFoundPskStorage, CSP>
1892 {
new(tree: &'a TreeKemPublic, sender: LeafIndex, cipher_suite_provider: CSP) -> Self1893 fn new(tree: &'a TreeKemPublic, sender: LeafIndex, cipher_suite_provider: CSP) -> Self {
1894 Self {
1895 tree,
1896 sender,
1897 cache: make_proposal_cache(),
1898 additional_proposals: Vec::new(),
1899 identity_provider: BasicWithCustomProvider::new(BasicIdentityProvider::new()),
1900 user_rules: pass_through_rules(),
1901 psk_storage: AlwaysFoundPskStorage,
1902 cipher_suite_provider,
1903 }
1904 }
1905 }
1906
1907 impl<'a, C, F, P, CSP> CommitSender<'a, C, F, P, CSP>
1908 where
1909 C: IdentityProvider,
1910 F: MlsRules,
1911 P: PreSharedKeyStorage,
1912 CSP: CipherSuiteProvider,
1913 {
1914 #[cfg(feature = "by_ref_proposal")]
with_identity_provider<V>(self, identity_provider: V) -> CommitSender<'a, V, F, P, CSP> where V: IdentityProvider,1915 fn with_identity_provider<V>(self, identity_provider: V) -> CommitSender<'a, V, F, P, CSP>
1916 where
1917 V: IdentityProvider,
1918 {
1919 CommitSender {
1920 identity_provider,
1921 cipher_suite_provider: self.cipher_suite_provider,
1922 tree: self.tree,
1923 sender: self.sender,
1924 cache: self.cache,
1925 additional_proposals: self.additional_proposals,
1926 user_rules: self.user_rules,
1927 psk_storage: self.psk_storage,
1928 }
1929 }
1930
cache<S>(mut self, r: ProposalRef, p: Proposal, proposer: S) -> Self where S: Into<Sender>,1931 fn cache<S>(mut self, r: ProposalRef, p: Proposal, proposer: S) -> Self
1932 where
1933 S: Into<Sender>,
1934 {
1935 self.cache.insert(r, p, proposer.into());
1936 self
1937 }
1938
with_additional<I>(mut self, proposals: I) -> Self where I: IntoIterator<Item = Proposal>,1939 fn with_additional<I>(mut self, proposals: I) -> Self
1940 where
1941 I: IntoIterator<Item = Proposal>,
1942 {
1943 self.additional_proposals.extend(proposals);
1944 self
1945 }
1946
with_user_rules<G>(self, f: G) -> CommitSender<'a, C, G, P, CSP> where G: MlsRules,1947 fn with_user_rules<G>(self, f: G) -> CommitSender<'a, C, G, P, CSP>
1948 where
1949 G: MlsRules,
1950 {
1951 CommitSender {
1952 tree: self.tree,
1953 sender: self.sender,
1954 cache: self.cache,
1955 additional_proposals: self.additional_proposals,
1956 identity_provider: self.identity_provider,
1957 user_rules: f,
1958 psk_storage: self.psk_storage,
1959 cipher_suite_provider: self.cipher_suite_provider,
1960 }
1961 }
1962
with_psk_storage<V>(self, v: V) -> CommitSender<'a, C, F, V, CSP> where V: PreSharedKeyStorage,1963 fn with_psk_storage<V>(self, v: V) -> CommitSender<'a, C, F, V, CSP>
1964 where
1965 V: PreSharedKeyStorage,
1966 {
1967 CommitSender {
1968 tree: self.tree,
1969 sender: self.sender,
1970 cache: self.cache,
1971 additional_proposals: self.additional_proposals,
1972 identity_provider: self.identity_provider,
1973 user_rules: self.user_rules,
1974 psk_storage: v,
1975 cipher_suite_provider: self.cipher_suite_provider,
1976 }
1977 }
1978
1979 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
send(&self) -> Result<(Vec<ProposalOrRef>, ProvisionalState), MlsError>1980 async fn send(&self) -> Result<(Vec<ProposalOrRef>, ProvisionalState), MlsError> {
1981 let state = self
1982 .cache
1983 .prepare_commit_default(
1984 Sender::Member(*self.sender),
1985 self.additional_proposals.clone(),
1986 &get_test_group_context(1, TEST_CIPHER_SUITE).await,
1987 &self.identity_provider,
1988 &self.cipher_suite_provider,
1989 self.tree,
1990 None,
1991 &self.psk_storage,
1992 &self.user_rules,
1993 )
1994 .await?;
1995
1996 let proposals = state.applied_proposals.clone().into_proposals_or_refs();
1997
1998 Ok((proposals, state))
1999 }
2000 }
2001
2002 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
key_package_with_invalid_signature() -> KeyPackage2003 async fn key_package_with_invalid_signature() -> KeyPackage {
2004 let mut kp = test_key_package(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE, "mallory").await;
2005 kp.signature.clear();
2006 kp
2007 }
2008
2009 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
key_package_with_public_key(key: crypto::HpkePublicKey) -> KeyPackage2010 async fn key_package_with_public_key(key: crypto::HpkePublicKey) -> KeyPackage {
2011 let cs = test_cipher_suite_provider(TEST_CIPHER_SUITE);
2012
2013 let (mut key_package, signer) =
2014 test_key_package_with_signer(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE, "test").await;
2015
2016 key_package.leaf_node.public_key = key;
2017
2018 key_package
2019 .leaf_node
2020 .sign(
2021 &cs,
2022 &signer,
2023 &LeafNodeSigningContext {
2024 group_id: None,
2025 leaf_index: None,
2026 },
2027 )
2028 .await
2029 .unwrap();
2030
2031 key_package.sign(&cs, &signer, &()).await.unwrap();
2032
2033 key_package
2034 }
2035
2036 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
receiving_add_with_invalid_key_package_fails()2037 async fn receiving_add_with_invalid_key_package_fails() {
2038 let (alice, tree) = new_tree("alice").await;
2039
2040 let res = CommitReceiver::new(
2041 &tree,
2042 alice,
2043 alice,
2044 test_cipher_suite_provider(TEST_CIPHER_SUITE),
2045 )
2046 .receive([Proposal::Add(Box::new(AddProposal {
2047 key_package: key_package_with_invalid_signature().await,
2048 }))])
2049 .await;
2050
2051 assert_matches!(res, Err(MlsError::InvalidSignature));
2052 }
2053
2054 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
sending_additional_add_with_invalid_key_package_fails()2055 async fn sending_additional_add_with_invalid_key_package_fails() {
2056 let (alice, tree) = new_tree("alice").await;
2057
2058 let res = CommitSender::new(&tree, alice, test_cipher_suite_provider(TEST_CIPHER_SUITE))
2059 .with_additional([Proposal::Add(Box::new(AddProposal {
2060 key_package: key_package_with_invalid_signature().await,
2061 }))])
2062 .send()
2063 .await;
2064
2065 assert_matches!(res, Err(MlsError::InvalidSignature));
2066 }
2067
2068 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
sending_add_with_invalid_key_package_filters_it_out()2069 async fn sending_add_with_invalid_key_package_filters_it_out() {
2070 let (alice, tree) = new_tree("alice").await;
2071
2072 let proposal = Proposal::Add(Box::new(AddProposal {
2073 key_package: key_package_with_invalid_signature().await,
2074 }));
2075
2076 let proposal_info = make_proposal_info(&proposal, alice).await;
2077
2078 let processed_proposals =
2079 CommitSender::new(&tree, alice, test_cipher_suite_provider(TEST_CIPHER_SUITE))
2080 .cache(
2081 proposal_info.proposal_ref().unwrap().clone(),
2082 proposal.clone(),
2083 alice,
2084 )
2085 .send()
2086 .await
2087 .unwrap();
2088
2089 assert_eq!(processed_proposals.0, Vec::new());
2090
2091 #[cfg(feature = "state_update")]
2092 assert_eq!(processed_proposals.1.unused_proposals, vec![proposal_info]);
2093 }
2094
2095 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
sending_add_with_hpke_key_of_another_member_fails()2096 async fn sending_add_with_hpke_key_of_another_member_fails() {
2097 let (alice, tree) = new_tree("alice").await;
2098
2099 let res = CommitSender::new(&tree, alice, test_cipher_suite_provider(TEST_CIPHER_SUITE))
2100 .with_additional([Proposal::Add(Box::new(AddProposal {
2101 key_package: key_package_with_public_key(
2102 tree.get_leaf_node(alice).unwrap().public_key.clone(),
2103 )
2104 .await,
2105 }))])
2106 .send()
2107 .await;
2108
2109 assert_matches!(res, Err(MlsError::DuplicateLeafData(_)));
2110 }
2111
2112 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
sending_add_with_hpke_key_of_another_member_filters_it_out()2113 async fn sending_add_with_hpke_key_of_another_member_filters_it_out() {
2114 let (alice, tree) = new_tree("alice").await;
2115
2116 let proposal = Proposal::Add(Box::new(AddProposal {
2117 key_package: key_package_with_public_key(
2118 tree.get_leaf_node(alice).unwrap().public_key.clone(),
2119 )
2120 .await,
2121 }));
2122
2123 let proposal_info = make_proposal_info(&proposal, alice).await;
2124
2125 let processed_proposals =
2126 CommitSender::new(&tree, alice, test_cipher_suite_provider(TEST_CIPHER_SUITE))
2127 .cache(
2128 proposal_info.proposal_ref().unwrap().clone(),
2129 proposal.clone(),
2130 alice,
2131 )
2132 .send()
2133 .await
2134 .unwrap();
2135
2136 assert_eq!(processed_proposals.0, Vec::new());
2137
2138 #[cfg(feature = "state_update")]
2139 assert_eq!(processed_proposals.1.unused_proposals, vec![proposal_info]);
2140 }
2141
2142 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
receiving_update_with_invalid_leaf_node_fails()2143 async fn receiving_update_with_invalid_leaf_node_fails() {
2144 let (alice, mut tree) = new_tree("alice").await;
2145 let bob = add_member(&mut tree, "bob").await;
2146
2147 let proposal = Proposal::Update(UpdateProposal {
2148 leaf_node: get_basic_test_node(TEST_CIPHER_SUITE, "alice").await,
2149 });
2150
2151 let proposal_ref = make_proposal_ref(&proposal, bob).await;
2152
2153 let res = CommitReceiver::new(
2154 &tree,
2155 alice,
2156 bob,
2157 test_cipher_suite_provider(TEST_CIPHER_SUITE),
2158 )
2159 .cache(proposal_ref.clone(), proposal, bob)
2160 .receive([proposal_ref])
2161 .await;
2162
2163 assert_matches!(res, Err(MlsError::InvalidLeafNodeSource));
2164 }
2165
2166 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
sending_update_with_invalid_leaf_node_filters_it_out()2167 async fn sending_update_with_invalid_leaf_node_filters_it_out() {
2168 let (alice, mut tree) = new_tree("alice").await;
2169 let bob = add_member(&mut tree, "bob").await;
2170
2171 let proposal = Proposal::Update(UpdateProposal {
2172 leaf_node: get_basic_test_node(TEST_CIPHER_SUITE, "alice").await,
2173 });
2174
2175 let proposal_info = make_proposal_info(&proposal, bob).await;
2176
2177 let processed_proposals =
2178 CommitSender::new(&tree, alice, test_cipher_suite_provider(TEST_CIPHER_SUITE))
2179 .cache(proposal_info.proposal_ref().unwrap().clone(), proposal, bob)
2180 .send()
2181 .await
2182 .unwrap();
2183
2184 assert_eq!(processed_proposals.0, Vec::new());
2185
2186 #[cfg(feature = "state_update")]
2187 assert_eq!(processed_proposals.1.unused_proposals, vec![proposal_info]);
2188 }
2189
2190 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
receiving_remove_with_invalid_index_fails()2191 async fn receiving_remove_with_invalid_index_fails() {
2192 let (alice, tree) = new_tree("alice").await;
2193
2194 let res = CommitReceiver::new(
2195 &tree,
2196 alice,
2197 alice,
2198 test_cipher_suite_provider(TEST_CIPHER_SUITE),
2199 )
2200 .receive([Proposal::Remove(RemoveProposal {
2201 to_remove: LeafIndex(10),
2202 })])
2203 .await;
2204
2205 assert_matches!(res, Err(MlsError::InvalidNodeIndex(20)));
2206 }
2207
2208 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
sending_additional_remove_with_invalid_index_fails()2209 async fn sending_additional_remove_with_invalid_index_fails() {
2210 let (alice, tree) = new_tree("alice").await;
2211
2212 let res = CommitSender::new(&tree, alice, test_cipher_suite_provider(TEST_CIPHER_SUITE))
2213 .with_additional([Proposal::Remove(RemoveProposal {
2214 to_remove: LeafIndex(10),
2215 })])
2216 .send()
2217 .await;
2218
2219 assert_matches!(res, Err(MlsError::InvalidNodeIndex(20)));
2220 }
2221
2222 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
sending_remove_with_invalid_index_filters_it_out()2223 async fn sending_remove_with_invalid_index_filters_it_out() {
2224 let (alice, tree) = new_tree("alice").await;
2225
2226 let proposal = Proposal::Remove(RemoveProposal {
2227 to_remove: LeafIndex(10),
2228 });
2229
2230 let proposal_info = make_proposal_info(&proposal, alice).await;
2231
2232 let processed_proposals =
2233 CommitSender::new(&tree, alice, test_cipher_suite_provider(TEST_CIPHER_SUITE))
2234 .cache(
2235 proposal_info.proposal_ref().unwrap().clone(),
2236 proposal.clone(),
2237 alice,
2238 )
2239 .send()
2240 .await
2241 .unwrap();
2242
2243 assert_eq!(processed_proposals.0, Vec::new());
2244
2245 #[cfg(feature = "state_update")]
2246 assert_eq!(processed_proposals.1.unused_proposals, vec![proposal_info]);
2247 }
2248
2249 #[cfg(feature = "psk")]
make_external_psk(id: &[u8], nonce: PskNonce) -> PreSharedKeyProposal2250 fn make_external_psk(id: &[u8], nonce: PskNonce) -> PreSharedKeyProposal {
2251 PreSharedKeyProposal {
2252 psk: PreSharedKeyID {
2253 key_id: JustPreSharedKeyID::External(ExternalPskId::new(id.to_vec())),
2254 psk_nonce: nonce,
2255 },
2256 }
2257 }
2258
2259 #[cfg(feature = "psk")]
new_external_psk(id: &[u8]) -> PreSharedKeyProposal2260 fn new_external_psk(id: &[u8]) -> PreSharedKeyProposal {
2261 make_external_psk(
2262 id,
2263 PskNonce::random(&test_cipher_suite_provider(TEST_CIPHER_SUITE)).unwrap(),
2264 )
2265 }
2266
2267 #[cfg(feature = "psk")]
2268 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
receiving_psk_with_invalid_nonce_fails()2269 async fn receiving_psk_with_invalid_nonce_fails() {
2270 let invalid_nonce = PskNonce(vec![0, 1, 2]);
2271 let (alice, tree) = new_tree("alice").await;
2272
2273 let res = CommitReceiver::new(
2274 &tree,
2275 alice,
2276 alice,
2277 test_cipher_suite_provider(TEST_CIPHER_SUITE),
2278 )
2279 .receive([Proposal::Psk(make_external_psk(
2280 b"foo",
2281 invalid_nonce.clone(),
2282 ))])
2283 .await;
2284
2285 assert_matches!(res, Err(MlsError::InvalidPskNonceLength,));
2286 }
2287
2288 #[cfg(feature = "psk")]
2289 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
sending_additional_psk_with_invalid_nonce_fails()2290 async fn sending_additional_psk_with_invalid_nonce_fails() {
2291 let invalid_nonce = PskNonce(vec![0, 1, 2]);
2292 let (alice, tree) = new_tree("alice").await;
2293
2294 let res = CommitSender::new(&tree, alice, test_cipher_suite_provider(TEST_CIPHER_SUITE))
2295 .with_additional([Proposal::Psk(make_external_psk(
2296 b"foo",
2297 invalid_nonce.clone(),
2298 ))])
2299 .send()
2300 .await;
2301
2302 assert_matches!(res, Err(MlsError::InvalidPskNonceLength));
2303 }
2304
2305 #[cfg(feature = "psk")]
2306 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
sending_psk_with_invalid_nonce_filters_it_out()2307 async fn sending_psk_with_invalid_nonce_filters_it_out() {
2308 let invalid_nonce = PskNonce(vec![0, 1, 2]);
2309 let (alice, tree) = new_tree("alice").await;
2310 let proposal = Proposal::Psk(make_external_psk(b"foo", invalid_nonce));
2311
2312 let proposal_info = make_proposal_info(&proposal, alice).await;
2313
2314 let processed_proposals =
2315 CommitSender::new(&tree, alice, test_cipher_suite_provider(TEST_CIPHER_SUITE))
2316 .cache(
2317 proposal_info.proposal_ref().unwrap().clone(),
2318 proposal.clone(),
2319 alice,
2320 )
2321 .send()
2322 .await
2323 .unwrap();
2324
2325 assert_eq!(processed_proposals.0, Vec::new());
2326
2327 #[cfg(feature = "state_update")]
2328 assert_eq!(processed_proposals.1.unused_proposals, vec![proposal_info]);
2329 }
2330
2331 #[cfg(feature = "psk")]
make_resumption_psk(usage: ResumptionPSKUsage) -> PreSharedKeyProposal2332 fn make_resumption_psk(usage: ResumptionPSKUsage) -> PreSharedKeyProposal {
2333 PreSharedKeyProposal {
2334 psk: PreSharedKeyID {
2335 key_id: JustPreSharedKeyID::Resumption(ResumptionPsk {
2336 usage,
2337 psk_group_id: PskGroupId(TEST_GROUP.to_vec()),
2338 psk_epoch: 1,
2339 }),
2340 psk_nonce: PskNonce::random(&test_cipher_suite_provider(TEST_CIPHER_SUITE))
2341 .unwrap(),
2342 },
2343 }
2344 }
2345
2346 #[cfg(feature = "psk")]
2347 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
receiving_resumption_psk_with_bad_usage_fails(usage: ResumptionPSKUsage)2348 async fn receiving_resumption_psk_with_bad_usage_fails(usage: ResumptionPSKUsage) {
2349 let (alice, tree) = new_tree("alice").await;
2350
2351 let res = CommitReceiver::new(
2352 &tree,
2353 alice,
2354 alice,
2355 test_cipher_suite_provider(TEST_CIPHER_SUITE),
2356 )
2357 .receive([Proposal::Psk(make_resumption_psk(usage))])
2358 .await;
2359
2360 assert_matches!(res, Err(MlsError::InvalidTypeOrUsageInPreSharedKeyProposal));
2361 }
2362
2363 #[cfg(feature = "psk")]
2364 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
sending_additional_resumption_psk_with_bad_usage_fails(usage: ResumptionPSKUsage)2365 async fn sending_additional_resumption_psk_with_bad_usage_fails(usage: ResumptionPSKUsage) {
2366 let (alice, tree) = new_tree("alice").await;
2367
2368 let res = CommitSender::new(&tree, alice, test_cipher_suite_provider(TEST_CIPHER_SUITE))
2369 .with_additional([Proposal::Psk(make_resumption_psk(usage))])
2370 .send()
2371 .await;
2372
2373 assert_matches!(res, Err(MlsError::InvalidTypeOrUsageInPreSharedKeyProposal));
2374 }
2375
2376 #[cfg(feature = "psk")]
2377 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
sending_resumption_psk_with_bad_usage_filters_it_out(usage: ResumptionPSKUsage)2378 async fn sending_resumption_psk_with_bad_usage_filters_it_out(usage: ResumptionPSKUsage) {
2379 let (alice, tree) = new_tree("alice").await;
2380 let proposal = Proposal::Psk(make_resumption_psk(usage));
2381 let proposal_info = make_proposal_info(&proposal, alice).await;
2382
2383 let processed_proposals =
2384 CommitSender::new(&tree, alice, test_cipher_suite_provider(TEST_CIPHER_SUITE))
2385 .cache(
2386 proposal_info.proposal_ref().unwrap().clone(),
2387 proposal.clone(),
2388 alice,
2389 )
2390 .send()
2391 .await
2392 .unwrap();
2393
2394 assert_eq!(processed_proposals.0, Vec::new());
2395
2396 #[cfg(feature = "state_update")]
2397 assert_eq!(processed_proposals.1.unused_proposals, vec![proposal_info]);
2398 }
2399
2400 #[cfg(feature = "psk")]
2401 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
receiving_resumption_psk_with_reinit_usage_fails()2402 async fn receiving_resumption_psk_with_reinit_usage_fails() {
2403 receiving_resumption_psk_with_bad_usage_fails(ResumptionPSKUsage::Reinit).await;
2404 }
2405
2406 #[cfg(feature = "psk")]
2407 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
sending_additional_resumption_psk_with_reinit_usage_fails()2408 async fn sending_additional_resumption_psk_with_reinit_usage_fails() {
2409 sending_additional_resumption_psk_with_bad_usage_fails(ResumptionPSKUsage::Reinit).await;
2410 }
2411
2412 #[cfg(feature = "psk")]
2413 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
sending_resumption_psk_with_reinit_usage_filters_it_out()2414 async fn sending_resumption_psk_with_reinit_usage_filters_it_out() {
2415 sending_resumption_psk_with_bad_usage_filters_it_out(ResumptionPSKUsage::Reinit).await;
2416 }
2417
2418 #[cfg(feature = "psk")]
2419 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
receiving_resumption_psk_with_branch_usage_fails()2420 async fn receiving_resumption_psk_with_branch_usage_fails() {
2421 receiving_resumption_psk_with_bad_usage_fails(ResumptionPSKUsage::Branch).await;
2422 }
2423
2424 #[cfg(feature = "psk")]
2425 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
sending_additional_resumption_psk_with_branch_usage_fails()2426 async fn sending_additional_resumption_psk_with_branch_usage_fails() {
2427 sending_additional_resumption_psk_with_bad_usage_fails(ResumptionPSKUsage::Branch).await;
2428 }
2429
2430 #[cfg(feature = "psk")]
2431 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
sending_resumption_psk_with_branch_usage_filters_it_out()2432 async fn sending_resumption_psk_with_branch_usage_filters_it_out() {
2433 sending_resumption_psk_with_bad_usage_filters_it_out(ResumptionPSKUsage::Branch).await;
2434 }
2435
make_reinit(version: ProtocolVersion) -> ReInitProposal2436 fn make_reinit(version: ProtocolVersion) -> ReInitProposal {
2437 ReInitProposal {
2438 group_id: TEST_GROUP.to_vec(),
2439 version,
2440 cipher_suite: TEST_CIPHER_SUITE,
2441 extensions: ExtensionList::new(),
2442 }
2443 }
2444
2445 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
receiving_reinit_downgrading_version_fails()2446 async fn receiving_reinit_downgrading_version_fails() {
2447 let smaller_protocol_version = ProtocolVersion::from(0);
2448 let (alice, tree) = new_tree("alice").await;
2449
2450 let res = CommitReceiver::new(
2451 &tree,
2452 alice,
2453 alice,
2454 test_cipher_suite_provider(TEST_CIPHER_SUITE),
2455 )
2456 .receive([Proposal::ReInit(make_reinit(smaller_protocol_version))])
2457 .await;
2458
2459 assert_matches!(res, Err(MlsError::InvalidProtocolVersionInReInit));
2460 }
2461
2462 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
sending_additional_reinit_downgrading_version_fails()2463 async fn sending_additional_reinit_downgrading_version_fails() {
2464 let smaller_protocol_version = ProtocolVersion::from(0);
2465 let (alice, tree) = new_tree("alice").await;
2466
2467 let res = CommitSender::new(&tree, alice, test_cipher_suite_provider(TEST_CIPHER_SUITE))
2468 .with_additional([Proposal::ReInit(make_reinit(smaller_protocol_version))])
2469 .send()
2470 .await;
2471
2472 assert_matches!(res, Err(MlsError::InvalidProtocolVersionInReInit));
2473 }
2474
2475 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
sending_reinit_downgrading_version_filters_it_out()2476 async fn sending_reinit_downgrading_version_filters_it_out() {
2477 let smaller_protocol_version = ProtocolVersion::from(0);
2478 let (alice, tree) = new_tree("alice").await;
2479 let proposal = Proposal::ReInit(make_reinit(smaller_protocol_version));
2480 let proposal_info = make_proposal_info(&proposal, alice).await;
2481
2482 let processed_proposals =
2483 CommitSender::new(&tree, alice, test_cipher_suite_provider(TEST_CIPHER_SUITE))
2484 .cache(
2485 proposal_info.proposal_ref().unwrap().clone(),
2486 proposal.clone(),
2487 alice,
2488 )
2489 .send()
2490 .await
2491 .unwrap();
2492
2493 assert_eq!(processed_proposals.0, Vec::new());
2494
2495 #[cfg(feature = "state_update")]
2496 assert_eq!(processed_proposals.1.unused_proposals, vec![proposal_info]);
2497 }
2498
2499 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
receiving_update_for_committer_fails()2500 async fn receiving_update_for_committer_fails() {
2501 let (alice, tree) = new_tree("alice").await;
2502 let update = Proposal::Update(make_update_proposal("alice").await);
2503 let update_ref = make_proposal_ref(&update, alice).await;
2504
2505 let res = CommitReceiver::new(
2506 &tree,
2507 alice,
2508 alice,
2509 test_cipher_suite_provider(TEST_CIPHER_SUITE),
2510 )
2511 .cache(update_ref.clone(), update, alice)
2512 .receive([update_ref])
2513 .await;
2514
2515 assert_matches!(res, Err(MlsError::InvalidCommitSelfUpdate));
2516 }
2517
2518 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
sending_additional_update_for_committer_fails()2519 async fn sending_additional_update_for_committer_fails() {
2520 let (alice, tree) = new_tree("alice").await;
2521
2522 let res = CommitSender::new(&tree, alice, test_cipher_suite_provider(TEST_CIPHER_SUITE))
2523 .with_additional([Proposal::Update(make_update_proposal("alice").await)])
2524 .send()
2525 .await;
2526
2527 assert_matches!(res, Err(MlsError::InvalidProposalTypeForSender));
2528 }
2529
2530 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
sending_update_for_committer_filters_it_out()2531 async fn sending_update_for_committer_filters_it_out() {
2532 let (alice, tree) = new_tree("alice").await;
2533 let proposal = Proposal::Update(make_update_proposal("alice").await);
2534 let proposal_info = make_proposal_info(&proposal, alice).await;
2535
2536 let processed_proposals =
2537 CommitSender::new(&tree, alice, test_cipher_suite_provider(TEST_CIPHER_SUITE))
2538 .cache(
2539 proposal_info.proposal_ref().unwrap().clone(),
2540 proposal.clone(),
2541 alice,
2542 )
2543 .send()
2544 .await
2545 .unwrap();
2546
2547 assert_eq!(processed_proposals.0, Vec::new());
2548
2549 #[cfg(feature = "state_update")]
2550 assert_eq!(processed_proposals.1.unused_proposals, vec![proposal_info]);
2551 }
2552
2553 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
receiving_remove_for_committer_fails()2554 async fn receiving_remove_for_committer_fails() {
2555 let (alice, tree) = new_tree("alice").await;
2556
2557 let res = CommitReceiver::new(
2558 &tree,
2559 alice,
2560 alice,
2561 test_cipher_suite_provider(TEST_CIPHER_SUITE),
2562 )
2563 .receive([Proposal::Remove(RemoveProposal { to_remove: alice })])
2564 .await;
2565
2566 assert_matches!(res, Err(MlsError::CommitterSelfRemoval));
2567 }
2568
2569 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
sending_additional_remove_for_committer_fails()2570 async fn sending_additional_remove_for_committer_fails() {
2571 let (alice, tree) = new_tree("alice").await;
2572
2573 let res = CommitSender::new(&tree, alice, test_cipher_suite_provider(TEST_CIPHER_SUITE))
2574 .with_additional([Proposal::Remove(RemoveProposal { to_remove: alice })])
2575 .send()
2576 .await;
2577
2578 assert_matches!(res, Err(MlsError::CommitterSelfRemoval));
2579 }
2580
2581 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
sending_remove_for_committer_filters_it_out()2582 async fn sending_remove_for_committer_filters_it_out() {
2583 let (alice, tree) = new_tree("alice").await;
2584 let proposal = Proposal::Remove(RemoveProposal { to_remove: alice });
2585 let proposal_info = make_proposal_info(&proposal, alice).await;
2586
2587 let processed_proposals =
2588 CommitSender::new(&tree, alice, test_cipher_suite_provider(TEST_CIPHER_SUITE))
2589 .cache(
2590 proposal_info.proposal_ref().unwrap().clone(),
2591 proposal.clone(),
2592 alice,
2593 )
2594 .send()
2595 .await
2596 .unwrap();
2597
2598 assert_eq!(processed_proposals.0, Vec::new());
2599
2600 #[cfg(feature = "state_update")]
2601 assert_eq!(processed_proposals.1.unused_proposals, vec![proposal_info]);
2602 }
2603
2604 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
receiving_update_and_remove_for_same_leaf_fails()2605 async fn receiving_update_and_remove_for_same_leaf_fails() {
2606 let (alice, mut tree) = new_tree("alice").await;
2607 let bob = add_member(&mut tree, "bob").await;
2608
2609 let update = Proposal::Update(make_update_proposal("bob").await);
2610 let update_ref = make_proposal_ref(&update, bob).await;
2611
2612 let remove = Proposal::Remove(RemoveProposal { to_remove: bob });
2613 let remove_ref = make_proposal_ref(&remove, bob).await;
2614
2615 let res = CommitReceiver::new(
2616 &tree,
2617 alice,
2618 alice,
2619 test_cipher_suite_provider(TEST_CIPHER_SUITE),
2620 )
2621 .cache(update_ref.clone(), update, bob)
2622 .cache(remove_ref.clone(), remove, bob)
2623 .receive([update_ref, remove_ref])
2624 .await;
2625
2626 assert_matches!(res, Err(MlsError::UpdatingNonExistingMember));
2627 }
2628
2629 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
sending_update_and_remove_for_same_leaf_filters_update_out()2630 async fn sending_update_and_remove_for_same_leaf_filters_update_out() {
2631 let (alice, mut tree) = new_tree("alice").await;
2632 let bob = add_member(&mut tree, "bob").await;
2633
2634 let update = Proposal::Update(make_update_proposal("bob").await);
2635 let update_info = make_proposal_info(&update, alice).await;
2636
2637 let remove = Proposal::Remove(RemoveProposal { to_remove: bob });
2638 let remove_ref = make_proposal_ref(&remove, alice).await;
2639
2640 let processed_proposals =
2641 CommitSender::new(&tree, alice, test_cipher_suite_provider(TEST_CIPHER_SUITE))
2642 .cache(
2643 update_info.proposal_ref().unwrap().clone(),
2644 update.clone(),
2645 alice,
2646 )
2647 .cache(remove_ref.clone(), remove, alice)
2648 .send()
2649 .await
2650 .unwrap();
2651
2652 assert_eq!(processed_proposals.0, vec![remove_ref.into()]);
2653
2654 #[cfg(feature = "state_update")]
2655 assert_eq!(processed_proposals.1.unused_proposals, vec![update_info]);
2656 }
2657
2658 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
make_add_proposal() -> Box<AddProposal>2659 async fn make_add_proposal() -> Box<AddProposal> {
2660 Box::new(AddProposal {
2661 key_package: test_key_package(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE, "frank").await,
2662 })
2663 }
2664
2665 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
receiving_add_proposals_for_same_client_fails()2666 async fn receiving_add_proposals_for_same_client_fails() {
2667 let (alice, tree) = new_tree("alice").await;
2668
2669 let res = CommitReceiver::new(
2670 &tree,
2671 alice,
2672 alice,
2673 test_cipher_suite_provider(TEST_CIPHER_SUITE),
2674 )
2675 .receive([
2676 Proposal::Add(make_add_proposal().await),
2677 Proposal::Add(make_add_proposal().await),
2678 ])
2679 .await;
2680
2681 assert_matches!(res, Err(MlsError::DuplicateLeafData(1)));
2682 }
2683
2684 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
sending_additional_add_proposals_for_same_client_fails()2685 async fn sending_additional_add_proposals_for_same_client_fails() {
2686 let (alice, tree) = new_tree("alice").await;
2687
2688 let res = CommitSender::new(&tree, alice, test_cipher_suite_provider(TEST_CIPHER_SUITE))
2689 .with_additional([
2690 Proposal::Add(make_add_proposal().await),
2691 Proposal::Add(make_add_proposal().await),
2692 ])
2693 .send()
2694 .await;
2695
2696 assert_matches!(res, Err(MlsError::DuplicateLeafData(1)));
2697 }
2698
2699 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
sending_add_proposals_for_same_client_keeps_only_one()2700 async fn sending_add_proposals_for_same_client_keeps_only_one() {
2701 let (alice, tree) = new_tree("alice").await;
2702
2703 let add_one = Proposal::Add(make_add_proposal().await);
2704 let add_two = Proposal::Add(make_add_proposal().await);
2705 let add_ref_one = make_proposal_ref(&add_one, alice).await;
2706 let add_ref_two = make_proposal_ref(&add_two, alice).await;
2707
2708 let processed_proposals =
2709 CommitSender::new(&tree, alice, test_cipher_suite_provider(TEST_CIPHER_SUITE))
2710 .cache(add_ref_one.clone(), add_one.clone(), alice)
2711 .cache(add_ref_two.clone(), add_two.clone(), alice)
2712 .send()
2713 .await
2714 .unwrap();
2715
2716 let committed_add_ref = match &*processed_proposals.0 {
2717 [ProposalOrRef::Reference(add_ref)] => add_ref,
2718 _ => panic!("committed proposals list does not contain exactly one reference"),
2719 };
2720
2721 let add_refs = [add_ref_one, add_ref_two];
2722 assert!(add_refs.contains(committed_add_ref));
2723
2724 #[cfg(feature = "state_update")]
2725 assert_matches!(
2726 &*processed_proposals.1.unused_proposals,
2727 [rejected_add_info] if committed_add_ref != rejected_add_info.proposal_ref().unwrap() && add_refs.contains(rejected_add_info.proposal_ref().unwrap())
2728 );
2729 }
2730
2731 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
receiving_update_for_different_identity_fails()2732 async fn receiving_update_for_different_identity_fails() {
2733 let (alice, mut tree) = new_tree("alice").await;
2734 let bob = add_member(&mut tree, "bob").await;
2735
2736 let update = Proposal::Update(make_update_proposal_custom("carol", 1).await);
2737 let update_ref = make_proposal_ref(&update, bob).await;
2738
2739 let res = CommitReceiver::new(
2740 &tree,
2741 alice,
2742 alice,
2743 test_cipher_suite_provider(TEST_CIPHER_SUITE),
2744 )
2745 .cache(update_ref.clone(), update, bob)
2746 .receive([update_ref])
2747 .await;
2748
2749 assert_matches!(res, Err(MlsError::InvalidSuccessor));
2750 }
2751
2752 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
sending_update_for_different_identity_filters_it_out()2753 async fn sending_update_for_different_identity_filters_it_out() {
2754 let (alice, mut tree) = new_tree("alice").await;
2755 let bob = add_member(&mut tree, "bob").await;
2756
2757 let update = Proposal::Update(make_update_proposal("carol").await);
2758 let update_info = make_proposal_info(&update, bob).await;
2759
2760 let processed_proposals =
2761 CommitSender::new(&tree, alice, test_cipher_suite_provider(TEST_CIPHER_SUITE))
2762 .cache(update_info.proposal_ref().unwrap().clone(), update, bob)
2763 .send()
2764 .await
2765 .unwrap();
2766
2767 assert_eq!(processed_proposals.0, Vec::new());
2768
2769 // Bob proposed the update, so it is not listed as rejected when Alice commits it because
2770 // she didn't propose it.
2771 #[cfg(feature = "state_update")]
2772 assert_eq!(processed_proposals.1.unused_proposals, vec![update_info]);
2773 }
2774
2775 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
receiving_add_for_same_client_as_existing_member_fails()2776 async fn receiving_add_for_same_client_as_existing_member_fails() {
2777 let (alice, public_tree) = new_tree("alice").await;
2778 let add = Proposal::Add(make_add_proposal().await);
2779
2780 let ProvisionalState { public_tree, .. } = CommitReceiver::new(
2781 &public_tree,
2782 alice,
2783 alice,
2784 test_cipher_suite_provider(TEST_CIPHER_SUITE),
2785 )
2786 .receive([add.clone()])
2787 .await
2788 .unwrap();
2789
2790 let res = CommitReceiver::new(
2791 &public_tree,
2792 alice,
2793 alice,
2794 test_cipher_suite_provider(TEST_CIPHER_SUITE),
2795 )
2796 .receive([add])
2797 .await;
2798
2799 assert_matches!(res, Err(MlsError::DuplicateLeafData(1)));
2800 }
2801
2802 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
sending_additional_add_for_same_client_as_existing_member_fails()2803 async fn sending_additional_add_for_same_client_as_existing_member_fails() {
2804 let (alice, public_tree) = new_tree("alice").await;
2805 let add = Proposal::Add(make_add_proposal().await);
2806
2807 let ProvisionalState { public_tree, .. } = CommitReceiver::new(
2808 &public_tree,
2809 alice,
2810 alice,
2811 test_cipher_suite_provider(TEST_CIPHER_SUITE),
2812 )
2813 .receive([add.clone()])
2814 .await
2815 .unwrap();
2816
2817 let res = CommitSender::new(
2818 &public_tree,
2819 alice,
2820 test_cipher_suite_provider(TEST_CIPHER_SUITE),
2821 )
2822 .with_additional([add])
2823 .send()
2824 .await;
2825
2826 assert_matches!(res, Err(MlsError::DuplicateLeafData(1)));
2827 }
2828
2829 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
sending_add_for_same_client_as_existing_member_filters_it_out()2830 async fn sending_add_for_same_client_as_existing_member_filters_it_out() {
2831 let (alice, public_tree) = new_tree("alice").await;
2832 let add = Proposal::Add(make_add_proposal().await);
2833
2834 let ProvisionalState { public_tree, .. } = CommitReceiver::new(
2835 &public_tree,
2836 alice,
2837 alice,
2838 test_cipher_suite_provider(TEST_CIPHER_SUITE),
2839 )
2840 .receive([add.clone()])
2841 .await
2842 .unwrap();
2843
2844 let proposal_info = make_proposal_info(&add, alice).await;
2845
2846 let processed_proposals = CommitSender::new(
2847 &public_tree,
2848 alice,
2849 test_cipher_suite_provider(TEST_CIPHER_SUITE),
2850 )
2851 .cache(
2852 proposal_info.proposal_ref().unwrap().clone(),
2853 add.clone(),
2854 alice,
2855 )
2856 .send()
2857 .await
2858 .unwrap();
2859
2860 assert_eq!(processed_proposals.0, Vec::new());
2861
2862 #[cfg(feature = "state_update")]
2863 assert_eq!(processed_proposals.1.unused_proposals, vec![proposal_info]);
2864 }
2865
2866 #[cfg(feature = "psk")]
2867 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
receiving_psk_proposals_with_same_psk_id_fails()2868 async fn receiving_psk_proposals_with_same_psk_id_fails() {
2869 let (alice, tree) = new_tree("alice").await;
2870 let psk_proposal = Proposal::Psk(new_external_psk(b"foo"));
2871
2872 let res = CommitReceiver::new(
2873 &tree,
2874 alice,
2875 alice,
2876 test_cipher_suite_provider(TEST_CIPHER_SUITE),
2877 )
2878 .receive([psk_proposal.clone(), psk_proposal])
2879 .await;
2880
2881 assert_matches!(res, Err(MlsError::DuplicatePskIds));
2882 }
2883
2884 #[cfg(feature = "psk")]
2885 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
sending_additional_psk_proposals_with_same_psk_id_fails()2886 async fn sending_additional_psk_proposals_with_same_psk_id_fails() {
2887 let (alice, tree) = new_tree("alice").await;
2888 let psk_proposal = Proposal::Psk(new_external_psk(b"foo"));
2889
2890 let res = CommitSender::new(&tree, alice, test_cipher_suite_provider(TEST_CIPHER_SUITE))
2891 .with_additional([psk_proposal.clone(), psk_proposal])
2892 .send()
2893 .await;
2894
2895 assert_matches!(res, Err(MlsError::DuplicatePskIds));
2896 }
2897
2898 #[cfg(feature = "psk")]
2899 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
sending_psk_proposals_with_same_psk_id_keeps_only_one()2900 async fn sending_psk_proposals_with_same_psk_id_keeps_only_one() {
2901 let (alice, mut tree) = new_tree("alice").await;
2902 let bob = add_member(&mut tree, "bob").await;
2903
2904 let proposal = Proposal::Psk(new_external_psk(b"foo"));
2905
2906 let proposal_info = [
2907 make_proposal_info(&proposal, alice).await,
2908 make_proposal_info(&proposal, bob).await,
2909 ];
2910
2911 let processed_proposals =
2912 CommitSender::new(&tree, alice, test_cipher_suite_provider(TEST_CIPHER_SUITE))
2913 .cache(
2914 proposal_info[0].proposal_ref().unwrap().clone(),
2915 proposal.clone(),
2916 alice,
2917 )
2918 .cache(
2919 proposal_info[1].proposal_ref().unwrap().clone(),
2920 proposal,
2921 bob,
2922 )
2923 .send()
2924 .await
2925 .unwrap();
2926
2927 let committed_info = match processed_proposals
2928 .1
2929 .applied_proposals
2930 .clone()
2931 .into_proposals()
2932 .collect_vec()
2933 .as_slice()
2934 {
2935 [r] => r.clone(),
2936 _ => panic!("Expected single proposal reference in {processed_proposals:?}"),
2937 };
2938
2939 assert!(proposal_info.contains(&committed_info));
2940
2941 #[cfg(feature = "state_update")]
2942 match &*processed_proposals.1.unused_proposals {
2943 [r] => {
2944 assert_ne!(*r, committed_info);
2945 assert!(proposal_info.contains(r));
2946 }
2947 _ => panic!(
2948 "Expected one proposal reference in {:?}",
2949 processed_proposals.1.unused_proposals
2950 ),
2951 }
2952 }
2953
2954 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
receiving_multiple_group_context_extensions_fails()2955 async fn receiving_multiple_group_context_extensions_fails() {
2956 let (alice, tree) = new_tree("alice").await;
2957
2958 let res = CommitReceiver::new(
2959 &tree,
2960 alice,
2961 alice,
2962 test_cipher_suite_provider(TEST_CIPHER_SUITE),
2963 )
2964 .receive([
2965 Proposal::GroupContextExtensions(ExtensionList::new()),
2966 Proposal::GroupContextExtensions(ExtensionList::new()),
2967 ])
2968 .await;
2969
2970 assert_matches!(
2971 res,
2972 Err(MlsError::MoreThanOneGroupContextExtensionsProposal)
2973 );
2974 }
2975
2976 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
sending_multiple_additional_group_context_extensions_fails()2977 async fn sending_multiple_additional_group_context_extensions_fails() {
2978 let (alice, tree) = new_tree("alice").await;
2979
2980 let res = CommitSender::new(&tree, alice, test_cipher_suite_provider(TEST_CIPHER_SUITE))
2981 .with_additional([
2982 Proposal::GroupContextExtensions(ExtensionList::new()),
2983 Proposal::GroupContextExtensions(ExtensionList::new()),
2984 ])
2985 .send()
2986 .await;
2987
2988 assert_matches!(
2989 res,
2990 Err(MlsError::MoreThanOneGroupContextExtensionsProposal)
2991 );
2992 }
2993
make_extension_list(foo: u8) -> ExtensionList2994 fn make_extension_list(foo: u8) -> ExtensionList {
2995 vec![TestExtension { foo }.into_extension().unwrap()].into()
2996 }
2997
2998 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
sending_multiple_group_context_extensions_keeps_only_one()2999 async fn sending_multiple_group_context_extensions_keeps_only_one() {
3000 let cipher_suite_provider = test_cipher_suite_provider(TEST_CIPHER_SUITE);
3001
3002 let (alice, tree) = {
3003 let (signing_identity, signature_key) =
3004 get_test_signing_identity(TEST_CIPHER_SUITE, b"alice").await;
3005
3006 let properties = ConfigProperties {
3007 capabilities: Capabilities {
3008 extensions: vec![42.into()],
3009 ..Capabilities::default()
3010 },
3011 extensions: Default::default(),
3012 };
3013
3014 let (leaf, secret) = LeafNode::generate(
3015 &cipher_suite_provider,
3016 properties,
3017 signing_identity,
3018 &signature_key,
3019 Lifetime::years(1).unwrap(),
3020 )
3021 .await
3022 .unwrap();
3023
3024 let (pub_tree, priv_tree) =
3025 TreeKemPublic::derive(leaf, secret, &BasicIdentityProvider, &Default::default())
3026 .await
3027 .unwrap();
3028
3029 (priv_tree.self_index, pub_tree)
3030 };
3031
3032 let proposals = [
3033 Proposal::GroupContextExtensions(make_extension_list(0)),
3034 Proposal::GroupContextExtensions(make_extension_list(1)),
3035 ];
3036
3037 let gce_info = [
3038 make_proposal_info(&proposals[0], alice).await,
3039 make_proposal_info(&proposals[1], alice).await,
3040 ];
3041
3042 let processed_proposals =
3043 CommitSender::new(&tree, alice, test_cipher_suite_provider(TEST_CIPHER_SUITE))
3044 .cache(
3045 gce_info[0].proposal_ref().unwrap().clone(),
3046 proposals[0].clone(),
3047 alice,
3048 )
3049 .cache(
3050 gce_info[1].proposal_ref().unwrap().clone(),
3051 proposals[1].clone(),
3052 alice,
3053 )
3054 .send()
3055 .await
3056 .unwrap();
3057
3058 let committed_gce_info = match processed_proposals
3059 .1
3060 .applied_proposals
3061 .clone()
3062 .into_proposals()
3063 .collect_vec()
3064 .as_slice()
3065 {
3066 [gce_info] => gce_info.clone(),
3067 _ => panic!("committed proposals list does not contain exactly one reference"),
3068 };
3069
3070 assert!(gce_info.contains(&committed_gce_info));
3071
3072 #[cfg(feature = "state_update")]
3073 assert_matches!(
3074 &*processed_proposals.1.unused_proposals,
3075 [rejected_gce_info] if committed_gce_info != *rejected_gce_info && gce_info.contains(rejected_gce_info)
3076 );
3077 }
3078
3079 #[cfg(feature = "by_ref_proposal")]
3080 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
make_external_senders_extension() -> ExtensionList3081 async fn make_external_senders_extension() -> ExtensionList {
3082 let identity = get_test_signing_identity(TEST_CIPHER_SUITE, b"alice")
3083 .await
3084 .0;
3085
3086 vec![ExternalSendersExt::new(vec![identity])
3087 .into_extension()
3088 .unwrap()]
3089 .into()
3090 }
3091
3092 #[cfg(feature = "by_ref_proposal")]
3093 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
receiving_invalid_external_senders_extension_fails()3094 async fn receiving_invalid_external_senders_extension_fails() {
3095 let (alice, tree) = new_tree("alice").await;
3096
3097 let res = CommitReceiver::new(
3098 &tree,
3099 alice,
3100 alice,
3101 test_cipher_suite_provider(TEST_CIPHER_SUITE),
3102 )
3103 .with_identity_provider(FailureIdentityProvider::new())
3104 .receive([Proposal::GroupContextExtensions(
3105 make_external_senders_extension().await,
3106 )])
3107 .await;
3108
3109 assert_matches!(res, Err(MlsError::IdentityProviderError(_)));
3110 }
3111
3112 #[cfg(feature = "by_ref_proposal")]
3113 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
sending_additional_invalid_external_senders_extension_fails()3114 async fn sending_additional_invalid_external_senders_extension_fails() {
3115 let (alice, tree) = new_tree("alice").await;
3116
3117 let res = CommitSender::new(&tree, alice, test_cipher_suite_provider(TEST_CIPHER_SUITE))
3118 .with_identity_provider(FailureIdentityProvider::new())
3119 .with_additional([Proposal::GroupContextExtensions(
3120 make_external_senders_extension().await,
3121 )])
3122 .send()
3123 .await;
3124
3125 assert_matches!(res, Err(MlsError::IdentityProviderError(_)));
3126 }
3127
3128 #[cfg(feature = "by_ref_proposal")]
3129 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
sending_invalid_external_senders_extension_filters_it_out()3130 async fn sending_invalid_external_senders_extension_filters_it_out() {
3131 let (alice, tree) = new_tree("alice").await;
3132
3133 let proposal = Proposal::GroupContextExtensions(make_external_senders_extension().await);
3134
3135 let proposal_info = make_proposal_info(&proposal, alice).await;
3136
3137 let processed_proposals =
3138 CommitSender::new(&tree, alice, test_cipher_suite_provider(TEST_CIPHER_SUITE))
3139 .with_identity_provider(FailureIdentityProvider::new())
3140 .cache(
3141 proposal_info.proposal_ref().unwrap().clone(),
3142 proposal.clone(),
3143 alice,
3144 )
3145 .send()
3146 .await
3147 .unwrap();
3148
3149 assert_eq!(processed_proposals.0, Vec::new());
3150
3151 #[cfg(feature = "state_update")]
3152 assert_eq!(processed_proposals.1.unused_proposals, vec![proposal_info]);
3153 }
3154
3155 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
receiving_reinit_with_other_proposals_fails()3156 async fn receiving_reinit_with_other_proposals_fails() {
3157 let (alice, tree) = new_tree("alice").await;
3158
3159 let res = CommitReceiver::new(
3160 &tree,
3161 alice,
3162 alice,
3163 test_cipher_suite_provider(TEST_CIPHER_SUITE),
3164 )
3165 .receive([
3166 Proposal::ReInit(make_reinit(TEST_PROTOCOL_VERSION)),
3167 Proposal::Add(make_add_proposal().await),
3168 ])
3169 .await;
3170
3171 assert_matches!(res, Err(MlsError::OtherProposalWithReInit));
3172 }
3173
3174 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
sending_additional_reinit_with_other_proposals_fails()3175 async fn sending_additional_reinit_with_other_proposals_fails() {
3176 let (alice, tree) = new_tree("alice").await;
3177
3178 let res = CommitSender::new(&tree, alice, test_cipher_suite_provider(TEST_CIPHER_SUITE))
3179 .with_additional([
3180 Proposal::ReInit(make_reinit(TEST_PROTOCOL_VERSION)),
3181 Proposal::Add(make_add_proposal().await),
3182 ])
3183 .send()
3184 .await;
3185
3186 assert_matches!(res, Err(MlsError::OtherProposalWithReInit));
3187 }
3188
3189 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
sending_reinit_with_other_proposals_filters_it_out()3190 async fn sending_reinit_with_other_proposals_filters_it_out() {
3191 let (alice, tree) = new_tree("alice").await;
3192 let reinit = Proposal::ReInit(make_reinit(TEST_PROTOCOL_VERSION));
3193 let reinit_info = make_proposal_info(&reinit, alice).await;
3194 let add = Proposal::Add(make_add_proposal().await);
3195 let add_ref = make_proposal_ref(&add, alice).await;
3196
3197 let processed_proposals =
3198 CommitSender::new(&tree, alice, test_cipher_suite_provider(TEST_CIPHER_SUITE))
3199 .cache(
3200 reinit_info.proposal_ref().unwrap().clone(),
3201 reinit.clone(),
3202 alice,
3203 )
3204 .cache(add_ref.clone(), add, alice)
3205 .send()
3206 .await
3207 .unwrap();
3208
3209 assert_eq!(processed_proposals.0, vec![add_ref.into()]);
3210
3211 #[cfg(feature = "state_update")]
3212 assert_eq!(processed_proposals.1.unused_proposals, vec![reinit_info]);
3213 }
3214
3215 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
receiving_multiple_reinits_fails()3216 async fn receiving_multiple_reinits_fails() {
3217 let (alice, tree) = new_tree("alice").await;
3218
3219 let res = CommitReceiver::new(
3220 &tree,
3221 alice,
3222 alice,
3223 test_cipher_suite_provider(TEST_CIPHER_SUITE),
3224 )
3225 .receive([
3226 Proposal::ReInit(make_reinit(TEST_PROTOCOL_VERSION)),
3227 Proposal::ReInit(make_reinit(TEST_PROTOCOL_VERSION)),
3228 ])
3229 .await;
3230
3231 assert_matches!(res, Err(MlsError::OtherProposalWithReInit));
3232 }
3233
3234 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
sending_additional_multiple_reinits_fails()3235 async fn sending_additional_multiple_reinits_fails() {
3236 let (alice, tree) = new_tree("alice").await;
3237
3238 let res = CommitSender::new(&tree, alice, test_cipher_suite_provider(TEST_CIPHER_SUITE))
3239 .with_additional([
3240 Proposal::ReInit(make_reinit(TEST_PROTOCOL_VERSION)),
3241 Proposal::ReInit(make_reinit(TEST_PROTOCOL_VERSION)),
3242 ])
3243 .send()
3244 .await;
3245
3246 assert_matches!(res, Err(MlsError::OtherProposalWithReInit));
3247 }
3248
3249 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
sending_multiple_reinits_keeps_only_one()3250 async fn sending_multiple_reinits_keeps_only_one() {
3251 let (alice, tree) = new_tree("alice").await;
3252 let reinit = Proposal::ReInit(make_reinit(TEST_PROTOCOL_VERSION));
3253 let reinit_ref = make_proposal_ref(&reinit, alice).await;
3254 let other_reinit = Proposal::ReInit(ReInitProposal {
3255 group_id: b"other_group".to_vec(),
3256 ..make_reinit(TEST_PROTOCOL_VERSION)
3257 });
3258 let other_reinit_ref = make_proposal_ref(&other_reinit, alice).await;
3259
3260 let processed_proposals =
3261 CommitSender::new(&tree, alice, test_cipher_suite_provider(TEST_CIPHER_SUITE))
3262 .cache(reinit_ref.clone(), reinit.clone(), alice)
3263 .cache(other_reinit_ref.clone(), other_reinit.clone(), alice)
3264 .send()
3265 .await
3266 .unwrap();
3267
3268 let processed_ref = match &*processed_proposals.0 {
3269 [ProposalOrRef::Reference(r)] => r,
3270 p => panic!("Expected single proposal reference but found {p:?}"),
3271 };
3272
3273 assert!(*processed_ref == reinit_ref || *processed_ref == other_reinit_ref);
3274
3275 #[cfg(feature = "state_update")]
3276 {
3277 let (rejected_ref, unused_proposal) = match &*processed_proposals.1.unused_proposals {
3278 [r] => (r.proposal_ref().unwrap().clone(), r.proposal.clone()),
3279 p => panic!("Expected single proposal but found {p:?}"),
3280 };
3281
3282 assert_ne!(rejected_ref, *processed_ref);
3283 assert!(rejected_ref == reinit_ref || rejected_ref == other_reinit_ref);
3284 assert!(unused_proposal == reinit || unused_proposal == other_reinit);
3285 }
3286 }
3287
make_external_init() -> ExternalInit3288 fn make_external_init() -> ExternalInit {
3289 ExternalInit {
3290 kem_output: vec![33; test_cipher_suite_provider(TEST_CIPHER_SUITE).kdf_extract_size()],
3291 }
3292 }
3293
3294 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
receiving_external_init_from_member_fails()3295 async fn receiving_external_init_from_member_fails() {
3296 let (alice, tree) = new_tree("alice").await;
3297
3298 let res = CommitReceiver::new(
3299 &tree,
3300 alice,
3301 alice,
3302 test_cipher_suite_provider(TEST_CIPHER_SUITE),
3303 )
3304 .receive([Proposal::ExternalInit(make_external_init())])
3305 .await;
3306
3307 assert_matches!(res, Err(MlsError::InvalidProposalTypeForSender));
3308 }
3309
3310 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
sending_additional_external_init_from_member_fails()3311 async fn sending_additional_external_init_from_member_fails() {
3312 let (alice, tree) = new_tree("alice").await;
3313
3314 let res = CommitSender::new(&tree, alice, test_cipher_suite_provider(TEST_CIPHER_SUITE))
3315 .with_additional([Proposal::ExternalInit(make_external_init())])
3316 .send()
3317 .await;
3318
3319 assert_matches!(res, Err(MlsError::InvalidProposalTypeForSender));
3320 }
3321
3322 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
sending_external_init_from_member_filters_it_out()3323 async fn sending_external_init_from_member_filters_it_out() {
3324 let (alice, tree) = new_tree("alice").await;
3325 let external_init = Proposal::ExternalInit(make_external_init());
3326 let external_init_info = make_proposal_info(&external_init, alice).await;
3327
3328 let processed_proposals =
3329 CommitSender::new(&tree, alice, test_cipher_suite_provider(TEST_CIPHER_SUITE))
3330 .cache(
3331 external_init_info.proposal_ref().unwrap().clone(),
3332 external_init.clone(),
3333 alice,
3334 )
3335 .send()
3336 .await
3337 .unwrap();
3338
3339 assert_eq!(processed_proposals.0, Vec::new());
3340
3341 #[cfg(feature = "state_update")]
3342 assert_eq!(
3343 processed_proposals.1.unused_proposals,
3344 vec![external_init_info]
3345 );
3346 }
3347
required_capabilities_proposal(extension: u16) -> Proposal3348 fn required_capabilities_proposal(extension: u16) -> Proposal {
3349 let required_capabilities = RequiredCapabilitiesExt {
3350 extensions: vec![extension.into()],
3351 ..Default::default()
3352 };
3353
3354 let ext = vec![required_capabilities.into_extension().unwrap()];
3355
3356 Proposal::GroupContextExtensions(ext.into())
3357 }
3358
3359 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
receiving_required_capabilities_not_supported_by_member_fails()3360 async fn receiving_required_capabilities_not_supported_by_member_fails() {
3361 let (alice, tree) = new_tree("alice").await;
3362
3363 let res = CommitReceiver::new(
3364 &tree,
3365 alice,
3366 alice,
3367 test_cipher_suite_provider(TEST_CIPHER_SUITE),
3368 )
3369 .receive([required_capabilities_proposal(33)])
3370 .await;
3371
3372 assert_matches!(
3373 res,
3374 Err(MlsError::RequiredExtensionNotFound(v)) if v == 33.into()
3375 );
3376 }
3377
3378 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
sending_required_capabilities_not_supported_by_member_fails()3379 async fn sending_required_capabilities_not_supported_by_member_fails() {
3380 let (alice, tree) = new_tree("alice").await;
3381
3382 let res = CommitSender::new(&tree, alice, test_cipher_suite_provider(TEST_CIPHER_SUITE))
3383 .with_additional([required_capabilities_proposal(33)])
3384 .send()
3385 .await;
3386
3387 assert_matches!(
3388 res,
3389 Err(MlsError::RequiredExtensionNotFound(v)) if v == 33.into()
3390 );
3391 }
3392
3393 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
sending_additional_required_capabilities_not_supported_by_member_filters_it_out()3394 async fn sending_additional_required_capabilities_not_supported_by_member_filters_it_out() {
3395 let (alice, tree) = new_tree("alice").await;
3396
3397 let proposal = required_capabilities_proposal(33);
3398 let proposal_info = make_proposal_info(&proposal, alice).await;
3399
3400 let processed_proposals =
3401 CommitSender::new(&tree, alice, test_cipher_suite_provider(TEST_CIPHER_SUITE))
3402 .cache(
3403 proposal_info.proposal_ref().unwrap().clone(),
3404 proposal.clone(),
3405 alice,
3406 )
3407 .send()
3408 .await
3409 .unwrap();
3410
3411 assert_eq!(processed_proposals.0, Vec::new());
3412
3413 #[cfg(feature = "state_update")]
3414 assert_eq!(processed_proposals.1.unused_proposals, vec![proposal_info]);
3415 }
3416
3417 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
committing_update_from_pk1_to_pk2_and_update_from_pk2_to_pk3_works()3418 async fn committing_update_from_pk1_to_pk2_and_update_from_pk2_to_pk3_works() {
3419 let (alice_leaf, alice_secret, alice_signer) =
3420 get_basic_test_node_sig_key(TEST_CIPHER_SUITE, "alice").await;
3421
3422 let (mut tree, priv_tree) = TreeKemPublic::derive(
3423 alice_leaf.clone(),
3424 alice_secret,
3425 &BasicIdentityProvider,
3426 &Default::default(),
3427 )
3428 .await
3429 .unwrap();
3430
3431 let alice = priv_tree.self_index;
3432
3433 let bob = add_member(&mut tree, "bob").await;
3434 let carol = add_member(&mut tree, "carol").await;
3435
3436 let bob_current_leaf = tree.get_leaf_node(bob).unwrap();
3437
3438 let mut alice_new_leaf = LeafNode {
3439 public_key: bob_current_leaf.public_key.clone(),
3440 leaf_node_source: LeafNodeSource::Update,
3441 ..alice_leaf
3442 };
3443
3444 alice_new_leaf
3445 .sign(
3446 &test_cipher_suite_provider(TEST_CIPHER_SUITE),
3447 &alice_signer,
3448 &(TEST_GROUP, 0).into(),
3449 )
3450 .await
3451 .unwrap();
3452
3453 let bob_new_leaf = update_leaf_node("bob", 1).await;
3454
3455 let pk1_to_pk2 = Proposal::Update(UpdateProposal {
3456 leaf_node: alice_new_leaf.clone(),
3457 });
3458
3459 let pk1_to_pk2_ref = make_proposal_ref(&pk1_to_pk2, alice).await;
3460
3461 let pk2_to_pk3 = Proposal::Update(UpdateProposal {
3462 leaf_node: bob_new_leaf.clone(),
3463 });
3464
3465 let pk2_to_pk3_ref = make_proposal_ref(&pk2_to_pk3, bob).await;
3466
3467 let effects = CommitReceiver::new(
3468 &tree,
3469 carol,
3470 carol,
3471 test_cipher_suite_provider(TEST_CIPHER_SUITE),
3472 )
3473 .cache(pk1_to_pk2_ref.clone(), pk1_to_pk2, alice)
3474 .cache(pk2_to_pk3_ref.clone(), pk2_to_pk3, bob)
3475 .receive([pk1_to_pk2_ref, pk2_to_pk3_ref])
3476 .await
3477 .unwrap();
3478
3479 assert_eq!(effects.applied_proposals.update_senders, vec![alice, bob]);
3480
3481 assert_eq!(
3482 effects
3483 .applied_proposals
3484 .updates
3485 .into_iter()
3486 .map(|p| p.proposal.leaf_node)
3487 .collect_vec(),
3488 vec![alice_new_leaf, bob_new_leaf]
3489 );
3490 }
3491
3492 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
committing_update_from_pk1_to_pk2_and_removal_of_pk2_works()3493 async fn committing_update_from_pk1_to_pk2_and_removal_of_pk2_works() {
3494 let cipher_suite_provider = test_cipher_suite_provider(TEST_CIPHER_SUITE);
3495
3496 let (alice_leaf, alice_secret, alice_signer) =
3497 get_basic_test_node_sig_key(TEST_CIPHER_SUITE, "alice").await;
3498
3499 let (mut tree, priv_tree) = TreeKemPublic::derive(
3500 alice_leaf.clone(),
3501 alice_secret,
3502 &BasicIdentityProvider,
3503 &Default::default(),
3504 )
3505 .await
3506 .unwrap();
3507
3508 let alice = priv_tree.self_index;
3509
3510 let bob = add_member(&mut tree, "bob").await;
3511 let carol = add_member(&mut tree, "carol").await;
3512
3513 let bob_current_leaf = tree.get_leaf_node(bob).unwrap();
3514
3515 let mut alice_new_leaf = LeafNode {
3516 public_key: bob_current_leaf.public_key.clone(),
3517 leaf_node_source: LeafNodeSource::Update,
3518 ..alice_leaf
3519 };
3520
3521 alice_new_leaf
3522 .sign(
3523 &cipher_suite_provider,
3524 &alice_signer,
3525 &(TEST_GROUP, 0).into(),
3526 )
3527 .await
3528 .unwrap();
3529
3530 let pk1_to_pk2 = Proposal::Update(UpdateProposal {
3531 leaf_node: alice_new_leaf.clone(),
3532 });
3533
3534 let pk1_to_pk2_ref = make_proposal_ref(&pk1_to_pk2, alice).await;
3535
3536 let remove_pk2 = Proposal::Remove(RemoveProposal { to_remove: bob });
3537
3538 let remove_pk2_ref = make_proposal_ref(&remove_pk2, bob).await;
3539
3540 let effects = CommitReceiver::new(
3541 &tree,
3542 carol,
3543 carol,
3544 test_cipher_suite_provider(TEST_CIPHER_SUITE),
3545 )
3546 .cache(pk1_to_pk2_ref.clone(), pk1_to_pk2, alice)
3547 .cache(remove_pk2_ref.clone(), remove_pk2, bob)
3548 .receive([pk1_to_pk2_ref, remove_pk2_ref])
3549 .await
3550 .unwrap();
3551
3552 assert_eq!(effects.applied_proposals.update_senders, vec![alice]);
3553
3554 assert_eq!(
3555 effects
3556 .applied_proposals
3557 .updates
3558 .into_iter()
3559 .map(|p| p.proposal.leaf_node)
3560 .collect_vec(),
3561 vec![alice_new_leaf]
3562 );
3563
3564 assert_eq!(
3565 effects
3566 .applied_proposals
3567 .removals
3568 .into_iter()
3569 .map(|p| p.proposal.to_remove)
3570 .collect_vec(),
3571 vec![bob]
3572 );
3573 }
3574
3575 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
unsupported_credential_key_package(name: &str) -> KeyPackage3576 async fn unsupported_credential_key_package(name: &str) -> KeyPackage {
3577 let (mut signing_identity, secret_key) =
3578 get_test_signing_identity(TEST_CIPHER_SUITE, name.as_bytes()).await;
3579
3580 signing_identity.credential = Credential::Custom(CustomCredential::new(
3581 CredentialType::new(BasicWithCustomProvider::CUSTOM_CREDENTIAL_TYPE),
3582 random_bytes(32),
3583 ));
3584
3585 let generator = KeyPackageGenerator {
3586 protocol_version: TEST_PROTOCOL_VERSION,
3587 cipher_suite_provider: &test_cipher_suite_provider(TEST_CIPHER_SUITE),
3588 signing_identity: &signing_identity,
3589 signing_key: &secret_key,
3590 identity_provider: &BasicWithCustomProvider::new(BasicIdentityProvider::new()),
3591 };
3592
3593 generator
3594 .generate(
3595 Lifetime::years(1).unwrap(),
3596 Capabilities {
3597 credentials: vec![42.into()],
3598 ..Default::default()
3599 },
3600 Default::default(),
3601 Default::default(),
3602 )
3603 .await
3604 .unwrap()
3605 .key_package
3606 }
3607
3608 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
receiving_add_with_leaf_not_supporting_credential_type_of_other_leaf_fails()3609 async fn receiving_add_with_leaf_not_supporting_credential_type_of_other_leaf_fails() {
3610 let (alice, tree) = new_tree("alice").await;
3611
3612 let res = CommitReceiver::new(
3613 &tree,
3614 alice,
3615 alice,
3616 test_cipher_suite_provider(TEST_CIPHER_SUITE),
3617 )
3618 .receive([Proposal::Add(Box::new(AddProposal {
3619 key_package: unsupported_credential_key_package("bob").await,
3620 }))])
3621 .await;
3622
3623 assert_matches!(res, Err(MlsError::InUseCredentialTypeUnsupportedByNewLeaf));
3624 }
3625
3626 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
sending_additional_add_with_leaf_not_supporting_credential_type_of_other_leaf_fails()3627 async fn sending_additional_add_with_leaf_not_supporting_credential_type_of_other_leaf_fails() {
3628 let (alice, tree) = new_tree("alice").await;
3629
3630 let res = CommitSender::new(&tree, alice, test_cipher_suite_provider(TEST_CIPHER_SUITE))
3631 .with_additional([Proposal::Add(Box::new(AddProposal {
3632 key_package: unsupported_credential_key_package("bob").await,
3633 }))])
3634 .send()
3635 .await;
3636
3637 assert_matches!(res, Err(MlsError::InUseCredentialTypeUnsupportedByNewLeaf));
3638 }
3639
3640 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
sending_add_with_leaf_not_supporting_credential_type_of_other_leaf_filters_it_out()3641 async fn sending_add_with_leaf_not_supporting_credential_type_of_other_leaf_filters_it_out() {
3642 let (alice, tree) = new_tree("alice").await;
3643
3644 let add = Proposal::Add(Box::new(AddProposal {
3645 key_package: unsupported_credential_key_package("bob").await,
3646 }));
3647
3648 let add_info = make_proposal_info(&add, alice).await;
3649
3650 let processed_proposals =
3651 CommitSender::new(&tree, alice, test_cipher_suite_provider(TEST_CIPHER_SUITE))
3652 .cache(add_info.proposal_ref().unwrap().clone(), add.clone(), alice)
3653 .send()
3654 .await
3655 .unwrap();
3656
3657 assert_eq!(processed_proposals.0, Vec::new());
3658
3659 #[cfg(feature = "state_update")]
3660 assert_eq!(processed_proposals.1.unused_proposals, vec![add_info]);
3661 }
3662
3663 #[cfg(feature = "custom_proposal")]
3664 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
sending_custom_proposal_with_member_not_supporting_proposal_type_fails()3665 async fn sending_custom_proposal_with_member_not_supporting_proposal_type_fails() {
3666 let (alice, tree) = new_tree("alice").await;
3667
3668 let custom_proposal = Proposal::Custom(CustomProposal::new(ProposalType::new(42), vec![]));
3669
3670 let res = CommitSender::new(&tree, alice, test_cipher_suite_provider(TEST_CIPHER_SUITE))
3671 .with_additional([custom_proposal.clone()])
3672 .send()
3673 .await;
3674
3675 assert_matches!(
3676 res,
3677 Err(
3678 MlsError::UnsupportedCustomProposal(c)
3679 ) if c == custom_proposal.proposal_type()
3680 );
3681 }
3682
3683 #[cfg(feature = "custom_proposal")]
3684 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
sending_custom_proposal_with_member_not_supporting_filters_it_out()3685 async fn sending_custom_proposal_with_member_not_supporting_filters_it_out() {
3686 let (alice, tree) = new_tree("alice").await;
3687
3688 let custom_proposal = Proposal::Custom(CustomProposal::new(ProposalType::new(42), vec![]));
3689
3690 let custom_info = make_proposal_info(&custom_proposal, alice).await;
3691
3692 let processed_proposals =
3693 CommitSender::new(&tree, alice, test_cipher_suite_provider(TEST_CIPHER_SUITE))
3694 .cache(
3695 custom_info.proposal_ref().unwrap().clone(),
3696 custom_proposal.clone(),
3697 alice,
3698 )
3699 .send()
3700 .await
3701 .unwrap();
3702
3703 assert_eq!(processed_proposals.0, Vec::new());
3704
3705 #[cfg(feature = "state_update")]
3706 assert_eq!(processed_proposals.1.unused_proposals, vec![custom_info]);
3707 }
3708
3709 #[cfg(feature = "custom_proposal")]
3710 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
receiving_custom_proposal_with_member_not_supporting_fails()3711 async fn receiving_custom_proposal_with_member_not_supporting_fails() {
3712 let (alice, tree) = new_tree("alice").await;
3713
3714 let custom_proposal = Proposal::Custom(CustomProposal::new(ProposalType::new(42), vec![]));
3715
3716 let res = CommitReceiver::new(
3717 &tree,
3718 alice,
3719 alice,
3720 test_cipher_suite_provider(TEST_CIPHER_SUITE),
3721 )
3722 .receive([custom_proposal.clone()])
3723 .await;
3724
3725 assert_matches!(
3726 res,
3727 Err(MlsError::UnsupportedCustomProposal(c)) if c == custom_proposal.proposal_type()
3728 );
3729 }
3730
3731 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
receiving_group_extension_unsupported_by_leaf_fails()3732 async fn receiving_group_extension_unsupported_by_leaf_fails() {
3733 let (alice, tree) = new_tree("alice").await;
3734
3735 let res = CommitReceiver::new(
3736 &tree,
3737 alice,
3738 alice,
3739 test_cipher_suite_provider(TEST_CIPHER_SUITE),
3740 )
3741 .receive([Proposal::GroupContextExtensions(make_extension_list(0))])
3742 .await;
3743
3744 assert_matches!(
3745 res,
3746 Err(
3747 MlsError::UnsupportedGroupExtension(v)
3748 ) if v == 42.into()
3749 );
3750 }
3751
3752 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
sending_additional_group_extension_unsupported_by_leaf_fails()3753 async fn sending_additional_group_extension_unsupported_by_leaf_fails() {
3754 let (alice, tree) = new_tree("alice").await;
3755
3756 let res = CommitSender::new(&tree, alice, test_cipher_suite_provider(TEST_CIPHER_SUITE))
3757 .with_additional([Proposal::GroupContextExtensions(make_extension_list(0))])
3758 .send()
3759 .await;
3760
3761 assert_matches!(
3762 res,
3763 Err(
3764 MlsError::UnsupportedGroupExtension(v)
3765 ) if v == 42.into()
3766 );
3767 }
3768
3769 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
sending_group_extension_unsupported_by_leaf_filters_it_out()3770 async fn sending_group_extension_unsupported_by_leaf_filters_it_out() {
3771 let (alice, tree) = new_tree("alice").await;
3772
3773 let proposal = Proposal::GroupContextExtensions(make_extension_list(0));
3774 let proposal_info = make_proposal_info(&proposal, alice).await;
3775
3776 let processed_proposals =
3777 CommitSender::new(&tree, alice, test_cipher_suite_provider(TEST_CIPHER_SUITE))
3778 .cache(
3779 proposal_info.proposal_ref().unwrap().clone(),
3780 proposal.clone(),
3781 alice,
3782 )
3783 .send()
3784 .await
3785 .unwrap();
3786
3787 assert_eq!(processed_proposals.0, Vec::new());
3788
3789 #[cfg(feature = "state_update")]
3790 assert_eq!(processed_proposals.1.unused_proposals, vec![proposal_info]);
3791 }
3792
3793 #[cfg(feature = "psk")]
3794 #[derive(Debug)]
3795 struct AlwaysNotFoundPskStorage;
3796
3797 #[cfg(feature = "psk")]
3798 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
3799 #[cfg_attr(mls_build_async, maybe_async::must_be_async)]
3800 impl PreSharedKeyStorage for AlwaysNotFoundPskStorage {
3801 type Error = Infallible;
3802
get(&self, _: &ExternalPskId) -> Result<Option<PreSharedKey>, Self::Error>3803 async fn get(&self, _: &ExternalPskId) -> Result<Option<PreSharedKey>, Self::Error> {
3804 Ok(None)
3805 }
3806 }
3807
3808 #[cfg(feature = "psk")]
3809 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
receiving_external_psk_with_unknown_id_fails()3810 async fn receiving_external_psk_with_unknown_id_fails() {
3811 let (alice, tree) = new_tree("alice").await;
3812
3813 let res = CommitReceiver::new(
3814 &tree,
3815 alice,
3816 alice,
3817 test_cipher_suite_provider(TEST_CIPHER_SUITE),
3818 )
3819 .with_psk_storage(AlwaysNotFoundPskStorage)
3820 .receive([Proposal::Psk(new_external_psk(b"abc"))])
3821 .await;
3822
3823 assert_matches!(res, Err(MlsError::MissingRequiredPsk));
3824 }
3825
3826 #[cfg(feature = "psk")]
3827 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
sending_additional_external_psk_with_unknown_id_fails()3828 async fn sending_additional_external_psk_with_unknown_id_fails() {
3829 let (alice, tree) = new_tree("alice").await;
3830
3831 let res = CommitSender::new(&tree, alice, test_cipher_suite_provider(TEST_CIPHER_SUITE))
3832 .with_psk_storage(AlwaysNotFoundPskStorage)
3833 .with_additional([Proposal::Psk(new_external_psk(b"abc"))])
3834 .send()
3835 .await;
3836
3837 assert_matches!(res, Err(MlsError::MissingRequiredPsk));
3838 }
3839
3840 #[cfg(feature = "psk")]
3841 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
sending_external_psk_with_unknown_id_filters_it_out()3842 async fn sending_external_psk_with_unknown_id_filters_it_out() {
3843 let (alice, tree) = new_tree("alice").await;
3844 let proposal = Proposal::Psk(new_external_psk(b"abc"));
3845 let proposal_info = make_proposal_info(&proposal, alice).await;
3846
3847 let processed_proposals =
3848 CommitSender::new(&tree, alice, test_cipher_suite_provider(TEST_CIPHER_SUITE))
3849 .with_psk_storage(AlwaysNotFoundPskStorage)
3850 .cache(
3851 proposal_info.proposal_ref().unwrap().clone(),
3852 proposal.clone(),
3853 alice,
3854 )
3855 .send()
3856 .await
3857 .unwrap();
3858
3859 assert_eq!(processed_proposals.0, Vec::new());
3860
3861 #[cfg(feature = "state_update")]
3862 assert_eq!(processed_proposals.1.unused_proposals, vec![proposal_info]);
3863 }
3864
3865 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
user_defined_filter_can_remove_proposals()3866 async fn user_defined_filter_can_remove_proposals() {
3867 struct RemoveGroupContextExtensions;
3868
3869 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
3870 #[cfg_attr(mls_build_async, maybe_async::must_be_async)]
3871 impl MlsRules for RemoveGroupContextExtensions {
3872 type Error = Infallible;
3873
3874 async fn filter_proposals(
3875 &self,
3876 _: CommitDirection,
3877 _: CommitSource,
3878 _: &Roster,
3879 _: &ExtensionList,
3880 mut proposals: ProposalBundle,
3881 ) -> Result<ProposalBundle, Self::Error> {
3882 proposals.group_context_extensions.clear();
3883 Ok(proposals)
3884 }
3885
3886 #[cfg_attr(coverage_nightly, coverage(off))]
3887 fn commit_options(
3888 &self,
3889 _: &Roster,
3890 _: &ExtensionList,
3891 _: &ProposalBundle,
3892 ) -> Result<CommitOptions, Self::Error> {
3893 Ok(Default::default())
3894 }
3895
3896 #[cfg_attr(coverage_nightly, coverage(off))]
3897 fn encryption_options(
3898 &self,
3899 _: &Roster,
3900 _: &ExtensionList,
3901 ) -> Result<EncryptionOptions, Self::Error> {
3902 Ok(Default::default())
3903 }
3904 }
3905
3906 let (alice, tree) = new_tree("alice").await;
3907
3908 let (committed, _) =
3909 CommitSender::new(&tree, alice, test_cipher_suite_provider(TEST_CIPHER_SUITE))
3910 .with_additional([Proposal::GroupContextExtensions(Default::default())])
3911 .with_user_rules(RemoveGroupContextExtensions)
3912 .send()
3913 .await
3914 .unwrap();
3915
3916 assert_eq!(committed, Vec::new());
3917 }
3918
3919 struct FailureMlsRules;
3920
3921 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
3922 #[cfg_attr(mls_build_async, maybe_async::must_be_async)]
3923 impl MlsRules for FailureMlsRules {
3924 type Error = MlsError;
3925
filter_proposals( &self, _: CommitDirection, _: CommitSource, _: &Roster, _: &ExtensionList, _: ProposalBundle, ) -> Result<ProposalBundle, Self::Error>3926 async fn filter_proposals(
3927 &self,
3928 _: CommitDirection,
3929 _: CommitSource,
3930 _: &Roster,
3931 _: &ExtensionList,
3932 _: ProposalBundle,
3933 ) -> Result<ProposalBundle, Self::Error> {
3934 Err(MlsError::InvalidSignature)
3935 }
3936
3937 #[cfg_attr(coverage_nightly, coverage(off))]
commit_options( &self, _: &Roster, _: &ExtensionList, _: &ProposalBundle, ) -> Result<CommitOptions, Self::Error>3938 fn commit_options(
3939 &self,
3940 _: &Roster,
3941 _: &ExtensionList,
3942 _: &ProposalBundle,
3943 ) -> Result<CommitOptions, Self::Error> {
3944 Ok(Default::default())
3945 }
3946
3947 #[cfg_attr(coverage_nightly, coverage(off))]
encryption_options( &self, _: &Roster, _: &ExtensionList, ) -> Result<EncryptionOptions, Self::Error>3948 fn encryption_options(
3949 &self,
3950 _: &Roster,
3951 _: &ExtensionList,
3952 ) -> Result<EncryptionOptions, Self::Error> {
3953 Ok(Default::default())
3954 }
3955 }
3956
3957 struct InjectMlsRules {
3958 to_inject: Proposal,
3959 source: ProposalSource,
3960 }
3961
3962 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
3963 #[cfg_attr(mls_build_async, maybe_async::must_be_async)]
3964 impl MlsRules for InjectMlsRules {
3965 type Error = MlsError;
3966
filter_proposals( &self, _: CommitDirection, _: CommitSource, _: &Roster, _: &ExtensionList, mut proposals: ProposalBundle, ) -> Result<ProposalBundle, Self::Error>3967 async fn filter_proposals(
3968 &self,
3969 _: CommitDirection,
3970 _: CommitSource,
3971 _: &Roster,
3972 _: &ExtensionList,
3973 mut proposals: ProposalBundle,
3974 ) -> Result<ProposalBundle, Self::Error> {
3975 proposals.add(
3976 self.to_inject.clone(),
3977 Sender::Member(0),
3978 self.source.clone(),
3979 );
3980 Ok(proposals)
3981 }
3982
3983 #[cfg_attr(coverage_nightly, coverage(off))]
commit_options( &self, _: &Roster, _: &ExtensionList, _: &ProposalBundle, ) -> Result<CommitOptions, Self::Error>3984 fn commit_options(
3985 &self,
3986 _: &Roster,
3987 _: &ExtensionList,
3988 _: &ProposalBundle,
3989 ) -> Result<CommitOptions, Self::Error> {
3990 Ok(Default::default())
3991 }
3992
3993 #[cfg_attr(coverage_nightly, coverage(off))]
encryption_options( &self, _: &Roster, _: &ExtensionList, ) -> Result<EncryptionOptions, Self::Error>3994 fn encryption_options(
3995 &self,
3996 _: &Roster,
3997 _: &ExtensionList,
3998 ) -> Result<EncryptionOptions, Self::Error> {
3999 Ok(Default::default())
4000 }
4001 }
4002
4003 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
user_defined_filter_can_inject_proposals()4004 async fn user_defined_filter_can_inject_proposals() {
4005 let (alice, tree) = new_tree("alice").await;
4006
4007 let test_proposal = Proposal::GroupContextExtensions(Default::default());
4008
4009 let (committed, _) =
4010 CommitSender::new(&tree, alice, test_cipher_suite_provider(TEST_CIPHER_SUITE))
4011 .with_user_rules(InjectMlsRules {
4012 to_inject: test_proposal.clone(),
4013 source: ProposalSource::ByValue,
4014 })
4015 .send()
4016 .await
4017 .unwrap();
4018
4019 assert_eq!(
4020 committed,
4021 vec![ProposalOrRef::Proposal(test_proposal.into())]
4022 );
4023 }
4024
4025 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
user_defined_filter_can_inject_local_only_proposals()4026 async fn user_defined_filter_can_inject_local_only_proposals() {
4027 let (alice, tree) = new_tree("alice").await;
4028
4029 let test_proposal = Proposal::GroupContextExtensions(Default::default());
4030
4031 let (committed, _) =
4032 CommitSender::new(&tree, alice, test_cipher_suite_provider(TEST_CIPHER_SUITE))
4033 .with_user_rules(InjectMlsRules {
4034 to_inject: test_proposal.clone(),
4035 source: ProposalSource::Local,
4036 })
4037 .send()
4038 .await
4039 .unwrap();
4040
4041 assert_eq!(committed, vec![]);
4042 }
4043
4044 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
user_defined_filter_cant_break_base_rules()4045 async fn user_defined_filter_cant_break_base_rules() {
4046 let (alice, tree) = new_tree("alice").await;
4047
4048 let test_proposal = Proposal::Update(UpdateProposal {
4049 leaf_node: get_basic_test_node(TEST_CIPHER_SUITE, "leaf").await,
4050 });
4051
4052 let res = CommitSender::new(&tree, alice, test_cipher_suite_provider(TEST_CIPHER_SUITE))
4053 .with_user_rules(InjectMlsRules {
4054 to_inject: test_proposal.clone(),
4055 source: ProposalSource::ByValue,
4056 })
4057 .send()
4058 .await;
4059
4060 assert_matches!(res, Err(MlsError::InvalidProposalTypeForSender { .. }))
4061 }
4062
4063 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
user_defined_filter_can_refuse_to_send_commit()4064 async fn user_defined_filter_can_refuse_to_send_commit() {
4065 let (alice, tree) = new_tree("alice").await;
4066
4067 let res = CommitSender::new(&tree, alice, test_cipher_suite_provider(TEST_CIPHER_SUITE))
4068 .with_additional([Proposal::GroupContextExtensions(Default::default())])
4069 .with_user_rules(FailureMlsRules)
4070 .send()
4071 .await;
4072
4073 assert_matches!(res, Err(MlsError::MlsRulesError(_)));
4074 }
4075
4076 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
user_defined_filter_can_reject_incoming_commit()4077 async fn user_defined_filter_can_reject_incoming_commit() {
4078 let (alice, tree) = new_tree("alice").await;
4079
4080 let res = CommitReceiver::new(
4081 &tree,
4082 alice,
4083 alice,
4084 test_cipher_suite_provider(TEST_CIPHER_SUITE),
4085 )
4086 .with_user_rules(FailureMlsRules)
4087 .receive([Proposal::GroupContextExtensions(Default::default())])
4088 .await;
4089
4090 assert_matches!(res, Err(MlsError::MlsRulesError(_)));
4091 }
4092
4093 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
proposers_are_verified()4094 async fn proposers_are_verified() {
4095 let (alice, mut tree) = new_tree("alice").await;
4096 let bob = add_member(&mut tree, "bob").await;
4097
4098 #[cfg(feature = "by_ref_proposal")]
4099 let identity = get_test_signing_identity(TEST_CIPHER_SUITE, b"carol")
4100 .await
4101 .0;
4102
4103 #[cfg(feature = "by_ref_proposal")]
4104 let external_senders = ExternalSendersExt::new(vec![identity]);
4105
4106 let proposals: &[Proposal] = &[
4107 Proposal::Add(make_add_proposal().await),
4108 Proposal::Update(make_update_proposal("alice").await),
4109 Proposal::Remove(RemoveProposal { to_remove: bob }),
4110 #[cfg(feature = "psk")]
4111 Proposal::Psk(make_external_psk(
4112 b"ted",
4113 PskNonce::random(&test_cipher_suite_provider(TEST_CIPHER_SUITE)).unwrap(),
4114 )),
4115 Proposal::ReInit(make_reinit(TEST_PROTOCOL_VERSION)),
4116 Proposal::ExternalInit(make_external_init()),
4117 Proposal::GroupContextExtensions(Default::default()),
4118 ];
4119
4120 let proposers = [
4121 Sender::Member(*alice),
4122 #[cfg(feature = "by_ref_proposal")]
4123 Sender::External(0),
4124 Sender::NewMemberCommit,
4125 Sender::NewMemberProposal,
4126 ];
4127
4128 for ((proposer, proposal), by_ref) in proposers
4129 .into_iter()
4130 .cartesian_product(proposals)
4131 .cartesian_product([true])
4132 {
4133 let committer = Sender::Member(*alice);
4134
4135 let receiver = CommitReceiver::new(
4136 &tree,
4137 committer,
4138 alice,
4139 test_cipher_suite_provider(TEST_CIPHER_SUITE),
4140 );
4141
4142 #[cfg(feature = "by_ref_proposal")]
4143 let extensions: ExtensionList =
4144 vec![external_senders.clone().into_extension().unwrap()].into();
4145
4146 #[cfg(feature = "by_ref_proposal")]
4147 let receiver = receiver.with_extensions(extensions);
4148
4149 let (receiver, proposals, proposer) = if by_ref {
4150 let proposal_ref = make_proposal_ref(proposal, proposer).await;
4151 let receiver = receiver.cache(proposal_ref.clone(), proposal.clone(), proposer);
4152 (receiver, vec![ProposalOrRef::from(proposal_ref)], proposer)
4153 } else {
4154 (receiver, vec![proposal.clone().into()], committer)
4155 };
4156
4157 let res = receiver.receive(proposals).await;
4158
4159 if proposer_can_propose(proposer, proposal.proposal_type(), by_ref).is_err() {
4160 assert_matches!(res, Err(MlsError::InvalidProposalTypeForSender));
4161 } else {
4162 let is_self_update = proposal.proposal_type() == ProposalType::UPDATE
4163 && by_ref
4164 && matches!(proposer, Sender::Member(_));
4165
4166 if !is_self_update {
4167 res.unwrap();
4168 }
4169 }
4170 }
4171 }
4172
4173 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
make_update_proposal(name: &str) -> UpdateProposal4174 async fn make_update_proposal(name: &str) -> UpdateProposal {
4175 UpdateProposal {
4176 leaf_node: update_leaf_node(name, 1).await,
4177 }
4178 }
4179
4180 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
make_update_proposal_custom(name: &str, leaf_index: u32) -> UpdateProposal4181 async fn make_update_proposal_custom(name: &str, leaf_index: u32) -> UpdateProposal {
4182 UpdateProposal {
4183 leaf_node: update_leaf_node(name, leaf_index).await,
4184 }
4185 }
4186
4187 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
when_receiving_commit_unused_proposals_are_proposals_in_cache_but_not_in_commit()4188 async fn when_receiving_commit_unused_proposals_are_proposals_in_cache_but_not_in_commit() {
4189 let (alice, tree) = new_tree("alice").await;
4190
4191 let proposal = Proposal::GroupContextExtensions(Default::default());
4192 let proposal_ref = make_proposal_ref(&proposal, alice).await;
4193
4194 let state = CommitReceiver::new(
4195 &tree,
4196 alice,
4197 alice,
4198 test_cipher_suite_provider(TEST_CIPHER_SUITE),
4199 )
4200 .cache(proposal_ref.clone(), proposal, alice)
4201 .receive([Proposal::Add(Box::new(AddProposal {
4202 key_package: test_key_package(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE, "bob").await,
4203 }))])
4204 .await
4205 .unwrap();
4206
4207 let [p] = &state.unused_proposals[..] else {
4208 panic!(
4209 "Expected single unused proposal but got {:?}",
4210 state.unused_proposals
4211 );
4212 };
4213
4214 assert_eq!(p.proposal_ref(), Some(&proposal_ref));
4215 }
4216 }
4217