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 .getNotificationService().push(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.getNotificationService()
323 .push(message);
324 }
325 this.file = this.mXmppConnectionService.getFileBackend()
326 .getJingleFile(message, false);
327 if (message.getEncryption() == Message.ENCRYPTION_OTR) {
328 byte[] key = conversation.getSymmetricKey();
329 if (key == null) {
330 this.sendCancel();
331 this.cancel();
332 return;
333 } else {
334 this.file.setKey(key);
335 }
336 }
337 this.file.setExpectedSize(size);
338 } else {
339 this.sendCancel();
340 this.cancel();
341 }
342 } else {
343 this.sendCancel();
344 this.cancel();
345 }
346 } else {
347 this.sendCancel();
348 this.cancel();
349 }
350 }
351
352 private void sendInitRequest() {
353 JinglePacket packet = this.bootstrapPacket("session-initiate");
354 Content content = new Content(this.contentCreator, this.contentName);
355 if (message.getType() == Message.TYPE_IMAGE) {
356 content.setTransportId(this.transportId);
357 this.file = this.mXmppConnectionService.getFileBackend()
358 .getJingleFile(message, false);
359 if (message.getEncryption() == Message.ENCRYPTION_OTR) {
360 Conversation conversation = this.message.getConversation();
361 this.mXmppConnectionService.renewSymmetricKey(conversation);
362 content.setFileOffer(this.file, true);
363 this.file.setKey(conversation.getSymmetricKey());
364 } else {
365 content.setFileOffer(this.file, false);
366 }
367 this.transportId = this.mJingleConnectionManager.nextRandomId();
368 content.setTransportId(this.transportId);
369 content.socks5transport().setChildren(getCandidatesAsElements());
370 packet.setContent(content);
371 this.sendJinglePacket(packet);
372 this.status = STATUS_INITIATED;
373 }
374 }
375
376 private List<Element> getCandidatesAsElements() {
377 List<Element> elements = new ArrayList<Element>();
378 for (JingleCandidate c : this.candidates) {
379 elements.add(c.toElement());
380 }
381 return elements;
382 }
383
384 private void sendAccept() {
385 status = STATUS_ACCEPTED;
386 mXmppConnectionService.markMessage(message, Message.STATUS_RECEIVING);
387 this.mJingleConnectionManager.getPrimaryCandidate(this.account,
388 new OnPrimaryCandidateFound() {
389
390 @Override
391 public void onPrimaryCandidateFound(boolean success,
392 final JingleCandidate candidate) {
393 final JinglePacket packet = bootstrapPacket("session-accept");
394 final Content content = new Content(contentCreator,
395 contentName);
396 content.setFileOffer(fileOffer);
397 content.setTransportId(transportId);
398 if ((success) && (!equalCandidateExists(candidate))) {
399 final JingleSocks5Transport socksConnection = new JingleSocks5Transport(
400 JingleConnection.this, candidate);
401 connections.put(candidate.getCid(), socksConnection);
402 socksConnection.connect(new OnTransportConnected() {
403
404 @Override
405 public void failed() {
406 Log.d(Config.LOGTAG,
407 "connection to our own primary candidate failed");
408 content.socks5transport().setChildren(
409 getCandidatesAsElements());
410 packet.setContent(content);
411 sendJinglePacket(packet);
412 connectNextCandidate();
413 }
414
415 @Override
416 public void established() {
417 Log.d(Config.LOGTAG,
418 "connected to primary candidate");
419 mergeCandidate(candidate);
420 content.socks5transport().setChildren(
421 getCandidatesAsElements());
422 packet.setContent(content);
423 sendJinglePacket(packet);
424 connectNextCandidate();
425 }
426 });
427 } else {
428 Log.d(Config.LOGTAG,
429 "did not find a primary candidate for ourself");
430 content.socks5transport().setChildren(
431 getCandidatesAsElements());
432 packet.setContent(content);
433 sendJinglePacket(packet);
434 connectNextCandidate();
435 }
436 }
437 });
438
439 }
440
441 private JinglePacket bootstrapPacket(String action) {
442 JinglePacket packet = new JinglePacket();
443 packet.setAction(action);
444 packet.setFrom(account.getFullJid());
445 packet.setTo(this.message.getCounterpart());
446 packet.setSessionId(this.sessionId);
447 packet.setInitiator(this.initiator);
448 return packet;
449 }
450
451 private void sendJinglePacket(JinglePacket packet) {
452 // Log.d(Config.LOGTAG,packet.toString());
453 account.getXmppConnection().sendIqPacket(packet, responseListener);
454 }
455
456 private boolean receiveAccept(JinglePacket packet) {
457 Content content = packet.getJingleContent();
458 mergeCandidates(JingleCandidate.parse(content.socks5transport()
459 .getChildren()));
460 this.status = STATUS_ACCEPTED;
461 mXmppConnectionService.markMessage(message, Message.STATUS_UNSEND);
462 this.connectNextCandidate();
463 return true;
464 }
465
466 private boolean receiveTransportInfo(JinglePacket packet) {
467 Content content = packet.getJingleContent();
468 if (content.hasSocks5Transport()) {
469 if (content.socks5transport().hasChild("activated")) {
470 if ((this.transport != null)
471 && (this.transport instanceof JingleSocks5Transport)) {
472 onProxyActivated.success();
473 } else {
474 String cid = content.socks5transport()
475 .findChild("activated").getAttribute("cid");
476 Log.d(Config.LOGTAG, "received proxy activated (" + cid
477 + ")prior to choosing our own transport");
478 JingleSocks5Transport connection = this.connections
479 .get(cid);
480 if (connection != null) {
481 connection.setActivated(true);
482 } else {
483 Log.d(Config.LOGTAG, "activated connection not found");
484 this.sendCancel();
485 this.cancel();
486 }
487 }
488 return true;
489 } else if (content.socks5transport().hasChild("proxy-error")) {
490 onProxyActivated.failed();
491 return true;
492 } else if (content.socks5transport().hasChild("candidate-error")) {
493 Log.d(Config.LOGTAG, "received candidate error");
494 this.receivedCandidate = true;
495 if ((status == STATUS_ACCEPTED) && (this.sentCandidate)) {
496 this.connect();
497 }
498 return true;
499 } else if (content.socks5transport().hasChild("candidate-used")) {
500 String cid = content.socks5transport()
501 .findChild("candidate-used").getAttribute("cid");
502 if (cid != null) {
503 Log.d(Config.LOGTAG, "candidate used by counterpart:" + cid);
504 JingleCandidate candidate = getCandidate(cid);
505 candidate.flagAsUsedByCounterpart();
506 this.receivedCandidate = true;
507 if ((status == STATUS_ACCEPTED) && (this.sentCandidate)) {
508 this.connect();
509 } else {
510 Log.d(Config.LOGTAG,
511 "ignoring because file is already in transmission or we havent sent our candidate yet");
512 }
513 return true;
514 } else {
515 return false;
516 }
517 } else {
518 return false;
519 }
520 } else {
521 return true;
522 }
523 }
524
525 private void connect() {
526 final JingleSocks5Transport connection = chooseConnection();
527 this.transport = connection;
528 if (connection == null) {
529 Log.d(Config.LOGTAG, "could not find suitable candidate");
530 this.disconnect();
531 if (this.initiator.equals(account.getFullJid())) {
532 this.sendFallbackToIbb();
533 }
534 } else {
535 this.status = STATUS_TRANSMITTING;
536 if (connection.needsActivation()) {
537 if (connection.getCandidate().isOurs()) {
538 Log.d(Config.LOGTAG, "candidate "
539 + connection.getCandidate().getCid()
540 + " was our proxy. going to activate");
541 IqPacket activation = new IqPacket(IqPacket.TYPE_SET);
542 activation.setTo(connection.getCandidate().getJid());
543 activation.query("http://jabber.org/protocol/bytestreams")
544 .setAttribute("sid", this.getSessionId());
545 activation.query().addChild("activate")
546 .setContent(this.getCounterPart());
547 this.account.getXmppConnection().sendIqPacket(activation,
548 new OnIqPacketReceived() {
549
550 @Override
551 public void onIqPacketReceived(Account account,
552 IqPacket packet) {
553 if (packet.getType() == IqPacket.TYPE_ERROR) {
554 onProxyActivated.failed();
555 } else {
556 onProxyActivated.success();
557 sendProxyActivated(connection
558 .getCandidate().getCid());
559 }
560 }
561 });
562 } else {
563 Log.d(Config.LOGTAG,
564 "candidate "
565 + connection.getCandidate().getCid()
566 + " was a proxy. waiting for other party to activate");
567 }
568 } else {
569 if (initiator.equals(account.getFullJid())) {
570 Log.d(Config.LOGTAG, "we were initiating. sending file");
571 connection.send(file, onFileTransmissionSatusChanged);
572 } else {
573 Log.d(Config.LOGTAG, "we were responding. receiving file");
574 connection.receive(file, onFileTransmissionSatusChanged);
575 }
576 }
577 }
578 }
579
580 private JingleSocks5Transport chooseConnection() {
581 JingleSocks5Transport connection = null;
582 for (Entry<String, JingleSocks5Transport> cursor : connections
583 .entrySet()) {
584 JingleSocks5Transport currentConnection = cursor.getValue();
585 // Log.d(Config.LOGTAG,"comparing candidate: "+currentConnection.getCandidate().toString());
586 if (currentConnection.isEstablished()
587 && (currentConnection.getCandidate().isUsedByCounterpart() || (!currentConnection
588 .getCandidate().isOurs()))) {
589 // Log.d(Config.LOGTAG,"is usable");
590 if (connection == null) {
591 connection = currentConnection;
592 } else {
593 if (connection.getCandidate().getPriority() < currentConnection
594 .getCandidate().getPriority()) {
595 connection = currentConnection;
596 } else if (connection.getCandidate().getPriority() == currentConnection
597 .getCandidate().getPriority()) {
598 // Log.d(Config.LOGTAG,"found two candidates with same priority");
599 if (initiator.equals(account.getFullJid())) {
600 if (currentConnection.getCandidate().isOurs()) {
601 connection = currentConnection;
602 }
603 } else {
604 if (!currentConnection.getCandidate().isOurs()) {
605 connection = currentConnection;
606 }
607 }
608 }
609 }
610 }
611 }
612 return connection;
613 }
614
615 private void sendSuccess() {
616 JinglePacket packet = bootstrapPacket("session-terminate");
617 Reason reason = new Reason();
618 reason.addChild("success");
619 packet.setReason(reason);
620 this.sendJinglePacket(packet);
621 this.disconnect();
622 this.status = STATUS_FINISHED;
623 this.mXmppConnectionService.markMessage(this.message,
624 Message.STATUS_RECEIVED);
625 this.mJingleConnectionManager.finishConnection(this);
626 }
627
628 private void sendFallbackToIbb() {
629 JinglePacket packet = this.bootstrapPacket("transport-replace");
630 Content content = new Content(this.contentCreator, this.contentName);
631 this.transportId = this.mJingleConnectionManager.nextRandomId();
632 content.setTransportId(this.transportId);
633 content.ibbTransport().setAttribute("block-size",
634 Integer.toString(this.ibbBlockSize));
635 packet.setContent(content);
636 this.sendJinglePacket(packet);
637 }
638
639 private boolean receiveFallbackToIbb(JinglePacket packet) {
640 String receivedBlockSize = packet.getJingleContent().ibbTransport()
641 .getAttribute("block-size");
642 if (receivedBlockSize != null) {
643 int bs = Integer.parseInt(receivedBlockSize);
644 if (bs > this.ibbBlockSize) {
645 this.ibbBlockSize = bs;
646 }
647 }
648 this.transportId = packet.getJingleContent().getTransportId();
649 this.transport = new JingleInbandTransport(this.account,
650 this.responder, this.transportId, this.ibbBlockSize);
651 this.transport.receive(file, onFileTransmissionSatusChanged);
652 JinglePacket answer = bootstrapPacket("transport-accept");
653 Content content = new Content("initiator", "a-file-offer");
654 content.setTransportId(this.transportId);
655 content.ibbTransport().setAttribute("block-size",
656 Integer.toString(this.ibbBlockSize));
657 answer.setContent(content);
658 this.sendJinglePacket(answer);
659 return true;
660 }
661
662 private boolean receiveTransportAccept(JinglePacket packet) {
663 if (packet.getJingleContent().hasIbbTransport()) {
664 String receivedBlockSize = packet.getJingleContent().ibbTransport()
665 .getAttribute("block-size");
666 if (receivedBlockSize != null) {
667 int bs = Integer.parseInt(receivedBlockSize);
668 if (bs > this.ibbBlockSize) {
669 this.ibbBlockSize = bs;
670 }
671 }
672 this.transport = new JingleInbandTransport(this.account,
673 this.responder, this.transportId, this.ibbBlockSize);
674 this.transport.connect(new OnTransportConnected() {
675
676 @Override
677 public void failed() {
678 Log.d(Config.LOGTAG, "ibb open failed");
679 }
680
681 @Override
682 public void established() {
683 JingleConnection.this.transport.send(file,
684 onFileTransmissionSatusChanged);
685 }
686 });
687 return true;
688 } else {
689 return false;
690 }
691 }
692
693 private void receiveSuccess() {
694 this.status = STATUS_FINISHED;
695 this.mXmppConnectionService.markMessage(this.message,
696 Message.STATUS_SEND);
697 this.disconnect();
698 this.mJingleConnectionManager.finishConnection(this);
699 }
700
701 public void cancel() {
702 this.status = STATUS_CANCELED;
703 this.disconnect();
704 if (this.message != null) {
705 if (this.responder.equals(account.getFullJid())) {
706 this.mXmppConnectionService.markMessage(this.message,
707 Message.STATUS_RECEPTION_FAILED);
708 } else {
709 if (this.status == STATUS_INITIATED) {
710 this.mXmppConnectionService.markMessage(this.message,
711 Message.STATUS_SEND_REJECTED);
712 } else {
713 this.mXmppConnectionService.markMessage(this.message,
714 Message.STATUS_SEND_FAILED);
715 }
716 }
717 }
718 this.mJingleConnectionManager.finishConnection(this);
719 }
720
721 private void sendCancel() {
722 JinglePacket packet = bootstrapPacket("session-terminate");
723 Reason reason = new Reason();
724 reason.addChild("cancel");
725 packet.setReason(reason);
726 this.sendJinglePacket(packet);
727 }
728
729 private void connectNextCandidate() {
730 for (JingleCandidate candidate : this.candidates) {
731 if ((!connections.containsKey(candidate.getCid()) && (!candidate
732 .isOurs()))) {
733 this.connectWithCandidate(candidate);
734 return;
735 }
736 }
737 this.sendCandidateError();
738 }
739
740 private void connectWithCandidate(final JingleCandidate candidate) {
741 final JingleSocks5Transport socksConnection = new JingleSocks5Transport(
742 this, candidate);
743 connections.put(candidate.getCid(), socksConnection);
744 socksConnection.connect(new OnTransportConnected() {
745
746 @Override
747 public void failed() {
748 Log.d(Config.LOGTAG,
749 "connection failed with " + candidate.getHost() + ":"
750 + candidate.getPort());
751 connectNextCandidate();
752 }
753
754 @Override
755 public void established() {
756 Log.d(Config.LOGTAG,
757 "established connection with " + candidate.getHost()
758 + ":" + candidate.getPort());
759 sendCandidateUsed(candidate.getCid());
760 }
761 });
762 }
763
764 private void disconnect() {
765 Iterator<Entry<String, JingleSocks5Transport>> it = this.connections
766 .entrySet().iterator();
767 while (it.hasNext()) {
768 Entry<String, JingleSocks5Transport> pairs = it.next();
769 pairs.getValue().disconnect();
770 it.remove();
771 }
772 }
773
774 private void sendProxyActivated(String cid) {
775 JinglePacket packet = bootstrapPacket("transport-info");
776 Content content = new Content(this.contentCreator, this.contentName);
777 content.setTransportId(this.transportId);
778 content.socks5transport().addChild("activated")
779 .setAttribute("cid", cid);
780 packet.setContent(content);
781 this.sendJinglePacket(packet);
782 }
783
784 private void sendCandidateUsed(final String cid) {
785 JinglePacket packet = bootstrapPacket("transport-info");
786 Content content = new Content(this.contentCreator, this.contentName);
787 content.setTransportId(this.transportId);
788 content.socks5transport().addChild("candidate-used")
789 .setAttribute("cid", cid);
790 packet.setContent(content);
791 this.sentCandidate = true;
792 if ((receivedCandidate) && (status == STATUS_ACCEPTED)) {
793 connect();
794 }
795 this.sendJinglePacket(packet);
796 }
797
798 private void sendCandidateError() {
799 JinglePacket packet = bootstrapPacket("transport-info");
800 Content content = new Content(this.contentCreator, this.contentName);
801 content.setTransportId(this.transportId);
802 content.socks5transport().addChild("candidate-error");
803 packet.setContent(content);
804 this.sentCandidate = true;
805 if ((receivedCandidate) && (status == STATUS_ACCEPTED)) {
806 connect();
807 }
808 this.sendJinglePacket(packet);
809 }
810
811 public String getInitiator() {
812 return this.initiator;
813 }
814
815 public String getResponder() {
816 return this.responder;
817 }
818
819 public int getStatus() {
820 return this.status;
821 }
822
823 private boolean equalCandidateExists(JingleCandidate candidate) {
824 for (JingleCandidate c : this.candidates) {
825 if (c.equalValues(candidate)) {
826 return true;
827 }
828 }
829 return false;
830 }
831
832 private void mergeCandidate(JingleCandidate candidate) {
833 for (JingleCandidate c : this.candidates) {
834 if (c.equals(candidate)) {
835 return;
836 }
837 }
838 this.candidates.add(candidate);
839 }
840
841 private void mergeCandidates(List<JingleCandidate> candidates) {
842 for (JingleCandidate c : candidates) {
843 mergeCandidate(c);
844 }
845 }
846
847 private JingleCandidate getCandidate(String cid) {
848 for (JingleCandidate c : this.candidates) {
849 if (c.getCid().equals(cid)) {
850 return c;
851 }
852 }
853 return null;
854 }
855
856 interface OnProxyActivated {
857 public void success();
858
859 public void failed();
860 }
861
862 public boolean hasTransportId(String sid) {
863 return sid.equals(this.transportId);
864 }
865
866 public JingleTransport getTransport() {
867 return this.transport;
868 }
869
870 public void start() {
871 if (status == STATUS_INITIATED) {
872 new Thread(new Runnable() {
873
874 @Override
875 public void run() {
876 sendAccept();
877 }
878 }).start();
879 } else {
880 Log.d(Config.LOGTAG, "status (" + status + ") was not ok");
881 }
882 }
883}