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