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