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