1use nostr::{EventId, UnsignedEvent};
15use nrc_mls_storage::NostrMlsStorageProvider;
16use openmls::group::{GroupId, MlsGroupStateError, ProcessMessageError, ValidationError};
17use openmls_basic_credential::SignatureKeyPair;
18use tls_codec::{Deserialize as TlsDeserialize, Serialize as TlsSerialize};
19
20use crate::error::Error;
21use crate::prelude::*;
22use crate::{util, NostrMls};
23
24const DEFAULT_EPOCH_LOOKBACK: u64 = 5;
26
27#[derive(Debug)]
29pub enum MessageProcessingResult {
30 ApplicationMessage(message_types::Message),
32 Proposal(UpdateGroupResult),
34 ExternalJoinProposal,
36 Commit,
38 Unprocessable,
40}
41
42impl<Storage> NostrMls<Storage>
43where
44 Storage: NostrMlsStorageProvider,
45{
46 pub fn get_message(&self, event_id: &EventId) -> Result<Option<message_types::Message>, Error> {
61 self.storage()
62 .find_message_by_event_id(event_id)
63 .map_err(|e| Error::Message(e.to_string()))
64 }
65
66 pub fn get_messages(
80 &self,
81 mls_group_id: &GroupId,
82 ) -> Result<Vec<message_types::Message>, Error> {
83 self.storage()
84 .messages(mls_group_id)
85 .map_err(|e| Error::Message(e.to_string()))
86 }
87
88 fn create_message_for_event(
106 &self,
107 group: &mut MlsGroup,
108 rumor: &mut UnsignedEvent,
109 ) -> Result<Vec<u8>, Error> {
110 let signer: SignatureKeyPair = self.load_mls_signer(group)?;
112
113 rumor.ensure_id();
115
116 let json: String = rumor.as_json();
118
119 let message_out = group.create_message(&self.provider, &signer, json.as_bytes())?;
121
122 let serialized_message = message_out.tls_serialize_detached()?;
123
124 Ok(serialized_message)
125 }
126
127 pub fn create_message(
146 &self,
147 mls_group_id: &GroupId,
148 mut rumor: UnsignedEvent,
149 ) -> Result<Event, Error> {
150 let mut mls_group = self
152 .load_mls_group(mls_group_id)?
153 .ok_or(Error::GroupNotFound)?;
154
155 let mut group: group_types::Group = self
157 .get_group(mls_group_id)
158 .map_err(|e| Error::Group(e.to_string()))?
159 .ok_or(Error::GroupNotFound)?;
160
161 let message: Vec<u8> = self.create_message_for_event(&mut mls_group, &mut rumor)?;
163
164 let rumor_id: EventId = rumor.id();
166
167 let event = self.build_encrypted_message_event(mls_group.group_id(), message)?;
168
169 let message: message_types::Message = message_types::Message {
171 id: rumor_id,
172 pubkey: rumor.pubkey,
173 kind: rumor.kind,
174 mls_group_id: mls_group_id.clone(),
175 created_at: rumor.created_at,
176 content: rumor.content.clone(),
177 tags: rumor.tags.clone(),
178 event: rumor.clone(),
179 wrapper_event_id: event.id,
180 state: message_types::MessageState::Created,
181 };
182
183 let processed_message: message_types::ProcessedMessage = message_types::ProcessedMessage {
185 wrapper_event_id: event.id,
186 message_event_id: Some(rumor_id),
187 processed_at: Timestamp::now(),
188 state: message_types::ProcessedMessageState::Created,
189 failure_reason: None,
190 };
191
192 self.storage()
194 .save_message(message.clone())
195 .map_err(|e| Error::Message(e.to_string()))?;
196
197 self.storage()
199 .save_processed_message(processed_message)
200 .map_err(|e| Error::Message(e.to_string()))?;
201
202 group.last_message_at = Some(rumor.created_at);
204 group.last_message_id = Some(message.id);
205 self.storage()
206 .save_group(group)
207 .map_err(|e| Error::Group(e.to_string()))?;
208
209 Ok(event)
210 }
211
212 fn process_message_for_group(
230 &self,
231 group: &mut MlsGroup,
232 message_bytes: &[u8],
233 ) -> Result<ProcessedMessageContent, Error> {
234 let mls_message = MlsMessageIn::tls_deserialize_exact(message_bytes)?;
235
236 tracing::debug!(target: "nostr_mls::messages::process_message_for_group", "Received message: {:?}", mls_message);
237 let protocol_message = mls_message.try_into_protocol_message()?;
238
239 if protocol_message.group_id() != group.group_id() {
241 return Err(Error::ProtocolGroupIdMismatch);
242 }
243
244 let processed_message = match group.process_message(&self.provider, protocol_message) {
245 Ok(processed_message) => processed_message,
246 Err(ProcessMessageError::ValidationError(ValidationError::CannotDecryptOwnMessage)) => {
247 return Err(Error::CannotDecryptOwnMessage);
248 }
249 Err(e) => {
250 tracing::error!(target: "nostr_mls::messages::process_message_for_group", "Error processing message: {:?}", e);
251 return Err(Error::ProcessMessage(e));
252 }
253 };
254
255 tracing::debug!(
256 target: "nostr_mls::messages::process_message_for_group",
257 "Processed message: {:?}",
258 processed_message
259 );
260
261 Ok(processed_message.into_content())
262 }
263
264 fn process_application_message_for_group(
284 &self,
285 mut group: group_types::Group,
286 event: &Event,
287 application_message: ApplicationMessage,
288 ) -> Result<message_types::Message, Error> {
289 let bytes = application_message.into_bytes();
291 let mut rumor: UnsignedEvent = UnsignedEvent::from_json(bytes)?;
292
293 let rumor_id: EventId = rumor.id();
294
295 let processed_message = message_types::ProcessedMessage {
296 wrapper_event_id: event.id,
297 message_event_id: Some(rumor_id),
298 processed_at: Timestamp::now(),
299 state: message_types::ProcessedMessageState::Processed,
300 failure_reason: None,
301 };
302
303 let message = message_types::Message {
304 id: rumor_id,
305 pubkey: rumor.pubkey,
306 kind: rumor.kind,
307 mls_group_id: group.mls_group_id.clone(),
308 created_at: rumor.created_at,
309 content: rumor.content.clone(),
310 tags: rumor.tags.clone(),
311 event: rumor.clone(),
312 wrapper_event_id: event.id,
313 state: message_types::MessageState::Processed,
314 };
315
316 self.storage()
317 .save_message(message.clone())
318 .map_err(|e| Error::Message(e.to_string()))?;
319
320 self.storage()
321 .save_processed_message(processed_message.clone())
322 .map_err(|e| Error::Message(e.to_string()))?;
323
324 group.last_message_at = Some(rumor.created_at);
326 group.last_message_id = Some(message.id);
327 self.storage()
328 .save_group(group)
329 .map_err(|e| Error::Group(e.to_string()))?;
330
331 tracing::debug!(target: "nostr_mls::messages::process_message", "Processed message: {:?}", processed_message);
332 tracing::debug!(target: "nostr_mls::messages::process_message", "Message: {:?}", message);
333 Ok(message)
334 }
335
336 fn process_proposal_message_for_group(
357 &self,
358 mls_group: &mut MlsGroup,
359 event: &Event,
360 staged_proposal: QueuedProposal,
361 ) -> Result<UpdateGroupResult, Error> {
362 match staged_proposal.sender() {
363 Sender::Member(leaf_index) => {
364 let member = mls_group.member_at(*leaf_index);
365
366 match member {
367 Some(member) => {
368 if self.is_member_admin(mls_group.group_id(), &member)? {
370 mls_group
371 .store_pending_proposal(self.provider.storage(), staged_proposal)
372 .map_err(|e| Error::Message(e.to_string()))?;
373
374 let _added_members =
375 self.pending_added_members_pubkeys(mls_group.group_id())?;
376
377 let mls_signer = self.load_mls_signer(mls_group)?;
378
379 let (commit_message, welcomes_option, _group_info) = mls_group
380 .commit_to_pending_proposals(&self.provider, &mls_signer)?;
381
382 let serialized_commit_message = commit_message
383 .tls_serialize_detached()
384 .map_err(|e| Error::Group(e.to_string()))?;
385
386 let commit_event = self.build_encrypted_message_event(
387 mls_group.group_id(),
388 serialized_commit_message,
389 )?;
390
391 let welcome_rumors: Option<Vec<UnsignedEvent>> = None;
395 if welcomes_option.is_some() {
396 return Err(Error::NotImplemented(
397 "Processing welcome rumors from proposals is not supported"
398 .to_string(),
399 ));
400 }
401
402 let processed_message = message_types::ProcessedMessage {
404 wrapper_event_id: event.id,
405 message_event_id: None,
406 processed_at: Timestamp::now(),
407 state: message_types::ProcessedMessageState::Processed,
408 failure_reason: None,
409 };
410
411 self.storage()
412 .save_processed_message(processed_message)
413 .map_err(|e| Error::Message(e.to_string()))?;
414
415 Ok(UpdateGroupResult {
416 evolution_event: commit_event,
417 welcome_rumors,
418 })
419 } else {
420 Err(Error::ProposalFromNonAdmin)
421 }
422 }
423 None => {
424 tracing::warn!(target: "nostr_mls::messages::process_message_for_group", "Received proposal from non-member.");
425 Err(Error::MessageFromNonMember)
426 }
427 }
428 }
429 Sender::External(_) => {
430 Err(Error::NotImplemented("Processing external proposals from external proposal extensions is not supported".to_string()))
432 }
433 Sender::NewMemberCommit => {
434 Err(Error::NotImplemented(
436 "Processing external proposals for new member commits is not supported"
437 .to_string(),
438 ))
439 }
440 Sender::NewMemberProposal => {
441 Err(Error::NotImplemented(
443 "Processing external proposals for new member proposals is not supported"
444 .to_string(),
445 ))
446 }
447 }
448 }
449
450 fn process_commit_message_for_group(
470 &self,
471 mls_group: &mut MlsGroup,
472 event: &Event,
473 staged_commit: StagedCommit,
474 ) -> Result<(), Error> {
475 mls_group
476 .merge_staged_commit(&self.provider, staged_commit)
477 .map_err(|e| Error::Message(e.to_string()))?;
478
479 self.exporter_secret(mls_group.group_id())?;
481
482 self.sync_group_metadata_from_mls(mls_group.group_id())?;
485
486 let processed_message = message_types::ProcessedMessage {
488 wrapper_event_id: event.id,
489 message_event_id: None,
490 processed_at: Timestamp::now(),
491 state: message_types::ProcessedMessageState::Processed,
492 failure_reason: None,
493 };
494
495 self.storage()
496 .save_processed_message(processed_message)
497 .map_err(|e| Error::Message(e.to_string()))?;
498 Ok(())
499 }
500
501 fn validate_event_and_extract_group_id(&self, event: &Event) -> Result<[u8; 32], Error> {
515 if event.kind != Kind::MlsGroupMessage {
516 return Err(Error::UnexpectedEvent {
517 expected: Kind::MlsGroupMessage,
518 received: event.kind,
519 });
520 }
521
522 let nostr_group_id_tag = event
523 .tags
524 .iter()
525 .find(|tag| tag.kind() == TagKind::h())
526 .ok_or(Error::Message("Group ID Tag not found".to_string()))?;
527
528 let nostr_group_id: [u8; 32] = hex::decode(
529 nostr_group_id_tag
530 .content()
531 .ok_or(Error::Message("Group ID Tag content not found".to_string()))?,
532 )
533 .map_err(|e| Error::Message(e.to_string()))?
534 .try_into()
535 .map_err(|_e| Error::Message("Failed to convert nostr group id to [u8; 32]".to_string()))?;
536
537 Ok(nostr_group_id)
538 }
539
540 fn load_group_and_decrypt_message(
556 &self,
557 nostr_group_id: [u8; 32],
558 event: &Event,
559 ) -> Result<(group_types::Group, MlsGroup, Vec<u8>), Error> {
560 let group = self
561 .storage()
562 .find_group_by_nostr_group_id(&nostr_group_id)
563 .map_err(|e| Error::Group(e.to_string()))?
564 .ok_or(Error::GroupNotFound)?;
565
566 let mls_group: MlsGroup = self
568 .load_mls_group(&group.mls_group_id)
569 .map_err(|e| Error::Group(e.to_string()))?
570 .ok_or(Error::GroupNotFound)?;
571
572 let message_bytes: Vec<u8> =
574 self.try_decrypt_with_recent_epochs(&mls_group, &event.content)?;
575
576 Ok((group, mls_group, message_bytes))
577 }
578
579 fn process_decrypted_message(
596 &self,
597 group: group_types::Group,
598 mls_group: &mut MlsGroup,
599 message_bytes: &[u8],
600 event: &Event,
601 ) -> Result<MessageProcessingResult, Error> {
602 match self.process_message_for_group(mls_group, message_bytes) {
603 Ok(ProcessedMessageContent::ApplicationMessage(application_message)) => {
604 Ok(MessageProcessingResult::ApplicationMessage(
605 self.process_application_message_for_group(group, event, application_message)?,
606 ))
607 }
608 Ok(ProcessedMessageContent::ProposalMessage(staged_proposal)) => {
609 Ok(MessageProcessingResult::Proposal(
610 self.process_proposal_message_for_group(mls_group, event, *staged_proposal)?,
611 ))
612 }
613 Ok(ProcessedMessageContent::StagedCommitMessage(staged_commit)) => {
614 self.process_commit_message_for_group(mls_group, event, *staged_commit)?;
615 Ok(MessageProcessingResult::Commit)
616 }
617 Ok(ProcessedMessageContent::ExternalJoinProposalMessage(_external_join_proposal)) => {
618 let processed_message = message_types::ProcessedMessage {
620 wrapper_event_id: event.id,
621 message_event_id: None,
622 processed_at: Timestamp::now(),
623 state: message_types::ProcessedMessageState::Processed,
624 failure_reason: None,
625 };
626
627 self.storage()
628 .save_processed_message(processed_message)
629 .map_err(|e| Error::Message(e.to_string()))?;
630
631 Ok(MessageProcessingResult::ExternalJoinProposal)
632 }
633 Err(e) => Err(e),
634 }
635 }
636
637 fn handle_message_processing_error(
654 &self,
655 error: Error,
656 event: &Event,
657 group: &group_types::Group,
658 ) -> Result<MessageProcessingResult, Error> {
659 match error {
660 Error::CannotDecryptOwnMessage => {
661 tracing::debug!(target: "nostr_mls::messages::process_message", "Cannot decrypt own message, checking for cached message");
662
663 let mut processed_message = self
664 .storage()
665 .find_processed_message_by_event_id(&event.id)
666 .map_err(|e| Error::Message(e.to_string()))?
667 .ok_or(Error::Message("Processed message not found".to_string()))?;
668
669 match processed_message.state {
672 message_types::ProcessedMessageState::Created => {
673 let message_event_id: EventId = processed_message
674 .message_event_id
675 .ok_or(Error::Message("Message event ID not found".to_string()))?;
676
677 let mut message = self
678 .get_message(&message_event_id)?
679 .ok_or(Error::Message("Message not found".to_string()))?;
680
681 message.state = message_types::MessageState::Processed;
682 self.storage()
683 .save_message(message)
684 .map_err(|e| Error::Message(e.to_string()))?;
685
686 processed_message.state = message_types::ProcessedMessageState::Processed;
687 self.storage()
688 .save_processed_message(processed_message.clone())
689 .map_err(|e| Error::Message(e.to_string()))?;
690
691 tracing::debug!(target: "nostr_mls::messages::process_message", "Updated state of own cached message");
692 let message = self
693 .get_message(&message_event_id)?
694 .ok_or(Error::MessageNotFound)?;
695 Ok(MessageProcessingResult::ApplicationMessage(message))
696 }
697 message_types::ProcessedMessageState::ProcessedCommit => {
698 tracing::debug!(target: "nostr_mls::messages::process_message", "Message already processed as a commit");
699
700 self.sync_group_metadata_from_mls(&group.mls_group_id)
704 .map_err(|e| {
705 Error::Message(format!("Failed to sync group metadata: {}", e))
706 })?;
707
708 Ok(MessageProcessingResult::Commit)
709 }
710 message_types::ProcessedMessageState::Processed
711 | message_types::ProcessedMessageState::Failed => {
712 tracing::debug!(target: "nostr_mls::messages::process_message", "Message cannot be processed (already processed or failed)");
713 Ok(MessageProcessingResult::Unprocessable)
714 }
715 }
716 }
717 Error::ProcessMessage(ProcessMessageError::ValidationError(
718 ValidationError::WrongEpoch,
719 )) => {
720 tracing::debug!(target: "nostr_mls::messages::process_message", "Epoch mismatch error, checking if this is our own commit");
722
723 if let Ok(Some(processed_message)) = self
724 .storage()
725 .find_processed_message_by_event_id(&event.id)
726 .map_err(|e| Error::Message(e.to_string()))
727 {
728 if processed_message.state
729 == message_types::ProcessedMessageState::ProcessedCommit
730 {
731 tracing::debug!(target: "nostr_mls::messages::process_message", "Found own commit with epoch mismatch, syncing group metadata");
732
733 self.sync_group_metadata_from_mls(&group.mls_group_id)
735 .map_err(|e| {
736 Error::Message(format!("Failed to sync group metadata: {}", e))
737 })?;
738
739 return Ok(MessageProcessingResult::Commit);
740 }
741 }
742
743 tracing::error!(target: "nostr_mls::messages::process_message", "Epoch mismatch for message that is not our own commit: {:?}", error);
745 let processed_message = message_types::ProcessedMessage {
746 wrapper_event_id: event.id,
747 message_event_id: None,
748 processed_at: Timestamp::now(),
749 state: message_types::ProcessedMessageState::Failed,
750 failure_reason: Some("Epoch mismatch".to_string()),
751 };
752 self.storage()
753 .save_processed_message(processed_message)
754 .map_err(|e| Error::Message(e.to_string()))?;
755
756 Ok(MessageProcessingResult::Unprocessable)
757 }
758 Error::ProcessMessage(ProcessMessageError::ValidationError(
759 ValidationError::WrongGroupId,
760 )) => {
761 tracing::error!(target: "nostr_mls::messages::process_message", "Group ID mismatch: {:?}", error);
762 let processed_message = message_types::ProcessedMessage {
763 wrapper_event_id: event.id,
764 message_event_id: None,
765 processed_at: Timestamp::now(),
766 state: message_types::ProcessedMessageState::Failed,
767 failure_reason: Some("Group ID mismatch".to_string()),
768 };
769 self.storage()
770 .save_processed_message(processed_message)
771 .map_err(|e| Error::Message(e.to_string()))?;
772
773 Ok(MessageProcessingResult::Unprocessable)
774 }
775 Error::ProcessMessage(ProcessMessageError::GroupStateError(
776 MlsGroupStateError::UseAfterEviction,
777 )) => {
778 tracing::error!(target: "nostr_mls::messages::process_message", "Attempted to use group after eviction: {:?}", error);
779 let processed_message = message_types::ProcessedMessage {
780 wrapper_event_id: event.id,
781 message_event_id: None,
782 processed_at: Timestamp::now(),
783 state: message_types::ProcessedMessageState::Failed,
784 failure_reason: Some("Use after eviction".to_string()),
785 };
786 self.storage()
787 .save_processed_message(processed_message)
788 .map_err(|e| Error::Message(e.to_string()))?;
789
790 Ok(MessageProcessingResult::Unprocessable)
791 }
792 _ => {
793 tracing::error!(target: "nostr_mls::messages::process_message", "Unexpected error processing message: {:?}", error);
794 let processed_message = message_types::ProcessedMessage {
795 wrapper_event_id: event.id,
796 message_event_id: None,
797 processed_at: Timestamp::now(),
798 state: message_types::ProcessedMessageState::Failed,
799 failure_reason: Some(error.to_string()),
800 };
801 self.storage()
802 .save_processed_message(processed_message)
803 .map_err(|e| Error::Message(e.to_string()))?;
804
805 Ok(MessageProcessingResult::Unprocessable)
806 }
807 }
808 }
809
810 fn try_decrypt_with_past_epochs(
828 &self,
829 mls_group: &MlsGroup,
830 encrypted_content: &str,
831 max_epoch_lookback: u64,
832 ) -> Result<Vec<u8>, Error> {
833 let group_id = mls_group.group_id();
834 let current_epoch: u64 = mls_group.epoch().as_u64();
835
836 let start_epoch: u64 = current_epoch.saturating_sub(1);
838 let end_epoch: u64 = start_epoch.saturating_sub(max_epoch_lookback);
839
840 for epoch in (end_epoch..=start_epoch).rev() {
841 tracing::debug!(
842 target: "nostr_mls::messages::try_decrypt_with_recent_epochs",
843 "Trying to decrypt with epoch {} for group {:?}",
844 epoch,
845 group_id
846 );
847
848 if let Ok(Some(secret)) = self
850 .storage()
851 .get_group_exporter_secret(group_id, epoch)
852 .map_err(|e| Error::Group(e.to_string()))
853 {
854 match util::decrypt_with_exporter_secret(&secret, encrypted_content) {
856 Ok(decrypted_bytes) => {
857 tracing::debug!(
858 target: "nostr_mls::messages::try_decrypt_with_recent_epochs",
859 "Successfully decrypted message with epoch {} for group {:?}",
860 epoch,
861 group_id
862 );
863 return Ok(decrypted_bytes);
864 }
865 Err(e) => {
866 tracing::trace!(
867 target: "nostr_mls::messages::try_decrypt_with_recent_epochs",
868 "Failed to decrypt with epoch {}: {:?}",
869 epoch,
870 e
871 );
872 }
874 }
875 } else {
876 tracing::trace!(
877 target: "nostr_mls::messages::try_decrypt_with_recent_epochs",
878 "No exporter secret found for epoch {} in group {:?}",
879 epoch,
880 group_id
881 );
882 }
883 }
884
885 Err(Error::Message(format!(
886 "Failed to decrypt message with any exporter secret from epochs {} to {} for group {:?}",
887 end_epoch, start_epoch, group_id
888 )))
889 }
890
891 fn try_decrypt_with_recent_epochs(
893 &self,
894 mls_group: &MlsGroup,
895 encrypted_content: &str,
896 ) -> Result<Vec<u8>, Error> {
897 let secret = self.exporter_secret(mls_group.group_id())?;
899
900 match util::decrypt_with_exporter_secret(&secret, encrypted_content) {
902 Ok(decrypted_bytes) => {
903 tracing::debug!(
904 "Successfully decrypted message with current exporter secret for group {:?}",
905 mls_group.group_id()
906 );
907 Ok(decrypted_bytes)
908 }
909 Err(_) => {
911 tracing::debug!("Failed to decrypt message with current exporter secret. Trying with past ones.");
912
913 self.try_decrypt_with_past_epochs(
915 mls_group,
916 encrypted_content,
917 DEFAULT_EPOCH_LOOKBACK,
918 )
919 }
920 }
921 }
922
923 pub fn process_message(&self, event: &Event) -> Result<MessageProcessingResult, Error> {
941 let nostr_group_id = self.validate_event_and_extract_group_id(event)?;
943
944 let (group, mut mls_group, message_bytes) =
946 self.load_group_and_decrypt_message(nostr_group_id, event)?;
947
948 match self.process_decrypted_message(group.clone(), &mut mls_group, &message_bytes, event) {
950 Ok(result) => Ok(result),
951 Err(error) => {
952 self.handle_message_processing_error(error, event, &group)
954 }
955 }
956 }
957}
958
959#[cfg(test)]
960mod tests {
961 use nostr::{EventBuilder, Keys, Kind, PublicKey, Tag, TagKind};
962
963 use super::*;
964 use crate::test_util::*;
965 use crate::tests::create_test_nostr_mls;
966
967 #[test]
968 fn test_get_message_not_found() {
969 let nostr_mls = create_test_nostr_mls();
970 let non_existent_event_id = EventId::all_zeros();
971
972 let result = nostr_mls.get_message(&non_existent_event_id);
973 assert!(result.is_ok());
974 assert!(result.unwrap().is_none());
975 }
976
977 #[test]
978 fn test_get_messages_empty_group() {
979 let nostr_mls = create_test_nostr_mls();
980 let (creator, members, admins) = create_test_group_members();
981 let group_id = create_test_group(&nostr_mls, &creator, &members, &admins);
982
983 let messages = nostr_mls
984 .get_messages(&group_id)
985 .expect("Failed to get messages");
986 assert!(messages.is_empty());
987 }
988
989 #[test]
990 fn test_create_message_success() {
991 let nostr_mls = create_test_nostr_mls();
992 let (creator, members, admins) = create_test_group_members();
993 let group_id = create_test_group(&nostr_mls, &creator, &members, &admins);
994
995 let mut rumor = create_test_rumor(&creator, "Hello, world!");
997 let rumor_id = rumor.id();
998
999 let result = nostr_mls.create_message(&group_id, rumor);
1000 assert!(result.is_ok());
1001
1002 let event = result.unwrap();
1003 assert_eq!(event.kind, Kind::MlsGroupMessage);
1004
1005 let stored_message = nostr_mls
1007 .get_message(&rumor_id)
1008 .expect("Failed to get message")
1009 .expect("Message should exist");
1010
1011 assert_eq!(stored_message.id, rumor_id);
1012 assert_eq!(stored_message.content, "Hello, world!");
1013 assert_eq!(stored_message.state, message_types::MessageState::Created);
1014 assert_eq!(stored_message.wrapper_event_id, event.id);
1015 }
1016
1017 #[test]
1018 fn test_create_message_group_not_found() {
1019 let nostr_mls = create_test_nostr_mls();
1020 let creator = Keys::generate();
1021 let rumor = create_test_rumor(&creator, "Hello, world!");
1022 let non_existent_group_id = GroupId::from_slice(&[1, 2, 3, 4]);
1023
1024 let result = nostr_mls.create_message(&non_existent_group_id, rumor);
1025 assert!(result.is_err());
1026 assert!(matches!(result.unwrap_err(), Error::GroupNotFound));
1027 }
1028
1029 #[test]
1030 fn test_create_message_updates_group_metadata() {
1031 let nostr_mls = create_test_nostr_mls();
1032 let (creator, members, admins) = create_test_group_members();
1033 let group_id = create_test_group(&nostr_mls, &creator, &members, &admins);
1034
1035 let initial_group = nostr_mls
1037 .get_group(&group_id)
1038 .expect("Failed to get group")
1039 .expect("Group should exist");
1040 assert!(initial_group.last_message_at.is_none());
1041 assert!(initial_group.last_message_id.is_none());
1042
1043 let mut rumor = create_test_rumor(&creator, "Hello, world!");
1045 let rumor_id = rumor.id();
1046 let rumor_timestamp = rumor.created_at;
1047
1048 let _event = nostr_mls
1049 .create_message(&group_id, rumor)
1050 .expect("Failed to create message");
1051
1052 let updated_group = nostr_mls
1054 .get_group(&group_id)
1055 .expect("Failed to get group")
1056 .expect("Group should exist");
1057
1058 assert_eq!(updated_group.last_message_at, Some(rumor_timestamp));
1059 assert_eq!(updated_group.last_message_id, Some(rumor_id));
1060 }
1061
1062 #[test]
1063 fn test_process_message_invalid_kind() {
1064 let nostr_mls = create_test_nostr_mls();
1065 let creator = Keys::generate();
1066
1067 let event = EventBuilder::new(Kind::TextNote, "test content")
1069 .sign_with_keys(&creator)
1070 .expect("Failed to sign event");
1071
1072 let result = nostr_mls.process_message(&event);
1073 assert!(result.is_err());
1074 assert!(matches!(result.unwrap_err(), Error::UnexpectedEvent { .. }));
1075 }
1076
1077 #[test]
1078 fn test_process_message_missing_group_id_tag() {
1079 let nostr_mls = create_test_nostr_mls();
1080 let creator = Keys::generate();
1081
1082 let event = EventBuilder::new(Kind::MlsGroupMessage, "test content")
1084 .sign_with_keys(&creator)
1085 .expect("Failed to sign event");
1086
1087 let result = nostr_mls.process_message(&event);
1088 assert!(result.is_err());
1089 assert!(matches!(result.unwrap_err(), Error::Message(_)));
1090 }
1091
1092 #[test]
1093 fn test_process_message_group_not_found() {
1094 let nostr_mls = create_test_nostr_mls();
1095 let creator = Keys::generate();
1096
1097 let fake_group_id = hex::encode([1u8; 32]);
1099 let tag = Tag::custom(TagKind::h(), [fake_group_id]);
1100
1101 let event = EventBuilder::new(Kind::MlsGroupMessage, "encrypted_content")
1102 .tag(tag)
1103 .sign_with_keys(&creator)
1104 .expect("Failed to sign event");
1105
1106 let result = nostr_mls.process_message(&event);
1107 assert!(result.is_err());
1108 assert!(matches!(result.unwrap_err(), Error::GroupNotFound));
1109 }
1110
1111 #[test]
1112 fn test_message_state_tracking() {
1113 let nostr_mls = create_test_nostr_mls();
1114 let (creator, members, admins) = create_test_group_members();
1115 let group_id = create_test_group(&nostr_mls, &creator, &members, &admins);
1116
1117 let mut rumor = create_test_rumor(&creator, "Test message state");
1119 let rumor_id = rumor.id();
1120
1121 let event = nostr_mls
1122 .create_message(&group_id, rumor)
1123 .expect("Failed to create message");
1124
1125 let message = nostr_mls
1127 .get_message(&rumor_id)
1128 .expect("Failed to get message")
1129 .expect("Message should exist");
1130
1131 assert_eq!(message.state, message_types::MessageState::Created);
1132
1133 let processed_message = nostr_mls
1135 .storage()
1136 .find_processed_message_by_event_id(&event.id)
1137 .expect("Failed to get processed message")
1138 .expect("Processed message should exist");
1139
1140 assert_eq!(
1141 processed_message.state,
1142 message_types::ProcessedMessageState::Created
1143 );
1144 assert_eq!(processed_message.message_event_id, Some(rumor_id));
1145 assert_eq!(processed_message.wrapper_event_id, event.id);
1146 }
1147
1148 #[test]
1149 fn test_get_messages_for_group() {
1150 let nostr_mls = create_test_nostr_mls();
1151 let (creator, members, admins) = create_test_group_members();
1152 let group_id = create_test_group(&nostr_mls, &creator, &members, &admins);
1153
1154 let rumor1 = create_test_rumor(&creator, "First message");
1156 let rumor2 = create_test_rumor(&creator, "Second message");
1157
1158 let _event1 = nostr_mls
1159 .create_message(&group_id, rumor1)
1160 .expect("Failed to create first message");
1161 let _event2 = nostr_mls
1162 .create_message(&group_id, rumor2)
1163 .expect("Failed to create second message");
1164
1165 let messages = nostr_mls
1167 .get_messages(&group_id)
1168 .expect("Failed to get messages");
1169
1170 assert_eq!(messages.len(), 2);
1171
1172 let contents: Vec<&str> = messages.iter().map(|m| m.content.as_str()).collect();
1174 assert!(contents.contains(&"First message"));
1175 assert!(contents.contains(&"Second message"));
1176
1177 for message in &messages {
1179 assert_eq!(message.mls_group_id, group_id);
1180 }
1181 }
1182
1183 #[test]
1184 fn test_message_processing_result_variants() {
1185 let dummy_message = message_types::Message {
1187 id: EventId::all_zeros(),
1188 pubkey: PublicKey::from_hex(
1189 "8a9de562cbbed225b6ea0118dd3997a02df92c0bffd2224f71081a7450c3e549",
1190 )
1191 .unwrap(),
1192 kind: Kind::TextNote,
1193 mls_group_id: GroupId::from_slice(&[1, 2, 3, 4]),
1194 created_at: Timestamp::now(),
1195 content: "Test".to_string(),
1196 tags: Tags::new(),
1197 event: EventBuilder::new(Kind::TextNote, "Test").build(
1198 PublicKey::from_hex(
1199 "8a9de562cbbed225b6ea0118dd3997a02df92c0bffd2224f71081a7450c3e549",
1200 )
1201 .unwrap(),
1202 ),
1203 wrapper_event_id: EventId::all_zeros(),
1204 state: message_types::MessageState::Processed,
1205 };
1206
1207 let app_result = MessageProcessingResult::ApplicationMessage(dummy_message);
1208 let commit_result = MessageProcessingResult::Commit;
1209 let external_join_result = MessageProcessingResult::ExternalJoinProposal;
1210 let unprocessable_result = MessageProcessingResult::Unprocessable;
1211
1212 match app_result {
1214 MessageProcessingResult::ApplicationMessage(_) => {}
1215 _ => panic!("Expected ApplicationMessage variant"),
1216 }
1217
1218 match commit_result {
1219 MessageProcessingResult::Commit => {}
1220 _ => panic!("Expected Commit variant"),
1221 }
1222
1223 match external_join_result {
1224 MessageProcessingResult::ExternalJoinProposal => {}
1225 _ => panic!("Expected ExternalJoinProposal variant"),
1226 }
1227
1228 match unprocessable_result {
1229 MessageProcessingResult::Unprocessable => {}
1230 _ => panic!("Expected Unprocessable variant"),
1231 }
1232 }
1233
1234 #[test]
1235 fn test_message_content_preservation() {
1236 let nostr_mls = create_test_nostr_mls();
1237 let (creator, members, admins) = create_test_group_members();
1238 let group_id = create_test_group(&nostr_mls, &creator, &members, &admins);
1239
1240 let test_cases = vec![
1242 "Simple text message",
1243 "Message with emojis 🚀 🎉 ✨",
1244 "Message with\nmultiple\nlines",
1245 "Message with special chars: !@#$%^&*()",
1246 "Minimal content",
1247 ];
1248
1249 for content in test_cases {
1250 let mut rumor = create_test_rumor(&creator, content);
1251 let rumor_id = rumor.id();
1252
1253 let _event = nostr_mls
1254 .create_message(&group_id, rumor)
1255 .expect("Failed to create message");
1256
1257 let stored_message = nostr_mls
1258 .get_message(&rumor_id)
1259 .expect("Failed to get message")
1260 .expect("Message should exist");
1261
1262 assert_eq!(stored_message.content, content);
1263 assert_eq!(stored_message.pubkey, creator.public_key());
1264 }
1265 }
1266
1267 #[test]
1268 fn test_create_message_ensures_rumor_id() {
1269 let nostr_mls = create_test_nostr_mls();
1270 let (creator, members, admins) = create_test_group_members();
1271 let group_id = create_test_group(&nostr_mls, &creator, &members, &admins);
1272
1273 let rumor = create_test_rumor(&creator, "Test message");
1275
1276 let result = nostr_mls.create_message(&group_id, rumor);
1277 assert!(result.is_ok());
1278
1279 let event = result.unwrap();
1281 let messages = nostr_mls
1282 .get_messages(&group_id)
1283 .expect("Failed to get messages");
1284
1285 assert_eq!(messages.len(), 1);
1286 assert_eq!(messages[0].wrapper_event_id, event.id);
1287 }
1288
1289 #[test]
1290 fn test_merge_pending_commit_syncs_group_metadata() {
1291 let nostr_mls = create_test_nostr_mls();
1292
1293 let creator_keys = Keys::generate();
1295 let member1_keys = Keys::generate();
1296 let member2_keys = Keys::generate();
1297
1298 let creator_pk = creator_keys.public_key();
1299 let member1_pk = member1_keys.public_key();
1300
1301 let members = vec![member1_keys.clone(), member2_keys.clone()];
1302 let admins = vec![creator_pk, member1_pk]; let group_id = create_test_group(&nostr_mls, &creator_keys, &members, &admins);
1306
1307 let initial_group = nostr_mls
1309 .get_group(&group_id)
1310 .expect("Failed to get initial group")
1311 .expect("Initial group should exist");
1312
1313 let initial_epoch = initial_group.epoch;
1314 let initial_name = initial_group.name.clone();
1315
1316 let new_name = "Updated Group Name via MLS Commit".to_string();
1318 let update = crate::groups::NostrGroupDataUpdate::new().name(new_name.clone());
1319 let _update_result = nostr_mls
1320 .update_group_data(&group_id, update)
1321 .expect("Failed to update group name");
1322
1323 let pre_merge_group = nostr_mls
1325 .get_group(&group_id)
1326 .expect("Failed to get pre-merge group")
1327 .expect("Pre-merge group should exist");
1328
1329 assert_eq!(
1330 pre_merge_group.name, initial_name,
1331 "Stored group name should still be old before merge"
1332 );
1333 assert_eq!(
1334 pre_merge_group.epoch, initial_epoch,
1335 "Stored group epoch should still be old before merge"
1336 );
1337
1338 let pre_merge_mls_group = nostr_mls
1340 .load_mls_group(&group_id)
1341 .expect("Failed to load pre-merge MLS group")
1342 .expect("Pre-merge MLS group should exist");
1343
1344 let pre_merge_mls_epoch = pre_merge_mls_group.epoch().as_u64();
1345 assert_eq!(
1346 pre_merge_mls_epoch, initial_epoch,
1347 "MLS group epoch should not advance until commit is merged"
1348 );
1349
1350 nostr_mls
1352 .merge_pending_commit(&group_id)
1353 .expect("Failed to merge pending commit");
1354
1355 let post_merge_group = nostr_mls
1357 .get_group(&group_id)
1358 .expect("Failed to get post-merge group")
1359 .expect("Post-merge group should exist");
1360
1361 assert!(
1363 post_merge_group.epoch > initial_epoch,
1364 "Stored group epoch should advance after merge"
1365 );
1366
1367 let post_merge_mls_group = nostr_mls
1369 .load_mls_group(&group_id)
1370 .expect("Failed to load post-merge MLS group")
1371 .expect("Post-merge MLS group should exist");
1372
1373 let group_data =
1374 super::extension::NostrGroupDataExtension::from_group(&post_merge_mls_group)
1375 .expect("Failed to get group data extension");
1376
1377 assert_eq!(
1378 post_merge_group.name, group_data.name,
1379 "Stored group name should match extension after merge"
1380 );
1381 assert_eq!(
1382 post_merge_group.name, new_name,
1383 "Stored group name should be updated after merge"
1384 );
1385 assert_eq!(
1386 post_merge_group.description, group_data.description,
1387 "Stored group description should match extension"
1388 );
1389 assert_eq!(
1390 post_merge_group.admin_pubkeys, group_data.admins,
1391 "Stored group admins should match extension"
1392 );
1393
1394 let mut manually_desync_group = post_merge_group.clone();
1396 manually_desync_group.name = "Manually Corrupted Name".to_string();
1397 manually_desync_group.epoch = initial_epoch;
1398 nostr_mls
1399 .storage()
1400 .save_group(manually_desync_group)
1401 .expect("Failed to save corrupted group");
1402
1403 let corrupted_group = nostr_mls
1405 .get_group(&group_id)
1406 .expect("Failed to get corrupted group")
1407 .expect("Corrupted group should exist");
1408
1409 assert_eq!(
1410 corrupted_group.name, "Manually Corrupted Name",
1411 "Group should be manually corrupted"
1412 );
1413 assert_eq!(
1414 corrupted_group.epoch, initial_epoch,
1415 "Group epoch should be manually corrupted"
1416 );
1417
1418 nostr_mls
1420 .sync_group_metadata_from_mls(&group_id)
1421 .expect("Failed to sync group metadata");
1422
1423 let re_synced_group = nostr_mls
1425 .get_group(&group_id)
1426 .expect("Failed to get re-synced group")
1427 .expect("Re-synced group should exist");
1428
1429 assert_eq!(
1430 re_synced_group.name, new_name,
1431 "Group name should be re-synced"
1432 );
1433 assert!(
1434 re_synced_group.epoch > initial_epoch,
1435 "Group epoch should be re-synced"
1436 );
1437 assert_eq!(
1438 re_synced_group.admin_pubkeys, group_data.admins,
1439 "Group admins should be re-synced"
1440 );
1441 }
1442
1443 #[test]
1444 fn test_processing_own_commit_syncs_group_metadata() {
1445 let nostr_mls = create_test_nostr_mls();
1446
1447 let creator_keys = Keys::generate();
1449 let member1_keys = Keys::generate();
1450 let member2_keys = Keys::generate();
1451
1452 let creator_pk = creator_keys.public_key();
1453 let member1_pk = member1_keys.public_key();
1454
1455 let members = vec![member1_keys.clone(), member2_keys.clone()];
1456 let admins = vec![creator_pk, member1_pk];
1457
1458 let group_id = create_test_group(&nostr_mls, &creator_keys, &members, &admins);
1459
1460 let initial_group = nostr_mls
1462 .get_group(&group_id)
1463 .expect("Failed to get initial group")
1464 .expect("Initial group should exist");
1465
1466 let initial_epoch = initial_group.epoch;
1467
1468 let new_name = "Updated Name for Own Commit Test".to_string();
1470 let update = crate::groups::NostrGroupDataUpdate::new().name(new_name.clone());
1471 let update_result = nostr_mls
1472 .update_group_data(&group_id, update)
1473 .expect("Failed to update group name");
1474
1475 nostr_mls
1476 .merge_pending_commit(&group_id)
1477 .expect("Failed to merge pending commit");
1478
1479 let commit_event_id = update_result.evolution_event.id;
1481 let processed_message = nostr_mls
1482 .storage()
1483 .find_processed_message_by_event_id(&commit_event_id)
1484 .expect("Failed to find processed message")
1485 .expect("Processed message should exist");
1486
1487 assert_eq!(
1488 processed_message.state,
1489 message_types::ProcessedMessageState::ProcessedCommit
1490 );
1491
1492 let mut corrupted_group = initial_group.clone();
1494 corrupted_group.name = "Corrupted Name".to_string();
1495 corrupted_group.epoch = initial_epoch;
1496 nostr_mls
1497 .storage()
1498 .save_group(corrupted_group)
1499 .expect("Failed to save corrupted group");
1500
1501 let out_of_sync_group = nostr_mls
1503 .get_group(&group_id)
1504 .expect("Failed to get out of sync group")
1505 .expect("Out of sync group should exist");
1506
1507 assert_eq!(out_of_sync_group.name, "Corrupted Name");
1508 assert_eq!(out_of_sync_group.epoch, initial_epoch);
1509
1510 let message_result = nostr_mls
1512 .process_message(&update_result.evolution_event)
1513 .expect("Failed to process own commit message");
1514
1515 assert!(matches!(message_result, MessageProcessingResult::Commit));
1517
1518 let synced_group = nostr_mls
1520 .get_group(&group_id)
1521 .expect("Failed to get synced group")
1522 .expect("Synced group should exist");
1523
1524 assert_eq!(
1525 synced_group.name, new_name,
1526 "Processing own commit should sync group name"
1527 );
1528 assert!(
1529 synced_group.epoch > initial_epoch,
1530 "Processing own commit should sync group epoch"
1531 );
1532
1533 let mls_group = nostr_mls
1535 .load_mls_group(&group_id)
1536 .expect("Failed to load MLS group")
1537 .expect("MLS group should exist");
1538
1539 assert_eq!(
1540 synced_group.epoch,
1541 mls_group.epoch().as_u64(),
1542 "Stored and MLS group epochs should match"
1543 );
1544
1545 let group_data = super::extension::NostrGroupDataExtension::from_group(&mls_group)
1546 .expect("Failed to get group data extension");
1547
1548 assert_eq!(
1549 synced_group.name, group_data.name,
1550 "Stored group name should match extension"
1551 );
1552 assert_eq!(
1553 synced_group.admin_pubkeys, group_data.admins,
1554 "Stored group admins should match extension"
1555 );
1556 }
1557}