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