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}