use mdk_storage_traits::MdkStorageProvider;
use mdk_storage_traits::groups::types as group_types;
use mdk_storage_traits::messages::types as message_types;
use nostr::{Event, EventId, JsonUtil, Timestamp, UnsignedEvent};
use openmls::prelude::ApplicationMessage;
use crate::MDK;
use super::Result;
impl<Storage> MDK<Storage>
where
Storage: MdkStorageProvider,
{
pub(super) fn process_application_message(
&self,
mut group: group_types::Group,
mls_epoch: u64,
event: &Event,
application_message: ApplicationMessage,
sender_credential: openmls::credentials::Credential,
) -> Result<message_types::Message> {
let bytes = application_message.into_bytes();
let mut rumor: UnsignedEvent = UnsignedEvent::from_json(bytes)?;
self.verify_rumor_author(&rumor.pubkey, sender_credential)?;
let rumor_id: EventId = rumor.id();
let processed_message = super::create_processed_message_record(
event.id,
Some(rumor_id),
Some(mls_epoch),
Some(group.mls_group_id.clone()),
message_types::ProcessedMessageState::Processed,
None,
);
let now = Timestamp::now();
let message = message_types::Message {
id: rumor_id,
pubkey: rumor.pubkey,
kind: rumor.kind,
mls_group_id: group.mls_group_id.clone(),
created_at: rumor.created_at,
processed_at: now,
content: rumor.content.clone(),
tags: rumor.tags.clone(),
event: rumor.clone(),
wrapper_event_id: event.id,
state: message_types::MessageState::Processed,
epoch: Some(mls_epoch),
};
self.save_message_record(message.clone())?;
self.save_processed_message_record(processed_message.clone())?;
if group.update_last_message_if_newer(&message) {
self.save_group_record(group)?;
}
tracing::debug!(
target: "mdk_core::messages::process_message",
"Processed application message"
);
Ok(message)
}
}
#[cfg(test)]
mod tests {
use mdk_storage_traits::messages::MessageStorage;
use mdk_storage_traits::messages::types as message_types;
use nostr::{Keys, Kind};
use crate::messages::MessageProcessingResult;
use crate::test_util::*;
use crate::tests::create_test_mdk;
#[test]
fn test_message_state_tracking() {
let mdk = create_test_mdk();
let (creator, members, admins) = create_test_group_members();
let group_id = create_test_group(&mdk, &creator, &members, &admins);
let mut rumor = create_test_rumor(&creator, "Test message state");
let rumor_id = rumor.id();
let event = mdk
.create_message(&group_id, rumor, None)
.expect("Failed to create message");
let message = mdk
.get_message(&group_id, &rumor_id)
.expect("Failed to get message")
.expect("Message should exist");
assert_eq!(message.state, message_types::MessageState::Created);
let processed_message = mdk
.storage()
.find_processed_message_by_event_id(&event.id)
.expect("Failed to get processed message")
.expect("Processed message should exist");
assert_eq!(
processed_message.state,
message_types::ProcessedMessageState::Created
);
assert_eq!(processed_message.message_event_id, Some(rumor_id));
assert_eq!(processed_message.wrapper_event_id, event.id);
}
#[test]
fn test_message_state_transitions() {
let mdk = create_test_mdk();
let (creator, members, admins) = create_test_group_members();
let group_id = create_test_group(&mdk, &creator, &members, &admins);
let mut rumor = create_test_rumor(&creator, "Test message");
let rumor_id = rumor.id();
let _event = mdk
.create_message(&group_id, rumor, None)
.expect("Failed to create message");
let message = mdk
.get_message(&group_id, &rumor_id)
.expect("Failed to get message")
.expect("Message should exist");
assert_eq!(
message.state,
message_types::MessageState::Created,
"Initial state should be Created"
);
assert_eq!(message.content, "Test message");
assert_eq!(message.pubkey, creator.public_key());
}
#[test]
fn test_message_from_non_member() {
let creator_mdk = create_test_mdk();
let (creator, members, admins) = create_test_group_members();
let group_id = create_test_group(&creator_mdk, &creator, &members, &admins);
let non_member = Keys::generate();
let rumor = create_test_rumor(&non_member, "I'm not in this group");
let non_member_mdk = create_test_mdk();
let result = non_member_mdk.create_message(&group_id, rumor, None);
assert!(
result.is_err(),
"Non-member should not be able to create messages"
);
}
#[test]
fn test_message_from_non_member_rejected() {
let alice_keys = Keys::generate();
let bob_keys = Keys::generate();
let charlie_keys = Keys::generate();
let alice_mdk = create_test_mdk();
let bob_mdk = create_test_mdk();
let charlie_mdk = create_test_mdk();
let admins = vec![alice_keys.public_key()];
let bob_key_package = create_key_package_event(&bob_mdk, &bob_keys);
let create_result = alice_mdk
.create_group(
&alice_keys.public_key(),
vec![bob_key_package],
create_nostr_group_config_data(admins),
)
.expect("Alice should be able to create group");
let group_id = create_result.group.mls_group_id.clone();
alice_mdk
.merge_pending_commit(&group_id)
.expect("Failed to merge Alice's create commit");
let bob_welcome_rumor = &create_result.welcome_rumors[0];
let bob_welcome = bob_mdk
.process_welcome(&nostr::EventId::all_zeros(), bob_welcome_rumor)
.expect("Bob should be able to process welcome");
bob_mdk
.accept_welcome(&bob_welcome)
.expect("Bob should be able to accept welcome");
let members = alice_mdk
.get_members(&group_id)
.expect("Failed to get members");
assert_eq!(members.len(), 2, "Group should have 2 members");
assert!(
!members.contains(&charlie_keys.public_key()),
"Charlie should not be a member"
);
let charlie_rumor = create_test_rumor(&charlie_keys, "Unauthorized message");
let charlie_message_result = charlie_mdk.create_message(&group_id, charlie_rumor, None);
assert!(
charlie_message_result.is_err(),
"Non-member should not be able to create message for group"
);
assert!(
matches!(charlie_message_result, Err(crate::Error::GroupNotFound)),
"Should return GroupNotFound error for non-member"
);
let final_members = alice_mdk
.get_members(&group_id)
.expect("Failed to get members");
assert_eq!(
final_members.len(),
2,
"Member count should remain unchanged"
);
}
#[test]
fn test_multi_client_message_synchronization() {
let alice_keys = Keys::generate();
let bob_keys = Keys::generate();
let alice_mdk = create_test_mdk();
let bob_mdk = create_test_mdk();
let admins = vec![alice_keys.public_key(), bob_keys.public_key()];
let bob_key_package = create_key_package_event(&bob_mdk, &bob_keys);
let create_result = alice_mdk
.create_group(
&alice_keys.public_key(),
vec![bob_key_package],
create_nostr_group_config_data(admins),
)
.expect("Alice should be able to create group");
let group_id = create_result.group.mls_group_id.clone();
alice_mdk
.merge_pending_commit(&group_id)
.expect("Failed to merge Alice's create commit");
let bob_welcome_rumor = &create_result.welcome_rumors[0];
let bob_welcome = bob_mdk
.process_welcome(&nostr::EventId::all_zeros(), bob_welcome_rumor)
.expect("Bob should be able to process welcome");
bob_mdk
.accept_welcome(&bob_welcome)
.expect("Bob should be able to accept welcome");
assert_eq!(
group_id, bob_welcome.mls_group_id,
"Alice and Bob should have the same group ID"
);
let rumor1 = create_test_rumor(&alice_keys, "Hello from Alice");
let msg_event1 = alice_mdk
.create_message(&group_id, rumor1, None)
.expect("Alice should be able to send message");
assert_eq!(msg_event1.kind, Kind::MlsGroupMessage);
let bob_process1 = bob_mdk
.process_message(&msg_event1)
.expect("Bob should be able to process Alice's message");
match bob_process1 {
MessageProcessingResult::ApplicationMessage(msg) => {
assert_eq!(msg.content, "Hello from Alice");
}
_ => panic!("Expected ApplicationMessage but got different result type"),
}
let update_result = alice_mdk
.self_update(&group_id)
.expect("Alice should be able to create update");
let _alice_process_update = alice_mdk
.process_message(&update_result.evolution_event)
.expect("Alice should process her update");
alice_mdk
.merge_pending_commit(&group_id)
.expect("Alice should merge update");
let _bob_process_update = bob_mdk
.process_message(&update_result.evolution_event)
.expect("Bob should process Alice's update");
let rumor2 = create_test_rumor(&alice_keys, "Message in epoch 1");
let msg_event2 = alice_mdk
.create_message(&group_id, rumor2, None)
.expect("Alice should send message in new epoch");
let bob_process2 = bob_mdk
.process_message(&msg_event2)
.expect("Bob should process message from epoch 1");
match bob_process2 {
MessageProcessingResult::ApplicationMessage(msg) => {
assert_eq!(msg.content, "Message in epoch 1");
}
_ => panic!("Expected ApplicationMessage but got different result type"),
}
let rumor3 = create_test_rumor(&bob_keys, "Hello from Bob");
let msg_event3 = bob_mdk
.create_message(&group_id, rumor3, None)
.expect("Bob should be able to send message");
let alice_process3 = alice_mdk
.process_message(&msg_event3)
.expect("Alice should process Bob's message");
match alice_process3 {
MessageProcessingResult::ApplicationMessage(msg) => {
assert_eq!(msg.content, "Hello from Bob");
}
_ => panic!("Expected ApplicationMessage but got different result type"),
}
let alice_final_epoch = alice_mdk
.get_group(&group_id)
.expect("Failed to get Alice's group")
.expect("Alice's group should exist")
.epoch;
let bob_final_epoch = bob_mdk
.get_group(&group_id)
.expect("Failed to get Bob's group")
.expect("Bob's group should exist")
.epoch;
assert_eq!(
alice_final_epoch, bob_final_epoch,
"Both clients should be in the same epoch"
);
let alice_messages = alice_mdk
.get_messages(&group_id, None)
.expect("Failed to get Alice's messages");
let bob_messages = bob_mdk
.get_messages(&group_id, None)
.expect("Failed to get Bob's messages");
assert_eq!(alice_messages.len(), 3, "Alice should have 3 messages");
assert_eq!(bob_messages.len(), 3, "Bob should have 3 messages");
let alice_contents: Vec<&str> = alice_messages.iter().map(|m| m.content.as_str()).collect();
let bob_contents: Vec<&str> = bob_messages.iter().map(|m| m.content.as_str()).collect();
assert!(alice_contents.contains(&"Hello from Alice"));
assert!(alice_contents.contains(&"Message in epoch 1"));
assert!(alice_contents.contains(&"Hello from Bob"));
assert!(bob_contents.contains(&"Hello from Alice"));
assert!(bob_contents.contains(&"Message in epoch 1"));
assert!(bob_contents.contains(&"Hello from Bob"));
}
}