1use crate::chat::ChatIdBlocked;
6use crate::chat::{Chat, ChatId, send_msg};
7use crate::config::Config;
8use crate::constants::{Blocked, Chattype};
9use crate::contact::ContactId;
10use crate::context::{Context, WeakContext};
11use crate::events::EventType;
12use crate::headerdef::HeaderDef;
13use crate::log::warn;
14use crate::message::{Message, MsgId, Viewtype};
15use crate::mimeparser::{MimeMessage, SystemMessage};
16use crate::net::dns::lookup_host_with_cache;
17use crate::param::Param;
18use crate::stock_str;
19use crate::tools::{normalize_text, time};
20use anyhow::{Context as _, Result, ensure};
21use deltachat_derive::{FromSql, ToSql};
22use num_traits::FromPrimitive;
23use serde::Serialize;
24use std::str::FromStr;
25use std::time::Duration;
26use tokio::task;
27use tokio::time::sleep;
28
29const RINGING_SECONDS: i64 = 120;
39
40const CALL_ACCEPTED_TIMESTAMP: Param = Param::Arg;
43const CALL_ENDED_TIMESTAMP: Param = Param::Arg4;
44
45const STUN_PORT: u16 = 3478;
46
47const CALL_CANCELED_TIMESTAMP: Param = Param::Arg2;
55
56#[derive(Debug, Default)]
58pub struct CallInfo {
59 pub place_call_info: String,
61
62 pub accept_call_info: String,
64
65 pub msg: Message,
68}
69
70impl CallInfo {
71 pub fn is_incoming(&self) -> bool {
73 self.msg.from_id != ContactId::SELF
74 }
75
76 pub fn is_stale(&self) -> bool {
78 (self.is_incoming() || self.msg.timestamp_sent != 0) && self.remaining_ring_seconds() <= 0
79 }
80
81 fn remaining_ring_seconds(&self) -> i64 {
82 #[expect(clippy::arithmetic_side_effects)]
83 let remaining_seconds = self.msg.timestamp_sent + RINGING_SECONDS - time();
84 remaining_seconds.clamp(0, RINGING_SECONDS)
85 }
86
87 async fn update_text(&self, context: &Context, text: &str) -> Result<()> {
88 context
89 .sql
90 .execute(
91 "UPDATE msgs SET txt=?, txt_normalized=? WHERE id=?",
92 (text, normalize_text(text), self.msg.id),
93 )
94 .await?;
95 Ok(())
96 }
97
98 async fn update_text_duration(&self, context: &Context) -> Result<()> {
99 let minutes = self.duration_seconds() / 60;
100 let duration = match minutes {
101 0 => "<1 minute".to_string(),
102 1 => "1 minute".to_string(),
103 n => format!("{n} minutes"),
104 };
105
106 if self.is_incoming() {
107 let incoming_call_str =
108 stock_str::incoming_call(context, self.has_video_initially()).await;
109 self.update_text(context, &format!("{incoming_call_str}\n{duration}"))
110 .await?;
111 } else {
112 let outgoing_call_str =
113 stock_str::outgoing_call(context, self.has_video_initially()).await;
114 self.update_text(context, &format!("{outgoing_call_str}\n{duration}"))
115 .await?;
116 }
117 Ok(())
118 }
119
120 async fn mark_as_accepted(&mut self, context: &Context) -> Result<()> {
123 self.msg.param.set_i64(CALL_ACCEPTED_TIMESTAMP, time());
124 self.msg.update_param(context).await?;
125 Ok(())
126 }
127
128 pub fn is_accepted(&self) -> bool {
130 self.msg.param.exists(CALL_ACCEPTED_TIMESTAMP)
131 }
132
133 pub fn has_video_initially(&self) -> bool {
135 self.msg
136 .param
137 .get_bool(Param::WebrtcHasVideoInitially)
138 .unwrap_or(false)
139 }
140
141 pub fn is_canceled(&self) -> bool {
149 self.msg.param.exists(CALL_CANCELED_TIMESTAMP)
150 }
151
152 async fn mark_as_ended(&mut self, context: &Context) -> Result<()> {
153 self.msg.param.set_i64(CALL_ENDED_TIMESTAMP, time());
154 self.msg.update_param(context).await?;
155 Ok(())
156 }
157
158 async fn mark_as_canceled(&mut self, context: &Context) -> Result<()> {
166 let now = time();
167 self.msg.param.set_i64(CALL_ENDED_TIMESTAMP, now);
168 self.msg.param.set_i64(CALL_CANCELED_TIMESTAMP, now);
169 self.msg.update_param(context).await?;
170 Ok(())
171 }
172
173 pub fn is_ended(&self) -> bool {
175 self.msg.param.exists(CALL_ENDED_TIMESTAMP)
176 }
177
178 #[expect(clippy::arithmetic_side_effects)]
180 pub fn duration_seconds(&self) -> i64 {
181 if let (Some(start), Some(end)) = (
182 self.msg.param.get_i64(CALL_ACCEPTED_TIMESTAMP),
183 self.msg.param.get_i64(CALL_ENDED_TIMESTAMP),
184 ) {
185 let seconds = end - start;
186 if seconds <= 0 {
187 return 1;
188 }
189 return seconds;
190 }
191 0
192 }
193}
194
195impl Context {
196 pub async fn place_outgoing_call(
198 &self,
199 chat_id: ChatId,
200 place_call_info: String,
201 has_video_initially: bool,
202 ) -> Result<MsgId> {
203 let chat = Chat::load_from_db(self, chat_id).await?;
204 ensure!(
205 chat.typ == Chattype::Single,
206 "Can only place calls in 1:1 chats"
207 );
208 ensure!(!chat.is_self_talk(), "Cannot call self");
209
210 let outgoing_call_str = stock_str::outgoing_call(self, has_video_initially).await;
211 let mut call = Message {
212 viewtype: Viewtype::Call,
213 text: outgoing_call_str,
214 ..Default::default()
215 };
216 call.param.set(Param::WebrtcRoom, &place_call_info);
217 call.param
218 .set_int(Param::WebrtcHasVideoInitially, has_video_initially.into());
219 call.id = send_msg(self, chat_id, &mut call).await?;
220
221 let wait = RINGING_SECONDS;
222 let context = self.get_weak_context();
223 task::spawn(Context::emit_end_call_if_unaccepted(
224 context,
225 wait.try_into()?,
226 call.id,
227 ));
228
229 Ok(call.id)
230 }
231
232 pub async fn accept_incoming_call(
234 &self,
235 call_id: MsgId,
236 accept_call_info: String,
237 ) -> Result<()> {
238 let mut call: CallInfo = self.load_call_by_id(call_id).await?.with_context(|| {
239 format!("accept_incoming_call is called with {call_id} which does not refer to a call")
240 })?;
241 ensure!(call.is_incoming());
242 if call.is_accepted() || call.is_ended() {
243 info!(self, "Call already accepted/ended");
244 return Ok(());
245 }
246
247 call.mark_as_accepted(self).await?;
248 let chat = Chat::load_from_db(self, call.msg.chat_id).await?;
249 if chat.is_contact_request() {
250 chat.id.accept(self).await?;
251 }
252
253 let mut msg = Message {
255 viewtype: Viewtype::Text,
256 text: "[Call accepted]".into(),
257 ..Default::default()
258 };
259 msg.param.set_cmd(SystemMessage::CallAccepted);
260 msg.hidden = true;
261 msg.param
262 .set(Param::WebrtcAccepted, accept_call_info.to_string());
263 msg.set_quote(self, Some(&call.msg)).await?;
264 msg.id = send_msg(self, call.msg.chat_id, &mut msg).await?;
265 self.emit_event(EventType::IncomingCallAccepted {
266 msg_id: call.msg.id,
267 chat_id: call.msg.chat_id,
268 });
269 self.emit_msgs_changed(call.msg.chat_id, call_id);
270 Ok(())
271 }
272
273 pub async fn end_call(&self, call_id: MsgId) -> Result<()> {
275 let mut call: CallInfo = self.load_call_by_id(call_id).await?.with_context(|| {
276 format!("end_call is called with {call_id} which does not refer to a call")
277 })?;
278 if call.is_ended() {
279 info!(self, "Call already ended");
280 return Ok(());
281 }
282
283 if !call.is_accepted() {
284 if call.is_incoming() {
285 call.mark_as_ended(self).await?;
286 let declined_call_str = stock_str::declined_call(self).await;
287 call.update_text(self, &declined_call_str).await?;
288 } else {
289 call.mark_as_canceled(self).await?;
290 let canceled_call_str = stock_str::canceled_call(self).await;
291 call.update_text(self, &canceled_call_str).await?;
292 }
293 } else {
294 call.mark_as_ended(self).await?;
295 call.update_text_duration(self).await?;
296 }
297
298 let mut msg = Message {
299 viewtype: Viewtype::Text,
300 text: "[Call ended]".into(),
301 ..Default::default()
302 };
303 msg.param.set_cmd(SystemMessage::CallEnded);
304 msg.hidden = true;
305 msg.set_quote(self, Some(&call.msg)).await?;
306 msg.id = send_msg(self, call.msg.chat_id, &mut msg).await?;
307
308 self.emit_event(EventType::CallEnded {
309 msg_id: call.msg.id,
310 chat_id: call.msg.chat_id,
311 });
312 self.emit_msgs_changed(call.msg.chat_id, call_id);
313 Ok(())
314 }
315
316 async fn emit_end_call_if_unaccepted(
317 context: WeakContext,
318 wait: u64,
319 call_id: MsgId,
320 ) -> Result<()> {
321 sleep(Duration::from_secs(wait)).await;
322 let context = context.upgrade()?;
323 let Some(mut call) = context.load_call_by_id(call_id).await? else {
324 warn!(
325 context,
326 "emit_end_call_if_unaccepted is called with {call_id} which does not refer to a call."
327 );
328 return Ok(());
329 };
330 if !call.is_accepted() && !call.is_ended() {
331 if call.is_incoming() {
332 call.mark_as_canceled(&context).await?;
333 let missed_call_str = stock_str::missed_call(&context).await;
334 call.update_text(&context, &missed_call_str).await?;
335 } else {
336 call.mark_as_ended(&context).await?;
337 let canceled_call_str = stock_str::canceled_call(&context).await;
338 call.update_text(&context, &canceled_call_str).await?;
339 }
340 context.emit_msgs_changed(call.msg.chat_id, call_id);
341 context.emit_event(EventType::CallEnded {
342 msg_id: call.msg.id,
343 chat_id: call.msg.chat_id,
344 });
345 }
346 Ok(())
347 }
348
349 pub(crate) async fn handle_call_msg(
350 &self,
351 call_id: MsgId,
352 mime_message: &MimeMessage,
353 from_id: ContactId,
354 ) -> Result<()> {
355 if mime_message.is_call() {
356 let Some(call) = self.load_call_by_id(call_id).await? else {
357 warn!(self, "{call_id} does not refer to a call message");
358 return Ok(());
359 };
360
361 if call.is_incoming() {
362 if call.is_stale() {
363 let missed_call_str = stock_str::missed_call(self).await;
364 call.update_text(self, &missed_call_str).await?;
365 self.emit_incoming_msg(call.msg.chat_id, call_id); } else {
367 let incoming_call_str =
368 stock_str::incoming_call(self, call.has_video_initially()).await;
369 call.update_text(self, &incoming_call_str).await?;
370 self.emit_msgs_changed(call.msg.chat_id, call_id); let can_call_me = match who_can_call_me(self).await? {
372 WhoCanCallMe::Contacts => ChatIdBlocked::lookup_by_contact(self, from_id)
373 .await?
374 .is_some_and(|chat_id_blocked| {
375 match chat_id_blocked.blocked {
376 Blocked::Not => true,
377 Blocked::Yes | Blocked::Request => {
378 false
384 }
385 }
386 }),
387 WhoCanCallMe::Everybody => ChatIdBlocked::lookup_by_contact(self, from_id)
388 .await?
389 .is_none_or(|chat_id_blocked| chat_id_blocked.blocked != Blocked::Yes),
390 WhoCanCallMe::Nobody => false,
391 };
392 if can_call_me {
393 self.emit_event(EventType::IncomingCall {
394 msg_id: call.msg.id,
395 chat_id: call.msg.chat_id,
396 place_call_info: call.place_call_info.to_string(),
397 has_video: call.has_video_initially(),
398 });
399 }
400 let wait = call.remaining_ring_seconds();
401 let context = self.get_weak_context();
402 task::spawn(Context::emit_end_call_if_unaccepted(
403 context,
404 wait.try_into()?,
405 call.msg.id,
406 ));
407 }
408 } else {
409 let outgoing_call_str =
410 stock_str::outgoing_call(self, call.has_video_initially()).await;
411 call.update_text(self, &outgoing_call_str).await?;
412 self.emit_msgs_changed(call.msg.chat_id, call_id);
413 }
414 } else {
415 match mime_message.is_system_message {
416 SystemMessage::CallAccepted => {
417 let Some(mut call) = self.load_call_by_id(call_id).await? else {
418 warn!(self, "{call_id} does not refer to a call message");
419 return Ok(());
420 };
421
422 if call.is_ended() || call.is_accepted() {
423 info!(self, "CallAccepted received for accepted/ended call");
424 return Ok(());
425 }
426
427 call.mark_as_accepted(self).await?;
428 self.emit_msgs_changed(call.msg.chat_id, call_id);
429 if call.is_incoming() {
430 self.emit_event(EventType::IncomingCallAccepted {
431 msg_id: call.msg.id,
432 chat_id: call.msg.chat_id,
433 });
434 } else {
435 let accept_call_info = mime_message
436 .get_header(HeaderDef::ChatWebrtcAccepted)
437 .unwrap_or_default();
438 self.emit_event(EventType::OutgoingCallAccepted {
439 msg_id: call.msg.id,
440 chat_id: call.msg.chat_id,
441 accept_call_info: accept_call_info.to_string(),
442 });
443 }
444 }
445 SystemMessage::CallEnded => {
446 let Some(mut call) = self.load_call_by_id(call_id).await? else {
447 warn!(self, "{call_id} does not refer to a call message");
448 return Ok(());
449 };
450
451 if call.is_ended() {
452 info!(self, "CallEnded received for ended call");
454 return Ok(());
455 }
456
457 if !call.is_accepted() {
458 if call.is_incoming() {
459 if from_id == ContactId::SELF {
460 call.mark_as_ended(self).await?;
461 let declined_call_str = stock_str::declined_call(self).await;
462 call.update_text(self, &declined_call_str).await?;
463 } else {
464 call.mark_as_canceled(self).await?;
465 let missed_call_str = stock_str::missed_call(self).await;
466 call.update_text(self, &missed_call_str).await?;
467 }
468 } else {
469 if from_id == ContactId::SELF {
471 call.mark_as_canceled(self).await?;
472 let canceled_call_str = stock_str::canceled_call(self).await;
473 call.update_text(self, &canceled_call_str).await?;
474 } else {
475 call.mark_as_ended(self).await?;
476 let declined_call_str = stock_str::declined_call(self).await;
477 call.update_text(self, &declined_call_str).await?;
478 }
479 }
480 } else {
481 call.mark_as_ended(self).await?;
482 call.update_text_duration(self).await?;
483 }
484
485 self.emit_msgs_changed(call.msg.chat_id, call_id);
486 self.emit_event(EventType::CallEnded {
487 msg_id: call.msg.id,
488 chat_id: call.msg.chat_id,
489 });
490 }
491 _ => {}
492 }
493 }
494 Ok(())
495 }
496
497 pub async fn load_call_by_id(&self, call_id: MsgId) -> Result<Option<CallInfo>> {
502 let call = Message::load_from_db(self, call_id).await?;
503 Ok(self.load_call_by_message(call))
504 }
505
506 fn load_call_by_message(&self, call: Message) -> Option<CallInfo> {
510 if call.viewtype != Viewtype::Call {
511 return None;
515 }
516
517 Some(CallInfo {
518 place_call_info: call
519 .param
520 .get(Param::WebrtcRoom)
521 .unwrap_or_default()
522 .to_string(),
523 accept_call_info: call
524 .param
525 .get(Param::WebrtcAccepted)
526 .unwrap_or_default()
527 .to_string(),
528 msg: call,
529 })
530 }
531}
532
533#[derive(Debug, PartialEq, Eq)]
535pub enum CallState {
536 Alerting,
542
543 Active,
545
546 Completed {
549 duration: i64,
551 },
552
553 Missed,
556
557 Declined,
561
562 Canceled,
569}
570
571pub async fn call_state(context: &Context, msg_id: MsgId) -> Result<CallState> {
575 let call = context
576 .load_call_by_id(msg_id)
577 .await?
578 .with_context(|| format!("{msg_id} is not a call message"))?;
579 let state = if call.is_incoming() {
580 if call.is_accepted() {
581 if call.is_ended() {
582 CallState::Completed {
583 duration: call.duration_seconds(),
584 }
585 } else {
586 CallState::Active
587 }
588 } else if call.is_canceled() {
589 CallState::Missed
592 } else if call.is_ended() {
593 CallState::Declined
594 } else if call.is_stale() {
595 CallState::Missed
596 } else {
597 CallState::Alerting
598 }
599 } else if call.is_accepted() {
600 if call.is_ended() {
601 CallState::Completed {
602 duration: call.duration_seconds(),
603 }
604 } else {
605 CallState::Active
606 }
607 } else if call.is_canceled() {
608 CallState::Canceled
609 } else if call.is_ended() || call.is_stale() {
610 CallState::Declined
611 } else {
612 CallState::Alerting
613 };
614 Ok(state)
615}
616
617#[derive(Serialize, Debug, Clone, PartialEq)]
619struct IceServer {
620 pub urls: Vec<String>,
622
623 pub username: Option<String>,
625
626 pub credential: Option<String>,
628}
629
630pub(crate) async fn create_ice_servers_from_metadata(
640 metadata: &str,
641) -> Result<(i64, Vec<UnresolvedIceServer>)> {
642 let (hostname, rest) = metadata.split_once(':').context("Missing hostname")?;
643 let (port, rest) = rest.split_once(':').context("Missing port")?;
644 let port = u16::from_str(port).context("Failed to parse the port")?;
645 let (ts, password) = rest.split_once(':').context("Missing timestamp")?;
646 let expiration_timestamp = i64::from_str(ts).context("Failed to parse the timestamp")?;
647 let ice_servers = vec![UnresolvedIceServer::Turn {
648 hostname: hostname.to_string(),
649 port,
650 username: ts.to_string(),
651 credential: password.to_string(),
652 }];
653 Ok((expiration_timestamp, ice_servers))
654}
655
656#[derive(Debug, Clone)]
658pub(crate) enum UnresolvedIceServer {
659 Stun { hostname: String, port: u16 },
661
662 Turn {
664 hostname: String,
665 port: u16,
666 username: String,
667 credential: String,
668 },
669}
670
671pub(crate) async fn resolve_ice_servers(
676 context: &Context,
677 unresolved_ice_servers: Vec<UnresolvedIceServer>,
678) -> Result<String> {
679 let mut result: Vec<IceServer> = Vec::new();
680
681 let load_cache = false;
683
684 for unresolved_ice_server in unresolved_ice_servers {
685 match unresolved_ice_server {
686 UnresolvedIceServer::Stun { hostname, port } => {
687 match lookup_host_with_cache(context, &hostname, port, "", load_cache).await {
688 Ok(addrs) => {
689 let urls: Vec<String> = addrs
690 .into_iter()
691 .map(|addr| format!("stun:{addr}"))
692 .collect();
693 let stun_server = IceServer {
694 urls,
695 username: None,
696 credential: None,
697 };
698 result.push(stun_server);
699 }
700 Err(err) => {
701 warn!(
702 context,
703 "Failed to resolve STUN {hostname}:{port}: {err:#}."
704 );
705 }
706 }
707 }
708 UnresolvedIceServer::Turn {
709 hostname,
710 port,
711 username,
712 credential,
713 } => match lookup_host_with_cache(context, &hostname, port, "", load_cache).await {
714 Ok(addrs) => {
715 let urls: Vec<String> = addrs
716 .into_iter()
717 .map(|addr| format!("turn:{addr}"))
718 .collect();
719 let turn_server = IceServer {
720 urls,
721 username: Some(username),
722 credential: Some(credential),
723 };
724 result.push(turn_server);
725 }
726 Err(err) => {
727 warn!(
728 context,
729 "Failed to resolve TURN {hostname}:{port}: {err:#}."
730 );
731 }
732 },
733 }
734 }
735 let json = serde_json::to_string(&result)?;
736 Ok(json)
737}
738
739pub(crate) fn create_fallback_ice_servers() -> Vec<UnresolvedIceServer> {
741 vec![
749 UnresolvedIceServer::Stun {
750 hostname: "nine.testrun.org".to_string(),
751 port: STUN_PORT,
752 },
753 UnresolvedIceServer::Turn {
754 hostname: "turn.delta.chat".to_string(),
755 port: STUN_PORT,
756 username: "public".to_string(),
757 credential: "o4tR7yG4rG2slhXqRUf9zgmHz".to_string(),
758 },
759 ]
760}
761
762pub async fn ice_servers(context: &Context) -> Result<String> {
772 if let Some(ref metadata) = *context.metadata.read().await {
773 let ice_servers = resolve_ice_servers(context, metadata.ice_servers.clone()).await?;
774 Ok(ice_servers)
775 } else {
776 Ok("[]".to_string())
777 }
778}
779
780#[derive(
782 Debug, Default, Display, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive, FromSql, ToSql,
783)]
784#[repr(u8)]
785pub enum WhoCanCallMe {
786 Everybody = 0,
790
791 #[default]
793 Contacts = 1,
794
795 Nobody = 2,
797}
798
799async fn who_can_call_me(context: &Context) -> Result<WhoCanCallMe> {
801 let who_can_call_me =
802 WhoCanCallMe::from_i32(context.get_config_int(Config::WhoCanCallMe).await?)
803 .unwrap_or_default();
804 Ok(who_can_call_me)
805}
806
807#[cfg(test)]
808mod calls_tests;