deltachat/
calls.rs

1//! # Handle calls.
2//!
3//! Internally, calls are bound a user-visible message initializing the call.
4//! This means, the "Call ID" is a "Message ID" - similar to Webxdc IDs.
5use 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
29/// How long callee's or caller's phone ring.
30///
31/// For the callee, this is to prevent endless ringing
32/// in case the initial "call" is received, but then the caller went offline.
33/// Moreover, this prevents outdated calls to ring
34/// in case the initial "call" message arrives delayed.
35///
36/// For the caller, this means they should also not wait longer,
37/// as the callee won't start the call afterwards.
38const RINGING_SECONDS: i64 = 120;
39
40// For persisting parameters in the call, we use Param::Arg*
41
42const CALL_ACCEPTED_TIMESTAMP: Param = Param::Arg;
43const CALL_ENDED_TIMESTAMP: Param = Param::Arg4;
44
45const STUN_PORT: u16 = 3478;
46
47/// Set if incoming call was ended explicitly
48/// by the other side before we accepted it.
49///
50/// It is used to distinguish "ended" calls
51/// that are rejected by us from the calls
52/// canceled by the other side
53/// immediately after ringing started.
54const CALL_CANCELED_TIMESTAMP: Param = Param::Arg2;
55
56/// Information about the status of a call.
57#[derive(Debug, Default)]
58pub struct CallInfo {
59    /// User-defined text as given to place_outgoing_call()
60    pub place_call_info: String,
61
62    /// User-defined text as given to accept_incoming_call()
63    pub accept_call_info: String,
64
65    /// Message referring to the call.
66    /// Data are persisted along with the message using Param::Arg*
67    pub msg: Message,
68}
69
70impl CallInfo {
71    /// Returns true if the call is an incoming call.
72    pub fn is_incoming(&self) -> bool {
73        self.msg.from_id != ContactId::SELF
74    }
75
76    /// Returns true if the call should not ring anymore.
77    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    /// Mark calls as accepted.
121    /// This is needed for all devices where a stale-timer runs, to prevent accepted calls being terminated as stale.
122    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    /// Returns true if the call is accepted.
129    pub fn is_accepted(&self) -> bool {
130        self.msg.param.exists(CALL_ACCEPTED_TIMESTAMP)
131    }
132
133    /// Returns true if the call is started as a video call.
134    pub fn has_video_initially(&self) -> bool {
135        self.msg
136            .param
137            .get_bool(Param::WebrtcHasVideoInitially)
138            .unwrap_or(false)
139    }
140
141    /// Returns true if the call is missed
142    /// because the caller canceled it
143    /// explicitly before ringing stopped.
144    ///
145    /// For outgoing calls this means
146    /// the receiver has rejected the call
147    /// explicitly.
148    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    /// Explicitly mark the call as canceled.
159    ///
160    /// For incoming calls this should be called
161    /// when "call ended" message is received
162    /// from the caller before we picked up the call.
163    /// In this case the call becomes "missed" early
164    /// before the ringing timeout.
165    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    /// Returns true if the call is ended.
174    pub fn is_ended(&self) -> bool {
175        self.msg.param.exists(CALL_ENDED_TIMESTAMP)
176    }
177
178    /// Returns call duration in seconds.
179    #[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    /// Start an outgoing call.
197    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    /// Accept an incoming call.
233    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        // send an acceptance message around: to the caller as well as to the other devices of the callee
254        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    /// Cancel, decline or hangup an incoming or outgoing call.
274    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); // notify missed call
366                } 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); // ringing calls are not additionally notified
371                    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                                        // Do not notify about incoming calls
379                                        // from contact requests and blocked contacts.
380                                        //
381                                        // User can still access the call and accept it
382                                        // via the chat in case of contact requests.
383                                        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                        // may happen eg. if a a message is missed
453                        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                            // outgoing
470                            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    /// Loads information about the call given its ID.
498    ///
499    /// If the message referred to by ID is
500    /// not a call message, returns `None`.
501    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    // Loads information about the call given the `Message`.
507    //
508    // If the `Message` is not a call message, returns `None`
509    fn load_call_by_message(&self, call: Message) -> Option<CallInfo> {
510        if call.viewtype != Viewtype::Call {
511            // This can happen e.g. if a "call accepted"
512            // or "call ended" message is received
513            // with `In-Reply-To` referring to non-call message.
514            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/// State of the call for display in the message bubble.
534#[derive(Debug, PartialEq, Eq)]
535pub enum CallState {
536    /// Fresh incoming or outgoing call that is still ringing.
537    ///
538    /// There is no separate state for outgoing call
539    /// that has been dialled but not ringing on the other side yet
540    /// as we don't know whether the other side received our call.
541    Alerting,
542
543    /// Active call.
544    Active,
545
546    /// Completed call that was once active
547    /// and then was terminated for any reason.
548    Completed {
549        /// Call duration in seconds.
550        duration: i64,
551    },
552
553    /// Incoming call that was not picked up within a timeout
554    /// or was explicitly ended by the caller before we picked up.
555    Missed,
556
557    /// Incoming call that was explicitly ended on our side
558    /// before picking up or outgoing call
559    /// that was declined before the timeout.
560    Declined,
561
562    /// Outgoing call that has been canceled on our side
563    /// before receiving a response.
564    ///
565    /// Incoming calls cannot be canceled,
566    /// on the receiver side canceled calls
567    /// usually result in missed calls.
568    Canceled,
569}
570
571/// Returns call state given the message ID.
572///
573/// Returns an error if the message is not a call message.
574pub 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            // Call was explicitly canceled
590            // by the caller before we picked it up.
591            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/// ICE server for JSON serialization.
618#[derive(Serialize, Debug, Clone, PartialEq)]
619struct IceServer {
620    /// STUN or TURN URLs.
621    pub urls: Vec<String>,
622
623    /// Username for TURN server authentication.
624    pub username: Option<String>,
625
626    /// Password for logging into the server.
627    pub credential: Option<String>,
628}
629
630/// Creates ICE servers from a line received over IMAP METADATA.
631///
632/// IMAP METADATA returns a line such as
633/// `example.com:3478:1758650868:8Dqkyyu11MVESBqjbIylmB06rv8=`
634///
635/// 1758650868 is the username and expiration timestamp
636/// at the same time,
637/// while `8Dqkyyu11MVESBqjbIylmB06rv8=`
638/// is the password.
639pub(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/// STUN or TURN server with unresolved DNS name.
657#[derive(Debug, Clone)]
658pub(crate) enum UnresolvedIceServer {
659    /// STUN server.
660    Stun { hostname: String, port: u16 },
661
662    /// TURN server with the username and password.
663    Turn {
664        hostname: String,
665        port: u16,
666        username: String,
667        credential: String,
668    },
669}
670
671/// Resolves domain names of ICE servers.
672///
673/// On failure to resolve, logs the error
674/// and skips the server, but does not fail.
675pub(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    // Do not use cache because there is no TLS.
682    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
739/// Creates JSON with ICE servers when no TURN servers are known.
740pub(crate) fn create_fallback_ice_servers() -> Vec<UnresolvedIceServer> {
741    // Do not use public STUN server from https://stunprotocol.org/.
742    // It changes the hostname every year
743    // (e.g. stunserver2025.stunprotocol.org
744    // which was previously stunserver2024.stunprotocol.org)
745    // because of bandwidth costs:
746    // <https://github.com/jselbie/stunserver/issues/50>
747
748    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
762/// Returns JSON with ICE servers.
763///
764/// <https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/RTCPeerConnection#iceservers>
765///
766/// All returned servers are resolved to their IP addresses.
767/// The primary point of DNS lookup is that Delta Chat Desktop
768/// relies on the servers being specified by IP,
769/// because it itself cannot utilize DNS. See
770/// <https://github.com/deltachat/deltachat-desktop/issues/5447>.
771pub 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/// "Who can call me" config options.
781#[derive(
782    Debug, Default, Display, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive, FromSql, ToSql,
783)]
784#[repr(u8)]
785pub enum WhoCanCallMe {
786    /// Everybody can call me if they are not blocked.
787    ///
788    /// This includes contact requests.
789    Everybody = 0,
790
791    /// Every contact who is not blocked and not a contact request, can call.
792    #[default]
793    Contacts = 1,
794
795    /// Nobody can call me.
796    Nobody = 2,
797}
798
799/// Returns currently configuration of the "who can call me" option.
800async 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;