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