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::boxed::Box;
6 use alloc::vec::Vec;
7 
8 #[cfg(feature = "custom_proposal")]
9 use itertools::Itertools;
10 
11 use crate::{
12     group::{
13         AddProposal, BorrowedProposal, Proposal, ProposalOrRef, ProposalType, ReInitProposal,
14         RemoveProposal, Sender,
15     },
16     ExtensionList,
17 };
18 
19 #[cfg(feature = "by_ref_proposal")]
20 use crate::group::{proposal_cache::CachedProposal, LeafIndex, ProposalRef, UpdateProposal};
21 
22 #[cfg(feature = "psk")]
23 use crate::group::PreSharedKeyProposal;
24 
25 #[cfg(feature = "custom_proposal")]
26 use crate::group::proposal::CustomProposal;
27 
28 use crate::group::ExternalInit;
29 
30 use core::iter::empty;
31 
32 #[derive(Clone, Debug, Default)]
33 #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
34 /// A collection of proposals.
35 pub struct ProposalBundle {
36     pub(crate) additions: Vec<ProposalInfo<AddProposal>>,
37     #[cfg(feature = "by_ref_proposal")]
38     pub(crate) updates: Vec<ProposalInfo<UpdateProposal>>,
39     #[cfg(feature = "by_ref_proposal")]
40     pub(crate) update_senders: Vec<LeafIndex>,
41     pub(crate) removals: Vec<ProposalInfo<RemoveProposal>>,
42     #[cfg(feature = "psk")]
43     pub(crate) psks: Vec<ProposalInfo<PreSharedKeyProposal>>,
44     pub(crate) reinitializations: Vec<ProposalInfo<ReInitProposal>>,
45     pub(crate) external_initializations: Vec<ProposalInfo<ExternalInit>>,
46     pub(crate) group_context_extensions: Vec<ProposalInfo<ExtensionList>>,
47     #[cfg(feature = "custom_proposal")]
48     pub(crate) custom_proposals: Vec<ProposalInfo<CustomProposal>>,
49 }
50 
51 impl ProposalBundle {
add(&mut self, proposal: Proposal, sender: Sender, source: ProposalSource)52     pub fn add(&mut self, proposal: Proposal, sender: Sender, source: ProposalSource) {
53         match proposal {
54             Proposal::Add(proposal) => self.additions.push(ProposalInfo {
55                 proposal: *proposal,
56                 sender,
57                 source,
58             }),
59             #[cfg(feature = "by_ref_proposal")]
60             Proposal::Update(proposal) => self.updates.push(ProposalInfo {
61                 proposal,
62                 sender,
63                 source,
64             }),
65             Proposal::Remove(proposal) => self.removals.push(ProposalInfo {
66                 proposal,
67                 sender,
68                 source,
69             }),
70             #[cfg(feature = "psk")]
71             Proposal::Psk(proposal) => self.psks.push(ProposalInfo {
72                 proposal,
73                 sender,
74                 source,
75             }),
76             Proposal::ReInit(proposal) => self.reinitializations.push(ProposalInfo {
77                 proposal,
78                 sender,
79                 source,
80             }),
81             Proposal::ExternalInit(proposal) => self.external_initializations.push(ProposalInfo {
82                 proposal,
83                 sender,
84                 source,
85             }),
86             Proposal::GroupContextExtensions(proposal) => {
87                 self.group_context_extensions.push(ProposalInfo {
88                     proposal,
89                     sender,
90                     source,
91                 })
92             }
93             #[cfg(feature = "custom_proposal")]
94             Proposal::Custom(proposal) => self.custom_proposals.push(ProposalInfo {
95                 proposal,
96                 sender,
97                 source,
98             }),
99         }
100     }
101 
102     /// Remove the proposal of type `T` at `index`
103     ///
104     /// Type `T` can be any of the standard MLS proposal types defined in the
105     /// [`proposal`](crate::group::proposal) module.
106     ///
107     /// `index` is consistent with the index returned by any of the proposal
108     /// type specific functions in this module.
remove<T: Proposable>(&mut self, index: usize)109     pub fn remove<T: Proposable>(&mut self, index: usize) {
110         T::remove(self, index);
111     }
112 
113     /// Iterate over proposals, filtered by type.
114     ///
115     /// Type `T` can be any of the standard MLS proposal types defined in the
116     /// [`proposal`](crate::group::proposal) module.
by_type<'a, T: Proposable + 'a>(&'a self) -> impl Iterator<Item = &'a ProposalInfo<T>>117     pub fn by_type<'a, T: Proposable + 'a>(&'a self) -> impl Iterator<Item = &'a ProposalInfo<T>> {
118         T::filter(self).iter()
119     }
120 
121     /// Retain proposals, filtered by type.
122     ///
123     /// Type `T` can be any of the standard MLS proposal types defined in the
124     /// [`proposal`](crate::group::proposal) module.
retain_by_type<T, F, E>(&mut self, mut f: F) -> Result<(), E> where T: Proposable, F: FnMut(&ProposalInfo<T>) -> Result<bool, E>,125     pub fn retain_by_type<T, F, E>(&mut self, mut f: F) -> Result<(), E>
126     where
127         T: Proposable,
128         F: FnMut(&ProposalInfo<T>) -> Result<bool, E>,
129     {
130         let mut res = Ok(());
131 
132         T::retain(self, |p| match f(p) {
133             Ok(keep) => keep,
134             Err(e) => {
135                 if res.is_ok() {
136                     res = Err(e);
137                 }
138                 false
139             }
140         });
141 
142         res
143     }
144 
145     /// Retain custom proposals in the bundle.
146     #[cfg(feature = "custom_proposal")]
retain_custom<F, E>(&mut self, mut f: F) -> Result<(), E> where F: FnMut(&ProposalInfo<CustomProposal>) -> Result<bool, E>,147     pub fn retain_custom<F, E>(&mut self, mut f: F) -> Result<(), E>
148     where
149         F: FnMut(&ProposalInfo<CustomProposal>) -> Result<bool, E>,
150     {
151         let mut res = Ok(());
152 
153         self.custom_proposals.retain(|p| match f(p) {
154             Ok(keep) => keep,
155             Err(e) => {
156                 if res.is_ok() {
157                     res = Err(e);
158                 }
159                 false
160             }
161         });
162 
163         res
164     }
165 
166     /// Retain MLS standard proposals in the bundle.
retain<F, E>(&mut self, mut f: F) -> Result<(), E> where F: FnMut(&ProposalInfo<BorrowedProposal<'_>>) -> Result<bool, E>,167     pub fn retain<F, E>(&mut self, mut f: F) -> Result<(), E>
168     where
169         F: FnMut(&ProposalInfo<BorrowedProposal<'_>>) -> Result<bool, E>,
170     {
171         self.retain_by_type::<AddProposal, _, _>(|proposal| {
172             f(&proposal.as_ref().map(BorrowedProposal::from))
173         })?;
174 
175         #[cfg(feature = "by_ref_proposal")]
176         self.retain_by_type::<UpdateProposal, _, _>(|proposal| {
177             f(&proposal.as_ref().map(BorrowedProposal::from))
178         })?;
179 
180         self.retain_by_type::<RemoveProposal, _, _>(|proposal| {
181             f(&proposal.as_ref().map(BorrowedProposal::from))
182         })?;
183 
184         #[cfg(feature = "psk")]
185         self.retain_by_type::<PreSharedKeyProposal, _, _>(|proposal| {
186             f(&proposal.as_ref().map(BorrowedProposal::from))
187         })?;
188 
189         self.retain_by_type::<ReInitProposal, _, _>(|proposal| {
190             f(&proposal.as_ref().map(BorrowedProposal::from))
191         })?;
192 
193         self.retain_by_type::<ExternalInit, _, _>(|proposal| {
194             f(&proposal.as_ref().map(BorrowedProposal::from))
195         })?;
196 
197         self.retain_by_type::<ExtensionList, _, _>(|proposal| {
198             f(&proposal.as_ref().map(BorrowedProposal::from))
199         })?;
200 
201         Ok(())
202     }
203 
204     /// The number of proposals in the bundle
length(&self) -> usize205     pub fn length(&self) -> usize {
206         let len = 0;
207 
208         #[cfg(feature = "psk")]
209         let len = len + self.psks.len();
210 
211         let len = len + self.external_initializations.len();
212 
213         #[cfg(feature = "custom_proposal")]
214         let len = len + self.custom_proposals.len();
215 
216         #[cfg(feature = "by_ref_proposal")]
217         let len = len + self.updates.len();
218 
219         len + self.additions.len()
220             + self.removals.len()
221             + self.reinitializations.len()
222             + self.group_context_extensions.len()
223     }
224 
225     /// Iterate over all proposals inside the bundle.
iter_proposals(&self) -> impl Iterator<Item = ProposalInfo<BorrowedProposal<'_>>>226     pub fn iter_proposals(&self) -> impl Iterator<Item = ProposalInfo<BorrowedProposal<'_>>> {
227         let res = self
228             .additions
229             .iter()
230             .map(|p| p.as_ref().map(BorrowedProposal::Add))
231             .chain(
232                 self.removals
233                     .iter()
234                     .map(|p| p.as_ref().map(BorrowedProposal::Remove)),
235             )
236             .chain(
237                 self.reinitializations
238                     .iter()
239                     .map(|p| p.as_ref().map(BorrowedProposal::ReInit)),
240             );
241 
242         #[cfg(feature = "by_ref_proposal")]
243         let res = res.chain(
244             self.updates
245                 .iter()
246                 .map(|p| p.as_ref().map(BorrowedProposal::Update)),
247         );
248 
249         #[cfg(feature = "psk")]
250         let res = res.chain(
251             self.psks
252                 .iter()
253                 .map(|p| p.as_ref().map(BorrowedProposal::Psk)),
254         );
255 
256         let res = res.chain(
257             self.external_initializations
258                 .iter()
259                 .map(|p| p.as_ref().map(BorrowedProposal::ExternalInit)),
260         );
261 
262         let res = res.chain(
263             self.group_context_extensions
264                 .iter()
265                 .map(|p| p.as_ref().map(BorrowedProposal::GroupContextExtensions)),
266         );
267 
268         #[cfg(feature = "custom_proposal")]
269         let res = res.chain(
270             self.custom_proposals
271                 .iter()
272                 .map(|p| p.as_ref().map(BorrowedProposal::Custom)),
273         );
274 
275         res
276     }
277 
278     /// Iterate over proposal in the bundle, consuming the bundle.
into_proposals(self) -> impl Iterator<Item = ProposalInfo<Proposal>>279     pub fn into_proposals(self) -> impl Iterator<Item = ProposalInfo<Proposal>> {
280         let res = empty();
281 
282         #[cfg(feature = "custom_proposal")]
283         let res = res.chain(
284             self.custom_proposals
285                 .into_iter()
286                 .map(|p| p.map(Proposal::Custom)),
287         );
288 
289         let res = res.chain(
290             self.external_initializations
291                 .into_iter()
292                 .map(|p| p.map(Proposal::ExternalInit)),
293         );
294 
295         #[cfg(feature = "psk")]
296         let res = res.chain(self.psks.into_iter().map(|p| p.map(Proposal::Psk)));
297 
298         #[cfg(feature = "by_ref_proposal")]
299         let res = res.chain(self.updates.into_iter().map(|p| p.map(Proposal::Update)));
300 
301         res.chain(
302             self.additions
303                 .into_iter()
304                 .map(|p| p.map(|p| Proposal::Add(alloc::boxed::Box::new(p)))),
305         )
306         .chain(self.removals.into_iter().map(|p| p.map(Proposal::Remove)))
307         .chain(
308             self.reinitializations
309                 .into_iter()
310                 .map(|p| p.map(Proposal::ReInit)),
311         )
312         .chain(
313             self.group_context_extensions
314                 .into_iter()
315                 .map(|p| p.map(Proposal::GroupContextExtensions)),
316         )
317     }
318 
into_proposals_or_refs(self) -> Vec<ProposalOrRef>319     pub(crate) fn into_proposals_or_refs(self) -> Vec<ProposalOrRef> {
320         self.into_proposals()
321             .filter_map(|p| match p.source {
322                 ProposalSource::ByValue => Some(ProposalOrRef::Proposal(Box::new(p.proposal))),
323                 #[cfg(feature = "by_ref_proposal")]
324                 ProposalSource::ByReference(reference) => Some(ProposalOrRef::Reference(reference)),
325                 _ => None,
326             })
327             .collect()
328     }
329 
330     /// Add proposals in the bundle.
add_proposals(&self) -> &[ProposalInfo<AddProposal>]331     pub fn add_proposals(&self) -> &[ProposalInfo<AddProposal>] {
332         &self.additions
333     }
334 
335     /// Update proposals in the bundle.
336     #[cfg(feature = "by_ref_proposal")]
update_proposals(&self) -> &[ProposalInfo<UpdateProposal>]337     pub fn update_proposals(&self) -> &[ProposalInfo<UpdateProposal>] {
338         &self.updates
339     }
340 
341     /// Senders of update proposals in the bundle.
342     #[cfg(feature = "by_ref_proposal")]
update_proposal_senders(&self) -> &[LeafIndex]343     pub fn update_proposal_senders(&self) -> &[LeafIndex] {
344         &self.update_senders
345     }
346 
347     /// Remove proposals in the bundle.
remove_proposals(&self) -> &[ProposalInfo<RemoveProposal>]348     pub fn remove_proposals(&self) -> &[ProposalInfo<RemoveProposal>] {
349         &self.removals
350     }
351 
352     /// Pre-shared key proposals in the bundle.
353     #[cfg(feature = "psk")]
psk_proposals(&self) -> &[ProposalInfo<PreSharedKeyProposal>]354     pub fn psk_proposals(&self) -> &[ProposalInfo<PreSharedKeyProposal>] {
355         &self.psks
356     }
357 
358     /// Reinit proposals in the bundle.
reinit_proposals(&self) -> &[ProposalInfo<ReInitProposal>]359     pub fn reinit_proposals(&self) -> &[ProposalInfo<ReInitProposal>] {
360         &self.reinitializations
361     }
362 
363     /// External init proposals in the bundle.
external_init_proposals(&self) -> &[ProposalInfo<ExternalInit>]364     pub fn external_init_proposals(&self) -> &[ProposalInfo<ExternalInit>] {
365         &self.external_initializations
366     }
367 
368     /// Group context extension proposals in the bundle.
group_context_ext_proposals(&self) -> &[ProposalInfo<ExtensionList>]369     pub fn group_context_ext_proposals(&self) -> &[ProposalInfo<ExtensionList>] {
370         &self.group_context_extensions
371     }
372 
373     /// Custom proposals in the bundle.
374     #[cfg(feature = "custom_proposal")]
custom_proposals(&self) -> &[ProposalInfo<CustomProposal>]375     pub fn custom_proposals(&self) -> &[ProposalInfo<CustomProposal>] {
376         &self.custom_proposals
377     }
378 
group_context_extensions_proposal(&self) -> Option<&ProposalInfo<ExtensionList>>379     pub(crate) fn group_context_extensions_proposal(&self) -> Option<&ProposalInfo<ExtensionList>> {
380         self.group_context_extensions.first()
381     }
382 
383     /// Custom proposal types that are in use within this bundle.
384     #[cfg(feature = "custom_proposal")]
custom_proposal_types(&self) -> impl Iterator<Item = ProposalType> + '_385     pub fn custom_proposal_types(&self) -> impl Iterator<Item = ProposalType> + '_ {
386         #[cfg(feature = "std")]
387         let res = self
388             .custom_proposals
389             .iter()
390             .map(|v| v.proposal.proposal_type())
391             .unique();
392 
393         #[cfg(not(feature = "std"))]
394         let res = self
395             .custom_proposals
396             .iter()
397             .map(|v| v.proposal.proposal_type())
398             .collect::<alloc::collections::BTreeSet<_>>()
399             .into_iter();
400 
401         res
402     }
403 
404     /// Standard proposal types that are in use within this bundle.
proposal_types(&self) -> impl Iterator<Item = ProposalType> + '_405     pub fn proposal_types(&self) -> impl Iterator<Item = ProposalType> + '_ {
406         let res = (!self.additions.is_empty())
407             .then_some(ProposalType::ADD)
408             .into_iter()
409             .chain((!self.removals.is_empty()).then_some(ProposalType::REMOVE))
410             .chain((!self.reinitializations.is_empty()).then_some(ProposalType::RE_INIT));
411 
412         #[cfg(feature = "by_ref_proposal")]
413         let res = res.chain((!self.updates.is_empty()).then_some(ProposalType::UPDATE));
414 
415         #[cfg(feature = "psk")]
416         let res = res.chain((!self.psks.is_empty()).then_some(ProposalType::PSK));
417 
418         let res = res.chain(
419             (!self.external_initializations.is_empty()).then_some(ProposalType::EXTERNAL_INIT),
420         );
421 
422         #[cfg(not(feature = "custom_proposal"))]
423         return res.chain(
424             (!self.group_context_extensions.is_empty())
425                 .then_some(ProposalType::GROUP_CONTEXT_EXTENSIONS),
426         );
427 
428         #[cfg(feature = "custom_proposal")]
429         return res
430             .chain(
431                 (!self.group_context_extensions.is_empty())
432                     .then_some(ProposalType::GROUP_CONTEXT_EXTENSIONS),
433             )
434             .chain(self.custom_proposal_types());
435     }
436 }
437 
438 impl FromIterator<(Proposal, Sender, ProposalSource)> for ProposalBundle {
from_iter<I>(iter: I) -> Self where I: IntoIterator<Item = (Proposal, Sender, ProposalSource)>,439     fn from_iter<I>(iter: I) -> Self
440     where
441         I: IntoIterator<Item = (Proposal, Sender, ProposalSource)>,
442     {
443         let mut bundle = ProposalBundle::default();
444         for (proposal, sender, source) in iter {
445             bundle.add(proposal, sender, source);
446         }
447         bundle
448     }
449 }
450 
451 #[cfg(feature = "by_ref_proposal")]
452 impl<'a> FromIterator<(&'a ProposalRef, &'a CachedProposal)> for ProposalBundle {
from_iter<I>(iter: I) -> Self where I: IntoIterator<Item = (&'a ProposalRef, &'a CachedProposal)>,453     fn from_iter<I>(iter: I) -> Self
454     where
455         I: IntoIterator<Item = (&'a ProposalRef, &'a CachedProposal)>,
456     {
457         iter.into_iter()
458             .map(|(r, p)| {
459                 (
460                     p.proposal.clone(),
461                     p.sender,
462                     ProposalSource::ByReference(r.clone()),
463                 )
464             })
465             .collect()
466     }
467 }
468 
469 #[cfg(feature = "by_ref_proposal")]
470 impl<'a> FromIterator<&'a (ProposalRef, CachedProposal)> for ProposalBundle {
from_iter<I>(iter: I) -> Self where I: IntoIterator<Item = &'a (ProposalRef, CachedProposal)>,471     fn from_iter<I>(iter: I) -> Self
472     where
473         I: IntoIterator<Item = &'a (ProposalRef, CachedProposal)>,
474     {
475         iter.into_iter().map(|pair| (&pair.0, &pair.1)).collect()
476     }
477 }
478 
479 #[cfg_attr(
480     all(feature = "ffi", not(test)),
481     safer_ffi_gen::ffi_type(clone, opaque)
482 )]
483 #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
484 #[derive(Clone, Debug, PartialEq)]
485 pub enum ProposalSource {
486     ByValue,
487     #[cfg(feature = "by_ref_proposal")]
488     ByReference(ProposalRef),
489     Local,
490 }
491 
492 #[cfg_attr(all(feature = "ffi", not(test)), safer_ffi_gen::ffi_type(opaque))]
493 #[derive(Clone, Debug, PartialEq)]
494 #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
495 #[non_exhaustive]
496 /// Proposal description used as input to a
497 /// [`MlsRules`](crate::MlsRules).
498 pub struct ProposalInfo<T> {
499     /// The underlying proposal value.
500     pub proposal: T,
501     /// The sender of this proposal.
502     pub sender: Sender,
503     /// The source of the proposal.
504     pub source: ProposalSource,
505 }
506 
507 #[cfg_attr(all(feature = "ffi", not(test)), ::safer_ffi_gen::safer_ffi_gen)]
508 impl<T> ProposalInfo<T> {
509     /// Create a new ProposalInfo.
510     ///
511     /// The resulting value will be either transmitted with a commit or
512     /// locally injected into a commit resolution depending on the
513     /// `can_transmit` flag.
514     ///
515     /// This function is useful when implementing custom
516     /// [`MlsRules`](crate::MlsRules).
517     #[cfg_attr(all(feature = "ffi", not(test)), safer_ffi_gen::safer_ffi_gen_ignore)]
new(proposal: T, sender: Sender, can_transmit: bool) -> Self518     pub fn new(proposal: T, sender: Sender, can_transmit: bool) -> Self {
519         let source = if can_transmit {
520             ProposalSource::ByValue
521         } else {
522             ProposalSource::Local
523         };
524 
525         ProposalInfo {
526             proposal,
527             sender,
528             source,
529         }
530     }
531 
532     #[cfg(all(feature = "ffi", not(test)))]
sender(&self) -> &Sender533     pub fn sender(&self) -> &Sender {
534         &self.sender
535     }
536 
537     #[cfg(all(feature = "ffi", not(test)))]
source(&self) -> &ProposalSource538     pub fn source(&self) -> &ProposalSource {
539         &self.source
540     }
541 
542     #[cfg_attr(all(feature = "ffi", not(test)), safer_ffi_gen::safer_ffi_gen_ignore)]
map<U, F>(self, f: F) -> ProposalInfo<U> where F: FnOnce(T) -> U,543     pub fn map<U, F>(self, f: F) -> ProposalInfo<U>
544     where
545         F: FnOnce(T) -> U,
546     {
547         ProposalInfo {
548             proposal: f(self.proposal),
549             sender: self.sender,
550             source: self.source,
551         }
552     }
553 
554     #[cfg_attr(all(feature = "ffi", not(test)), safer_ffi_gen::safer_ffi_gen_ignore)]
as_ref(&self) -> ProposalInfo<&T>555     pub fn as_ref(&self) -> ProposalInfo<&T> {
556         ProposalInfo {
557             proposal: &self.proposal,
558             sender: self.sender,
559             source: self.source.clone(),
560         }
561     }
562 
563     #[inline(always)]
is_by_value(&self) -> bool564     pub fn is_by_value(&self) -> bool {
565         self.source == ProposalSource::ByValue
566     }
567 
568     #[inline(always)]
is_by_reference(&self) -> bool569     pub fn is_by_reference(&self) -> bool {
570         !self.is_by_value()
571     }
572 
573     /// The [`ProposalRef`] of this proposal if its source is [`ProposalSource::ByReference`]
574     #[cfg(feature = "by_ref_proposal")]
proposal_ref(&self) -> Option<&ProposalRef>575     pub fn proposal_ref(&self) -> Option<&ProposalRef> {
576         match self.source {
577             ProposalSource::ByReference(ref reference) => Some(reference),
578             _ => None,
579         }
580     }
581 }
582 
583 #[cfg(all(feature = "ffi", not(test)))]
584 safer_ffi_gen::specialize!(ProposalInfoFfi = ProposalInfo<Proposal>);
585 
586 pub trait Proposable: Sized {
587     const TYPE: ProposalType;
588 
filter(bundle: &ProposalBundle) -> &[ProposalInfo<Self>]589     fn filter(bundle: &ProposalBundle) -> &[ProposalInfo<Self>];
remove(bundle: &mut ProposalBundle, index: usize)590     fn remove(bundle: &mut ProposalBundle, index: usize);
retain<F>(bundle: &mut ProposalBundle, keep: F) where F: FnMut(&ProposalInfo<Self>) -> bool591     fn retain<F>(bundle: &mut ProposalBundle, keep: F)
592     where
593         F: FnMut(&ProposalInfo<Self>) -> bool;
594 }
595 
596 macro_rules! impl_proposable {
597     ($ty:ty, $proposal_type:ident, $field:ident) => {
598         impl Proposable for $ty {
599             const TYPE: ProposalType = ProposalType::$proposal_type;
600 
601             fn filter(bundle: &ProposalBundle) -> &[ProposalInfo<Self>] {
602                 &bundle.$field
603             }
604 
605             fn remove(bundle: &mut ProposalBundle, index: usize) {
606                 if index < bundle.$field.len() {
607                     bundle.$field.remove(index);
608                 }
609             }
610 
611             fn retain<F>(bundle: &mut ProposalBundle, keep: F)
612             where
613                 F: FnMut(&ProposalInfo<Self>) -> bool,
614             {
615                 bundle.$field.retain(keep);
616             }
617         }
618     };
619 }
620 
621 impl_proposable!(AddProposal, ADD, additions);
622 #[cfg(feature = "by_ref_proposal")]
623 impl_proposable!(UpdateProposal, UPDATE, updates);
624 impl_proposable!(RemoveProposal, REMOVE, removals);
625 #[cfg(feature = "psk")]
626 impl_proposable!(PreSharedKeyProposal, PSK, psks);
627 impl_proposable!(ReInitProposal, RE_INIT, reinitializations);
628 impl_proposable!(ExternalInit, EXTERNAL_INIT, external_initializations);
629 impl_proposable!(
630     ExtensionList,
631     GROUP_CONTEXT_EXTENSIONS,
632     group_context_extensions
633 );
634