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)]
38#[cfg_attr(feature = "test-utils", derive(TlsSerialize))]
39pub struct MlsMessageIn {
40    pub(crate) version: ProtocolVersion,
41    pub(crate) body: MlsMessageBodyIn,
42}
43
44/// MLSMessage (Body)
45///
46/// Note: Because [`MlsMessageBodyIn`] already discriminates between
47/// `public_message`, `private_message`, etc., we don't use the `wire_format`
48/// field. This prevents inconsistent assignments where `wire_format`
49/// contradicts the variant given in `body`.
50///
51/// ```c
52/// // draft-ietf-mls-protocol-17
53/// struct {
54///     // ... continued from [MlsMessage] ...
55///
56///     WireFormat wire_format;
57///     select (MLSMessage.wire_format) {
58///         case mls_plaintext:
59///             PublicMessage plaintext;
60///         case mls_ciphertext:
61///             PrivateMessage ciphertext;
62///         case mls_welcome:
63///             Welcome welcome;
64///         case mls_group_info:
65///             GroupInfo group_info;
66///         case mls_key_package:
67///             KeyPackage key_package;
68///     }
69/// } MLSMessage;
70/// ```
71#[derive(Debug, PartialEq, Clone, TlsDeserialize, TlsDeserializeBytes, TlsSize)]
72#[cfg_attr(feature = "test-utils", derive(TlsSerialize))]
73#[repr(u16)]
74pub enum MlsMessageBodyIn {
75    /// Plaintext message
76    #[tls_codec(discriminant = 1)]
77    PublicMessage(PublicMessageIn),
78
79    /// Ciphertext message
80    #[tls_codec(discriminant = 2)]
81    PrivateMessage(PrivateMessageIn),
82
83    /// Welcome message
84    #[tls_codec(discriminant = 3)]
85    Welcome(Welcome),
86
87    /// Group information
88    #[tls_codec(discriminant = 4)]
89    GroupInfo(VerifiableGroupInfo),
90
91    /// KeyPackage
92    #[tls_codec(discriminant = 5)]
93    KeyPackage(KeyPackageIn),
94}
95
96impl MlsMessageIn {
97    /// Returns the wire format.
98    pub fn wire_format(&self) -> WireFormat {
99        match self.body {
100            MlsMessageBodyIn::PrivateMessage(_) => WireFormat::PrivateMessage,
101            MlsMessageBodyIn::PublicMessage(_) => WireFormat::PublicMessage,
102            MlsMessageBodyIn::Welcome(_) => WireFormat::Welcome,
103            MlsMessageBodyIn::GroupInfo(_) => WireFormat::GroupInfo,
104            MlsMessageBodyIn::KeyPackage(_) => WireFormat::KeyPackage,
105        }
106    }
107
108    /// Extract the content of an [`MlsMessageIn`] after deserialization for use
109    /// with the [`MlsGroup`] API.
110    pub fn extract(self) -> MlsMessageBodyIn {
111        self.body
112    }
113
114    /// Try to convert the message into a [`ProtocolMessage`].
115    pub fn try_into_protocol_message(self) -> Result<ProtocolMessage, ProtocolMessageError> {
116        self.try_into()
117    }
118
119    #[cfg(any(test, feature = "test-utils"))]
120    pub fn into_keypackage(self) -> Option<crate::key_packages::KeyPackage> {
121        match self.body {
122            MlsMessageBodyIn::KeyPackage(key_package) => {
123                debug_assert!(key_package.version_is_supported(self.version));
124                Some(key_package.into())
125            }
126            _ => None,
127        }
128    }
129
130    #[cfg(test)]
131    pub(crate) fn into_plaintext(self) -> Option<PublicMessage> {
132        match self.body {
133            MlsMessageBodyIn::PublicMessage(m) => Some(m.into()),
134            _ => None,
135        }
136    }
137
138    #[cfg(test)]
139    pub(crate) fn into_ciphertext(self) -> Option<PrivateMessageIn> {
140        match self.body {
141            MlsMessageBodyIn::PrivateMessage(m) => Some(m),
142            _ => None,
143        }
144    }
145
146    /// Convert this message into a [`Welcome`].
147    ///
148    /// Returns `None` if this message is not a welcome message.
149    #[cfg(any(feature = "test-utils", test))]
150    pub fn into_welcome(self) -> Option<Welcome> {
151        match self.body {
152            MlsMessageBodyIn::Welcome(w) => Some(w),
153            _ => None,
154        }
155    }
156
157    #[cfg(any(feature = "test-utils", test))]
158    pub fn into_protocol_message(self) -> Option<ProtocolMessage> {
159        match self.body {
160            MlsMessageBodyIn::PublicMessage(m) => Some(m.into()),
161            MlsMessageBodyIn::PrivateMessage(m) => Some(m.into()),
162            _ => None,
163        }
164    }
165
166    #[cfg(any(feature = "test-utils", test))]
167    pub fn into_verifiable_group_info(self) -> Option<VerifiableGroupInfo> {
168        match self.body {
169            MlsMessageBodyIn::GroupInfo(group_info) => Some(group_info),
170            _ => None,
171        }
172    }
173}
174
175/// Enum containing a message for use with `process_message` and an
176/// [`MlsGroup`]. Both [`PublicMessage`] and [`PrivateMessage`] implement
177/// [`Into<ProtocolMessage>`].
178#[derive(Debug, Clone)]
179pub enum ProtocolMessage {
180    /// A [`ProtocolMessage`] containing a [`PrivateMessage`].
181    PrivateMessage(PrivateMessageIn),
182    /// A [`ProtocolMessage`] containing a [`PublicMessage`].
183    PublicMessage(Box<PublicMessageIn>),
184}
185
186impl ProtocolMessage {
187    /// Returns the wire format.
188    pub fn wire_format(&self) -> WireFormat {
189        match self {
190            ProtocolMessage::PrivateMessage(_) => WireFormat::PrivateMessage,
191            ProtocolMessage::PublicMessage(_) => WireFormat::PublicMessage,
192        }
193    }
194
195    /// Returns the group ID.
196    pub fn group_id(&self) -> &GroupId {
197        match self {
198            ProtocolMessage::PrivateMessage(ref m) => m.group_id(),
199            ProtocolMessage::PublicMessage(ref m) => m.group_id(),
200        }
201    }
202
203    /// Returns the epoch.
204    pub fn epoch(&self) -> GroupEpoch {
205        match self {
206            ProtocolMessage::PrivateMessage(ref m) => m.epoch(),
207            ProtocolMessage::PublicMessage(ref m) => m.epoch(),
208        }
209    }
210
211    /// Returns the content type.
212    pub fn content_type(&self) -> ContentType {
213        match self {
214            ProtocolMessage::PrivateMessage(ref m) => m.content_type(),
215            ProtocolMessage::PublicMessage(ref m) => m.content_type(),
216        }
217    }
218
219    /// Returns `true` if this is either an external proposal or external commit
220    pub fn is_external(&self) -> bool {
221        match &self {
222            ProtocolMessage::PublicMessage(p) => {
223                matches!(
224                    p.sender(),
225                    Sender::NewMemberProposal | Sender::NewMemberCommit | Sender::External(_)
226                )
227            }
228            // external message cannot be encrypted
229            ProtocolMessage::PrivateMessage(_) => false,
230        }
231    }
232
233    /// Returns `true` if this is a handshake message and `false` otherwise.
234    pub fn is_handshake_message(&self) -> bool {
235        self.content_type().is_handshake_message()
236    }
237}
238
239impl From<PrivateMessageIn> for ProtocolMessage {
240    fn from(private_message: PrivateMessageIn) -> Self {
241        ProtocolMessage::PrivateMessage(private_message)
242    }
243}
244
245impl From<PublicMessageIn> for ProtocolMessage {
246    fn from(public_message: PublicMessageIn) -> Self {
247        ProtocolMessage::PublicMessage(Box::new(public_message))
248    }
249}
250
251impl TryFrom<MlsMessageIn> for ProtocolMessage {
252    type Error = ProtocolMessageError;
253
254    fn try_from(msg: MlsMessageIn) -> Result<Self, Self::Error> {
255        match msg.body {
256            MlsMessageBodyIn::PublicMessage(m) => Ok(m.into()),
257            MlsMessageBodyIn::PrivateMessage(m) => Ok(ProtocolMessage::PrivateMessage(m)),
258            _ => Err(ProtocolMessageError::WrongWireFormat),
259        }
260    }
261}
262
263#[cfg(any(feature = "test-utils", test))]
264impl From<PublicMessage> for ProtocolMessage {
265    fn from(msg: PublicMessage) -> Self {
266        PublicMessageIn::from(msg).into()
267    }
268}