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