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