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.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}