1use crate::{error::LibraryError, group::errors::ValidationError, versions::ProtocolVersion};
7
8use super::{
9 mls_auth_content::FramedContentAuthData,
10 mls_auth_content_in::{AuthenticatedContentIn, VerifiableAuthenticatedContentIn},
11 mls_content::{framed_content_tbs_serialized_detached, AuthenticatedContentTbm},
12 mls_content_in::FramedContentIn,
13 *,
14};
15
16use openmls_traits::types::Ciphersuite;
17use std::io::{Read, Write};
18use tls_codec::{Deserialize as TlsDeserializeTrait, Serialize as TlsSerializeTrait};
19
20#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
35pub struct PublicMessageIn {
36 pub(crate) content: FramedContentIn,
37 pub(crate) auth: FramedContentAuthData,
38 pub(crate) membership_tag: Option<MembershipTag>,
39}
40
41#[cfg(any(test, feature = "test-utils"))]
42impl PublicMessageIn {
43 pub(crate) fn content(&self) -> &crate::framing::mls_content_in::FramedContentBodyIn {
44 &self.content.body
45 }
46}
47
48#[cfg(test)]
49impl PublicMessageIn {
50 pub fn set_confirmation_tag(&mut self, confirmation_tag: Option<ConfirmationTag>) {
51 self.auth.confirmation_tag = confirmation_tag;
52 }
53
54 pub fn unset_membership_tag(&mut self) {
55 self.membership_tag = None;
56 }
57
58 pub(crate) fn set_content(&mut self, content: FramedContentBodyIn) {
59 self.content.body = content;
60 }
61
62 pub fn set_epoch(&mut self, epoch: u64) {
63 self.content.epoch = epoch.into();
64 }
65
66 pub(crate) fn set_sender(&mut self, sender: Sender) {
68 self.content.sender = sender;
69 }
70}
71
72impl From<AuthenticatedContentIn> for PublicMessageIn {
73 fn from(v: AuthenticatedContentIn) -> Self {
74 Self {
75 content: v.content,
76 auth: v.auth,
77 membership_tag: None,
78 }
79 }
80}
81
82#[cfg(feature = "extensions-draft")]
83impl PublicMessageIn {
84 pub fn unverified_app_ephemeral_proposals(
102 &self,
103 component_id: crate::component::ComponentId,
104 ) -> Vec<&crate::messages::proposals::AppEphemeralProposal> {
105 use crate::{
106 framing::mls_content_in::FramedContentBodyIn,
107 messages::proposals_in::{ProposalIn, ProposalOrRefIn},
108 };
109
110 let FramedContentBodyIn::Commit(commit) = &self.content.body else {
111 return vec![];
112 };
113 commit
114 .unverified_proposals()
115 .iter()
116 .filter_map(|proposal_or_ref| match proposal_or_ref {
117 ProposalOrRefIn::Proposal(proposal) => match proposal.as_ref() {
118 ProposalIn::AppEphemeral(app_ephemeral)
119 if app_ephemeral.component_id() == component_id =>
120 {
121 Some(app_ephemeral.as_ref())
122 }
123 _ => None,
124 },
125 ProposalOrRefIn::Reference(_) => None,
126 })
127 .collect()
128 }
129}
130
131impl PublicMessageIn {
132 pub(crate) fn new(
134 content: FramedContentIn,
135 auth: FramedContentAuthData,
136 membership_tag: Option<MembershipTag>,
137 ) -> Self {
138 Self {
139 content,
140 auth,
141 membership_tag,
142 }
143 }
144
145 pub fn content_type(&self) -> ContentType {
147 self.content.body.content_type()
148 }
149
150 pub fn sender(&self) -> &Sender {
152 &self.content.sender
153 }
154
155 #[cfg(test)]
156 pub(crate) fn set_membership_tag(
157 &mut self,
158 provider: &impl openmls_traits::OpenMlsProvider,
159 ciphersuite: Ciphersuite,
160 membership_key: &MembershipKey,
161 serialized_context: &[u8],
162 ) -> Result<(), LibraryError> {
163 let tbs_payload = framed_content_tbs_serialized_detached(
164 ProtocolVersion::default(),
165 WireFormat::PublicMessage,
166 &self.content,
167 &self.content.sender,
168 serialized_context,
169 )
170 .map_err(LibraryError::missing_bound_check)?;
171 let tbm_payload = AuthenticatedContentTbm::new(&tbs_payload, &self.auth)?;
172 let membership_tag =
173 membership_key.tag_message(provider.crypto(), ciphersuite, tbm_payload)?;
174
175 self.membership_tag = Some(membership_tag);
176 Ok(())
177 }
178
179 pub(crate) fn verify_membership(
184 &self,
185 crypto: &impl openmls_traits::crypto::OpenMlsCrypto,
186 ciphersuite: Ciphersuite,
187 membership_key: &MembershipKey,
188 serialized_context: &[u8],
189 ) -> Result<(), ValidationError> {
190 log::debug!("Verifying membership tag.");
191 log_crypto!(trace, " Membership key: {:x?}", membership_key);
192 log_crypto!(trace, " Serialized context: {:x?}", serialized_context);
193 let tbs_payload = framed_content_tbs_serialized_detached(
194 ProtocolVersion::default(),
195 WireFormat::PublicMessage,
196 &self.content,
197 &self.content.sender,
198 serialized_context,
199 )
200 .map_err(LibraryError::missing_bound_check)?;
201 let tbm_payload = AuthenticatedContentTbm::new(&tbs_payload, &self.auth)?;
202 let expected_membership_tag =
203 &membership_key.tag_message(crypto, ciphersuite, tbm_payload)?;
204
205 if let Some(membership_tag) = &self.membership_tag {
208 if membership_tag != expected_membership_tag {
210 return Err(ValidationError::InvalidMembershipTag);
211 }
212 } else {
213 return Err(ValidationError::MissingMembershipTag);
214 }
215 Ok(())
216 }
217
218 pub fn epoch(&self) -> GroupEpoch {
220 self.content.epoch
221 }
222
223 pub fn group_id(&self) -> &GroupId {
225 &self.content.group_id
226 }
227
228 pub(crate) fn into_verifiable_content(
230 self,
231 serialized_context: impl Into<Option<Vec<u8>>>,
232 ) -> VerifiableAuthenticatedContentIn {
233 VerifiableAuthenticatedContentIn::new(
234 WireFormat::PublicMessage,
235 self.content,
236 serialized_context,
237 self.auth,
238 )
239 }
240
241 pub(crate) fn membership_tag(&self) -> Option<&MembershipTag> {
243 self.membership_tag.as_ref()
244 }
245
246 pub fn confirmation_tag(&self) -> Option<&ConfirmationTag> {
248 self.auth.confirmation_tag.as_ref()
249 }
250}
251
252#[cfg(test)]
253impl From<PublicMessageIn> for FramedContentTbsIn {
254 fn from(v: PublicMessageIn) -> Self {
255 FramedContentTbsIn {
256 version: ProtocolVersion::default(),
257 wire_format: WireFormat::PublicMessage,
258 content: v.content,
259 serialized_context: None,
260 }
261 }
262}
263
264impl<'a> TryFrom<&'a PublicMessageIn> for InterimTranscriptHashInput<'a> {
265 type Error = &'static str;
266
267 fn try_from(public_message: &'a PublicMessageIn) -> Result<Self, Self::Error> {
268 match public_message.auth.confirmation_tag.as_ref() {
269 Some(confirmation_tag) => Ok(InterimTranscriptHashInput { confirmation_tag }),
270 None => Err("PublicMessage needs to contain a confirmation tag."),
271 }
272 }
273}
274
275impl TlsDeserializeTrait for PublicMessageIn {
276 fn tls_deserialize<R: Read>(bytes: &mut R) -> Result<Self, Error> {
277 let content = FramedContentIn::tls_deserialize(bytes)?;
278 let auth = FramedContentAuthData::deserialize(bytes, content.body.content_type())?;
279 let membership_tag = if content.sender.is_member() {
280 Some(MembershipTag::tls_deserialize(bytes)?)
281 } else {
282 None
283 };
284
285 Ok(PublicMessageIn::new(content, auth, membership_tag))
286 }
287}
288
289impl DeserializeBytes for PublicMessageIn {
290 fn tls_deserialize_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), Error>
291 where
292 Self: Sized,
293 {
294 let mut bytes_ref = bytes;
295 let message = PublicMessageIn::tls_deserialize(&mut bytes_ref)?;
296 Ok((message, bytes_ref))
297 }
298}
299
300impl Size for PublicMessageIn {
301 #[inline]
302 fn tls_serialized_len(&self) -> usize {
303 self.content.tls_serialized_len()
304 + self.auth.tls_serialized_len()
305 + if let Some(membership_tag) = &self.membership_tag {
306 membership_tag.tls_serialized_len()
307 } else {
308 0
309 }
310 }
311}
312
313impl TlsSerializeTrait for PublicMessageIn {
314 fn tls_serialize<W: Write>(&self, writer: &mut W) -> Result<usize, tls_codec::Error> {
315 let mut written = self.content.tls_serialize(writer)?;
317 written += self.auth.tls_serialize(writer)?;
318 written += if let Some(membership_tag) = &self.membership_tag {
319 membership_tag.tls_serialize(writer)?
320 } else {
321 0
322 };
323 Ok(written)
324 }
325}
326
327impl From<PublicMessage> for PublicMessageIn {
328 fn from(v: PublicMessage) -> Self {
329 PublicMessageIn {
330 content: v.content.into(),
331 auth: v.auth,
332 membership_tag: v.membership_tag,
333 }
334 }
335}
336
337#[cfg(any(feature = "test-utils", test))]
340impl From<PublicMessageIn> for PublicMessage {
341 fn from(v: PublicMessageIn) -> Self {
342 PublicMessage {
343 content: v.content.into(),
344 auth: v.auth,
345 membership_tag: v.membership_tag,
346 }
347 }
348}