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}