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