1package eu.siacs.conversations.xmpp.jingle;
2
3import android.util.Log;
4import com.google.common.base.Strings;
5import com.google.common.collect.ImmutableSet;
6import com.google.common.primitives.Ints;
7import eu.siacs.conversations.Config;
8import eu.siacs.conversations.utils.IP;
9import eu.siacs.conversations.xml.Element;
10import eu.siacs.conversations.xml.Namespace;
11import im.conversations.android.xmpp.model.stanza.Iq;
12import java.util.Arrays;
13import java.util.Collections;
14import java.util.List;
15import java.util.Set;
16import org.webrtc.PeerConnection;
17
18public final class IceServers {
19
20 public static Set<PeerConnection.IceServer> parse(final Iq response) {
21 if (response.getType() != Iq.Type.RESULT) {
22 return Collections.emptySet();
23 }
24 final var builder = new ImmutableSet.Builder<PeerConnection.IceServer>();
25 final Element services =
26 response.findChild("services", Namespace.EXTERNAL_SERVICE_DISCOVERY);
27 final List<Element> children =
28 services == null ? Collections.emptyList() : services.getChildren();
29 for (final Element child : children) {
30 if ("service".equals(child.getName())) {
31 final String type = child.getAttribute("type");
32 final String host = child.getAttribute("host");
33 final String sport = child.getAttribute("port");
34 final Integer port = sport == null ? null : Ints.tryParse(sport);
35 final String transport = child.getAttribute("transport");
36 final String username = child.getAttribute("username");
37 final String password = child.getAttribute("password");
38 if (Strings.isNullOrEmpty(host) || port == null) {
39 continue;
40 }
41 if (port < 0 || port > 65535) {
42 continue;
43 }
44
45 if (Arrays.asList("stun", "stuns", "turn", "turns").contains(type)
46 && Arrays.asList("udp", "tcp").contains(transport)) {
47 if (Arrays.asList("stuns", "turns").contains(type) && "udp".equals(transport)) {
48 Log.w(
49 Config.LOGTAG,
50 "skipping invalid combination of udp/tls in external services");
51 continue;
52 }
53
54 // STUN URLs do not support a query section since M110
55 final String uri;
56 if (Arrays.asList("stun", "stuns").contains(type)) {
57 uri = String.format("%s:%s:%s", type, IP.wrapIPv6(host), port);
58 } else {
59 uri =
60 String.format(
61 "%s:%s:%s?transport=%s",
62 type, IP.wrapIPv6(host), port, transport);
63 }
64
65 final PeerConnection.IceServer.Builder iceServerBuilder =
66 PeerConnection.IceServer.builder(uri);
67 iceServerBuilder.setTlsCertPolicy(
68 PeerConnection.TlsCertPolicy.TLS_CERT_POLICY_INSECURE_NO_CHECK);
69 if (username != null && password != null) {
70 iceServerBuilder.setUsername(username);
71 iceServerBuilder.setPassword(password);
72 } else if (Arrays.asList("turn", "turns").contains(type)) {
73 // The WebRTC spec requires throwing an
74 // InvalidAccessError when username (from libwebrtc
75 // source coder)
76 // https://chromium.googlesource.com/external/webrtc/+/master/pc/ice_server_parsing.cc
77 Log.w(
78 Config.LOGTAG,
79 "skipping "
80 + type
81 + "/"
82 + transport
83 + " without username and password");
84 continue;
85 }
86 final PeerConnection.IceServer iceServer = iceServerBuilder.createIceServer();
87 Log.w(Config.LOGTAG, "discovered ICE Server: " + iceServer);
88 builder.add(iceServer);
89 }
90 }
91 }
92 return builder.build();
93 }
94}