1use crate::room_state::member::MemberId;
2use crate::room_state::privacy::{PrivacyMode, SecretVersion};
3use crate::room_state::ChatRoomParametersV1;
4use crate::util::sign_struct;
5use crate::util::{truncated_base64, verify_struct};
6use crate::ChatRoomStateV1;
7use ed25519_dalek::{Signature, SigningKey, VerifyingKey};
8use freenet_scaffold::util::{fast_hash, FastHash};
9use freenet_scaffold::ComposableState;
10use serde::{Deserialize, Serialize};
11use std::collections::HashMap;
12use std::fmt;
13use std::time::SystemTime;
14
15#[derive(Clone, PartialEq, Debug, Default)]
18pub struct MessageActionsState {
19 pub edited_content: HashMap<MessageId, String>,
21 pub deleted: std::collections::HashSet<MessageId>,
23 pub reactions: HashMap<MessageId, HashMap<String, Vec<MemberId>>>,
25}
26
27#[derive(Serialize, Deserialize, Clone, PartialEq, Debug, Default)]
28pub struct MessagesV1 {
29 pub messages: Vec<AuthorizedMessageV1>,
30 #[serde(skip)]
32 pub actions_state: MessageActionsState,
33}
34
35impl ComposableState for MessagesV1 {
36 type ParentState = ChatRoomStateV1;
37 type Summary = Vec<MessageId>;
38 type Delta = Vec<AuthorizedMessageV1>;
39 type Parameters = ChatRoomParametersV1;
40
41 fn verify(
42 &self,
43 parent_state: &Self::ParentState,
44 parameters: &Self::Parameters,
45 ) -> Result<(), String> {
46 let members_by_id = parent_state.members.members_by_member_id();
47 let owner_id = parameters.owner_id();
48
49 for message in &self.messages {
50 let verifying_key = if message.message.author == owner_id {
51 ¶meters.owner
53 } else if let Some(member) = members_by_id.get(&message.message.author) {
54 &member.member.member_vk
56 } else {
57 return Err(format!(
58 "Message author not found: {:?}",
59 message.message.author
60 ));
61 };
62
63 if message.validate(verifying_key).is_err() {
64 return Err(format!("Invalid message signature: id:{:?}", message.id()));
65 }
66 }
67
68 Ok(())
69 }
70
71 fn summarize(
72 &self,
73 _parent_state: &Self::ParentState,
74 _parameters: &Self::Parameters,
75 ) -> Self::Summary {
76 self.messages.iter().map(|m| m.id()).collect()
77 }
78
79 fn delta(
80 &self,
81 _parent_state: &Self::ParentState,
82 _parameters: &Self::Parameters,
83 old_state_summary: &Self::Summary,
84 ) -> Option<Self::Delta> {
85 let delta: Vec<AuthorizedMessageV1> = self
86 .messages
87 .iter()
88 .filter(|m| !old_state_summary.contains(&m.id()))
89 .cloned()
90 .collect();
91 if delta.is_empty() {
92 None
93 } else {
94 Some(delta)
95 }
96 }
97
98 fn apply_delta(
99 &mut self,
100 parent_state: &Self::ParentState,
101 parameters: &Self::Parameters,
102 delta: &Option<Self::Delta>,
103 ) -> Result<(), String> {
104 let max_recent_messages = parent_state.configuration.configuration.max_recent_messages;
105 let max_message_size = parent_state.configuration.configuration.max_message_size;
106 let privacy_mode = &parent_state.configuration.configuration.privacy_mode;
107 let current_secret_version = parent_state.secrets.current_version;
108
109 if let Some(delta) = delta {
111 for msg in delta {
112 let content = &msg.message.content;
113
114 match content {
115 RoomMessageBody::Private { secret_version, .. } => {
116 if *privacy_mode == PrivacyMode::Private
118 && *secret_version != current_secret_version
119 {
120 return Err(format!(
121 "Private message secret version {} does not match current version {}",
122 secret_version, current_secret_version
123 ));
124 }
125
126 let members = parent_state.members.members_by_member_id();
128 if !parent_state.secrets.has_complete_distribution(&members) {
129 return Err(
130 "Cannot accept private messages: incomplete secret distribution"
131 .to_string(),
132 );
133 }
134 }
135 RoomMessageBody::Public { .. } => {
136 if *privacy_mode == PrivacyMode::Private && !content.is_event() {
139 return Err("Cannot send public messages in private room".to_string());
140 }
141 }
142 }
143 }
144
145 let existing_ids: std::collections::HashSet<_> =
147 self.messages.iter().map(|m| m.id()).collect();
148 self.messages.extend(
149 delta
150 .iter()
151 .filter(|msg| !existing_ids.contains(&msg.id()))
152 .cloned(),
153 );
154 }
155
156 self.messages
159 .retain(|m| m.message.content.content_len() <= max_message_size);
160
161 let members_by_id = parent_state.members.members_by_member_id();
163 let owner_id = MemberId::from(¶meters.owner);
164 self.messages.retain(|m| {
165 members_by_id.contains_key(&m.message.author) || m.message.author == owner_id
166 });
167
168 self.messages.sort_by(|a, b| {
171 a.message
172 .time
173 .cmp(&b.message.time)
174 .then_with(|| a.id().cmp(&b.id()))
175 });
176
177 if self.messages.len() > max_recent_messages {
179 self.messages
180 .drain(0..self.messages.len() - max_recent_messages);
181 }
182
183 self.rebuild_actions_state();
185
186 Ok(())
187 }
188}
189
190impl MessagesV1 {
191 pub fn rebuild_actions_state(&mut self) {
197 self.rebuild_actions_state_with_decrypted(&HashMap::new());
198 }
199
200 pub fn rebuild_actions_state_with_decrypted(
209 &mut self,
210 decrypted_content: &HashMap<MessageId, Vec<u8>>,
211 ) {
212 use crate::room_state::content::{
213 ActionContentV1, DecodedContent, ACTION_TYPE_DELETE, ACTION_TYPE_EDIT,
214 ACTION_TYPE_REACTION, ACTION_TYPE_REMOVE_REACTION,
215 };
216
217 self.actions_state = MessageActionsState::default();
219
220 let message_authors: HashMap<MessageId, MemberId> = self
222 .messages
223 .iter()
224 .filter(|m| !m.message.content.is_action())
225 .map(|m| (m.id(), m.message.author))
226 .collect();
227
228 for msg in &self.messages {
230 let actor = msg.message.author;
231
232 if !msg.message.content.is_action() {
234 continue;
235 }
236
237 let action = match &msg.message.content {
239 RoomMessageBody::Public { .. } => {
240 match msg.message.content.decode_content() {
242 Some(DecodedContent::Action(action)) => action,
243 _ => continue,
244 }
245 }
246 RoomMessageBody::Private { .. } => {
247 let msg_id = msg.id();
249 if let Some(plaintext) = decrypted_content.get(&msg_id) {
250 match ActionContentV1::decode(plaintext) {
251 Ok(action) => action,
252 Err(_) => continue,
253 }
254 } else {
255 continue;
257 }
258 }
259 };
260
261 let target = &action.target;
262
263 match action.action_type {
264 ACTION_TYPE_EDIT => {
265 if let Some(&original_author) = message_authors.get(target) {
267 if actor == original_author {
268 if !self.actions_state.deleted.contains(target) {
270 if let Some(payload) = action.edit_payload() {
271 self.actions_state
272 .edited_content
273 .insert(target.clone(), payload.new_text);
274 }
275 }
276 }
277 }
278 }
279 ACTION_TYPE_DELETE => {
280 if let Some(&original_author) = message_authors.get(target) {
282 if actor == original_author {
283 self.actions_state.deleted.insert(target.clone());
284 self.actions_state.edited_content.remove(target);
286 }
287 }
288 }
289 ACTION_TYPE_REACTION => {
290 if message_authors.contains_key(target)
292 && !self.actions_state.deleted.contains(target)
293 {
294 if let Some(payload) = action.reaction_payload() {
295 let reactions = self
296 .actions_state
297 .reactions
298 .entry(target.clone())
299 .or_default();
300 let reactors = reactions.entry(payload.emoji).or_default();
301 if !reactors.contains(&actor) {
303 reactors.push(actor);
304 }
305 }
306 }
307 }
308 ACTION_TYPE_REMOVE_REACTION => {
309 if let Some(payload) = action.reaction_payload() {
311 if let Some(reactions) = self.actions_state.reactions.get_mut(target) {
312 if let Some(reactors) = reactions.get_mut(&payload.emoji) {
313 reactors.retain(|r| r != &actor);
314 if reactors.is_empty() {
316 reactions.remove(&payload.emoji);
317 }
318 }
319 if reactions.is_empty() {
320 self.actions_state.reactions.remove(target);
321 }
322 }
323 }
324 }
325 _ => {
326 }
328 }
329 }
330 }
331
332 pub fn is_edited(&self, message_id: &MessageId) -> bool {
334 self.actions_state.edited_content.contains_key(message_id)
335 }
336
337 pub fn is_deleted(&self, message_id: &MessageId) -> bool {
339 self.actions_state.deleted.contains(message_id)
340 }
341
342 pub fn effective_text(&self, message: &AuthorizedMessageV1) -> Option<String> {
345 let id = message.id();
346 if let Some(edited_text) = self.actions_state.edited_content.get(&id) {
348 return Some(edited_text.clone());
349 }
350 message.message.content.as_public_string()
352 }
353
354 pub fn reactions(&self, message_id: &MessageId) -> Option<&HashMap<String, Vec<MemberId>>> {
356 self.actions_state.reactions.get(message_id)
357 }
358
359 pub fn display_messages(&self) -> impl Iterator<Item = &AuthorizedMessageV1> {
361 self.messages.iter().filter(|m| {
362 !m.message.content.is_action() && !self.actions_state.deleted.contains(&m.id())
363 })
364 }
365}
366
367#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)]
387pub enum RoomMessageBody {
388 Public {
390 content_type: u32,
392 content_version: u32,
394 data: Vec<u8>,
396 },
397 Private {
399 content_type: u32,
401 content_version: u32,
403 ciphertext: Vec<u8>,
405 nonce: [u8; 12],
407 secret_version: SecretVersion,
409 },
410}
411
412impl RoomMessageBody {
413 pub fn public(text: String) -> Self {
415 use crate::room_state::content::{TextContentV1, CONTENT_TYPE_TEXT, TEXT_CONTENT_VERSION};
416 let content = TextContentV1::new(text);
417 Self::Public {
418 content_type: CONTENT_TYPE_TEXT,
419 content_version: TEXT_CONTENT_VERSION,
420 data: content.encode(),
421 }
422 }
423
424 pub fn join_event() -> Self {
426 use crate::room_state::content::{
427 EventContentV1, CONTENT_TYPE_EVENT, EVENT_CONTENT_VERSION,
428 };
429 let content = EventContentV1::join();
430 Self::Public {
431 content_type: CONTENT_TYPE_EVENT,
432 content_version: EVENT_CONTENT_VERSION,
433 data: content.encode(),
434 }
435 }
436
437 pub fn public_raw(content_type: u32, content_version: u32, data: Vec<u8>) -> Self {
439 Self::Public {
440 content_type,
441 content_version,
442 data,
443 }
444 }
445
446 pub fn private(
448 content_type: u32,
449 content_version: u32,
450 ciphertext: Vec<u8>,
451 nonce: [u8; 12],
452 secret_version: SecretVersion,
453 ) -> Self {
454 Self::Private {
455 content_type,
456 content_version,
457 ciphertext,
458 nonce,
459 secret_version,
460 }
461 }
462
463 pub fn private_text(
465 ciphertext: Vec<u8>,
466 nonce: [u8; 12],
467 secret_version: SecretVersion,
468 ) -> Self {
469 use crate::room_state::content::{CONTENT_TYPE_TEXT, TEXT_CONTENT_VERSION};
470 Self::Private {
471 content_type: CONTENT_TYPE_TEXT,
472 content_version: TEXT_CONTENT_VERSION,
473 ciphertext,
474 nonce,
475 secret_version,
476 }
477 }
478
479 pub fn edit(target: MessageId, new_text: String) -> Self {
481 use crate::room_state::content::{
482 ActionContentV1, ACTION_CONTENT_VERSION, CONTENT_TYPE_ACTION,
483 };
484 let action = ActionContentV1::edit(target, new_text);
485 Self::Public {
486 content_type: CONTENT_TYPE_ACTION,
487 content_version: ACTION_CONTENT_VERSION,
488 data: action.encode(),
489 }
490 }
491
492 pub fn delete(target: MessageId) -> Self {
494 use crate::room_state::content::{
495 ActionContentV1, ACTION_CONTENT_VERSION, CONTENT_TYPE_ACTION,
496 };
497 let action = ActionContentV1::delete(target);
498 Self::Public {
499 content_type: CONTENT_TYPE_ACTION,
500 content_version: ACTION_CONTENT_VERSION,
501 data: action.encode(),
502 }
503 }
504
505 pub fn reaction(target: MessageId, emoji: String) -> Self {
507 use crate::room_state::content::{
508 ActionContentV1, ACTION_CONTENT_VERSION, CONTENT_TYPE_ACTION,
509 };
510 let action = ActionContentV1::reaction(target, emoji);
511 Self::Public {
512 content_type: CONTENT_TYPE_ACTION,
513 content_version: ACTION_CONTENT_VERSION,
514 data: action.encode(),
515 }
516 }
517
518 pub fn remove_reaction(target: MessageId, emoji: String) -> Self {
520 use crate::room_state::content::{
521 ActionContentV1, ACTION_CONTENT_VERSION, CONTENT_TYPE_ACTION,
522 };
523 let action = ActionContentV1::remove_reaction(target, emoji);
524 Self::Public {
525 content_type: CONTENT_TYPE_ACTION,
526 content_version: ACTION_CONTENT_VERSION,
527 data: action.encode(),
528 }
529 }
530
531 pub fn reply(
533 text: String,
534 target_message_id: MessageId,
535 target_author_name: String,
536 target_content_preview: String,
537 ) -> Self {
538 use crate::room_state::content::{
539 ReplyContentV1, CONTENT_TYPE_REPLY, REPLY_CONTENT_VERSION,
540 };
541 let reply = ReplyContentV1::new(
542 text,
543 target_message_id,
544 target_author_name,
545 target_content_preview,
546 );
547 Self::Public {
548 content_type: CONTENT_TYPE_REPLY,
549 content_version: REPLY_CONTENT_VERSION,
550 data: reply.encode(),
551 }
552 }
553
554 pub fn private_action(
563 ciphertext: Vec<u8>,
564 nonce: [u8; 12],
565 secret_version: SecretVersion,
566 ) -> Self {
567 use crate::room_state::content::{ACTION_CONTENT_VERSION, CONTENT_TYPE_ACTION};
568 Self::Private {
569 content_type: CONTENT_TYPE_ACTION,
570 content_version: ACTION_CONTENT_VERSION,
571 ciphertext,
572 nonce,
573 secret_version,
574 }
575 }
576
577 pub fn is_public(&self) -> bool {
579 matches!(self, Self::Public { .. })
580 }
581
582 pub fn is_private(&self) -> bool {
584 matches!(self, Self::Private { .. })
585 }
586
587 pub fn content_type(&self) -> u32 {
589 match self {
590 Self::Public { content_type, .. } | Self::Private { content_type, .. } => *content_type,
591 }
592 }
593
594 pub fn content_version(&self) -> u32 {
596 match self {
597 Self::Public {
598 content_version, ..
599 }
600 | Self::Private {
601 content_version, ..
602 } => *content_version,
603 }
604 }
605
606 pub fn is_action(&self) -> bool {
608 use crate::room_state::content::CONTENT_TYPE_ACTION;
609 self.content_type() == CONTENT_TYPE_ACTION
610 }
611
612 pub fn is_event(&self) -> bool {
614 use crate::room_state::content::CONTENT_TYPE_EVENT;
615 self.content_type() == CONTENT_TYPE_EVENT
616 }
617
618 pub fn decode_content(&self) -> Option<crate::room_state::content::DecodedContent> {
621 use crate::room_state::content::{
622 ActionContentV1, DecodedContent, EventContentV1, ReplyContentV1, TextContentV1,
623 CONTENT_TYPE_ACTION, CONTENT_TYPE_EVENT, CONTENT_TYPE_REPLY, CONTENT_TYPE_TEXT,
624 };
625 match self {
626 Self::Public {
627 content_type,
628 content_version,
629 data,
630 } => match *content_type {
631 CONTENT_TYPE_TEXT => TextContentV1::decode(data).ok().map(DecodedContent::Text),
632 CONTENT_TYPE_ACTION => ActionContentV1::decode(data)
633 .ok()
634 .map(DecodedContent::Action),
635 CONTENT_TYPE_REPLY => ReplyContentV1::decode(data).ok().map(DecodedContent::Reply),
636 CONTENT_TYPE_EVENT => EventContentV1::decode(data).ok().map(DecodedContent::Event),
637 _ => Some(DecodedContent::Unknown {
638 content_type: *content_type,
639 content_version: *content_version,
640 }),
641 },
642 Self::Private { .. } => None,
643 }
644 }
645
646 pub fn target_id(&self) -> Option<MessageId> {
648 use crate::room_state::content::{ActionContentV1, CONTENT_TYPE_ACTION};
649 match self {
650 Self::Public {
651 content_type, data, ..
652 } if *content_type == CONTENT_TYPE_ACTION => {
653 ActionContentV1::decode(data).ok().map(|a| a.target)
654 }
655 _ => None,
656 }
657 }
658
659 pub fn content_len(&self) -> usize {
661 match self {
662 Self::Public { data, .. } => data.len(),
663 Self::Private { ciphertext, .. } => ciphertext.len(),
664 }
665 }
666
667 pub fn secret_version(&self) -> Option<SecretVersion> {
669 match self {
670 Self::Public { .. } => None,
671 Self::Private { secret_version, .. } => Some(*secret_version),
672 }
673 }
674
675 pub fn to_string_lossy(&self) -> String {
677 match self {
678 Self::Public { .. } => {
679 if let Some(decoded) = self.decode_content() {
680 decoded.to_display_string()
681 } else {
682 "[Failed to decode message]".to_string()
683 }
684 }
685 Self::Private {
686 ciphertext,
687 secret_version,
688 ..
689 } => {
690 format!(
691 "[Encrypted message: {} bytes, v{}]",
692 ciphertext.len(),
693 secret_version
694 )
695 }
696 }
697 }
698
699 pub fn as_public_string(&self) -> Option<String> {
701 self.decode_content()
702 .and_then(|c| c.as_text().map(|s| s.to_string()))
703 }
704}
705
706impl fmt::Display for RoomMessageBody {
707 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
708 write!(f, "{}", self.to_string_lossy())
709 }
710}
711
712#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)]
713pub struct MessageV1 {
714 pub room_owner: MemberId,
715 pub author: MemberId,
716 pub time: SystemTime,
717 pub content: RoomMessageBody,
718}
719
720impl Default for MessageV1 {
721 fn default() -> Self {
722 Self {
723 room_owner: MemberId(FastHash(0)),
724 author: MemberId(FastHash(0)),
725 time: SystemTime::UNIX_EPOCH,
726 content: RoomMessageBody::public(String::new()),
727 }
728 }
729}
730
731#[derive(Clone, PartialEq, Serialize, Deserialize)]
732pub struct AuthorizedMessageV1 {
733 pub message: MessageV1,
734 pub signature: Signature,
735}
736
737impl fmt::Debug for AuthorizedMessageV1 {
738 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
739 f.debug_struct("AuthorizedMessage")
740 .field("message", &self.message)
741 .field(
742 "signature",
743 &format_args!("{}", truncated_base64(self.signature.to_bytes())),
744 )
745 .finish()
746 }
747}
748
749#[derive(Eq, PartialEq, Hash, Serialize, Deserialize, Clone, Debug, Ord, PartialOrd)]
750pub struct MessageId(pub FastHash);
751
752impl fmt::Display for MessageId {
753 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
754 write!(f, "{:?}", self.0)
755 }
756}
757
758impl AuthorizedMessageV1 {
759 pub fn new(message: MessageV1, signing_key: &SigningKey) -> Self {
760 Self {
761 message: message.clone(),
762 signature: sign_struct(&message, signing_key),
763 }
764 }
765
766 pub fn with_signature(message: MessageV1, signature: Signature) -> Self {
769 Self { message, signature }
770 }
771
772 pub fn validate(
773 &self,
774 verifying_key: &VerifyingKey,
775 ) -> Result<(), ed25519_dalek::SignatureError> {
776 verify_struct(&self.message, &self.signature, verifying_key)
777 }
778
779 pub fn id(&self) -> MessageId {
780 MessageId(fast_hash(&self.signature.to_bytes()))
781 }
782}
783
784#[cfg(test)]
785mod tests {
786 use super::*;
787 use ed25519_dalek::{Signer, SigningKey};
788 use rand::rngs::OsRng;
789 use std::time::Duration;
790
791 fn create_test_message(owner_id: MemberId, author_id: MemberId) -> MessageV1 {
792 MessageV1 {
793 room_owner: owner_id,
794 author: author_id,
795 time: SystemTime::now(),
796 content: RoomMessageBody::public("Test message".to_string()),
797 }
798 }
799
800 #[test]
801 fn test_messages_v1_default() {
802 let default_messages = MessagesV1::default();
803 assert!(default_messages.messages.is_empty());
804 }
805
806 #[test]
807 fn test_authorized_message_v1_debug() {
808 let signing_key = SigningKey::generate(&mut OsRng);
809 let owner_id = MemberId(FastHash(0));
810 let author_id = MemberId(FastHash(1));
811
812 let message = create_test_message(owner_id, author_id);
813 let authorized_message = AuthorizedMessageV1::new(message, &signing_key);
814
815 let debug_output = format!("{:?}", authorized_message);
816 assert!(debug_output.contains("AuthorizedMessage"));
817 assert!(debug_output.contains("message"));
818 assert!(debug_output.contains("signature"));
819 }
820
821 #[test]
822 fn test_authorized_message_new_and_validate() {
823 let signing_key = SigningKey::generate(&mut OsRng);
824 let verifying_key = signing_key.verifying_key();
825 let owner_id = MemberId(FastHash(0));
826 let author_id = MemberId(FastHash(1));
827
828 let message = create_test_message(owner_id, author_id);
829 let authorized_message = AuthorizedMessageV1::new(message.clone(), &signing_key);
830
831 assert_eq!(authorized_message.message, message);
832 assert!(authorized_message.validate(&verifying_key).is_ok());
833
834 let wrong_key = SigningKey::generate(&mut OsRng).verifying_key();
836 assert!(authorized_message.validate(&wrong_key).is_err());
837
838 let mut tampered_message = authorized_message.clone();
840 tampered_message.message.content = RoomMessageBody::public("Tampered content".to_string());
841 assert!(tampered_message.validate(&verifying_key).is_err());
842 }
843
844 #[test]
845 fn test_message_id() {
846 let signing_key = SigningKey::generate(&mut OsRng);
847 let owner_id = MemberId(FastHash(0));
848 let author_id = MemberId(FastHash(1));
849
850 let message = create_test_message(owner_id, author_id);
851 let authorized_message = AuthorizedMessageV1::new(message, &signing_key);
852
853 let id1 = authorized_message.id();
854 let id2 = authorized_message.id();
855
856 assert_eq!(id1, id2);
857
858 let message2 = create_test_message(owner_id, author_id);
860 let authorized_message2 = AuthorizedMessageV1::new(message2, &signing_key);
861 assert_ne!(authorized_message.id(), authorized_message2.id());
862 }
863
864 #[test]
865 fn test_messages_verify() {
866 let owner_signing_key = SigningKey::generate(&mut OsRng);
868 let owner_verifying_key = owner_signing_key.verifying_key();
869 let owner_id = MemberId::from(&owner_verifying_key);
870
871 let author_signing_key = SigningKey::generate(&mut OsRng);
873 let author_verifying_key = author_signing_key.verifying_key();
874 let author_id = MemberId::from(&author_verifying_key);
875
876 let message = create_test_message(owner_id, author_id);
878 let authorized_message = AuthorizedMessageV1::new(message, &author_signing_key);
879
880 let messages = MessagesV1 {
882 messages: vec![authorized_message],
883 ..Default::default()
884 };
885
886 let mut parent_state = ChatRoomStateV1::default();
888 let author_member = crate::room_state::member::Member {
889 owner_member_id: owner_id,
890 invited_by: owner_id,
891 member_vk: author_verifying_key,
892 };
893 let authorized_author =
894 crate::room_state::member::AuthorizedMember::new(author_member, &owner_signing_key);
895 parent_state.members.members = vec![authorized_author];
896
897 let parameters = ChatRoomParametersV1 {
899 owner: owner_verifying_key,
900 };
901
902 assert!(
904 messages.verify(&parent_state, ¶meters).is_ok(),
905 "Valid messages should pass verification: {:?}",
906 messages.verify(&parent_state, ¶meters)
907 );
908
909 let mut invalid_messages = messages.clone();
911 invalid_messages.messages[0].signature = Signature::from_bytes(&[0; 64]); assert!(
913 invalid_messages.verify(&parent_state, ¶meters).is_err(),
914 "Messages with invalid signature should fail verification"
915 );
916
917 let non_existent_author_id =
919 MemberId::from(&SigningKey::generate(&mut OsRng).verifying_key());
920 let invalid_message = create_test_message(owner_id, non_existent_author_id);
921 let invalid_authorized_message =
922 AuthorizedMessageV1::new(invalid_message, &author_signing_key);
923 let invalid_messages = MessagesV1 {
924 messages: vec![invalid_authorized_message],
925 ..Default::default()
926 };
927 assert!(
928 invalid_messages.verify(&parent_state, ¶meters).is_err(),
929 "Messages with non-existent author should fail verification"
930 );
931 }
932
933 #[test]
934 fn test_messages_summarize() {
935 let signing_key = SigningKey::generate(&mut OsRng);
936 let owner_id = MemberId(FastHash(0));
937 let author_id = MemberId(FastHash(1));
938
939 let message1 = create_test_message(owner_id, author_id);
940 let message2 = create_test_message(owner_id, author_id);
941
942 let authorized_message1 = AuthorizedMessageV1::new(message1, &signing_key);
943 let authorized_message2 = AuthorizedMessageV1::new(message2, &signing_key);
944
945 let messages = MessagesV1 {
946 messages: vec![authorized_message1.clone(), authorized_message2.clone()],
947 ..Default::default()
948 };
949
950 let parent_state = ChatRoomStateV1::default();
951 let parameters = ChatRoomParametersV1 {
952 owner: signing_key.verifying_key(),
953 };
954
955 let summary = messages.summarize(&parent_state, ¶meters);
956 assert_eq!(summary.len(), 2);
957 assert_eq!(summary[0], authorized_message1.id());
958 assert_eq!(summary[1], authorized_message2.id());
959
960 let empty_messages = MessagesV1::default();
962 let empty_summary = empty_messages.summarize(&parent_state, ¶meters);
963 assert!(empty_summary.is_empty());
964 }
965
966 #[test]
967 fn test_messages_delta() {
968 let signing_key = SigningKey::generate(&mut OsRng);
969 let owner_id = MemberId(FastHash(0));
970 let author_id = MemberId(FastHash(1));
971
972 let base = SystemTime::now();
974 let message1 = MessageV1 {
975 room_owner: owner_id,
976 author: author_id,
977 time: base,
978 content: RoomMessageBody::public("Message 1".to_string()),
979 };
980 let message2 = MessageV1 {
981 room_owner: owner_id,
982 author: author_id,
983 time: base + Duration::from_millis(1),
984 content: RoomMessageBody::public("Message 2".to_string()),
985 };
986 let message3 = MessageV1 {
987 room_owner: owner_id,
988 author: author_id,
989 time: base + Duration::from_millis(2),
990 content: RoomMessageBody::public("Message 3".to_string()),
991 };
992
993 let authorized_message1 = AuthorizedMessageV1::new(message1, &signing_key);
994 let authorized_message2 = AuthorizedMessageV1::new(message2, &signing_key);
995 let authorized_message3 = AuthorizedMessageV1::new(message3, &signing_key);
996
997 let messages = MessagesV1 {
998 messages: vec![
999 authorized_message1.clone(),
1000 authorized_message2.clone(),
1001 authorized_message3.clone(),
1002 ],
1003 ..Default::default()
1004 };
1005
1006 let parent_state = ChatRoomStateV1::default();
1007 let parameters = ChatRoomParametersV1 {
1008 owner: signing_key.verifying_key(),
1009 };
1010
1011 let old_summary = vec![authorized_message1.id(), authorized_message2.id()];
1013 let delta = messages
1014 .delta(&parent_state, ¶meters, &old_summary)
1015 .unwrap();
1016 assert_eq!(delta.len(), 1);
1017 assert_eq!(delta[0], authorized_message3);
1018
1019 let empty_summary: Vec<MessageId> = vec![];
1021 let full_delta = messages
1022 .delta(&parent_state, ¶meters, &empty_summary)
1023 .unwrap();
1024 assert_eq!(full_delta.len(), 3);
1025 assert_eq!(full_delta, messages.messages);
1026
1027 let full_summary = vec![
1029 authorized_message1.id(),
1030 authorized_message2.id(),
1031 authorized_message3.id(),
1032 ];
1033 let no_delta = messages.delta(&parent_state, ¶meters, &full_summary);
1034 assert!(no_delta.is_none());
1035 }
1036
1037 #[test]
1038 fn test_messages_apply_delta() {
1039 let owner_signing_key = SigningKey::generate(&mut OsRng);
1041 let owner_verifying_key = owner_signing_key.verifying_key();
1042 let owner_id = MemberId::from(&owner_verifying_key);
1043
1044 let author_signing_key = SigningKey::generate(&mut OsRng);
1045 let author_verifying_key = author_signing_key.verifying_key();
1046 let author_id = MemberId::from(&author_verifying_key);
1047
1048 let mut parent_state = ChatRoomStateV1::default();
1049 parent_state.configuration.configuration.max_recent_messages = 3;
1050 parent_state.configuration.configuration.max_message_size = 100;
1051 parent_state.members.members = vec![crate::room_state::member::AuthorizedMember {
1052 member: crate::room_state::member::Member {
1053 owner_member_id: owner_id,
1054 invited_by: owner_id,
1055 member_vk: author_verifying_key,
1056 },
1057 signature: owner_signing_key.try_sign(&[0; 32]).unwrap(),
1058 }];
1059
1060 let parameters = ChatRoomParametersV1 {
1061 owner: owner_verifying_key,
1062 };
1063
1064 let create_message = |time: SystemTime| {
1066 let message = MessageV1 {
1067 room_owner: owner_id,
1068 author: author_id,
1069 time,
1070 content: RoomMessageBody::public("Test message".to_string()),
1071 };
1072 AuthorizedMessageV1::new(message, &author_signing_key)
1073 };
1074
1075 let now = SystemTime::now();
1076 let message1 = create_message(now - Duration::from_secs(3));
1077 let message2 = create_message(now - Duration::from_secs(2));
1078 let message3 = create_message(now - Duration::from_secs(1));
1079 let message4 = create_message(now);
1080
1081 let mut messages = MessagesV1 {
1083 messages: vec![message1.clone(), message2.clone()],
1084 ..Default::default()
1085 };
1086
1087 let delta = vec![message3.clone(), message4.clone()];
1089 assert!(messages
1090 .apply_delta(&parent_state, ¶meters, &Some(delta))
1091 .is_ok());
1092
1093 assert_eq!(
1095 messages.messages.len(),
1096 3,
1097 "Should have 3 messages after applying delta"
1098 );
1099 assert!(
1100 !messages.messages.contains(&message1),
1101 "Oldest message should be removed"
1102 );
1103 assert!(
1104 messages.messages.contains(&message2),
1105 "Second oldest message should be retained"
1106 );
1107 assert!(
1108 messages.messages.contains(&message3),
1109 "New message should be added"
1110 );
1111 assert!(
1112 messages.messages.contains(&message4),
1113 "Newest message should be added"
1114 );
1115
1116 let old_message = create_message(now - Duration::from_secs(4));
1118 let delta = vec![old_message.clone()];
1119 assert!(messages
1120 .apply_delta(&parent_state, ¶meters, &Some(delta))
1121 .is_ok());
1122
1123 assert_eq!(messages.messages.len(), 3, "Should still have 3 messages");
1125 assert!(
1126 !messages.messages.contains(&old_message),
1127 "Older message should not be added"
1128 );
1129 assert!(
1130 messages.messages.contains(&message2),
1131 "Message2 should be retained"
1132 );
1133 assert!(
1134 messages.messages.contains(&message3),
1135 "Message3 should be retained"
1136 );
1137 assert!(
1138 messages.messages.contains(&message4),
1139 "Newest message should be retained"
1140 );
1141 }
1142
1143 #[test]
1144 fn test_oversized_message_filtered_by_apply_delta() {
1145 let owner_sk = SigningKey::generate(&mut OsRng);
1146 let owner_vk = owner_sk.verifying_key();
1147 let owner_id = MemberId::from(&owner_vk);
1148
1149 let author_sk = SigningKey::generate(&mut OsRng);
1150 let author_vk = author_sk.verifying_key();
1151 let author_id = MemberId::from(&author_vk);
1152
1153 let mut parent_state = ChatRoomStateV1::default();
1154 parent_state.configuration.configuration.max_message_size = 50;
1155 parent_state.configuration.configuration.max_recent_messages = 10;
1156 parent_state.members.members = vec![crate::room_state::member::AuthorizedMember {
1157 member: crate::room_state::member::Member {
1158 owner_member_id: owner_id,
1159 invited_by: owner_id,
1160 member_vk: author_vk,
1161 },
1162 signature: owner_sk.try_sign(&[0; 32]).unwrap(),
1163 }];
1164
1165 let parameters = ChatRoomParametersV1 { owner: owner_vk };
1166
1167 let small_msg = AuthorizedMessageV1::new(
1169 MessageV1 {
1170 room_owner: owner_id,
1171 author: author_id,
1172 time: SystemTime::now(),
1173 content: RoomMessageBody::public("short".to_string()),
1174 },
1175 &author_sk,
1176 );
1177 let big_msg = AuthorizedMessageV1::new(
1178 MessageV1 {
1179 room_owner: owner_id,
1180 author: author_id,
1181 time: SystemTime::now(),
1182 content: RoomMessageBody::public("x".repeat(100)),
1183 },
1184 &author_sk,
1185 );
1186
1187 assert!(small_msg.message.content.content_len() <= 50);
1188 assert!(big_msg.message.content.content_len() > 50);
1189
1190 let mut messages = MessagesV1::default();
1191 let delta = vec![small_msg.clone(), big_msg.clone()];
1192 assert!(messages
1193 .apply_delta(&parent_state, ¶meters, &Some(delta))
1194 .is_ok());
1195
1196 assert_eq!(
1197 messages.messages.len(),
1198 1,
1199 "Only small message should survive"
1200 );
1201 assert!(messages.messages.contains(&small_msg));
1202 assert!(
1203 !messages.messages.contains(&big_msg),
1204 "Oversized message should be filtered"
1205 );
1206 }
1207
1208 #[test]
1209 fn test_message_author_preservation_across_users() {
1210 let user1_sk = SigningKey::generate(&mut OsRng);
1212 let user1_vk = user1_sk.verifying_key();
1213 let user1_id = MemberId::from(&user1_vk);
1214
1215 let user2_sk = SigningKey::generate(&mut OsRng);
1216 let user2_vk = user2_sk.verifying_key();
1217 let user2_id = MemberId::from(&user2_vk);
1218
1219 let owner_sk = SigningKey::generate(&mut OsRng);
1220 let owner_vk = owner_sk.verifying_key();
1221 let owner_id = MemberId::from(&owner_vk);
1222
1223 println!("User1 ID: {}", user1_id);
1224 println!("User2 ID: {}", user2_id);
1225 println!("Owner ID: {}", owner_id);
1226
1227 let msg1 = MessageV1 {
1229 room_owner: owner_id,
1230 author: user1_id,
1231 content: RoomMessageBody::public("Message from user1".to_string()),
1232 time: SystemTime::now(),
1233 };
1234
1235 let msg2 = MessageV1 {
1236 room_owner: owner_id,
1237 author: user2_id,
1238 content: RoomMessageBody::public("Message from user2".to_string()),
1239 time: SystemTime::now() + Duration::from_secs(1),
1240 };
1241
1242 let auth_msg1 = AuthorizedMessageV1::new(msg1.clone(), &user1_sk);
1243 let auth_msg2 = AuthorizedMessageV1::new(msg2.clone(), &user2_sk);
1244
1245 let messages = MessagesV1 {
1247 messages: vec![auth_msg1.clone(), auth_msg2.clone()],
1248 ..Default::default()
1249 };
1250
1251 assert_eq!(messages.messages.len(), 2);
1253
1254 let stored_msg1 = &messages.messages[0];
1255 let stored_msg2 = &messages.messages[1];
1256
1257 assert_eq!(
1258 stored_msg1.message.author, user1_id,
1259 "Message 1 author should be user1, but got {}",
1260 stored_msg1.message.author
1261 );
1262 assert_eq!(
1263 stored_msg2.message.author, user2_id,
1264 "Message 2 author should be user2, but got {}",
1265 stored_msg2.message.author
1266 );
1267
1268 assert_ne!(user1_id, user2_id, "User IDs should be different");
1270
1271 let user1_id_str = user1_id.to_string();
1273 let user2_id_str = user2_id.to_string();
1274
1275 println!("User1 ID string: {}", user1_id_str);
1276 println!("User2 ID string: {}", user2_id_str);
1277
1278 assert_ne!(
1279 user1_id_str, user2_id_str,
1280 "User ID strings should be different"
1281 );
1282 }
1283
1284 #[test]
1285 fn test_edit_action() {
1286 let signing_key = SigningKey::generate(&mut OsRng);
1287 let verifying_key = signing_key.verifying_key();
1288 let owner_id = MemberId::from(&verifying_key);
1289 let author_id = owner_id;
1290
1291 let original_msg = MessageV1 {
1293 room_owner: owner_id,
1294 author: author_id,
1295 time: SystemTime::now(),
1296 content: RoomMessageBody::public("Original content".to_string()),
1297 };
1298 let auth_original = AuthorizedMessageV1::new(original_msg, &signing_key);
1299 let original_id = auth_original.id();
1300
1301 let edit_msg = MessageV1 {
1303 room_owner: owner_id,
1304 author: author_id,
1305 time: SystemTime::now() + Duration::from_secs(1),
1306 content: RoomMessageBody::edit(original_id.clone(), "Edited content".to_string()),
1307 };
1308 let auth_edit = AuthorizedMessageV1::new(edit_msg, &signing_key);
1309
1310 let mut messages = MessagesV1 {
1312 messages: vec![auth_original.clone(), auth_edit],
1313 ..Default::default()
1314 };
1315 messages.rebuild_actions_state();
1316
1317 assert!(messages.is_edited(&original_id));
1319 let effective = messages.effective_text(&auth_original);
1320 assert_eq!(effective, Some("Edited content".to_string()));
1321
1322 let display: Vec<_> = messages.display_messages().collect();
1324 assert_eq!(display.len(), 1);
1325 }
1326
1327 #[test]
1328 fn test_edit_by_non_author_ignored() {
1329 let owner_sk = SigningKey::generate(&mut OsRng);
1330 let owner_vk = owner_sk.verifying_key();
1331 let owner_id = MemberId::from(&owner_vk);
1332
1333 let other_sk = SigningKey::generate(&mut OsRng);
1334 let other_id = MemberId::from(&other_sk.verifying_key());
1335
1336 let original_msg = MessageV1 {
1338 room_owner: owner_id,
1339 author: owner_id,
1340 time: SystemTime::now(),
1341 content: RoomMessageBody::public("Original content".to_string()),
1342 };
1343 let auth_original = AuthorizedMessageV1::new(original_msg, &owner_sk);
1344 let original_id = auth_original.id();
1345
1346 let edit_msg = MessageV1 {
1348 room_owner: owner_id,
1349 author: other_id,
1350 time: SystemTime::now() + Duration::from_secs(1),
1351 content: RoomMessageBody::edit(original_id.clone(), "Hacked content".to_string()),
1352 };
1353 let auth_edit = AuthorizedMessageV1::new(edit_msg, &other_sk);
1354
1355 let mut messages = MessagesV1 {
1356 messages: vec![auth_original.clone(), auth_edit],
1357 ..Default::default()
1358 };
1359 messages.rebuild_actions_state();
1360
1361 assert!(!messages.is_edited(&original_id));
1363 let effective = messages.effective_text(&auth_original);
1364 assert_eq!(effective, Some("Original content".to_string()));
1365 }
1366
1367 #[test]
1368 fn test_delete_action() {
1369 let signing_key = SigningKey::generate(&mut OsRng);
1370 let verifying_key = signing_key.verifying_key();
1371 let owner_id = MemberId::from(&verifying_key);
1372
1373 let original_msg = MessageV1 {
1375 room_owner: owner_id,
1376 author: owner_id,
1377 time: SystemTime::now(),
1378 content: RoomMessageBody::public("Will be deleted".to_string()),
1379 };
1380 let auth_original = AuthorizedMessageV1::new(original_msg, &signing_key);
1381 let original_id = auth_original.id();
1382
1383 let delete_msg = MessageV1 {
1385 room_owner: owner_id,
1386 author: owner_id,
1387 time: SystemTime::now() + Duration::from_secs(1),
1388 content: RoomMessageBody::delete(original_id.clone()),
1389 };
1390 let auth_delete = AuthorizedMessageV1::new(delete_msg, &signing_key);
1391
1392 let mut messages = MessagesV1 {
1393 messages: vec![auth_original, auth_delete],
1394 ..Default::default()
1395 };
1396 messages.rebuild_actions_state();
1397
1398 assert!(messages.is_deleted(&original_id));
1400
1401 let display: Vec<_> = messages.display_messages().collect();
1403 assert_eq!(display.len(), 0);
1404 }
1405
1406 #[test]
1407 fn test_reaction_action() {
1408 let user1_sk = SigningKey::generate(&mut OsRng);
1409 let user1_id = MemberId::from(&user1_sk.verifying_key());
1410
1411 let user2_sk = SigningKey::generate(&mut OsRng);
1412 let user2_id = MemberId::from(&user2_sk.verifying_key());
1413
1414 let owner_id = user1_id;
1415
1416 let original_msg = MessageV1 {
1418 room_owner: owner_id,
1419 author: user1_id,
1420 time: SystemTime::now(),
1421 content: RoomMessageBody::public("React to me!".to_string()),
1422 };
1423 let auth_original = AuthorizedMessageV1::new(original_msg, &user1_sk);
1424 let original_id = auth_original.id();
1425
1426 let reaction_msg = MessageV1 {
1428 room_owner: owner_id,
1429 author: user2_id,
1430 time: SystemTime::now() + Duration::from_secs(1),
1431 content: RoomMessageBody::reaction(original_id.clone(), "👍".to_string()),
1432 };
1433 let auth_reaction = AuthorizedMessageV1::new(reaction_msg, &user2_sk);
1434
1435 let reaction_msg2 = MessageV1 {
1437 room_owner: owner_id,
1438 author: user1_id,
1439 time: SystemTime::now() + Duration::from_secs(2),
1440 content: RoomMessageBody::reaction(original_id.clone(), "👍".to_string()),
1441 };
1442 let auth_reaction2 = AuthorizedMessageV1::new(reaction_msg2, &user1_sk);
1443
1444 let mut messages = MessagesV1 {
1445 messages: vec![auth_original, auth_reaction, auth_reaction2],
1446 ..Default::default()
1447 };
1448 messages.rebuild_actions_state();
1449
1450 let reactions = messages.reactions(&original_id).unwrap();
1452 let thumbs_up = reactions.get("👍").unwrap();
1453 assert_eq!(thumbs_up.len(), 2);
1454 assert!(thumbs_up.contains(&user1_id));
1455 assert!(thumbs_up.contains(&user2_id));
1456 }
1457
1458 #[test]
1459 fn test_remove_reaction_action() {
1460 let user_sk = SigningKey::generate(&mut OsRng);
1461 let user_id = MemberId::from(&user_sk.verifying_key());
1462 let owner_id = user_id;
1463
1464 let original_msg = MessageV1 {
1466 room_owner: owner_id,
1467 author: user_id,
1468 time: SystemTime::now(),
1469 content: RoomMessageBody::public("Test message".to_string()),
1470 };
1471 let auth_original = AuthorizedMessageV1::new(original_msg, &user_sk);
1472 let original_id = auth_original.id();
1473
1474 let reaction_msg = MessageV1 {
1476 room_owner: owner_id,
1477 author: user_id,
1478 time: SystemTime::now() + Duration::from_secs(1),
1479 content: RoomMessageBody::reaction(original_id.clone(), "❤️".to_string()),
1480 };
1481 let auth_reaction = AuthorizedMessageV1::new(reaction_msg, &user_sk);
1482
1483 let remove_msg = MessageV1 {
1485 room_owner: owner_id,
1486 author: user_id,
1487 time: SystemTime::now() + Duration::from_secs(2),
1488 content: RoomMessageBody::remove_reaction(original_id.clone(), "❤️".to_string()),
1489 };
1490 let auth_remove = AuthorizedMessageV1::new(remove_msg, &user_sk);
1491
1492 let mut messages = MessagesV1 {
1493 messages: vec![auth_original, auth_reaction, auth_remove],
1494 ..Default::default()
1495 };
1496 messages.rebuild_actions_state();
1497
1498 assert!(messages.reactions(&original_id).is_none());
1500 }
1501
1502 #[test]
1503 fn test_action_on_deleted_message_ignored() {
1504 let signing_key = SigningKey::generate(&mut OsRng);
1505 let verifying_key = signing_key.verifying_key();
1506 let owner_id = MemberId::from(&verifying_key);
1507
1508 let original_msg = MessageV1 {
1510 room_owner: owner_id,
1511 author: owner_id,
1512 time: SystemTime::now(),
1513 content: RoomMessageBody::public("Will be deleted".to_string()),
1514 };
1515 let auth_original = AuthorizedMessageV1::new(original_msg, &signing_key);
1516 let original_id = auth_original.id();
1517
1518 let delete_msg = MessageV1 {
1520 room_owner: owner_id,
1521 author: owner_id,
1522 time: SystemTime::now() + Duration::from_secs(1),
1523 content: RoomMessageBody::delete(original_id.clone()),
1524 };
1525 let auth_delete = AuthorizedMessageV1::new(delete_msg, &signing_key);
1526
1527 let edit_msg = MessageV1 {
1529 room_owner: owner_id,
1530 author: owner_id,
1531 time: SystemTime::now() + Duration::from_secs(2),
1532 content: RoomMessageBody::edit(original_id.clone(), "Too late!".to_string()),
1533 };
1534 let auth_edit = AuthorizedMessageV1::new(edit_msg, &signing_key);
1535
1536 let mut messages = MessagesV1 {
1537 messages: vec![auth_original, auth_delete, auth_edit],
1538 ..Default::default()
1539 };
1540 messages.rebuild_actions_state();
1541
1542 assert!(messages.is_deleted(&original_id));
1544 assert!(!messages.is_edited(&original_id));
1545 }
1546
1547 #[test]
1548 fn test_display_messages_filters_actions() {
1549 let signing_key = SigningKey::generate(&mut OsRng);
1550 let verifying_key = signing_key.verifying_key();
1551 let owner_id = MemberId::from(&verifying_key);
1552
1553 let msg1 = MessageV1 {
1555 room_owner: owner_id,
1556 author: owner_id,
1557 time: SystemTime::now(),
1558 content: RoomMessageBody::public("Hello".to_string()),
1559 };
1560 let auth_msg1 = AuthorizedMessageV1::new(msg1, &signing_key);
1561 let msg1_id = auth_msg1.id();
1562
1563 let reaction_msg = MessageV1 {
1565 room_owner: owner_id,
1566 author: owner_id,
1567 time: SystemTime::now() + Duration::from_secs(1),
1568 content: RoomMessageBody::reaction(msg1_id, "👍".to_string()),
1569 };
1570 let auth_reaction = AuthorizedMessageV1::new(reaction_msg, &signing_key);
1571
1572 let msg2 = MessageV1 {
1574 room_owner: owner_id,
1575 author: owner_id,
1576 time: SystemTime::now() + Duration::from_secs(2),
1577 content: RoomMessageBody::public("World".to_string()),
1578 };
1579 let auth_msg2 = AuthorizedMessageV1::new(msg2, &signing_key);
1580
1581 let mut messages = MessagesV1 {
1582 messages: vec![auth_msg1, auth_reaction, auth_msg2],
1583 ..Default::default()
1584 };
1585 messages.rebuild_actions_state();
1586
1587 let display: Vec<_> = messages.display_messages().collect();
1589 assert_eq!(display.len(), 2);
1590 assert_eq!(
1591 display[0].message.content.as_public_string(),
1592 Some("Hello".to_string())
1593 );
1594 assert_eq!(
1595 display[1].message.content.as_public_string(),
1596 Some("World".to_string())
1597 );
1598 }
1599}