Skip to main content

openmls/framing/
message_in.rs

1//! MLS Message (Input)
2//!
3//! This module defines the [`MlsMessageIn`] structs which implements the
4//! `MLSMessage` struct as defined by the MLS specification, but is used
5//! exclusively as input for the [`MlsGroup`] API. [`MlsMessageOut`] also
6//! implements `MLSMessage`, but for outputs.
7//!
8//! The [`MlsMessageIn`] struct is meant to be deserialized upon receiving it
9//! from the DS. After deserialization, its content (either a [`PublicMessage`],
10//! [`PrivateMessage`], [`KeyPackageIn`], [`Welcome`] or
11//! [`GroupInfo`](crate::messages::group_info::GroupInfo)) can be extracted via
12//! [`MlsMessageIn::extract()`] for use with the [`MlsGroup`] API.
13//!
14//! If an [`MlsMessageIn`] contains a [`PublicMessage`] or [`PrivateMessage`],
15//! can be used to determine which group can be used to process the message.
16
17use super::*;
18use crate::{
19    key_packages::KeyPackageIn, messages::group_info::VerifiableGroupInfo,
20    versions::ProtocolVersion,
21};
22
23/// Before use with the [`MlsGroup`] API, the message has to be unpacked via
24/// `extract` to yield its [`MlsMessageBodyIn`].
25///
26/// ```c
27/// // draft-ietf-mls-protocol-17
28/// struct {
29///     ProtocolVersion version = mls10;
30///
31///     // ... continued in [MlsMessageBody] ...
32/// } MLSMessage;
33/// ```
34///
35/// The `-In` suffix of this struct is to separate it from the [`MlsMessageOut`]
36/// which is commonly returned by functions of the [`MlsGroup`] API.
37#[derive(PartialEq, Debug, Clone, TlsSize, TlsSerialize)]
38pub struct MlsMessageIn {
39    pub(crate) version: ProtocolVersion,
40    pub(crate) body: MlsMessageBodyIn,
41}
42
43/// MLSMessage (Body)
44///
45/// Note: Because [`MlsMessageBodyIn`] already discriminates between
46/// `public_message`, `private_message`, etc., we don't use the `wire_format`
47/// field. This prevents inconsistent assignments where `wire_format`
48/// contradicts the variant given in `body`.
49#[derive(Debug, PartialEq, Clone, TlsDeserialize, TlsDeserializeBytes, TlsSerialize, TlsSize)]
50#[repr(u16)]
51pub enum MlsMessageBodyIn {
52    /// Plaintext message
53    #[tls_codec(discriminant = 1)]
54    PublicMessage(PublicMessageIn),
55
56    /// Ciphertext message
57    #[tls_codec(discriminant = 2)]
58    PrivateMessage(PrivateMessageIn),
59
60    /// Welcome message
61    #[tls_codec(discriminant = 3)]
62    Welcome(Welcome),
63
64    /// Group information
65    #[tls_codec(discriminant = 4)]
66    GroupInfo(VerifiableGroupInfo),
67
68    /// KeyPackage
69    #[tls_codec(discriminant = 5)]
70    KeyPackage(KeyPackageIn),
71
72    /// Targeted message (draft-ietf-mls-targeted-messages)
73    #[cfg(feature = "targeted-messages-draft")]
74    #[cfg_attr(docsrs, doc(cfg(feature = "targeted-messages-draft")))]
75    #[tls_codec(discriminant = 6)]
76    TargetedMessage(crate::targeted_messages::TargetedMessageIn),
77}
78
79impl MlsMessageIn {
80    /// Returns the wire format.
81    pub fn wire_format(&self) -> WireFormat {
82        match self.body {
83            MlsMessageBodyIn::PrivateMessage(_) => WireFormat::PrivateMessage,
84            MlsMessageBodyIn::PublicMessage(_) => WireFormat::PublicMessage,
85            MlsMessageBodyIn::Welcome(_) => WireFormat::Welcome,
86            MlsMessageBodyIn::GroupInfo(_) => WireFormat::GroupInfo,
87            MlsMessageBodyIn::KeyPackage(_) => WireFormat::KeyPackage,
88            #[cfg(feature = "targeted-messages-draft")]
89            MlsMessageBodyIn::TargetedMessage(_) => WireFormat::TargetedMessage,
90        }
91    }
92
93    /// Extract the content of an [`MlsMessageIn`] after deserialization for use
94    /// with the [`MlsGroup`] API.
95    pub fn extract(self) -> MlsMessageBodyIn {
96        self.body
97    }
98
99    /// Try to convert the message into a [`ProtocolMessage`].
100    pub fn try_into_protocol_message(self) -> Result<ProtocolMessage, ProtocolMessageError> {
101        self.try_into()
102    }
103
104    /// Extract a [`TargetedMessageIn`](crate::targeted_messages::TargetedMessageIn)
105    /// from this message, if it contains one.
106    #[cfg(all(feature = "targeted-messages-draft", any(feature = "test-utils", test)))]
107    #[cfg_attr(docsrs, doc(cfg(feature = "targeted-messages-draft")))]
108    pub fn into_targeted_message(self) -> Option<crate::targeted_messages::TargetedMessageIn> {
109        match self.body {
110            MlsMessageBodyIn::TargetedMessage(tm) => Some(tm),
111            _ => None,
112        }
113    }
114
115    #[cfg(any(test, feature = "test-utils"))]
116    pub fn into_keypackage(self) -> Option<crate::key_packages::KeyPackage> {
117        match self.body {
118            MlsMessageBodyIn::KeyPackage(key_package) => {
119                debug_assert!(key_package.version_is_supported(self.version));
120                Some(key_package.into())
121            }
122            _ => None,
123        }
124    }
125
126    #[cfg(test)]
127    pub(crate) fn into_plaintext(self) -> Option<PublicMessage> {
128        match self.body {
129            MlsMessageBodyIn::PublicMessage(m) => Some(m.into()),
130            _ => None,
131        }
132    }
133
134    #[cfg(test)]
135    pub(crate) fn into_ciphertext(self) -> Option<PrivateMessageIn> {
136        match self.body {
137            MlsMessageBodyIn::PrivateMessage(m) => Some(m),
138            _ => None,
139        }
140    }
141
142    /// Convert this message into a [`Welcome`].
143    ///
144    /// Returns `None` if this message is not a welcome message.
145    #[cfg(any(feature = "test-utils", test))]
146    pub fn into_welcome(self) -> Option<Welcome> {
147        match self.body {
148            MlsMessageBodyIn::Welcome(w) => Some(w),
149            _ => None,
150        }
151    }
152
153    #[cfg(any(feature = "test-utils", test))]
154    pub fn into_protocol_message(self) -> Option<ProtocolMessage> {
155        match self.body {
156            MlsMessageBodyIn::PublicMessage(m) => Some(m.into()),
157            MlsMessageBodyIn::PrivateMessage(m) => Some(m.into()),
158            _ => None,
159        }
160    }
161
162    #[cfg(any(feature = "test-utils", test))]
163    pub fn into_verifiable_group_info(self) -> Option<VerifiableGroupInfo> {
164        match self.body {
165            MlsMessageBodyIn::GroupInfo(group_info) => Some(group_info),
166            _ => None,
167        }
168    }
169}
170
171/// Enum containing a message for use with `process_message` and an
172/// [`MlsGroup`]. Both [`PublicMessage`] and [`PrivateMessage`] implement
173/// [`Into<ProtocolMessage>`].
174#[derive(Debug, Clone)]
175pub enum ProtocolMessage {
176    /// A [`ProtocolMessage`] containing a [`PrivateMessage`].
177    PrivateMessage(PrivateMessageIn),
178    /// A [`ProtocolMessage`] containing a [`PublicMessage`].
179    PublicMessage(Box<PublicMessageIn>),
180}
181
182impl ProtocolMessage {
183    /// Returns the wire format.
184    pub fn wire_format(&self) -> WireFormat {
185        match self {
186            ProtocolMessage::PrivateMessage(_) => WireFormat::PrivateMessage,
187            ProtocolMessage::PublicMessage(_) => WireFormat::PublicMessage,
188        }
189    }
190
191    /// Returns the group ID.
192    pub fn group_id(&self) -> &GroupId {
193        match self {
194            ProtocolMessage::PrivateMessage(ref m) => m.group_id(),
195            ProtocolMessage::PublicMessage(ref m) => m.group_id(),
196        }
197    }
198
199    /// Returns the epoch.
200    pub fn epoch(&self) -> GroupEpoch {
201        match self {
202            ProtocolMessage::PrivateMessage(ref m) => m.epoch(),
203            ProtocolMessage::PublicMessage(ref m) => m.epoch(),
204        }
205    }
206
207    /// Returns the content type.
208    pub fn content_type(&self) -> ContentType {
209        match self {
210            ProtocolMessage::PrivateMessage(ref m) => m.content_type(),
211            ProtocolMessage::PublicMessage(ref m) => m.content_type(),
212        }
213    }
214
215    /// Returns `true` if this is either an external proposal or external commit
216    pub fn is_external(&self) -> bool {
217        match &self {
218            ProtocolMessage::PublicMessage(p) => {
219                matches!(
220                    p.sender(),
221                    Sender::NewMemberProposal | Sender::NewMemberCommit | Sender::External(_)
222                )
223            }
224            // external message cannot be encrypted
225            ProtocolMessage::PrivateMessage(_) => false,
226        }
227    }
228
229    /// Returns `true` if this is a handshake message and `false` otherwise.
230    pub fn is_handshake_message(&self) -> bool {
231        self.content_type().is_handshake_message()
232    }
233}
234
235impl From<PrivateMessageIn> for ProtocolMessage {
236    fn from(private_message: PrivateMessageIn) -> Self {
237        ProtocolMessage::PrivateMessage(private_message)
238    }
239}
240
241impl From<PublicMessageIn> for ProtocolMessage {
242    fn from(public_message: PublicMessageIn) -> Self {
243        ProtocolMessage::PublicMessage(Box::new(public_message))
244    }
245}
246
247impl TryFrom<MlsMessageIn> for ProtocolMessage {
248    type Error = ProtocolMessageError;
249
250    fn try_from(msg: MlsMessageIn) -> Result<Self, Self::Error> {
251        match msg.body {
252            MlsMessageBodyIn::PublicMessage(m) => Ok(m.into()),
253            MlsMessageBodyIn::PrivateMessage(m) => Ok(ProtocolMessage::PrivateMessage(m)),
254            _ => Err(ProtocolMessageError::WrongWireFormat),
255        }
256    }
257}
258
259#[cfg(any(feature = "test-utils", test))]
260impl From<PublicMessage> for ProtocolMessage {
261    fn from(msg: PublicMessage) -> Self {
262        PublicMessageIn::from(msg).into()
263    }
264}