JingleConnection.java

  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}