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