Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 14 additions & 2 deletions src/calls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ impl Context {
chat.id.accept(self).await?;
}

call.update_text(self, "Call accepted").await?;
call.msg
.mark_call_as_accepted(self, accept_call_info.to_string())
.await?;
Expand All @@ -125,6 +126,7 @@ impl Context {
..Default::default()
};
msg.param.set_cmd(SystemMessage::CallAccepted);
msg.hidden = true;
msg.param
.set(Param::WebrtcAccepted, accept_call_info.to_string());
msg.set_quote(self, Some(&call.msg)).await?;
Expand All @@ -133,20 +135,24 @@ impl Context {
msg_id: call.msg.id,
accept_call_info,
});
self.emit_msgs_changed(call.msg.chat_id, call_id);
Ok(())
}

/// Cancel, reject or hangup an incoming or outgoing call.
pub async fn end_call(&self, call_id: MsgId) -> Result<()> {
let call: CallInfo = self.load_call_by_id(call_id).await?;

call.update_text(self, "Call ended").await?;

if call.is_accepted || !call.is_incoming {
let mut msg = Message {
viewtype: Viewtype::Text,
text: "Call ended".into(),
..Default::default()
};
msg.param.set_cmd(SystemMessage::CallEnded);
msg.hidden = true;
msg.set_quote(self, Some(&call.msg)).await?;
msg.id = send_msg(self, call.msg.chat_id, &mut msg).await?;
} else if call.is_incoming {
Expand All @@ -161,6 +167,7 @@ impl Context {
self.emit_event(EventType::CallEnded {
msg_id: call.msg.id,
});
self.emit_msgs_changed(call.msg.chat_id, call_id);
Ok(())
}

Expand Down Expand Up @@ -189,9 +196,9 @@ impl Context {
if call.is_incoming {
if call.is_stale_call() {
call.update_text(self, "Missed call").await?;
self.emit_incoming_msg(call.msg.chat_id, call_id);
self.emit_incoming_msg(call.msg.chat_id, call_id); // notify missed call
} else {
self.emit_msgs_changed(call.msg.chat_id, call_id);
self.emit_msgs_changed(call.msg.chat_id, call_id); // ringing calls are not additionally notified
self.emit_event(EventType::IncomingCall {
msg_id: call.msg.id,
place_call_info: call.place_call_info.to_string(),
Expand All @@ -210,6 +217,7 @@ impl Context {
match mime_message.is_system_message {
SystemMessage::CallAccepted => {
let call = self.load_call_by_id(call_id).await?;
call.update_text(self, "Call accepted").await?;
self.emit_msgs_changed(call.msg.chat_id, call_id);
if call.is_incoming {
self.emit_event(EventType::IncomingCallAccepted {
Expand All @@ -232,6 +240,7 @@ impl Context {
}
SystemMessage::CallEnded => {
let call = self.load_call_by_id(call_id).await?;
call.update_text(self, "Call ended").await?;
self.emit_msgs_changed(call.msg.chat_id, call_id);
self.emit_event(EventType::CallEnded {
msg_id: call.msg.id,
Expand All @@ -245,7 +254,10 @@ impl Context {

pub(crate) async fn sync_call_rejection(&self, rfc724_mid: &str) -> Result<()> {
if let Some((msg_id, _)) = rfc724_mid_exists(self, rfc724_mid).await? {
let call = self.load_call_by_id(msg_id).await?;
call.update_text(self, "Call ended").await?;
self.emit_event(EventType::CallEnded { msg_id });
self.emit_msgs_changed(call.msg.chat_id, msg_id);
}
Ok(())
}
Expand Down
83 changes: 50 additions & 33 deletions src/calls/calls_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ struct CallSetup {
pub alice: TestContext,
pub alice2: TestContext,
pub alice_call: Message,
pub alice2_call: Message,
pub bob: TestContext,
pub bob2: TestContext,
pub bob_call: Message,
Expand Down Expand Up @@ -61,6 +62,7 @@ async fn setup_call() -> Result<CallSetup> {
alice,
alice2,
alice_call,
alice2_call,
bob,
bob2,
bob_call,
Expand All @@ -73,13 +75,14 @@ async fn accept_call() -> Result<CallSetup> {
alice,
alice2,
alice_call,
alice2_call,
bob,
bob2,
bob_call,
bob2_call,
} = setup_call().await?;

// Bob accepts the incoming call, this does not add an additional message to the chat
// Bob accepts the incoming call
bob.accept_incoming_call(bob_call.id, "accepted_info".to_string())
.await?;
bob.evtracker
Expand All @@ -91,21 +94,22 @@ async fn accept_call() -> Result<CallSetup> {
assert_eq!(info.place_call_info, "place_info");
assert_eq!(info.accept_call_info, "accepted_info");

let bob_accept_msg = bob2.recv_msg(&sent2).await;
assert!(bob_accept_msg.is_info());
assert_eq!(bob_accept_msg.get_info_type(), SystemMessage::CallAccepted);
bob2.recv_msg_trash(&sent2).await;
assert_eq!(
Message::load_from_db(&bob, bob_call.id).await?.text,
"Call accepted"
);
bob2.evtracker
.get_matching(|evt| matches!(evt, EventType::IncomingCallAccepted { .. }))
.await;
let info = bob2.load_call_by_id(bob2_call.id).await?;
assert!(!info.is_accepted); // "accepted" is only true on the device that does the call

// Alice receives the acceptance message
let alice_accept_msg = alice.recv_msg(&sent2).await;
assert!(alice_accept_msg.is_info());
alice.recv_msg_trash(&sent2).await;
assert_eq!(
alice_accept_msg.get_info_type(),
SystemMessage::CallAccepted
Message::load_from_db(&alice, alice_call.id).await?.text,
"Call accepted"
);
alice
.evtracker
Expand All @@ -116,11 +120,10 @@ async fn accept_call() -> Result<CallSetup> {
assert_eq!(info.place_call_info, "place_info");
assert_eq!(info.accept_call_info, "accepted_info");

let alice2_accept_msg = alice2.recv_msg(&sent2).await;
assert!(alice2_accept_msg.is_info());
alice2.recv_msg_trash(&sent2).await;
assert_eq!(
alice2_accept_msg.get_info_type(),
SystemMessage::CallAccepted
Message::load_from_db(&alice2, alice2_call.id).await?.text,
"Call accepted"
);
alice2
.evtracker
Expand All @@ -131,53 +134,58 @@ async fn accept_call() -> Result<CallSetup> {
alice,
alice2,
alice_call,
alice2_call,
bob,
bob2,
bob_call,
bob2_call,
})
}

fn assert_is_call_ended_info_msg(msg: Message) {
assert!(msg.is_info());
assert_eq!(msg.get_info_type(), SystemMessage::CallEnded);
async fn assert_is_call_ended(t: &TestContext, call_id: MsgId) -> Result<()> {
assert_eq!(Message::load_from_db(t, call_id).await?.text, "Call ended");
Ok(())
}

#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_accept_call_callee_ends() -> Result<()> {
// Alice calls Bob, Bob accepts
let CallSetup {
alice,
alice_call,
alice2,
alice2_call,
bob,
bob2,
bob_call,
bob2_call,
..
} = accept_call().await?;

// Bob has accepted the call and also ends it
bob.end_call(bob_call.id).await?;
assert_is_call_ended(&bob, bob_call.id).await?;
bob.evtracker
.get_matching(|evt| matches!(evt, EventType::CallEnded { .. }))
.await;
let sent3 = bob.pop_sent_msg().await;

let bob2_end_call_msg = bob2.recv_msg(&sent3).await;
assert_is_call_ended_info_msg(bob2_end_call_msg);
bob2.recv_msg_trash(&sent3).await;
assert_is_call_ended(&bob2, bob2_call.id).await?;
bob2.evtracker
.get_matching(|evt| matches!(evt, EventType::CallEnded { .. }))
.await;

// Alice receives the ending message
let alice_end_call_msg = alice.recv_msg(&sent3).await;
assert_is_call_ended_info_msg(alice_end_call_msg);
alice.recv_msg_trash(&sent3).await;
assert_is_call_ended(&alice, alice_call.id).await?;
alice
.evtracker
.get_matching(|evt| matches!(evt, EventType::CallEnded { .. }))
.await;

let alice2_end_call_msg = alice2.recv_msg(&sent3).await;
assert_is_call_ended_info_msg(alice2_end_call_msg);
alice2.recv_msg_trash(&sent3).await;
assert_is_call_ended(&alice2, alice2_call.id).await?;
alice2
.evtracker
.get_matching(|evt| matches!(evt, EventType::CallEnded { .. }))
Expand All @@ -192,9 +200,11 @@ async fn test_accept_call_caller_ends() -> Result<()> {
let CallSetup {
alice,
alice2,
alice2_call,
bob,
bob2,
bob_call,
bob2_call,
..
} = accept_call().await?;

Expand All @@ -206,22 +216,22 @@ async fn test_accept_call_caller_ends() -> Result<()> {
.await;
let sent3 = alice.pop_sent_msg().await;

let alice2_end_call_msg = alice2.recv_msg(&sent3).await;
assert_is_call_ended_info_msg(alice2_end_call_msg);
alice2.recv_msg_trash(&sent3).await;
assert_is_call_ended(&alice2, alice2_call.id).await?;
alice2
.evtracker
.get_matching(|evt| matches!(evt, EventType::CallEnded { .. }))
.await;

// Bob receives the ending message
let bob_end_call_msg = bob.recv_msg(&sent3).await;
assert_is_call_ended_info_msg(bob_end_call_msg);
bob.recv_msg_trash(&sent3).await;
assert_is_call_ended(&bob, bob_call.id).await?;
bob.evtracker
.get_matching(|evt| matches!(evt, EventType::CallEnded { .. }))
.await;

let bob2_end_call_msg = bob2.recv_msg(&sent3).await;
assert_is_call_ended_info_msg(bob2_end_call_msg);
bob2.recv_msg_trash(&sent3).await;
assert_is_call_ended(&bob2, bob2_call.id).await?;
bob2.evtracker
.get_matching(|evt| matches!(evt, EventType::CallEnded { .. }))
.await;
Expand All @@ -236,18 +246,21 @@ async fn test_callee_rejects_call() -> Result<()> {
bob,
bob2,
bob_call,
bob2_call,
..
} = setup_call().await?;

// Bob does not want to talk with Alice.
// To protect Bob's privacy, no message is sent to Alice (who will time out).
// To let Bob close the call window on all devices, a sync message is used instead.
bob.end_call(bob_call.id).await?;
assert_is_call_ended(&bob, bob_call.id).await?;
bob.evtracker
.get_matching(|evt| matches!(evt, EventType::CallEnded { .. }))
.await;

sync(&bob, &bob2).await;
assert_is_call_ended(&bob2, bob2_call.id).await?;
bob2.evtracker
.get_matching(|evt| matches!(evt, EventType::CallEnded { .. }))
.await;
Expand All @@ -262,35 +275,39 @@ async fn test_caller_cancels_call() -> Result<()> {
alice,
alice2,
alice_call,
alice2_call,
bob,
bob2,
bob_call,
bob2_call,
..
} = setup_call().await?;

// Alice changes their mind before Bob picks up
alice.end_call(alice_call.id).await?;
assert_is_call_ended(&alice, alice_call.id).await?;
alice
.evtracker
.get_matching(|evt| matches!(evt, EventType::CallEnded { .. }))
.await;
let sent3 = alice.pop_sent_msg().await;

let alice2_call_ended_msg = alice2.recv_msg(&sent3).await;
assert_is_call_ended_info_msg(alice2_call_ended_msg);
alice2.recv_msg_trash(&sent3).await;
assert_is_call_ended(&alice2, alice2_call.id).await?;
alice2
.evtracker
.get_matching(|evt| matches!(evt, EventType::CallEnded { .. }))
.await;

// Bob receives the ending message
let bob_call_ended_msg = bob.recv_msg(&sent3).await;
assert_is_call_ended_info_msg(bob_call_ended_msg);
bob.recv_msg_trash(&sent3).await;
assert_is_call_ended(&bob, bob_call.id).await?;
bob.evtracker
.get_matching(|evt| matches!(evt, EventType::CallEnded { .. }))
.await;

let bob2_call_ended_msg = bob2.recv_msg(&sent3).await;
assert_is_call_ended_info_msg(bob2_call_ended_msg);
bob2.recv_msg_trash(&sent3).await;
assert_is_call_ended(&bob2, bob2_call.id).await?;
bob2.evtracker
.get_matching(|evt| matches!(evt, EventType::CallEnded { .. }))
.await;
Expand Down
5 changes: 5 additions & 0 deletions src/receive_imf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1153,6 +1153,11 @@ async fn decide_chat_assignment(
{
info!(context, "Chat edit/delete/iroh/sync message (TRASH).");
true
} else if mime_parser.is_system_message == SystemMessage::CallAccepted
|| mime_parser.is_system_message == SystemMessage::CallEnded
{
info!(context, "Call state changed (TRASH).");
true
} else if mime_parser.decrypting_failed && !mime_parser.incoming {
// Outgoing undecryptable message.
let last_time = context
Expand Down
Loading