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 if *secret_version != current_secret_version {
119 return Err(format!(
120 "Private message secret version {} does not match current version {}",
121 secret_version, current_secret_version
122 ));
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 {
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)]
383pub enum RoomMessageBody {
384 Public {
386 content_type: u32,
388 content_version: u32,
390 data: Vec<u8>,
392 },
393 Private {
395 content_type: u32,
397 content_version: u32,
399 ciphertext: Vec<u8>,
401 nonce: [u8; 12],
403 secret_version: SecretVersion,
405 },
406}
407
408impl RoomMessageBody {
409 pub fn public(text: String) -> Self {
411 use crate::room_state::content::{TextContentV1, CONTENT_TYPE_TEXT, TEXT_CONTENT_VERSION};
412 let content = TextContentV1::new(text);
413 Self::Public {
414 content_type: CONTENT_TYPE_TEXT,
415 content_version: TEXT_CONTENT_VERSION,
416 data: content.encode(),
417 }
418 }
419
420 pub fn public_raw(content_type: u32, content_version: u32, data: Vec<u8>) -> Self {
422 Self::Public {
423 content_type,
424 content_version,
425 data,
426 }
427 }
428
429 pub fn private(
431 content_type: u32,
432 content_version: u32,
433 ciphertext: Vec<u8>,
434 nonce: [u8; 12],
435 secret_version: SecretVersion,
436 ) -> Self {
437 Self::Private {
438 content_type,
439 content_version,
440 ciphertext,
441 nonce,
442 secret_version,
443 }
444 }
445
446 pub fn private_text(
448 ciphertext: Vec<u8>,
449 nonce: [u8; 12],
450 secret_version: SecretVersion,
451 ) -> Self {
452 use crate::room_state::content::{CONTENT_TYPE_TEXT, TEXT_CONTENT_VERSION};
453 Self::Private {
454 content_type: CONTENT_TYPE_TEXT,
455 content_version: TEXT_CONTENT_VERSION,
456 ciphertext,
457 nonce,
458 secret_version,
459 }
460 }
461
462 pub fn edit(target: MessageId, new_text: String) -> Self {
464 use crate::room_state::content::{
465 ActionContentV1, ACTION_CONTENT_VERSION, CONTENT_TYPE_ACTION,
466 };
467 let action = ActionContentV1::edit(target, new_text);
468 Self::Public {
469 content_type: CONTENT_TYPE_ACTION,
470 content_version: ACTION_CONTENT_VERSION,
471 data: action.encode(),
472 }
473 }
474
475 pub fn delete(target: MessageId) -> Self {
477 use crate::room_state::content::{
478 ActionContentV1, ACTION_CONTENT_VERSION, CONTENT_TYPE_ACTION,
479 };
480 let action = ActionContentV1::delete(target);
481 Self::Public {
482 content_type: CONTENT_TYPE_ACTION,
483 content_version: ACTION_CONTENT_VERSION,
484 data: action.encode(),
485 }
486 }
487
488 pub fn reaction(target: MessageId, emoji: String) -> Self {
490 use crate::room_state::content::{
491 ActionContentV1, ACTION_CONTENT_VERSION, CONTENT_TYPE_ACTION,
492 };
493 let action = ActionContentV1::reaction(target, emoji);
494 Self::Public {
495 content_type: CONTENT_TYPE_ACTION,
496 content_version: ACTION_CONTENT_VERSION,
497 data: action.encode(),
498 }
499 }
500
501 pub fn remove_reaction(target: MessageId, emoji: String) -> Self {
503 use crate::room_state::content::{
504 ActionContentV1, ACTION_CONTENT_VERSION, CONTENT_TYPE_ACTION,
505 };
506 let action = ActionContentV1::remove_reaction(target, emoji);
507 Self::Public {
508 content_type: CONTENT_TYPE_ACTION,
509 content_version: ACTION_CONTENT_VERSION,
510 data: action.encode(),
511 }
512 }
513
514 pub fn reply(
516 text: String,
517 target_message_id: MessageId,
518 target_author_name: String,
519 target_content_preview: String,
520 ) -> Self {
521 use crate::room_state::content::{
522 ReplyContentV1, CONTENT_TYPE_REPLY, REPLY_CONTENT_VERSION,
523 };
524 let reply = ReplyContentV1::new(
525 text,
526 target_message_id,
527 target_author_name,
528 target_content_preview,
529 );
530 Self::Public {
531 content_type: CONTENT_TYPE_REPLY,
532 content_version: REPLY_CONTENT_VERSION,
533 data: reply.encode(),
534 }
535 }
536
537 pub fn private_action(
546 ciphertext: Vec<u8>,
547 nonce: [u8; 12],
548 secret_version: SecretVersion,
549 ) -> Self {
550 use crate::room_state::content::{ACTION_CONTENT_VERSION, CONTENT_TYPE_ACTION};
551 Self::Private {
552 content_type: CONTENT_TYPE_ACTION,
553 content_version: ACTION_CONTENT_VERSION,
554 ciphertext,
555 nonce,
556 secret_version,
557 }
558 }
559
560 pub fn is_public(&self) -> bool {
562 matches!(self, Self::Public { .. })
563 }
564
565 pub fn is_private(&self) -> bool {
567 matches!(self, Self::Private { .. })
568 }
569
570 pub fn content_type(&self) -> u32 {
572 match self {
573 Self::Public { content_type, .. } | Self::Private { content_type, .. } => *content_type,
574 }
575 }
576
577 pub fn content_version(&self) -> u32 {
579 match self {
580 Self::Public {
581 content_version, ..
582 }
583 | Self::Private {
584 content_version, ..
585 } => *content_version,
586 }
587 }
588
589 pub fn is_action(&self) -> bool {
591 use crate::room_state::content::CONTENT_TYPE_ACTION;
592 self.content_type() == CONTENT_TYPE_ACTION
593 }
594
595 pub fn decode_content(&self) -> Option<crate::room_state::content::DecodedContent> {
598 use crate::room_state::content::{
599 ActionContentV1, DecodedContent, ReplyContentV1, TextContentV1, CONTENT_TYPE_ACTION,
600 CONTENT_TYPE_REPLY, CONTENT_TYPE_TEXT,
601 };
602 match self {
603 Self::Public {
604 content_type,
605 content_version,
606 data,
607 } => match *content_type {
608 CONTENT_TYPE_TEXT => TextContentV1::decode(data).ok().map(DecodedContent::Text),
609 CONTENT_TYPE_ACTION => ActionContentV1::decode(data)
610 .ok()
611 .map(DecodedContent::Action),
612 CONTENT_TYPE_REPLY => ReplyContentV1::decode(data).ok().map(DecodedContent::Reply),
613 _ => Some(DecodedContent::Unknown {
614 content_type: *content_type,
615 content_version: *content_version,
616 }),
617 },
618 Self::Private { .. } => None,
619 }
620 }
621
622 pub fn target_id(&self) -> Option<MessageId> {
624 use crate::room_state::content::{ActionContentV1, CONTENT_TYPE_ACTION};
625 match self {
626 Self::Public {
627 content_type, data, ..
628 } if *content_type == CONTENT_TYPE_ACTION => {
629 ActionContentV1::decode(data).ok().map(|a| a.target)
630 }
631 _ => None,
632 }
633 }
634
635 pub fn content_len(&self) -> usize {
637 match self {
638 Self::Public { data, .. } => data.len(),
639 Self::Private { ciphertext, .. } => ciphertext.len(),
640 }
641 }
642
643 pub fn secret_version(&self) -> Option<SecretVersion> {
645 match self {
646 Self::Public { .. } => None,
647 Self::Private { secret_version, .. } => Some(*secret_version),
648 }
649 }
650
651 pub fn to_string_lossy(&self) -> String {
653 match self {
654 Self::Public { .. } => {
655 if let Some(decoded) = self.decode_content() {
656 decoded.to_display_string()
657 } else {
658 "[Failed to decode message]".to_string()
659 }
660 }
661 Self::Private {
662 ciphertext,
663 secret_version,
664 ..
665 } => {
666 format!(
667 "[Encrypted message: {} bytes, v{}]",
668 ciphertext.len(),
669 secret_version
670 )
671 }
672 }
673 }
674
675 pub fn as_public_string(&self) -> Option<String> {
677 self.decode_content()
678 .and_then(|c| c.as_text().map(|s| s.to_string()))
679 }
680}
681
682impl fmt::Display for RoomMessageBody {
683 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
684 write!(f, "{}", self.to_string_lossy())
685 }
686}
687
688#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)]
689pub struct MessageV1 {
690 pub room_owner: MemberId,
691 pub author: MemberId,
692 pub time: SystemTime,
693 pub content: RoomMessageBody,
694}
695
696impl Default for MessageV1 {
697 fn default() -> Self {
698 Self {
699 room_owner: MemberId(FastHash(0)),
700 author: MemberId(FastHash(0)),
701 time: SystemTime::UNIX_EPOCH,
702 content: RoomMessageBody::public(String::new()),
703 }
704 }
705}
706
707#[derive(Clone, PartialEq, Serialize, Deserialize)]
708pub struct AuthorizedMessageV1 {
709 pub message: MessageV1,
710 pub signature: Signature,
711}
712
713impl fmt::Debug for AuthorizedMessageV1 {
714 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
715 f.debug_struct("AuthorizedMessage")
716 .field("message", &self.message)
717 .field(
718 "signature",
719 &format_args!("{}", truncated_base64(self.signature.to_bytes())),
720 )
721 .finish()
722 }
723}
724
725#[derive(Eq, PartialEq, Hash, Serialize, Deserialize, Clone, Debug, Ord, PartialOrd)]
726pub struct MessageId(pub FastHash);
727
728impl fmt::Display for MessageId {
729 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
730 write!(f, "{:?}", self.0)
731 }
732}
733
734impl AuthorizedMessageV1 {
735 pub fn new(message: MessageV1, signing_key: &SigningKey) -> Self {
736 Self {
737 message: message.clone(),
738 signature: sign_struct(&message, signing_key),
739 }
740 }
741
742 pub fn with_signature(message: MessageV1, signature: Signature) -> Self {
745 Self { message, signature }
746 }
747
748 pub fn validate(
749 &self,
750 verifying_key: &VerifyingKey,
751 ) -> Result<(), ed25519_dalek::SignatureError> {
752 verify_struct(&self.message, &self.signature, verifying_key)
753 }
754
755 pub fn id(&self) -> MessageId {
756 MessageId(fast_hash(&self.signature.to_bytes()))
757 }
758}
759
760#[cfg(test)]
761mod tests {
762 use super::*;
763 use ed25519_dalek::{Signer, SigningKey};
764 use rand::rngs::OsRng;
765 use std::time::Duration;
766
767 fn create_test_message(owner_id: MemberId, author_id: MemberId) -> MessageV1 {
768 MessageV1 {
769 room_owner: owner_id,
770 author: author_id,
771 time: SystemTime::now(),
772 content: RoomMessageBody::public("Test message".to_string()),
773 }
774 }
775
776 #[test]
777 fn test_messages_v1_default() {
778 let default_messages = MessagesV1::default();
779 assert!(default_messages.messages.is_empty());
780 }
781
782 #[test]
783 fn test_authorized_message_v1_debug() {
784 let signing_key = SigningKey::generate(&mut OsRng);
785 let owner_id = MemberId(FastHash(0));
786 let author_id = MemberId(FastHash(1));
787
788 let message = create_test_message(owner_id, author_id);
789 let authorized_message = AuthorizedMessageV1::new(message, &signing_key);
790
791 let debug_output = format!("{:?}", authorized_message);
792 assert!(debug_output.contains("AuthorizedMessage"));
793 assert!(debug_output.contains("message"));
794 assert!(debug_output.contains("signature"));
795 }
796
797 #[test]
798 fn test_authorized_message_new_and_validate() {
799 let signing_key = SigningKey::generate(&mut OsRng);
800 let verifying_key = signing_key.verifying_key();
801 let owner_id = MemberId(FastHash(0));
802 let author_id = MemberId(FastHash(1));
803
804 let message = create_test_message(owner_id, author_id);
805 let authorized_message = AuthorizedMessageV1::new(message.clone(), &signing_key);
806
807 assert_eq!(authorized_message.message, message);
808 assert!(authorized_message.validate(&verifying_key).is_ok());
809
810 let wrong_key = SigningKey::generate(&mut OsRng).verifying_key();
812 assert!(authorized_message.validate(&wrong_key).is_err());
813
814 let mut tampered_message = authorized_message.clone();
816 tampered_message.message.content = RoomMessageBody::public("Tampered content".to_string());
817 assert!(tampered_message.validate(&verifying_key).is_err());
818 }
819
820 #[test]
821 fn test_message_id() {
822 let signing_key = SigningKey::generate(&mut OsRng);
823 let owner_id = MemberId(FastHash(0));
824 let author_id = MemberId(FastHash(1));
825
826 let message = create_test_message(owner_id, author_id);
827 let authorized_message = AuthorizedMessageV1::new(message, &signing_key);
828
829 let id1 = authorized_message.id();
830 let id2 = authorized_message.id();
831
832 assert_eq!(id1, id2);
833
834 let message2 = create_test_message(owner_id, author_id);
836 let authorized_message2 = AuthorizedMessageV1::new(message2, &signing_key);
837 assert_ne!(authorized_message.id(), authorized_message2.id());
838 }
839
840 #[test]
841 fn test_messages_verify() {
842 let owner_signing_key = SigningKey::generate(&mut OsRng);
844 let owner_verifying_key = owner_signing_key.verifying_key();
845 let owner_id = MemberId::from(&owner_verifying_key);
846
847 let author_signing_key = SigningKey::generate(&mut OsRng);
849 let author_verifying_key = author_signing_key.verifying_key();
850 let author_id = MemberId::from(&author_verifying_key);
851
852 let message = create_test_message(owner_id, author_id);
854 let authorized_message = AuthorizedMessageV1::new(message, &author_signing_key);
855
856 let messages = MessagesV1 {
858 messages: vec![authorized_message],
859 ..Default::default()
860 };
861
862 let mut parent_state = ChatRoomStateV1::default();
864 let author_member = crate::room_state::member::Member {
865 owner_member_id: owner_id,
866 invited_by: owner_id,
867 member_vk: author_verifying_key,
868 };
869 let authorized_author =
870 crate::room_state::member::AuthorizedMember::new(author_member, &owner_signing_key);
871 parent_state.members.members = vec![authorized_author];
872
873 let parameters = ChatRoomParametersV1 {
875 owner: owner_verifying_key,
876 };
877
878 assert!(
880 messages.verify(&parent_state, ¶meters).is_ok(),
881 "Valid messages should pass verification: {:?}",
882 messages.verify(&parent_state, ¶meters)
883 );
884
885 let mut invalid_messages = messages.clone();
887 invalid_messages.messages[0].signature = Signature::from_bytes(&[0; 64]); assert!(
889 invalid_messages.verify(&parent_state, ¶meters).is_err(),
890 "Messages with invalid signature should fail verification"
891 );
892
893 let non_existent_author_id =
895 MemberId::from(&SigningKey::generate(&mut OsRng).verifying_key());
896 let invalid_message = create_test_message(owner_id, non_existent_author_id);
897 let invalid_authorized_message =
898 AuthorizedMessageV1::new(invalid_message, &author_signing_key);
899 let invalid_messages = MessagesV1 {
900 messages: vec![invalid_authorized_message],
901 ..Default::default()
902 };
903 assert!(
904 invalid_messages.verify(&parent_state, ¶meters).is_err(),
905 "Messages with non-existent author should fail verification"
906 );
907 }
908
909 #[test]
910 fn test_messages_summarize() {
911 let signing_key = SigningKey::generate(&mut OsRng);
912 let owner_id = MemberId(FastHash(0));
913 let author_id = MemberId(FastHash(1));
914
915 let message1 = create_test_message(owner_id, author_id);
916 let message2 = create_test_message(owner_id, author_id);
917
918 let authorized_message1 = AuthorizedMessageV1::new(message1, &signing_key);
919 let authorized_message2 = AuthorizedMessageV1::new(message2, &signing_key);
920
921 let messages = MessagesV1 {
922 messages: vec![authorized_message1.clone(), authorized_message2.clone()],
923 ..Default::default()
924 };
925
926 let parent_state = ChatRoomStateV1::default();
927 let parameters = ChatRoomParametersV1 {
928 owner: signing_key.verifying_key(),
929 };
930
931 let summary = messages.summarize(&parent_state, ¶meters);
932 assert_eq!(summary.len(), 2);
933 assert_eq!(summary[0], authorized_message1.id());
934 assert_eq!(summary[1], authorized_message2.id());
935
936 let empty_messages = MessagesV1::default();
938 let empty_summary = empty_messages.summarize(&parent_state, ¶meters);
939 assert!(empty_summary.is_empty());
940 }
941
942 #[test]
943 fn test_messages_delta() {
944 let signing_key = SigningKey::generate(&mut OsRng);
945 let owner_id = MemberId(FastHash(0));
946 let author_id = MemberId(FastHash(1));
947
948 let message1 = create_test_message(owner_id, author_id);
949 let message2 = create_test_message(owner_id, author_id);
950 let message3 = create_test_message(owner_id, author_id);
951
952 let authorized_message1 = AuthorizedMessageV1::new(message1, &signing_key);
953 let authorized_message2 = AuthorizedMessageV1::new(message2, &signing_key);
954 let authorized_message3 = AuthorizedMessageV1::new(message3, &signing_key);
955
956 let messages = MessagesV1 {
957 messages: vec![
958 authorized_message1.clone(),
959 authorized_message2.clone(),
960 authorized_message3.clone(),
961 ],
962 ..Default::default()
963 };
964
965 let parent_state = ChatRoomStateV1::default();
966 let parameters = ChatRoomParametersV1 {
967 owner: signing_key.verifying_key(),
968 };
969
970 let old_summary = vec![authorized_message1.id(), authorized_message2.id()];
972 let delta = messages
973 .delta(&parent_state, ¶meters, &old_summary)
974 .unwrap();
975 assert_eq!(delta.len(), 1);
976 assert_eq!(delta[0], authorized_message3);
977
978 let empty_summary: Vec<MessageId> = vec![];
980 let full_delta = messages
981 .delta(&parent_state, ¶meters, &empty_summary)
982 .unwrap();
983 assert_eq!(full_delta.len(), 3);
984 assert_eq!(full_delta, messages.messages);
985
986 let full_summary = vec![
988 authorized_message1.id(),
989 authorized_message2.id(),
990 authorized_message3.id(),
991 ];
992 let no_delta = messages.delta(&parent_state, ¶meters, &full_summary);
993 assert!(no_delta.is_none());
994 }
995
996 #[test]
997 fn test_messages_apply_delta() {
998 let owner_signing_key = SigningKey::generate(&mut OsRng);
1000 let owner_verifying_key = owner_signing_key.verifying_key();
1001 let owner_id = MemberId::from(&owner_verifying_key);
1002
1003 let author_signing_key = SigningKey::generate(&mut OsRng);
1004 let author_verifying_key = author_signing_key.verifying_key();
1005 let author_id = MemberId::from(&author_verifying_key);
1006
1007 let mut parent_state = ChatRoomStateV1::default();
1008 parent_state.configuration.configuration.max_recent_messages = 3;
1009 parent_state.configuration.configuration.max_message_size = 100;
1010 parent_state.members.members = vec![crate::room_state::member::AuthorizedMember {
1011 member: crate::room_state::member::Member {
1012 owner_member_id: owner_id,
1013 invited_by: owner_id,
1014 member_vk: author_verifying_key,
1015 },
1016 signature: owner_signing_key.try_sign(&[0; 32]).unwrap(),
1017 }];
1018
1019 let parameters = ChatRoomParametersV1 {
1020 owner: owner_verifying_key,
1021 };
1022
1023 let create_message = |time: SystemTime| {
1025 let message = MessageV1 {
1026 room_owner: owner_id,
1027 author: author_id,
1028 time,
1029 content: RoomMessageBody::public("Test message".to_string()),
1030 };
1031 AuthorizedMessageV1::new(message, &author_signing_key)
1032 };
1033
1034 let now = SystemTime::now();
1035 let message1 = create_message(now - Duration::from_secs(3));
1036 let message2 = create_message(now - Duration::from_secs(2));
1037 let message3 = create_message(now - Duration::from_secs(1));
1038 let message4 = create_message(now);
1039
1040 let mut messages = MessagesV1 {
1042 messages: vec![message1.clone(), message2.clone()],
1043 ..Default::default()
1044 };
1045
1046 let delta = vec![message3.clone(), message4.clone()];
1048 assert!(messages
1049 .apply_delta(&parent_state, ¶meters, &Some(delta))
1050 .is_ok());
1051
1052 assert_eq!(
1054 messages.messages.len(),
1055 3,
1056 "Should have 3 messages after applying delta"
1057 );
1058 assert!(
1059 !messages.messages.contains(&message1),
1060 "Oldest message should be removed"
1061 );
1062 assert!(
1063 messages.messages.contains(&message2),
1064 "Second oldest message should be retained"
1065 );
1066 assert!(
1067 messages.messages.contains(&message3),
1068 "New message should be added"
1069 );
1070 assert!(
1071 messages.messages.contains(&message4),
1072 "Newest message should be added"
1073 );
1074
1075 let old_message = create_message(now - Duration::from_secs(4));
1077 let delta = vec![old_message.clone()];
1078 assert!(messages
1079 .apply_delta(&parent_state, ¶meters, &Some(delta))
1080 .is_ok());
1081
1082 assert_eq!(messages.messages.len(), 3, "Should still have 3 messages");
1084 assert!(
1085 !messages.messages.contains(&old_message),
1086 "Older message should not be added"
1087 );
1088 assert!(
1089 messages.messages.contains(&message2),
1090 "Message2 should be retained"
1091 );
1092 assert!(
1093 messages.messages.contains(&message3),
1094 "Message3 should be retained"
1095 );
1096 assert!(
1097 messages.messages.contains(&message4),
1098 "Newest message should be retained"
1099 );
1100 }
1101
1102 #[test]
1103 fn test_message_author_preservation_across_users() {
1104 let user1_sk = SigningKey::generate(&mut OsRng);
1106 let user1_vk = user1_sk.verifying_key();
1107 let user1_id = MemberId::from(&user1_vk);
1108
1109 let user2_sk = SigningKey::generate(&mut OsRng);
1110 let user2_vk = user2_sk.verifying_key();
1111 let user2_id = MemberId::from(&user2_vk);
1112
1113 let owner_sk = SigningKey::generate(&mut OsRng);
1114 let owner_vk = owner_sk.verifying_key();
1115 let owner_id = MemberId::from(&owner_vk);
1116
1117 println!("User1 ID: {}", user1_id);
1118 println!("User2 ID: {}", user2_id);
1119 println!("Owner ID: {}", owner_id);
1120
1121 let msg1 = MessageV1 {
1123 room_owner: owner_id,
1124 author: user1_id,
1125 content: RoomMessageBody::public("Message from user1".to_string()),
1126 time: SystemTime::now(),
1127 };
1128
1129 let msg2 = MessageV1 {
1130 room_owner: owner_id,
1131 author: user2_id,
1132 content: RoomMessageBody::public("Message from user2".to_string()),
1133 time: SystemTime::now() + Duration::from_secs(1),
1134 };
1135
1136 let auth_msg1 = AuthorizedMessageV1::new(msg1.clone(), &user1_sk);
1137 let auth_msg2 = AuthorizedMessageV1::new(msg2.clone(), &user2_sk);
1138
1139 let messages = MessagesV1 {
1141 messages: vec![auth_msg1.clone(), auth_msg2.clone()],
1142 ..Default::default()
1143 };
1144
1145 assert_eq!(messages.messages.len(), 2);
1147
1148 let stored_msg1 = &messages.messages[0];
1149 let stored_msg2 = &messages.messages[1];
1150
1151 assert_eq!(
1152 stored_msg1.message.author, user1_id,
1153 "Message 1 author should be user1, but got {}",
1154 stored_msg1.message.author
1155 );
1156 assert_eq!(
1157 stored_msg2.message.author, user2_id,
1158 "Message 2 author should be user2, but got {}",
1159 stored_msg2.message.author
1160 );
1161
1162 assert_ne!(user1_id, user2_id, "User IDs should be different");
1164
1165 let user1_id_str = user1_id.to_string();
1167 let user2_id_str = user2_id.to_string();
1168
1169 println!("User1 ID string: {}", user1_id_str);
1170 println!("User2 ID string: {}", user2_id_str);
1171
1172 assert_ne!(
1173 user1_id_str, user2_id_str,
1174 "User ID strings should be different"
1175 );
1176 }
1177
1178 #[test]
1179 fn test_edit_action() {
1180 let signing_key = SigningKey::generate(&mut OsRng);
1181 let verifying_key = signing_key.verifying_key();
1182 let owner_id = MemberId::from(&verifying_key);
1183 let author_id = owner_id;
1184
1185 let original_msg = MessageV1 {
1187 room_owner: owner_id,
1188 author: author_id,
1189 time: SystemTime::now(),
1190 content: RoomMessageBody::public("Original content".to_string()),
1191 };
1192 let auth_original = AuthorizedMessageV1::new(original_msg, &signing_key);
1193 let original_id = auth_original.id();
1194
1195 let edit_msg = MessageV1 {
1197 room_owner: owner_id,
1198 author: author_id,
1199 time: SystemTime::now() + Duration::from_secs(1),
1200 content: RoomMessageBody::edit(original_id.clone(), "Edited content".to_string()),
1201 };
1202 let auth_edit = AuthorizedMessageV1::new(edit_msg, &signing_key);
1203
1204 let mut messages = MessagesV1 {
1206 messages: vec![auth_original.clone(), auth_edit],
1207 ..Default::default()
1208 };
1209 messages.rebuild_actions_state();
1210
1211 assert!(messages.is_edited(&original_id));
1213 let effective = messages.effective_text(&auth_original);
1214 assert_eq!(effective, Some("Edited content".to_string()));
1215
1216 let display: Vec<_> = messages.display_messages().collect();
1218 assert_eq!(display.len(), 1);
1219 }
1220
1221 #[test]
1222 fn test_edit_by_non_author_ignored() {
1223 let owner_sk = SigningKey::generate(&mut OsRng);
1224 let owner_vk = owner_sk.verifying_key();
1225 let owner_id = MemberId::from(&owner_vk);
1226
1227 let other_sk = SigningKey::generate(&mut OsRng);
1228 let other_id = MemberId::from(&other_sk.verifying_key());
1229
1230 let original_msg = MessageV1 {
1232 room_owner: owner_id,
1233 author: owner_id,
1234 time: SystemTime::now(),
1235 content: RoomMessageBody::public("Original content".to_string()),
1236 };
1237 let auth_original = AuthorizedMessageV1::new(original_msg, &owner_sk);
1238 let original_id = auth_original.id();
1239
1240 let edit_msg = MessageV1 {
1242 room_owner: owner_id,
1243 author: other_id,
1244 time: SystemTime::now() + Duration::from_secs(1),
1245 content: RoomMessageBody::edit(original_id.clone(), "Hacked content".to_string()),
1246 };
1247 let auth_edit = AuthorizedMessageV1::new(edit_msg, &other_sk);
1248
1249 let mut messages = MessagesV1 {
1250 messages: vec![auth_original.clone(), auth_edit],
1251 ..Default::default()
1252 };
1253 messages.rebuild_actions_state();
1254
1255 assert!(!messages.is_edited(&original_id));
1257 let effective = messages.effective_text(&auth_original);
1258 assert_eq!(effective, Some("Original content".to_string()));
1259 }
1260
1261 #[test]
1262 fn test_delete_action() {
1263 let signing_key = SigningKey::generate(&mut OsRng);
1264 let verifying_key = signing_key.verifying_key();
1265 let owner_id = MemberId::from(&verifying_key);
1266
1267 let original_msg = MessageV1 {
1269 room_owner: owner_id,
1270 author: owner_id,
1271 time: SystemTime::now(),
1272 content: RoomMessageBody::public("Will be deleted".to_string()),
1273 };
1274 let auth_original = AuthorizedMessageV1::new(original_msg, &signing_key);
1275 let original_id = auth_original.id();
1276
1277 let delete_msg = MessageV1 {
1279 room_owner: owner_id,
1280 author: owner_id,
1281 time: SystemTime::now() + Duration::from_secs(1),
1282 content: RoomMessageBody::delete(original_id.clone()),
1283 };
1284 let auth_delete = AuthorizedMessageV1::new(delete_msg, &signing_key);
1285
1286 let mut messages = MessagesV1 {
1287 messages: vec![auth_original, auth_delete],
1288 ..Default::default()
1289 };
1290 messages.rebuild_actions_state();
1291
1292 assert!(messages.is_deleted(&original_id));
1294
1295 let display: Vec<_> = messages.display_messages().collect();
1297 assert_eq!(display.len(), 0);
1298 }
1299
1300 #[test]
1301 fn test_reaction_action() {
1302 let user1_sk = SigningKey::generate(&mut OsRng);
1303 let user1_id = MemberId::from(&user1_sk.verifying_key());
1304
1305 let user2_sk = SigningKey::generate(&mut OsRng);
1306 let user2_id = MemberId::from(&user2_sk.verifying_key());
1307
1308 let owner_id = user1_id;
1309
1310 let original_msg = MessageV1 {
1312 room_owner: owner_id,
1313 author: user1_id,
1314 time: SystemTime::now(),
1315 content: RoomMessageBody::public("React to me!".to_string()),
1316 };
1317 let auth_original = AuthorizedMessageV1::new(original_msg, &user1_sk);
1318 let original_id = auth_original.id();
1319
1320 let reaction_msg = MessageV1 {
1322 room_owner: owner_id,
1323 author: user2_id,
1324 time: SystemTime::now() + Duration::from_secs(1),
1325 content: RoomMessageBody::reaction(original_id.clone(), "👍".to_string()),
1326 };
1327 let auth_reaction = AuthorizedMessageV1::new(reaction_msg, &user2_sk);
1328
1329 let reaction_msg2 = MessageV1 {
1331 room_owner: owner_id,
1332 author: user1_id,
1333 time: SystemTime::now() + Duration::from_secs(2),
1334 content: RoomMessageBody::reaction(original_id.clone(), "👍".to_string()),
1335 };
1336 let auth_reaction2 = AuthorizedMessageV1::new(reaction_msg2, &user1_sk);
1337
1338 let mut messages = MessagesV1 {
1339 messages: vec![auth_original, auth_reaction, auth_reaction2],
1340 ..Default::default()
1341 };
1342 messages.rebuild_actions_state();
1343
1344 let reactions = messages.reactions(&original_id).unwrap();
1346 let thumbs_up = reactions.get("👍").unwrap();
1347 assert_eq!(thumbs_up.len(), 2);
1348 assert!(thumbs_up.contains(&user1_id));
1349 assert!(thumbs_up.contains(&user2_id));
1350 }
1351
1352 #[test]
1353 fn test_remove_reaction_action() {
1354 let user_sk = SigningKey::generate(&mut OsRng);
1355 let user_id = MemberId::from(&user_sk.verifying_key());
1356 let owner_id = user_id;
1357
1358 let original_msg = MessageV1 {
1360 room_owner: owner_id,
1361 author: user_id,
1362 time: SystemTime::now(),
1363 content: RoomMessageBody::public("Test message".to_string()),
1364 };
1365 let auth_original = AuthorizedMessageV1::new(original_msg, &user_sk);
1366 let original_id = auth_original.id();
1367
1368 let reaction_msg = MessageV1 {
1370 room_owner: owner_id,
1371 author: user_id,
1372 time: SystemTime::now() + Duration::from_secs(1),
1373 content: RoomMessageBody::reaction(original_id.clone(), "❤️".to_string()),
1374 };
1375 let auth_reaction = AuthorizedMessageV1::new(reaction_msg, &user_sk);
1376
1377 let remove_msg = MessageV1 {
1379 room_owner: owner_id,
1380 author: user_id,
1381 time: SystemTime::now() + Duration::from_secs(2),
1382 content: RoomMessageBody::remove_reaction(original_id.clone(), "❤️".to_string()),
1383 };
1384 let auth_remove = AuthorizedMessageV1::new(remove_msg, &user_sk);
1385
1386 let mut messages = MessagesV1 {
1387 messages: vec![auth_original, auth_reaction, auth_remove],
1388 ..Default::default()
1389 };
1390 messages.rebuild_actions_state();
1391
1392 assert!(messages.reactions(&original_id).is_none());
1394 }
1395
1396 #[test]
1397 fn test_action_on_deleted_message_ignored() {
1398 let signing_key = SigningKey::generate(&mut OsRng);
1399 let verifying_key = signing_key.verifying_key();
1400 let owner_id = MemberId::from(&verifying_key);
1401
1402 let original_msg = MessageV1 {
1404 room_owner: owner_id,
1405 author: owner_id,
1406 time: SystemTime::now(),
1407 content: RoomMessageBody::public("Will be deleted".to_string()),
1408 };
1409 let auth_original = AuthorizedMessageV1::new(original_msg, &signing_key);
1410 let original_id = auth_original.id();
1411
1412 let delete_msg = MessageV1 {
1414 room_owner: owner_id,
1415 author: owner_id,
1416 time: SystemTime::now() + Duration::from_secs(1),
1417 content: RoomMessageBody::delete(original_id.clone()),
1418 };
1419 let auth_delete = AuthorizedMessageV1::new(delete_msg, &signing_key);
1420
1421 let edit_msg = MessageV1 {
1423 room_owner: owner_id,
1424 author: owner_id,
1425 time: SystemTime::now() + Duration::from_secs(2),
1426 content: RoomMessageBody::edit(original_id.clone(), "Too late!".to_string()),
1427 };
1428 let auth_edit = AuthorizedMessageV1::new(edit_msg, &signing_key);
1429
1430 let mut messages = MessagesV1 {
1431 messages: vec![auth_original, auth_delete, auth_edit],
1432 ..Default::default()
1433 };
1434 messages.rebuild_actions_state();
1435
1436 assert!(messages.is_deleted(&original_id));
1438 assert!(!messages.is_edited(&original_id));
1439 }
1440
1441 #[test]
1442 fn test_display_messages_filters_actions() {
1443 let signing_key = SigningKey::generate(&mut OsRng);
1444 let verifying_key = signing_key.verifying_key();
1445 let owner_id = MemberId::from(&verifying_key);
1446
1447 let msg1 = MessageV1 {
1449 room_owner: owner_id,
1450 author: owner_id,
1451 time: SystemTime::now(),
1452 content: RoomMessageBody::public("Hello".to_string()),
1453 };
1454 let auth_msg1 = AuthorizedMessageV1::new(msg1, &signing_key);
1455 let msg1_id = auth_msg1.id();
1456
1457 let reaction_msg = MessageV1 {
1459 room_owner: owner_id,
1460 author: owner_id,
1461 time: SystemTime::now() + Duration::from_secs(1),
1462 content: RoomMessageBody::reaction(msg1_id, "👍".to_string()),
1463 };
1464 let auth_reaction = AuthorizedMessageV1::new(reaction_msg, &signing_key);
1465
1466 let msg2 = MessageV1 {
1468 room_owner: owner_id,
1469 author: owner_id,
1470 time: SystemTime::now() + Duration::from_secs(2),
1471 content: RoomMessageBody::public("World".to_string()),
1472 };
1473 let auth_msg2 = AuthorizedMessageV1::new(msg2, &signing_key);
1474
1475 let mut messages = MessagesV1 {
1476 messages: vec![auth_msg1, auth_reaction, auth_msg2],
1477 ..Default::default()
1478 };
1479 messages.rebuild_actions_state();
1480
1481 let display: Vec<_> = messages.display_messages().collect();
1483 assert_eq!(display.len(), 2);
1484 assert_eq!(
1485 display[0].message.content.as_public_string(),
1486 Some("Hello".to_string())
1487 );
1488 assert_eq!(
1489 display[1].message.content.as_public_string(),
1490 Some("World".to_string())
1491 );
1492 }
1493}