diff --git a/deltachat-jsonrpc/src/api.rs b/deltachat-jsonrpc/src/api.rs index 595a13ac5..034be6457 100644 --- a/deltachat-jsonrpc/src/api.rs +++ b/deltachat-jsonrpc/src/api.rs @@ -8,6 +8,7 @@ use std::{collections::HashMap, str::FromStr}; use anyhow::{anyhow, bail, ensure, Context, Result}; pub use deltachat::accounts::Accounts; use deltachat::blob::BlobObject; +use deltachat::calls::ice_servers; use deltachat::chat::{ self, add_contact_to_chat, forward_msgs, get_chat_media, get_chat_msgs, get_chat_msgs_ex, marknoticed_chat, remove_contact_from_chat, Chat, ChatId, ChatItem, MessageListOptions, @@ -2111,6 +2112,12 @@ impl CommandApi { Ok(()) } + /// Returns JSON with ICE servers, to be used for WebRTC video calls. + async fn ice_servers(&self, account_id: u32) -> Result { + let ctx = self.get_context(account_id).await?; + ice_servers(&ctx).await + } + /// Makes an HTTP GET request and returns a response. /// /// `url` is the HTTP or HTTPS URL. diff --git a/deltachat-rpc-client/src/deltachat_rpc_client/account.py b/deltachat-rpc-client/src/deltachat_rpc_client/account.py index 6f919f71e..e1f066720 100644 --- a/deltachat-rpc-client/src/deltachat_rpc_client/account.py +++ b/deltachat-rpc-client/src/deltachat_rpc_client/account.py @@ -2,6 +2,7 @@ from __future__ import annotations +import json from dataclasses import dataclass from typing import TYPE_CHECKING, Optional, Union from warnings import warn @@ -470,3 +471,8 @@ class Account: def initiate_autocrypt_key_transfer(self) -> None: """Send Autocrypt Setup Message.""" return self._rpc.initiate_autocrypt_key_transfer(self.id) + + def ice_servers(self) -> list: + """Return ICE servers for WebRTC configuration.""" + ice_servers_json = self._rpc.ice_servers(self.id) + return json.loads(ice_servers_json) diff --git a/deltachat-rpc-client/tests/test_calls.py b/deltachat-rpc-client/tests/test_calls.py index f1c2bfebb..66ac99729 100644 --- a/deltachat-rpc-client/tests/test_calls.py +++ b/deltachat-rpc-client/tests/test_calls.py @@ -66,3 +66,10 @@ a=extmap:1 urn:ietf:params:rtp-hdrext:sdes:mid\r incoming_call_event = bob.wait_for_event(EventType.INCOMING_CALL) assert incoming_call_event.place_call_info == place_call_info assert incoming_call_event.has_video + + +def test_ice_servers(acfactory) -> None: + alice = acfactory.get_online_account() + + ice_servers = alice.ice_servers() + assert len(ice_servers) == 1 diff --git a/src/calls.rs b/src/calls.rs index 6978d8ce5..1b8e3ca75 100644 --- a/src/calls.rs +++ b/src/calls.rs @@ -11,10 +11,12 @@ use crate::headerdef::HeaderDef; use crate::log::{info, warn}; use crate::message::{self, Message, MsgId, Viewtype}; use crate::mimeparser::{MimeMessage, SystemMessage}; +use crate::net::dns::lookup_host_with_cache; use crate::param::Param; use crate::tools::time; use anyhow::{Context as _, Result, ensure}; use sdp::SessionDescription; +use serde::Serialize; use std::io::Cursor; use std::time::Duration; use tokio::task; @@ -393,5 +395,51 @@ fn sdp_has_video(sdp: &str) -> Result { Ok(false) } +/// ICE server for JSON serialization. +#[derive(Serialize, Debug, Clone, PartialEq)] +struct IceServer { + /// STUN or TURN URLs. + pub urls: Vec, + + /// Username for TURN server authentication. + pub username: Option, + + /// Password for logging into the server. + pub credential: Option, +} + +/// Returns JSON with ICE servers. +/// +/// +/// +/// All returned servers are resolved to their IP addresses. +/// The primary point of DNS lookup is that Delta Chat Desktop +/// relies on the servers being specified by IP, +/// because it itself cannot utilize DNS. See +/// . +pub async fn ice_servers(context: &Context) -> Result { + let hostname = "ci-chatmail.testrun.org"; + let port = 3478; + let username = "ohV8aec1".to_string(); + let password = "zo3theiY".to_string(); + + // Do not use cache because there is no TLS. + let load_cache = false; + let urls: Vec = lookup_host_with_cache(context, hostname, port, "", load_cache) + .await? + .into_iter() + .map(|addr| format!("turn:{addr}")) + .collect(); + + let ice_server = IceServer { + urls, + username: Some(username), + credential: Some(password), + }; + + let json = serde_json::to_string(&[ice_server])?; + Ok(json) +} + #[cfg(test)] mod calls_tests;