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