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