mls_spec/drafts/
additional_wire_formats.rs

1use crate::{
2    SensitiveBytes,
3    crypto::Mac,
4    defs::{Epoch, WireFormat},
5    group::GroupId,
6    messages::{ContentType, ContentTypeInner, FramedContentAuthData, Sender},
7};
8
9pub const WIRE_FORMAT_MLS_MESSAGE_WITHOUT_AAD: u16 = 0xFADF; // TODO: Waiting for IANA registration
10static_assertions::const_assert!(
11    *WireFormat::RESERVED_PRIVATE_USE_RANGE.start() <= WIRE_FORMAT_MLS_MESSAGE_WITHOUT_AAD
12        && WIRE_FORMAT_MLS_MESSAGE_WITHOUT_AAD <= *WireFormat::RESERVED_PRIVATE_USE_RANGE.end()
13);
14
15#[derive(
16    Debug,
17    Clone,
18    Copy,
19    PartialEq,
20    Eq,
21    tls_codec::TlsSize,
22    tls_codec::TlsSerialize,
23    tls_codec::TlsDeserialize,
24)]
25#[cfg_attr(
26    feature = "serde",
27    derive(serde_repr::Serialize_repr, serde_repr::Deserialize_repr)
28)]
29#[repr(u8)]
30pub enum MessageWithoutAadType {
31    PublicMessage = 0,
32    PrivateMessage = 1,
33}
34
35///
36/// <https://www.ietf.org/archive/id/draft-pham-mls-additional-wire-formats-00.html#section-2-2>
37///
38/// ```notrust,ignore
39/// enum {
40///     PublicMessageWithoutAAD(0),
41///     PrivateMessageWithoutAAD(1),
42/// } MessageWithoutAAD;
43/// ```
44///
45#[derive(
46    Debug,
47    Clone,
48    PartialEq,
49    Eq,
50    tls_codec::TlsSize,
51    tls_codec::TlsSerialize,
52    tls_codec::TlsDeserialize,
53)]
54#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
55#[repr(u8)]
56#[allow(clippy::large_enum_variant)]
57pub enum MessageWithoutAad {
58    #[tls_codec(discriminant = "MessageWithoutAadType::PublicMessage")]
59    PublicMessageWithoutAad(PublicMessageWithoutAad),
60    #[tls_codec(discriminant = "MessageWithoutAadType::PrivateMessage")]
61    PrivateMessageWithoutAad(PrivateMessageWithoutAad),
62}
63
64///
65/// ```notrust,ignore
66/// struct {
67///     opaque group_id<V>;
68///     uint64 epoch;
69///     Sender sender;
70///
71///     ContentType content_type;
72///         select (FramedContent.content_type) {
73///             case application:
74///                 opaque application_data<V>;
75///             case proposal:
76///                 Proposal proposal;
77///             case commit:
78///                 Commit commit;
79///         };
80/// } FramedContentWithoutAAD;
81/// ```
82///
83#[derive(
84    Debug,
85    Clone,
86    PartialEq,
87    Eq,
88    tls_codec::TlsSize,
89    tls_codec::TlsSerialize,
90    tls_codec::TlsDeserialize,
91)]
92#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
93pub struct FramedContentWithoutAad {
94    #[tls_codec(with = "crate::tlspl::bytes")]
95    pub group_id: GroupId,
96    pub epoch: Epoch,
97    pub sender: Sender,
98    pub content: ContentTypeInner,
99}
100
101#[derive(Debug, Clone, PartialEq, Eq)]
102#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
103pub struct PublicMessageWithoutAad {
104    pub content: FramedContentWithoutAad,
105    pub auth: FramedContentAuthData,
106    pub membership_tag: Option<Mac>,
107}
108
109impl tls_codec::Serialize for PublicMessageWithoutAad {
110    fn tls_serialize<W: std::io::Write>(&self, writer: &mut W) -> Result<usize, tls_codec::Error> {
111        let mut written = self.content.tls_serialize(writer)?;
112        written += self.auth.tls_serialize(writer)?;
113        if matches!(self.content.sender, Sender::Member(_)) {
114            let Some(mac) = &self.membership_tag else {
115                return Err(tls_codec::Error::EncodingError(
116                    "PublicMessageWithoutAad.content.sender is Member but `membership_tag` is missing".into(),
117                ));
118            };
119            written += mac.tls_serialize(writer)?;
120        }
121
122        Ok(written)
123    }
124}
125
126impl tls_codec::Deserialize for PublicMessageWithoutAad {
127    fn tls_deserialize<R: std::io::Read>(bytes: &mut R) -> Result<Self, tls_codec::Error>
128    where
129        Self: Sized,
130    {
131        let content = FramedContentWithoutAad::tls_deserialize(bytes)?;
132        let auth = FramedContentAuthData::tls_deserialize_with_content_type(
133            bytes,
134            (&content.content).into(),
135        )?;
136
137        let membership_tag = if matches!(content.sender, Sender::Member(_)) {
138            Some(Mac::tls_deserialize(bytes)?)
139        } else {
140            None
141        };
142
143        Ok(Self {
144            content,
145            auth,
146            membership_tag,
147        })
148    }
149}
150
151impl tls_codec::Size for PublicMessageWithoutAad {
152    fn tls_serialized_len(&self) -> usize {
153        let membership_tag_len = if matches!(self.content.sender, Sender::Member(_)) {
154            self.membership_tag
155                .as_ref()
156                .map(tls_codec::Size::tls_serialized_len)
157                .unwrap_or_default()
158        } else {
159            debug_assert!(
160                self.membership_tag.is_none(),
161                "PublicMessageWithoutAad contains a membership_tag while it shouldn't. There's a bug somewhere"
162            );
163
164            0
165        };
166
167        self.content.tls_serialized_len() + self.auth.tls_serialized_len() + membership_tag_len
168    }
169}
170
171#[derive(
172    Debug,
173    Clone,
174    PartialEq,
175    Eq,
176    tls_codec::TlsSize,
177    tls_codec::TlsSerialize,
178    tls_codec::TlsDeserialize,
179)]
180#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
181pub struct PrivateMessageWithoutAad {
182    #[tls_codec(with = "crate::tlspl::bytes")]
183    pub group_id: GroupId,
184    pub epoch: Epoch,
185    pub content_type: ContentType,
186    pub encrypted_sender_data: SensitiveBytes,
187    pub ciphertext: SensitiveBytes,
188}