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}