IceServers.java

 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}