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.Downloadable;
19import eu.siacs.conversations.entities.Message;
20import eu.siacs.conversations.services.XmppConnectionService;
21import eu.siacs.conversations.xml.Element;
22import eu.siacs.conversations.xmpp.OnIqPacketReceived;
23import eu.siacs.conversations.xmpp.jingle.stanzas.Content;
24import eu.siacs.conversations.xmpp.jingle.stanzas.JinglePacket;
25import eu.siacs.conversations.xmpp.jingle.stanzas.Reason;
26import eu.siacs.conversations.xmpp.stanzas.IqPacket;
27
28public class JingleConnection implements Downloadable {
29
30 private final String[] extensions = { "webp", "jpeg", "jpg", "png" };
31 private final String[] cryptoExtensions = { "pgp", "gpg", "otr" };
32
33 private JingleConnectionManager mJingleConnectionManager;
34 private XmppConnectionService mXmppConnectionService;
35
36 public static final int STATUS_INITIATED = 0;
37 public static final int STATUS_ACCEPTED = 1;
38 public static final int STATUS_TERMINATED = 2;
39 public static final int STATUS_CANCELED = 3;
40 public static final int STATUS_FINISHED = 4;
41 public static final int STATUS_TRANSMITTING = 5;
42 public static final int STATUS_FAILED = 99;
43
44 private int ibbBlockSize = 4096;
45
46 private int status = -1;
47 private Message message;
48 private String sessionId;
49 private Account account;
50 private String initiator;
51 private String responder;
52 private List<JingleCandidate> candidates = new ArrayList<JingleCandidate>();
53 private ConcurrentHashMap<String, JingleSocks5Transport> connections = new ConcurrentHashMap<String, JingleSocks5Transport>();
54
55 private String transportId;
56 private Element fileOffer;
57 private JingleFile file = null;
58
59 private String contentName;
60 private String contentCreator;
61
62 private boolean receivedCandidate = false;
63 private boolean sentCandidate = false;
64
65 private boolean acceptedAutomatically = false;
66
67 private JingleTransport transport = null;
68
69 private OnIqPacketReceived responseListener = new OnIqPacketReceived() {
70
71 @Override
72 public void onIqPacketReceived(Account account, IqPacket packet) {
73 if (packet.getType() == IqPacket.TYPE_ERROR) {
74 if (initiator.equals(account.getFullJid())) {
75 mXmppConnectionService.markMessage(message,
76 Message.STATUS_SEND_FAILED);
77 }
78 status = STATUS_FAILED;
79 }
80 }
81 };
82
83 final OnFileTransmissionStatusChanged onFileTransmissionSatusChanged = new OnFileTransmissionStatusChanged() {
84
85 @Override
86 public void onFileTransmitted(JingleFile file) {
87 if (responder.equals(account.getFullJid())) {
88 sendSuccess();
89 if (acceptedAutomatically) {
90 message.markUnread();
91 JingleConnection.this.mXmppConnectionService
92 .pushNotification(message);
93 }
94 BitmapFactory.Options options = new BitmapFactory.Options();
95 options.inJustDecodeBounds = true;
96 BitmapFactory.decodeFile(file.getAbsolutePath(), options);
97 int imageHeight = options.outHeight;
98 int imageWidth = options.outWidth;
99 message.setBody(Long.toString(file.getSize()) + ','
100 + imageWidth + ',' + imageHeight);
101 mXmppConnectionService.databaseBackend.createMessage(message);
102 mXmppConnectionService.markMessage(message,
103 Message.STATUS_RECEIVED);
104 }
105 Log.d(Config.LOGTAG,
106 "sucessfully transmitted file:" + file.getAbsolutePath());
107 if (message.getEncryption() != Message.ENCRYPTION_PGP) {
108 Intent intent = new Intent(
109 Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
110 intent.setData(Uri.fromFile(file));
111 mXmppConnectionService.sendBroadcast(intent);
112 }
113 }
114
115 @Override
116 public void onFileTransferAborted() {
117 JingleConnection.this.sendCancel();
118 JingleConnection.this.cancel();
119 }
120 };
121
122 private OnProxyActivated onProxyActivated = new OnProxyActivated() {
123
124 @Override
125 public void success() {
126 if (initiator.equals(account.getFullJid())) {
127 Log.d(Config.LOGTAG, "we were initiating. sending file");
128 transport.send(file, onFileTransmissionSatusChanged);
129 } else {
130 transport.receive(file, onFileTransmissionSatusChanged);
131 Log.d(Config.LOGTAG, "we were responding. receiving file");
132 }
133 }
134
135 @Override
136 public void failed() {
137 Log.d(Config.LOGTAG, "proxy activation failed");
138 }
139 };
140
141 public JingleConnection(JingleConnectionManager mJingleConnectionManager) {
142 this.mJingleConnectionManager = mJingleConnectionManager;
143 this.mXmppConnectionService = mJingleConnectionManager
144 .getXmppConnectionService();
145 }
146
147 public String getSessionId() {
148 return this.sessionId;
149 }
150
151 public String getAccountJid() {
152 return this.account.getFullJid();
153 }
154
155 public String getCounterPart() {
156 return this.message.getCounterpart();
157 }
158
159 public void deliverPacket(JinglePacket packet) {
160 boolean returnResult = true;
161 if (packet.isAction("session-terminate")) {
162 Reason reason = packet.getReason();
163 if (reason != null) {
164 if (reason.hasChild("cancel")) {
165 this.cancel();
166 } else if (reason.hasChild("success")) {
167 this.receiveSuccess();
168 } else {
169 this.cancel();
170 }
171 } else {
172 this.cancel();
173 }
174 } else if (packet.isAction("session-accept")) {
175 returnResult = receiveAccept(packet);
176 } else if (packet.isAction("transport-info")) {
177 returnResult = receiveTransportInfo(packet);
178 } else if (packet.isAction("transport-replace")) {
179 if (packet.getJingleContent().hasIbbTransport()) {
180 returnResult = this.receiveFallbackToIbb(packet);
181 } else {
182 returnResult = false;
183 Log.d(Config.LOGTAG, "trying to fallback to something unknown"
184 + packet.toString());
185 }
186 } else if (packet.isAction("transport-accept")) {
187 returnResult = this.receiveTransportAccept(packet);
188 } else {
189 Log.d(Config.LOGTAG, "packet arrived in connection. action was "
190 + packet.getAction());
191 returnResult = false;
192 }
193 IqPacket response;
194 if (returnResult) {
195 response = packet.generateRespone(IqPacket.TYPE_RESULT);
196
197 } else {
198 response = packet.generateRespone(IqPacket.TYPE_ERROR);
199 }
200 account.getXmppConnection().sendIqPacket(response, null);
201 }
202
203 public void init(Message message) {
204 this.contentCreator = "initiator";
205 this.contentName = this.mJingleConnectionManager.nextRandomId();
206 this.message = message;
207 this.account = message.getConversation().getAccount();
208 this.initiator = this.account.getFullJid();
209 this.responder = this.message.getCounterpart();
210 this.sessionId = this.mJingleConnectionManager.nextRandomId();
211 if (this.candidates.size() > 0) {
212 this.sendInitRequest();
213 } else {
214 this.mJingleConnectionManager.getPrimaryCandidate(account,
215 new OnPrimaryCandidateFound() {
216
217 @Override
218 public void onPrimaryCandidateFound(boolean success,
219 final JingleCandidate candidate) {
220 if (success) {
221 final JingleSocks5Transport socksConnection = new JingleSocks5Transport(
222 JingleConnection.this, candidate);
223 connections.put(candidate.getCid(),
224 socksConnection);
225 socksConnection
226 .connect(new OnTransportConnected() {
227
228 @Override
229 public void failed() {
230 Log.d(Config.LOGTAG,
231 "connection to our own primary candidete failed");
232 sendInitRequest();
233 }
234
235 @Override
236 public void established() {
237 Log.d(Config.LOGTAG,
238 "succesfully connected to our own primary candidate");
239 mergeCandidate(candidate);
240 sendInitRequest();
241 }
242 });
243 mergeCandidate(candidate);
244 } else {
245 Log.d(Config.LOGTAG,
246 "no primary candidate of our own was found");
247 sendInitRequest();
248 }
249 }
250 });
251 }
252
253 }
254
255 public void init(Account account, JinglePacket packet) {
256 this.status = STATUS_INITIATED;
257 Conversation conversation = this.mXmppConnectionService
258 .findOrCreateConversation(account,
259 packet.getFrom().split("/", 2)[0], false);
260 this.message = new Message(conversation, "", Message.ENCRYPTION_NONE);
261 this.message.setType(Message.TYPE_IMAGE);
262 this.message.setStatus(Message.STATUS_RECEIVED_OFFER);
263 this.message.setDownloadable(this);
264 String[] fromParts = packet.getFrom().split("/", 2);
265 this.message.setPresence(fromParts[1]);
266 this.account = account;
267 this.initiator = packet.getFrom();
268 this.responder = this.account.getFullJid();
269 this.sessionId = packet.getSessionId();
270 Content content = packet.getJingleContent();
271 this.contentCreator = content.getAttribute("creator");
272 this.contentName = content.getAttribute("name");
273 this.transportId = content.getTransportId();
274 this.mergeCandidates(JingleCandidate.parse(content.socks5transport()
275 .getChildren()));
276 this.fileOffer = packet.getJingleContent().getFileOffer();
277 if (fileOffer != null) {
278 Element fileSize = fileOffer.findChild("size");
279 Element fileNameElement = fileOffer.findChild("name");
280 if (fileNameElement != null) {
281 boolean supportedFile = false;
282 String[] filename = fileNameElement.getContent()
283 .toLowerCase(Locale.US).split("\\.");
284 if (Arrays.asList(this.extensions).contains(
285 filename[filename.length - 1])) {
286 supportedFile = true;
287 } else if (Arrays.asList(this.cryptoExtensions).contains(
288 filename[filename.length - 1])) {
289 if (filename.length == 3) {
290 if (Arrays.asList(this.extensions).contains(
291 filename[filename.length - 2])) {
292 supportedFile = true;
293 if (filename[filename.length - 1].equals("otr")) {
294 Log.d(Config.LOGTAG, "receiving otr file");
295 this.message
296 .setEncryption(Message.ENCRYPTION_OTR);
297 } else {
298 this.message
299 .setEncryption(Message.ENCRYPTION_PGP);
300 }
301 }
302 }
303 }
304 if (supportedFile) {
305 long size = Long.parseLong(fileSize.getContent());
306 message.setBody(Long.toString(size));
307 conversation.getMessages().add(message);
308 if (size <= this.mJingleConnectionManager
309 .getAutoAcceptFileSize()) {
310 Log.d(Config.LOGTAG, "auto accepting file from "
311 + packet.getFrom());
312 this.acceptedAutomatically = true;
313 this.sendAccept();
314 } else {
315 message.markUnread();
316 Log.d(Config.LOGTAG,
317 "not auto accepting new file offer with size: "
318 + size
319 + " allowed size:"
320 + this.mJingleConnectionManager
321 .getAutoAcceptFileSize());
322 this.mXmppConnectionService.pushNotification(message);
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 start() {
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}