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