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