IceUdpTransportInfo.java

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