IceUdpTransportInfo.java

  1package eu.siacs.conversations.xmpp.jingle.stanzas;
  2
  3import androidx.annotation.NonNull;
  4
  5import com.google.common.base.Joiner;
  6import com.google.common.base.MoreObjects;
  7import com.google.common.base.Objects;
  8import com.google.common.base.Preconditions;
  9import com.google.common.base.Strings;
 10import com.google.common.collect.ArrayListMultimap;
 11import com.google.common.collect.Collections2;
 12import com.google.common.collect.ImmutableList;
 13import com.google.common.collect.Iterables;
 14
 15import java.util.ArrayList;
 16import java.util.Collection;
 17import java.util.HashMap;
 18import java.util.Hashtable;
 19import java.util.LinkedHashMap;
 20import java.util.List;
 21import java.util.Locale;
 22import java.util.Map;
 23import java.util.UUID;
 24
 25import eu.siacs.conversations.xml.Element;
 26import eu.siacs.conversations.xml.Namespace;
 27import eu.siacs.conversations.xmpp.jingle.SessionDescription;
 28
 29public class IceUdpTransportInfo extends GenericTransportInfo {
 30
 31    public IceUdpTransportInfo() {
 32        super("transport", Namespace.JINGLE_TRANSPORT_ICE_UDP);
 33    }
 34
 35    public static IceUdpTransportInfo upgrade(final Element element) {
 36        Preconditions.checkArgument("transport".equals(element.getName()), "Name of provided element is not transport");
 37        Preconditions.checkArgument(Namespace.JINGLE_TRANSPORT_ICE_UDP.equals(element.getNamespace()), "Element does not match ice-udp transport namespace");
 38        final IceUdpTransportInfo transportInfo = new IceUdpTransportInfo();
 39        transportInfo.setAttributes(element.getAttributes());
 40        transportInfo.setChildren(element.getChildren());
 41        return transportInfo;
 42    }
 43
 44    public static IceUdpTransportInfo of(SessionDescription sessionDescription, SessionDescription.Media media) {
 45        final String ufrag = Iterables.getFirst(media.attributes.get("ice-ufrag"), null);
 46        final String pwd = Iterables.getFirst(media.attributes.get("ice-pwd"), null);
 47        IceUdpTransportInfo iceUdpTransportInfo = new IceUdpTransportInfo();
 48        if (ufrag != null) {
 49            iceUdpTransportInfo.setAttribute("ufrag", ufrag);
 50        }
 51        if (pwd != null) {
 52            iceUdpTransportInfo.setAttribute("pwd", pwd);
 53        }
 54        final Fingerprint fingerprint = Fingerprint.of(sessionDescription, media);
 55        if (fingerprint != null) {
 56            iceUdpTransportInfo.addChild(fingerprint);
 57        }
 58        return iceUdpTransportInfo;
 59
 60    }
 61
 62    public Fingerprint getFingerprint() {
 63        final Element fingerprint = this.findChild("fingerprint", Namespace.JINGLE_APPS_DTLS);
 64        return fingerprint == null ? null : Fingerprint.upgrade(fingerprint);
 65    }
 66
 67    public Credentials getCredentials() {
 68        final String ufrag = this.getAttribute("ufrag");
 69        final String password = this.getAttribute("pwd");
 70        return new Credentials(ufrag, password);
 71    }
 72
 73    public List<Candidate> getCandidates() {
 74        final ImmutableList.Builder<Candidate> builder = new ImmutableList.Builder<>();
 75        for (final Element child : getChildren()) {
 76            if ("candidate".equals(child.getName())) {
 77                builder.add(Candidate.upgrade(child));
 78            }
 79        }
 80        return builder.build();
 81    }
 82
 83    public IceUdpTransportInfo cloneWrapper() {
 84        final IceUdpTransportInfo transportInfo = new IceUdpTransportInfo();
 85        transportInfo.setAttributes(new Hashtable<>(getAttributes()));
 86        return transportInfo;
 87    }
 88
 89    public IceUdpTransportInfo modifyCredentials(final Credentials credentials, final Setup setup) {
 90        final IceUdpTransportInfo transportInfo = new IceUdpTransportInfo();
 91        transportInfo.setAttribute("ufrag", credentials.ufrag);
 92        transportInfo.setAttribute("pwd", credentials.password);
 93        for (final Element child : getChildren()) {
 94            if (child.getName().equals("fingerprint") && Namespace.JINGLE_APPS_DTLS.equals(child.getNamespace())) {
 95                final Fingerprint fingerprint = new Fingerprint();
 96                fingerprint.setAttributes(new Hashtable<>(child.getAttributes()));
 97                fingerprint.setContent(child.getContent());
 98                fingerprint.setAttribute("setup", setup.toString().toLowerCase(Locale.ROOT));
 99                transportInfo.addChild(fingerprint);
100            }
101        }
102        return transportInfo;
103    }
104
105    public static class Credentials {
106        public final String ufrag;
107        public final String password;
108
109        public Credentials(String ufrag, String password) {
110            this.ufrag = ufrag;
111            this.password = password;
112        }
113
114        @Override
115        public boolean equals(Object o) {
116            if (this == o) return true;
117            if (o == null || getClass() != o.getClass()) return false;
118            Credentials that = (Credentials) o;
119            return Objects.equal(ufrag, that.ufrag) && Objects.equal(password, that.password);
120        }
121
122        @Override
123        public int hashCode() {
124            return Objects.hashCode(ufrag, password);
125        }
126
127        @Override
128        @NonNull
129        public String toString() {
130            return MoreObjects.toStringHelper(this)
131                    .add("ufrag", ufrag)
132                    .add("password", password)
133                    .toString();
134        }
135    }
136
137    public static class Candidate extends Element {
138
139        private Candidate() {
140            super("candidate");
141        }
142
143        public static Candidate upgrade(final Element element) {
144            Preconditions.checkArgument("candidate".equals(element.getName()));
145            final Candidate candidate = new Candidate();
146            candidate.setAttributes(element.getAttributes());
147            candidate.setChildren(element.getChildren());
148            return candidate;
149        }
150
151        // https://tools.ietf.org/html/draft-ietf-mmusic-ice-sip-sdp-39#section-5.1
152        public static Candidate fromSdpAttribute(final String attribute, String currentUfrag) {
153            final String[] pair = attribute.split(":", 2);
154            if (pair.length == 2 && "candidate".equals(pair[0])) {
155                final String[] segments = pair[1].split(" ");
156                if (segments.length >= 6) {
157                    final String id = UUID.randomUUID().toString();
158                    final String foundation = segments[0];
159                    final String component = segments[1];
160                    final String transport = segments[2].toLowerCase(Locale.ROOT);
161                    final String priority = segments[3];
162                    final String connectionAddress = segments[4];
163                    final String port = segments[5];
164                    final HashMap<String, String> additional = new HashMap<>();
165                    for (int i = 6; i < segments.length - 1; i = i + 2) {
166                        additional.put(segments[i], segments[i + 1]);
167                    }
168                    final String ufrag = additional.get("ufrag");
169                    if (ufrag != null && !ufrag.equals(currentUfrag)) {
170                        return null;
171                    }
172                    final Candidate candidate = new Candidate();
173                    candidate.setAttribute("component", component);
174                    candidate.setAttribute("foundation", foundation);
175                    candidate.setAttribute("generation", additional.get("generation"));
176                    candidate.setAttribute("rel-addr", additional.get("raddr"));
177                    candidate.setAttribute("rel-port", additional.get("rport"));
178                    candidate.setAttribute("id", id);
179                    candidate.setAttribute("ip", connectionAddress);
180                    candidate.setAttribute("port", port);
181                    candidate.setAttribute("priority", priority);
182                    candidate.setAttribute("protocol", transport);
183                    candidate.setAttribute("type", additional.get("typ"));
184                    return candidate;
185                }
186            }
187            return null;
188        }
189
190        public int getComponent() {
191            return getAttributeAsInt("component");
192        }
193
194        public int getFoundation() {
195            return getAttributeAsInt("foundation");
196        }
197
198        public int getGeneration() {
199            return getAttributeAsInt("generation");
200        }
201
202        public String getId() {
203            return getAttribute("id");
204        }
205
206        public String getIp() {
207            return getAttribute("ip");
208        }
209
210        public int getNetwork() {
211            return getAttributeAsInt("network");
212        }
213
214        public int getPort() {
215            return getAttributeAsInt("port");
216        }
217
218        public int getPriority() {
219            return getAttributeAsInt("priority");
220        }
221
222        public String getProtocol() {
223            return getAttribute("protocol");
224        }
225
226        public String getRelAddr() {
227            return getAttribute("rel-addr");
228        }
229
230        public int getRelPort() {
231            return getAttributeAsInt("rel-port");
232        }
233
234        public String getType() { //TODO might be converted to enum
235            return getAttribute("type");
236        }
237
238        private int getAttributeAsInt(final String name) {
239            final String value = this.getAttribute(name);
240            if (value == null) {
241                return 0;
242            }
243            try {
244                return Integer.parseInt(value);
245            } catch (NumberFormatException e) {
246                return 0;
247            }
248        }
249
250        public String toSdpAttribute(final String ufrag) {
251            final String foundation = this.getAttribute("foundation");
252            checkNotNullNoWhitespace(foundation, "foundation");
253            final String component = this.getAttribute("component");
254            checkNotNullNoWhitespace(component, "component");
255            final String protocol = this.getAttribute("protocol");
256            checkNotNullNoWhitespace(protocol, "protocol");
257            final String transport = protocol.toLowerCase(Locale.ROOT);
258            if (!"udp".equals(transport)) {
259                throw new IllegalArgumentException(String.format("'%s' is not a supported protocol", transport));
260            }
261            final String priority = this.getAttribute("priority");
262            checkNotNullNoWhitespace(priority, "priority");
263            final String connectionAddress = this.getAttribute("ip");
264            checkNotNullNoWhitespace(connectionAddress, "ip");
265            final String port = this.getAttribute("port");
266            checkNotNullNoWhitespace(port, "port");
267            final Map<String, String> additionalParameter = new LinkedHashMap<>();
268            final String relAddr = this.getAttribute("rel-addr");
269            final String type = this.getAttribute("type");
270            if (type != null) {
271                additionalParameter.put("typ", type);
272            }
273            if (relAddr != null) {
274                additionalParameter.put("raddr", relAddr);
275            }
276            final String relPort = this.getAttribute("rel-port");
277            if (relPort != null) {
278                additionalParameter.put("rport", relPort);
279            }
280            final String generation = this.getAttribute("generation");
281            if (generation != null) {
282                additionalParameter.put("generation", generation);
283            }
284            if (ufrag != null) {
285                additionalParameter.put("ufrag", ufrag);
286            }
287            final String parametersString = Joiner.on(' ').join(Collections2.transform(additionalParameter.entrySet(), input -> String.format("%s %s", input.getKey(), input.getValue())));
288            return String.format(
289                    "candidate:%s %s %s %s %s %s %s",
290                    foundation,
291                    component,
292                    transport,
293                    priority,
294                    connectionAddress,
295                    port,
296                    parametersString
297
298            );
299        }
300    }
301
302    private static void checkNotNullNoWhitespace(final String value, final String name) {
303        if (Strings.isNullOrEmpty(value)) {
304            throw new IllegalArgumentException(String.format("Parameter %s is missing or empty", name));
305        }
306        SessionDescription.checkNoWhitespace(value, String.format("Parameter %s contains white spaces", name));
307    }
308
309
310    public static class Fingerprint extends Element {
311
312        private Fingerprint() {
313            super("fingerprint", Namespace.JINGLE_APPS_DTLS);
314        }
315
316        public static Fingerprint upgrade(final Element element) {
317            Preconditions.checkArgument("fingerprint".equals(element.getName()));
318            Preconditions.checkArgument(Namespace.JINGLE_APPS_DTLS.equals(element.getNamespace()));
319            final Fingerprint fingerprint = new Fingerprint();
320            fingerprint.setAttributes(element.getAttributes());
321            fingerprint.setContent(element.getContent());
322            return fingerprint;
323        }
324
325        private static Fingerprint of(ArrayListMultimap<String, String> attributes) {
326            final String fingerprint = Iterables.getFirst(attributes.get("fingerprint"), null);
327            final String setup = Iterables.getFirst(attributes.get("setup"), null);
328            if (setup != null && fingerprint != null) {
329                final String[] fingerprintParts = fingerprint.split(" ", 2);
330                if (fingerprintParts.length == 2) {
331                    final String hash = fingerprintParts[0];
332                    final String actualFingerprint = fingerprintParts[1];
333                    final Fingerprint element = new Fingerprint();
334                    element.setAttribute("hash", hash);
335                    element.setAttribute("setup", setup);
336                    element.setContent(actualFingerprint);
337                    return element;
338                }
339            }
340            return null;
341        }
342
343        public static Fingerprint of(final SessionDescription sessionDescription, final SessionDescription.Media media) {
344            final Fingerprint fingerprint = of(media.attributes);
345            return fingerprint == null ? of(sessionDescription.attributes) : fingerprint;
346        }
347
348        public String getHash() {
349            return this.getAttribute("hash");
350        }
351
352        public Setup getSetup() {
353            final String setup = this.getAttribute("setup");
354            return setup == null ? null : Setup.of(setup);
355        }
356    }
357
358    public enum Setup {
359        ACTPASS, PASSIVE, ACTIVE;
360
361        public static Setup of(String setup) {
362            try {
363                return valueOf(setup.toUpperCase(Locale.ROOT));
364            } catch (IllegalArgumentException e) {
365                return null;
366            }
367        }
368
369        public Setup flip() {
370            if (this == PASSIVE) {
371                return ACTIVE;
372            }
373            if (this == ACTIVE) {
374                return PASSIVE;
375            }
376            throw new IllegalStateException(this.name()+" can not be flipped");
377        }
378    }
379}