1package im.conversations.android.xmpp.model.disco.external;
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 im.conversations.android.annotation.XmlElement;
10import im.conversations.android.xmpp.model.Extension;
11import java.util.Arrays;
12import java.util.Collection;
13import java.util.Set;
14import org.webrtc.PeerConnection;
15
16@XmlElement
17public class Services extends Extension {
18
19 public Services() {
20 super(Services.class);
21 }
22
23 public Collection<Service> getServices() {
24 return this.getExtensions(Service.class);
25 }
26
27 public Set<PeerConnection.IceServer> getIceServers() {
28 final var builder = new ImmutableSet.Builder<PeerConnection.IceServer>();
29 for (final var service : this.getServices()) {
30 final String type = service.getAttribute("type");
31 final String host = service.getAttribute("host");
32 final String sport = service.getAttribute("port");
33 final Integer port = sport == null ? null : Ints.tryParse(sport);
34 final String transport = service.getAttribute("transport");
35 final String username = service.getAttribute("username");
36 final String password = service.getAttribute("password");
37 if (Strings.isNullOrEmpty(host) || port == null) {
38 continue;
39 }
40 if (port < 0 || port > 65535) {
41 continue;
42 }
43
44 if (Arrays.asList("stun", "stuns", "turn", "turns").contains(type)
45 && Arrays.asList("udp", "tcp").contains(transport)) {
46 if (Arrays.asList("stuns", "turns").contains(type) && "udp".equals(transport)) {
47 Log.w(
48 Config.LOGTAG,
49 "skipping invalid combination of udp/tls in external services");
50 continue;
51 }
52
53 // STUN URLs do not support a query section since M110
54 final String uri;
55 if (Arrays.asList("stun", "stuns").contains(type)) {
56 uri = String.format("%s:%s:%s", type, IP.wrapIPv6(host), port);
57 } else {
58 uri =
59 String.format(
60 "%s:%s:%s?transport=%s",
61 type, IP.wrapIPv6(host), port, transport);
62 }
63
64 final PeerConnection.IceServer.Builder iceServerBuilder =
65 PeerConnection.IceServer.builder(uri);
66 iceServerBuilder.setTlsCertPolicy(
67 PeerConnection.TlsCertPolicy.TLS_CERT_POLICY_INSECURE_NO_CHECK);
68 if (username != null && password != null) {
69 iceServerBuilder.setUsername(username);
70 iceServerBuilder.setPassword(password);
71 } else if (Arrays.asList("turn", "turns").contains(type)) {
72 // The WebRTC spec requires throwing an
73 // InvalidAccessError when username (from libwebrtc
74 // source coder)
75 // https://chromium.googlesource.com/external/webrtc/+/master/pc/ice_server_parsing.cc
76 Log.w(
77 Config.LOGTAG,
78 "skipping "
79 + type
80 + "/"
81 + transport
82 + " without username and password");
83 continue;
84 }
85 final PeerConnection.IceServer iceServer = iceServerBuilder.createIceServer();
86 Log.w(Config.LOGTAG, "discovered ICE Server: " + iceServer);
87 builder.add(iceServer);
88 }
89 }
90 return builder.build();
91 }
92}