Skip to main content

mls_rs/group/
proposal.rs

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
5use alloc::{boxed::Box, vec::Vec};
6
7#[cfg(feature = "by_ref_proposal")]
8use crate::tree_kem::leaf_node::LeafNode;
9
10use crate::{
11    client::MlsError, tree_kem::node::LeafIndex, CipherSuite, KeyPackage, MlsMessage,
12    ProtocolVersion,
13};
14use core::fmt::{self, Debug};
15use mls_rs_codec::{MlsDecode, MlsEncode, MlsSize};
16use mls_rs_core::{group::Capabilities, identity::SigningIdentity};
17
18#[cfg(feature = "by_ref_proposal")]
19use crate::group::proposal_ref::ProposalRef;
20
21pub use mls_rs_core::extension::ExtensionList;
22pub use mls_rs_core::group::ProposalType;
23
24#[cfg(feature = "psk")]
25use 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).
31pub struct AddProposal {
32    pub(crate) key_package: KeyPackage,
33}
34
35impl AddProposal {
36    /// The [`KeyPackage`] used by this proposal to add
37    /// a [`Member`](mls_rs_core::group::Member) to the group.
38    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.
44    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.
50    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.
56    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.
62    pub fn leaf_node_extensions(&self) -> ExtensionList {
63        self.key_package.leaf_node.ungreased_extensions()
64    }
65}
66
67impl From<KeyPackage> for AddProposal {
68    fn from(key_package: KeyPackage) -> Self {
69        Self { key_package }
70    }
71}
72
73impl TryFrom<MlsMessage> for AddProposal {
74    type Error = MlsError;
75
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).
90pub struct UpdateProposal {
91    pub(crate) leaf_node: LeafNode,
92}
93
94#[cfg(feature = "by_ref_proposal")]
95impl UpdateProposal {
96    /// The new [`SigningIdentity`] of the [`Member`](mls_rs_core::group::Member)
97    /// that is being updated by this proposal.
98    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.
104    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.
110    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).
120pub struct RemoveProposal {
121    pub(crate) to_remove: LeafIndex,
122}
123
124impl RemoveProposal {
125    /// The index of the [`Member`](mls_rs_core::group::Member) that will be removed by
126    /// this proposal.
127    pub fn to_remove(&self) -> u32 {
128        *self.to_remove
129    }
130}
131
132impl RemoveProposal {
133    pub fn removing(member_index: u32) -> Result<Self, MlsError> {
134        Ok(Self {
135            to_remove: LeafIndex::try_from(member_index)?,
136        })
137    }
138}
139
140impl TryFrom<u32> for RemoveProposal {
141    type Error = MlsError;
142
143    fn try_from(value: u32) -> Result<Self, Self::Error> {
144        Self::removing(value)
145    }
146}
147
148#[cfg(feature = "psk")]
149#[derive(Clone, Debug, PartialEq, Eq, MlsSize, MlsEncode, MlsDecode)]
150#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
151#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
152/// A proposal to add a pre-shared key to a group.
153pub struct PreSharedKeyProposal {
154    pub(crate) psk: PreSharedKeyID,
155}
156
157#[cfg(feature = "psk")]
158impl PreSharedKeyProposal {
159    /// The external pre-shared key id of this proposal.
160    ///
161    /// MLS requires the pre-shared key type for PreSharedKeyProposal to be of
162    /// type `External`.
163    ///
164    /// Returns `None` in the condition that the underlying psk is not external.
165    pub fn external_psk_id(&self) -> Option<&ExternalPskId> {
166        match self.psk.key_id {
167            JustPreSharedKeyID::External(ref ext) => Some(ext),
168            JustPreSharedKeyID::Resumption(_) => None,
169        }
170    }
171}
172
173#[derive(Clone, PartialEq, MlsSize, MlsEncode, MlsDecode)]
174#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
175#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
176/// A proposal to reinitialize a group using new parameters.
177pub struct ReInitProposal {
178    #[mls_codec(with = "mls_rs_codec::byte_vec")]
179    #[cfg_attr(feature = "serde", serde(with = "mls_rs_core::vec_serde"))]
180    pub(crate) group_id: Vec<u8>,
181    pub(crate) version: ProtocolVersion,
182    pub(crate) cipher_suite: CipherSuite,
183    pub(crate) extensions: ExtensionList,
184}
185
186impl Debug for ReInitProposal {
187    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
188        f.debug_struct("ReInitProposal")
189            .field(
190                "group_id",
191                &mls_rs_core::debug::pretty_group_id(&self.group_id),
192            )
193            .field("version", &self.version)
194            .field("cipher_suite", &self.cipher_suite)
195            .field("extensions", &self.extensions)
196            .finish()
197    }
198}
199
200impl ReInitProposal {
201    /// The unique id of the new group post reinitialization.
202    pub fn group_id(&self) -> &[u8] {
203        &self.group_id
204    }
205
206    /// The new protocol version to use post reinitialization.
207    pub fn new_version(&self) -> ProtocolVersion {
208        self.version
209    }
210
211    /// The new ciphersuite to use post reinitialization.
212    pub fn new_cipher_suite(&self) -> CipherSuite {
213        self.cipher_suite
214    }
215
216    /// Group context extensions to set in the new group post reinitialization.
217    pub fn new_group_context_extensions(&self) -> &ExtensionList {
218        &self.extensions
219    }
220}
221
222#[derive(Clone, PartialEq, Eq, MlsSize, MlsEncode, MlsDecode)]
223#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
224#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
225/// A proposal used for external commits.
226pub struct ExternalInit {
227    #[mls_codec(with = "mls_rs_codec::byte_vec")]
228    #[cfg_attr(feature = "serde", serde(with = "mls_rs_core::vec_serde"))]
229    pub(crate) kem_output: Vec<u8>,
230}
231
232impl Debug for ExternalInit {
233    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
234        f.debug_struct("ExternalInit").finish()
235    }
236}
237
238#[derive(Clone, PartialEq, Eq, MlsSize, MlsEncode, MlsDecode, Debug)]
239#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
240#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
241#[cfg(all(
242    feature = "by_ref_proposal",
243    feature = "custom_proposal",
244    feature = "self_remove_proposal"
245))]
246/// A proposal to remove the current [`Member`](mls_rs_core::group::Member) of a
247/// [`Group`](crate::group::Group).
248pub struct SelfRemoveProposal {}
249
250#[cfg(feature = "custom_proposal")]
251#[derive(Clone, PartialEq, Eq)]
252#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
253#[cfg_attr(
254    all(feature = "ffi", not(test)),
255    safer_ffi_gen::ffi_type(clone, opaque)
256)]
257#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
258/// A user defined custom proposal.
259///
260/// User defined proposals are passed through the protocol as an opaque value.
261pub struct CustomProposal {
262    proposal_type: ProposalType,
263    #[cfg_attr(feature = "serde", serde(with = "mls_rs_core::vec_serde"))]
264    data: Vec<u8>,
265}
266
267#[cfg(feature = "custom_proposal")]
268impl Debug for CustomProposal {
269    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
270        f.debug_struct("CustomProposal")
271            .field("proposal_type", &self.proposal_type)
272            .field("data", &mls_rs_core::debug::pretty_bytes(&self.data))
273            .finish()
274    }
275}
276
277#[cfg(feature = "custom_proposal")]
278#[cfg_attr(all(feature = "ffi", not(test)), safer_ffi_gen::safer_ffi_gen)]
279impl CustomProposal {
280    /// Create a custom proposal.
281    ///
282    /// # Warning
283    ///
284    /// Avoid using the [`ProposalType`] values that have constants already
285    /// defined by this crate. Using existing constants in a custom proposal
286    /// has unspecified behavior.
287    pub fn new(proposal_type: ProposalType, data: Vec<u8>) -> Self {
288        Self {
289            proposal_type,
290            data,
291        }
292    }
293
294    /// The proposal type used for this custom proposal.
295    pub fn proposal_type(&self) -> ProposalType {
296        self.proposal_type
297    }
298
299    /// The opaque data communicated by this custom proposal.
300    pub fn data(&self) -> &[u8] {
301        &self.data
302    }
303}
304
305#[cfg(feature = "custom_proposal")]
306/// Encode/Decode for the `data` field of CustomProposal. This allows specialization over
307/// the decoding based on the ProposalType, if users want to use an encoding other than the
308/// default bytes vector (in particular, if users want the CustomProposal wrapper to be hidden
309/// in the encoding, so the underlying custom proposal can be decoded directly).
310pub trait CustomDecoder: Sized {
311    fn encode_from_bytes(
312        data: &Vec<u8>,
313        writer: &mut Vec<u8>,
314        _proposal_type: &ProposalType,
315    ) -> Result<(), mls_rs_codec::Error> {
316        mls_rs_codec::byte_vec::mls_encode(data, writer)
317    }
318    fn decode_from_bytes(
319        reader: &mut &[u8],
320        _proposal_type: &ProposalType,
321    ) -> Result<Vec<u8>, mls_rs_codec::Error> {
322        mls_rs_codec::byte_vec::mls_decode(reader)
323    }
324    fn encoded_byte_len(data: &Vec<u8>, _proposal_type: &ProposalType) -> usize {
325        mls_rs_codec::byte_vec::mls_encoded_len(data)
326    }
327}
328
329#[cfg(all(feature = "custom_proposal", not(feature = "gsma_rcs_e2ee_feature")))]
330impl CustomDecoder for CustomProposal {}
331
332#[cfg(all(feature = "custom_proposal", feature = "gsma_rcs_e2ee_feature"))]
333impl CustomDecoder for CustomProposal {
334    fn encode_from_bytes(
335        data: &Vec<u8>,
336        writer: &mut Vec<u8>,
337        proposal_type: &ProposalType,
338    ) -> Result<(), mls_rs_codec::Error> {
339        match proposal_type {
340            // directly extend with the serialized proposals; don't encode as a byte array
341            // as the length should not be included in the encoding
342            &ProposalType::RCS_SIGNATURE | &ProposalType::RCS_SERVER_REMOVE => {
343                writer.extend(data);
344                Ok(())
345            }
346            _ => mls_rs_codec::byte_vec::mls_encode(data, writer),
347        }
348    }
349    fn decode_from_bytes(
350        reader: &mut &[u8],
351        proposal_type: &ProposalType,
352    ) -> Result<Vec<u8>, mls_rs_codec::Error> {
353        match *proposal_type {
354            // empty struct
355            ProposalType::RCS_SIGNATURE => Ok(Vec::new()),
356            // remove proposal
357            ProposalType::RCS_SERVER_REMOVE => {
358                let decoded = RemoveProposal::mls_decode(reader)?;
359                let mut writer = Vec::new();
360                RemoveProposal::mls_encode(&decoded, &mut writer)?;
361                // return, to be used in the data field of CustomProposal, the encoded proposal
362                Ok(writer)
363            }
364            _ => mls_rs_codec::byte_vec::mls_decode(reader),
365        }
366    }
367    fn encoded_byte_len(data: &Vec<u8>, proposal_type: &ProposalType) -> usize {
368        match proposal_type {
369            &ProposalType::RCS_SIGNATURE | &ProposalType::RCS_SERVER_REMOVE => data.len(),
370            _ => mls_rs_codec::byte_vec::mls_encoded_len(data),
371        }
372    }
373}
374
375/// Trait to simplify creating custom proposals that are serialized with MLS
376/// encoding.
377#[cfg(feature = "custom_proposal")]
378pub trait MlsCustomProposal: MlsSize + MlsEncode + MlsDecode + Sized {
379    fn proposal_type() -> ProposalType;
380
381    fn to_custom_proposal(&self) -> Result<CustomProposal, mls_rs_codec::Error> {
382        Ok(CustomProposal::new(
383            Self::proposal_type(),
384            self.mls_encode_to_vec()?,
385        ))
386    }
387
388    fn from_custom_proposal(proposal: &CustomProposal) -> Result<Self, mls_rs_codec::Error> {
389        if proposal.proposal_type() != Self::proposal_type() {
390            // #[cfg(feature = "std")]
391            // return Err(mls_rs_codec::Error::Custom(
392            //     "invalid proposal type".to_string(),
393            // ));
394
395            //#[cfg(not(feature = "std"))]
396            return Err(mls_rs_codec::Error::Custom(4));
397        }
398
399        Self::mls_decode(&mut proposal.data())
400    }
401}
402
403#[allow(clippy::large_enum_variant)]
404#[derive(Clone, Debug, PartialEq)]
405#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
406#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
407#[repr(u16)]
408#[non_exhaustive]
409/// An enum that represents all possible types of proposals.
410pub enum Proposal {
411    Add(alloc::boxed::Box<AddProposal>),
412    #[cfg(feature = "by_ref_proposal")]
413    Update(UpdateProposal),
414    Remove(RemoveProposal),
415    #[cfg(feature = "psk")]
416    Psk(PreSharedKeyProposal),
417    ReInit(ReInitProposal),
418    ExternalInit(ExternalInit),
419    GroupContextExtensions(ExtensionList),
420    #[cfg(all(
421        feature = "by_ref_proposal",
422        feature = "custom_proposal",
423        feature = "self_remove_proposal"
424    ))]
425    SelfRemove(SelfRemoveProposal),
426    #[cfg(feature = "custom_proposal")]
427    Custom(CustomProposal),
428}
429
430impl MlsSize for Proposal {
431    fn mls_encoded_len(&self) -> usize {
432        let inner_len = match self {
433            Proposal::Add(p) => p.mls_encoded_len(),
434            #[cfg(feature = "by_ref_proposal")]
435            Proposal::Update(p) => p.mls_encoded_len(),
436            Proposal::Remove(p) => p.mls_encoded_len(),
437            #[cfg(feature = "psk")]
438            Proposal::Psk(p) => p.mls_encoded_len(),
439            Proposal::ReInit(p) => p.mls_encoded_len(),
440            Proposal::ExternalInit(p) => p.mls_encoded_len(),
441            Proposal::GroupContextExtensions(p) => p.mls_encoded_len(),
442            #[cfg(all(
443                feature = "by_ref_proposal",
444                feature = "custom_proposal",
445                feature = "self_remove_proposal"
446            ))]
447            Proposal::SelfRemove(p) => p.mls_encoded_len(),
448            #[cfg(feature = "custom_proposal")]
449            Proposal::Custom(p) => CustomProposal::encoded_byte_len(&p.data, &p.proposal_type),
450        };
451
452        self.proposal_type().mls_encoded_len() + inner_len
453    }
454}
455
456impl MlsEncode for Proposal {
457    fn mls_encode(&self, writer: &mut Vec<u8>) -> Result<(), mls_rs_codec::Error> {
458        self.proposal_type().mls_encode(writer)?;
459
460        match self {
461            Proposal::Add(p) => p.mls_encode(writer),
462            #[cfg(feature = "by_ref_proposal")]
463            Proposal::Update(p) => p.mls_encode(writer),
464            Proposal::Remove(p) => p.mls_encode(writer),
465            #[cfg(feature = "psk")]
466            Proposal::Psk(p) => p.mls_encode(writer),
467            Proposal::ReInit(p) => p.mls_encode(writer),
468            Proposal::ExternalInit(p) => p.mls_encode(writer),
469            Proposal::GroupContextExtensions(p) => p.mls_encode(writer),
470            #[cfg(all(
471                feature = "by_ref_proposal",
472                feature = "custom_proposal",
473                feature = "self_remove_proposal"
474            ))]
475            Proposal::SelfRemove(p) => p.mls_encode(writer),
476            #[cfg(feature = "custom_proposal")]
477            Proposal::Custom(p) => {
478                if p.proposal_type.raw_value() <= 7 {
479                    // #[cfg(feature = "std")]
480                    // return Err(mls_rs_codec::Error::Custom(
481                    //     "custom proposal types can not be set to defined values of 0-7".to_string(),
482                    // ));
483
484                    // #[cfg(not(feature = "std"))]
485                    return Err(mls_rs_codec::Error::Custom(2));
486                }
487                CustomProposal::encode_from_bytes(&p.data, writer, &p.proposal_type)
488            }
489        }
490    }
491}
492
493impl MlsDecode for Proposal {
494    fn mls_decode(reader: &mut &[u8]) -> Result<Self, mls_rs_codec::Error> {
495        let proposal_type = ProposalType::mls_decode(reader)?;
496
497        Ok(match proposal_type {
498            ProposalType::ADD => {
499                Proposal::Add(alloc::boxed::Box::new(AddProposal::mls_decode(reader)?))
500            }
501            #[cfg(feature = "by_ref_proposal")]
502            ProposalType::UPDATE => Proposal::Update(UpdateProposal::mls_decode(reader)?),
503            ProposalType::REMOVE => Proposal::Remove(RemoveProposal::mls_decode(reader)?),
504            #[cfg(feature = "psk")]
505            ProposalType::PSK => Proposal::Psk(PreSharedKeyProposal::mls_decode(reader)?),
506            ProposalType::RE_INIT => Proposal::ReInit(ReInitProposal::mls_decode(reader)?),
507            ProposalType::EXTERNAL_INIT => {
508                Proposal::ExternalInit(ExternalInit::mls_decode(reader)?)
509            }
510            ProposalType::GROUP_CONTEXT_EXTENSIONS => {
511                Proposal::GroupContextExtensions(ExtensionList::mls_decode(reader)?)
512            }
513            #[cfg(all(
514                feature = "by_ref_proposal",
515                feature = "custom_proposal",
516                feature = "self_remove_proposal"
517            ))]
518            ProposalType::SELF_REMOVE => {
519                Proposal::SelfRemove(SelfRemoveProposal::mls_decode(reader)?)
520            }
521            #[cfg(feature = "custom_proposal")]
522            custom => Proposal::Custom(CustomProposal {
523                proposal_type: custom,
524                data: CustomProposal::decode_from_bytes(reader, &custom)?,
525            }),
526            // TODO fix test dependency on openssl loading codec with default features
527            #[cfg(not(feature = "custom_proposal"))]
528            _ => return Err(mls_rs_codec::Error::Custom(3)),
529        })
530    }
531}
532
533impl Proposal {
534    pub fn proposal_type(&self) -> ProposalType {
535        match self {
536            Proposal::Add(_) => ProposalType::ADD,
537            #[cfg(feature = "by_ref_proposal")]
538            Proposal::Update(_) => ProposalType::UPDATE,
539            Proposal::Remove(_) => ProposalType::REMOVE,
540            #[cfg(feature = "psk")]
541            Proposal::Psk(_) => ProposalType::PSK,
542            Proposal::ReInit(_) => ProposalType::RE_INIT,
543            Proposal::ExternalInit(_) => ProposalType::EXTERNAL_INIT,
544            Proposal::GroupContextExtensions(_) => ProposalType::GROUP_CONTEXT_EXTENSIONS,
545            #[cfg(all(
546                feature = "by_ref_proposal",
547                feature = "custom_proposal",
548                feature = "self_remove_proposal"
549            ))]
550            Proposal::SelfRemove(_) => ProposalType::SELF_REMOVE,
551            #[cfg(feature = "custom_proposal")]
552            Proposal::Custom(c) => c.proposal_type,
553        }
554    }
555}
556
557#[derive(Clone, Debug, PartialEq)]
558/// An enum that represents a borrowed version of [`Proposal`].
559pub enum BorrowedProposal<'a> {
560    Add(&'a AddProposal),
561    #[cfg(feature = "by_ref_proposal")]
562    Update(&'a UpdateProposal),
563    Remove(&'a RemoveProposal),
564    #[cfg(feature = "psk")]
565    Psk(&'a PreSharedKeyProposal),
566    ReInit(&'a ReInitProposal),
567    ExternalInit(&'a ExternalInit),
568    GroupContextExtensions(&'a ExtensionList),
569    #[cfg(all(
570        feature = "by_ref_proposal",
571        feature = "custom_proposal",
572        feature = "self_remove_proposal"
573    ))]
574    SelfRemove(&'a SelfRemoveProposal),
575    #[cfg(feature = "custom_proposal")]
576    Custom(&'a CustomProposal),
577}
578
579impl<'a> From<BorrowedProposal<'a>> for Proposal {
580    fn from(value: BorrowedProposal<'a>) -> Self {
581        match value {
582            BorrowedProposal::Add(add) => Proposal::Add(alloc::boxed::Box::new(add.clone())),
583            #[cfg(feature = "by_ref_proposal")]
584            BorrowedProposal::Update(update) => Proposal::Update(update.clone()),
585            BorrowedProposal::Remove(remove) => Proposal::Remove(remove.clone()),
586            #[cfg(feature = "psk")]
587            BorrowedProposal::Psk(psk) => Proposal::Psk(psk.clone()),
588            BorrowedProposal::ReInit(reinit) => Proposal::ReInit(reinit.clone()),
589            BorrowedProposal::ExternalInit(external) => Proposal::ExternalInit(external.clone()),
590            BorrowedProposal::GroupContextExtensions(ext) => {
591                Proposal::GroupContextExtensions(ext.clone())
592            }
593            #[cfg(all(
594                feature = "by_ref_proposal",
595                feature = "custom_proposal",
596                feature = "self_remove_proposal"
597            ))]
598            BorrowedProposal::SelfRemove(self_remove) => Proposal::SelfRemove(self_remove.clone()),
599            #[cfg(feature = "custom_proposal")]
600            BorrowedProposal::Custom(custom) => Proposal::Custom(custom.clone()),
601        }
602    }
603}
604
605impl BorrowedProposal<'_> {
606    pub fn proposal_type(&self) -> ProposalType {
607        match self {
608            BorrowedProposal::Add(_) => ProposalType::ADD,
609            #[cfg(feature = "by_ref_proposal")]
610            BorrowedProposal::Update(_) => ProposalType::UPDATE,
611            BorrowedProposal::Remove(_) => ProposalType::REMOVE,
612            #[cfg(feature = "psk")]
613            BorrowedProposal::Psk(_) => ProposalType::PSK,
614            BorrowedProposal::ReInit(_) => ProposalType::RE_INIT,
615            BorrowedProposal::ExternalInit(_) => ProposalType::EXTERNAL_INIT,
616            BorrowedProposal::GroupContextExtensions(_) => ProposalType::GROUP_CONTEXT_EXTENSIONS,
617            #[cfg(all(
618                feature = "by_ref_proposal",
619                feature = "custom_proposal",
620                feature = "self_remove_proposal"
621            ))]
622            BorrowedProposal::SelfRemove(_) => ProposalType::SELF_REMOVE,
623            #[cfg(feature = "custom_proposal")]
624            BorrowedProposal::Custom(c) => c.proposal_type,
625        }
626    }
627}
628
629impl<'a> From<&'a Proposal> for BorrowedProposal<'a> {
630    fn from(p: &'a Proposal) -> Self {
631        match p {
632            Proposal::Add(p) => BorrowedProposal::Add(p),
633            #[cfg(feature = "by_ref_proposal")]
634            Proposal::Update(p) => BorrowedProposal::Update(p),
635            Proposal::Remove(p) => BorrowedProposal::Remove(p),
636            #[cfg(feature = "psk")]
637            Proposal::Psk(p) => BorrowedProposal::Psk(p),
638            Proposal::ReInit(p) => BorrowedProposal::ReInit(p),
639            Proposal::ExternalInit(p) => BorrowedProposal::ExternalInit(p),
640            Proposal::GroupContextExtensions(p) => BorrowedProposal::GroupContextExtensions(p),
641            #[cfg(all(
642                feature = "by_ref_proposal",
643                feature = "custom_proposal",
644                feature = "self_remove_proposal"
645            ))]
646            Proposal::SelfRemove(p) => BorrowedProposal::SelfRemove(p),
647            #[cfg(feature = "custom_proposal")]
648            Proposal::Custom(p) => BorrowedProposal::Custom(p),
649        }
650    }
651}
652
653impl<'a> From<&'a AddProposal> for BorrowedProposal<'a> {
654    fn from(p: &'a AddProposal) -> Self {
655        Self::Add(p)
656    }
657}
658
659#[cfg(feature = "by_ref_proposal")]
660impl<'a> From<&'a UpdateProposal> for BorrowedProposal<'a> {
661    fn from(p: &'a UpdateProposal) -> Self {
662        Self::Update(p)
663    }
664}
665
666impl<'a> From<&'a RemoveProposal> for BorrowedProposal<'a> {
667    fn from(p: &'a RemoveProposal) -> Self {
668        Self::Remove(p)
669    }
670}
671
672#[cfg(feature = "psk")]
673impl<'a> From<&'a PreSharedKeyProposal> for BorrowedProposal<'a> {
674    fn from(p: &'a PreSharedKeyProposal) -> Self {
675        Self::Psk(p)
676    }
677}
678
679impl<'a> From<&'a ReInitProposal> for BorrowedProposal<'a> {
680    fn from(p: &'a ReInitProposal) -> Self {
681        Self::ReInit(p)
682    }
683}
684
685impl<'a> From<&'a ExternalInit> for BorrowedProposal<'a> {
686    fn from(p: &'a ExternalInit) -> Self {
687        Self::ExternalInit(p)
688    }
689}
690
691impl<'a> From<&'a ExtensionList> for BorrowedProposal<'a> {
692    fn from(p: &'a ExtensionList) -> Self {
693        Self::GroupContextExtensions(p)
694    }
695}
696
697#[cfg(all(
698    feature = "by_ref_proposal",
699    feature = "custom_proposal",
700    feature = "self_remove_proposal"
701))]
702impl<'a> From<&'a SelfRemoveProposal> for BorrowedProposal<'a> {
703    fn from(p: &'a SelfRemoveProposal) -> Self {
704        Self::SelfRemove(p)
705    }
706}
707
708#[cfg(feature = "custom_proposal")]
709impl<'a> From<&'a CustomProposal> for BorrowedProposal<'a> {
710    fn from(p: &'a CustomProposal) -> Self {
711        Self::Custom(p)
712    }
713}
714
715#[derive(Clone, Debug, PartialEq, MlsSize, MlsEncode, MlsDecode)]
716#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
717#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
718#[repr(u8)]
719pub(crate) enum ProposalOrRef {
720    Proposal(Box<Proposal>) = 1u8,
721    #[cfg(feature = "by_ref_proposal")]
722    Reference(ProposalRef) = 2u8,
723}
724
725impl From<Proposal> for ProposalOrRef {
726    fn from(proposal: Proposal) -> Self {
727        Self::Proposal(Box::new(proposal))
728    }
729}
730
731#[cfg(feature = "by_ref_proposal")]
732impl From<ProposalRef> for ProposalOrRef {
733    fn from(r: ProposalRef) -> Self {
734        Self::Reference(r)
735    }
736}