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