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