• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2 // Copyright by contributors to this project.
3 // SPDX-License-Identifier: (Apache-2.0 OR MIT)
4 
5 use alloc::{boxed::Box, vec::Vec};
6 
7 #[cfg(feature = "by_ref_proposal")]
8 use crate::tree_kem::leaf_node::LeafNode;
9 
10 use crate::{
11     client::MlsError, tree_kem::node::LeafIndex, CipherSuite, KeyPackage, MlsMessage,
12     ProtocolVersion,
13 };
14 use core::fmt::{self, Debug};
15 use mls_rs_codec::{MlsDecode, MlsEncode, MlsSize};
16 use mls_rs_core::{group::Capabilities, identity::SigningIdentity};
17 
18 #[cfg(feature = "by_ref_proposal")]
19 use crate::group::proposal_ref::ProposalRef;
20 
21 pub use mls_rs_core::extension::ExtensionList;
22 pub use mls_rs_core::group::ProposalType;
23 
24 #[cfg(feature = "psk")]
25 use crate::psk::{ExternalPskId, JustPreSharedKeyID, PreSharedKeyID};
26 
27 #[derive(Clone, Debug, PartialEq, MlsSize, MlsEncode, MlsDecode)]
28 #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
29 #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
30 /// A proposal that adds a member to a [`Group`](crate::group::Group).
31 pub struct AddProposal {
32     pub(crate) key_package: KeyPackage,
33 }
34 
35 impl AddProposal {
36     /// The [`KeyPackage`] used by this proposal to add
37     /// a [`Member`](mls_rs_core::group::Member) to the group.
key_package(&self) -> &KeyPackage38     pub fn key_package(&self) -> &KeyPackage {
39         &self.key_package
40     }
41 
42     /// The [`SigningIdentity`] of the [`Member`](mls_rs_core::group::Member)
43     /// that will be added by this proposal.
signing_identity(&self) -> &SigningIdentity44     pub fn signing_identity(&self) -> &SigningIdentity {
45         self.key_package.signing_identity()
46     }
47 
48     /// Client [`Capabilities`] of the [`Member`](mls_rs_core::group::Member)
49     /// that will be added by this proposal.
capabilities(&self) -> Capabilities50     pub fn capabilities(&self) -> Capabilities {
51         self.key_package.leaf_node.ungreased_capabilities()
52     }
53 
54     /// Key package extensions that are assoiciated with the
55     /// [`Member`](mls_rs_core::group::Member) that will be added by this proposal.
key_package_extensions(&self) -> ExtensionList56     pub fn key_package_extensions(&self) -> ExtensionList {
57         self.key_package.ungreased_extensions()
58     }
59 
60     /// Leaf node extensions that will be entered into the group state for the
61     /// [`Member`](mls_rs_core::group::Member) that will be added.
leaf_node_extensions(&self) -> ExtensionList62     pub fn leaf_node_extensions(&self) -> ExtensionList {
63         self.key_package.leaf_node.ungreased_extensions()
64     }
65 }
66 
67 impl From<KeyPackage> for AddProposal {
from(key_package: KeyPackage) -> Self68     fn from(key_package: KeyPackage) -> Self {
69         Self { key_package }
70     }
71 }
72 
73 impl TryFrom<MlsMessage> for AddProposal {
74     type Error = MlsError;
75 
try_from(value: MlsMessage) -> Result<Self, Self::Error>76     fn try_from(value: MlsMessage) -> Result<Self, Self::Error> {
77         value
78             .into_key_package()
79             .ok_or(MlsError::UnexpectedMessageType)
80             .map(Into::into)
81     }
82 }
83 
84 #[cfg(feature = "by_ref_proposal")]
85 #[derive(Clone, Debug, PartialEq, MlsSize, MlsEncode, MlsDecode)]
86 #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
87 #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
88 /// A proposal that will update an existing [`Member`](mls_rs_core::group::Member) of a
89 /// [`Group`](crate::group::Group).
90 pub struct UpdateProposal {
91     pub(crate) leaf_node: LeafNode,
92 }
93 
94 #[cfg(feature = "by_ref_proposal")]
95 impl UpdateProposal {
96     /// The new [`SigningIdentity`] of the [`Member`](mls_rs_core::group::Member)
97     /// that is being updated by this proposal.
signing_identity(&self) -> &SigningIdentity98     pub fn signing_identity(&self) -> &SigningIdentity {
99         &self.leaf_node.signing_identity
100     }
101 
102     /// New Client [`Capabilities`] of the [`Member`](mls_rs_core::group::Member)
103     /// that will be updated by this proposal.
capabilities(&self) -> Capabilities104     pub fn capabilities(&self) -> Capabilities {
105         self.leaf_node.ungreased_capabilities()
106     }
107 
108     /// New Leaf node extensions that will be entered into the group state for the
109     /// [`Member`](mls_rs_core::group::Member) that is being updated by this proposal.
leaf_node_extensions(&self) -> ExtensionList110     pub fn leaf_node_extensions(&self) -> ExtensionList {
111         self.leaf_node.ungreased_extensions()
112     }
113 }
114 
115 #[derive(Clone, Debug, PartialEq, Eq, MlsSize, MlsEncode, MlsDecode)]
116 #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
117 #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
118 /// A proposal to remove an existing [`Member`](mls_rs_core::group::Member) of a
119 /// [`Group`](crate::group::Group).
120 pub struct RemoveProposal {
121     pub(crate) to_remove: LeafIndex,
122 }
123 
124 impl RemoveProposal {
125     /// The index of the [`Member`](mls_rs_core::group::Member) that will be removed by
126     /// this proposal.
to_remove(&self) -> u32127     pub fn to_remove(&self) -> u32 {
128         *self.to_remove
129     }
130 }
131 
132 impl From<u32> for RemoveProposal {
from(value: u32) -> Self133     fn from(value: u32) -> Self {
134         RemoveProposal {
135             to_remove: LeafIndex(value),
136         }
137     }
138 }
139 
140 #[cfg(feature = "psk")]
141 #[derive(Clone, Debug, PartialEq, Eq, MlsSize, MlsEncode, MlsDecode)]
142 #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
143 #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
144 /// A proposal to add a pre-shared key to a group.
145 pub struct PreSharedKeyProposal {
146     pub(crate) psk: PreSharedKeyID,
147 }
148 
149 #[cfg(feature = "psk")]
150 impl PreSharedKeyProposal {
151     /// The external pre-shared key id of this proposal.
152     ///
153     /// MLS requires the pre-shared key type for PreSharedKeyProposal to be of
154     /// type `External`.
155     ///
156     /// Returns `None` in the condition that the underlying psk is not external.
external_psk_id(&self) -> Option<&ExternalPskId>157     pub fn external_psk_id(&self) -> Option<&ExternalPskId> {
158         match self.psk.key_id {
159             JustPreSharedKeyID::External(ref ext) => Some(ext),
160             JustPreSharedKeyID::Resumption(_) => None,
161         }
162     }
163 }
164 
165 #[derive(Clone, PartialEq, MlsSize, MlsEncode, MlsDecode)]
166 #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
167 #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
168 /// A proposal to reinitialize a group using new parameters.
169 pub struct ReInitProposal {
170     #[mls_codec(with = "mls_rs_codec::byte_vec")]
171     #[cfg_attr(feature = "serde", serde(with = "mls_rs_core::vec_serde"))]
172     pub(crate) group_id: Vec<u8>,
173     pub(crate) version: ProtocolVersion,
174     pub(crate) cipher_suite: CipherSuite,
175     pub(crate) extensions: ExtensionList,
176 }
177 
178 impl Debug for ReInitProposal {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result179     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
180         f.debug_struct("ReInitProposal")
181             .field(
182                 "group_id",
183                 &mls_rs_core::debug::pretty_group_id(&self.group_id),
184             )
185             .field("version", &self.version)
186             .field("cipher_suite", &self.cipher_suite)
187             .field("extensions", &self.extensions)
188             .finish()
189     }
190 }
191 
192 impl ReInitProposal {
193     /// The unique id of the new group post reinitialization.
group_id(&self) -> &[u8]194     pub fn group_id(&self) -> &[u8] {
195         &self.group_id
196     }
197 
198     /// The new protocol version to use post reinitialization.
new_version(&self) -> ProtocolVersion199     pub fn new_version(&self) -> ProtocolVersion {
200         self.version
201     }
202 
203     /// The new ciphersuite to use post reinitialization.
new_cipher_suite(&self) -> CipherSuite204     pub fn new_cipher_suite(&self) -> CipherSuite {
205         self.cipher_suite
206     }
207 
208     /// Group context extensions to set in the new group post reinitialization.
new_group_context_extensions(&self) -> &ExtensionList209     pub fn new_group_context_extensions(&self) -> &ExtensionList {
210         &self.extensions
211     }
212 }
213 
214 #[derive(Clone, PartialEq, Eq, MlsSize, MlsEncode, MlsDecode)]
215 #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
216 #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
217 /// A proposal used for external commits.
218 pub struct ExternalInit {
219     #[mls_codec(with = "mls_rs_codec::byte_vec")]
220     #[cfg_attr(feature = "serde", serde(with = "mls_rs_core::vec_serde"))]
221     pub(crate) kem_output: Vec<u8>,
222 }
223 
224 impl Debug for ExternalInit {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result225     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
226         f.debug_struct("ExternalInit")
227             .field(
228                 "kem_output",
229                 &mls_rs_core::debug::pretty_bytes(&self.kem_output),
230             )
231             .finish()
232     }
233 }
234 
235 #[cfg(feature = "custom_proposal")]
236 #[derive(Clone, PartialEq, Eq)]
237 #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
238 #[cfg_attr(
239     all(feature = "ffi", not(test)),
240     safer_ffi_gen::ffi_type(clone, opaque)
241 )]
242 #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
243 /// A user defined custom proposal.
244 ///
245 /// User defined proposals are passed through the protocol as an opaque value.
246 pub struct CustomProposal {
247     proposal_type: ProposalType,
248     #[cfg_attr(feature = "serde", serde(with = "mls_rs_core::vec_serde"))]
249     data: Vec<u8>,
250 }
251 
252 #[cfg(feature = "custom_proposal")]
253 impl Debug for CustomProposal {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result254     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
255         f.debug_struct("CustomProposal")
256             .field("proposal_type", &self.proposal_type)
257             .field("data", &mls_rs_core::debug::pretty_bytes(&self.data))
258             .finish()
259     }
260 }
261 
262 #[cfg(feature = "custom_proposal")]
263 #[cfg_attr(all(feature = "ffi", not(test)), safer_ffi_gen::safer_ffi_gen)]
264 impl CustomProposal {
265     /// Create a custom proposal.
266     ///
267     /// # Warning
268     ///
269     /// Avoid using the [`ProposalType`] values that have constants already
270     /// defined by this crate. Using existing constants in a custom proposal
271     /// has unspecified behavior.
new(proposal_type: ProposalType, data: Vec<u8>) -> Self272     pub fn new(proposal_type: ProposalType, data: Vec<u8>) -> Self {
273         Self {
274             proposal_type,
275             data,
276         }
277     }
278 
279     /// The proposal type used for this custom proposal.
proposal_type(&self) -> ProposalType280     pub fn proposal_type(&self) -> ProposalType {
281         self.proposal_type
282     }
283 
284     /// The opaque data communicated by this custom proposal.
data(&self) -> &[u8]285     pub fn data(&self) -> &[u8] {
286         &self.data
287     }
288 }
289 
290 /// Trait to simplify creating custom proposals that are serialized with MLS
291 /// encoding.
292 #[cfg(feature = "custom_proposal")]
293 pub trait MlsCustomProposal: MlsSize + MlsEncode + MlsDecode + Sized {
proposal_type() -> ProposalType294     fn proposal_type() -> ProposalType;
295 
to_custom_proposal(&self) -> Result<CustomProposal, mls_rs_codec::Error>296     fn to_custom_proposal(&self) -> Result<CustomProposal, mls_rs_codec::Error> {
297         Ok(CustomProposal::new(
298             Self::proposal_type(),
299             self.mls_encode_to_vec()?,
300         ))
301     }
302 
from_custom_proposal(proposal: &CustomProposal) -> Result<Self, mls_rs_codec::Error>303     fn from_custom_proposal(proposal: &CustomProposal) -> Result<Self, mls_rs_codec::Error> {
304         if proposal.proposal_type() != Self::proposal_type() {
305             // #[cfg(feature = "std")]
306             // return Err(mls_rs_codec::Error::Custom(
307             //     "invalid proposal type".to_string(),
308             // ));
309 
310             //#[cfg(not(feature = "std"))]
311             return Err(mls_rs_codec::Error::Custom(4));
312         }
313 
314         Self::mls_decode(&mut proposal.data())
315     }
316 }
317 
318 #[allow(clippy::large_enum_variant)]
319 #[derive(Clone, Debug, PartialEq)]
320 #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
321 #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
322 #[repr(u16)]
323 #[non_exhaustive]
324 /// An enum that represents all possible types of proposals.
325 pub enum Proposal {
326     Add(alloc::boxed::Box<AddProposal>),
327     #[cfg(feature = "by_ref_proposal")]
328     Update(UpdateProposal),
329     Remove(RemoveProposal),
330     #[cfg(feature = "psk")]
331     Psk(PreSharedKeyProposal),
332     ReInit(ReInitProposal),
333     ExternalInit(ExternalInit),
334     GroupContextExtensions(ExtensionList),
335     #[cfg(feature = "custom_proposal")]
336     Custom(CustomProposal),
337 }
338 
339 impl MlsSize for Proposal {
mls_encoded_len(&self) -> usize340     fn mls_encoded_len(&self) -> usize {
341         let inner_len = match self {
342             Proposal::Add(p) => p.mls_encoded_len(),
343             #[cfg(feature = "by_ref_proposal")]
344             Proposal::Update(p) => p.mls_encoded_len(),
345             Proposal::Remove(p) => p.mls_encoded_len(),
346             #[cfg(feature = "psk")]
347             Proposal::Psk(p) => p.mls_encoded_len(),
348             Proposal::ReInit(p) => p.mls_encoded_len(),
349             Proposal::ExternalInit(p) => p.mls_encoded_len(),
350             Proposal::GroupContextExtensions(p) => p.mls_encoded_len(),
351             #[cfg(feature = "custom_proposal")]
352             Proposal::Custom(p) => mls_rs_codec::byte_vec::mls_encoded_len(&p.data),
353         };
354 
355         self.proposal_type().mls_encoded_len() + inner_len
356     }
357 }
358 
359 impl MlsEncode for Proposal {
mls_encode(&self, writer: &mut Vec<u8>) -> Result<(), mls_rs_codec::Error>360     fn mls_encode(&self, writer: &mut Vec<u8>) -> Result<(), mls_rs_codec::Error> {
361         self.proposal_type().mls_encode(writer)?;
362 
363         match self {
364             Proposal::Add(p) => p.mls_encode(writer),
365             #[cfg(feature = "by_ref_proposal")]
366             Proposal::Update(p) => p.mls_encode(writer),
367             Proposal::Remove(p) => p.mls_encode(writer),
368             #[cfg(feature = "psk")]
369             Proposal::Psk(p) => p.mls_encode(writer),
370             Proposal::ReInit(p) => p.mls_encode(writer),
371             Proposal::ExternalInit(p) => p.mls_encode(writer),
372             Proposal::GroupContextExtensions(p) => p.mls_encode(writer),
373             #[cfg(feature = "custom_proposal")]
374             Proposal::Custom(p) => {
375                 if p.proposal_type.raw_value() <= 7 {
376                     // #[cfg(feature = "std")]
377                     // return Err(mls_rs_codec::Error::Custom(
378                     //     "custom proposal types can not be set to defined values of 0-7".to_string(),
379                     // ));
380 
381                     // #[cfg(not(feature = "std"))]
382                     return Err(mls_rs_codec::Error::Custom(2));
383                 }
384                 mls_rs_codec::byte_vec::mls_encode(&p.data, writer)
385             }
386         }
387     }
388 }
389 
390 impl MlsDecode for Proposal {
mls_decode(reader: &mut &[u8]) -> Result<Self, mls_rs_codec::Error>391     fn mls_decode(reader: &mut &[u8]) -> Result<Self, mls_rs_codec::Error> {
392         let proposal_type = ProposalType::mls_decode(reader)?;
393 
394         Ok(match proposal_type {
395             ProposalType::ADD => {
396                 Proposal::Add(alloc::boxed::Box::new(AddProposal::mls_decode(reader)?))
397             }
398             #[cfg(feature = "by_ref_proposal")]
399             ProposalType::UPDATE => Proposal::Update(UpdateProposal::mls_decode(reader)?),
400             ProposalType::REMOVE => Proposal::Remove(RemoveProposal::mls_decode(reader)?),
401             #[cfg(feature = "psk")]
402             ProposalType::PSK => Proposal::Psk(PreSharedKeyProposal::mls_decode(reader)?),
403             ProposalType::RE_INIT => Proposal::ReInit(ReInitProposal::mls_decode(reader)?),
404             ProposalType::EXTERNAL_INIT => {
405                 Proposal::ExternalInit(ExternalInit::mls_decode(reader)?)
406             }
407             ProposalType::GROUP_CONTEXT_EXTENSIONS => {
408                 Proposal::GroupContextExtensions(ExtensionList::mls_decode(reader)?)
409             }
410             #[cfg(feature = "custom_proposal")]
411             custom => Proposal::Custom(CustomProposal {
412                 proposal_type: custom,
413                 data: mls_rs_codec::byte_vec::mls_decode(reader)?,
414             }),
415             // TODO fix test dependency on openssl loading codec with default features
416             #[cfg(not(feature = "custom_proposal"))]
417             _ => return Err(mls_rs_codec::Error::Custom(3)),
418         })
419     }
420 }
421 
422 impl Proposal {
proposal_type(&self) -> ProposalType423     pub fn proposal_type(&self) -> ProposalType {
424         match self {
425             Proposal::Add(_) => ProposalType::ADD,
426             #[cfg(feature = "by_ref_proposal")]
427             Proposal::Update(_) => ProposalType::UPDATE,
428             Proposal::Remove(_) => ProposalType::REMOVE,
429             #[cfg(feature = "psk")]
430             Proposal::Psk(_) => ProposalType::PSK,
431             Proposal::ReInit(_) => ProposalType::RE_INIT,
432             Proposal::ExternalInit(_) => ProposalType::EXTERNAL_INIT,
433             Proposal::GroupContextExtensions(_) => ProposalType::GROUP_CONTEXT_EXTENSIONS,
434             #[cfg(feature = "custom_proposal")]
435             Proposal::Custom(c) => c.proposal_type,
436         }
437     }
438 }
439 
440 #[derive(Clone, Debug, PartialEq)]
441 /// An enum that represents a borrowed version of [`Proposal`].
442 pub enum BorrowedProposal<'a> {
443     Add(&'a AddProposal),
444     #[cfg(feature = "by_ref_proposal")]
445     Update(&'a UpdateProposal),
446     Remove(&'a RemoveProposal),
447     #[cfg(feature = "psk")]
448     Psk(&'a PreSharedKeyProposal),
449     ReInit(&'a ReInitProposal),
450     ExternalInit(&'a ExternalInit),
451     GroupContextExtensions(&'a ExtensionList),
452     #[cfg(feature = "custom_proposal")]
453     Custom(&'a CustomProposal),
454 }
455 
456 impl<'a> From<BorrowedProposal<'a>> for Proposal {
from(value: BorrowedProposal<'a>) -> Self457     fn from(value: BorrowedProposal<'a>) -> Self {
458         match value {
459             BorrowedProposal::Add(add) => Proposal::Add(alloc::boxed::Box::new(add.clone())),
460             #[cfg(feature = "by_ref_proposal")]
461             BorrowedProposal::Update(update) => Proposal::Update(update.clone()),
462             BorrowedProposal::Remove(remove) => Proposal::Remove(remove.clone()),
463             #[cfg(feature = "psk")]
464             BorrowedProposal::Psk(psk) => Proposal::Psk(psk.clone()),
465             BorrowedProposal::ReInit(reinit) => Proposal::ReInit(reinit.clone()),
466             BorrowedProposal::ExternalInit(external) => Proposal::ExternalInit(external.clone()),
467             BorrowedProposal::GroupContextExtensions(ext) => {
468                 Proposal::GroupContextExtensions(ext.clone())
469             }
470             #[cfg(feature = "custom_proposal")]
471             BorrowedProposal::Custom(custom) => Proposal::Custom(custom.clone()),
472         }
473     }
474 }
475 
476 impl BorrowedProposal<'_> {
proposal_type(&self) -> ProposalType477     pub fn proposal_type(&self) -> ProposalType {
478         match self {
479             BorrowedProposal::Add(_) => ProposalType::ADD,
480             #[cfg(feature = "by_ref_proposal")]
481             BorrowedProposal::Update(_) => ProposalType::UPDATE,
482             BorrowedProposal::Remove(_) => ProposalType::REMOVE,
483             #[cfg(feature = "psk")]
484             BorrowedProposal::Psk(_) => ProposalType::PSK,
485             BorrowedProposal::ReInit(_) => ProposalType::RE_INIT,
486             BorrowedProposal::ExternalInit(_) => ProposalType::EXTERNAL_INIT,
487             BorrowedProposal::GroupContextExtensions(_) => ProposalType::GROUP_CONTEXT_EXTENSIONS,
488             #[cfg(feature = "custom_proposal")]
489             BorrowedProposal::Custom(c) => c.proposal_type,
490         }
491     }
492 }
493 
494 impl<'a> From<&'a Proposal> for BorrowedProposal<'a> {
from(p: &'a Proposal) -> Self495     fn from(p: &'a Proposal) -> Self {
496         match p {
497             Proposal::Add(p) => BorrowedProposal::Add(p),
498             #[cfg(feature = "by_ref_proposal")]
499             Proposal::Update(p) => BorrowedProposal::Update(p),
500             Proposal::Remove(p) => BorrowedProposal::Remove(p),
501             #[cfg(feature = "psk")]
502             Proposal::Psk(p) => BorrowedProposal::Psk(p),
503             Proposal::ReInit(p) => BorrowedProposal::ReInit(p),
504             Proposal::ExternalInit(p) => BorrowedProposal::ExternalInit(p),
505             Proposal::GroupContextExtensions(p) => BorrowedProposal::GroupContextExtensions(p),
506             #[cfg(feature = "custom_proposal")]
507             Proposal::Custom(p) => BorrowedProposal::Custom(p),
508         }
509     }
510 }
511 
512 impl<'a> From<&'a AddProposal> for BorrowedProposal<'a> {
from(p: &'a AddProposal) -> Self513     fn from(p: &'a AddProposal) -> Self {
514         Self::Add(p)
515     }
516 }
517 
518 #[cfg(feature = "by_ref_proposal")]
519 impl<'a> From<&'a UpdateProposal> for BorrowedProposal<'a> {
from(p: &'a UpdateProposal) -> Self520     fn from(p: &'a UpdateProposal) -> Self {
521         Self::Update(p)
522     }
523 }
524 
525 impl<'a> From<&'a RemoveProposal> for BorrowedProposal<'a> {
from(p: &'a RemoveProposal) -> Self526     fn from(p: &'a RemoveProposal) -> Self {
527         Self::Remove(p)
528     }
529 }
530 
531 #[cfg(feature = "psk")]
532 impl<'a> From<&'a PreSharedKeyProposal> for BorrowedProposal<'a> {
from(p: &'a PreSharedKeyProposal) -> Self533     fn from(p: &'a PreSharedKeyProposal) -> Self {
534         Self::Psk(p)
535     }
536 }
537 
538 impl<'a> From<&'a ReInitProposal> for BorrowedProposal<'a> {
from(p: &'a ReInitProposal) -> Self539     fn from(p: &'a ReInitProposal) -> Self {
540         Self::ReInit(p)
541     }
542 }
543 
544 impl<'a> From<&'a ExternalInit> for BorrowedProposal<'a> {
from(p: &'a ExternalInit) -> Self545     fn from(p: &'a ExternalInit) -> Self {
546         Self::ExternalInit(p)
547     }
548 }
549 
550 impl<'a> From<&'a ExtensionList> for BorrowedProposal<'a> {
from(p: &'a ExtensionList) -> Self551     fn from(p: &'a ExtensionList) -> Self {
552         Self::GroupContextExtensions(p)
553     }
554 }
555 
556 #[cfg(feature = "custom_proposal")]
557 impl<'a> From<&'a CustomProposal> for BorrowedProposal<'a> {
from(p: &'a CustomProposal) -> Self558     fn from(p: &'a CustomProposal) -> Self {
559         Self::Custom(p)
560     }
561 }
562 
563 #[derive(Clone, Debug, PartialEq, MlsSize, MlsEncode, MlsDecode)]
564 #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
565 #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
566 #[repr(u8)]
567 pub(crate) enum ProposalOrRef {
568     Proposal(Box<Proposal>) = 1u8,
569     #[cfg(feature = "by_ref_proposal")]
570     Reference(ProposalRef) = 2u8,
571 }
572 
573 impl From<Proposal> for ProposalOrRef {
from(proposal: Proposal) -> Self574     fn from(proposal: Proposal) -> Self {
575         Self::Proposal(Box::new(proposal))
576     }
577 }
578 
579 #[cfg(feature = "by_ref_proposal")]
580 impl From<ProposalRef> for ProposalOrRef {
from(r: ProposalRef) -> Self581     fn from(r: ProposalRef) -> Self {
582         Self::Reference(r)
583     }
584 }
585