1package eu.siacs.conversations.xmpp.jingle;
2
3import java.util.ArrayList;
4import java.util.Arrays;
5import java.util.HashMap;
6import java.util.Iterator;
7import java.util.List;
8import java.util.Locale;
9import java.util.Map.Entry;
10
11import android.graphics.BitmapFactory;
12import android.util.Log;
13import eu.siacs.conversations.entities.Account;
14import eu.siacs.conversations.entities.Conversation;
15import eu.siacs.conversations.entities.Message;
16import eu.siacs.conversations.services.XmppConnectionService;
17import eu.siacs.conversations.xml.Element;
18import eu.siacs.conversations.xmpp.OnIqPacketReceived;
19import eu.siacs.conversations.xmpp.jingle.stanzas.Content;
20import eu.siacs.conversations.xmpp.jingle.stanzas.JinglePacket;
21import eu.siacs.conversations.xmpp.jingle.stanzas.Reason;
22import eu.siacs.conversations.xmpp.stanzas.IqPacket;
23
24public class JingleConnection {
25
26 private final String[] extensions = {"webp","jpeg","jpg","png"};
27 private final String[] cryptoExtensions = {"pgp","gpg","otr"};
28
29 private JingleConnectionManager mJingleConnectionManager;
30 private XmppConnectionService mXmppConnectionService;
31
32 public static final int STATUS_INITIATED = 0;
33 public static final int STATUS_ACCEPTED = 1;
34 public static final int STATUS_TERMINATED = 2;
35 public static final int STATUS_CANCELED = 3;
36 public static final int STATUS_FINISHED = 4;
37 public static final int STATUS_TRANSMITTING = 5;
38 public static final int STATUS_FAILED = 99;
39
40 private int ibbBlockSize = 4096;
41
42 private int status = -1;
43 private Message message;
44 private String sessionId;
45 private Account account;
46 private String initiator;
47 private String responder;
48 private List<JingleCandidate> candidates = new ArrayList<JingleCandidate>();
49 private HashMap<String, JingleSocks5Transport> connections = new HashMap<String, JingleSocks5Transport>();
50
51 private String transportId;
52 private Element fileOffer;
53 private JingleFile file = null;
54
55 private String contentName;
56 private String contentCreator;
57
58 private boolean receivedCandidate = false;
59 private boolean sentCandidate = false;
60
61 private boolean acceptedAutomatically = false;
62
63 private JingleTransport transport = null;
64
65 private OnIqPacketReceived responseListener = new OnIqPacketReceived() {
66
67 @Override
68 public void onIqPacketReceived(Account account, IqPacket packet) {
69 if (packet.getType() == IqPacket.TYPE_ERROR) {
70 if (initiator.equals(account.getFullJid())) {
71 mXmppConnectionService.markMessage(message, Message.STATUS_SEND_FAILED);
72 }
73 status = STATUS_FAILED;
74 }
75 }
76 };
77
78 final OnFileTransmissionStatusChanged onFileTransmissionSatusChanged = new OnFileTransmissionStatusChanged() {
79
80 @Override
81 public void onFileTransmitted(JingleFile file) {
82 if (responder.equals(account.getFullJid())) {
83 sendSuccess();
84 if (acceptedAutomatically) {
85 message.markUnread();
86 JingleConnection.this.mXmppConnectionService.updateUi(message.getConversation(), true);
87 }
88 BitmapFactory.Options options = new BitmapFactory.Options();
89 options.inJustDecodeBounds = true;
90 BitmapFactory.decodeFile(file.getAbsolutePath(),options);
91 int imageHeight = options.outHeight;
92 int imageWidth = options.outWidth;
93 message.setBody(""+file.getSize()+","+imageWidth+","+imageHeight);
94 mXmppConnectionService.databaseBackend.createMessage(message);
95 mXmppConnectionService.markMessage(message, Message.STATUS_RECIEVED);
96 }
97 Log.d("xmppService","sucessfully transmitted file:"+file.getAbsolutePath());
98 }
99
100 @Override
101 public void onFileTransferAborted() {
102 JingleConnection.this.sendCancel();
103 JingleConnection.this.cancel();
104 }
105 };
106
107 private OnProxyActivated onProxyActivated = new OnProxyActivated() {
108
109 @Override
110 public void success() {
111 if (initiator.equals(account.getFullJid())) {
112 Log.d("xmppService","we were initiating. sending file");
113 transport.send(file,onFileTransmissionSatusChanged);
114 } else {
115 transport.receive(file,onFileTransmissionSatusChanged);
116 Log.d("xmppService","we were responding. receiving file");
117 }
118 }
119
120 @Override
121 public void failed() {
122 Log.d("xmppService","proxy activation failed");
123 }
124 };
125
126 public JingleConnection(JingleConnectionManager mJingleConnectionManager) {
127 this.mJingleConnectionManager = mJingleConnectionManager;
128 this.mXmppConnectionService = mJingleConnectionManager.getXmppConnectionService();
129 }
130
131 public String getSessionId() {
132 return this.sessionId;
133 }
134
135 public String getAccountJid() {
136 return this.account.getFullJid();
137 }
138
139 public String getCounterPart() {
140 return this.message.getCounterpart();
141 }
142
143 public void deliverPacket(JinglePacket packet) {
144 boolean returnResult = true;
145 if (packet.isAction("session-terminate")) {
146 Reason reason = packet.getReason();
147 if (reason!=null) {
148 if (reason.hasChild("cancel")) {
149 this.cancel();
150 } else if (reason.hasChild("success")) {
151 this.receiveSuccess();
152 } else {
153 this.cancel();
154 }
155 } else {
156 this.cancel();
157 }
158 } else if (packet.isAction("session-accept")) {
159 returnResult = receiveAccept(packet);
160 } else if (packet.isAction("transport-info")) {
161 returnResult = receiveTransportInfo(packet);
162 } else if (packet.isAction("transport-replace")) {
163 if (packet.getJingleContent().hasIbbTransport()) {
164 returnResult = this.receiveFallbackToIbb(packet);
165 } else {
166 returnResult = false;
167 Log.d("xmppService","trying to fallback to something unknown"+packet.toString());
168 }
169 } else if (packet.isAction("transport-accept")) {
170 returnResult = this.receiveTransportAccept(packet);
171 } else {
172 Log.d("xmppService","packet arrived in connection. action was "+packet.getAction());
173 returnResult = false;
174 }
175 IqPacket response;
176 if (returnResult) {
177 response = packet.generateRespone(IqPacket.TYPE_RESULT);
178
179 } else {
180 response = packet.generateRespone(IqPacket.TYPE_ERROR);
181 }
182 account.getXmppConnection().sendIqPacket(response, null);
183 }
184
185 public void init(Message message) {
186 this.contentCreator = "initiator";
187 this.contentName = this.mJingleConnectionManager.nextRandomId();
188 this.message = message;
189 this.account = message.getConversation().getAccount();
190 this.initiator = this.account.getFullJid();
191 this.responder = this.message.getCounterpart();
192 this.sessionId = this.mJingleConnectionManager.nextRandomId();
193 if (this.candidates.size() > 0) {
194 this.sendInitRequest();
195 } else {
196 this.mJingleConnectionManager.getPrimaryCandidate(account, new OnPrimaryCandidateFound() {
197
198 @Override
199 public void onPrimaryCandidateFound(boolean success, final JingleCandidate candidate) {
200 if (success) {
201 final JingleSocks5Transport socksConnection = new JingleSocks5Transport(JingleConnection.this, candidate);
202 connections.put(candidate.getCid(), socksConnection);
203 socksConnection.connect(new OnTransportConnected() {
204
205 @Override
206 public void failed() {
207 Log.d("xmppService","connection to our own primary candidete failed");
208 sendInitRequest();
209 }
210
211 @Override
212 public void established() {
213 Log.d("xmppService","succesfully connected to our own primary candidate");
214 mergeCandidate(candidate);
215 sendInitRequest();
216 }
217 });
218 mergeCandidate(candidate);
219 } else {
220 Log.d("xmppService","no primary candidate of our own was found");
221 sendInitRequest();
222 }
223 }
224 });
225 }
226
227 }
228
229 public void init(Account account, JinglePacket packet) {
230 this.status = STATUS_INITIATED;
231 Conversation conversation = this.mXmppConnectionService.findOrCreateConversation(account, packet.getFrom().split("/")[0], false);
232 this.message = new Message(conversation, "", Message.ENCRYPTION_NONE);
233 this.message.setType(Message.TYPE_IMAGE);
234 this.message.setStatus(Message.STATUS_RECEIVED_OFFER);
235 this.message.setJingleConnection(this);
236 String[] fromParts = packet.getFrom().split("/");
237 this.message.setPresence(fromParts[1]);
238 this.account = account;
239 this.initiator = packet.getFrom();
240 this.responder = this.account.getFullJid();
241 this.sessionId = packet.getSessionId();
242 Content content = packet.getJingleContent();
243 this.contentCreator = content.getAttribute("creator");
244 this.contentName = content.getAttribute("name");
245 this.transportId = content.getTransportId();
246 this.mergeCandidates(JingleCandidate.parse(content.socks5transport().getChildren()));
247 this.fileOffer = packet.getJingleContent().getFileOffer();
248 if (fileOffer!=null) {
249 Element fileSize = fileOffer.findChild("size");
250 Element fileNameElement = fileOffer.findChild("name");
251 if (fileNameElement!=null) {
252 boolean supportedFile = false;
253 String[] filename = fileNameElement.getContent().toLowerCase(Locale.US).split("\\.");
254 if (Arrays.asList(this.extensions).contains(filename[filename.length - 1])) {
255 supportedFile = true;
256 } else if (Arrays.asList(this.cryptoExtensions).contains(filename[filename.length - 1])) {
257 if (filename.length == 3) {
258 if (Arrays.asList(this.extensions).contains(filename[filename.length -2])) {
259 supportedFile = true;
260 if (filename[filename.length - 1].equals("otr")) {
261 Log.d("xmppService","receiving otr file");
262 this.message.setEncryption(Message.ENCRYPTION_OTR);
263 } else {
264 this.message.setEncryption(Message.ENCRYPTION_PGP);
265 }
266 }
267 }
268 }
269 if (supportedFile) {
270 long size = Long.parseLong(fileSize.getContent());
271 message.setBody(""+size);
272 conversation.getMessages().add(message);
273 if (size<=this.mJingleConnectionManager.getAutoAcceptFileSize()) {
274 Log.d("xmppService","auto accepting file from "+packet.getFrom());
275 this.acceptedAutomatically = true;
276 this.sendAccept();
277 } else {
278 message.markUnread();
279 Log.d("xmppService","not auto accepting new file offer with size: "+size+" allowed size:"+this.mJingleConnectionManager.getAutoAcceptFileSize());
280 this.mXmppConnectionService.updateUi(conversation, true);
281 }
282 this.file = this.mXmppConnectionService.getFileBackend().getJingleFile(message,false);
283 if (message.getEncryption() == Message.ENCRYPTION_OTR) {
284 byte[] key = conversation.getSymmetricKey();
285 if (key==null) {
286 this.sendCancel();
287 this.cancel();
288 return;
289 } else {
290 this.file.setKey(conversation.getSymmetricKey());
291 }
292 }
293 this.file.setExpectedSize(size);
294 } else {
295 this.sendCancel();
296 this.cancel();
297 }
298 } else {
299 this.sendCancel();
300 this.cancel();
301 }
302 } else {
303 this.sendCancel();
304 this.cancel();
305 }
306 }
307
308 private void sendInitRequest() {
309 JinglePacket packet = this.bootstrapPacket("session-initiate");
310 Content content = new Content(this.contentCreator,this.contentName);
311 if (message.getType() == Message.TYPE_IMAGE) {
312 content.setTransportId(this.transportId);
313 this.file = this.mXmppConnectionService.getFileBackend().getJingleFile(message,false);
314 if (message.getEncryption() == Message.ENCRYPTION_OTR) {
315 Conversation conversation = this.message.getConversation();
316 this.mXmppConnectionService.renewSymmetricKey(conversation);
317 content.setFileOffer(this.file, true);
318 this.file.setKey(conversation.getSymmetricKey());
319 } else {
320 content.setFileOffer(this.file,false);
321 }
322 this.transportId = this.mJingleConnectionManager.nextRandomId();
323 content.setTransportId(this.transportId);
324 content.socks5transport().setChildren(getCandidatesAsElements());
325 packet.setContent(content);
326 this.sendJinglePacket(packet);
327 this.status = STATUS_INITIATED;
328 }
329 }
330
331 private List<Element> getCandidatesAsElements() {
332 List<Element> elements = new ArrayList<Element>();
333 for(JingleCandidate c : this.candidates) {
334 elements.add(c.toElement());
335 }
336 return elements;
337 }
338
339 private void sendAccept() {
340 status = STATUS_ACCEPTED;
341 mXmppConnectionService.markMessage(message, Message.STATUS_RECIEVING);
342 this.mJingleConnectionManager.getPrimaryCandidate(this.account, new OnPrimaryCandidateFound() {
343
344 @Override
345 public void onPrimaryCandidateFound(boolean success,final JingleCandidate candidate) {
346 final JinglePacket packet = bootstrapPacket("session-accept");
347 final Content content = new Content(contentCreator,contentName);
348 content.setFileOffer(fileOffer);
349 content.setTransportId(transportId);
350 if ((success)&&(!equalCandidateExists(candidate))) {
351 final JingleSocks5Transport socksConnection = new JingleSocks5Transport(JingleConnection.this, candidate);
352 connections.put(candidate.getCid(), socksConnection);
353 socksConnection.connect(new OnTransportConnected() {
354
355 @Override
356 public void failed() {
357 Log.d("xmppService","connection to our own primary candidate failed");
358 content.socks5transport().setChildren(getCandidatesAsElements());
359 packet.setContent(content);
360 sendJinglePacket(packet);
361 connectNextCandidate();
362 }
363
364 @Override
365 public void established() {
366 Log.d("xmppService","connected to primary candidate");
367 mergeCandidate(candidate);
368 content.socks5transport().setChildren(getCandidatesAsElements());
369 packet.setContent(content);
370 sendJinglePacket(packet);
371 connectNextCandidate();
372 }
373 });
374 } else {
375 Log.d("xmppService","did not find a primary candidate for ourself");
376 content.socks5transport().setChildren(getCandidatesAsElements());
377 packet.setContent(content);
378 sendJinglePacket(packet);
379 connectNextCandidate();
380 }
381 }
382 });
383
384 }
385
386 private JinglePacket bootstrapPacket(String action) {
387 JinglePacket packet = new JinglePacket();
388 packet.setAction(action);
389 packet.setFrom(account.getFullJid());
390 packet.setTo(this.message.getCounterpart());
391 packet.setSessionId(this.sessionId);
392 packet.setInitiator(this.initiator);
393 return packet;
394 }
395
396 private void sendJinglePacket(JinglePacket packet) {
397 //Log.d("xmppService",packet.toString());
398 account.getXmppConnection().sendIqPacket(packet,responseListener);
399 }
400
401 private boolean receiveAccept(JinglePacket packet) {
402 Content content = packet.getJingleContent();
403 mergeCandidates(JingleCandidate.parse(content.socks5transport().getChildren()));
404 this.status = STATUS_ACCEPTED;
405 mXmppConnectionService.markMessage(message, Message.STATUS_UNSEND);
406 this.connectNextCandidate();
407 return true;
408 }
409
410 private boolean receiveTransportInfo(JinglePacket packet) {
411 Content content = packet.getJingleContent();
412 if (content.hasSocks5Transport()) {
413 if (content.socks5transport().hasChild("activated")) {
414 if ((this.transport!=null)&&(this.transport instanceof JingleSocks5Transport)) {
415 onProxyActivated.success();
416 } else {
417 String cid = content.socks5transport().findChild("activated").getAttribute("cid");
418 Log.d("xmppService","received proxy activated ("+cid+")prior to choosing our own transport");
419 JingleSocks5Transport connection = this.connections.get(cid);
420 if (connection!=null) {
421 connection.setActivated(true);
422 } else {
423 Log.d("xmppService","activated connection not found");
424 this.sendCancel();
425 this.cancel();
426 }
427 }
428 return true;
429 } else if (content.socks5transport().hasChild("proxy-error")) {
430 onProxyActivated.failed();
431 return true;
432 } else if (content.socks5transport().hasChild("candidate-error")) {
433 Log.d("xmppService","received candidate error");
434 this.receivedCandidate = true;
435 if ((status == STATUS_ACCEPTED)&&(this.sentCandidate)) {
436 this.connect();
437 }
438 return true;
439 } else if (content.socks5transport().hasChild("candidate-used")){
440 String cid = content.socks5transport().findChild("candidate-used").getAttribute("cid");
441 if (cid!=null) {
442 Log.d("xmppService","candidate used by counterpart:"+cid);
443 JingleCandidate candidate = getCandidate(cid);
444 candidate.flagAsUsedByCounterpart();
445 this.receivedCandidate = true;
446 if ((status == STATUS_ACCEPTED)&&(this.sentCandidate)) {
447 this.connect();
448 } else {
449 Log.d("xmppService","ignoring because file is already in transmission or we havent sent our candidate yet");
450 }
451 return true;
452 } else {
453 return false;
454 }
455 } else {
456 return false;
457 }
458 } else {
459 return true;
460 }
461 }
462
463 private void connect() {
464 final JingleSocks5Transport connection = chooseConnection();
465 this.transport = connection;
466 if (connection==null) {
467 Log.d("xmppService","could not find suitable candidate");
468 this.disconnect();
469 if (this.initiator.equals(account.getFullJid())) {
470 this.sendFallbackToIbb();
471 }
472 } else {
473 this.status = STATUS_TRANSMITTING;
474 if (connection.needsActivation()) {
475 if (connection.getCandidate().isOurs()) {
476 Log.d("xmppService","candidate "+connection.getCandidate().getCid()+" was our proxy. going to activate");
477 IqPacket activation = new IqPacket(IqPacket.TYPE_SET);
478 activation.setTo(connection.getCandidate().getJid());
479 activation.query("http://jabber.org/protocol/bytestreams").setAttribute("sid", this.getSessionId());
480 activation.query().addChild("activate").setContent(this.getCounterPart());
481 this.account.getXmppConnection().sendIqPacket(activation, new OnIqPacketReceived() {
482
483 @Override
484 public void onIqPacketReceived(Account account, IqPacket packet) {
485 if (packet.getType()==IqPacket.TYPE_ERROR) {
486 onProxyActivated.failed();
487 } else {
488 onProxyActivated.success();
489 sendProxyActivated(connection.getCandidate().getCid());
490 }
491 }
492 });
493 } else {
494 Log.d("xmppService","candidate "+connection.getCandidate().getCid()+" was a proxy. waiting for other party to activate");
495 }
496 } else {
497 if (initiator.equals(account.getFullJid())) {
498 Log.d("xmppService","we were initiating. sending file");
499 connection.send(file,onFileTransmissionSatusChanged);
500 } else {
501 Log.d("xmppService","we were responding. receiving file");
502 connection.receive(file,onFileTransmissionSatusChanged);
503 }
504 }
505 }
506 }
507
508 private JingleSocks5Transport chooseConnection() {
509 JingleSocks5Transport connection = null;
510 Iterator<Entry<String, JingleSocks5Transport>> it = this.connections.entrySet().iterator();
511 while (it.hasNext()) {
512 Entry<String, JingleSocks5Transport> pairs = it.next();
513 JingleSocks5Transport currentConnection = pairs.getValue();
514 //Log.d("xmppService","comparing candidate: "+currentConnection.getCandidate().toString());
515 if (currentConnection.isEstablished()&&(currentConnection.getCandidate().isUsedByCounterpart()||(!currentConnection.getCandidate().isOurs()))) {
516 //Log.d("xmppService","is usable");
517 if (connection==null) {
518 connection = currentConnection;
519 } else {
520 if (connection.getCandidate().getPriority()<currentConnection.getCandidate().getPriority()) {
521 connection = currentConnection;
522 } else if (connection.getCandidate().getPriority()==currentConnection.getCandidate().getPriority()) {
523 //Log.d("xmppService","found two candidates with same priority");
524 if (initiator.equals(account.getFullJid())) {
525 if (currentConnection.getCandidate().isOurs()) {
526 connection = currentConnection;
527 }
528 } else {
529 if (!currentConnection.getCandidate().isOurs()) {
530 connection = currentConnection;
531 }
532 }
533 }
534 }
535 }
536 it.remove();
537 }
538 return connection;
539 }
540
541 private void sendSuccess() {
542 JinglePacket packet = bootstrapPacket("session-terminate");
543 Reason reason = new Reason();
544 reason.addChild("success");
545 packet.setReason(reason);
546 this.sendJinglePacket(packet);
547 this.disconnect();
548 this.status = STATUS_FINISHED;
549 this.mXmppConnectionService.markMessage(this.message, Message.STATUS_RECIEVED);
550 this.mJingleConnectionManager.finishConnection(this);
551 }
552
553 private void sendFallbackToIbb() {
554 JinglePacket packet = this.bootstrapPacket("transport-replace");
555 Content content = new Content(this.contentCreator,this.contentName);
556 this.transportId = this.mJingleConnectionManager.nextRandomId();
557 content.setTransportId(this.transportId);
558 content.ibbTransport().setAttribute("block-size",""+this.ibbBlockSize);
559 packet.setContent(content);
560 this.sendJinglePacket(packet);
561 }
562
563 private boolean receiveFallbackToIbb(JinglePacket packet) {
564 String receivedBlockSize = packet.getJingleContent().ibbTransport().getAttribute("block-size");
565 if (receivedBlockSize!=null) {
566 int bs = Integer.parseInt(receivedBlockSize);
567 if (bs>this.ibbBlockSize) {
568 this.ibbBlockSize = bs;
569 }
570 }
571 this.transportId = packet.getJingleContent().getTransportId();
572 this.transport = new JingleInbandTransport(this.account,this.responder,this.transportId,this.ibbBlockSize);
573 this.transport.receive(file, onFileTransmissionSatusChanged);
574 JinglePacket answer = bootstrapPacket("transport-accept");
575 Content content = new Content("initiator", "a-file-offer");
576 content.setTransportId(this.transportId);
577 content.ibbTransport().setAttribute("block-size", ""+this.ibbBlockSize);
578 answer.setContent(content);
579 this.sendJinglePacket(answer);
580 return true;
581 }
582
583 private boolean receiveTransportAccept(JinglePacket packet) {
584 if (packet.getJingleContent().hasIbbTransport()) {
585 String receivedBlockSize = packet.getJingleContent().ibbTransport().getAttribute("block-size");
586 if (receivedBlockSize!=null) {
587 int bs = Integer.parseInt(receivedBlockSize);
588 if (bs>this.ibbBlockSize) {
589 this.ibbBlockSize = bs;
590 }
591 }
592 this.transport = new JingleInbandTransport(this.account,this.responder,this.transportId,this.ibbBlockSize);
593 this.transport.connect(new OnTransportConnected() {
594
595 @Override
596 public void failed() {
597 Log.d("xmppService","ibb open failed");
598 }
599
600 @Override
601 public void established() {
602 JingleConnection.this.transport.send(file, onFileTransmissionSatusChanged);
603 }
604 });
605 return true;
606 } else {
607 return false;
608 }
609 }
610
611 private void receiveSuccess() {
612 this.status = STATUS_FINISHED;
613 this.mXmppConnectionService.markMessage(this.message, Message.STATUS_SEND);
614 this.disconnect();
615 this.mJingleConnectionManager.finishConnection(this);
616 }
617
618 public void cancel() {
619 this.disconnect();
620 if (this.message!=null) {
621 if (this.responder.equals(account.getFullJid())) {
622 this.mXmppConnectionService.markMessage(this.message, Message.STATUS_RECEPTION_FAILED);
623 } else {
624 if (this.status == STATUS_INITIATED) {
625 this.mXmppConnectionService.markMessage(this.message, Message.STATUS_SEND_REJECTED);
626 } else {
627 this.mXmppConnectionService.markMessage(this.message, Message.STATUS_SEND_FAILED);
628 }
629 }
630 }
631 this.status = STATUS_CANCELED;
632 this.mJingleConnectionManager.finishConnection(this);
633 }
634
635 private void sendCancel() {
636 JinglePacket packet = bootstrapPacket("session-terminate");
637 Reason reason = new Reason();
638 reason.addChild("cancel");
639 packet.setReason(reason);
640 this.sendJinglePacket(packet);
641 }
642
643 private void connectNextCandidate() {
644 for(JingleCandidate candidate : this.candidates) {
645 if ((!connections.containsKey(candidate.getCid())&&(!candidate.isOurs()))) {
646 this.connectWithCandidate(candidate);
647 return;
648 }
649 }
650 this.sendCandidateError();
651 }
652
653 private void connectWithCandidate(final JingleCandidate candidate) {
654 final JingleSocks5Transport socksConnection = new JingleSocks5Transport(this,candidate);
655 connections.put(candidate.getCid(), socksConnection);
656 socksConnection.connect(new OnTransportConnected() {
657
658 @Override
659 public void failed() {
660 Log.d("xmppService", "connection failed with "+candidate.getHost()+":"+candidate.getPort());
661 connectNextCandidate();
662 }
663
664 @Override
665 public void established() {
666 Log.d("xmppService", "established connection with "+candidate.getHost()+":"+candidate.getPort());
667 sendCandidateUsed(candidate.getCid());
668 }
669 });
670 }
671
672 private void disconnect() {
673 Iterator<Entry<String, JingleSocks5Transport>> it = this.connections.entrySet().iterator();
674 while (it.hasNext()) {
675 Entry<String, JingleSocks5Transport> pairs = it.next();
676 pairs.getValue().disconnect();
677 it.remove();
678 }
679 }
680
681 private void sendProxyActivated(String cid) {
682 JinglePacket packet = bootstrapPacket("transport-info");
683 Content content = new Content(this.contentCreator,this.contentName);
684 content.setTransportId(this.transportId);
685 content.socks5transport().addChild("activated").setAttribute("cid", cid);
686 packet.setContent(content);
687 this.sendJinglePacket(packet);
688 }
689
690 private void sendCandidateUsed(final String cid) {
691 JinglePacket packet = bootstrapPacket("transport-info");
692 Content content = new Content(this.contentCreator,this.contentName);
693 content.setTransportId(this.transportId);
694 content.socks5transport().addChild("candidate-used").setAttribute("cid", cid);
695 packet.setContent(content);
696 this.sentCandidate = true;
697 if ((receivedCandidate)&&(status == STATUS_ACCEPTED)) {
698 connect();
699 }
700 this.sendJinglePacket(packet);
701 }
702
703 private void sendCandidateError() {
704 JinglePacket packet = bootstrapPacket("transport-info");
705 Content content = new Content(this.contentCreator,this.contentName);
706 content.setTransportId(this.transportId);
707 content.socks5transport().addChild("candidate-error");
708 packet.setContent(content);
709 this.sentCandidate = true;
710 if ((receivedCandidate)&&(status == STATUS_ACCEPTED)) {
711 connect();
712 }
713 this.sendJinglePacket(packet);
714 }
715
716 public String getInitiator() {
717 return this.initiator;
718 }
719
720 public String getResponder() {
721 return this.responder;
722 }
723
724 public int getStatus() {
725 return this.status;
726 }
727
728 private boolean equalCandidateExists(JingleCandidate candidate) {
729 for(JingleCandidate c : this.candidates) {
730 if (c.equalValues(candidate)) {
731 return true;
732 }
733 }
734 return false;
735 }
736
737 private void mergeCandidate(JingleCandidate candidate) {
738 for(JingleCandidate c : this.candidates) {
739 if (c.equals(candidate)) {
740 return;
741 }
742 }
743 this.candidates.add(candidate);
744 }
745
746 private void mergeCandidates(List<JingleCandidate> candidates) {
747 for(JingleCandidate c : candidates) {
748 mergeCandidate(c);
749 }
750 }
751
752 private JingleCandidate getCandidate(String cid) {
753 for(JingleCandidate c : this.candidates) {
754 if (c.getCid().equals(cid)) {
755 return c;
756 }
757 }
758 return null;
759 }
760
761 interface OnProxyActivated {
762 public void success();
763 public void failed();
764 }
765
766 public boolean hasTransportId(String sid) {
767 return sid.equals(this.transportId);
768 }
769
770 public JingleTransport getTransport() {
771 return this.transport;
772 }
773
774 public void accept() {
775 if (status==STATUS_INITIATED) {
776 new Thread(new Runnable() {
777
778 @Override
779 public void run() {
780 sendAccept();
781 }
782 }).start();
783 } else {
784 Log.d("xmppService","status ("+status+") was not ok");
785 }
786 }
787}