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