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