1use std::io::{Read, Write};
6
7use openmls_traits::{crypto::OpenMlsCrypto, types::Ciphersuite};
8use serde::{Deserialize, Serialize};
9use thiserror::Error;
10use tls_codec::{
11 Deserialize as TlsDeserializeTrait, DeserializeBytes, Error, Serialize as TlsSerializeTrait,
12 Size, TlsDeserialize, TlsDeserializeBytes, TlsSerialize, TlsSize, VLBytes,
13};
14
15use crate::{
16 binary_tree::array_representation::LeafNodeIndex,
17 ciphersuite::hash_ref::{make_proposal_ref, KeyPackageRef, ProposalRef},
18 error::LibraryError,
19 extensions::Extensions,
20 framing::{
21 mls_auth_content::AuthenticatedContent, mls_content::FramedContentBody, ContentType,
22 },
23 group::{GroupContext, GroupId},
24 key_packages::*,
25 schedule::psk::*,
26 treesync::LeafNode,
27 versions::ProtocolVersion,
28};
29
30#[cfg(feature = "extensions-draft")]
31use crate::component::ComponentId;
32
33#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Debug, Hash)]
76#[cfg_attr(
77 feature = "0-8-1-storage-format",
78 derive(serde::Serialize, serde::Deserialize)
79)]
80#[cfg_attr(
81 not(feature = "0-8-1-storage-format"),
82 derive(
83 openmls_serialization_helpers::Serialize,
84 openmls_serialization_helpers::Deserialize,
85 )
86)]
87#[allow(missing_docs)]
88#[repr(u16)]
89pub enum ProposalType {
90 #[cfg_attr(not(feature = "0-8-1-storage-format"), storage_tag = 0)]
91 Add,
92 #[cfg_attr(not(feature = "0-8-1-storage-format"), storage_tag = 1)]
93 Update,
94 #[cfg_attr(not(feature = "0-8-1-storage-format"), storage_tag = 2)]
95 Remove,
96 #[cfg_attr(not(feature = "0-8-1-storage-format"), storage_tag = 3)]
97 PreSharedKey,
98 #[cfg_attr(not(feature = "0-8-1-storage-format"), storage_tag = 4)]
99 Reinit,
100 #[cfg_attr(not(feature = "0-8-1-storage-format"), storage_tag = 5)]
101 ExternalInit,
102 #[cfg_attr(not(feature = "0-8-1-storage-format"), storage_tag = 6)]
103 GroupContextExtensions,
104 #[cfg_attr(not(feature = "0-8-1-storage-format"), storage_tag = 8)]
106 SelfRemove,
107 #[cfg(feature = "extensions-draft")]
108 #[cfg_attr(not(feature = "0-8-1-storage-format"), storage_tag = 11)]
109 AppEphemeral,
110 #[cfg(feature = "extensions-draft")]
111 #[cfg_attr(not(feature = "0-8-1-storage-format"), storage_tag = 12)]
112 AppDataUpdate,
113 #[cfg_attr(not(feature = "0-8-1-storage-format"), storage_tag = 10)]
114 Grease(u16),
115 #[cfg_attr(not(feature = "0-8-1-storage-format"), storage_tag = 9)]
116 Custom(u16),
117}
118
119impl ProposalType {
120 pub(crate) fn is_default(self) -> bool {
123 match self {
124 ProposalType::Add
125 | ProposalType::Update
126 | ProposalType::Remove
127 | ProposalType::PreSharedKey
128 | ProposalType::Reinit
129 | ProposalType::ExternalInit
130 | ProposalType::GroupContextExtensions => true,
131 ProposalType::SelfRemove | ProposalType::Grease(_) | ProposalType::Custom(_) => false,
132 #[cfg(feature = "extensions-draft")]
133 ProposalType::AppEphemeral | ProposalType::AppDataUpdate => false,
134 }
135 }
136
137 pub fn is_grease(&self) -> bool {
142 matches!(self, ProposalType::Grease(_))
143 }
144}
145
146impl Size for ProposalType {
147 fn tls_serialized_len(&self) -> usize {
148 2
149 }
150}
151
152impl TlsDeserializeTrait for ProposalType {
153 fn tls_deserialize<R: Read>(bytes: &mut R) -> Result<Self, Error>
154 where
155 Self: Sized,
156 {
157 let mut proposal_type = [0u8; 2];
158 bytes.read_exact(&mut proposal_type)?;
159
160 Ok(ProposalType::from(u16::from_be_bytes(proposal_type)))
161 }
162}
163
164impl TlsSerializeTrait for ProposalType {
165 fn tls_serialize<W: Write>(&self, writer: &mut W) -> Result<usize, Error> {
166 writer.write_all(&u16::from(*self).to_be_bytes())?;
167
168 Ok(2)
169 }
170}
171
172impl DeserializeBytes for ProposalType {
173 fn tls_deserialize_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), Error>
174 where
175 Self: Sized,
176 {
177 let mut bytes_ref = bytes;
178 let proposal_type = ProposalType::tls_deserialize(&mut bytes_ref)?;
179 Ok((proposal_type, bytes_ref))
180 }
181}
182
183impl ProposalType {
184 pub fn is_path_required(&self) -> bool {
186 matches!(
187 self,
188 Self::Update
189 | Self::Remove
190 | Self::ExternalInit
191 | Self::GroupContextExtensions
192 | Self::SelfRemove
193 )
194 }
195}
196
197impl From<u16> for ProposalType {
198 fn from(value: u16) -> Self {
199 match value {
200 1 => ProposalType::Add,
201 2 => ProposalType::Update,
202 3 => ProposalType::Remove,
203 4 => ProposalType::PreSharedKey,
204 5 => ProposalType::Reinit,
205 6 => ProposalType::ExternalInit,
206 7 => ProposalType::GroupContextExtensions,
207 #[cfg(feature = "extensions-draft")]
208 8 => ProposalType::AppDataUpdate,
209 #[cfg(feature = "extensions-draft")]
210 0x0009 => ProposalType::AppEphemeral,
211 0x000a => ProposalType::SelfRemove,
212 other if crate::grease::is_grease_value(other) => ProposalType::Grease(other),
213 other => ProposalType::Custom(other),
214 }
215 }
216}
217
218impl From<ProposalType> for u16 {
219 fn from(value: ProposalType) -> Self {
220 match value {
221 ProposalType::Add => 1,
222 ProposalType::Update => 2,
223 ProposalType::Remove => 3,
224 ProposalType::PreSharedKey => 4,
225 ProposalType::Reinit => 5,
226 ProposalType::ExternalInit => 6,
227 ProposalType::GroupContextExtensions => 7,
228 #[cfg(feature = "extensions-draft")]
229 ProposalType::AppDataUpdate => 8,
230 #[cfg(feature = "extensions-draft")]
231 ProposalType::AppEphemeral => 0x0009,
232 ProposalType::SelfRemove => 0x000a,
233 ProposalType::Grease(id) => id,
234 ProposalType::Custom(id) => id,
235 }
236 }
237}
238
239#[derive(Debug, PartialEq, Clone)]
259#[cfg_attr(
260 feature = "0-8-1-storage-format",
261 derive(serde::Serialize, serde::Deserialize)
262)]
263#[cfg_attr(
264 not(feature = "0-8-1-storage-format"),
265 derive(
266 openmls_serialization_helpers::Serialize,
267 openmls_serialization_helpers::Deserialize,
268 )
269)]
270#[allow(missing_docs)]
271pub enum Proposal {
272 #[cfg_attr(not(feature = "0-8-1-storage-format"), storage_tag = 0)]
273 Add(Box<AddProposal>),
274 #[cfg_attr(not(feature = "0-8-1-storage-format"), storage_tag = 1)]
275 Update(Box<UpdateProposal>),
276 #[cfg_attr(not(feature = "0-8-1-storage-format"), storage_tag = 2)]
277 Remove(Box<RemoveProposal>),
278 #[cfg_attr(not(feature = "0-8-1-storage-format"), storage_tag = 3)]
279 PreSharedKey(Box<PreSharedKeyProposal>),
280 #[cfg_attr(not(feature = "0-8-1-storage-format"), storage_tag = 4)]
281 ReInit(Box<ReInitProposal>),
282 #[cfg_attr(not(feature = "0-8-1-storage-format"), storage_tag = 5)]
283 ExternalInit(Box<ExternalInitProposal>),
284 #[cfg_attr(not(feature = "0-8-1-storage-format"), storage_tag = 6)]
285 GroupContextExtensions(Box<GroupContextExtensionProposal>),
286 #[cfg(feature = "extensions-draft")]
289 #[cfg_attr(not(feature = "0-8-1-storage-format"), storage_tag = 10)]
290 AppDataUpdate(Box<AppDataUpdateProposal>),
291 #[cfg_attr(not(feature = "0-8-1-storage-format"), storage_tag = 8)]
293 SelfRemove,
294 #[cfg(feature = "extensions-draft")]
295 #[cfg_attr(not(feature = "0-8-1-storage-format"), storage_tag = 11)]
296 AppEphemeral(Box<AppEphemeralProposal>),
297 #[cfg_attr(not(feature = "0-8-1-storage-format"), storage_tag = 9)]
298 Custom(Box<CustomProposal>),
299}
300
301impl Proposal {
302 pub(crate) fn remove(r: RemoveProposal) -> Self {
304 Self::Remove(Box::new(r))
305 }
306
307 pub(crate) fn add(a: AddProposal) -> Self {
309 Self::Add(Box::new(a))
310 }
311
312 pub(crate) fn custom(c: CustomProposal) -> Self {
314 Self::Custom(Box::new(c))
315 }
316
317 pub(crate) fn psk(p: PreSharedKeyProposal) -> Self {
319 Self::PreSharedKey(Box::new(p))
320 }
321
322 pub(crate) fn update(p: UpdateProposal) -> Self {
324 Self::Update(Box::new(p))
325 }
326
327 pub(crate) fn group_context_extensions(p: GroupContextExtensionProposal) -> Self {
329 Self::GroupContextExtensions(Box::new(p))
330 }
331
332 pub(crate) fn external_init(p: ExternalInitProposal) -> Self {
334 Self::ExternalInit(Box::new(p))
335 }
336
337 pub fn re_init(p: ReInitProposal) -> Self {
339 Self::ReInit(Box::new(p))
340 }
341
342 pub fn proposal_type(&self) -> ProposalType {
344 match self {
345 Proposal::Add(_) => ProposalType::Add,
346 Proposal::Update(_) => ProposalType::Update,
347 Proposal::Remove(_) => ProposalType::Remove,
348 Proposal::PreSharedKey(_) => ProposalType::PreSharedKey,
349 Proposal::ReInit(_) => ProposalType::Reinit,
350 Proposal::ExternalInit(_) => ProposalType::ExternalInit,
351 Proposal::GroupContextExtensions(_) => ProposalType::GroupContextExtensions,
352 #[cfg(feature = "extensions-draft")]
353 Proposal::AppDataUpdate(_) => ProposalType::AppDataUpdate,
354 Proposal::SelfRemove => ProposalType::SelfRemove,
355 #[cfg(feature = "extensions-draft")]
356 Proposal::AppEphemeral(_) => ProposalType::AppEphemeral,
357 Proposal::Custom(custom) => ProposalType::Custom(custom.proposal_type.to_owned()),
358 }
359 }
360
361 pub(crate) fn is_type(&self, proposal_type: ProposalType) -> bool {
362 self.proposal_type() == proposal_type
363 }
364
365 pub fn is_path_required(&self) -> bool {
367 self.proposal_type().is_path_required()
368 }
369
370 pub(crate) fn has_lower_priority_than(&self, new_proposal: &Proposal) -> bool {
371 match (self, new_proposal) {
372 (Proposal::Update(_), _) => true,
374 (Proposal::Remove(_), Proposal::Update(_)) => false,
376 (Proposal::Remove(_), Proposal::Remove(_)) => true,
378 (_, Proposal::SelfRemove) => true,
380 (Proposal::SelfRemove, Proposal::Update(_) | Proposal::Remove(_)) => false,
381 _ => {
382 debug_assert!(false);
383 false
384 }
385 }
386 }
387
388 pub(crate) fn as_remove(&self) -> Option<&RemoveProposal> {
390 if let Self::Remove(v) = self {
391 Some(v)
392 } else {
393 None
394 }
395 }
396
397 #[must_use]
401 pub fn is_remove(&self) -> bool {
402 matches!(self, Self::Remove(..))
403 }
404}
405
406#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, TlsSerialize, TlsSize)]
418pub struct AddProposal {
419 pub(crate) key_package: KeyPackage,
420}
421
422impl AddProposal {
423 pub fn key_package(&self) -> &KeyPackage {
425 &self.key_package
426 }
427}
428
429impl From<KeyPackage> for AddProposal {
430 fn from(key_package: KeyPackage) -> AddProposal {
431 AddProposal { key_package }
432 }
433}
434
435#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, TlsSerialize, TlsSize)]
448pub struct UpdateProposal {
449 pub(crate) leaf_node: LeafNode,
450}
451
452impl UpdateProposal {
453 pub fn leaf_node(&self) -> &LeafNode {
455 &self.leaf_node
456 }
457}
458
459#[derive(
471 Debug,
472 PartialEq,
473 Eq,
474 Clone,
475 Serialize,
476 Deserialize,
477 TlsDeserialize,
478 TlsDeserializeBytes,
479 TlsSerialize,
480 TlsSize,
481)]
482pub struct RemoveProposal {
483 pub(crate) removed: LeafNodeIndex,
484}
485
486impl RemoveProposal {
487 pub fn removed(&self) -> LeafNodeIndex {
489 self.removed
490 }
491}
492
493#[derive(
505 Debug,
506 PartialEq,
507 Eq,
508 Clone,
509 Serialize,
510 Deserialize,
511 TlsDeserialize,
512 TlsDeserializeBytes,
513 TlsSerialize,
514 TlsSize,
515)]
516pub struct PreSharedKeyProposal {
517 psk: PreSharedKeyId,
518}
519
520impl PreSharedKeyProposal {
521 pub fn new(psk: PreSharedKeyId) -> Self {
523 Self { psk }
524 }
525
526 pub fn psk(&self) -> &PreSharedKeyId {
528 &self.psk
529 }
530
531 pub(crate) fn into_psk_id(self) -> PreSharedKeyId {
533 self.psk
534 }
535}
536
537#[derive(
554 Debug,
555 PartialEq,
556 Eq,
557 Clone,
558 Serialize,
559 Deserialize,
560 TlsDeserialize,
561 TlsDeserializeBytes,
562 TlsSerialize,
563 TlsSize,
564)]
565pub struct ReInitProposal {
566 pub(crate) group_id: GroupId,
567 pub(crate) version: ProtocolVersion,
568 pub(crate) ciphersuite: Ciphersuite,
569 pub(crate) extensions: Extensions<GroupContext>,
570}
571
572impl ReInitProposal {
573 pub fn new(
576 group_id: GroupId,
577 version: ProtocolVersion,
578 ciphersuite: Ciphersuite,
579 extensions: Extensions<GroupContext>,
580 ) -> Self {
581 Self {
582 group_id,
583 version,
584 ciphersuite,
585 extensions,
586 }
587 }
588
589 pub fn group_id(&self) -> &GroupId {
591 &self.group_id
592 }
593
594 pub fn version(&self) -> ProtocolVersion {
596 self.version
597 }
598
599 pub fn ciphersuite(&self) -> Ciphersuite {
601 self.ciphersuite
602 }
603
604 pub fn extensions(&self) -> &Extensions<GroupContext> {
606 &self.extensions
607 }
608}
609
610#[derive(
622 Debug,
623 PartialEq,
624 Eq,
625 Clone,
626 Serialize,
627 Deserialize,
628 TlsDeserialize,
629 TlsDeserializeBytes,
630 TlsSerialize,
631 TlsSize,
632)]
633pub struct ExternalInitProposal {
634 kem_output: VLBytes,
635}
636
637impl ExternalInitProposal {
638 pub(crate) fn kem_output(&self) -> &[u8] {
640 self.kem_output.as_slice()
641 }
642}
643
644impl From<Vec<u8>> for ExternalInitProposal {
645 fn from(kem_output: Vec<u8>) -> Self {
646 ExternalInitProposal {
647 kem_output: kem_output.into(),
648 }
649 }
650}
651
652#[cfg(feature = "extensions-draft")]
653#[derive(
657 Debug,
658 PartialEq,
659 Clone,
660 Serialize,
661 Deserialize,
662 TlsDeserialize,
663 TlsDeserializeBytes,
664 TlsSerialize,
665 TlsSize,
666)]
667pub struct AppAck {
668 received_ranges: Vec<MessageRange>,
669}
670
671#[cfg(feature = "extensions-draft")]
672#[derive(
674 Debug,
675 PartialEq,
676 Clone,
677 Serialize,
678 Deserialize,
679 TlsDeserialize,
680 TlsDeserializeBytes,
681 TlsSerialize,
682 TlsSize,
683)]
684pub struct AppEphemeralProposal {
685 component_id: ComponentId,
687 data: VLBytes,
689}
690
691#[cfg(feature = "extensions-draft")]
692impl AppEphemeralProposal {
693 pub fn new(component_id: ComponentId, data: Vec<u8>) -> Self {
695 Self {
696 component_id,
697 data: data.into(),
698 }
699 }
700 pub fn component_id(&self) -> ComponentId {
702 self.component_id
703 }
704
705 pub fn data(&self) -> &[u8] {
707 self.data.as_slice()
708 }
709}
710
711#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
723pub struct GroupContextExtensionProposal {
724 extensions: Extensions<GroupContext>,
725}
726
727impl Size for GroupContextExtensionProposal {
728 fn tls_serialized_len(&self) -> usize {
729 self.extensions.tls_serialized_len()
730 }
731}
732
733impl TlsSerializeTrait for GroupContextExtensionProposal {
734 fn tls_serialize<W: Write>(&self, writer: &mut W) -> Result<usize, Error> {
735 self.extensions.tls_serialize(writer)
736 }
737}
738
739impl GroupContextExtensionProposal {
740 pub(crate) fn new(extensions: Extensions<GroupContext>) -> Self {
742 Self { extensions }
743 }
744
745 pub fn extensions(&self) -> &Extensions<GroupContext> {
747 &self.extensions
748 }
749
750 pub fn into_extensions(self) -> Extensions<GroupContext> {
752 self.extensions
753 }
754}
755
756#[derive(
779 PartialEq,
780 Clone,
781 Copy,
782 Debug,
783 TlsSerialize,
784 TlsDeserialize,
785 TlsDeserializeBytes,
786 TlsSize,
787 Serialize,
788 Deserialize,
789)]
790#[repr(u8)]
791pub enum ProposalOrRefType {
792 Proposal = 1,
794 Reference = 2,
796}
797
798#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, TlsSerialize, TlsSize)]
800#[repr(u8)]
801#[allow(missing_docs)]
802pub enum ProposalOrRef {
803 #[tls_codec(discriminant = 1)]
804 Proposal(Box<Proposal>),
805 Reference(Box<ProposalRef>),
806}
807
808impl ProposalOrRef {
809 pub(crate) fn proposal(p: Proposal) -> Self {
811 Self::Proposal(Box::new(p))
812 }
813
814 pub(crate) fn reference(p: ProposalRef) -> Self {
816 Self::Reference(Box::new(p))
817 }
818
819 pub(crate) fn as_proposal(&self) -> Option<&Proposal> {
820 if let Self::Proposal(v) = self {
821 Some(v)
822 } else {
823 None
824 }
825 }
826
827 pub(crate) fn as_reference(&self) -> Option<&ProposalRef> {
828 if let Self::Reference(v) = self {
829 Some(v)
830 } else {
831 None
832 }
833 }
834}
835
836impl From<Proposal> for ProposalOrRef {
837 fn from(value: Proposal) -> Self {
838 Self::proposal(value)
839 }
840}
841
842impl From<ProposalRef> for ProposalOrRef {
843 fn from(value: ProposalRef) -> Self {
844 Self::reference(value)
845 }
846}
847
848#[derive(Error, Debug)]
849pub(crate) enum ProposalRefError {
850 #[error("Expected `Proposal`, got `{wrong:?}`.")]
851 AuthenticatedContentHasWrongType { wrong: ContentType },
852 #[error(transparent)]
853 Other(#[from] LibraryError),
854}
855
856impl ProposalRef {
857 pub(crate) fn from_authenticated_content_by_ref(
858 crypto: &impl OpenMlsCrypto,
859 ciphersuite: Ciphersuite,
860 authenticated_content: &AuthenticatedContent,
861 ) -> Result<Self, ProposalRefError> {
862 if !matches!(
863 authenticated_content.content(),
864 FramedContentBody::Proposal(_)
865 ) {
866 return Err(ProposalRefError::AuthenticatedContentHasWrongType {
867 wrong: authenticated_content.content().content_type(),
868 });
869 };
870
871 let encoded = authenticated_content
872 .tls_serialize_detached()
873 .map_err(|error| ProposalRefError::Other(LibraryError::missing_bound_check(error)))?;
874
875 make_proposal_ref(&encoded, ciphersuite, crypto)
876 .map_err(|error| ProposalRefError::Other(LibraryError::unexpected_crypto_error(error)))
877 }
878
879 pub(crate) fn from_raw_proposal(
885 ciphersuite: Ciphersuite,
886 crypto: &impl OpenMlsCrypto,
887 proposal: &Proposal,
888 ) -> Result<Self, LibraryError> {
889 let mut data = b"Internal OpenMLS ProposalRef Label".to_vec();
891
892 let mut encoded = proposal
893 .tls_serialize_detached()
894 .map_err(LibraryError::missing_bound_check)?;
895
896 data.append(&mut encoded);
897
898 make_proposal_ref(&data, ciphersuite, crypto).map_err(LibraryError::unexpected_crypto_error)
899 }
900}
901
902#[derive(
910 Debug,
911 PartialEq,
912 Clone,
913 Serialize,
914 Deserialize,
915 TlsDeserialize,
916 TlsDeserializeBytes,
917 TlsSerialize,
918 TlsSize,
919)]
920pub(crate) struct MessageRange {
921 sender: KeyPackageRef,
922 first_generation: u32,
923 last_generation: u32,
924}
925
926#[cfg(feature = "extensions-draft")]
927mod app_data_update;
928#[cfg(feature = "extensions-draft")]
929pub use app_data_update::*;
930
931#[derive(
933 Debug,
934 PartialEq,
935 Clone,
936 Serialize,
937 Deserialize,
938 TlsSize,
939 TlsSerialize,
940 TlsDeserialize,
941 TlsDeserializeBytes,
942)]
943pub struct CustomProposal {
944 proposal_type: u16,
945 payload: Vec<u8>,
946}
947
948impl CustomProposal {
949 pub fn new(proposal_type: u16, payload: Vec<u8>) -> Self {
951 Self {
952 proposal_type,
953 payload,
954 }
955 }
956
957 pub fn proposal_type(&self) -> u16 {
959 self.proposal_type
960 }
961
962 pub fn payload(&self) -> &[u8] {
964 &self.payload
965 }
966}
967
968#[cfg(test)]
969mod tests {
970 use tls_codec::{Deserialize, Serialize};
971
972 use super::ProposalType;
973
974 #[test]
975 fn that_unknown_proposal_types_are_de_serialized_correctly() {
976 let proposal_types = [0x0000u16, 0x0B0B, 0x7C7C, 0xF000, 0xFFFF];
978
979 for proposal_type in proposal_types.into_iter() {
980 let test = proposal_type.to_be_bytes().to_vec();
982
983 let got = ProposalType::tls_deserialize_exact(&test).unwrap();
985
986 match got {
987 ProposalType::Custom(got_proposal_type) => {
988 assert_eq!(proposal_type, got_proposal_type);
989 }
990 other => panic!("Expected `ProposalType::Unknown`, got `{other:?}`."),
991 }
992
993 let got_serialized = got.tls_serialize_detached().unwrap();
995 assert_eq!(test, got_serialized);
996 }
997 }
998}