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(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
254/// A user defined custom proposal.
255///
256/// User defined proposals are passed through the protocol as an opaque value.
257pub struct CustomProposal {
258    proposal_type: ProposalType,
259    #[cfg_attr(feature = "serde", serde(with = "mls_rs_core::vec_serde"))]
260    data: Vec<u8>,
261}
262
263#[cfg(feature = "custom_proposal")]
264impl Debug for CustomProposal {
265    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
266        f.debug_struct("CustomProposal")
267            .field("proposal_type", &self.proposal_type)
268            .field("data", &mls_rs_core::debug::pretty_bytes(&self.data))
269            .finish()
270    }
271}
272
273#[cfg(feature = "custom_proposal")]
274impl CustomProposal {
275    /// Create a custom proposal.
276    ///
277    /// # Warning
278    ///
279    /// Avoid using the [`ProposalType`] values that have constants already
280    /// defined by this crate. Using existing constants in a custom proposal
281    /// has unspecified behavior.
282    pub fn new(proposal_type: ProposalType, data: Vec<u8>) -> Self {
283        Self {
284            proposal_type,
285            data,
286        }
287    }
288
289    /// The proposal type used for this custom proposal.
290    pub fn proposal_type(&self) -> ProposalType {
291        self.proposal_type
292    }
293
294    /// The opaque data communicated by this custom proposal.
295    pub fn data(&self) -> &[u8] {
296        &self.data
297    }
298}
299
300#[cfg(feature = "custom_proposal")]
301/// Encode/Decode for the `data` field of CustomProposal. This allows specialization over
302/// the decoding based on the ProposalType, if users want to use an encoding other than the
303/// default bytes vector (in particular, if users want the CustomProposal wrapper to be hidden
304/// in the encoding, so the underlying custom proposal can be decoded directly).
305pub trait CustomDecoder: Sized {
306    fn encode_from_bytes(
307        data: &Vec<u8>,
308        writer: &mut Vec<u8>,
309        _proposal_type: &ProposalType,
310    ) -> Result<(), mls_rs_codec::Error> {
311        mls_rs_codec::byte_vec::mls_encode(data, writer)
312    }
313    fn decode_from_bytes(
314        reader: &mut &[u8],
315        _proposal_type: &ProposalType,
316    ) -> Result<Vec<u8>, mls_rs_codec::Error> {
317        mls_rs_codec::byte_vec::mls_decode(reader)
318    }
319    fn encoded_byte_len(data: &Vec<u8>, _proposal_type: &ProposalType) -> usize {
320        mls_rs_codec::byte_vec::mls_encoded_len(data)
321    }
322}
323
324#[cfg(all(feature = "custom_proposal", not(feature = "gsma_rcs_e2ee_feature")))]
325impl CustomDecoder for CustomProposal {}
326
327#[cfg(all(feature = "custom_proposal", feature = "gsma_rcs_e2ee_feature"))]
328impl CustomDecoder for CustomProposal {
329    fn encode_from_bytes(
330        data: &Vec<u8>,
331        writer: &mut Vec<u8>,
332        proposal_type: &ProposalType,
333    ) -> Result<(), mls_rs_codec::Error> {
334        match proposal_type {
335            // directly extend with the serialized proposals; don't encode as a byte array
336            // as the length should not be included in the encoding
337            &ProposalType::RCS_SIGNATURE | &ProposalType::RCS_SERVER_REMOVE => {
338                writer.extend(data);
339                Ok(())
340            }
341            _ => mls_rs_codec::byte_vec::mls_encode(data, writer),
342        }
343    }
344    fn decode_from_bytes(
345        reader: &mut &[u8],
346        proposal_type: &ProposalType,
347    ) -> Result<Vec<u8>, mls_rs_codec::Error> {
348        match *proposal_type {
349            // empty struct
350            ProposalType::RCS_SIGNATURE => Ok(Vec::new()),
351            // remove proposal
352            ProposalType::RCS_SERVER_REMOVE => {
353                let decoded = RemoveProposal::mls_decode(reader)?;
354                let mut writer = Vec::new();
355                RemoveProposal::mls_encode(&decoded, &mut writer)?;
356                // return, to be used in the data field of CustomProposal, the encoded proposal
357                Ok(writer)
358            }
359            _ => mls_rs_codec::byte_vec::mls_decode(reader),
360        }
361    }
362    fn encoded_byte_len(data: &Vec<u8>, proposal_type: &ProposalType) -> usize {
363        match proposal_type {
364            &ProposalType::RCS_SIGNATURE | &ProposalType::RCS_SERVER_REMOVE => data.len(),
365            _ => mls_rs_codec::byte_vec::mls_encoded_len(data),
366        }
367    }
368}
369
370/// Trait to simplify creating custom proposals that are serialized with MLS
371/// encoding.
372#[cfg(feature = "custom_proposal")]
373pub trait MlsCustomProposal: MlsSize + MlsEncode + MlsDecode + Sized {
374    fn proposal_type() -> ProposalType;
375
376    fn to_custom_proposal(&self) -> Result<CustomProposal, mls_rs_codec::Error> {
377        Ok(CustomProposal::new(
378            Self::proposal_type(),
379            self.mls_encode_to_vec()?,
380        ))
381    }
382
383    fn from_custom_proposal(proposal: &CustomProposal) -> Result<Self, mls_rs_codec::Error> {
384        if proposal.proposal_type() != Self::proposal_type() {
385            // #[cfg(feature = "std")]
386            // return Err(mls_rs_codec::Error::Custom(
387            //     "invalid proposal type".to_string(),
388            // ));
389
390            //#[cfg(not(feature = "std"))]
391            return Err(mls_rs_codec::Error::Custom(4));
392        }
393
394        Self::mls_decode(&mut proposal.data())
395    }
396}
397
398#[allow(clippy::large_enum_variant)]
399#[derive(Clone, Debug, PartialEq)]
400#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
401#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
402#[repr(u16)]
403#[non_exhaustive]
404/// An enum that represents all possible types of proposals.
405pub enum Proposal {
406    Add(alloc::boxed::Box<AddProposal>),
407    #[cfg(feature = "by_ref_proposal")]
408    Update(UpdateProposal),
409    Remove(RemoveProposal),
410    #[cfg(feature = "psk")]
411    Psk(PreSharedKeyProposal),
412    ReInit(ReInitProposal),
413    ExternalInit(ExternalInit),
414    GroupContextExtensions(ExtensionList),
415    #[cfg(all(
416        feature = "by_ref_proposal",
417        feature = "custom_proposal",
418        feature = "self_remove_proposal"
419    ))]
420    SelfRemove(SelfRemoveProposal),
421    #[cfg(feature = "custom_proposal")]
422    Custom(CustomProposal),
423}
424
425impl MlsSize for Proposal {
426    fn mls_encoded_len(&self) -> usize {
427        let inner_len = match self {
428            Proposal::Add(p) => p.mls_encoded_len(),
429            #[cfg(feature = "by_ref_proposal")]
430            Proposal::Update(p) => p.mls_encoded_len(),
431            Proposal::Remove(p) => p.mls_encoded_len(),
432            #[cfg(feature = "psk")]
433            Proposal::Psk(p) => p.mls_encoded_len(),
434            Proposal::ReInit(p) => p.mls_encoded_len(),
435            Proposal::ExternalInit(p) => p.mls_encoded_len(),
436            Proposal::GroupContextExtensions(p) => p.mls_encoded_len(),
437            #[cfg(all(
438                feature = "by_ref_proposal",
439                feature = "custom_proposal",
440                feature = "self_remove_proposal"
441            ))]
442            Proposal::SelfRemove(p) => p.mls_encoded_len(),
443            #[cfg(feature = "custom_proposal")]
444            Proposal::Custom(p) => CustomProposal::encoded_byte_len(&p.data, &p.proposal_type),
445        };
446
447        self.proposal_type().mls_encoded_len() + inner_len
448    }
449}
450
451impl MlsEncode for Proposal {
452    fn mls_encode(&self, writer: &mut Vec<u8>) -> Result<(), mls_rs_codec::Error> {
453        self.proposal_type().mls_encode(writer)?;
454
455        match self {
456            Proposal::Add(p) => p.mls_encode(writer),
457            #[cfg(feature = "by_ref_proposal")]
458            Proposal::Update(p) => p.mls_encode(writer),
459            Proposal::Remove(p) => p.mls_encode(writer),
460            #[cfg(feature = "psk")]
461            Proposal::Psk(p) => p.mls_encode(writer),
462            Proposal::ReInit(p) => p.mls_encode(writer),
463            Proposal::ExternalInit(p) => p.mls_encode(writer),
464            Proposal::GroupContextExtensions(p) => p.mls_encode(writer),
465            #[cfg(all(
466                feature = "by_ref_proposal",
467                feature = "custom_proposal",
468                feature = "self_remove_proposal"
469            ))]
470            Proposal::SelfRemove(p) => p.mls_encode(writer),
471            #[cfg(feature = "custom_proposal")]
472            Proposal::Custom(p) => {
473                if p.proposal_type.raw_value() <= 7 {
474                    // #[cfg(feature = "std")]
475                    // return Err(mls_rs_codec::Error::Custom(
476                    //     "custom proposal types can not be set to defined values of 0-7".to_string(),
477                    // ));
478
479                    // #[cfg(not(feature = "std"))]
480                    return Err(mls_rs_codec::Error::Custom(2));
481                }
482                CustomProposal::encode_from_bytes(&p.data, writer, &p.proposal_type)
483            }
484        }
485    }
486}
487
488impl MlsDecode for Proposal {
489    fn mls_decode(reader: &mut &[u8]) -> Result<Self, mls_rs_codec::Error> {
490        let proposal_type = ProposalType::mls_decode(reader)?;
491
492        Ok(match proposal_type {
493            ProposalType::ADD => {
494                Proposal::Add(alloc::boxed::Box::new(AddProposal::mls_decode(reader)?))
495            }
496            #[cfg(feature = "by_ref_proposal")]
497            ProposalType::UPDATE => Proposal::Update(UpdateProposal::mls_decode(reader)?),
498            ProposalType::REMOVE => Proposal::Remove(RemoveProposal::mls_decode(reader)?),
499            #[cfg(feature = "psk")]
500            ProposalType::PSK => Proposal::Psk(PreSharedKeyProposal::mls_decode(reader)?),
501            ProposalType::RE_INIT => Proposal::ReInit(ReInitProposal::mls_decode(reader)?),
502            ProposalType::EXTERNAL_INIT => {
503                Proposal::ExternalInit(ExternalInit::mls_decode(reader)?)
504            }
505            ProposalType::GROUP_CONTEXT_EXTENSIONS => {
506                Proposal::GroupContextExtensions(ExtensionList::mls_decode(reader)?)
507            }
508            #[cfg(all(
509                feature = "by_ref_proposal",
510                feature = "custom_proposal",
511                feature = "self_remove_proposal"
512            ))]
513            ProposalType::SELF_REMOVE => {
514                Proposal::SelfRemove(SelfRemoveProposal::mls_decode(reader)?)
515            }
516            #[cfg(feature = "custom_proposal")]
517            custom => Proposal::Custom(CustomProposal {
518                proposal_type: custom,
519                data: CustomProposal::decode_from_bytes(reader, &custom)?,
520            }),
521            // TODO fix test dependency on openssl loading codec with default features
522            #[cfg(not(feature = "custom_proposal"))]
523            _ => return Err(mls_rs_codec::Error::Custom(3)),
524        })
525    }
526}
527
528impl Proposal {
529    pub fn proposal_type(&self) -> ProposalType {
530        match self {
531            Proposal::Add(_) => ProposalType::ADD,
532            #[cfg(feature = "by_ref_proposal")]
533            Proposal::Update(_) => ProposalType::UPDATE,
534            Proposal::Remove(_) => ProposalType::REMOVE,
535            #[cfg(feature = "psk")]
536            Proposal::Psk(_) => ProposalType::PSK,
537            Proposal::ReInit(_) => ProposalType::RE_INIT,
538            Proposal::ExternalInit(_) => ProposalType::EXTERNAL_INIT,
539            Proposal::GroupContextExtensions(_) => ProposalType::GROUP_CONTEXT_EXTENSIONS,
540            #[cfg(all(
541                feature = "by_ref_proposal",
542                feature = "custom_proposal",
543                feature = "self_remove_proposal"
544            ))]
545            Proposal::SelfRemove(_) => ProposalType::SELF_REMOVE,
546            #[cfg(feature = "custom_proposal")]
547            Proposal::Custom(c) => c.proposal_type,
548        }
549    }
550}
551
552#[derive(Clone, Debug, PartialEq)]
553/// An enum that represents a borrowed version of [`Proposal`].
554pub enum BorrowedProposal<'a> {
555    Add(&'a AddProposal),
556    #[cfg(feature = "by_ref_proposal")]
557    Update(&'a UpdateProposal),
558    Remove(&'a RemoveProposal),
559    #[cfg(feature = "psk")]
560    Psk(&'a PreSharedKeyProposal),
561    ReInit(&'a ReInitProposal),
562    ExternalInit(&'a ExternalInit),
563    GroupContextExtensions(&'a ExtensionList),
564    #[cfg(all(
565        feature = "by_ref_proposal",
566        feature = "custom_proposal",
567        feature = "self_remove_proposal"
568    ))]
569    SelfRemove(&'a SelfRemoveProposal),
570    #[cfg(feature = "custom_proposal")]
571    Custom(&'a CustomProposal),
572}
573
574impl<'a> From<BorrowedProposal<'a>> for Proposal {
575    fn from(value: BorrowedProposal<'a>) -> Self {
576        match value {
577            BorrowedProposal::Add(add) => Proposal::Add(alloc::boxed::Box::new(add.clone())),
578            #[cfg(feature = "by_ref_proposal")]
579            BorrowedProposal::Update(update) => Proposal::Update(update.clone()),
580            BorrowedProposal::Remove(remove) => Proposal::Remove(remove.clone()),
581            #[cfg(feature = "psk")]
582            BorrowedProposal::Psk(psk) => Proposal::Psk(psk.clone()),
583            BorrowedProposal::ReInit(reinit) => Proposal::ReInit(reinit.clone()),
584            BorrowedProposal::ExternalInit(external) => Proposal::ExternalInit(external.clone()),
585            BorrowedProposal::GroupContextExtensions(ext) => {
586                Proposal::GroupContextExtensions(ext.clone())
587            }
588            #[cfg(all(
589                feature = "by_ref_proposal",
590                feature = "custom_proposal",
591                feature = "self_remove_proposal"
592            ))]
593            BorrowedProposal::SelfRemove(self_remove) => Proposal::SelfRemove(self_remove.clone()),
594            #[cfg(feature = "custom_proposal")]
595            BorrowedProposal::Custom(custom) => Proposal::Custom(custom.clone()),
596        }
597    }
598}
599
600impl BorrowedProposal<'_> {
601    pub fn proposal_type(&self) -> ProposalType {
602        match self {
603            BorrowedProposal::Add(_) => ProposalType::ADD,
604            #[cfg(feature = "by_ref_proposal")]
605            BorrowedProposal::Update(_) => ProposalType::UPDATE,
606            BorrowedProposal::Remove(_) => ProposalType::REMOVE,
607            #[cfg(feature = "psk")]
608            BorrowedProposal::Psk(_) => ProposalType::PSK,
609            BorrowedProposal::ReInit(_) => ProposalType::RE_INIT,
610            BorrowedProposal::ExternalInit(_) => ProposalType::EXTERNAL_INIT,
611            BorrowedProposal::GroupContextExtensions(_) => ProposalType::GROUP_CONTEXT_EXTENSIONS,
612            #[cfg(all(
613                feature = "by_ref_proposal",
614                feature = "custom_proposal",
615                feature = "self_remove_proposal"
616            ))]
617            BorrowedProposal::SelfRemove(_) => ProposalType::SELF_REMOVE,
618            #[cfg(feature = "custom_proposal")]
619            BorrowedProposal::Custom(c) => c.proposal_type,
620        }
621    }
622}
623
624impl<'a> From<&'a Proposal> for BorrowedProposal<'a> {
625    fn from(p: &'a Proposal) -> Self {
626        match p {
627            Proposal::Add(p) => BorrowedProposal::Add(p),
628            #[cfg(feature = "by_ref_proposal")]
629            Proposal::Update(p) => BorrowedProposal::Update(p),
630            Proposal::Remove(p) => BorrowedProposal::Remove(p),
631            #[cfg(feature = "psk")]
632            Proposal::Psk(p) => BorrowedProposal::Psk(p),
633            Proposal::ReInit(p) => BorrowedProposal::ReInit(p),
634            Proposal::ExternalInit(p) => BorrowedProposal::ExternalInit(p),
635            Proposal::GroupContextExtensions(p) => BorrowedProposal::GroupContextExtensions(p),
636            #[cfg(all(
637                feature = "by_ref_proposal",
638                feature = "custom_proposal",
639                feature = "self_remove_proposal"
640            ))]
641            Proposal::SelfRemove(p) => BorrowedProposal::SelfRemove(p),
642            #[cfg(feature = "custom_proposal")]
643            Proposal::Custom(p) => BorrowedProposal::Custom(p),
644        }
645    }
646}
647
648impl<'a> From<&'a AddProposal> for BorrowedProposal<'a> {
649    fn from(p: &'a AddProposal) -> Self {
650        Self::Add(p)
651    }
652}
653
654#[cfg(feature = "by_ref_proposal")]
655impl<'a> From<&'a UpdateProposal> for BorrowedProposal<'a> {
656    fn from(p: &'a UpdateProposal) -> Self {
657        Self::Update(p)
658    }
659}
660
661impl<'a> From<&'a RemoveProposal> for BorrowedProposal<'a> {
662    fn from(p: &'a RemoveProposal) -> Self {
663        Self::Remove(p)
664    }
665}
666
667#[cfg(feature = "psk")]
668impl<'a> From<&'a PreSharedKeyProposal> for BorrowedProposal<'a> {
669    fn from(p: &'a PreSharedKeyProposal) -> Self {
670        Self::Psk(p)
671    }
672}
673
674impl<'a> From<&'a ReInitProposal> for BorrowedProposal<'a> {
675    fn from(p: &'a ReInitProposal) -> Self {
676        Self::ReInit(p)
677    }
678}
679
680impl<'a> From<&'a ExternalInit> for BorrowedProposal<'a> {
681    fn from(p: &'a ExternalInit) -> Self {
682        Self::ExternalInit(p)
683    }
684}
685
686impl<'a> From<&'a ExtensionList> for BorrowedProposal<'a> {
687    fn from(p: &'a ExtensionList) -> Self {
688        Self::GroupContextExtensions(p)
689    }
690}
691
692#[cfg(all(
693    feature = "by_ref_proposal",
694    feature = "custom_proposal",
695    feature = "self_remove_proposal"
696))]
697impl<'a> From<&'a SelfRemoveProposal> for BorrowedProposal<'a> {
698    fn from(p: &'a SelfRemoveProposal) -> Self {
699        Self::SelfRemove(p)
700    }
701}
702
703#[cfg(feature = "custom_proposal")]
704impl<'a> From<&'a CustomProposal> for BorrowedProposal<'a> {
705    fn from(p: &'a CustomProposal) -> Self {
706        Self::Custom(p)
707    }
708}
709
710#[derive(Clone, Debug, PartialEq, MlsSize, MlsEncode, MlsDecode)]
711#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
712#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
713#[repr(u8)]
714pub(crate) enum ProposalOrRef {
715    Proposal(Box<Proposal>) = 1u8,
716    #[cfg(feature = "by_ref_proposal")]
717    Reference(ProposalRef) = 2u8,
718}
719
720impl From<Proposal> for ProposalOrRef {
721    fn from(proposal: Proposal) -> Self {
722        Self::Proposal(Box::new(proposal))
723    }
724}
725
726#[cfg(feature = "by_ref_proposal")]
727impl From<ProposalRef> for ProposalOrRef {
728    fn from(r: ProposalRef) -> Self {
729        Self::Reference(r)
730    }
731}