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 if (!this.mXmppConnectionService.renewSymmetricKey(conversation)) {
372 Log.d(Config.LOGTAG,account.getJid().toBareJid()+": could not set symmetric key");
373 cancel();
374 }
375 content.setFileOffer(this.file, true);
376 this.file.setKey(conversation.getSymmetricKey());
377 } else {
378 content.setFileOffer(this.file, false);
379 }
380 this.transportId = this.mJingleConnectionManager.nextRandomId();
381 content.setTransportId(this.transportId);
382 content.socks5transport().setChildren(getCandidatesAsElements());
383 packet.setContent(content);
384 this.sendJinglePacket(packet,new OnIqPacketReceived() {
385
386 @Override
387 public void onIqPacketReceived(Account account, IqPacket packet) {
388 if (packet.getType() != IqPacket.TYPE.ERROR) {
389 Log.d(Config.LOGTAG,account.getJid().toBareJid()+": other party received offer");
390 mJingleStatus = JINGLE_STATUS_INITIATED;
391 mXmppConnectionService.markMessage(message, Message.STATUS_OFFERED);
392 } else {
393 fail();
394 }
395 }
396 });
397
398 }
399 }
400
401 private List<Element> getCandidatesAsElements() {
402 List<Element> elements = new ArrayList<>();
403 for (JingleCandidate c : this.candidates) {
404 if (c.isOurs()) {
405 elements.add(c.toElement());
406 }
407 }
408 return elements;
409 }
410
411 private void sendAccept() {
412 mJingleStatus = JINGLE_STATUS_ACCEPTED;
413 this.mStatus = Transferable.STATUS_DOWNLOADING;
414 mXmppConnectionService.updateConversationUi();
415 this.mJingleConnectionManager.getPrimaryCandidate(this.account, new OnPrimaryCandidateFound() {
416 @Override
417 public void onPrimaryCandidateFound(boolean success, final JingleCandidate candidate) {
418 final JinglePacket packet = bootstrapPacket("session-accept");
419 final Content content = new Content(contentCreator,contentName);
420 content.setFileOffer(fileOffer);
421 content.setTransportId(transportId);
422 if (success && candidate != null && !equalCandidateExists(candidate)) {
423 final JingleSocks5Transport socksConnection = new JingleSocks5Transport(
424 JingleConnection.this,
425 candidate);
426 connections.put(candidate.getCid(), socksConnection);
427 socksConnection.connect(new OnTransportConnected() {
428
429 @Override
430 public void failed() {
431 Log.d(Config.LOGTAG,"connection to our own primary candidate failed");
432 content.socks5transport().setChildren(getCandidatesAsElements());
433 packet.setContent(content);
434 sendJinglePacket(packet);
435 connectNextCandidate();
436 }
437
438 @Override
439 public void established() {
440 Log.d(Config.LOGTAG, "connected to primary candidate");
441 mergeCandidate(candidate);
442 content.socks5transport().setChildren(getCandidatesAsElements());
443 packet.setContent(content);
444 sendJinglePacket(packet);
445 connectNextCandidate();
446 }
447 });
448 } else {
449 Log.d(Config.LOGTAG,"did not find a primary candidate for ourself");
450 content.socks5transport().setChildren(getCandidatesAsElements());
451 packet.setContent(content);
452 sendJinglePacket(packet);
453 connectNextCandidate();
454 }
455 }
456 });
457 }
458
459 private JinglePacket bootstrapPacket(String action) {
460 JinglePacket packet = new JinglePacket();
461 packet.setAction(action);
462 packet.setFrom(account.getJid());
463 packet.setTo(this.message.getCounterpart());
464 packet.setSessionId(this.sessionId);
465 packet.setInitiator(this.initiator);
466 return packet;
467 }
468
469 private void sendJinglePacket(JinglePacket packet) {
470 mXmppConnectionService.sendIqPacket(account,packet,responseListener);
471 }
472
473 private void sendJinglePacket(JinglePacket packet, OnIqPacketReceived callback) {
474 mXmppConnectionService.sendIqPacket(account,packet,callback);
475 }
476
477 private boolean receiveAccept(JinglePacket packet) {
478 Content content = packet.getJingleContent();
479 mergeCandidates(JingleCandidate.parse(content.socks5transport()
480 .getChildren()));
481 this.mJingleStatus = JINGLE_STATUS_ACCEPTED;
482 mXmppConnectionService.markMessage(message, Message.STATUS_UNSEND);
483 this.connectNextCandidate();
484 return true;
485 }
486
487 private boolean receiveTransportInfo(JinglePacket packet) {
488 Content content = packet.getJingleContent();
489 if (content.hasSocks5Transport()) {
490 if (content.socks5transport().hasChild("activated")) {
491 if ((this.transport != null) && (this.transport instanceof JingleSocks5Transport)) {
492 onProxyActivated.success();
493 } else {
494 String cid = content.socks5transport().findChild("activated").getAttribute("cid");
495 Log.d(Config.LOGTAG, "received proxy activated (" + cid
496 + ")prior to choosing our own transport");
497 JingleSocks5Transport connection = this.connections.get(cid);
498 if (connection != null) {
499 connection.setActivated(true);
500 } else {
501 Log.d(Config.LOGTAG, "activated connection not found");
502 this.sendCancel();
503 this.fail();
504 }
505 }
506 return true;
507 } else if (content.socks5transport().hasChild("proxy-error")) {
508 onProxyActivated.failed();
509 return true;
510 } else if (content.socks5transport().hasChild("candidate-error")) {
511 Log.d(Config.LOGTAG, "received candidate error");
512 this.receivedCandidate = true;
513 if ((mJingleStatus == JINGLE_STATUS_ACCEPTED)
514 && (this.sentCandidate)) {
515 this.connect();
516 }
517 return true;
518 } else if (content.socks5transport().hasChild("candidate-used")) {
519 String cid = content.socks5transport()
520 .findChild("candidate-used").getAttribute("cid");
521 if (cid != null) {
522 Log.d(Config.LOGTAG, "candidate used by counterpart:" + cid);
523 JingleCandidate candidate = getCandidate(cid);
524 candidate.flagAsUsedByCounterpart();
525 this.receivedCandidate = true;
526 if ((mJingleStatus == JINGLE_STATUS_ACCEPTED)
527 && (this.sentCandidate)) {
528 this.connect();
529 } else {
530 Log.d(Config.LOGTAG,
531 "ignoring because file is already in transmission or we havent sent our candidate yet");
532 }
533 return true;
534 } else {
535 return false;
536 }
537 } else {
538 return false;
539 }
540 } else {
541 return true;
542 }
543 }
544
545 private void connect() {
546 final JingleSocks5Transport connection = chooseConnection();
547 this.transport = connection;
548 if (connection == null) {
549 Log.d(Config.LOGTAG, "could not find suitable candidate");
550 this.disconnectSocks5Connections();
551 if (this.initiator.equals(account.getJid())) {
552 this.sendFallbackToIbb();
553 }
554 } else {
555 this.mJingleStatus = JINGLE_STATUS_TRANSMITTING;
556 if (connection.needsActivation()) {
557 if (connection.getCandidate().isOurs()) {
558 Log.d(Config.LOGTAG, "candidate "
559 + connection.getCandidate().getCid()
560 + " was our proxy. going to activate");
561 IqPacket activation = new IqPacket(IqPacket.TYPE.SET);
562 activation.setTo(connection.getCandidate().getJid());
563 activation.query("http://jabber.org/protocol/bytestreams")
564 .setAttribute("sid", this.getSessionId());
565 activation.query().addChild("activate")
566 .setContent(this.getCounterPart().toString());
567 mXmppConnectionService.sendIqPacket(account,activation,
568 new OnIqPacketReceived() {
569
570 @Override
571 public void onIqPacketReceived(Account account,
572 IqPacket packet) {
573 if (packet.getType() == IqPacket.TYPE.ERROR) {
574 onProxyActivated.failed();
575 } else {
576 onProxyActivated.success();
577 sendProxyActivated(connection
578 .getCandidate().getCid());
579 }
580 }
581 });
582 } else {
583 Log.d(Config.LOGTAG,
584 "candidate "
585 + connection.getCandidate().getCid()
586 + " was a proxy. waiting for other party to activate");
587 }
588 } else {
589 if (initiator.equals(account.getJid())) {
590 Log.d(Config.LOGTAG, "we were initiating. sending file");
591 connection.send(file, onFileTransmissionSatusChanged);
592 } else {
593 Log.d(Config.LOGTAG, "we were responding. receiving file");
594 connection.receive(file, onFileTransmissionSatusChanged);
595 }
596 }
597 }
598 }
599
600 private JingleSocks5Transport chooseConnection() {
601 JingleSocks5Transport connection = null;
602 for (Entry<String, JingleSocks5Transport> cursor : connections
603 .entrySet()) {
604 JingleSocks5Transport currentConnection = cursor.getValue();
605 // Log.d(Config.LOGTAG,"comparing candidate: "+currentConnection.getCandidate().toString());
606 if (currentConnection.isEstablished()
607 && (currentConnection.getCandidate().isUsedByCounterpart() || (!currentConnection
608 .getCandidate().isOurs()))) {
609 // Log.d(Config.LOGTAG,"is usable");
610 if (connection == null) {
611 connection = currentConnection;
612 } else {
613 if (connection.getCandidate().getPriority() < currentConnection
614 .getCandidate().getPriority()) {
615 connection = currentConnection;
616 } else if (connection.getCandidate().getPriority() == currentConnection
617 .getCandidate().getPriority()) {
618 // Log.d(Config.LOGTAG,"found two candidates with same priority");
619 if (initiator.equals(account.getJid())) {
620 if (currentConnection.getCandidate().isOurs()) {
621 connection = currentConnection;
622 }
623 } else {
624 if (!currentConnection.getCandidate().isOurs()) {
625 connection = currentConnection;
626 }
627 }
628 }
629 }
630 }
631 }
632 return connection;
633 }
634
635 private void sendSuccess() {
636 JinglePacket packet = bootstrapPacket("session-terminate");
637 Reason reason = new Reason();
638 reason.addChild("success");
639 packet.setReason(reason);
640 this.sendJinglePacket(packet);
641 this.disconnectSocks5Connections();
642 this.mJingleStatus = JINGLE_STATUS_FINISHED;
643 this.message.setStatus(Message.STATUS_RECEIVED);
644 this.message.setTransferable(null);
645 this.mXmppConnectionService.updateMessage(message);
646 this.mJingleConnectionManager.finishConnection(this);
647 }
648
649 private void sendFallbackToIbb() {
650 Log.d(Config.LOGTAG, "sending fallback to ibb");
651 JinglePacket packet = this.bootstrapPacket("transport-replace");
652 Content content = new Content(this.contentCreator, this.contentName);
653 this.transportId = this.mJingleConnectionManager.nextRandomId();
654 content.setTransportId(this.transportId);
655 content.ibbTransport().setAttribute("block-size",
656 Integer.toString(this.ibbBlockSize));
657 packet.setContent(content);
658 this.sendJinglePacket(packet);
659 }
660
661 private boolean receiveFallbackToIbb(JinglePacket packet) {
662 Log.d(Config.LOGTAG, "receiving fallack to ibb");
663 String receivedBlockSize = packet.getJingleContent().ibbTransport()
664 .getAttribute("block-size");
665 if (receivedBlockSize != null) {
666 int bs = Integer.parseInt(receivedBlockSize);
667 if (bs > this.ibbBlockSize) {
668 this.ibbBlockSize = bs;
669 }
670 }
671 this.transportId = packet.getJingleContent().getTransportId();
672 this.transport = new JingleInbandTransport(this, this.transportId, this.ibbBlockSize);
673 this.transport.receive(file, onFileTransmissionSatusChanged);
674 JinglePacket answer = bootstrapPacket("transport-accept");
675 Content content = new Content("initiator", "a-file-offer");
676 content.setTransportId(this.transportId);
677 content.ibbTransport().setAttribute("block-size",
678 Integer.toString(this.ibbBlockSize));
679 answer.setContent(content);
680 this.sendJinglePacket(answer);
681 return true;
682 }
683
684 private boolean receiveTransportAccept(JinglePacket packet) {
685 if (packet.getJingleContent().hasIbbTransport()) {
686 String receivedBlockSize = packet.getJingleContent().ibbTransport()
687 .getAttribute("block-size");
688 if (receivedBlockSize != null) {
689 int bs = Integer.parseInt(receivedBlockSize);
690 if (bs > this.ibbBlockSize) {
691 this.ibbBlockSize = bs;
692 }
693 }
694 this.transport = new JingleInbandTransport(this, this.transportId, this.ibbBlockSize);
695 this.transport.connect(new OnTransportConnected() {
696
697 @Override
698 public void failed() {
699 Log.d(Config.LOGTAG, "ibb open failed");
700 }
701
702 @Override
703 public void established() {
704 JingleConnection.this.transport.send(file,
705 onFileTransmissionSatusChanged);
706 }
707 });
708 return true;
709 } else {
710 return false;
711 }
712 }
713
714 private void receiveSuccess() {
715 this.mJingleStatus = JINGLE_STATUS_FINISHED;
716 this.mXmppConnectionService.markMessage(this.message,Message.STATUS_SEND_RECEIVED);
717 this.disconnectSocks5Connections();
718 if (this.transport != null && this.transport instanceof JingleInbandTransport) {
719 this.transport.disconnect();
720 }
721 this.message.setTransferable(null);
722 this.mJingleConnectionManager.finishConnection(this);
723 }
724
725 public void cancel() {
726 this.disconnectSocks5Connections();
727 if (this.transport != null && this.transport instanceof JingleInbandTransport) {
728 this.transport.disconnect();
729 }
730 this.sendCancel();
731 this.mJingleConnectionManager.finishConnection(this);
732 if (this.responder.equals(account.getJid())) {
733 this.message.setTransferable(new TransferablePlaceholder(Transferable.STATUS_FAILED));
734 if (this.file!=null) {
735 file.delete();
736 }
737 this.mXmppConnectionService.updateConversationUi();
738 } else {
739 this.mXmppConnectionService.markMessage(this.message,
740 Message.STATUS_SEND_FAILED);
741 this.message.setTransferable(null);
742 }
743 }
744
745 private void fail() {
746 this.mJingleStatus = JINGLE_STATUS_FAILED;
747 this.disconnectSocks5Connections();
748 if (this.transport != null && this.transport instanceof JingleInbandTransport) {
749 this.transport.disconnect();
750 }
751 if (this.message != null) {
752 if (this.responder.equals(account.getJid())) {
753 this.message.setTransferable(new TransferablePlaceholder(Transferable.STATUS_FAILED));
754 if (this.file!=null) {
755 file.delete();
756 }
757 this.mXmppConnectionService.updateConversationUi();
758 } else {
759 this.mXmppConnectionService.markMessage(this.message,
760 Message.STATUS_SEND_FAILED);
761 this.message.setTransferable(null);
762 }
763 }
764 this.mJingleConnectionManager.finishConnection(this);
765 }
766
767 private void sendCancel() {
768 JinglePacket packet = bootstrapPacket("session-terminate");
769 Reason reason = new Reason();
770 reason.addChild("cancel");
771 packet.setReason(reason);
772 this.sendJinglePacket(packet);
773 }
774
775 private void connectNextCandidate() {
776 for (JingleCandidate candidate : this.candidates) {
777 if ((!connections.containsKey(candidate.getCid()) && (!candidate
778 .isOurs()))) {
779 this.connectWithCandidate(candidate);
780 return;
781 }
782 }
783 this.sendCandidateError();
784 }
785
786 private void connectWithCandidate(final JingleCandidate candidate) {
787 final JingleSocks5Transport socksConnection = new JingleSocks5Transport(
788 this, candidate);
789 connections.put(candidate.getCid(), socksConnection);
790 socksConnection.connect(new OnTransportConnected() {
791
792 @Override
793 public void failed() {
794 Log.d(Config.LOGTAG,
795 "connection failed with " + candidate.getHost() + ":"
796 + candidate.getPort());
797 connectNextCandidate();
798 }
799
800 @Override
801 public void established() {
802 Log.d(Config.LOGTAG,
803 "established connection with " + candidate.getHost()
804 + ":" + candidate.getPort());
805 sendCandidateUsed(candidate.getCid());
806 }
807 });
808 }
809
810 private void disconnectSocks5Connections() {
811 Iterator<Entry<String, JingleSocks5Transport>> it = this.connections
812 .entrySet().iterator();
813 while (it.hasNext()) {
814 Entry<String, JingleSocks5Transport> pairs = it.next();
815 pairs.getValue().disconnect();
816 it.remove();
817 }
818 }
819
820 private void sendProxyActivated(String cid) {
821 JinglePacket packet = bootstrapPacket("transport-info");
822 Content content = new Content(this.contentCreator, this.contentName);
823 content.setTransportId(this.transportId);
824 content.socks5transport().addChild("activated")
825 .setAttribute("cid", cid);
826 packet.setContent(content);
827 this.sendJinglePacket(packet);
828 }
829
830 private void sendCandidateUsed(final String cid) {
831 JinglePacket packet = bootstrapPacket("transport-info");
832 Content content = new Content(this.contentCreator, this.contentName);
833 content.setTransportId(this.transportId);
834 content.socks5transport().addChild("candidate-used")
835 .setAttribute("cid", cid);
836 packet.setContent(content);
837 this.sentCandidate = true;
838 if ((receivedCandidate) && (mJingleStatus == JINGLE_STATUS_ACCEPTED)) {
839 connect();
840 }
841 this.sendJinglePacket(packet);
842 }
843
844 private void sendCandidateError() {
845 JinglePacket packet = bootstrapPacket("transport-info");
846 Content content = new Content(this.contentCreator, this.contentName);
847 content.setTransportId(this.transportId);
848 content.socks5transport().addChild("candidate-error");
849 packet.setContent(content);
850 this.sentCandidate = true;
851 if ((receivedCandidate) && (mJingleStatus == JINGLE_STATUS_ACCEPTED)) {
852 connect();
853 }
854 this.sendJinglePacket(packet);
855 }
856
857 public Jid getInitiator() {
858 return this.initiator;
859 }
860
861 public Jid getResponder() {
862 return this.responder;
863 }
864
865 public int getJingleStatus() {
866 return this.mJingleStatus;
867 }
868
869 private boolean equalCandidateExists(JingleCandidate candidate) {
870 for (JingleCandidate c : this.candidates) {
871 if (c.equalValues(candidate)) {
872 return true;
873 }
874 }
875 return false;
876 }
877
878 private void mergeCandidate(JingleCandidate candidate) {
879 for (JingleCandidate c : this.candidates) {
880 if (c.equals(candidate)) {
881 return;
882 }
883 }
884 this.candidates.add(candidate);
885 }
886
887 private void mergeCandidates(List<JingleCandidate> candidates) {
888 for (JingleCandidate c : candidates) {
889 mergeCandidate(c);
890 }
891 }
892
893 private JingleCandidate getCandidate(String cid) {
894 for (JingleCandidate c : this.candidates) {
895 if (c.getCid().equals(cid)) {
896 return c;
897 }
898 }
899 return null;
900 }
901
902 public void updateProgress(int i) {
903 this.mProgress = i;
904 if (SystemClock.elapsedRealtime() - this.mLastGuiRefresh > Config.PROGRESS_UI_UPDATE_INTERVAL) {
905 this.mLastGuiRefresh = SystemClock.elapsedRealtime();
906 mXmppConnectionService.updateConversationUi();
907 }
908 }
909
910 interface OnProxyActivated {
911 public void success();
912
913 public void failed();
914 }
915
916 public boolean hasTransportId(String sid) {
917 return sid.equals(this.transportId);
918 }
919
920 public JingleTransport getTransport() {
921 return this.transport;
922 }
923
924 public boolean start() {
925 if (account.getStatus() == Account.State.ONLINE) {
926 if (mJingleStatus == JINGLE_STATUS_INITIATED) {
927 new Thread(new Runnable() {
928
929 @Override
930 public void run() {
931 sendAccept();
932 }
933 }).start();
934 }
935 return true;
936 } else {
937 return false;
938 }
939 }
940
941 @Override
942 public int getStatus() {
943 return this.mStatus;
944 }
945
946 @Override
947 public long getFileSize() {
948 if (this.file != null) {
949 return this.file.getExpectedSize();
950 } else {
951 return 0;
952 }
953 }
954
955 @Override
956 public int getProgress() {
957 return this.mProgress;
958 }
959}