1use core::ops::Deref;
6
7use crate::{
8 client::MlsError,
9 tree_kem::{leaf_node::LeafNode, node::LeafIndex},
10 KeyPackage, KeyPackageRef,
11};
12
13use super::{Commit, FramedContentAuthData, GroupInfo, MembershipTag, Welcome};
14
15use crate::group::proposal::{Proposal, ProposalOrRef};
16
17#[cfg(feature = "by_ref_proposal")]
18use crate::mls_rules::ProposalRef;
19
20use alloc::vec::Vec;
21use core::fmt::{self, Debug};
22use mls_rs_codec::{MlsDecode, MlsEncode, MlsSize};
23use mls_rs_core::{
24 crypto::{CipherSuite, CipherSuiteProvider},
25 protocol_version::ProtocolVersion,
26};
27use zeroize::ZeroizeOnDrop;
28
29#[cfg(feature = "private_message")]
30use alloc::boxed::Box;
31
32#[cfg(feature = "custom_proposal")]
33use crate::group::proposal::CustomProposal;
34
35#[derive(Copy, Clone, Debug, PartialEq, Eq, MlsSize, MlsEncode, MlsDecode)]
36#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
37#[repr(u8)]
38pub enum ContentType {
39 #[cfg(feature = "private_message")]
40 Application = 1u8,
41 #[cfg(feature = "by_ref_proposal")]
42 Proposal = 2u8,
43 Commit = 3u8,
44}
45
46impl From<&Content> for ContentType {
47 fn from(content: &Content) -> Self {
48 match content {
49 #[cfg(feature = "private_message")]
50 Content::Application(_) => ContentType::Application,
51 #[cfg(feature = "by_ref_proposal")]
52 Content::Proposal(_) => ContentType::Proposal,
53 Content::Commit(_) => ContentType::Commit,
54 }
55 }
56}
57
58#[derive(Clone, Copy, Debug, PartialEq, Eq, MlsSize, MlsEncode, MlsDecode)]
59#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
60#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
61#[repr(u8)]
62#[non_exhaustive]
63pub enum Sender {
65 Member(u32) = 1u8,
67 #[cfg(feature = "by_ref_proposal")]
72 External(u32) = 2u8,
73 #[cfg(feature = "by_ref_proposal")]
75 NewMemberProposal = 3u8,
76 NewMemberCommit = 4u8,
78}
79
80impl From<LeafIndex> for Sender {
81 fn from(leaf_index: LeafIndex) -> Self {
82 Sender::Member(*leaf_index)
83 }
84}
85
86impl From<u32> for Sender {
87 fn from(leaf_index: u32) -> Self {
88 Sender::Member(leaf_index)
89 }
90}
91
92#[derive(Clone, PartialEq, Eq, MlsSize, MlsEncode, MlsDecode, ZeroizeOnDrop)]
93#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
94#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
95pub struct ApplicationData(
96 #[mls_codec(with = "mls_rs_codec::byte_vec")]
97 #[cfg_attr(feature = "serde", serde(with = "mls_rs_core::vec_serde"))]
98 Vec<u8>,
99);
100
101impl Debug for ApplicationData {
102 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
103 mls_rs_core::debug::pretty_bytes(&self.0)
104 .named("ApplicationData")
105 .fmt(f)
106 }
107}
108
109impl From<Vec<u8>> for ApplicationData {
110 fn from(data: Vec<u8>) -> Self {
111 Self(data)
112 }
113}
114
115impl Deref for ApplicationData {
116 type Target = [u8];
117
118 fn deref(&self) -> &Self::Target {
119 &self.0
120 }
121}
122
123impl ApplicationData {
124 pub fn as_bytes(&self) -> &[u8] {
126 &self.0
127 }
128}
129
130#[derive(Clone, Debug, PartialEq, MlsSize, MlsEncode, MlsDecode)]
131#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
132#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
133#[repr(u8)]
134pub(crate) enum Content {
135 #[cfg(feature = "private_message")]
136 Application(ApplicationData) = 1u8,
137 #[cfg(feature = "by_ref_proposal")]
138 Proposal(alloc::boxed::Box<Proposal>) = 2u8,
139 Commit(alloc::boxed::Box<Commit>) = 3u8,
140}
141
142impl Content {
143 pub fn content_type(&self) -> ContentType {
144 self.into()
145 }
146}
147
148#[derive(Clone, Debug, PartialEq)]
149#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
150pub(crate) struct PublicMessage {
151 pub content: FramedContent,
152 pub auth: FramedContentAuthData,
153 pub membership_tag: Option<MembershipTag>,
154}
155
156impl MlsSize for PublicMessage {
157 fn mls_encoded_len(&self) -> usize {
158 self.content.mls_encoded_len()
159 + self.auth.mls_encoded_len()
160 + self
161 .membership_tag
162 .as_ref()
163 .map_or(0, |tag| tag.mls_encoded_len())
164 }
165}
166
167impl MlsEncode for PublicMessage {
168 fn mls_encode(&self, writer: &mut Vec<u8>) -> Result<(), mls_rs_codec::Error> {
169 self.content.mls_encode(writer)?;
170 self.auth.mls_encode(writer)?;
171
172 self.membership_tag
173 .as_ref()
174 .map_or(Ok(()), |tag| tag.mls_encode(writer))
175 }
176}
177
178impl MlsDecode for PublicMessage {
179 fn mls_decode(reader: &mut &[u8]) -> Result<Self, mls_rs_codec::Error> {
180 let content = FramedContent::mls_decode(reader)?;
181 let auth = FramedContentAuthData::mls_decode(reader, content.content_type())?;
182
183 let membership_tag = match content.sender {
184 Sender::Member(_) => Some(MembershipTag::mls_decode(reader)?),
185 _ => None,
186 };
187
188 Ok(Self {
189 content,
190 auth,
191 membership_tag,
192 })
193 }
194}
195
196#[cfg(feature = "private_message")]
197#[derive(Clone, Debug, PartialEq)]
198pub(crate) struct PrivateMessageContent {
199 pub content: Content,
200 pub auth: FramedContentAuthData,
201}
202
203#[cfg(feature = "private_message")]
204impl MlsSize for PrivateMessageContent {
205 fn mls_encoded_len(&self) -> usize {
206 let content_len_without_type = match &self.content {
207 Content::Application(c) => c.mls_encoded_len(),
208 #[cfg(feature = "by_ref_proposal")]
209 Content::Proposal(c) => c.mls_encoded_len(),
210 Content::Commit(c) => c.mls_encoded_len(),
211 };
212
213 content_len_without_type + self.auth.mls_encoded_len()
214 }
215}
216
217#[cfg(feature = "private_message")]
218impl MlsEncode for PrivateMessageContent {
219 fn mls_encode(&self, writer: &mut Vec<u8>) -> Result<(), mls_rs_codec::Error> {
220 match &self.content {
221 Content::Application(c) => c.mls_encode(writer),
222 #[cfg(feature = "by_ref_proposal")]
223 Content::Proposal(c) => c.mls_encode(writer),
224 Content::Commit(c) => c.mls_encode(writer),
225 }?;
226
227 self.auth.mls_encode(writer)?;
228
229 Ok(())
230 }
231}
232
233#[cfg(feature = "private_message")]
234impl PrivateMessageContent {
235 pub(crate) fn mls_decode(
236 reader: &mut &[u8],
237 content_type: ContentType,
238 ) -> Result<Self, mls_rs_codec::Error> {
239 let content = match content_type {
240 ContentType::Application => Content::Application(ApplicationData::mls_decode(reader)?),
241 #[cfg(feature = "by_ref_proposal")]
242 ContentType::Proposal => Content::Proposal(Box::new(Proposal::mls_decode(reader)?)),
243 ContentType::Commit => {
244 Content::Commit(alloc::boxed::Box::new(Commit::mls_decode(reader)?))
245 }
246 };
247
248 let auth = FramedContentAuthData::mls_decode(reader, content.content_type())?;
249
250 if reader.iter().any(|&i| i != 0u8) {
251 return Err(mls_rs_codec::Error::Custom(5));
258 }
259
260 Ok(Self { content, auth })
261 }
262}
263
264#[cfg(feature = "private_message")]
265#[derive(Clone, PartialEq, Eq, MlsSize, MlsEncode, MlsDecode)]
266pub struct PrivateContentAAD {
267 #[mls_codec(with = "mls_rs_codec::byte_vec")]
268 pub group_id: Vec<u8>,
269 pub epoch: u64,
270 pub content_type: ContentType,
271 #[mls_codec(with = "mls_rs_codec::byte_vec")]
272 pub authenticated_data: Vec<u8>,
273}
274
275#[cfg(feature = "private_message")]
276impl Debug for PrivateContentAAD {
277 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
278 f.debug_struct("PrivateContentAAD")
279 .field(
280 "group_id",
281 &mls_rs_core::debug::pretty_group_id(&self.group_id),
282 )
283 .field("epoch", &self.epoch)
284 .field("content_type", &self.content_type)
285 .field(
286 "authenticated_data",
287 &mls_rs_core::debug::pretty_bytes(&self.authenticated_data),
288 )
289 .finish()
290 }
291}
292
293#[cfg(feature = "private_message")]
294#[derive(Clone, PartialEq, Eq, MlsSize, MlsEncode, MlsDecode)]
295#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
296pub struct PrivateMessage {
297 #[mls_codec(with = "mls_rs_codec::byte_vec")]
298 pub group_id: Vec<u8>,
299 pub epoch: u64,
300 pub content_type: ContentType,
301 #[mls_codec(with = "mls_rs_codec::byte_vec")]
302 pub authenticated_data: Vec<u8>,
303 #[mls_codec(with = "mls_rs_codec::byte_vec")]
304 pub encrypted_sender_data: Vec<u8>,
305 #[mls_codec(with = "mls_rs_codec::byte_vec")]
306 pub ciphertext: Vec<u8>,
307}
308
309#[cfg(feature = "private_message")]
310impl Debug for PrivateMessage {
311 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
312 f.debug_struct("PrivateMessage")
313 .field(
314 "group_id",
315 &mls_rs_core::debug::pretty_group_id(&self.group_id),
316 )
317 .field("epoch", &self.epoch)
318 .field("content_type", &self.content_type)
319 .field(
320 "authenticated_data",
321 &mls_rs_core::debug::pretty_bytes(&self.authenticated_data),
322 )
323 .field(
324 "encrypted_sender_data",
325 &mls_rs_core::debug::pretty_bytes(&self.encrypted_sender_data),
326 )
327 .field(
328 "ciphertext",
329 &mls_rs_core::debug::pretty_bytes(&self.ciphertext),
330 )
331 .finish()
332 }
333}
334
335#[cfg(feature = "private_message")]
336impl From<&PrivateMessage> for PrivateContentAAD {
337 fn from(ciphertext: &PrivateMessage) -> Self {
338 Self {
339 group_id: ciphertext.group_id.clone(),
340 epoch: ciphertext.epoch,
341 content_type: ciphertext.content_type,
342 authenticated_data: ciphertext.authenticated_data.clone(),
343 }
344 }
345}
346
347#[derive(Clone, Debug, PartialEq)]
348pub enum MlsMessageDescription<'a> {
349 Welcome {
350 key_package_refs: Vec<&'a KeyPackageRef>,
351 cipher_suite: CipherSuite,
352 },
353 PrivateProtocolMessage {
354 group_id: &'a [u8],
355 epoch_id: u64,
356 content_type: ContentType, },
358 PublicProtocolMessage {
359 group_id: &'a [u8],
360 epoch_id: u64,
361 content_type: ContentType,
362 sender: Sender,
363 authenticated_data: &'a [u8],
364 },
365 GroupInfo,
366 KeyPackage,
367}
368
369impl MlsMessage {
370 pub fn description(&self) -> MlsMessageDescription<'_> {
371 match &self.payload {
372 MlsMessagePayload::Welcome(w) => MlsMessageDescription::Welcome {
373 key_package_refs: w.secrets.iter().map(|s| &s.new_member).collect(),
374 cipher_suite: w.cipher_suite,
375 },
376 MlsMessagePayload::Plain(p) => MlsMessageDescription::PublicProtocolMessage {
377 group_id: &p.content.group_id,
378 epoch_id: p.content.epoch,
379 content_type: p.content.content_type(),
380 sender: p.content.sender,
381 authenticated_data: &p.content.authenticated_data,
382 },
383 #[cfg(feature = "private_message")]
384 MlsMessagePayload::Cipher(c) => MlsMessageDescription::PrivateProtocolMessage {
385 group_id: &c.group_id,
386 epoch_id: c.epoch,
387 content_type: c.content_type,
388 },
389 MlsMessagePayload::GroupInfo(_) => MlsMessageDescription::GroupInfo,
390 MlsMessagePayload::KeyPackage(_) => MlsMessageDescription::KeyPackage,
391 }
392 }
393}
394
395#[derive(Clone, Debug, PartialEq, MlsSize, MlsEncode, MlsDecode)]
396#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
397pub struct MlsMessage {
399 pub(crate) version: ProtocolVersion,
400 pub(crate) payload: MlsMessagePayload,
401}
402
403#[allow(dead_code)]
404impl MlsMessage {
405 pub(crate) fn new(version: ProtocolVersion, payload: MlsMessagePayload) -> MlsMessage {
406 Self { version, payload }
407 }
408
409 #[inline(always)]
410 pub(crate) fn into_plaintext(self) -> Option<PublicMessage> {
411 match self.payload {
412 MlsMessagePayload::Plain(plaintext) => Some(plaintext),
413 _ => None,
414 }
415 }
416
417 #[cfg(feature = "private_message")]
418 #[inline(always)]
419 pub(crate) fn into_ciphertext(self) -> Option<PrivateMessage> {
420 match self.payload {
421 MlsMessagePayload::Cipher(ciphertext) => Some(ciphertext),
422 _ => None,
423 }
424 }
425
426 #[inline(always)]
427 pub(crate) fn into_welcome(self) -> Option<Welcome> {
428 match self.payload {
429 MlsMessagePayload::Welcome(welcome) => Some(welcome),
430 _ => None,
431 }
432 }
433
434 #[inline(always)]
435 pub fn into_group_info(self) -> Option<GroupInfo> {
436 match self.payload {
437 MlsMessagePayload::GroupInfo(info) => Some(info),
438 _ => None,
439 }
440 }
441
442 #[inline(always)]
443 pub fn as_group_info(&self) -> Option<&GroupInfo> {
444 match &self.payload {
445 MlsMessagePayload::GroupInfo(info) => Some(info),
446 _ => None,
447 }
448 }
449
450 #[inline(always)]
451 pub fn into_key_package(self) -> Option<KeyPackage> {
452 match self.payload {
453 MlsMessagePayload::KeyPackage(kp) => Some(kp),
454 _ => None,
455 }
456 }
457
458 pub fn as_key_package(&self) -> Option<&KeyPackage> {
459 match &self.payload {
460 MlsMessagePayload::KeyPackage(kp) => Some(kp),
461 _ => None,
462 }
463 }
464
465 pub fn wire_format(&self) -> WireFormat {
467 match self.payload {
468 MlsMessagePayload::Plain(_) => WireFormat::PublicMessage,
469 #[cfg(feature = "private_message")]
470 MlsMessagePayload::Cipher(_) => WireFormat::PrivateMessage,
471 MlsMessagePayload::Welcome(_) => WireFormat::Welcome,
472 MlsMessagePayload::GroupInfo(_) => WireFormat::GroupInfo,
473 MlsMessagePayload::KeyPackage(_) => WireFormat::KeyPackage,
474 }
475 }
476
477 pub fn epoch(&self) -> Option<u64> {
482 match &self.payload {
483 MlsMessagePayload::Plain(p) => Some(p.content.epoch),
484 #[cfg(feature = "private_message")]
485 MlsMessagePayload::Cipher(c) => Some(c.epoch),
486 MlsMessagePayload::GroupInfo(gi) => Some(gi.group_context.epoch),
487 _ => None,
488 }
489 }
490
491 pub fn cipher_suite(&self) -> Option<CipherSuite> {
492 match &self.payload {
493 MlsMessagePayload::GroupInfo(i) => Some(i.group_context.cipher_suite),
494 MlsMessagePayload::Welcome(w) => Some(w.cipher_suite),
495 MlsMessagePayload::KeyPackage(k) => Some(k.cipher_suite),
496 _ => None,
497 }
498 }
499
500 pub fn group_id(&self) -> Option<&[u8]> {
501 match &self.payload {
502 MlsMessagePayload::Plain(p) => Some(&p.content.group_id),
503 #[cfg(feature = "private_message")]
504 MlsMessagePayload::Cipher(p) => Some(&p.group_id),
505 MlsMessagePayload::GroupInfo(p) => Some(&p.group_context.group_id),
506 MlsMessagePayload::KeyPackage(_) | MlsMessagePayload::Welcome(_) => None,
507 }
508 }
509
510 #[inline(never)]
512 pub fn from_bytes(bytes: &[u8]) -> Result<Self, MlsError> {
513 Self::mls_decode(&mut &*bytes).map_err(Into::into)
514 }
515
516 pub fn to_bytes(&self) -> Result<Vec<u8>, MlsError> {
518 self.mls_encode_to_vec().map_err(Into::into)
519 }
520
521 #[cfg(feature = "custom_proposal")]
524 pub fn custom_proposals_by_value(&self) -> Vec<&CustomProposal> {
525 match &self.payload {
526 MlsMessagePayload::Plain(plaintext) => match &plaintext.content.content {
527 Content::Commit(commit) => Self::find_custom_proposals(commit),
528 _ => Vec::new(),
529 },
530 _ => Vec::new(),
531 }
532 }
533
534 #[allow(unreachable_patterns)]
538 pub fn proposals_by_value(&self) -> Vec<&Proposal> {
539 match &self.payload {
540 MlsMessagePayload::Plain(plaintext) => match &plaintext.content.content {
541 Content::Commit(commit) => Self::find_all_proposals(commit),
542 _ => Vec::new(),
543 },
544 _ => Vec::new(),
545 }
546 }
547
548 #[allow(unreachable_patterns)]
553 pub fn commit_path_leaf_node(&self) -> Option<&LeafNode> {
554 match &self.payload {
555 MlsMessagePayload::Plain(plaintext) => match &plaintext.content.content {
556 Content::Commit(commit) => commit.path.as_ref().map(|path| &path.leaf_node),
557 _ => None,
558 },
559 _ => None,
560 }
561 }
562
563 pub fn welcome_key_package_references(&self) -> Vec<&KeyPackageRef> {
566 let MlsMessagePayload::Welcome(welcome) = &self.payload else {
567 return Vec::new();
568 };
569
570 welcome.secrets.iter().map(|s| &s.new_member).collect()
571 }
572
573 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
575 pub async fn key_package_reference<C: CipherSuiteProvider>(
576 &self,
577 cipher_suite: &C,
578 ) -> Result<Option<KeyPackageRef>, MlsError> {
579 let MlsMessagePayload::KeyPackage(kp) = &self.payload else {
580 return Ok(None);
581 };
582
583 kp.to_reference(cipher_suite).await.map(Some)
584 }
585
586 #[cfg(feature = "by_ref_proposal")]
589 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
590 pub async fn into_proposal_reference<C: CipherSuiteProvider>(
591 self,
592 cipher_suite: &C,
593 ) -> Result<Option<Vec<u8>>, MlsError> {
594 let MlsMessagePayload::Plain(public_message) = self.payload else {
595 return Ok(None);
596 };
597
598 ProposalRef::from_content(cipher_suite, &public_message.into())
599 .await
600 .map(|r| Some(r.to_vec()))
601 }
602}
603
604impl MlsMessage {
605 #[cfg(feature = "custom_proposal")]
606 fn find_custom_proposals(commit: &Commit) -> Vec<&CustomProposal> {
607 commit
608 .proposals
609 .iter()
610 .filter_map(|p| match p {
611 ProposalOrRef::Proposal(p) => match p.as_ref() {
612 crate::group::Proposal::Custom(p) => Some(p),
613 _ => None,
614 },
615 _ => None,
616 })
617 .collect()
618 }
619
620 #[allow(unreachable_patterns)]
621 fn find_all_proposals(commit: &Commit) -> Vec<&Proposal> {
622 commit
623 .proposals
624 .iter()
625 .filter_map(|p| match p {
626 ProposalOrRef::Proposal(p) => Some(p.as_ref()),
627 _ => None,
628 })
629 .collect()
630 }
631}
632
633#[allow(clippy::large_enum_variant)]
634#[derive(Clone, Debug, PartialEq, MlsSize, MlsEncode, MlsDecode)]
635#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
636#[repr(u16)]
637pub(crate) enum MlsMessagePayload {
638 Plain(PublicMessage) = 1u16,
639 #[cfg(feature = "private_message")]
640 Cipher(PrivateMessage) = 2u16,
641 Welcome(Welcome) = 3u16,
642 GroupInfo(GroupInfo) = 4u16,
643 KeyPackage(KeyPackage) = 5u16,
644}
645
646impl From<PublicMessage> for MlsMessagePayload {
647 fn from(m: PublicMessage) -> Self {
648 Self::Plain(m)
649 }
650}
651
652#[derive(
653 Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, MlsSize, MlsEncode, MlsDecode,
654)]
655#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
656#[repr(u16)]
657#[non_exhaustive]
658pub enum WireFormat {
660 PublicMessage = 1u16,
661 PrivateMessage = 2u16,
662 Welcome = 3u16,
663 GroupInfo = 4u16,
664 KeyPackage = 5u16,
665}
666
667#[derive(Clone, PartialEq, MlsSize, MlsEncode, MlsDecode)]
668#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
669#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
670pub(crate) struct FramedContent {
671 #[mls_codec(with = "mls_rs_codec::byte_vec")]
672 #[cfg_attr(feature = "serde", serde(with = "mls_rs_core::vec_serde"))]
673 pub group_id: Vec<u8>,
674 pub epoch: u64,
675 pub sender: Sender,
676 #[mls_codec(with = "mls_rs_codec::byte_vec")]
677 #[cfg_attr(feature = "serde", serde(with = "mls_rs_core::vec_serde"))]
678 pub authenticated_data: Vec<u8>,
679 pub content: Content,
680}
681
682impl Debug for FramedContent {
683 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
684 f.debug_struct("FramedContent")
685 .field(
686 "group_id",
687 &mls_rs_core::debug::pretty_group_id(&self.group_id),
688 )
689 .field("epoch", &self.epoch)
690 .field("sender", &self.sender)
691 .field(
692 "authenticated_data",
693 &mls_rs_core::debug::pretty_bytes(&self.authenticated_data),
694 )
695 .field("content", &self.content)
696 .finish()
697 }
698}
699
700impl FramedContent {
701 pub fn content_type(&self) -> ContentType {
702 self.content.content_type()
703 }
704}
705
706#[cfg(test)]
707pub(crate) mod test_utils {
708 #[cfg(feature = "private_message")]
709 use crate::group::test_utils::random_bytes;
710
711 use crate::group::{AuthenticatedContent, MessageSignature};
712
713 use super::*;
714
715 use alloc::boxed::Box;
716
717 pub(crate) fn get_test_auth_content() -> AuthenticatedContent {
718 let commit = Commit {
720 proposals: Default::default(),
721 path: None,
722 };
723
724 AuthenticatedContent {
725 wire_format: WireFormat::PublicMessage,
726 content: FramedContent {
727 group_id: Vec::new(),
728 epoch: 0,
729 sender: Sender::Member(1),
730 authenticated_data: Vec::new(),
731 content: Content::Commit(Box::new(commit)),
732 },
733 auth: FramedContentAuthData {
734 signature: MessageSignature::empty(),
735 confirmation_tag: None,
736 },
737 }
738 }
739
740 #[cfg(feature = "private_message")]
741 pub(crate) fn get_test_ciphertext_content() -> PrivateMessageContent {
742 PrivateMessageContent {
743 content: Content::Application(random_bytes(1024).into()),
744 auth: FramedContentAuthData {
745 signature: MessageSignature::from(random_bytes(128)),
746 confirmation_tag: None,
747 },
748 }
749 }
750
751 impl AsRef<[u8]> for ApplicationData {
752 fn as_ref(&self) -> &[u8] {
753 &self.0
754 }
755 }
756}
757
758#[cfg(feature = "private_message")]
759#[cfg(test)]
760mod tests {
761 use alloc::vec;
762 use assert_matches::assert_matches;
763
764 use crate::{
765 client::test_utils::{TEST_CIPHER_SUITE, TEST_PROTOCOL_VERSION},
766 crypto::test_utils::test_cipher_suite_provider,
767 group::{
768 framing::test_utils::get_test_ciphertext_content,
769 proposal_ref::test_utils::auth_content_from_proposal, test_utils::test_group,
770 RemoveProposal,
771 },
772 key_package::test_utils::test_key_package_message,
773 };
774
775 use super::*;
776
777 #[test]
778 fn test_mls_ciphertext_content_mls_encoding() {
779 let ciphertext_content = get_test_ciphertext_content();
780
781 let mut encoded = ciphertext_content.mls_encode_to_vec().unwrap();
782 encoded.extend_from_slice(&[0u8; 128]);
783
784 let decoded =
785 PrivateMessageContent::mls_decode(&mut &*encoded, (&ciphertext_content.content).into())
786 .unwrap();
787
788 assert_eq!(ciphertext_content, decoded);
789 }
790
791 #[test]
792 fn test_mls_ciphertext_content_non_zero_padding_error() {
793 let ciphertext_content = get_test_ciphertext_content();
794
795 let mut encoded = ciphertext_content.mls_encode_to_vec().unwrap();
796 encoded.extend_from_slice(&[1u8; 128]);
797
798 let decoded =
799 PrivateMessageContent::mls_decode(&mut &*encoded, (&ciphertext_content.content).into());
800
801 assert_matches!(decoded, Err(mls_rs_codec::Error::Custom(_)));
802 }
803
804 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
805 async fn proposal_ref() {
806 let cs = test_cipher_suite_provider(TEST_CIPHER_SUITE);
807
808 let test_auth = auth_content_from_proposal(
809 Proposal::Remove(RemoveProposal {
810 to_remove: LeafIndex::unchecked(0),
811 }),
812 Sender::External(0),
813 );
814
815 let expected_ref = ProposalRef::from_content(&cs, &test_auth).await.unwrap();
816
817 let test_message = MlsMessage {
818 version: TEST_PROTOCOL_VERSION,
819 payload: MlsMessagePayload::Plain(PublicMessage {
820 content: test_auth.content,
821 auth: test_auth.auth,
822 membership_tag: Some(cs.mac(&[1, 2, 3], &[1, 2, 3]).await.unwrap().into()),
823 }),
824 };
825
826 let computed_ref = test_message
827 .into_proposal_reference(&cs)
828 .await
829 .unwrap()
830 .unwrap();
831
832 assert_eq!(computed_ref, expected_ref.to_vec());
833 }
834
835 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
836 async fn message_description() {
837 let mut group = test_group(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE).await;
838
839 let message = group.commit(vec![]).await.unwrap();
840
841 let expected = MlsMessageDescription::PublicProtocolMessage {
842 group_id: group.group_id(),
843 epoch_id: group.context().epoch,
844 content_type: ContentType::Commit,
845 sender: Sender::Member(0),
846 authenticated_data: &[],
847 };
848
849 assert_eq!(message.commit_message.description(), expected);
850
851 group.apply_pending_commit().await.unwrap();
852
853 let message = group
854 .encrypt_application_message(b"123", vec![])
855 .await
856 .unwrap();
857
858 let expected = MlsMessageDescription::PrivateProtocolMessage {
859 group_id: group.group_id(),
860 epoch_id: group.context().epoch,
861 content_type: ContentType::Application,
862 };
863
864 assert_eq!(message.description(), expected);
865
866 let group_info = group
867 .group_info_message_allowing_ext_commit(true)
868 .await
869 .unwrap();
870
871 assert_eq!(group_info.description(), MlsMessageDescription::GroupInfo);
872
873 let key_package =
874 test_key_package_message(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE, "something").await;
875
876 assert_eq!(key_package.description(), MlsMessageDescription::KeyPackage);
877 }
878}