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