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