From d6af8d25267a4d0c4ea08d34a4b553e12a598391 Mon Sep 17 00:00:00 2001 From: iequidoo Date: Sun, 20 Jul 2025 15:56:32 -0300 Subject: [PATCH] feat: mimefactory: Order message recipients by time of addition (#6872) Sort recipients by `add_timestamp DESC` so that if the group is large and there are multiple SMTP messages, a newly added member receives the member addition message earlier and has gossiped keys of other members (otherwise the new member may receive messages from other members earlier and fail to verify them). --- src/mimefactory.rs | 7 ++++- src/mimefactory/mimefactory_tests.rs | 39 ++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/src/mimefactory.rs b/src/mimefactory.rs index 961290d8c..0d971bf24 100644 --- a/src/mimefactory.rs +++ b/src/mimefactory.rs @@ -253,6 +253,10 @@ impl MimeFactory { let mut missing_key_addresses = BTreeSet::new(); context .sql + // Sort recipients by `add_timestamp DESC` so that if the group is large and there + // are multiple SMTP messages, a newly added member receives the member addition + // message earlier and has gossiped keys of other members (otherwise the new member + // may receive messages from other members earlier and fail to verify them). .query_map( "SELECT c.authname, @@ -266,7 +270,8 @@ impl MimeFactory { LEFT JOIN contacts c ON cc.contact_id=c.id LEFT JOIN public_keys k ON k.fingerprint=c.fingerprint WHERE cc.chat_id=? - AND (cc.contact_id>9 OR (cc.contact_id=1 AND ?))", + AND (cc.contact_id>9 OR (cc.contact_id=1 AND ?)) + ORDER BY cc.add_timestamp DESC", (msg.chat_id, chat.typ == Chattype::Group), |row| { let authname: String = row.get(0)?; diff --git a/src/mimefactory/mimefactory_tests.rs b/src/mimefactory/mimefactory_tests.rs index 95a1e41a5..49fbdc228 100644 --- a/src/mimefactory/mimefactory_tests.rs +++ b/src/mimefactory/mimefactory_tests.rs @@ -2,6 +2,7 @@ use deltachat_contact_tools::ContactAddress; use mail_builder::headers::Header; use mailparse::{MailHeaderMap, addrparse_header}; use std::str; +use std::time::Duration; use super::*; use crate::chat::{ @@ -16,6 +17,7 @@ use crate::message; use crate::mimeparser::MimeMessage; use crate::receive_imf::receive_imf; use crate::test_utils::{TestContext, TestContextManager, get_chat_msg}; +use crate::tools::SystemTime; fn render_email_address(display_name: &str, addr: &str) -> String { let mut output = Vec::::new(); @@ -867,6 +869,43 @@ async fn test_dont_remove_self() -> Result<()> { Ok(()) } +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn test_new_member_is_first_recipient() -> Result<()> { + let mut tcm = TestContextManager::new(); + let alice = &tcm.alice().await; + let bob = &tcm.bob().await; + let charlie = &tcm.charlie().await; + + let bob_id = alice.add_or_lookup_contact_id(bob).await; + let charlie_id = alice.add_or_lookup_contact_id(charlie).await; + + let group = alice + .create_group_with_members(ProtectionStatus::Unprotected, "Group", &[bob]) + .await; + alice.send_text(group, "Hi! I created a group.").await; + + SystemTime::shift(Duration::from_secs(60)); + add_contact_to_chat(alice, group, charlie_id).await?; + let sent_msg = alice.pop_sent_msg().await; + assert!( + sent_msg + .recipients + .starts_with(&charlie.get_config(Config::Addr).await?.unwrap()) + ); + + remove_contact_from_chat(alice, group, bob_id).await?; + alice.pop_sent_msg().await; + SystemTime::shift(Duration::from_secs(60)); + add_contact_to_chat(alice, group, bob_id).await?; + let sent_msg = alice.pop_sent_msg().await; + assert!( + sent_msg + .recipients + .starts_with(&bob.get_config(Config::Addr).await?.unwrap()) + ); + Ok(()) +} + /// Regression test: mimefactory should never create an empty to header, /// also not if the Selftalk parameter is missing #[tokio::test(flavor = "multi_thread", worker_threads = 2)]