river_core/room_state/
message.rs

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::fmt;
12use std::time::SystemTime;
13
14#[derive(Serialize, Deserialize, Clone, PartialEq, Debug, Default)]
15pub struct MessagesV1 {
16    pub messages: Vec<AuthorizedMessageV1>,
17}
18
19impl ComposableState for MessagesV1 {
20    type ParentState = ChatRoomStateV1;
21    type Summary = Vec<MessageId>;
22    type Delta = Vec<AuthorizedMessageV1>;
23    type Parameters = ChatRoomParametersV1;
24
25    fn verify(
26        &self,
27        parent_state: &Self::ParentState,
28        parameters: &Self::Parameters,
29    ) -> Result<(), String> {
30        let members_by_id = parent_state.members.members_by_member_id();
31        let owner_id = parameters.owner_id();
32
33        for message in &self.messages {
34            let verifying_key = if message.message.author == owner_id {
35                // Owner's messages are validated against the owner's key
36                &parameters.owner
37            } else if let Some(member) = members_by_id.get(&message.message.author) {
38                // Regular member messages are validated against their member key
39                &member.member.member_vk
40            } else {
41                return Err(format!(
42                    "Message author not found: {:?}",
43                    message.message.author
44                ));
45            };
46
47            if message.validate(verifying_key).is_err() {
48                return Err(format!("Invalid message signature: id:{:?}", message.id()));
49            }
50        }
51
52        Ok(())
53    }
54
55    fn summarize(
56        &self,
57        _parent_state: &Self::ParentState,
58        _parameters: &Self::Parameters,
59    ) -> Self::Summary {
60        self.messages.iter().map(|m| m.id()).collect()
61    }
62
63    fn delta(
64        &self,
65        _parent_state: &Self::ParentState,
66        _parameters: &Self::Parameters,
67        old_state_summary: &Self::Summary,
68    ) -> Option<Self::Delta> {
69        let delta: Vec<AuthorizedMessageV1> = self
70            .messages
71            .iter()
72            .filter(|m| !old_state_summary.contains(&m.id()))
73            .cloned()
74            .collect();
75        if delta.is_empty() {
76            None
77        } else {
78            Some(delta)
79        }
80    }
81
82    fn apply_delta(
83        &mut self,
84        parent_state: &Self::ParentState,
85        parameters: &Self::Parameters,
86        delta: &Option<Self::Delta>,
87    ) -> Result<(), String> {
88        let max_recent_messages = parent_state.configuration.configuration.max_recent_messages;
89        let max_message_size = parent_state.configuration.configuration.max_message_size;
90        let privacy_mode = &parent_state.configuration.configuration.privacy_mode;
91        let current_secret_version = parent_state.secrets.current_version;
92
93        // Validate private message constraints before adding
94        if let Some(delta) = delta {
95            for msg in delta {
96                match &msg.message.content {
97                    RoomMessageBody::Private { secret_version, .. } => {
98                        // In private mode, verify secret version matches current
99                        if *privacy_mode == PrivacyMode::Private {
100                            if *secret_version != current_secret_version {
101                                return Err(format!(
102                                    "Private message secret version {} does not match current version {}",
103                                    secret_version, current_secret_version
104                                ));
105                            }
106                        }
107
108                        // Verify all current members have encrypted blobs for this version
109                        let members = parent_state.members.members_by_member_id();
110                        if !parent_state.secrets.has_complete_distribution(&members) {
111                            return Err(
112                                "Cannot accept private messages: incomplete secret distribution"
113                                    .to_string(),
114                            );
115                        }
116                    }
117                    RoomMessageBody::Public { .. } => {
118                        // In private mode, reject public messages
119                        if *privacy_mode == PrivacyMode::Private {
120                            return Err("Cannot send public messages in private room".to_string());
121                        }
122                    }
123                }
124            }
125
126            self.messages.extend(delta.iter().cloned());
127        }
128
129        // Always enforce message constraints
130        // Ensure there are no messages over the size limit
131        self.messages
132            .retain(|m| m.message.content.content_len() <= max_message_size);
133
134        // Ensure all messages are signed by a valid member or the room owner, remove if not
135        let members_by_id = parent_state.members.members_by_member_id();
136        let owner_id = MemberId::from(&parameters.owner);
137        self.messages.retain(|m| {
138            members_by_id.contains_key(&m.message.author) || m.message.author == owner_id
139        });
140
141        // Sort messages by time
142        self.messages
143            .sort_by(|a, b| a.message.time.cmp(&b.message.time));
144
145        // Remove oldest messages if there are too many
146        if self.messages.len() > max_recent_messages {
147            self.messages
148                .drain(0..self.messages.len() - max_recent_messages);
149        }
150
151        Ok(())
152    }
153}
154
155/// Message body that can be either public plaintext or private encrypted
156#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)]
157pub enum RoomMessageBody {
158    /// Public plaintext message
159    Public { plaintext: String },
160    /// Private encrypted message
161    Private {
162        ciphertext: Vec<u8>,
163        nonce: [u8; 12],
164        secret_version: SecretVersion,
165    },
166}
167
168impl RoomMessageBody {
169    /// Create a new public message
170    pub fn public(plaintext: String) -> Self {
171        Self::Public { plaintext }
172    }
173
174    /// Create a new private message
175    pub fn private(ciphertext: Vec<u8>, nonce: [u8; 12], secret_version: SecretVersion) -> Self {
176        Self::Private {
177            ciphertext,
178            nonce,
179            secret_version,
180        }
181    }
182
183    /// Check if this is a public message
184    pub fn is_public(&self) -> bool {
185        matches!(self, Self::Public { .. })
186    }
187
188    /// Check if this is a private message
189    pub fn is_private(&self) -> bool {
190        matches!(self, Self::Private { .. })
191    }
192
193    /// Get the content length for validation
194    pub fn content_len(&self) -> usize {
195        match self {
196            Self::Public { plaintext } => plaintext.len(),
197            Self::Private { ciphertext, .. } => ciphertext.len(),
198        }
199    }
200
201    /// Get the secret version (if private)
202    pub fn secret_version(&self) -> Option<SecretVersion> {
203        match self {
204            Self::Public { .. } => None,
205            Self::Private { secret_version, .. } => Some(*secret_version),
206        }
207    }
208
209    /// Get a string representation for display purposes
210    /// This is a temporary helper for UI integration during development
211    pub fn to_string_lossy(&self) -> String {
212        match self {
213            Self::Public { plaintext } => plaintext.clone(),
214            Self::Private {
215                ciphertext,
216                secret_version,
217                ..
218            } => {
219                format!(
220                    "[Encrypted message: {} bytes, v{}]",
221                    ciphertext.len(),
222                    secret_version
223                )
224            }
225        }
226    }
227
228    /// Try to get the public plaintext, returns None if private
229    pub fn as_public_string(&self) -> Option<&str> {
230        match self {
231            Self::Public { plaintext } => Some(plaintext),
232            Self::Private { .. } => None,
233        }
234    }
235}
236
237impl fmt::Display for RoomMessageBody {
238    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
239        write!(f, "{}", self.to_string_lossy())
240    }
241}
242
243#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)]
244pub struct MessageV1 {
245    pub room_owner: MemberId,
246    pub author: MemberId,
247    pub time: SystemTime,
248    pub content: RoomMessageBody,
249}
250
251impl Default for MessageV1 {
252    fn default() -> Self {
253        Self {
254            room_owner: MemberId(FastHash(0)),
255            author: MemberId(FastHash(0)),
256            time: SystemTime::UNIX_EPOCH,
257            content: RoomMessageBody::public(String::new()),
258        }
259    }
260}
261
262#[derive(Clone, PartialEq, Serialize, Deserialize)]
263pub struct AuthorizedMessageV1 {
264    pub message: MessageV1,
265    pub signature: Signature,
266}
267
268impl fmt::Debug for AuthorizedMessageV1 {
269    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
270        f.debug_struct("AuthorizedMessage")
271            .field("message", &self.message)
272            .field(
273                "signature",
274                &format_args!("{}", truncated_base64(self.signature.to_bytes())),
275            )
276            .finish()
277    }
278}
279
280#[derive(Eq, PartialEq, Hash, Serialize, Deserialize, Clone, Debug, Ord, PartialOrd)]
281pub struct MessageId(pub FastHash);
282
283impl fmt::Display for MessageId {
284    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
285        write!(f, "{:?}", self.0)
286    }
287}
288
289impl AuthorizedMessageV1 {
290    pub fn new(message: MessageV1, signing_key: &SigningKey) -> Self {
291        Self {
292            message: message.clone(),
293            signature: sign_struct(&message, signing_key),
294        }
295    }
296
297    pub fn validate(
298        &self,
299        verifying_key: &VerifyingKey,
300    ) -> Result<(), ed25519_dalek::SignatureError> {
301        verify_struct(&self.message, &self.signature, verifying_key)
302    }
303
304    pub fn id(&self) -> MessageId {
305        MessageId(fast_hash(&self.signature.to_bytes()))
306    }
307}
308
309#[cfg(test)]
310mod tests {
311    use super::*;
312    use ed25519_dalek::{Signer, SigningKey};
313    use rand::rngs::OsRng;
314    use std::time::Duration;
315
316    fn create_test_message(owner_id: MemberId, author_id: MemberId) -> MessageV1 {
317        MessageV1 {
318            room_owner: owner_id,
319            author: author_id,
320            time: SystemTime::now(),
321            content: RoomMessageBody::public("Test message".to_string()),
322        }
323    }
324
325    #[test]
326    fn test_messages_v1_default() {
327        let default_messages = MessagesV1::default();
328        assert!(default_messages.messages.is_empty());
329    }
330
331    #[test]
332    fn test_authorized_message_v1_debug() {
333        let signing_key = SigningKey::generate(&mut OsRng);
334        let owner_id = MemberId(FastHash(0));
335        let author_id = MemberId(FastHash(1));
336
337        let message = create_test_message(owner_id, author_id);
338        let authorized_message = AuthorizedMessageV1::new(message, &signing_key);
339
340        let debug_output = format!("{:?}", authorized_message);
341        assert!(debug_output.contains("AuthorizedMessage"));
342        assert!(debug_output.contains("message"));
343        assert!(debug_output.contains("signature"));
344    }
345
346    #[test]
347    fn test_authorized_message_new_and_validate() {
348        let signing_key = SigningKey::generate(&mut OsRng);
349        let verifying_key = signing_key.verifying_key();
350        let owner_id = MemberId(FastHash(0));
351        let author_id = MemberId(FastHash(1));
352
353        let message = create_test_message(owner_id, author_id);
354        let authorized_message = AuthorizedMessageV1::new(message.clone(), &signing_key);
355
356        assert_eq!(authorized_message.message, message);
357        assert!(authorized_message.validate(&verifying_key).is_ok());
358
359        // Test with wrong key
360        let wrong_key = SigningKey::generate(&mut OsRng).verifying_key();
361        assert!(authorized_message.validate(&wrong_key).is_err());
362
363        // Test with tampered message
364        let mut tampered_message = authorized_message.clone();
365        tampered_message.message.content = RoomMessageBody::public("Tampered content".to_string());
366        assert!(tampered_message.validate(&verifying_key).is_err());
367    }
368
369    #[test]
370    fn test_message_id() {
371        let signing_key = SigningKey::generate(&mut OsRng);
372        let owner_id = MemberId(FastHash(0));
373        let author_id = MemberId(FastHash(1));
374
375        let message = create_test_message(owner_id, author_id);
376        let authorized_message = AuthorizedMessageV1::new(message, &signing_key);
377
378        let id1 = authorized_message.id();
379        let id2 = authorized_message.id();
380
381        assert_eq!(id1, id2);
382
383        // Test that different messages have different IDs
384        let message2 = create_test_message(owner_id, author_id);
385        let authorized_message2 = AuthorizedMessageV1::new(message2, &signing_key);
386        assert_ne!(authorized_message.id(), authorized_message2.id());
387    }
388
389    #[test]
390    fn test_messages_verify() {
391        // Generate a new signing key and its corresponding verifying key for the owner
392        let owner_signing_key = SigningKey::generate(&mut OsRng);
393        let owner_verifying_key = owner_signing_key.verifying_key();
394        let owner_id = MemberId::from(&owner_verifying_key);
395
396        // Generate a signing key for the author
397        let author_signing_key = SigningKey::generate(&mut OsRng);
398        let author_verifying_key = author_signing_key.verifying_key();
399        let author_id = MemberId::from(&author_verifying_key);
400
401        // Create a test message and authorize it with the author's signing key
402        let message = create_test_message(owner_id, author_id);
403        let authorized_message = AuthorizedMessageV1::new(message, &author_signing_key);
404
405        // Create a Messages struct with the authorized message
406        let messages = MessagesV1 {
407            messages: vec![authorized_message],
408        };
409
410        // Set up a parent room_state (ChatRoomState) with the author as a member
411        let mut parent_state = ChatRoomStateV1::default();
412        let author_member = crate::room_state::member::Member {
413            owner_member_id: owner_id,
414            invited_by: owner_id,
415            member_vk: author_verifying_key,
416        };
417        let authorized_author =
418            crate::room_state::member::AuthorizedMember::new(author_member, &owner_signing_key);
419        parent_state.members.members = vec![authorized_author];
420
421        // Set up parameters for verification
422        let parameters = ChatRoomParametersV1 {
423            owner: owner_verifying_key,
424        };
425
426        // Verify that a valid message passes verification
427        assert!(
428            messages.verify(&parent_state, &parameters).is_ok(),
429            "Valid messages should pass verification: {:?}",
430            messages.verify(&parent_state, &parameters)
431        );
432
433        // Test with invalid signature
434        let mut invalid_messages = messages.clone();
435        invalid_messages.messages[0].signature = Signature::from_bytes(&[0; 64]); // Replace with an invalid signature
436        assert!(
437            invalid_messages.verify(&parent_state, &parameters).is_err(),
438            "Messages with invalid signature should fail verification"
439        );
440
441        // Test with non-existent author
442        let non_existent_author_id =
443            MemberId::from(&SigningKey::generate(&mut OsRng).verifying_key());
444        let invalid_message = create_test_message(owner_id, non_existent_author_id);
445        let invalid_authorized_message =
446            AuthorizedMessageV1::new(invalid_message, &author_signing_key);
447        let invalid_messages = MessagesV1 {
448            messages: vec![invalid_authorized_message],
449        };
450        assert!(
451            invalid_messages.verify(&parent_state, &parameters).is_err(),
452            "Messages with non-existent author should fail verification"
453        );
454    }
455
456    #[test]
457    fn test_messages_summarize() {
458        let signing_key = SigningKey::generate(&mut OsRng);
459        let owner_id = MemberId(FastHash(0));
460        let author_id = MemberId(FastHash(1));
461
462        let message1 = create_test_message(owner_id, author_id);
463        let message2 = create_test_message(owner_id, author_id);
464
465        let authorized_message1 = AuthorizedMessageV1::new(message1, &signing_key);
466        let authorized_message2 = AuthorizedMessageV1::new(message2, &signing_key);
467
468        let messages = MessagesV1 {
469            messages: vec![authorized_message1.clone(), authorized_message2.clone()],
470        };
471
472        let parent_state = ChatRoomStateV1::default();
473        let parameters = ChatRoomParametersV1 {
474            owner: signing_key.verifying_key(),
475        };
476
477        let summary = messages.summarize(&parent_state, &parameters);
478        assert_eq!(summary.len(), 2);
479        assert_eq!(summary[0], authorized_message1.id());
480        assert_eq!(summary[1], authorized_message2.id());
481
482        // Test empty messages
483        let empty_messages = MessagesV1 { messages: vec![] };
484        let empty_summary = empty_messages.summarize(&parent_state, &parameters);
485        assert!(empty_summary.is_empty());
486    }
487
488    #[test]
489    fn test_messages_delta() {
490        let signing_key = SigningKey::generate(&mut OsRng);
491        let owner_id = MemberId(FastHash(0));
492        let author_id = MemberId(FastHash(1));
493
494        let message1 = create_test_message(owner_id, author_id);
495        let message2 = create_test_message(owner_id, author_id);
496        let message3 = create_test_message(owner_id, author_id);
497
498        let authorized_message1 = AuthorizedMessageV1::new(message1, &signing_key);
499        let authorized_message2 = AuthorizedMessageV1::new(message2, &signing_key);
500        let authorized_message3 = AuthorizedMessageV1::new(message3, &signing_key);
501
502        let messages = MessagesV1 {
503            messages: vec![
504                authorized_message1.clone(),
505                authorized_message2.clone(),
506                authorized_message3.clone(),
507            ],
508        };
509
510        let parent_state = ChatRoomStateV1::default();
511        let parameters = ChatRoomParametersV1 {
512            owner: signing_key.verifying_key(),
513        };
514
515        // Test with partial old summary
516        let old_summary = vec![authorized_message1.id(), authorized_message2.id()];
517        let delta = messages
518            .delta(&parent_state, &parameters, &old_summary)
519            .unwrap();
520        assert_eq!(delta.len(), 1);
521        assert_eq!(delta[0], authorized_message3);
522
523        // Test with empty old summary
524        let empty_summary: Vec<MessageId> = vec![];
525        let full_delta = messages
526            .delta(&parent_state, &parameters, &empty_summary)
527            .unwrap();
528        assert_eq!(full_delta.len(), 3);
529        assert_eq!(full_delta, messages.messages);
530
531        // Test with full old summary (no changes)
532        let full_summary = vec![
533            authorized_message1.id(),
534            authorized_message2.id(),
535            authorized_message3.id(),
536        ];
537        let no_delta = messages.delta(&parent_state, &parameters, &full_summary);
538        assert!(no_delta.is_none());
539    }
540
541    #[test]
542    fn test_messages_apply_delta() {
543        // Setup
544        let owner_signing_key = SigningKey::generate(&mut OsRng);
545        let owner_verifying_key = owner_signing_key.verifying_key();
546        let owner_id = MemberId::from(&owner_verifying_key);
547
548        let author_signing_key = SigningKey::generate(&mut OsRng);
549        let author_verifying_key = author_signing_key.verifying_key();
550        let author_id = MemberId::from(&author_verifying_key);
551
552        let mut parent_state = ChatRoomStateV1::default();
553        parent_state.configuration.configuration.max_recent_messages = 3;
554        parent_state.configuration.configuration.max_message_size = 100;
555        parent_state.members.members = vec![crate::room_state::member::AuthorizedMember {
556            member: crate::room_state::member::Member {
557                owner_member_id: owner_id,
558                invited_by: owner_id,
559                member_vk: author_verifying_key,
560            },
561            signature: owner_signing_key.try_sign(&[0; 32]).unwrap(),
562        }];
563
564        let parameters = ChatRoomParametersV1 {
565            owner: owner_verifying_key,
566        };
567
568        // Create messages
569        let create_message = |time: SystemTime| {
570            let message = MessageV1 {
571                room_owner: owner_id,
572                author: author_id,
573                time,
574                content: RoomMessageBody::public("Test message".to_string()),
575            };
576            AuthorizedMessageV1::new(message, &author_signing_key)
577        };
578
579        let now = SystemTime::now();
580        let message1 = create_message(now - Duration::from_secs(3));
581        let message2 = create_message(now - Duration::from_secs(2));
582        let message3 = create_message(now - Duration::from_secs(1));
583        let message4 = create_message(now);
584
585        // Initial room_state with 2 messages
586        let mut messages = MessagesV1 {
587            messages: vec![message1.clone(), message2.clone()],
588        };
589
590        // Apply delta with 2 new messages
591        let delta = vec![message3.clone(), message4.clone()];
592        assert!(messages
593            .apply_delta(&parent_state, &parameters, &Some(delta))
594            .is_ok());
595
596        // Check results
597        assert_eq!(
598            messages.messages.len(),
599            3,
600            "Should have 3 messages after applying delta"
601        );
602        assert!(
603            !messages.messages.contains(&message1),
604            "Oldest message should be removed"
605        );
606        assert!(
607            messages.messages.contains(&message2),
608            "Second oldest message should be retained"
609        );
610        assert!(
611            messages.messages.contains(&message3),
612            "New message should be added"
613        );
614        assert!(
615            messages.messages.contains(&message4),
616            "Newest message should be added"
617        );
618
619        // Apply delta with an older message
620        let old_message = create_message(now - Duration::from_secs(4));
621        let delta = vec![old_message.clone()];
622        assert!(messages
623            .apply_delta(&parent_state, &parameters, &Some(delta))
624            .is_ok());
625
626        // Check results
627        assert_eq!(messages.messages.len(), 3, "Should still have 3 messages");
628        assert!(
629            !messages.messages.contains(&old_message),
630            "Older message should not be added"
631        );
632        assert!(
633            messages.messages.contains(&message2),
634            "Message2 should be retained"
635        );
636        assert!(
637            messages.messages.contains(&message3),
638            "Message3 should be retained"
639        );
640        assert!(
641            messages.messages.contains(&message4),
642            "Newest message should be retained"
643        );
644    }
645
646    #[test]
647    fn test_message_author_preservation_across_users() {
648        // Create two users
649        let user1_sk = SigningKey::generate(&mut OsRng);
650        let user1_vk = user1_sk.verifying_key();
651        let user1_id = MemberId::from(&user1_vk);
652
653        let user2_sk = SigningKey::generate(&mut OsRng);
654        let user2_vk = user2_sk.verifying_key();
655        let user2_id = MemberId::from(&user2_vk);
656
657        let owner_sk = SigningKey::generate(&mut OsRng);
658        let owner_vk = owner_sk.verifying_key();
659        let owner_id = MemberId::from(&owner_vk);
660
661        println!("User1 ID: {}", user1_id);
662        println!("User2 ID: {}", user2_id);
663        println!("Owner ID: {}", owner_id);
664
665        // Create messages from different users
666        let msg1 = MessageV1 {
667            room_owner: owner_id,
668            author: user1_id,
669            content: RoomMessageBody::public("Message from user1".to_string()),
670            time: SystemTime::now(),
671        };
672
673        let msg2 = MessageV1 {
674            room_owner: owner_id,
675            author: user2_id,
676            content: RoomMessageBody::public("Message from user2".to_string()),
677            time: SystemTime::now() + Duration::from_secs(1),
678        };
679
680        let auth_msg1 = AuthorizedMessageV1::new(msg1.clone(), &user1_sk);
681        let auth_msg2 = AuthorizedMessageV1::new(msg2.clone(), &user2_sk);
682
683        // Create a messages state with both messages
684        let messages = MessagesV1 {
685            messages: vec![auth_msg1.clone(), auth_msg2.clone()],
686        };
687
688        // Verify authors are preserved
689        assert_eq!(messages.messages.len(), 2);
690
691        let stored_msg1 = &messages.messages[0];
692        let stored_msg2 = &messages.messages[1];
693
694        assert_eq!(
695            stored_msg1.message.author, user1_id,
696            "Message 1 author should be user1, but got {}",
697            stored_msg1.message.author
698        );
699        assert_eq!(
700            stored_msg2.message.author, user2_id,
701            "Message 2 author should be user2, but got {}",
702            stored_msg2.message.author
703        );
704
705        // Test that author IDs are different
706        assert_ne!(user1_id, user2_id, "User IDs should be different");
707
708        // Test Display implementation
709        let user1_id_str = user1_id.to_string();
710        let user2_id_str = user2_id.to_string();
711
712        println!("User1 ID string: {}", user1_id_str);
713        println!("User2 ID string: {}", user2_id_str);
714
715        assert_ne!(
716            user1_id_str, user2_id_str,
717            "User ID strings should be different"
718        );
719    }
720}