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