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