IceServers.java

 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}