1package eu.siacs.conversations.xmpp.jingle;
2
3import java.util.ArrayList;
4import java.util.Arrays;
5import java.util.Iterator;
6import java.util.List;
7import java.util.Locale;
8import java.util.Map.Entry;
9import java.util.concurrent.ConcurrentHashMap;
10
11import android.content.Intent;
12import android.graphics.BitmapFactory;
13import android.net.Uri;
14import android.util.Log;
15import eu.siacs.conversations.entities.Account;
16import eu.siacs.conversations.entities.Conversation;
17import eu.siacs.conversations.entities.Message;
18import eu.siacs.conversations.services.XmppConnectionService;
19import eu.siacs.conversations.xml.Element;
20import eu.siacs.conversations.xmpp.OnIqPacketReceived;
21import eu.siacs.conversations.xmpp.jingle.stanzas.Content;
22import eu.siacs.conversations.xmpp.jingle.stanzas.JinglePacket;
23import eu.siacs.conversations.xmpp.jingle.stanzas.Reason;
24import eu.siacs.conversations.xmpp.stanzas.IqPacket;
25
26public class JingleConnection {
27
28 private final String[] extensions = { "webp", "jpeg", "jpg", "png" };
29 private final String[] cryptoExtensions = { "pgp", "gpg", "otr" };
30
31 private JingleConnectionManager mJingleConnectionManager;
32 private XmppConnectionService mXmppConnectionService;
33
34 public static final int STATUS_INITIATED = 0;
35 public static final int STATUS_ACCEPTED = 1;
36 public static final int STATUS_TERMINATED = 2;
37 public static final int STATUS_CANCELED = 3;
38 public static final int STATUS_FINISHED = 4;
39 public static final int STATUS_TRANSMITTING = 5;
40 public static final int STATUS_FAILED = 99;
41
42 private int ibbBlockSize = 4096;
43
44 private int status = -1;
45 private Message message;
46 private String sessionId;
47 private Account account;
48 private String initiator;
49 private String responder;
50 private List<JingleCandidate> candidates = new ArrayList<JingleCandidate>();
51 private ConcurrentHashMap<String, JingleSocks5Transport> connections = new ConcurrentHashMap<String, JingleSocks5Transport>();
52
53 private String transportId;
54 private Element fileOffer;
55 private JingleFile file = null;
56
57 private String contentName;
58 private String contentCreator;
59
60 private boolean receivedCandidate = false;
61 private boolean sentCandidate = false;
62
63 private boolean acceptedAutomatically = false;
64
65 private JingleTransport transport = null;
66
67 private OnIqPacketReceived responseListener = new OnIqPacketReceived() {
68
69 @Override
70 public void onIqPacketReceived(Account account, IqPacket packet) {
71 if (packet.getType() == IqPacket.TYPE_ERROR) {
72 if (initiator.equals(account.getFullJid())) {
73 mXmppConnectionService.markMessage(message,
74 Message.STATUS_SEND_FAILED);
75 }
76 status = STATUS_FAILED;
77 }
78 }
79 };
80
81 final OnFileTransmissionStatusChanged onFileTransmissionSatusChanged = new OnFileTransmissionStatusChanged() {
82
83 @Override
84 public void onFileTransmitted(JingleFile file) {
85 if (responder.equals(account.getFullJid())) {
86 sendSuccess();
87 if (acceptedAutomatically) {
88 message.markUnread();
89 JingleConnection.this.mXmppConnectionService.notifyUi(
90 message.getConversation(), true);
91 }
92 BitmapFactory.Options options = new BitmapFactory.Options();
93 options.inJustDecodeBounds = true;
94 BitmapFactory.decodeFile(file.getAbsolutePath(), options);
95 int imageHeight = options.outHeight;
96 int imageWidth = options.outWidth;
97 message.setBody("" + file.getSize() + "," + imageWidth + ","
98 + imageHeight);
99 mXmppConnectionService.databaseBackend.createMessage(message);
100 mXmppConnectionService.markMessage(message,
101 Message.STATUS_RECEIVED);
102 }
103 Log.d("xmppService",
104 "sucessfully transmitted file:" + file.getAbsolutePath());
105 if (message.getEncryption()!=Message.ENCRYPTION_PGP) {
106 Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
107 intent.setData(Uri.fromFile(file));
108 mXmppConnectionService.sendBroadcast(intent);
109 }
110 }
111
112 @Override
113 public void onFileTransferAborted() {
114 JingleConnection.this.sendCancel();
115 JingleConnection.this.cancel();
116 }
117 };
118
119 private OnProxyActivated onProxyActivated = new OnProxyActivated() {
120
121 @Override
122 public void success() {
123 if (initiator.equals(account.getFullJid())) {
124 Log.d("xmppService", "we were initiating. sending file");
125 transport.send(file, onFileTransmissionSatusChanged);
126 } else {
127 transport.receive(file, onFileTransmissionSatusChanged);
128 Log.d("xmppService", "we were responding. receiving file");
129 }
130 }
131
132 @Override
133 public void failed() {
134 Log.d("xmppService", "proxy activation failed");
135 }
136 };
137
138 public JingleConnection(JingleConnectionManager mJingleConnectionManager) {
139 this.mJingleConnectionManager = mJingleConnectionManager;
140 this.mXmppConnectionService = mJingleConnectionManager
141 .getXmppConnectionService();
142 }
143
144 public String getSessionId() {
145 return this.sessionId;
146 }
147
148 public String getAccountJid() {
149 return this.account.getFullJid();
150 }
151
152 public String getCounterPart() {
153 return this.message.getCounterpart();
154 }
155
156 public void deliverPacket(JinglePacket packet) {
157 boolean returnResult = true;
158 if (packet.isAction("session-terminate")) {
159 Reason reason = packet.getReason();
160 if (reason != null) {
161 if (reason.hasChild("cancel")) {
162 this.cancel();
163 } else if (reason.hasChild("success")) {
164 this.receiveSuccess();
165 } else {
166 this.cancel();
167 }
168 } else {
169 this.cancel();
170 }
171 } else if (packet.isAction("session-accept")) {
172 returnResult = receiveAccept(packet);
173 } else if (packet.isAction("transport-info")) {
174 returnResult = receiveTransportInfo(packet);
175 } else if (packet.isAction("transport-replace")) {
176 if (packet.getJingleContent().hasIbbTransport()) {
177 returnResult = this.receiveFallbackToIbb(packet);
178 } else {
179 returnResult = false;
180 Log.d("xmppService", "trying to fallback to something unknown"
181 + packet.toString());
182 }
183 } else if (packet.isAction("transport-accept")) {
184 returnResult = this.receiveTransportAccept(packet);
185 } else {
186 Log.d("xmppService", "packet arrived in connection. action was "
187 + packet.getAction());
188 returnResult = false;
189 }
190 IqPacket response;
191 if (returnResult) {
192 response = packet.generateRespone(IqPacket.TYPE_RESULT);
193
194 } else {
195 response = packet.generateRespone(IqPacket.TYPE_ERROR);
196 }
197 account.getXmppConnection().sendIqPacket(response, null);
198 }
199
200 public void init(Message message) {
201 this.contentCreator = "initiator";
202 this.contentName = this.mJingleConnectionManager.nextRandomId();
203 this.message = message;
204 this.account = message.getConversation().getAccount();
205 this.initiator = this.account.getFullJid();
206 this.responder = this.message.getCounterpart();
207 this.sessionId = this.mJingleConnectionManager.nextRandomId();
208 if (this.candidates.size() > 0) {
209 this.sendInitRequest();
210 } else {
211 this.mJingleConnectionManager.getPrimaryCandidate(account,
212 new OnPrimaryCandidateFound() {
213
214 @Override
215 public void onPrimaryCandidateFound(boolean success,
216 final JingleCandidate candidate) {
217 if (success) {
218 final JingleSocks5Transport socksConnection = new JingleSocks5Transport(
219 JingleConnection.this, candidate);
220 connections.put(candidate.getCid(),
221 socksConnection);
222 socksConnection
223 .connect(new OnTransportConnected() {
224
225 @Override
226 public void failed() {
227 Log.d("xmppService",
228 "connection to our own primary candidete failed");
229 sendInitRequest();
230 }
231
232 @Override
233 public void established() {
234 Log.d("xmppService",
235 "succesfully connected to our own primary candidate");
236 mergeCandidate(candidate);
237 sendInitRequest();
238 }
239 });
240 mergeCandidate(candidate);
241 } else {
242 Log.d("xmppService",
243 "no primary candidate of our own was found");
244 sendInitRequest();
245 }
246 }
247 });
248 }
249
250 }
251
252 public void init(Account account, JinglePacket packet) {
253 this.status = STATUS_INITIATED;
254 Conversation conversation = this.mXmppConnectionService
255 .findOrCreateConversation(account,
256 packet.getFrom().split("/")[0], false);
257 this.message = new Message(conversation, "", Message.ENCRYPTION_NONE);
258 this.message.setType(Message.TYPE_IMAGE);
259 this.message.setStatus(Message.STATUS_RECEIVED_OFFER);
260 this.message.setJingleConnection(this);
261 String[] fromParts = packet.getFrom().split("/");
262 this.message.setPresence(fromParts[1]);
263 this.account = account;
264 this.initiator = packet.getFrom();
265 this.responder = this.account.getFullJid();
266 this.sessionId = packet.getSessionId();
267 Content content = packet.getJingleContent();
268 this.contentCreator = content.getAttribute("creator");
269 this.contentName = content.getAttribute("name");
270 this.transportId = content.getTransportId();
271 this.mergeCandidates(JingleCandidate.parse(content.socks5transport()
272 .getChildren()));
273 this.fileOffer = packet.getJingleContent().getFileOffer();
274 if (fileOffer != null) {
275 Element fileSize = fileOffer.findChild("size");
276 Element fileNameElement = fileOffer.findChild("name");
277 if (fileNameElement != null) {
278 boolean supportedFile = false;
279 String[] filename = fileNameElement.getContent()
280 .toLowerCase(Locale.US).split("\\.");
281 if (Arrays.asList(this.extensions).contains(
282 filename[filename.length - 1])) {
283 supportedFile = true;
284 } else if (Arrays.asList(this.cryptoExtensions).contains(
285 filename[filename.length - 1])) {
286 if (filename.length == 3) {
287 if (Arrays.asList(this.extensions).contains(
288 filename[filename.length - 2])) {
289 supportedFile = true;
290 if (filename[filename.length - 1].equals("otr")) {
291 Log.d("xmppService", "receiving otr file");
292 this.message
293 .setEncryption(Message.ENCRYPTION_OTR);
294 } else {
295 this.message
296 .setEncryption(Message.ENCRYPTION_PGP);
297 }
298 }
299 }
300 }
301 if (supportedFile) {
302 long size = Long.parseLong(fileSize.getContent());
303 message.setBody("" + size);
304 conversation.getMessages().add(message);
305 if (size <= this.mJingleConnectionManager
306 .getAutoAcceptFileSize()) {
307 Log.d("xmppService", "auto accepting file from "
308 + packet.getFrom());
309 this.acceptedAutomatically = true;
310 this.sendAccept();
311 } else {
312 message.markUnread();
313 Log.d("xmppService",
314 "not auto accepting new file offer with size: "
315 + size
316 + " allowed size:"
317 + this.mJingleConnectionManager
318 .getAutoAcceptFileSize());
319 this.mXmppConnectionService
320 .notifyUi(conversation, true);
321 }
322 this.file = this.mXmppConnectionService.getFileBackend()
323 .getJingleFile(message, false);
324 if (message.getEncryption() == Message.ENCRYPTION_OTR) {
325 byte[] key = conversation.getSymmetricKey();
326 if (key == null) {
327 this.sendCancel();
328 this.cancel();
329 return;
330 } else {
331 this.file.setKey(key);
332 }
333 }
334 this.file.setExpectedSize(size);
335 } else {
336 this.sendCancel();
337 this.cancel();
338 }
339 } else {
340 this.sendCancel();
341 this.cancel();
342 }
343 } else {
344 this.sendCancel();
345 this.cancel();
346 }
347 }
348
349 private void sendInitRequest() {
350 JinglePacket packet = this.bootstrapPacket("session-initiate");
351 Content content = new Content(this.contentCreator, this.contentName);
352 if (message.getType() == Message.TYPE_IMAGE) {
353 content.setTransportId(this.transportId);
354 this.file = this.mXmppConnectionService.getFileBackend()
355 .getJingleFile(message, false);
356 if (message.getEncryption() == Message.ENCRYPTION_OTR) {
357 Conversation conversation = this.message.getConversation();
358 this.mXmppConnectionService.renewSymmetricKey(conversation);
359 content.setFileOffer(this.file, true);
360 this.file.setKey(conversation.getSymmetricKey());
361 } else {
362 content.setFileOffer(this.file, false);
363 }
364 this.transportId = this.mJingleConnectionManager.nextRandomId();
365 content.setTransportId(this.transportId);
366 content.socks5transport().setChildren(getCandidatesAsElements());
367 packet.setContent(content);
368 this.sendJinglePacket(packet);
369 this.status = STATUS_INITIATED;
370 }
371 }
372
373 private List<Element> getCandidatesAsElements() {
374 List<Element> elements = new ArrayList<Element>();
375 for (JingleCandidate c : this.candidates) {
376 elements.add(c.toElement());
377 }
378 return elements;
379 }
380
381 private void sendAccept() {
382 status = STATUS_ACCEPTED;
383 mXmppConnectionService.markMessage(message, Message.STATUS_RECEIVING);
384 this.mJingleConnectionManager.getPrimaryCandidate(this.account,
385 new OnPrimaryCandidateFound() {
386
387 @Override
388 public void onPrimaryCandidateFound(boolean success,
389 final JingleCandidate candidate) {
390 final JinglePacket packet = bootstrapPacket("session-accept");
391 final Content content = new Content(contentCreator,
392 contentName);
393 content.setFileOffer(fileOffer);
394 content.setTransportId(transportId);
395 if ((success) && (!equalCandidateExists(candidate))) {
396 final JingleSocks5Transport socksConnection = new JingleSocks5Transport(
397 JingleConnection.this, candidate);
398 connections.put(candidate.getCid(), socksConnection);
399 socksConnection.connect(new OnTransportConnected() {
400
401 @Override
402 public void failed() {
403 Log.d("xmppService",
404 "connection to our own primary candidate failed");
405 content.socks5transport().setChildren(
406 getCandidatesAsElements());
407 packet.setContent(content);
408 sendJinglePacket(packet);
409 connectNextCandidate();
410 }
411
412 @Override
413 public void established() {
414 Log.d("xmppService",
415 "connected to primary candidate");
416 mergeCandidate(candidate);
417 content.socks5transport().setChildren(
418 getCandidatesAsElements());
419 packet.setContent(content);
420 sendJinglePacket(packet);
421 connectNextCandidate();
422 }
423 });
424 } else {
425 Log.d("xmppService",
426 "did not find a primary candidate for ourself");
427 content.socks5transport().setChildren(
428 getCandidatesAsElements());
429 packet.setContent(content);
430 sendJinglePacket(packet);
431 connectNextCandidate();
432 }
433 }
434 });
435
436 }
437
438 private JinglePacket bootstrapPacket(String action) {
439 JinglePacket packet = new JinglePacket();
440 packet.setAction(action);
441 packet.setFrom(account.getFullJid());
442 packet.setTo(this.message.getCounterpart());
443 packet.setSessionId(this.sessionId);
444 packet.setInitiator(this.initiator);
445 return packet;
446 }
447
448 private void sendJinglePacket(JinglePacket packet) {
449 // Log.d("xmppService",packet.toString());
450 account.getXmppConnection().sendIqPacket(packet, responseListener);
451 }
452
453 private boolean receiveAccept(JinglePacket packet) {
454 Content content = packet.getJingleContent();
455 mergeCandidates(JingleCandidate.parse(content.socks5transport()
456 .getChildren()));
457 this.status = STATUS_ACCEPTED;
458 mXmppConnectionService.markMessage(message, Message.STATUS_UNSEND);
459 this.connectNextCandidate();
460 return true;
461 }
462
463 private boolean receiveTransportInfo(JinglePacket packet) {
464 Content content = packet.getJingleContent();
465 if (content.hasSocks5Transport()) {
466 if (content.socks5transport().hasChild("activated")) {
467 if ((this.transport != null)
468 && (this.transport instanceof JingleSocks5Transport)) {
469 onProxyActivated.success();
470 } else {
471 String cid = content.socks5transport()
472 .findChild("activated").getAttribute("cid");
473 Log.d("xmppService", "received proxy activated (" + cid
474 + ")prior to choosing our own transport");
475 JingleSocks5Transport connection = this.connections
476 .get(cid);
477 if (connection != null) {
478 connection.setActivated(true);
479 } else {
480 Log.d("xmppService", "activated connection not found");
481 this.sendCancel();
482 this.cancel();
483 }
484 }
485 return true;
486 } else if (content.socks5transport().hasChild("proxy-error")) {
487 onProxyActivated.failed();
488 return true;
489 } else if (content.socks5transport().hasChild("candidate-error")) {
490 Log.d("xmppService", "received candidate error");
491 this.receivedCandidate = true;
492 if ((status == STATUS_ACCEPTED) && (this.sentCandidate)) {
493 this.connect();
494 }
495 return true;
496 } else if (content.socks5transport().hasChild("candidate-used")) {
497 String cid = content.socks5transport()
498 .findChild("candidate-used").getAttribute("cid");
499 if (cid != null) {
500 Log.d("xmppService", "candidate used by counterpart:" + cid);
501 JingleCandidate candidate = getCandidate(cid);
502 candidate.flagAsUsedByCounterpart();
503 this.receivedCandidate = true;
504 if ((status == STATUS_ACCEPTED) && (this.sentCandidate)) {
505 this.connect();
506 } else {
507 Log.d("xmppService",
508 "ignoring because file is already in transmission or we havent sent our candidate yet");
509 }
510 return true;
511 } else {
512 return false;
513 }
514 } else {
515 return false;
516 }
517 } else {
518 return true;
519 }
520 }
521
522 private void connect() {
523 final JingleSocks5Transport connection = chooseConnection();
524 this.transport = connection;
525 if (connection == null) {
526 Log.d("xmppService", "could not find suitable candidate");
527 this.disconnect();
528 if (this.initiator.equals(account.getFullJid())) {
529 this.sendFallbackToIbb();
530 }
531 } else {
532 this.status = STATUS_TRANSMITTING;
533 if (connection.needsActivation()) {
534 if (connection.getCandidate().isOurs()) {
535 Log.d("xmppService", "candidate "
536 + connection.getCandidate().getCid()
537 + " was our proxy. going to activate");
538 IqPacket activation = new IqPacket(IqPacket.TYPE_SET);
539 activation.setTo(connection.getCandidate().getJid());
540 activation.query("http://jabber.org/protocol/bytestreams")
541 .setAttribute("sid", this.getSessionId());
542 activation.query().addChild("activate")
543 .setContent(this.getCounterPart());
544 this.account.getXmppConnection().sendIqPacket(activation,
545 new OnIqPacketReceived() {
546
547 @Override
548 public void onIqPacketReceived(Account account,
549 IqPacket packet) {
550 if (packet.getType() == IqPacket.TYPE_ERROR) {
551 onProxyActivated.failed();
552 } else {
553 onProxyActivated.success();
554 sendProxyActivated(connection
555 .getCandidate().getCid());
556 }
557 }
558 });
559 } else {
560 Log.d("xmppService",
561 "candidate "
562 + connection.getCandidate().getCid()
563 + " was a proxy. waiting for other party to activate");
564 }
565 } else {
566 if (initiator.equals(account.getFullJid())) {
567 Log.d("xmppService", "we were initiating. sending file");
568 connection.send(file, onFileTransmissionSatusChanged);
569 } else {
570 Log.d("xmppService", "we were responding. receiving file");
571 connection.receive(file, onFileTransmissionSatusChanged);
572 }
573 }
574 }
575 }
576
577 private JingleSocks5Transport chooseConnection() {
578 JingleSocks5Transport connection = null;
579 for (Entry<String, JingleSocks5Transport> cursor : connections
580 .entrySet()) {
581 JingleSocks5Transport currentConnection = cursor.getValue();
582 // Log.d("xmppService","comparing candidate: "+currentConnection.getCandidate().toString());
583 if (currentConnection.isEstablished()
584 && (currentConnection.getCandidate().isUsedByCounterpart() || (!currentConnection
585 .getCandidate().isOurs()))) {
586 // Log.d("xmppService","is usable");
587 if (connection == null) {
588 connection = currentConnection;
589 } else {
590 if (connection.getCandidate().getPriority() < currentConnection
591 .getCandidate().getPriority()) {
592 connection = currentConnection;
593 } else if (connection.getCandidate().getPriority() == currentConnection
594 .getCandidate().getPriority()) {
595 // Log.d("xmppService","found two candidates with same priority");
596 if (initiator.equals(account.getFullJid())) {
597 if (currentConnection.getCandidate().isOurs()) {
598 connection = currentConnection;
599 }
600 } else {
601 if (!currentConnection.getCandidate().isOurs()) {
602 connection = currentConnection;
603 }
604 }
605 }
606 }
607 }
608 }
609 return connection;
610 }
611
612 private void sendSuccess() {
613 JinglePacket packet = bootstrapPacket("session-terminate");
614 Reason reason = new Reason();
615 reason.addChild("success");
616 packet.setReason(reason);
617 this.sendJinglePacket(packet);
618 this.disconnect();
619 this.status = STATUS_FINISHED;
620 this.mXmppConnectionService.markMessage(this.message,
621 Message.STATUS_RECEIVED);
622 this.mJingleConnectionManager.finishConnection(this);
623 }
624
625 private void sendFallbackToIbb() {
626 JinglePacket packet = this.bootstrapPacket("transport-replace");
627 Content content = new Content(this.contentCreator, this.contentName);
628 this.transportId = this.mJingleConnectionManager.nextRandomId();
629 content.setTransportId(this.transportId);
630 content.ibbTransport().setAttribute("block-size",
631 "" + this.ibbBlockSize);
632 packet.setContent(content);
633 this.sendJinglePacket(packet);
634 }
635
636 private boolean receiveFallbackToIbb(JinglePacket packet) {
637 String receivedBlockSize = packet.getJingleContent().ibbTransport()
638 .getAttribute("block-size");
639 if (receivedBlockSize != null) {
640 int bs = Integer.parseInt(receivedBlockSize);
641 if (bs > this.ibbBlockSize) {
642 this.ibbBlockSize = bs;
643 }
644 }
645 this.transportId = packet.getJingleContent().getTransportId();
646 this.transport = new JingleInbandTransport(this.account,
647 this.responder, this.transportId, this.ibbBlockSize);
648 this.transport.receive(file, onFileTransmissionSatusChanged);
649 JinglePacket answer = bootstrapPacket("transport-accept");
650 Content content = new Content("initiator", "a-file-offer");
651 content.setTransportId(this.transportId);
652 content.ibbTransport().setAttribute("block-size",
653 "" + this.ibbBlockSize);
654 answer.setContent(content);
655 this.sendJinglePacket(answer);
656 return true;
657 }
658
659 private boolean receiveTransportAccept(JinglePacket packet) {
660 if (packet.getJingleContent().hasIbbTransport()) {
661 String receivedBlockSize = packet.getJingleContent().ibbTransport()
662 .getAttribute("block-size");
663 if (receivedBlockSize != null) {
664 int bs = Integer.parseInt(receivedBlockSize);
665 if (bs > this.ibbBlockSize) {
666 this.ibbBlockSize = bs;
667 }
668 }
669 this.transport = new JingleInbandTransport(this.account,
670 this.responder, this.transportId, this.ibbBlockSize);
671 this.transport.connect(new OnTransportConnected() {
672
673 @Override
674 public void failed() {
675 Log.d("xmppService", "ibb open failed");
676 }
677
678 @Override
679 public void established() {
680 JingleConnection.this.transport.send(file,
681 onFileTransmissionSatusChanged);
682 }
683 });
684 return true;
685 } else {
686 return false;
687 }
688 }
689
690 private void receiveSuccess() {
691 this.status = STATUS_FINISHED;
692 this.mXmppConnectionService.markMessage(this.message,
693 Message.STATUS_SEND);
694 this.disconnect();
695 this.mJingleConnectionManager.finishConnection(this);
696 }
697
698 public void cancel() {
699 this.status = STATUS_CANCELED;
700 this.disconnect();
701 if (this.message != null) {
702 if (this.responder.equals(account.getFullJid())) {
703 this.mXmppConnectionService.markMessage(this.message,
704 Message.STATUS_RECEPTION_FAILED);
705 } else {
706 if (this.status == STATUS_INITIATED) {
707 this.mXmppConnectionService.markMessage(this.message,
708 Message.STATUS_SEND_REJECTED);
709 } else {
710 this.mXmppConnectionService.markMessage(this.message,
711 Message.STATUS_SEND_FAILED);
712 }
713 }
714 }
715 this.mJingleConnectionManager.finishConnection(this);
716 }
717
718 private void sendCancel() {
719 JinglePacket packet = bootstrapPacket("session-terminate");
720 Reason reason = new Reason();
721 reason.addChild("cancel");
722 packet.setReason(reason);
723 this.sendJinglePacket(packet);
724 }
725
726 private void connectNextCandidate() {
727 for (JingleCandidate candidate : this.candidates) {
728 if ((!connections.containsKey(candidate.getCid()) && (!candidate
729 .isOurs()))) {
730 this.connectWithCandidate(candidate);
731 return;
732 }
733 }
734 this.sendCandidateError();
735 }
736
737 private void connectWithCandidate(final JingleCandidate candidate) {
738 final JingleSocks5Transport socksConnection = new JingleSocks5Transport(
739 this, candidate);
740 connections.put(candidate.getCid(), socksConnection);
741 socksConnection.connect(new OnTransportConnected() {
742
743 @Override
744 public void failed() {
745 Log.d("xmppService",
746 "connection failed with " + candidate.getHost() + ":"
747 + candidate.getPort());
748 connectNextCandidate();
749 }
750
751 @Override
752 public void established() {
753 Log.d("xmppService",
754 "established connection with " + candidate.getHost()
755 + ":" + candidate.getPort());
756 sendCandidateUsed(candidate.getCid());
757 }
758 });
759 }
760
761 private void disconnect() {
762 Iterator<Entry<String, JingleSocks5Transport>> it = this.connections
763 .entrySet().iterator();
764 while (it.hasNext()) {
765 Entry<String, JingleSocks5Transport> pairs = it.next();
766 pairs.getValue().disconnect();
767 it.remove();
768 }
769 }
770
771 private void sendProxyActivated(String cid) {
772 JinglePacket packet = bootstrapPacket("transport-info");
773 Content content = new Content(this.contentCreator, this.contentName);
774 content.setTransportId(this.transportId);
775 content.socks5transport().addChild("activated")
776 .setAttribute("cid", cid);
777 packet.setContent(content);
778 this.sendJinglePacket(packet);
779 }
780
781 private void sendCandidateUsed(final String cid) {
782 JinglePacket packet = bootstrapPacket("transport-info");
783 Content content = new Content(this.contentCreator, this.contentName);
784 content.setTransportId(this.transportId);
785 content.socks5transport().addChild("candidate-used")
786 .setAttribute("cid", cid);
787 packet.setContent(content);
788 this.sentCandidate = true;
789 if ((receivedCandidate) && (status == STATUS_ACCEPTED)) {
790 connect();
791 }
792 this.sendJinglePacket(packet);
793 }
794
795 private void sendCandidateError() {
796 JinglePacket packet = bootstrapPacket("transport-info");
797 Content content = new Content(this.contentCreator, this.contentName);
798 content.setTransportId(this.transportId);
799 content.socks5transport().addChild("candidate-error");
800 packet.setContent(content);
801 this.sentCandidate = true;
802 if ((receivedCandidate) && (status == STATUS_ACCEPTED)) {
803 connect();
804 }
805 this.sendJinglePacket(packet);
806 }
807
808 public String getInitiator() {
809 return this.initiator;
810 }
811
812 public String getResponder() {
813 return this.responder;
814 }
815
816 public int getStatus() {
817 return this.status;
818 }
819
820 private boolean equalCandidateExists(JingleCandidate candidate) {
821 for (JingleCandidate c : this.candidates) {
822 if (c.equalValues(candidate)) {
823 return true;
824 }
825 }
826 return false;
827 }
828
829 private void mergeCandidate(JingleCandidate candidate) {
830 for (JingleCandidate c : this.candidates) {
831 if (c.equals(candidate)) {
832 return;
833 }
834 }
835 this.candidates.add(candidate);
836 }
837
838 private void mergeCandidates(List<JingleCandidate> candidates) {
839 for (JingleCandidate c : candidates) {
840 mergeCandidate(c);
841 }
842 }
843
844 private JingleCandidate getCandidate(String cid) {
845 for (JingleCandidate c : this.candidates) {
846 if (c.getCid().equals(cid)) {
847 return c;
848 }
849 }
850 return null;
851 }
852
853 interface OnProxyActivated {
854 public void success();
855
856 public void failed();
857 }
858
859 public boolean hasTransportId(String sid) {
860 return sid.equals(this.transportId);
861 }
862
863 public JingleTransport getTransport() {
864 return this.transport;
865 }
866
867 public void accept() {
868 if (status == STATUS_INITIATED) {
869 new Thread(new Runnable() {
870
871 @Override
872 public void run() {
873 sendAccept();
874 }
875 }).start();
876 } else {
877 Log.d("xmppService", "status (" + status + ") was not ok");
878 }
879 }
880}