XmppConnection.java

  1package eu.siacs.conversations.xmpp;
  2
  3import java.io.IOException;
  4import java.io.InputStream;
  5import java.io.OutputStream;
  6import java.math.BigInteger;
  7import java.net.Socket;
  8import java.net.UnknownHostException;
  9import java.security.KeyManagementException;
 10import java.security.KeyStore;
 11import java.security.KeyStoreException;
 12import java.security.MessageDigest;
 13import java.security.NoSuchAlgorithmException;
 14import java.security.SecureRandom;
 15import java.security.cert.CertPathValidatorException;
 16import java.security.cert.CertificateException;
 17import java.security.cert.X509Certificate;
 18import java.util.HashSet;
 19import java.util.Hashtable;
 20import java.util.List;
 21
 22import javax.net.ssl.SSLContext;
 23import javax.net.ssl.SSLSocket;
 24import javax.net.ssl.SSLSocketFactory;
 25import javax.net.ssl.TrustManager;
 26import javax.net.ssl.TrustManagerFactory;
 27import javax.net.ssl.X509TrustManager;
 28
 29import org.json.JSONException;
 30import org.xmlpull.v1.XmlPullParserException;
 31
 32import android.os.Bundle;
 33import android.os.PowerManager;
 34import android.os.SystemClock;
 35import android.util.Log;
 36import eu.siacs.conversations.entities.Account;
 37import eu.siacs.conversations.utils.CryptoHelper;
 38import eu.siacs.conversations.utils.DNSHelper;
 39import eu.siacs.conversations.xml.Element;
 40import eu.siacs.conversations.xml.Tag;
 41import eu.siacs.conversations.xml.TagWriter;
 42import eu.siacs.conversations.xml.XmlReader;
 43import eu.siacs.conversations.xmpp.stanzas.AbstractStanza;
 44import eu.siacs.conversations.xmpp.stanzas.IqPacket;
 45import eu.siacs.conversations.xmpp.stanzas.MessagePacket;
 46import eu.siacs.conversations.xmpp.stanzas.PresencePacket;
 47import eu.siacs.conversations.xmpp.stanzas.streammgmt.AckPacket;
 48import eu.siacs.conversations.xmpp.stanzas.streammgmt.EnablePacket;
 49import eu.siacs.conversations.xmpp.stanzas.streammgmt.RequestPacket;
 50import eu.siacs.conversations.xmpp.stanzas.streammgmt.ResumePacket;
 51
 52public class XmppConnection implements Runnable {
 53
 54	protected Account account;
 55	private static final String LOGTAG = "xmppService";
 56
 57	private PowerManager.WakeLock wakeLock;
 58
 59	private SecureRandom random = new SecureRandom();
 60
 61	private Socket socket;
 62	private XmlReader tagReader;
 63	private TagWriter tagWriter;
 64
 65	private boolean shouldBind = true;
 66	private boolean shouldAuthenticate = true;
 67	private Element streamFeatures;
 68	private HashSet<String> discoFeatures = new HashSet<String>();
 69	
 70	private String streamId = null;
 71	
 72	private int stanzasReceived = 0;
 73	private int stanzasSent = 0;
 74	
 75	public long lastPaketReceived = 0;
 76	public long lastPingSent = 0;
 77	public long lastConnect = 0;
 78	public long lastSessionStarted = 0;
 79
 80	private static final int PACKET_IQ = 0;
 81	private static final int PACKET_MESSAGE = 1;
 82	private static final int PACKET_PRESENCE = 2;
 83
 84	private Hashtable<String, PacketReceived> packetCallbacks = new Hashtable<String, PacketReceived>();
 85	private OnPresencePacketReceived presenceListener = null;
 86	private OnIqPacketReceived unregisteredIqListener = null;
 87	private OnMessagePacketReceived messageListener = null;
 88	private OnStatusChanged statusListener = null;
 89	private OnTLSExceptionReceived tlsListener;
 90
 91	public XmppConnection(Account account, PowerManager pm) {
 92		this.account = account;
 93		wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
 94				"XmppConnection");
 95		tagReader = new XmlReader(wakeLock);
 96		tagWriter = new TagWriter();
 97	}
 98
 99	protected void changeStatus(int nextStatus) {
100		if (account.getStatus() != nextStatus) {
101			account.setStatus(nextStatus);
102			if (statusListener != null) {
103				statusListener.onStatusChanged(account);
104			}
105		}
106	}
107
108	protected void connect() {
109		Log.d(LOGTAG,account.getJid()+ ": connecting");
110		lastConnect = SystemClock.elapsedRealtime();
111		try {
112			tagReader = new XmlReader(wakeLock);
113			tagWriter = new TagWriter();
114			packetCallbacks.clear();
115			this.changeStatus(Account.STATUS_CONNECTING);
116			Bundle namePort = DNSHelper.getSRVRecord(account.getServer());
117			String srvRecordServer = namePort.getString("name");
118			int srvRecordPort = namePort.getInt("port");
119			if (srvRecordServer != null) {
120				Log.d(LOGTAG, account.getJid() + ": using values from dns "
121						+ srvRecordServer + ":" + srvRecordPort);
122				socket = new Socket(srvRecordServer, srvRecordPort);
123			} else {
124				socket = new Socket(account.getServer(), 5222);
125			}
126			OutputStream out = socket.getOutputStream();
127			tagWriter.setOutputStream(out);
128			InputStream in = socket.getInputStream();
129			tagReader.setInputStream(in);
130			tagWriter.beginDocument();
131			sendStartStream();
132			Tag nextTag;
133			while ((nextTag = tagReader.readTag()) != null) {
134				if (nextTag.isStart("stream")) {
135					processStream(nextTag);
136					break;
137				} else {
138					Log.d(LOGTAG, "found unexpected tag: " + nextTag.getName());
139					return;
140				}
141			}
142			if (socket.isConnected()) {
143				socket.close();
144			}
145		} catch (UnknownHostException e) {
146			this.changeStatus(Account.STATUS_SERVER_NOT_FOUND);
147			if (wakeLock.isHeld()) {
148				wakeLock.release();
149			}
150			return;
151		} catch (IOException e) {
152			if (account.getStatus() != Account.STATUS_TLS_ERROR) {
153				this.changeStatus(Account.STATUS_OFFLINE);
154			}
155			if (wakeLock.isHeld()) {
156				wakeLock.release();
157			}
158			return;
159		} catch (XmlPullParserException e) {
160			this.changeStatus(Account.STATUS_OFFLINE);
161			Log.d(LOGTAG, "xml exception " + e.getMessage());
162			if (wakeLock.isHeld()) {
163				wakeLock.release();
164			}
165			return;
166		}
167
168	}
169
170	@Override
171	public void run() {
172		connect();
173	}
174
175	private void processStream(Tag currentTag) throws XmlPullParserException,
176			IOException {
177		Tag nextTag = tagReader.readTag();
178		while ((nextTag != null) && (!nextTag.isEnd("stream"))) {
179			if (nextTag.isStart("error")) {
180				processStreamError(nextTag);
181			} else if (nextTag.isStart("features")) {
182				processStreamFeatures(nextTag);
183				if ((streamFeatures.getChildren().size() == 1)
184						&& (streamFeatures.hasChild("starttls"))
185						&& (!account.isOptionSet(Account.OPTION_USETLS))) {
186					changeStatus(Account.STATUS_SERVER_REQUIRES_TLS);
187				}
188			} else if (nextTag.isStart("proceed")) {
189				switchOverToTls(nextTag);
190			} else if (nextTag.isStart("success")) {
191				Log.d(LOGTAG, account.getJid()
192						+ ": logged in");
193				tagReader.readTag();
194				tagReader.reset();
195				sendStartStream();
196				processStream(tagReader.readTag());
197				break;
198			} else if (nextTag.isStart("failure")) {
199				tagReader.readElement(nextTag);
200				changeStatus(Account.STATUS_UNAUTHORIZED);
201			} else if (nextTag.isStart("enabled")) {
202				this.stanzasSent = 0;
203				Element enabled = tagReader.readElement(nextTag);
204				if ("true".equals(enabled.getAttribute("resume"))) {
205					this.streamId = enabled.getAttribute("id");
206					Log.d(LOGTAG,account.getJid()+": stream managment enabled (resumable)");
207				} else {
208					Log.d(LOGTAG,account.getJid()+": stream managment enabled");
209				}
210				this.lastSessionStarted = SystemClock.elapsedRealtime();
211				this.stanzasReceived = 0;
212				RequestPacket r = new RequestPacket();
213				tagWriter.writeStanzaAsync(r);
214			} else if (nextTag.isStart("resumed")) {
215				tagReader.readElement(nextTag);
216				changeStatus(Account.STATUS_ONLINE);
217				Log.d(LOGTAG,account.getJid()+": session resumed");
218			} else if (nextTag.isStart("r")) {
219				tagReader.readElement(nextTag);
220				AckPacket ack = new AckPacket(this.stanzasReceived);
221				//Log.d(LOGTAG,ack.toString());
222				tagWriter.writeStanzaAsync(ack);
223			} else if (nextTag.isStart("a")) {
224				Element ack = tagReader.readElement(nextTag);
225				lastPaketReceived = SystemClock.elapsedRealtime();
226				int serverSequence = Integer.parseInt(ack.getAttribute("h"));
227				if (serverSequence>this.stanzasSent) {
228					this.stanzasSent = serverSequence;
229				}
230				//Log.d(LOGTAG,"server ack"+ack.toString()+" ("+this.stanzasSent+")");
231			} else if (nextTag.isStart("failed")) {
232				tagReader.readElement(nextTag);
233				Log.d(LOGTAG,account.getJid()+": resumption failed");
234				streamId = null;
235				if (account.getStatus() != Account.STATUS_ONLINE) {
236					sendBindRequest();
237				}
238			} else if (nextTag.isStart("iq")) {
239				processIq(nextTag);
240			} else if (nextTag.isStart("message")) {
241				processMessage(nextTag);
242			} else if (nextTag.isStart("presence")) {
243				processPresence(nextTag);
244			} else {
245				Log.d(LOGTAG, "found unexpected tag: " + nextTag.getName()
246						+ " as child of " + currentTag.getName());
247			}
248			nextTag = tagReader.readTag();
249		}
250		if (account.getStatus() == Account.STATUS_ONLINE) {
251			account.setStatus(Account.STATUS_OFFLINE);
252			if (statusListener != null) {
253				statusListener.onStatusChanged(account);
254			}
255		}
256	}
257
258	private Element processPacket(Tag currentTag, int packetType)
259			throws XmlPullParserException, IOException {
260		Element element;
261		switch (packetType) {
262		case PACKET_IQ:
263			element = new IqPacket();
264			break;
265		case PACKET_MESSAGE:
266			element = new MessagePacket();
267			break;
268		case PACKET_PRESENCE:
269			element = new PresencePacket();
270			break;
271		default:
272			return null;
273		}
274		element.setAttributes(currentTag.getAttributes());
275		Tag nextTag = tagReader.readTag();
276		while (!nextTag.isEnd(element.getName())) {
277			if (!nextTag.isNo()) {
278				Element child = tagReader.readElement(nextTag);
279				element.addChild(child);
280			}
281			nextTag = tagReader.readTag();
282		}
283		++stanzasReceived;
284		lastPaketReceived = SystemClock.elapsedRealtime();
285		return element;
286	}
287
288	private void processIq(Tag currentTag) throws XmlPullParserException,
289			IOException {
290		IqPacket packet = (IqPacket) processPacket(currentTag, PACKET_IQ);
291		if (packetCallbacks.containsKey(packet.getId())) {
292			if (packetCallbacks.get(packet.getId()) instanceof OnIqPacketReceived) {
293				((OnIqPacketReceived) packetCallbacks.get(packet.getId()))
294						.onIqPacketReceived(account, packet);
295			}
296
297			packetCallbacks.remove(packet.getId());
298		} else if (this.unregisteredIqListener != null) {
299			this.unregisteredIqListener.onIqPacketReceived(account, packet);
300		}
301	}
302
303	private void processMessage(Tag currentTag) throws XmlPullParserException,
304			IOException {
305		MessagePacket packet = (MessagePacket) processPacket(currentTag,
306				PACKET_MESSAGE);
307		String id = packet.getAttribute("id");
308		if ((id != null) && (packetCallbacks.containsKey(id))) {
309			if (packetCallbacks.get(id) instanceof OnMessagePacketReceived) {
310				((OnMessagePacketReceived) packetCallbacks.get(id))
311						.onMessagePacketReceived(account, packet);
312			}
313			packetCallbacks.remove(id);
314		} else if (this.messageListener != null) {
315			this.messageListener.onMessagePacketReceived(account, packet);
316		}
317	}
318
319	private void processPresence(Tag currentTag) throws XmlPullParserException,
320			IOException {
321		PresencePacket packet = (PresencePacket) processPacket(currentTag,
322				PACKET_PRESENCE);
323		String id = packet.getAttribute("id");
324		if ((id != null) && (packetCallbacks.containsKey(id))) {
325			if (packetCallbacks.get(id) instanceof OnPresencePacketReceived) {
326				((OnPresencePacketReceived) packetCallbacks.get(id))
327						.onPresencePacketReceived(account, packet);
328			}
329			packetCallbacks.remove(id);
330		} else if (this.presenceListener != null) {
331			this.presenceListener.onPresencePacketReceived(account, packet);
332		}
333	}
334
335	private void sendStartTLS() throws IOException {
336		Tag startTLS = Tag.empty("starttls");
337		startTLS.setAttribute("xmlns", "urn:ietf:params:xml:ns:xmpp-tls");
338		tagWriter.writeTag(startTLS);
339	}
340
341	private void switchOverToTls(Tag currentTag) throws XmlPullParserException,
342			IOException {
343		Tag nextTag = tagReader.readTag(); // should be proceed end tag
344		try {
345			SSLContext sc = SSLContext.getInstance("TLS");
346			TrustManagerFactory tmf = TrustManagerFactory
347					.getInstance(TrustManagerFactory.getDefaultAlgorithm());
348			// Initialise the TMF as you normally would, for example:
349			// tmf.in
350			try {
351				tmf.init((KeyStore) null);
352			} catch (KeyStoreException e1) {
353				// TODO Auto-generated catch block
354				e1.printStackTrace();
355			}
356
357			TrustManager[] trustManagers = tmf.getTrustManagers();
358			final X509TrustManager origTrustmanager = (X509TrustManager) trustManagers[0];
359
360			TrustManager[] wrappedTrustManagers = new TrustManager[] { new X509TrustManager() {
361
362				@Override
363				public void checkClientTrusted(X509Certificate[] chain,
364						String authType) throws CertificateException {
365					origTrustmanager.checkClientTrusted(chain, authType);
366				}
367
368				@Override
369				public void checkServerTrusted(X509Certificate[] chain,
370						String authType) throws CertificateException {
371					try {
372						origTrustmanager.checkServerTrusted(chain, authType);
373					} catch (CertificateException e) {
374						if (e.getCause() instanceof CertPathValidatorException) {
375							String sha;
376							try {
377								MessageDigest sha1 = MessageDigest.getInstance("SHA1");
378								sha1.update(chain[0].getEncoded());
379								sha = CryptoHelper.bytesToHex(sha1.digest());
380								if (!sha.equals(account.getSSLFingerprint())) {
381									changeStatus(Account.STATUS_TLS_ERROR);
382									if (tlsListener!=null) {
383										tlsListener.onTLSExceptionReceived(sha,account);
384									}
385									throw new CertificateException();
386								}
387							} catch (NoSuchAlgorithmException e1) {
388								// TODO Auto-generated catch block
389								e1.printStackTrace();
390							}
391						} else {
392							throw new CertificateException();
393						}
394					}
395				}
396
397				@Override
398				public X509Certificate[] getAcceptedIssuers() {
399					return origTrustmanager.getAcceptedIssuers();
400				}
401
402			} };
403			sc.init(null, wrappedTrustManagers, null);
404			SSLSocketFactory factory = sc.getSocketFactory();
405			SSLSocket sslSocket = (SSLSocket) factory.createSocket(socket,
406						socket.getInetAddress().getHostAddress(), socket.getPort(),
407						true);
408			tagReader.setInputStream(sslSocket.getInputStream());
409			tagWriter.setOutputStream(sslSocket.getOutputStream());
410			sendStartStream();
411			Log.d(LOGTAG,account.getJid()+": TLS connection established");
412			processStream(tagReader.readTag());
413			sslSocket.close();
414		} catch (NoSuchAlgorithmException e1) {
415			// TODO Auto-generated catch block
416			e1.printStackTrace();
417		} catch (KeyManagementException e) {
418			// TODO Auto-generated catch block
419			e.printStackTrace();
420		}
421	}
422
423	private void sendSaslAuth() throws IOException, XmlPullParserException {
424		String saslString = CryptoHelper.saslPlain(account.getUsername(),
425				account.getPassword());
426		Element auth = new Element("auth");
427		auth.setAttribute("xmlns", "urn:ietf:params:xml:ns:xmpp-sasl");
428		auth.setAttribute("mechanism", "PLAIN");
429		auth.setContent(saslString);
430		tagWriter.writeElement(auth);
431	}
432
433	private void processStreamFeatures(Tag currentTag)
434			throws XmlPullParserException, IOException {
435		this.streamFeatures = tagReader.readElement(currentTag);
436		if (this.streamFeatures.hasChild("starttls")
437				&& account.isOptionSet(Account.OPTION_USETLS)) {
438			sendStartTLS();
439		} else if (this.streamFeatures.hasChild("mechanisms")
440				&& shouldAuthenticate) {
441			sendSaslAuth();
442		} else if (this.streamFeatures.hasChild("sm") && streamId != null) {
443			Log.d(LOGTAG,"found old stream id. trying to remuse");
444			ResumePacket resume = new ResumePacket(this.streamId,stanzasReceived);
445			this.tagWriter.writeStanzaAsync(resume);
446		} else if (this.streamFeatures.hasChild("bind") && shouldBind) {
447			sendBindRequest();
448			if (this.streamFeatures.hasChild("session")) {
449				Log.d(LOGTAG,"sending session");
450				IqPacket startSession = new IqPacket(IqPacket.TYPE_SET);
451				Element session = new Element("session");
452				session.setAttribute("xmlns",
453						"urn:ietf:params:xml:ns:xmpp-session");
454				session.setContent("");
455				startSession.addChild(session);
456				this.sendIqPacket(startSession, null);
457			}
458		}
459	}
460
461	private void sendInitialPresence() {
462		PresencePacket packet = new PresencePacket();
463		packet.setAttribute("from", account.getFullJid());
464		if (account.getKeys().has("pgp_signature")) {
465			try {
466				String signature = account.getKeys().getString("pgp_signature");	
467				Element status = new Element("status");
468				status.setContent("online");
469				packet.addChild(status);
470				Element x = new Element("x");
471				x.setAttribute("xmlns", "jabber:x:signed");
472				x.setContent(signature);
473				packet.addChild(x);
474			} catch (JSONException e) {
475				//
476			}
477		}
478		this.sendPresencePacket(packet);
479	}
480
481	private void sendBindRequest() throws IOException {
482		IqPacket iq = new IqPacket(IqPacket.TYPE_SET);
483		Element bind = new Element("bind");
484		bind.setAttribute("xmlns", "urn:ietf:params:xml:ns:xmpp-bind");
485		Element resource = new Element("resource");
486		resource.setContent("Conversations");
487		bind.addChild(resource);
488		iq.addChild(bind);
489		this.sendIqPacket(iq, new OnIqPacketReceived() {
490			@Override
491			public void onIqPacketReceived(Account account, IqPacket packet) {
492				String resource = packet.findChild("bind").findChild("jid")
493						.getContent().split("/")[1];
494				account.setResource(resource);
495				account.setStatus(Account.STATUS_ONLINE);
496				if (streamFeatures.hasChild("sm")) {
497					EnablePacket enable = new EnablePacket();
498					tagWriter.writeStanzaAsync(enable);
499				}
500				sendInitialPresence();
501				sendServiceDiscovery();
502				if (statusListener != null) {
503					statusListener.onStatusChanged(account);
504				}
505			}
506		});
507	}
508
509	private void sendServiceDiscovery() {
510		IqPacket iq = new IqPacket(IqPacket.TYPE_GET);
511		iq.setAttribute("to", account.getServer());
512		Element query = new Element("query");
513		query.setAttribute("xmlns", "http://jabber.org/protocol/disco#info");
514		iq.addChild(query);
515		this.sendIqPacket(iq, new OnIqPacketReceived() {
516
517			@Override
518			public void onIqPacketReceived(Account account, IqPacket packet) {
519				if (packet.hasChild("query")) {
520					List<Element> elements = packet.findChild("query")
521							.getChildren();
522					for (int i = 0; i < elements.size(); ++i) {
523						if (elements.get(i).getName().equals("feature")) {
524							discoFeatures.add(elements.get(i).getAttribute(
525									"var"));
526						}
527					}
528				}
529				if (discoFeatures.contains("urn:xmpp:carbons:2")) {
530					sendEnableCarbons();
531				}
532			}
533		});
534	}
535
536	private void sendEnableCarbons() {
537		IqPacket iq = new IqPacket(IqPacket.TYPE_SET);
538		Element enable = new Element("enable");
539		enable.setAttribute("xmlns", "urn:xmpp:carbons:2");
540		iq.addChild(enable);
541		this.sendIqPacket(iq, new OnIqPacketReceived() {
542
543			@Override
544			public void onIqPacketReceived(Account account, IqPacket packet) {
545				if (!packet.hasChild("error")) {
546					Log.d(LOGTAG, account.getJid()
547							+ ": successfully enabled carbons");
548				} else {
549					Log.d(LOGTAG, account.getJid()
550							+ ": error enableing carbons " + packet.toString());
551				}
552			}
553		});
554	}
555
556	private void processStreamError(Tag currentTag) {
557		Log.d(LOGTAG, "processStreamError");
558	}
559
560	private void sendStartStream() throws IOException {
561		Tag stream = Tag.start("stream:stream");
562		stream.setAttribute("from", account.getJid());
563		stream.setAttribute("to", account.getServer());
564		stream.setAttribute("version", "1.0");
565		stream.setAttribute("xml:lang", "en");
566		stream.setAttribute("xmlns", "jabber:client");
567		stream.setAttribute("xmlns:stream", "http://etherx.jabber.org/streams");
568		tagWriter.writeTag(stream);
569	}
570
571	private String nextRandomId() {
572		return new BigInteger(50, random).toString(32);
573	}
574
575	public void sendIqPacket(IqPacket packet, OnIqPacketReceived callback) {
576		String id = nextRandomId();
577		packet.setAttribute("id", id);
578		this.sendPacket(packet, callback);
579	}
580
581	public void sendMessagePacket(MessagePacket packet) {
582		this.sendPacket(packet, null);
583	}
584
585	public void sendMessagePacket(MessagePacket packet,
586			OnMessagePacketReceived callback) {
587		this.sendPacket(packet, callback);
588	}
589
590	public void sendPresencePacket(PresencePacket packet) {
591		this.sendPacket(packet, null);
592	}
593
594	public void sendPresencePacket(PresencePacket packet,
595			OnPresencePacketReceived callback) {
596		this.sendPacket(packet, callback);
597	}
598	
599	private synchronized void sendPacket(final AbstractStanza packet, PacketReceived callback) {
600		// TODO dont increment stanza count if packet = request packet or ack;
601		++stanzasSent;
602		tagWriter.writeStanzaAsync(packet);
603		if (callback != null) {
604			if (packet.getId()==null) {
605				packet.setId(nextRandomId());
606			}
607			packetCallbacks.put(packet.getId(), callback);
608		}
609	}
610	
611	public void sendPing() {
612		if (streamFeatures.hasChild("sm")) {
613			Log.d(LOGTAG,account.getJid()+": sending r as ping");
614			tagWriter.writeStanzaAsync(new RequestPacket());
615		} else {
616			Log.d(LOGTAG,account.getJid()+": sending iq as ping");
617			IqPacket iq = new IqPacket(IqPacket.TYPE_GET);
618			Element ping = new Element("ping");
619			iq.setAttribute("from",account.getFullJid());
620			ping.setAttribute("xmlns", "urn:xmpp:ping");
621			iq.addChild(ping);
622			this.sendIqPacket(iq, null);
623		}
624	}
625
626	public void setOnMessagePacketReceivedListener(
627			OnMessagePacketReceived listener) {
628		this.messageListener = listener;
629	}
630
631	public void setOnUnregisteredIqPacketReceivedListener(
632			OnIqPacketReceived listener) {
633		this.unregisteredIqListener = listener;
634	}
635
636	public void setOnPresencePacketReceivedListener(
637			OnPresencePacketReceived listener) {
638		this.presenceListener = listener;
639	}
640
641	public void setOnStatusChangedListener(OnStatusChanged listener) {
642		this.statusListener = listener;
643	}
644	
645	public void setOnTLSExceptionReceivedListener(OnTLSExceptionReceived listener) {
646		this.tlsListener = listener;
647	}
648
649	public void disconnect(boolean force) {
650		Log.d(LOGTAG,"disconnecting");
651		try {
652		if (force) {
653				socket.close();
654				return;
655		}
656		tagWriter.finish();
657		while(!tagWriter.finished()) {
658			//Log.d(LOGTAG,"not yet finished");
659			Thread.sleep(100);
660		}
661		tagWriter.writeTag(Tag.end("stream:stream"));
662		} catch (IOException e) {
663			Log.d(LOGTAG,"io exception during disconnect");
664		} catch (InterruptedException e) {
665			Log.d(LOGTAG,"interupted while waiting for disconnect");
666		}
667	}
668	
669	public boolean hasFeatureRosterManagment() {
670		if (this.streamFeatures==null) {
671			return false;
672		} else {
673			return this.streamFeatures.hasChild("ver");
674		}
675	}
676
677	public void r() {
678		this.tagWriter.writeStanzaAsync(new RequestPacket());
679	}
680
681	public int getReceivedStanzas() {
682		return this.stanzasReceived;
683	}
684	
685	public int getSentStanzas() {
686		return this.stanzasSent;
687	}
688}