IceUdpTransportInfo.java

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