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}