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
108 if let Some(delta) = delta {
110 for msg in delta {
111 let content = &msg.message.content;
112
113 match content {
114 RoomMessageBody::Private { secret_version, .. } => {
115 if *privacy_mode == PrivacyMode::Private
168 && !parent_state
169 .secrets
170 .versions
171 .iter()
172 .any(|v| v.record.version == *secret_version)
173 {
174 return Err(format!(
175 "Private message references unknown secret version {}",
176 secret_version
177 ));
178 }
179 }
180 RoomMessageBody::Public { .. } => {
181 if *privacy_mode == PrivacyMode::Private && !content.is_event() {
184 return Err("Cannot send public messages in private room".to_string());
185 }
186 }
187 }
188 }
189
190 let existing_ids: std::collections::HashSet<_> =
192 self.messages.iter().map(|m| m.id()).collect();
193 self.messages.extend(
194 delta
195 .iter()
196 .filter(|msg| !existing_ids.contains(&msg.id()))
197 .cloned(),
198 );
199 }
200
201 self.messages
204 .retain(|m| m.message.content.content_len() <= max_message_size);
205
206 let members_by_id = parent_state.members.members_by_member_id();
208 let owner_id = MemberId::from(¶meters.owner);
209 self.messages.retain(|m| {
210 members_by_id.contains_key(&m.message.author) || m.message.author == owner_id
211 });
212
213 self.messages.sort_by(|a, b| {
216 a.message
217 .time
218 .cmp(&b.message.time)
219 .then_with(|| a.id().cmp(&b.id()))
220 });
221
222 if self.messages.len() > max_recent_messages {
224 self.messages
225 .drain(0..self.messages.len() - max_recent_messages);
226 }
227
228 self.rebuild_actions_state();
230
231 Ok(())
232 }
233}
234
235impl MessagesV1 {
236 pub fn rebuild_actions_state(&mut self) {
242 self.rebuild_actions_state_with_decrypted(&HashMap::new());
243 }
244
245 pub fn rebuild_actions_state_with_decrypted(
254 &mut self,
255 decrypted_content: &HashMap<MessageId, Vec<u8>>,
256 ) {
257 use crate::room_state::content::{
258 ActionContentV1, DecodedContent, ACTION_TYPE_DELETE, ACTION_TYPE_EDIT,
259 ACTION_TYPE_REACTION, ACTION_TYPE_REMOVE_REACTION,
260 };
261
262 self.actions_state = MessageActionsState::default();
264
265 let message_authors: HashMap<MessageId, MemberId> = self
267 .messages
268 .iter()
269 .filter(|m| !m.message.content.is_action())
270 .map(|m| (m.id(), m.message.author))
271 .collect();
272
273 for msg in &self.messages {
275 let actor = msg.message.author;
276
277 if !msg.message.content.is_action() {
279 continue;
280 }
281
282 let action = match &msg.message.content {
284 RoomMessageBody::Public { .. } => {
285 match msg.message.content.decode_content() {
287 Some(DecodedContent::Action(action)) => action,
288 _ => continue,
289 }
290 }
291 RoomMessageBody::Private { .. } => {
292 let msg_id = msg.id();
294 if let Some(plaintext) = decrypted_content.get(&msg_id) {
295 match ActionContentV1::decode(plaintext) {
296 Ok(action) => action,
297 Err(_) => continue,
298 }
299 } else {
300 continue;
302 }
303 }
304 };
305
306 let target = &action.target;
307
308 match action.action_type {
309 ACTION_TYPE_EDIT => {
310 if let Some(&original_author) = message_authors.get(target) {
312 if actor == original_author {
313 if !self.actions_state.deleted.contains(target) {
315 if let Some(payload) = action.edit_payload() {
316 self.actions_state
317 .edited_content
318 .insert(target.clone(), payload.new_text);
319 }
320 }
321 }
322 }
323 }
324 ACTION_TYPE_DELETE => {
325 if let Some(&original_author) = message_authors.get(target) {
327 if actor == original_author {
328 self.actions_state.deleted.insert(target.clone());
329 self.actions_state.edited_content.remove(target);
331 }
332 }
333 }
334 ACTION_TYPE_REACTION => {
335 if message_authors.contains_key(target)
337 && !self.actions_state.deleted.contains(target)
338 {
339 if let Some(payload) = action.reaction_payload() {
340 let reactions = self
341 .actions_state
342 .reactions
343 .entry(target.clone())
344 .or_default();
345 let reactors = reactions.entry(payload.emoji).or_default();
346 if !reactors.contains(&actor) {
348 reactors.push(actor);
349 }
350 }
351 }
352 }
353 ACTION_TYPE_REMOVE_REACTION => {
354 if let Some(payload) = action.reaction_payload() {
356 if let Some(reactions) = self.actions_state.reactions.get_mut(target) {
357 if let Some(reactors) = reactions.get_mut(&payload.emoji) {
358 reactors.retain(|r| r != &actor);
359 if reactors.is_empty() {
361 reactions.remove(&payload.emoji);
362 }
363 }
364 if reactions.is_empty() {
365 self.actions_state.reactions.remove(target);
366 }
367 }
368 }
369 }
370 _ => {
371 }
373 }
374 }
375 }
376
377 pub fn is_edited(&self, message_id: &MessageId) -> bool {
379 self.actions_state.edited_content.contains_key(message_id)
380 }
381
382 pub fn is_deleted(&self, message_id: &MessageId) -> bool {
384 self.actions_state.deleted.contains(message_id)
385 }
386
387 pub fn effective_text(&self, message: &AuthorizedMessageV1) -> Option<String> {
390 let id = message.id();
391 if let Some(edited_text) = self.actions_state.edited_content.get(&id) {
393 return Some(edited_text.clone());
394 }
395 message.message.content.as_public_string()
397 }
398
399 pub fn reactions(&self, message_id: &MessageId) -> Option<&HashMap<String, Vec<MemberId>>> {
401 self.actions_state.reactions.get(message_id)
402 }
403
404 pub fn display_messages(&self) -> impl Iterator<Item = &AuthorizedMessageV1> {
406 self.messages.iter().filter(|m| {
407 !m.message.content.is_action() && !self.actions_state.deleted.contains(&m.id())
408 })
409 }
410}
411
412#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)]
432pub enum RoomMessageBody {
433 Public {
435 content_type: u32,
437 content_version: u32,
439 data: Vec<u8>,
441 },
442 Private {
444 content_type: u32,
446 content_version: u32,
448 ciphertext: Vec<u8>,
450 nonce: [u8; 12],
452 secret_version: SecretVersion,
454 },
455}
456
457impl RoomMessageBody {
458 pub fn public(text: String) -> Self {
460 use crate::room_state::content::{TextContentV1, CONTENT_TYPE_TEXT, TEXT_CONTENT_VERSION};
461 let content = TextContentV1::new(text);
462 Self::Public {
463 content_type: CONTENT_TYPE_TEXT,
464 content_version: TEXT_CONTENT_VERSION,
465 data: content.encode(),
466 }
467 }
468
469 pub fn join_event() -> Self {
471 use crate::room_state::content::{
472 EventContentV1, CONTENT_TYPE_EVENT, EVENT_CONTENT_VERSION,
473 };
474 let content = EventContentV1::join();
475 Self::Public {
476 content_type: CONTENT_TYPE_EVENT,
477 content_version: EVENT_CONTENT_VERSION,
478 data: content.encode(),
479 }
480 }
481
482 pub fn public_raw(content_type: u32, content_version: u32, data: Vec<u8>) -> Self {
484 Self::Public {
485 content_type,
486 content_version,
487 data,
488 }
489 }
490
491 pub fn private(
493 content_type: u32,
494 content_version: u32,
495 ciphertext: Vec<u8>,
496 nonce: [u8; 12],
497 secret_version: SecretVersion,
498 ) -> Self {
499 Self::Private {
500 content_type,
501 content_version,
502 ciphertext,
503 nonce,
504 secret_version,
505 }
506 }
507
508 pub fn private_text(
510 ciphertext: Vec<u8>,
511 nonce: [u8; 12],
512 secret_version: SecretVersion,
513 ) -> Self {
514 use crate::room_state::content::{CONTENT_TYPE_TEXT, TEXT_CONTENT_VERSION};
515 Self::Private {
516 content_type: CONTENT_TYPE_TEXT,
517 content_version: TEXT_CONTENT_VERSION,
518 ciphertext,
519 nonce,
520 secret_version,
521 }
522 }
523
524 pub fn edit(target: MessageId, new_text: String) -> Self {
526 use crate::room_state::content::{
527 ActionContentV1, ACTION_CONTENT_VERSION, CONTENT_TYPE_ACTION,
528 };
529 let action = ActionContentV1::edit(target, new_text);
530 Self::Public {
531 content_type: CONTENT_TYPE_ACTION,
532 content_version: ACTION_CONTENT_VERSION,
533 data: action.encode(),
534 }
535 }
536
537 pub fn delete(target: MessageId) -> Self {
539 use crate::room_state::content::{
540 ActionContentV1, ACTION_CONTENT_VERSION, CONTENT_TYPE_ACTION,
541 };
542 let action = ActionContentV1::delete(target);
543 Self::Public {
544 content_type: CONTENT_TYPE_ACTION,
545 content_version: ACTION_CONTENT_VERSION,
546 data: action.encode(),
547 }
548 }
549
550 pub fn reaction(target: MessageId, emoji: String) -> Self {
552 use crate::room_state::content::{
553 ActionContentV1, ACTION_CONTENT_VERSION, CONTENT_TYPE_ACTION,
554 };
555 let action = ActionContentV1::reaction(target, emoji);
556 Self::Public {
557 content_type: CONTENT_TYPE_ACTION,
558 content_version: ACTION_CONTENT_VERSION,
559 data: action.encode(),
560 }
561 }
562
563 pub fn remove_reaction(target: MessageId, emoji: String) -> Self {
565 use crate::room_state::content::{
566 ActionContentV1, ACTION_CONTENT_VERSION, CONTENT_TYPE_ACTION,
567 };
568 let action = ActionContentV1::remove_reaction(target, emoji);
569 Self::Public {
570 content_type: CONTENT_TYPE_ACTION,
571 content_version: ACTION_CONTENT_VERSION,
572 data: action.encode(),
573 }
574 }
575
576 pub fn reply(
578 text: String,
579 target_message_id: MessageId,
580 target_author_name: String,
581 target_content_preview: String,
582 ) -> Self {
583 use crate::room_state::content::{
584 ReplyContentV1, CONTENT_TYPE_REPLY, REPLY_CONTENT_VERSION,
585 };
586 let reply = ReplyContentV1::new(
587 text,
588 target_message_id,
589 target_author_name,
590 target_content_preview,
591 );
592 Self::Public {
593 content_type: CONTENT_TYPE_REPLY,
594 content_version: REPLY_CONTENT_VERSION,
595 data: reply.encode(),
596 }
597 }
598
599 pub fn private_action(
608 ciphertext: Vec<u8>,
609 nonce: [u8; 12],
610 secret_version: SecretVersion,
611 ) -> Self {
612 use crate::room_state::content::{ACTION_CONTENT_VERSION, CONTENT_TYPE_ACTION};
613 Self::Private {
614 content_type: CONTENT_TYPE_ACTION,
615 content_version: ACTION_CONTENT_VERSION,
616 ciphertext,
617 nonce,
618 secret_version,
619 }
620 }
621
622 pub fn is_public(&self) -> bool {
624 matches!(self, Self::Public { .. })
625 }
626
627 pub fn is_private(&self) -> bool {
629 matches!(self, Self::Private { .. })
630 }
631
632 pub fn content_type(&self) -> u32 {
634 match self {
635 Self::Public { content_type, .. } | Self::Private { content_type, .. } => *content_type,
636 }
637 }
638
639 pub fn content_version(&self) -> u32 {
641 match self {
642 Self::Public {
643 content_version, ..
644 }
645 | Self::Private {
646 content_version, ..
647 } => *content_version,
648 }
649 }
650
651 pub fn is_action(&self) -> bool {
653 use crate::room_state::content::CONTENT_TYPE_ACTION;
654 self.content_type() == CONTENT_TYPE_ACTION
655 }
656
657 pub fn is_event(&self) -> bool {
659 use crate::room_state::content::CONTENT_TYPE_EVENT;
660 self.content_type() == CONTENT_TYPE_EVENT
661 }
662
663 pub fn decode_content(&self) -> Option<crate::room_state::content::DecodedContent> {
666 use crate::room_state::content::{
667 ActionContentV1, DecodedContent, EventContentV1, ReplyContentV1, TextContentV1,
668 CONTENT_TYPE_ACTION, CONTENT_TYPE_EVENT, CONTENT_TYPE_REPLY, CONTENT_TYPE_TEXT,
669 };
670 match self {
671 Self::Public {
672 content_type,
673 content_version,
674 data,
675 } => match *content_type {
676 CONTENT_TYPE_TEXT => TextContentV1::decode(data).ok().map(DecodedContent::Text),
677 CONTENT_TYPE_ACTION => ActionContentV1::decode(data)
678 .ok()
679 .map(DecodedContent::Action),
680 CONTENT_TYPE_REPLY => ReplyContentV1::decode(data).ok().map(DecodedContent::Reply),
681 CONTENT_TYPE_EVENT => EventContentV1::decode(data).ok().map(DecodedContent::Event),
682 _ => Some(DecodedContent::Unknown {
683 content_type: *content_type,
684 content_version: *content_version,
685 }),
686 },
687 Self::Private { .. } => None,
688 }
689 }
690
691 pub fn target_id(&self) -> Option<MessageId> {
693 use crate::room_state::content::{ActionContentV1, CONTENT_TYPE_ACTION};
694 match self {
695 Self::Public {
696 content_type, data, ..
697 } if *content_type == CONTENT_TYPE_ACTION => {
698 ActionContentV1::decode(data).ok().map(|a| a.target)
699 }
700 _ => None,
701 }
702 }
703
704 pub fn content_len(&self) -> usize {
706 match self {
707 Self::Public { data, .. } => data.len(),
708 Self::Private { ciphertext, .. } => ciphertext.len(),
709 }
710 }
711
712 pub fn secret_version(&self) -> Option<SecretVersion> {
714 match self {
715 Self::Public { .. } => None,
716 Self::Private { secret_version, .. } => Some(*secret_version),
717 }
718 }
719
720 pub fn to_string_lossy(&self) -> String {
722 match self {
723 Self::Public { .. } => {
724 if let Some(decoded) = self.decode_content() {
725 decoded.to_display_string()
726 } else {
727 "[Failed to decode message]".to_string()
728 }
729 }
730 Self::Private {
731 ciphertext,
732 secret_version,
733 ..
734 } => {
735 format!(
736 "[Encrypted message: {} bytes, v{}]",
737 ciphertext.len(),
738 secret_version
739 )
740 }
741 }
742 }
743
744 pub fn as_public_string(&self) -> Option<String> {
746 self.decode_content()
747 .and_then(|c| c.as_text().map(|s| s.to_string()))
748 }
749}
750
751impl fmt::Display for RoomMessageBody {
752 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
753 write!(f, "{}", self.to_string_lossy())
754 }
755}
756
757#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)]
758pub struct MessageV1 {
759 pub room_owner: MemberId,
760 pub author: MemberId,
761 pub time: SystemTime,
762 pub content: RoomMessageBody,
763}
764
765impl Default for MessageV1 {
766 fn default() -> Self {
767 Self {
768 room_owner: MemberId(FastHash(0)),
769 author: MemberId(FastHash(0)),
770 time: SystemTime::UNIX_EPOCH,
771 content: RoomMessageBody::public(String::new()),
772 }
773 }
774}
775
776#[derive(Clone, PartialEq, Serialize, Deserialize)]
777pub struct AuthorizedMessageV1 {
778 pub message: MessageV1,
779 pub signature: Signature,
780}
781
782impl fmt::Debug for AuthorizedMessageV1 {
783 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
784 f.debug_struct("AuthorizedMessage")
785 .field("message", &self.message)
786 .field(
787 "signature",
788 &format_args!("{}", truncated_base64(self.signature.to_bytes())),
789 )
790 .finish()
791 }
792}
793
794#[derive(Eq, PartialEq, Hash, Serialize, Deserialize, Clone, Debug, Ord, PartialOrd)]
795pub struct MessageId(pub FastHash);
796
797impl fmt::Display for MessageId {
798 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
799 write!(f, "{:?}", self.0)
800 }
801}
802
803impl AuthorizedMessageV1 {
804 pub fn new(message: MessageV1, signing_key: &SigningKey) -> Self {
805 Self {
806 message: message.clone(),
807 signature: sign_struct(&message, signing_key),
808 }
809 }
810
811 pub fn with_signature(message: MessageV1, signature: Signature) -> Self {
814 Self { message, signature }
815 }
816
817 pub fn validate(
818 &self,
819 verifying_key: &VerifyingKey,
820 ) -> Result<(), ed25519_dalek::SignatureError> {
821 verify_struct(&self.message, &self.signature, verifying_key)
822 }
823
824 pub fn id(&self) -> MessageId {
825 MessageId(fast_hash(&self.signature.to_bytes()))
826 }
827}
828
829#[cfg(test)]
830mod tests {
831 use super::*;
832 use ed25519_dalek::{Signer, SigningKey};
833 use rand::rngs::OsRng;
834 use std::time::Duration;
835
836 fn create_test_message(owner_id: MemberId, author_id: MemberId) -> MessageV1 {
837 MessageV1 {
838 room_owner: owner_id,
839 author: author_id,
840 time: SystemTime::now(),
841 content: RoomMessageBody::public("Test message".to_string()),
842 }
843 }
844
845 #[test]
846 fn test_messages_v1_default() {
847 let default_messages = MessagesV1::default();
848 assert!(default_messages.messages.is_empty());
849 }
850
851 #[test]
852 fn test_authorized_message_v1_debug() {
853 let signing_key = SigningKey::generate(&mut OsRng);
854 let owner_id = MemberId(FastHash(0));
855 let author_id = MemberId(FastHash(1));
856
857 let message = create_test_message(owner_id, author_id);
858 let authorized_message = AuthorizedMessageV1::new(message, &signing_key);
859
860 let debug_output = format!("{:?}", authorized_message);
861 assert!(debug_output.contains("AuthorizedMessage"));
862 assert!(debug_output.contains("message"));
863 assert!(debug_output.contains("signature"));
864 }
865
866 #[test]
867 fn test_authorized_message_new_and_validate() {
868 let signing_key = SigningKey::generate(&mut OsRng);
869 let verifying_key = signing_key.verifying_key();
870 let owner_id = MemberId(FastHash(0));
871 let author_id = MemberId(FastHash(1));
872
873 let message = create_test_message(owner_id, author_id);
874 let authorized_message = AuthorizedMessageV1::new(message.clone(), &signing_key);
875
876 assert_eq!(authorized_message.message, message);
877 assert!(authorized_message.validate(&verifying_key).is_ok());
878
879 let wrong_key = SigningKey::generate(&mut OsRng).verifying_key();
881 assert!(authorized_message.validate(&wrong_key).is_err());
882
883 let mut tampered_message = authorized_message.clone();
885 tampered_message.message.content = RoomMessageBody::public("Tampered content".to_string());
886 assert!(tampered_message.validate(&verifying_key).is_err());
887 }
888
889 #[test]
890 fn test_message_id() {
891 let signing_key = SigningKey::generate(&mut OsRng);
892 let owner_id = MemberId(FastHash(0));
893 let author_id = MemberId(FastHash(1));
894
895 let message = create_test_message(owner_id, author_id);
896 let authorized_message = AuthorizedMessageV1::new(message, &signing_key);
897
898 let id1 = authorized_message.id();
899 let id2 = authorized_message.id();
900
901 assert_eq!(id1, id2);
902
903 let message2 = create_test_message(owner_id, author_id);
905 let authorized_message2 = AuthorizedMessageV1::new(message2, &signing_key);
906 assert_ne!(authorized_message.id(), authorized_message2.id());
907 }
908
909 #[test]
910 fn test_messages_verify() {
911 let owner_signing_key = SigningKey::generate(&mut OsRng);
913 let owner_verifying_key = owner_signing_key.verifying_key();
914 let owner_id = MemberId::from(&owner_verifying_key);
915
916 let author_signing_key = SigningKey::generate(&mut OsRng);
918 let author_verifying_key = author_signing_key.verifying_key();
919 let author_id = MemberId::from(&author_verifying_key);
920
921 let message = create_test_message(owner_id, author_id);
923 let authorized_message = AuthorizedMessageV1::new(message, &author_signing_key);
924
925 let messages = MessagesV1 {
927 messages: vec![authorized_message],
928 ..Default::default()
929 };
930
931 let mut parent_state = ChatRoomStateV1::default();
933 let author_member = crate::room_state::member::Member {
934 owner_member_id: owner_id,
935 invited_by: owner_id,
936 member_vk: author_verifying_key,
937 };
938 let authorized_author =
939 crate::room_state::member::AuthorizedMember::new(author_member, &owner_signing_key);
940 parent_state.members.members = vec![authorized_author];
941
942 let parameters = ChatRoomParametersV1 {
944 owner: owner_verifying_key,
945 };
946
947 assert!(
949 messages.verify(&parent_state, ¶meters).is_ok(),
950 "Valid messages should pass verification: {:?}",
951 messages.verify(&parent_state, ¶meters)
952 );
953
954 let mut invalid_messages = messages.clone();
956 invalid_messages.messages[0].signature = Signature::from_bytes(&[0; 64]); assert!(
958 invalid_messages.verify(&parent_state, ¶meters).is_err(),
959 "Messages with invalid signature should fail verification"
960 );
961
962 let non_existent_author_id =
964 MemberId::from(&SigningKey::generate(&mut OsRng).verifying_key());
965 let invalid_message = create_test_message(owner_id, non_existent_author_id);
966 let invalid_authorized_message =
967 AuthorizedMessageV1::new(invalid_message, &author_signing_key);
968 let invalid_messages = MessagesV1 {
969 messages: vec![invalid_authorized_message],
970 ..Default::default()
971 };
972 assert!(
973 invalid_messages.verify(&parent_state, ¶meters).is_err(),
974 "Messages with non-existent author should fail verification"
975 );
976 }
977
978 #[test]
979 fn test_messages_summarize() {
980 let signing_key = SigningKey::generate(&mut OsRng);
981 let owner_id = MemberId(FastHash(0));
982 let author_id = MemberId(FastHash(1));
983
984 let message1 = create_test_message(owner_id, author_id);
985 let message2 = create_test_message(owner_id, author_id);
986
987 let authorized_message1 = AuthorizedMessageV1::new(message1, &signing_key);
988 let authorized_message2 = AuthorizedMessageV1::new(message2, &signing_key);
989
990 let messages = MessagesV1 {
991 messages: vec![authorized_message1.clone(), authorized_message2.clone()],
992 ..Default::default()
993 };
994
995 let parent_state = ChatRoomStateV1::default();
996 let parameters = ChatRoomParametersV1 {
997 owner: signing_key.verifying_key(),
998 };
999
1000 let summary = messages.summarize(&parent_state, ¶meters);
1001 assert_eq!(summary.len(), 2);
1002 assert_eq!(summary[0], authorized_message1.id());
1003 assert_eq!(summary[1], authorized_message2.id());
1004
1005 let empty_messages = MessagesV1::default();
1007 let empty_summary = empty_messages.summarize(&parent_state, ¶meters);
1008 assert!(empty_summary.is_empty());
1009 }
1010
1011 #[test]
1012 fn test_messages_delta() {
1013 let signing_key = SigningKey::generate(&mut OsRng);
1014 let owner_id = MemberId(FastHash(0));
1015 let author_id = MemberId(FastHash(1));
1016
1017 let base = SystemTime::now();
1019 let message1 = MessageV1 {
1020 room_owner: owner_id,
1021 author: author_id,
1022 time: base,
1023 content: RoomMessageBody::public("Message 1".to_string()),
1024 };
1025 let message2 = MessageV1 {
1026 room_owner: owner_id,
1027 author: author_id,
1028 time: base + Duration::from_millis(1),
1029 content: RoomMessageBody::public("Message 2".to_string()),
1030 };
1031 let message3 = MessageV1 {
1032 room_owner: owner_id,
1033 author: author_id,
1034 time: base + Duration::from_millis(2),
1035 content: RoomMessageBody::public("Message 3".to_string()),
1036 };
1037
1038 let authorized_message1 = AuthorizedMessageV1::new(message1, &signing_key);
1039 let authorized_message2 = AuthorizedMessageV1::new(message2, &signing_key);
1040 let authorized_message3 = AuthorizedMessageV1::new(message3, &signing_key);
1041
1042 let messages = MessagesV1 {
1043 messages: vec![
1044 authorized_message1.clone(),
1045 authorized_message2.clone(),
1046 authorized_message3.clone(),
1047 ],
1048 ..Default::default()
1049 };
1050
1051 let parent_state = ChatRoomStateV1::default();
1052 let parameters = ChatRoomParametersV1 {
1053 owner: signing_key.verifying_key(),
1054 };
1055
1056 let old_summary = vec![authorized_message1.id(), authorized_message2.id()];
1058 let delta = messages
1059 .delta(&parent_state, ¶meters, &old_summary)
1060 .unwrap();
1061 assert_eq!(delta.len(), 1);
1062 assert_eq!(delta[0], authorized_message3);
1063
1064 let empty_summary: Vec<MessageId> = vec![];
1066 let full_delta = messages
1067 .delta(&parent_state, ¶meters, &empty_summary)
1068 .unwrap();
1069 assert_eq!(full_delta.len(), 3);
1070 assert_eq!(full_delta, messages.messages);
1071
1072 let full_summary = vec![
1074 authorized_message1.id(),
1075 authorized_message2.id(),
1076 authorized_message3.id(),
1077 ];
1078 let no_delta = messages.delta(&parent_state, ¶meters, &full_summary);
1079 assert!(no_delta.is_none());
1080 }
1081
1082 #[test]
1083 fn test_messages_apply_delta() {
1084 let owner_signing_key = SigningKey::generate(&mut OsRng);
1086 let owner_verifying_key = owner_signing_key.verifying_key();
1087 let owner_id = MemberId::from(&owner_verifying_key);
1088
1089 let author_signing_key = SigningKey::generate(&mut OsRng);
1090 let author_verifying_key = author_signing_key.verifying_key();
1091 let author_id = MemberId::from(&author_verifying_key);
1092
1093 let mut parent_state = ChatRoomStateV1::default();
1094 parent_state.configuration.configuration.max_recent_messages = 3;
1095 parent_state.configuration.configuration.max_message_size = 100;
1096 parent_state.members.members = vec![crate::room_state::member::AuthorizedMember {
1097 member: crate::room_state::member::Member {
1098 owner_member_id: owner_id,
1099 invited_by: owner_id,
1100 member_vk: author_verifying_key,
1101 },
1102 signature: owner_signing_key.try_sign(&[0; 32]).unwrap(),
1103 }];
1104
1105 let parameters = ChatRoomParametersV1 {
1106 owner: owner_verifying_key,
1107 };
1108
1109 let create_message = |time: SystemTime| {
1111 let message = MessageV1 {
1112 room_owner: owner_id,
1113 author: author_id,
1114 time,
1115 content: RoomMessageBody::public("Test message".to_string()),
1116 };
1117 AuthorizedMessageV1::new(message, &author_signing_key)
1118 };
1119
1120 let now = SystemTime::now();
1121 let message1 = create_message(now - Duration::from_secs(3));
1122 let message2 = create_message(now - Duration::from_secs(2));
1123 let message3 = create_message(now - Duration::from_secs(1));
1124 let message4 = create_message(now);
1125
1126 let mut messages = MessagesV1 {
1128 messages: vec![message1.clone(), message2.clone()],
1129 ..Default::default()
1130 };
1131
1132 let delta = vec![message3.clone(), message4.clone()];
1134 assert!(messages
1135 .apply_delta(&parent_state, ¶meters, &Some(delta))
1136 .is_ok());
1137
1138 assert_eq!(
1140 messages.messages.len(),
1141 3,
1142 "Should have 3 messages after applying delta"
1143 );
1144 assert!(
1145 !messages.messages.contains(&message1),
1146 "Oldest message should be removed"
1147 );
1148 assert!(
1149 messages.messages.contains(&message2),
1150 "Second oldest message should be retained"
1151 );
1152 assert!(
1153 messages.messages.contains(&message3),
1154 "New message should be added"
1155 );
1156 assert!(
1157 messages.messages.contains(&message4),
1158 "Newest message should be added"
1159 );
1160
1161 let old_message = create_message(now - Duration::from_secs(4));
1163 let delta = vec![old_message.clone()];
1164 assert!(messages
1165 .apply_delta(&parent_state, ¶meters, &Some(delta))
1166 .is_ok());
1167
1168 assert_eq!(messages.messages.len(), 3, "Should still have 3 messages");
1170 assert!(
1171 !messages.messages.contains(&old_message),
1172 "Older message should not be added"
1173 );
1174 assert!(
1175 messages.messages.contains(&message2),
1176 "Message2 should be retained"
1177 );
1178 assert!(
1179 messages.messages.contains(&message3),
1180 "Message3 should be retained"
1181 );
1182 assert!(
1183 messages.messages.contains(&message4),
1184 "Newest message should be retained"
1185 );
1186 }
1187
1188 #[test]
1189 fn test_oversized_message_filtered_by_apply_delta() {
1190 let owner_sk = SigningKey::generate(&mut OsRng);
1191 let owner_vk = owner_sk.verifying_key();
1192 let owner_id = MemberId::from(&owner_vk);
1193
1194 let author_sk = SigningKey::generate(&mut OsRng);
1195 let author_vk = author_sk.verifying_key();
1196 let author_id = MemberId::from(&author_vk);
1197
1198 let mut parent_state = ChatRoomStateV1::default();
1199 parent_state.configuration.configuration.max_message_size = 50;
1200 parent_state.configuration.configuration.max_recent_messages = 10;
1201 parent_state.members.members = vec![crate::room_state::member::AuthorizedMember {
1202 member: crate::room_state::member::Member {
1203 owner_member_id: owner_id,
1204 invited_by: owner_id,
1205 member_vk: author_vk,
1206 },
1207 signature: owner_sk.try_sign(&[0; 32]).unwrap(),
1208 }];
1209
1210 let parameters = ChatRoomParametersV1 { owner: owner_vk };
1211
1212 let small_msg = AuthorizedMessageV1::new(
1214 MessageV1 {
1215 room_owner: owner_id,
1216 author: author_id,
1217 time: SystemTime::now(),
1218 content: RoomMessageBody::public("short".to_string()),
1219 },
1220 &author_sk,
1221 );
1222 let big_msg = AuthorizedMessageV1::new(
1223 MessageV1 {
1224 room_owner: owner_id,
1225 author: author_id,
1226 time: SystemTime::now(),
1227 content: RoomMessageBody::public("x".repeat(100)),
1228 },
1229 &author_sk,
1230 );
1231
1232 assert!(small_msg.message.content.content_len() <= 50);
1233 assert!(big_msg.message.content.content_len() > 50);
1234
1235 let mut messages = MessagesV1::default();
1236 let delta = vec![small_msg.clone(), big_msg.clone()];
1237 assert!(messages
1238 .apply_delta(&parent_state, ¶meters, &Some(delta))
1239 .is_ok());
1240
1241 assert_eq!(
1242 messages.messages.len(),
1243 1,
1244 "Only small message should survive"
1245 );
1246 assert!(messages.messages.contains(&small_msg));
1247 assert!(
1248 !messages.messages.contains(&big_msg),
1249 "Oversized message should be filtered"
1250 );
1251 }
1252
1253 #[test]
1254 fn test_message_author_preservation_across_users() {
1255 let user1_sk = SigningKey::generate(&mut OsRng);
1257 let user1_vk = user1_sk.verifying_key();
1258 let user1_id = MemberId::from(&user1_vk);
1259
1260 let user2_sk = SigningKey::generate(&mut OsRng);
1261 let user2_vk = user2_sk.verifying_key();
1262 let user2_id = MemberId::from(&user2_vk);
1263
1264 let owner_sk = SigningKey::generate(&mut OsRng);
1265 let owner_vk = owner_sk.verifying_key();
1266 let owner_id = MemberId::from(&owner_vk);
1267
1268 println!("User1 ID: {}", user1_id);
1269 println!("User2 ID: {}", user2_id);
1270 println!("Owner ID: {}", owner_id);
1271
1272 let msg1 = MessageV1 {
1274 room_owner: owner_id,
1275 author: user1_id,
1276 content: RoomMessageBody::public("Message from user1".to_string()),
1277 time: SystemTime::now(),
1278 };
1279
1280 let msg2 = MessageV1 {
1281 room_owner: owner_id,
1282 author: user2_id,
1283 content: RoomMessageBody::public("Message from user2".to_string()),
1284 time: SystemTime::now() + Duration::from_secs(1),
1285 };
1286
1287 let auth_msg1 = AuthorizedMessageV1::new(msg1.clone(), &user1_sk);
1288 let auth_msg2 = AuthorizedMessageV1::new(msg2.clone(), &user2_sk);
1289
1290 let messages = MessagesV1 {
1292 messages: vec![auth_msg1.clone(), auth_msg2.clone()],
1293 ..Default::default()
1294 };
1295
1296 assert_eq!(messages.messages.len(), 2);
1298
1299 let stored_msg1 = &messages.messages[0];
1300 let stored_msg2 = &messages.messages[1];
1301
1302 assert_eq!(
1303 stored_msg1.message.author, user1_id,
1304 "Message 1 author should be user1, but got {}",
1305 stored_msg1.message.author
1306 );
1307 assert_eq!(
1308 stored_msg2.message.author, user2_id,
1309 "Message 2 author should be user2, but got {}",
1310 stored_msg2.message.author
1311 );
1312
1313 assert_ne!(user1_id, user2_id, "User IDs should be different");
1315
1316 let user1_id_str = user1_id.to_string();
1318 let user2_id_str = user2_id.to_string();
1319
1320 println!("User1 ID string: {}", user1_id_str);
1321 println!("User2 ID string: {}", user2_id_str);
1322
1323 assert_ne!(
1324 user1_id_str, user2_id_str,
1325 "User ID strings should be different"
1326 );
1327 }
1328
1329 #[test]
1330 fn test_edit_action() {
1331 let signing_key = SigningKey::generate(&mut OsRng);
1332 let verifying_key = signing_key.verifying_key();
1333 let owner_id = MemberId::from(&verifying_key);
1334 let author_id = owner_id;
1335
1336 let original_msg = MessageV1 {
1338 room_owner: owner_id,
1339 author: author_id,
1340 time: SystemTime::now(),
1341 content: RoomMessageBody::public("Original content".to_string()),
1342 };
1343 let auth_original = AuthorizedMessageV1::new(original_msg, &signing_key);
1344 let original_id = auth_original.id();
1345
1346 let edit_msg = MessageV1 {
1348 room_owner: owner_id,
1349 author: author_id,
1350 time: SystemTime::now() + Duration::from_secs(1),
1351 content: RoomMessageBody::edit(original_id.clone(), "Edited content".to_string()),
1352 };
1353 let auth_edit = AuthorizedMessageV1::new(edit_msg, &signing_key);
1354
1355 let mut messages = MessagesV1 {
1357 messages: vec![auth_original.clone(), auth_edit],
1358 ..Default::default()
1359 };
1360 messages.rebuild_actions_state();
1361
1362 assert!(messages.is_edited(&original_id));
1364 let effective = messages.effective_text(&auth_original);
1365 assert_eq!(effective, Some("Edited content".to_string()));
1366
1367 let display: Vec<_> = messages.display_messages().collect();
1369 assert_eq!(display.len(), 1);
1370 }
1371
1372 #[test]
1373 fn test_edit_by_non_author_ignored() {
1374 let owner_sk = SigningKey::generate(&mut OsRng);
1375 let owner_vk = owner_sk.verifying_key();
1376 let owner_id = MemberId::from(&owner_vk);
1377
1378 let other_sk = SigningKey::generate(&mut OsRng);
1379 let other_id = MemberId::from(&other_sk.verifying_key());
1380
1381 let original_msg = MessageV1 {
1383 room_owner: owner_id,
1384 author: owner_id,
1385 time: SystemTime::now(),
1386 content: RoomMessageBody::public("Original content".to_string()),
1387 };
1388 let auth_original = AuthorizedMessageV1::new(original_msg, &owner_sk);
1389 let original_id = auth_original.id();
1390
1391 let edit_msg = MessageV1 {
1393 room_owner: owner_id,
1394 author: other_id,
1395 time: SystemTime::now() + Duration::from_secs(1),
1396 content: RoomMessageBody::edit(original_id.clone(), "Hacked content".to_string()),
1397 };
1398 let auth_edit = AuthorizedMessageV1::new(edit_msg, &other_sk);
1399
1400 let mut messages = MessagesV1 {
1401 messages: vec![auth_original.clone(), auth_edit],
1402 ..Default::default()
1403 };
1404 messages.rebuild_actions_state();
1405
1406 assert!(!messages.is_edited(&original_id));
1408 let effective = messages.effective_text(&auth_original);
1409 assert_eq!(effective, Some("Original content".to_string()));
1410 }
1411
1412 #[test]
1413 fn test_delete_action() {
1414 let signing_key = SigningKey::generate(&mut OsRng);
1415 let verifying_key = signing_key.verifying_key();
1416 let owner_id = MemberId::from(&verifying_key);
1417
1418 let original_msg = MessageV1 {
1420 room_owner: owner_id,
1421 author: owner_id,
1422 time: SystemTime::now(),
1423 content: RoomMessageBody::public("Will be deleted".to_string()),
1424 };
1425 let auth_original = AuthorizedMessageV1::new(original_msg, &signing_key);
1426 let original_id = auth_original.id();
1427
1428 let delete_msg = MessageV1 {
1430 room_owner: owner_id,
1431 author: owner_id,
1432 time: SystemTime::now() + Duration::from_secs(1),
1433 content: RoomMessageBody::delete(original_id.clone()),
1434 };
1435 let auth_delete = AuthorizedMessageV1::new(delete_msg, &signing_key);
1436
1437 let mut messages = MessagesV1 {
1438 messages: vec![auth_original, auth_delete],
1439 ..Default::default()
1440 };
1441 messages.rebuild_actions_state();
1442
1443 assert!(messages.is_deleted(&original_id));
1445
1446 let display: Vec<_> = messages.display_messages().collect();
1448 assert_eq!(display.len(), 0);
1449 }
1450
1451 #[test]
1452 fn test_reaction_action() {
1453 let user1_sk = SigningKey::generate(&mut OsRng);
1454 let user1_id = MemberId::from(&user1_sk.verifying_key());
1455
1456 let user2_sk = SigningKey::generate(&mut OsRng);
1457 let user2_id = MemberId::from(&user2_sk.verifying_key());
1458
1459 let owner_id = user1_id;
1460
1461 let original_msg = MessageV1 {
1463 room_owner: owner_id,
1464 author: user1_id,
1465 time: SystemTime::now(),
1466 content: RoomMessageBody::public("React to me!".to_string()),
1467 };
1468 let auth_original = AuthorizedMessageV1::new(original_msg, &user1_sk);
1469 let original_id = auth_original.id();
1470
1471 let reaction_msg = MessageV1 {
1473 room_owner: owner_id,
1474 author: user2_id,
1475 time: SystemTime::now() + Duration::from_secs(1),
1476 content: RoomMessageBody::reaction(original_id.clone(), "👍".to_string()),
1477 };
1478 let auth_reaction = AuthorizedMessageV1::new(reaction_msg, &user2_sk);
1479
1480 let reaction_msg2 = MessageV1 {
1482 room_owner: owner_id,
1483 author: user1_id,
1484 time: SystemTime::now() + Duration::from_secs(2),
1485 content: RoomMessageBody::reaction(original_id.clone(), "👍".to_string()),
1486 };
1487 let auth_reaction2 = AuthorizedMessageV1::new(reaction_msg2, &user1_sk);
1488
1489 let mut messages = MessagesV1 {
1490 messages: vec![auth_original, auth_reaction, auth_reaction2],
1491 ..Default::default()
1492 };
1493 messages.rebuild_actions_state();
1494
1495 let reactions = messages.reactions(&original_id).unwrap();
1497 let thumbs_up = reactions.get("👍").unwrap();
1498 assert_eq!(thumbs_up.len(), 2);
1499 assert!(thumbs_up.contains(&user1_id));
1500 assert!(thumbs_up.contains(&user2_id));
1501 }
1502
1503 #[test]
1504 fn test_remove_reaction_action() {
1505 let user_sk = SigningKey::generate(&mut OsRng);
1506 let user_id = MemberId::from(&user_sk.verifying_key());
1507 let owner_id = user_id;
1508
1509 let original_msg = MessageV1 {
1511 room_owner: owner_id,
1512 author: user_id,
1513 time: SystemTime::now(),
1514 content: RoomMessageBody::public("Test message".to_string()),
1515 };
1516 let auth_original = AuthorizedMessageV1::new(original_msg, &user_sk);
1517 let original_id = auth_original.id();
1518
1519 let reaction_msg = MessageV1 {
1521 room_owner: owner_id,
1522 author: user_id,
1523 time: SystemTime::now() + Duration::from_secs(1),
1524 content: RoomMessageBody::reaction(original_id.clone(), "❤️".to_string()),
1525 };
1526 let auth_reaction = AuthorizedMessageV1::new(reaction_msg, &user_sk);
1527
1528 let remove_msg = MessageV1 {
1530 room_owner: owner_id,
1531 author: user_id,
1532 time: SystemTime::now() + Duration::from_secs(2),
1533 content: RoomMessageBody::remove_reaction(original_id.clone(), "❤️".to_string()),
1534 };
1535 let auth_remove = AuthorizedMessageV1::new(remove_msg, &user_sk);
1536
1537 let mut messages = MessagesV1 {
1538 messages: vec![auth_original, auth_reaction, auth_remove],
1539 ..Default::default()
1540 };
1541 messages.rebuild_actions_state();
1542
1543 assert!(messages.reactions(&original_id).is_none());
1545 }
1546
1547 #[test]
1548 fn test_action_on_deleted_message_ignored() {
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 original_msg = MessageV1 {
1555 room_owner: owner_id,
1556 author: owner_id,
1557 time: SystemTime::now(),
1558 content: RoomMessageBody::public("Will be deleted".to_string()),
1559 };
1560 let auth_original = AuthorizedMessageV1::new(original_msg, &signing_key);
1561 let original_id = auth_original.id();
1562
1563 let delete_msg = MessageV1 {
1565 room_owner: owner_id,
1566 author: owner_id,
1567 time: SystemTime::now() + Duration::from_secs(1),
1568 content: RoomMessageBody::delete(original_id.clone()),
1569 };
1570 let auth_delete = AuthorizedMessageV1::new(delete_msg, &signing_key);
1571
1572 let edit_msg = MessageV1 {
1574 room_owner: owner_id,
1575 author: owner_id,
1576 time: SystemTime::now() + Duration::from_secs(2),
1577 content: RoomMessageBody::edit(original_id.clone(), "Too late!".to_string()),
1578 };
1579 let auth_edit = AuthorizedMessageV1::new(edit_msg, &signing_key);
1580
1581 let mut messages = MessagesV1 {
1582 messages: vec![auth_original, auth_delete, auth_edit],
1583 ..Default::default()
1584 };
1585 messages.rebuild_actions_state();
1586
1587 assert!(messages.is_deleted(&original_id));
1589 assert!(!messages.is_edited(&original_id));
1590 }
1591
1592 #[test]
1593 fn test_display_messages_filters_actions() {
1594 let signing_key = SigningKey::generate(&mut OsRng);
1595 let verifying_key = signing_key.verifying_key();
1596 let owner_id = MemberId::from(&verifying_key);
1597
1598 let msg1 = MessageV1 {
1600 room_owner: owner_id,
1601 author: owner_id,
1602 time: SystemTime::now(),
1603 content: RoomMessageBody::public("Hello".to_string()),
1604 };
1605 let auth_msg1 = AuthorizedMessageV1::new(msg1, &signing_key);
1606 let msg1_id = auth_msg1.id();
1607
1608 let reaction_msg = MessageV1 {
1610 room_owner: owner_id,
1611 author: owner_id,
1612 time: SystemTime::now() + Duration::from_secs(1),
1613 content: RoomMessageBody::reaction(msg1_id, "👍".to_string()),
1614 };
1615 let auth_reaction = AuthorizedMessageV1::new(reaction_msg, &signing_key);
1616
1617 let msg2 = MessageV1 {
1619 room_owner: owner_id,
1620 author: owner_id,
1621 time: SystemTime::now() + Duration::from_secs(2),
1622 content: RoomMessageBody::public("World".to_string()),
1623 };
1624 let auth_msg2 = AuthorizedMessageV1::new(msg2, &signing_key);
1625
1626 let mut messages = MessagesV1 {
1627 messages: vec![auth_msg1, auth_reaction, auth_msg2],
1628 ..Default::default()
1629 };
1630 messages.rebuild_actions_state();
1631
1632 let display: Vec<_> = messages.display_messages().collect();
1634 assert_eq!(display.len(), 2);
1635 assert_eq!(
1636 display[0].message.content.as_public_string(),
1637 Some("Hello".to_string())
1638 );
1639 assert_eq!(
1640 display[1].message.content.as_public_string(),
1641 Some("World".to_string())
1642 );
1643 }
1644}