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