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