mirror of
https://github.com/chatmail/core.git
synced 2026-10-02 03:00:40 +03:00
feat: reduce unncessary gossipping of keys in group chats
- treat an Autocrypt header as a kind of Autocrypt-Gossip: addr=<self> letting it participate in the existing cooperative key gossipping mechanics. - speed up processing of incoming autocrypt-gossip headers by using 1 sql commit for N gossip headers instead of the current N commits.
This commit is contained in:
@@ -1027,6 +1027,24 @@ impl MimeMessage {
|
|||||||
self.signature.is_some()
|
self.signature.is_some()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the fingerprints of all keys distributed by this message:
|
||||||
|
/// - keys from Autocrypt-Gossip headers
|
||||||
|
/// - the key from the sender's Autocrypt header ("self-gossip")
|
||||||
|
///
|
||||||
|
/// Nothing is returned unless the message was correctly encrypted.
|
||||||
|
pub(crate) fn distributed_key_fingerprints(&self) -> Vec<String> {
|
||||||
|
let sender_fingerprint = if self.was_encrypted() {
|
||||||
|
self.autocrypt_fingerprint.clone()
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
self.gossiped_keys
|
||||||
|
.values()
|
||||||
|
.map(|gossiped_key| gossiped_key.public_key.dc_fingerprint().hex())
|
||||||
|
.chain(sender_fingerprint)
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns whether the email contains a `chat-version` header.
|
/// Returns whether the email contains a `chat-version` header.
|
||||||
/// This indicates that the email is a DC-email.
|
/// This indicates that the email is a DC-email.
|
||||||
pub(crate) fn has_chat_version(&self) -> bool {
|
pub(crate) fn has_chat_version(&self) -> bool {
|
||||||
|
|||||||
+10
-8
@@ -755,24 +755,26 @@ pub(crate) async fn receive_imf_inner(
|
|||||||
contact::update_last_seen(context, from_id, mime_parser.timestamp_sent).await?;
|
contact::update_last_seen(context, from_id, mime_parser.timestamp_sent).await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update gossiped timestamp for the chat if someone else or our other device sent
|
// Update gossiped timestamp for the chat if someone else or our other device
|
||||||
// Autocrypt-Gossip header to avoid sending Autocrypt-Gossip ourselves
|
// distributed keys to this chat, via Autocrypt-Gossip headers
|
||||||
// and waste traffic.
|
// or the sender's own Autocrypt header which is a kind of self-gossip.
|
||||||
let chat_id = received_msg.chat_id;
|
let chat_id = received_msg.chat_id;
|
||||||
if !chat_id.is_special() {
|
if !chat_id.is_special() {
|
||||||
for gossiped_key in mime_parser.gossiped_keys.values() {
|
let fingerprints = mime_parser.distributed_key_fingerprints();
|
||||||
|
if !fingerprints.is_empty() {
|
||||||
|
let timestamp_sent = mime_parser.timestamp_sent;
|
||||||
context
|
context
|
||||||
.sql
|
.sql
|
||||||
.transaction(move |transaction| {
|
.transaction(move |transaction| {
|
||||||
let fingerprint = gossiped_key.public_key.dc_fingerprint().hex();
|
let mut stmt = transaction.prepare(
|
||||||
transaction.execute(
|
|
||||||
"INSERT INTO gossip_timestamp (chat_id, fingerprint, timestamp)
|
"INSERT INTO gossip_timestamp (chat_id, fingerprint, timestamp)
|
||||||
VALUES (?, ?, ?)
|
VALUES (?, ?, ?)
|
||||||
ON CONFLICT (chat_id, fingerprint)
|
ON CONFLICT (chat_id, fingerprint)
|
||||||
DO UPDATE SET timestamp=MAX(timestamp, excluded.timestamp)",
|
DO UPDATE SET timestamp=MAX(timestamp, excluded.timestamp)",
|
||||||
(chat_id, &fingerprint, mime_parser.timestamp_sent),
|
|
||||||
)?;
|
)?;
|
||||||
|
for fingerprint in &fingerprints {
|
||||||
|
stmt.execute((chat_id, fingerprint, timestamp_sent))?;
|
||||||
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
})
|
||||||
.await?;
|
.await?;
|
||||||
|
|||||||
@@ -5503,6 +5503,35 @@ async fn test_group_introduction_no_gossip() -> Result<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Tests that the sender's own Autocrypt header counts like received gossip:
|
||||||
|
/// members do not re-gossip a key that its owner just distributed themselves.
|
||||||
|
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||||
|
async fn test_autocrypt_header_suppresses_gossip() -> Result<()> {
|
||||||
|
let mut tcm = TestContextManager::new();
|
||||||
|
let alice = &tcm.alice().await;
|
||||||
|
let bob = &tcm.bob().await;
|
||||||
|
let fiona = &tcm.fiona().await;
|
||||||
|
|
||||||
|
let alice_chat_id = alice
|
||||||
|
.create_group_with_members("Group", &[bob, fiona])
|
||||||
|
.await;
|
||||||
|
let sent = alice.send_text(alice_chat_id, "Hello group").await;
|
||||||
|
|
||||||
|
// Alice's first message gossips the other members' keys.
|
||||||
|
let msg = bob.recv_msg(&sent).await;
|
||||||
|
assert!(!bob.parse_msg(&sent).await.gossiped_keys.is_empty());
|
||||||
|
|
||||||
|
// Bob got Alice's key from her Autocrypt header
|
||||||
|
// and the other members' keys from her gossip,
|
||||||
|
// so Bob has nothing left to gossip.
|
||||||
|
let bob_chat_id = msg.chat_id;
|
||||||
|
bob_chat_id.accept(bob).await?;
|
||||||
|
let sent = bob.send_text(bob_chat_id, "Hello back").await;
|
||||||
|
assert!(fiona.parse_msg(&sent).await.gossiped_keys.is_empty());
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// Tests reception of an encrypted group message
|
/// Tests reception of an encrypted group message
|
||||||
/// without Chat-Group-ID.
|
/// without Chat-Group-ID.
|
||||||
///
|
///
|
||||||
|
|||||||
Reference in New Issue
Block a user