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