Skip to content

Commit 04c2585

Browse files
committed
feat: Synchronize encrypted groups creation across devices (#7001)
Unencrypted groups don't have grpid since key-contacts were merged, so we don't sync them for now.
1 parent 59fac54 commit 04c2585

File tree

3 files changed

+100
-29
lines changed

3 files changed

+100
-29
lines changed

src/chat.rs

Lines changed: 36 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ use crate::debug_logging::maybe_set_logging_xdc;
3030
use crate::download::DownloadState;
3131
use crate::ephemeral::{Timer as EphemeralTimer, start_chat_ephemeral_timers};
3232
use crate::events::EventType;
33+
use crate::key::self_fingerprint;
3334
use crate::location;
3435
use crate::log::{LogExt, error, info, warn};
3536
use crate::logged_debug_assert;
@@ -2006,17 +2007,21 @@ impl Chat {
20062007
/// Sends a `SyncAction` synchronising chat contacts to other devices.
20072008
pub(crate) async fn sync_contacts(&self, context: &Context) -> Result<()> {
20082009
if self.is_encrypted(context).await? {
2010+
let self_fp = self_fingerprint(context).await?;
20092011
let fingerprint_addrs = context
20102012
.sql
20112013
.query_map(
2012-
"SELECT c.fingerprint, c.addr
2014+
"SELECT c.id, c.fingerprint, c.addr
20132015
FROM contacts c INNER JOIN chats_contacts cc
20142016
ON c.id=cc.contact_id
20152017
WHERE cc.chat_id=? AND cc.add_timestamp >= cc.remove_timestamp",
20162018
(self.id,),
20172019
|row| {
2018-
let fingerprint = row.get(0)?;
2019-
let addr = row.get(1)?;
2020+
if row.get::<_, ContactId>(0)? == ContactId::SELF {
2021+
return Ok((self_fp.to_string(), String::new()));
2022+
}
2023+
let fingerprint = row.get(1)?;
2024+
let addr = row.get(2)?;
20202025
Ok((fingerprint, addr))
20212026
},
20222027
|addrs| addrs.collect::<Result<Vec<_>, _>>().map_err(Into::into),
@@ -3395,25 +3400,26 @@ pub async fn get_past_chat_contacts(context: &Context, chat_id: ChatId) -> Resul
33953400
Ok(list)
33963401
}
33973402

3398-
/// Creates a group chat with a given `name`.
3403+
/// Creates an encrypted group chat.
33993404
pub async fn create_group_chat(context: &Context, name: &str) -> Result<ChatId> {
3400-
let is_encrypted = true;
3401-
create_group_ex(context, is_encrypted, name).await
3405+
create_group_ex(context, Sync, create_id(), name).await
34023406
}
34033407

3404-
/// Creates a new unencrypted group chat.
3408+
/// Creates an unencrypted group chat.
34053409
pub async fn create_group_chat_unencrypted(context: &Context, name: &str) -> Result<ChatId> {
3406-
let is_encrypted = false;
3407-
create_group_ex(context, is_encrypted, name).await
3410+
create_group_ex(context, Sync, String::new(), name).await
34083411
}
34093412

34103413
/// Creates a group chat.
34113414
///
3412-
/// * `is_encrypted` - If true, the chat is encrypted (with key-contacts).
3415+
/// * `sync` - Whether a multi-device synchronization message should be sent. Ignored for
3416+
/// unencrypted chats currently.
3417+
/// * `grpid` - Group ID. Iff nonempty, the chat is encrypted (with key-contacts).
34133418
/// * `name` - Chat name.
34143419
pub(crate) async fn create_group_ex(
34153420
context: &Context,
3416-
is_encrypted: bool,
3421+
sync: sync::Sync,
3422+
grpid: String,
34173423
name: &str,
34183424
) -> Result<ChatId> {
34193425
let mut chat_name = sanitize_single_line(name);
@@ -3424,20 +3430,14 @@ pub(crate) async fn create_group_ex(
34243430
chat_name = "…".to_string();
34253431
}
34263432

3427-
let grpid = if is_encrypted {
3428-
create_id()
3429-
} else {
3430-
String::new()
3431-
};
3432-
34333433
let timestamp = create_smeared_timestamp(context);
34343434
let row_id = context
34353435
.sql
34363436
.insert(
34373437
"INSERT INTO chats
34383438
(type, name, grpid, param, created_timestamp)
34393439
VALUES(?, ?, ?, \'U=1\', ?);",
3440-
(Chattype::Group, chat_name, grpid, timestamp),
3440+
(Chattype::Group, &chat_name, &grpid, timestamp),
34413441
)
34423442
.await?;
34433443

@@ -3448,7 +3448,7 @@ pub(crate) async fn create_group_ex(
34483448
chatlist_events::emit_chatlist_changed(context);
34493449
chatlist_events::emit_chatlist_item_changed(context, chat_id);
34503450

3451-
if is_encrypted {
3451+
if !grpid.is_empty() {
34523452
// Add "Messages are end-to-end encrypted." message.
34533453
chat_id.add_encrypted_msg(context, timestamp).await?;
34543454
}
@@ -3459,7 +3459,11 @@ pub(crate) async fn create_group_ex(
34593459
let text = stock_str::new_group_send_first_message(context).await;
34603460
add_info_msg(context, chat_id, &text, create_smeared_timestamp(context)).await?;
34613461
}
3462-
3462+
if let (true, true) = (sync.into(), !grpid.is_empty()) {
3463+
let id = SyncId::Grpid(grpid);
3464+
let action = SyncAction::CreateGroupEncrypted(chat_name);
3465+
self::sync(context, id, action).await.log_err(context).ok();
3466+
}
34633467
Ok(chat_id)
34643468
}
34653469

@@ -4675,16 +4679,14 @@ async fn set_contacts_by_fingerprints(
46754679
"Cannot add key-contacts to unencrypted chat {id}"
46764680
);
46774681
ensure!(
4678-
chat.typ == Chattype::OutBroadcast,
4679-
"{id} is not a broadcast list",
4682+
matches!(chat.typ, Chattype::Group | Chattype::OutBroadcast),
4683+
"{id} is not a group or broadcast",
46804684
);
46814685
let mut contacts = HashSet::new();
46824686
for (fingerprint, addr) in fingerprint_addrs {
4683-
let contact_addr = ContactAddress::new(addr)?;
4684-
let contact =
4685-
Contact::add_or_lookup_ex(context, "", &contact_addr, fingerprint, Origin::Hidden)
4686-
.await?
4687-
.0;
4687+
let contact = Contact::add_or_lookup_ex(context, "", addr, fingerprint, Origin::Hidden)
4688+
.await?
4689+
.0;
46884690
contacts.insert(contact);
46894691
}
46904692
let contacts_old = HashSet::<ContactId>::from_iter(get_chat_contacts(context, id).await?);
@@ -4723,7 +4725,7 @@ pub(crate) enum SyncId {
47234725
/// "Message-ID"-s, from oldest to latest. Used for ad-hoc groups.
47244726
Msgids(Vec<String>),
47254727

4726-
// Special id for device chat.
4728+
/// Special id for device chat.
47274729
Device,
47284730
}
47294731

@@ -4737,6 +4739,8 @@ pub(crate) enum SyncAction {
47374739
SetMuted(MuteDuration),
47384740
/// Create broadcast channel with the given name.
47394741
CreateBroadcast(String),
4742+
/// Create encrypted group chat with the given name.
4743+
CreateGroupEncrypted(String),
47404744
Rename(String),
47414745
/// Set chat contacts by their addresses.
47424746
SetContacts(Vec<String>),
@@ -4802,6 +4806,9 @@ impl Context {
48024806
if let SyncAction::CreateBroadcast(name) = action {
48034807
create_broadcast_ex(self, Nosync, grpid.clone(), name.clone()).await?;
48044808
return Ok(());
4809+
} else if let SyncAction::CreateGroupEncrypted(name) = action {
4810+
create_group_ex(self, Nosync, grpid.clone(), name).await?;
4811+
return Ok(());
48054812
}
48064813
get_chat_id_by_grpid(self, grpid)
48074814
.await?
@@ -4823,7 +4830,7 @@ impl Context {
48234830
SyncAction::Accept => chat_id.accept_ex(self, Nosync).await,
48244831
SyncAction::SetVisibility(v) => chat_id.set_visibility_ex(self, Nosync, *v).await,
48254832
SyncAction::SetMuted(duration) => set_muted_ex(self, Nosync, chat_id, *duration).await,
4826-
SyncAction::CreateBroadcast(_) => {
4833+
SyncAction::CreateBroadcast(_) | SyncAction::CreateGroupEncrypted(..) => {
48274834
Err(anyhow!("sync_alter_chat({id:?}, {action:?}): Bad request."))
48284835
}
48294836
SyncAction::Rename(to) => rename_ex(self, Nosync, chat_id, to).await,

src/chat/chat_tests.rs

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3833,6 +3833,61 @@ async fn test_sync_name() -> Result<()> {
38333833
Ok(())
38343834
}
38353835

3836+
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
3837+
async fn test_sync_create_group() -> Result<()> {
3838+
let mut tcm = TestContextManager::new();
3839+
let alice0 = &tcm.alice().await;
3840+
let alice1 = &tcm.alice().await;
3841+
for a in [alice0, alice1] {
3842+
a.set_config_bool(Config::SyncMsgs, true).await?;
3843+
}
3844+
let bob = &tcm.bob().await;
3845+
let a0_bob_contact_id = alice0.add_or_lookup_contact_id(bob).await;
3846+
let a1_bob_contact_id = alice1.add_or_lookup_contact_id(bob).await;
3847+
let a0_chat_id = create_group_chat(alice0, "grp").await?;
3848+
sync(alice0, alice1).await;
3849+
let a0_chat = Chat::load_from_db(alice0, a0_chat_id).await?;
3850+
let a1_chat_id = get_chat_id_by_grpid(alice1, &a0_chat.grpid)
3851+
.await?
3852+
.unwrap()
3853+
.0;
3854+
let a1_chat = Chat::load_from_db(alice1, a1_chat_id).await?;
3855+
assert_eq!(a1_chat.get_type(), Chattype::Group);
3856+
assert_eq!(a1_chat.is_promoted(), false);
3857+
assert_eq!(a1_chat.get_name(), "grp");
3858+
3859+
set_chat_name(alice0, a0_chat_id, "renamed").await?;
3860+
sync(alice0, alice1).await;
3861+
let a1_chat = Chat::load_from_db(alice1, a1_chat_id).await?;
3862+
assert_eq!(a1_chat.is_promoted(), false);
3863+
assert_eq!(a1_chat.get_name(), "renamed");
3864+
3865+
add_contact_to_chat(alice0, a0_chat_id, a0_bob_contact_id).await?;
3866+
sync(alice0, alice1).await;
3867+
let a1_chat = Chat::load_from_db(alice1, a1_chat_id).await?;
3868+
assert_eq!(a1_chat.is_promoted(), false);
3869+
assert_eq!(
3870+
get_chat_contacts(alice1, a1_chat_id).await?,
3871+
[a1_bob_contact_id, ContactId::SELF]
3872+
);
3873+
3874+
// Let's test a contact removal from another device.
3875+
remove_contact_from_chat(alice1, a1_chat_id, a1_bob_contact_id).await?;
3876+
sync(alice1, alice0).await;
3877+
let a0_chat = Chat::load_from_db(alice0, a0_chat_id).await?;
3878+
assert_eq!(a0_chat.is_promoted(), false);
3879+
assert_eq!(
3880+
get_chat_contacts(alice0, a0_chat_id).await?,
3881+
[ContactId::SELF]
3882+
);
3883+
3884+
let sent_msg = alice0.send_text(a0_chat_id, "hi").await;
3885+
let msg = alice1.recv_msg(&sent_msg).await;
3886+
assert_eq!(msg.chat_id, a1_chat_id);
3887+
assert_eq!(a1_chat_id.is_promoted(alice1).await?, true);
3888+
Ok(())
3889+
}
3890+
38363891
/// Tests sending JPEG image with .png extension.
38373892
///
38383893
/// This is a regression test, previously sending failed

src/receive_imf.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2164,6 +2164,7 @@ RETURNING id
21642164

21652165
if !chat_id.is_trash() && !hidden {
21662166
let mut chat = Chat::load_from_db(context, chat_id).await?;
2167+
let mut update_param = false;
21672168

21682169
// In contrast to most other update-timestamps,
21692170
// use `sort_timestamp` instead of `sent_timestamp` for the subject-timestamp comparison.
@@ -2177,6 +2178,14 @@ RETURNING id
21772178
let subject = mime_parser.get_subject().unwrap_or_default();
21782179

21792180
chat.param.set(Param::LastSubject, subject);
2181+
update_param = true;
2182+
}
2183+
2184+
if chat.is_unpromoted() {
2185+
chat.param.remove(Param::Unpromoted);
2186+
update_param = true;
2187+
}
2188+
if update_param {
21802189
chat.update_param(context).await?;
21812190
}
21822191
}

0 commit comments

Comments
 (0)