Skip to content

Commit 94984f3

Browse files
committed
fix: do not fail to receive call accepted/ended messages referring to non-call Message-ID
In-Reply-To may refer to non-call message as we do not control the sender. It may also happen that call message was received by older version and processed as text, in which case correct In-Reply-To appears to be referring to the text message.
1 parent 0e47e89 commit 94984f3

File tree

3 files changed

+136
-22
lines changed

3 files changed

+136
-22
lines changed

deltachat-jsonrpc/src/api/types/calls.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use anyhow::Result;
1+
use anyhow::{Context as _, Result};
22

33
use deltachat::calls::{call_state, sdp_has_video, CallState};
44
use deltachat::context::Context;
@@ -26,7 +26,9 @@ pub struct JsonrpcCallInfo {
2626

2727
impl JsonrpcCallInfo {
2828
pub async fn from_msg_id(context: &Context, msg_id: MsgId) -> Result<JsonrpcCallInfo> {
29-
let call_info = context.load_call_by_id(msg_id).await?;
29+
let call_info = context.load_call_by_id(msg_id).await?.with_context(|| {
30+
format!("Attempting to get call state of non-call message {msg_id}")
31+
})?;
3032
let sdp_offer = call_info.place_call_info.clone();
3133
let has_video = sdp_has_video(&sdp_offer).unwrap_or_default();
3234
let state = JsonrpcCallState::from_msg_id(context, msg_id).await?;

src/calls.rs

Lines changed: 49 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,9 @@ impl Context {
213213
call_id: MsgId,
214214
accept_call_info: String,
215215
) -> Result<()> {
216-
let mut call: CallInfo = self.load_call_by_id(call_id).await?;
216+
let mut call: CallInfo = self.load_call_by_id(call_id).await?.with_context(|| {
217+
format!("accept_incoming_call is called with {call_id} which does not refer to a call")
218+
})?;
217219
ensure!(call.is_incoming());
218220
if call.is_accepted() || call.is_ended() {
219221
info!(self, "Call already accepted/ended");
@@ -248,7 +250,9 @@ impl Context {
248250

249251
/// Cancel, decline or hangup an incoming or outgoing call.
250252
pub async fn end_call(&self, call_id: MsgId) -> Result<()> {
251-
let mut call: CallInfo = self.load_call_by_id(call_id).await?;
253+
let mut call: CallInfo = self.load_call_by_id(call_id).await?.with_context(|| {
254+
format!("end_call is called with {call_id} which does not refer to a call")
255+
})?;
252256
if call.is_ended() {
253257
info!(self, "Call already ended");
254258
return Ok(());
@@ -291,7 +295,13 @@ impl Context {
291295
call_id: MsgId,
292296
) -> Result<()> {
293297
sleep(Duration::from_secs(wait)).await;
294-
let mut call = context.load_call_by_id(call_id).await?;
298+
let Some(mut call) = context.load_call_by_id(call_id).await? else {
299+
warn!(
300+
context,
301+
"emit_end_call_if_unaccepted is called with {call_id} which does not refer to a call."
302+
);
303+
return Ok(());
304+
};
295305
if !call.is_accepted() && !call.is_ended() {
296306
if call.is_incoming() {
297307
call.mark_as_canceled(&context).await?;
@@ -316,7 +326,10 @@ impl Context {
316326
from_id: ContactId,
317327
) -> Result<()> {
318328
if mime_message.is_call() {
319-
let call = self.load_call_by_id(call_id).await?;
329+
let Some(call) = self.load_call_by_id(call_id).await? else {
330+
warn!(self, "{call_id} does not refer to a call message");
331+
return Ok(());
332+
};
320333

321334
if call.is_incoming() {
322335
if call.is_stale() {
@@ -352,7 +365,11 @@ impl Context {
352365
} else {
353366
match mime_message.is_system_message {
354367
SystemMessage::CallAccepted => {
355-
let mut call = self.load_call_by_id(call_id).await?;
368+
let Some(mut call) = self.load_call_by_id(call_id).await? else {
369+
warn!(self, "{call_id} does not refer to a call message");
370+
return Ok(());
371+
};
372+
356373
if call.is_ended() || call.is_accepted() {
357374
info!(self, "CallAccepted received for accepted/ended call");
358375
return Ok(());
@@ -377,7 +394,11 @@ impl Context {
377394
}
378395
}
379396
SystemMessage::CallEnded => {
380-
let mut call = self.load_call_by_id(call_id).await?;
397+
let Some(mut call) = self.load_call_by_id(call_id).await? else {
398+
warn!(self, "{call_id} does not refer to a call message");
399+
return Ok(());
400+
};
401+
381402
if call.is_ended() {
382403
// may happen eg. if a a message is missed
383404
info!(self, "CallEnded received for ended call");
@@ -421,15 +442,26 @@ impl Context {
421442
}
422443

423444
/// Loads information about the call given its ID.
424-
pub async fn load_call_by_id(&self, call_id: MsgId) -> Result<CallInfo> {
445+
///
446+
/// If the message referred to by ID is
447+
/// not a call message, returns `None`.
448+
pub async fn load_call_by_id(&self, call_id: MsgId) -> Result<Option<CallInfo>> {
425449
let call = Message::load_from_db(self, call_id).await?;
426-
self.load_call_by_message(call)
450+
Ok(self.load_call_by_message(call))
427451
}
428452

429-
fn load_call_by_message(&self, call: Message) -> Result<CallInfo> {
430-
ensure!(call.viewtype == Viewtype::Call);
453+
// Loads information about the call given the `Message`.
454+
//
455+
// If the `Message` is not a call message, returns `None`
456+
fn load_call_by_message(&self, call: Message) -> Option<CallInfo> {
457+
if call.viewtype != Viewtype::Call {
458+
// This can happen e.g. if a "call accepted"
459+
// or "call ended" message is received
460+
// with `In-Reply-To` referring to non-call message.
461+
return None;
462+
}
431463

432-
Ok(CallInfo {
464+
Some(CallInfo {
433465
place_call_info: call
434466
.param
435467
.get(Param::WebrtcRoom)
@@ -497,8 +529,13 @@ pub enum CallState {
497529
}
498530

499531
/// Returns call state given the message ID.
532+
///
533+
/// Returns an error if the message is not a call message.
500534
pub async fn call_state(context: &Context, msg_id: MsgId) -> Result<CallState> {
501-
let call = context.load_call_by_id(msg_id).await?;
535+
let call = context
536+
.load_call_by_id(msg_id)
537+
.await?
538+
.with_context(|| format!("{msg_id} is not a call message"))?;
502539
let state = if call.is_incoming() {
503540
if call.is_accepted() {
504541
if call.is_ended() {

src/calls/calls_tests.rs

Lines changed: 83 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
use super::*;
22
use crate::chat::forward_msgs;
33
use crate::config::Config;
4+
use crate::constants::DC_CHAT_ID_TRASH;
5+
use crate::receive_imf::receive_imf;
46
use crate::test_utils::{TestContext, TestContextManager};
57

68
struct CallSetup {
@@ -53,7 +55,10 @@ async fn setup_call() -> Result<CallSetup> {
5355
for (t, m) in [(&alice, &alice_call), (&alice2, &alice2_call)] {
5456
assert!(!m.is_info());
5557
assert_eq!(m.viewtype, Viewtype::Call);
56-
let info = t.load_call_by_id(m.id).await?;
58+
let info = t
59+
.load_call_by_id(m.id)
60+
.await?
61+
.expect("m should be a call message");
5762
assert!(!info.is_incoming());
5863
assert!(!info.is_accepted());
5964
assert_eq!(info.place_call_info, PLACE_INFO);
@@ -71,7 +76,10 @@ async fn setup_call() -> Result<CallSetup> {
7176
t.evtracker
7277
.get_matching(|evt| matches!(evt, EventType::IncomingCall { .. }))
7378
.await;
74-
let info = t.load_call_by_id(m.id).await?;
79+
let info = t
80+
.load_call_by_id(m.id)
81+
.await?
82+
.expect("IncomingCall event should refer to a call message");
7583
assert!(info.is_incoming());
7684
assert!(!info.is_accepted());
7785
assert_eq!(info.place_call_info, PLACE_INFO);
@@ -111,7 +119,10 @@ async fn accept_call() -> Result<CallSetup> {
111119
.get_matching(|evt| matches!(evt, EventType::IncomingCallAccepted { .. }))
112120
.await;
113121
let sent2 = bob.pop_sent_msg().await;
114-
let info = bob.load_call_by_id(bob_call.id).await?;
122+
let info = bob
123+
.load_call_by_id(bob_call.id)
124+
.await?
125+
.expect("bob_call should be a call message");
115126
assert!(info.is_accepted());
116127
assert_eq!(info.place_call_info, PLACE_INFO);
117128
assert_eq!(call_state(&bob, bob_call.id).await?, CallState::Active);
@@ -121,7 +132,10 @@ async fn accept_call() -> Result<CallSetup> {
121132
bob2.evtracker
122133
.get_matching(|evt| matches!(evt, EventType::IncomingCallAccepted { .. }))
123134
.await;
124-
let info = bob2.load_call_by_id(bob2_call.id).await?;
135+
let info = bob2
136+
.load_call_by_id(bob2_call.id)
137+
.await?
138+
.expect("bob2_call should be a call message");
125139
assert!(info.is_accepted());
126140
assert_eq!(call_state(&bob2, bob2_call.id).await?, CallState::Active);
127141

@@ -140,7 +154,10 @@ async fn accept_call() -> Result<CallSetup> {
140154
accept_call_info: ACCEPT_INFO.to_string()
141155
}
142156
);
143-
let info = alice.load_call_by_id(alice_call.id).await?;
157+
let info = alice
158+
.load_call_by_id(alice_call.id)
159+
.await?
160+
.expect("alice_call should be a call message");
144161
assert!(info.is_accepted());
145162
assert_eq!(info.place_call_info, PLACE_INFO);
146163
assert_eq!(call_state(&alice, alice_call.id).await?, CallState::Active);
@@ -460,14 +477,20 @@ async fn test_mark_calls() -> Result<()> {
460477
alice, alice_call, ..
461478
} = setup_call().await?;
462479

463-
let mut call_info: CallInfo = alice.load_call_by_id(alice_call.id).await?;
480+
let mut call_info: CallInfo = alice
481+
.load_call_by_id(alice_call.id)
482+
.await?
483+
.expect("alice_call should be a call message");
464484
assert!(!call_info.is_accepted());
465485
assert!(!call_info.is_ended());
466486
call_info.mark_as_accepted(&alice).await?;
467487
assert!(call_info.is_accepted());
468488
assert!(!call_info.is_ended());
469489

470-
let mut call_info: CallInfo = alice.load_call_by_id(alice_call.id).await?;
490+
let mut call_info: CallInfo = alice
491+
.load_call_by_id(alice_call.id)
492+
.await?
493+
.expect("alice_call should be a call message");
471494
assert!(call_info.is_accepted());
472495
assert!(!call_info.is_ended());
473496

@@ -484,7 +507,10 @@ async fn test_update_call_text() -> Result<()> {
484507
alice, alice_call, ..
485508
} = setup_call().await?;
486509

487-
let call_info = alice.load_call_by_id(alice_call.id).await?;
510+
let call_info = alice
511+
.load_call_by_id(alice_call.id)
512+
.await?
513+
.expect("alice_call should be a call message");
488514
call_info.update_text(&alice, "foo bar").await?;
489515

490516
let alice_call = Message::load_from_db(&alice, alice_call.id).await?;
@@ -529,3 +555,52 @@ async fn test_forward_call() -> Result<()> {
529555

530556
Ok(())
531557
}
558+
559+
/// Tests that "end call" message referring
560+
/// to a text message does not make receive_imf fail.
561+
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
562+
async fn test_end_text_call() -> Result<()> {
563+
let mut tcm = TestContextManager::new();
564+
let alice = &tcm.alice().await;
565+
566+
let received1 = receive_imf(
567+
alice,
568+
b"From: bob@example.net\n\
569+
To: alice@example.org\n\
570+
Message-ID: <first@example.net>\n\
571+
Date: Sun, 22 Mar 2020 22:37:57 +0000\n\
572+
Chat-Version: 1.0\n\
573+
\n\
574+
Hello\n",
575+
false,
576+
)
577+
.await?
578+
.unwrap();
579+
assert_eq!(received1.msg_ids.len(), 1);
580+
let msg = Message::load_from_db(alice, received1.msg_ids[0])
581+
.await
582+
.unwrap();
583+
assert_eq!(msg.viewtype, Viewtype::Text);
584+
585+
// Receiving "Call ended" message that refers
586+
// to the text message does not result in an error.
587+
let received2 = receive_imf(
588+
alice,
589+
b"From: bob@example.net\n\
590+
To: alice@example.org\n\
591+
Message-ID: <second@example.net>\n\
592+
Date: Sun, 22 Mar 2020 23:37:57 +0000\n\
593+
In-Reply-To: <first@example.net>\n\
594+
Chat-Version: 1.0\n\
595+
Chat-Content: call-ended\n\
596+
\n\
597+
Call ended\n",
598+
false,
599+
)
600+
.await?
601+
.unwrap();
602+
assert_eq!(received2.msg_ids.len(), 1);
603+
assert_eq!(received2.chat_id, DC_CHAT_ID_TRASH);
604+
605+
Ok(())
606+
}

0 commit comments

Comments
 (0)