diff --git a/deltachat-jsonrpc/src/api/types/events.rs b/deltachat-jsonrpc/src/api/types/events.rs index 746eee72f..2b4633af4 100644 --- a/deltachat-jsonrpc/src/api/types/events.rs +++ b/deltachat-jsonrpc/src/api/types/events.rs @@ -1,4 +1,5 @@ use deltachat::{Event as CoreEvent, EventType as CoreEventType}; +use num_traits::ToPrimitive; use serde::Serialize; use typescript_type_def::TypeDef; @@ -303,6 +304,11 @@ pub enum EventType { /// ID of the contact that wants to join. contact_id: u32, + /// The type of the joined chat. + /// This can take the same values + /// as `BasicChat.chatType` ([`crate::api::types::chat::BasicChat::chat_type`]). + chat_type: u32, + /// Progress as: /// 300=vg-/vc-request received, typically shown as "bob@addr joins". /// 600=vg-/vc-request-with-auth received and verified, typically shown as "bob@addr verified". @@ -551,9 +557,11 @@ impl From for EventType { }, CoreEventType::SecurejoinInviterProgress { contact_id, + chat_type, progress, } => SecurejoinInviterProgress { contact_id: contact_id.to_u32(), + chat_type: chat_type.to_u32().unwrap_or(0), progress, }, CoreEventType::SecurejoinJoinerProgress { diff --git a/src/chat.rs b/src/chat.rs index f9d95fc9f..c95345e7d 100644 --- a/src/chat.rs +++ b/src/chat.rs @@ -1983,7 +1983,7 @@ impl Chat { } /// Adds missing values to the msg object, - /// writes the record to the database and returns its msg_id. + /// writes the record to the database. /// /// If `update_msg_id` is set, that record is reused; /// if `update_msg_id` is None, a new record is created. @@ -1992,7 +1992,7 @@ impl Chat { context: &Context, msg: &mut Message, update_msg_id: Option, - ) -> Result { + ) -> Result<()> { let mut to_id = 0; let mut location_id = 0; @@ -2270,7 +2270,7 @@ impl Chat { .await?; } context.scheduler.interrupt_ephemeral_task().await; - Ok(msg.id) + Ok(()) } /// Sends a `SyncAction` synchronising chat contacts to other devices. @@ -3006,8 +3006,7 @@ async fn prepare_send_msg( if !msg.hidden { chat_id.unarchive_if_not_muted(context, msg.state).await?; } - msg.id = chat.prepare_msg_raw(context, msg, update_msg_id).await?; - msg.chat_id = chat_id; + chat.prepare_msg_raw(context, msg, update_msg_id).await?; let row_ids = create_send_msg_jobs(context, msg) .await @@ -4561,13 +4560,13 @@ pub async fn forward_msgs(context: &Context, msg_ids: &[MsgId], chat_id: ChatId) msg.state = MessageState::OutPending; msg.rfc724_mid = create_outgoing_rfc724_mid(); msg.timestamp_sort = curr_timestamp; - let new_msg_id = chat.prepare_msg_raw(context, &mut msg, None).await?; + chat.prepare_msg_raw(context, &mut msg, None).await?; curr_timestamp += 1; if !create_send_msg_jobs(context, &mut msg).await?.is_empty() { context.scheduler.interrupt_smtp().await; } - created_msgs.push(new_msg_id); + created_msgs.push(msg.id); } for msg_id in created_msgs { context.emit_msgs_changed(chat_id, msg_id); diff --git a/src/chat/chat_tests.rs b/src/chat/chat_tests.rs index d08f27316..103cf3639 100644 --- a/src/chat/chat_tests.rs +++ b/src/chat/chat_tests.rs @@ -3459,6 +3459,30 @@ async fn test_chat_get_encryption_info() -> Result<()> { Ok(()) } +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn test_out_failed_on_all_keys_missing() -> Result<()> { + let mut tcm = TestContextManager::new(); + let alice = &tcm.alice().await; + let bob = &tcm.bob().await; + let fiona = &tcm.fiona().await; + + let bob_chat_id = bob + .create_group_with_members(ProtectionStatus::Unprotected, "", &[alice, fiona]) + .await; + bob.send_text(bob_chat_id, "Gossiping Fiona's key").await; + alice + .recv_msg(&bob.send_text(bob_chat_id, "No key gossip").await) + .await; + SystemTime::shift(Duration::from_secs(60)); + remove_contact_from_chat(bob, bob_chat_id, ContactId::SELF).await?; + let alice_chat_id = alice.recv_msg(&bob.pop_sent_msg().await).await.chat_id; + alice_chat_id.accept(alice).await?; + let mut msg = Message::new_text("Hi".to_string()); + send_msg(alice, alice_chat_id, &mut msg).await.ok(); + assert_eq!(msg.id.get_state(alice).await?, MessageState::OutFailed); + Ok(()) +} + #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn test_get_chat_media() -> Result<()> { let t = TestContext::new_alice().await; diff --git a/src/events/payload.rs b/src/events/payload.rs index 6bddadf79..1a6f1a679 100644 --- a/src/events/payload.rs +++ b/src/events/payload.rs @@ -5,6 +5,7 @@ use std::path::PathBuf; use crate::chat::ChatId; use crate::config::Config; +use crate::constants::Chattype; use crate::contact::ContactId; use crate::ephemeral::Timer as EphemeralTimer; use crate::message::MsgId; @@ -272,6 +273,9 @@ pub enum EventType { /// ID of the contact that wants to join. contact_id: ContactId, + /// The type of the joined chat. + chat_type: Chattype, + /// Progress as: /// 300=vg-/vc-request received, typically shown as "bob@addr joins". /// 600=vg-/vc-request-with-auth received and verified, typically shown as "bob@addr verified". diff --git a/src/securejoin.rs b/src/securejoin.rs index 12b9156ea..7030e6dce 100644 --- a/src/securejoin.rs +++ b/src/securejoin.rs @@ -32,16 +32,30 @@ use qrinvite::QrInvite; use crate::token::Namespace; -fn inviter_progress(context: &Context, contact_id: ContactId, progress: usize) { +fn inviter_progress( + context: &Context, + contact_id: ContactId, + step: &str, + progress: usize, +) -> Result<()> { logged_debug_assert!( context, progress <= 1000, "inviter_progress: contact {contact_id}, progress={progress}, but value in range 0..1000 expected with: 0=error, 1..999=progress, 1000=success." ); + let chat_type = match step.get(..3) { + Some("vc-") => Chattype::Single, + Some("vg-") => Chattype::Group, + Some("vb-") => Chattype::OutBroadcast, + _ => bail!("Unknown securejoin step {step}"), + }; context.emit_event(EventType::SecurejoinInviterProgress { contact_id, + chat_type, progress, }); + + Ok(()) } /// Generates a Secure Join QR code. @@ -346,7 +360,7 @@ pub(crate) async fn handle_securejoin_handshake( return Ok(HandshakeMessage::Ignore); } - inviter_progress(context, contact_id, 300); + inviter_progress(context, contact_id, step, 300)?; let from_addr = ContactAddress::new(&mime_message.from.addr)?; let autocrypt_fingerprint = mime_message.autocrypt_fingerprint.as_deref().unwrap_or(""); @@ -442,7 +456,7 @@ pub(crate) async fn handle_securejoin_handshake( ChatId::create_for_contact(context, contact_id).await?; } context.emit_event(EventType::ContactsChanged(Some(contact_id))); - inviter_progress(context, contact_id, 600); + inviter_progress(context, contact_id, step, 600)?; if let Some(group_chat_id) = group_chat_id { // Join group. secure_connection_established( @@ -455,8 +469,8 @@ pub(crate) async fn handle_securejoin_handshake( chat::add_contact_to_chat_ex(context, Nosync, group_chat_id, contact_id, true) .await?; - inviter_progress(context, contact_id, 800); - inviter_progress(context, contact_id, 1000); + inviter_progress(context, contact_id, step, 800)?; + inviter_progress(context, contact_id, step, 1000)?; if step == "vb-request-with-auth" { // For broadcasts, we don't want to delete the message, // because the other device should also internally add the member @@ -480,7 +494,7 @@ pub(crate) async fn handle_securejoin_handshake( .await .context("failed sending vc-contact-confirm message")?; - inviter_progress(context, contact_id, 1000); + inviter_progress(context, contact_id, step, 1000)?; Ok(HandshakeMessage::Ignore) // "Done" would delete the message and break multi-device (the key from Autocrypt-header is needed) } } @@ -609,11 +623,11 @@ pub(crate) async fn observe_securejoin_on_other_device( ChatId::set_protection_for_contact(context, contact_id, mime_message.timestamp_sent).await?; if step == "vg-member-added" { - inviter_progress(context, contact_id, 800); + inviter_progress(context, contact_id, step, 800)?; } // TODO superflous vb-member-added (we're early-returning above): if step == "vg-member-added" || step == "vb-member-added" || step == "vc-contact-confirm" { - inviter_progress(context, contact_id, 1000); + inviter_progress(context, contact_id, step, 1000)?; } if step == "vg-request-with-auth" || step == "vc-request-with-auth" { diff --git a/src/securejoin/securejoin_tests.rs b/src/securejoin/securejoin_tests.rs index 6339a53ac..c2a7e4dbc 100644 --- a/src/securejoin/securejoin_tests.rs +++ b/src/securejoin/securejoin_tests.rs @@ -366,6 +366,29 @@ async fn test_setup_contact_bob_knows_alice() -> Result<()> { alice.recv_msg_trash(&sent).await; assert_eq!(contact_bob.is_verified(alice).await?, true); + // Check Alice signalled success via the SecurejoinInviterProgress event. + let event = alice + .evtracker + .get_matching(|evt| { + matches!( + evt, + EventType::SecurejoinInviterProgress { progress: 1000, .. } + ) + }) + .await; + match event { + EventType::SecurejoinInviterProgress { + contact_id, + chat_type, + progress, + } => { + assert_eq!(contact_id, contact_bob.id); + assert_eq!(chat_type, Chattype::Single); + assert_eq!(progress, 1000); + } + _ => unreachable!(), + } + let sent = alice.pop_sent_msg().await; let msg = bob.parse_msg(&sent).await; assert!(msg.was_encrypted()); @@ -516,6 +539,29 @@ async fn test_secure_join() -> Result<()> { alice.recv_msg_trash(&sent).await; assert_eq!(contact_bob.is_verified(&alice).await?, true); + // Check Alice signalled success via the SecurejoinInviterProgress event. + let event = alice + .evtracker + .get_matching(|evt| { + matches!( + evt, + EventType::SecurejoinInviterProgress { progress: 1000, .. } + ) + }) + .await; + match event { + EventType::SecurejoinInviterProgress { + contact_id, + chat_type, + progress, + } => { + assert_eq!(contact_id, contact_bob.id); + assert_eq!(chat_type, Chattype::Group); + assert_eq!(progress, 1000); + } + _ => unreachable!(), + } + let sent = alice.pop_sent_msg().await; let msg = bob.parse_msg(&sent).await; assert!(msg.was_encrypted());