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 changeStatus(Account.STATUS_ONLINE);
224 Log.d(LOGTAG,account.getJid()+": session resumed");
225 } else if (nextTag.isStart("r")) {
226 tagReader.readElement(nextTag);
227 AckPacket ack = new AckPacket(this.stanzasReceived);
228 //Log.d(LOGTAG,ack.toString());
229 tagWriter.writeStanzaAsync(ack);
230 } else if (nextTag.isStart("a")) {
231 Element ack = tagReader.readElement(nextTag);
232 lastPaketReceived = SystemClock.elapsedRealtime();
233 int serverSequence = Integer.parseInt(ack.getAttribute("h"));
234 if (serverSequence>this.stanzasSent) {
235 this.stanzasSent = serverSequence;
236 }
237 //Log.d(LOGTAG,"server ack"+ack.toString()+" ("+this.stanzasSent+")");
238 } else if (nextTag.isStart("failed")) {
239 tagReader.readElement(nextTag);
240 Log.d(LOGTAG,account.getJid()+": resumption failed");
241 streamId = null;
242 if (account.getStatus() != Account.STATUS_ONLINE) {
243 sendBindRequest();
244 }
245 } else if (nextTag.isStart("iq")) {
246 processIq(nextTag);
247 } else if (nextTag.isStart("message")) {
248 processMessage(nextTag);
249 } else if (nextTag.isStart("presence")) {
250 processPresence(nextTag);
251 } else {
252 Log.d(LOGTAG, "found unexpected tag: " + nextTag.getName()
253 + " as child of " + currentTag.getName());
254 }
255 nextTag = tagReader.readTag();
256 }
257 if (account.getStatus() == Account.STATUS_ONLINE) {
258 account.setStatus(Account.STATUS_OFFLINE);
259 if (statusListener != null) {
260 statusListener.onStatusChanged(account);
261 }
262 }
263 }
264
265 private Element processPacket(Tag currentTag, int packetType)
266 throws XmlPullParserException, IOException {
267 Element element;
268 switch (packetType) {
269 case PACKET_IQ:
270 element = new IqPacket();
271 break;
272 case PACKET_MESSAGE:
273 element = new MessagePacket();
274 break;
275 case PACKET_PRESENCE:
276 element = new PresencePacket();
277 break;
278 default:
279 return null;
280 }
281 element.setAttributes(currentTag.getAttributes());
282 Tag nextTag = tagReader.readTag();
283 while (!nextTag.isEnd(element.getName())) {
284 if (!nextTag.isNo()) {
285 Element child = tagReader.readElement(nextTag);
286 element.addChild(child);
287 }
288 nextTag = tagReader.readTag();
289 }
290 ++stanzasReceived;
291 lastPaketReceived = SystemClock.elapsedRealtime();
292 return element;
293 }
294
295 private void processIq(Tag currentTag) throws XmlPullParserException,
296 IOException {
297 IqPacket packet = (IqPacket) processPacket(currentTag, PACKET_IQ);
298 if (packetCallbacks.containsKey(packet.getId())) {
299 if (packetCallbacks.get(packet.getId()) instanceof OnIqPacketReceived) {
300 ((OnIqPacketReceived) packetCallbacks.get(packet.getId()))
301 .onIqPacketReceived(account, packet);
302 }
303
304 packetCallbacks.remove(packet.getId());
305 } else if (this.unregisteredIqListener != null) {
306 this.unregisteredIqListener.onIqPacketReceived(account, packet);
307 }
308 }
309
310 private void processMessage(Tag currentTag) throws XmlPullParserException,
311 IOException {
312 MessagePacket packet = (MessagePacket) processPacket(currentTag,
313 PACKET_MESSAGE);
314 String id = packet.getAttribute("id");
315 if ((id != null) && (packetCallbacks.containsKey(id))) {
316 if (packetCallbacks.get(id) instanceof OnMessagePacketReceived) {
317 ((OnMessagePacketReceived) packetCallbacks.get(id))
318 .onMessagePacketReceived(account, packet);
319 }
320 packetCallbacks.remove(id);
321 } else if (this.messageListener != null) {
322 this.messageListener.onMessagePacketReceived(account, packet);
323 }
324 }
325
326 private void processPresence(Tag currentTag) throws XmlPullParserException,
327 IOException {
328 PresencePacket packet = (PresencePacket) processPacket(currentTag,
329 PACKET_PRESENCE);
330 String id = packet.getAttribute("id");
331 if ((id != null) && (packetCallbacks.containsKey(id))) {
332 if (packetCallbacks.get(id) instanceof OnPresencePacketReceived) {
333 ((OnPresencePacketReceived) packetCallbacks.get(id))
334 .onPresencePacketReceived(account, packet);
335 }
336 packetCallbacks.remove(id);
337 } else if (this.presenceListener != null) {
338 this.presenceListener.onPresencePacketReceived(account, packet);
339 }
340 }
341
342 private void sendStartTLS() throws IOException {
343 Tag startTLS = Tag.empty("starttls");
344 startTLS.setAttribute("xmlns", "urn:ietf:params:xml:ns:xmpp-tls");
345 tagWriter.writeTag(startTLS);
346 }
347
348 private void switchOverToTls(Tag currentTag) throws XmlPullParserException,
349 IOException {
350 Tag nextTag = tagReader.readTag(); // should be proceed end tag
351 try {
352 SSLContext sc = SSLContext.getInstance("TLS");
353 TrustManagerFactory tmf = TrustManagerFactory
354 .getInstance(TrustManagerFactory.getDefaultAlgorithm());
355 // Initialise the TMF as you normally would, for example:
356 // tmf.in
357 try {
358 tmf.init((KeyStore) null);
359 } catch (KeyStoreException e1) {
360 // TODO Auto-generated catch block
361 e1.printStackTrace();
362 }
363
364 TrustManager[] trustManagers = tmf.getTrustManagers();
365 final X509TrustManager origTrustmanager = (X509TrustManager) trustManagers[0];
366
367 TrustManager[] wrappedTrustManagers = new TrustManager[] { new X509TrustManager() {
368
369 @Override
370 public void checkClientTrusted(X509Certificate[] chain,
371 String authType) throws CertificateException {
372 origTrustmanager.checkClientTrusted(chain, authType);
373 }
374
375 @Override
376 public void checkServerTrusted(X509Certificate[] chain,
377 String authType) throws CertificateException {
378 try {
379 origTrustmanager.checkServerTrusted(chain, authType);
380 } catch (CertificateException e) {
381 if (e.getCause() instanceof CertPathValidatorException) {
382 String sha;
383 try {
384 MessageDigest sha1 = MessageDigest.getInstance("SHA1");
385 sha1.update(chain[0].getEncoded());
386 sha = CryptoHelper.bytesToHex(sha1.digest());
387 if (!sha.equals(account.getSSLFingerprint())) {
388 changeStatus(Account.STATUS_TLS_ERROR);
389 if (tlsListener!=null) {
390 tlsListener.onTLSExceptionReceived(sha,account);
391 }
392 throw new CertificateException();
393 }
394 } catch (NoSuchAlgorithmException e1) {
395 // TODO Auto-generated catch block
396 e1.printStackTrace();
397 }
398 } else {
399 throw new CertificateException();
400 }
401 }
402 }
403
404 @Override
405 public X509Certificate[] getAcceptedIssuers() {
406 return origTrustmanager.getAcceptedIssuers();
407 }
408
409 } };
410 sc.init(null, wrappedTrustManagers, null);
411 SSLSocketFactory factory = sc.getSocketFactory();
412 SSLSocket sslSocket = (SSLSocket) factory.createSocket(socket,
413 socket.getInetAddress().getHostAddress(), socket.getPort(),
414 true);
415 tagReader.setInputStream(sslSocket.getInputStream());
416 tagWriter.setOutputStream(sslSocket.getOutputStream());
417 sendStartStream();
418 Log.d(LOGTAG,account.getJid()+": TLS connection established");
419 processStream(tagReader.readTag());
420 sslSocket.close();
421 } catch (NoSuchAlgorithmException e1) {
422 // TODO Auto-generated catch block
423 e1.printStackTrace();
424 } catch (KeyManagementException e) {
425 // TODO Auto-generated catch block
426 e.printStackTrace();
427 }
428 }
429
430 private void sendSaslAuth() throws IOException, XmlPullParserException {
431 String saslString = CryptoHelper.saslPlain(account.getUsername(),
432 account.getPassword());
433 Element auth = new Element("auth");
434 auth.setAttribute("xmlns", "urn:ietf:params:xml:ns:xmpp-sasl");
435 auth.setAttribute("mechanism", "PLAIN");
436 auth.setContent(saslString);
437 tagWriter.writeElement(auth);
438 }
439
440 private void processStreamFeatures(Tag currentTag)
441 throws XmlPullParserException, IOException {
442 this.streamFeatures = tagReader.readElement(currentTag);
443 if (this.streamFeatures.hasChild("starttls")
444 && account.isOptionSet(Account.OPTION_USETLS)) {
445 sendStartTLS();
446 } else if (this.streamFeatures.hasChild("register")&&(account.isOptionSet(Account.OPTION_REGISTER))) {
447 sendRegistryRequest();
448 } else if (!this.streamFeatures.hasChild("register")&&(account.isOptionSet(Account.OPTION_REGISTER))) {
449 changeStatus(Account.STATUS_REGISTRATION_NOT_SUPPORTED);
450 disconnect(true);
451 } else if (this.streamFeatures.hasChild("mechanisms")
452 && shouldAuthenticate) {
453 sendSaslAuth();
454 } else if (this.streamFeatures.hasChild("sm") && streamId != null) {
455 Log.d(LOGTAG,"found old stream id. trying to remuse");
456 ResumePacket resume = new ResumePacket(this.streamId,stanzasReceived);
457 this.tagWriter.writeStanzaAsync(resume);
458 } else if (this.streamFeatures.hasChild("bind") && shouldBind) {
459 sendBindRequest();
460 if (this.streamFeatures.hasChild("session")) {
461 Log.d(LOGTAG,"sending session");
462 IqPacket startSession = new IqPacket(IqPacket.TYPE_SET);
463 Element session = new Element("session");
464 session.setAttribute("xmlns",
465 "urn:ietf:params:xml:ns:xmpp-session");
466 session.setContent("");
467 startSession.addChild(session);
468 this.sendIqPacket(startSession, null);
469 }
470 }
471 }
472
473 private void sendRegistryRequest() {
474 IqPacket register = new IqPacket(IqPacket.TYPE_GET);
475 register.query("jabber:iq:register");
476 register.setTo(account.getServer());
477 sendIqPacket(register, new OnIqPacketReceived() {
478
479 @Override
480 public void onIqPacketReceived(Account account, IqPacket packet) {
481 Element instructions = packet.query().findChild("instructions");
482 if (packet.query().hasChild("username")&&(packet.query().hasChild("password"))) {
483 IqPacket register = new IqPacket(IqPacket.TYPE_SET);
484 Element username = new Element("username").setContent(account.getUsername());
485 Element password = new Element("password").setContent(account.getPassword());
486 register.query("jabber:iq:register").addChild(username).addChild(password);
487 sendIqPacket(register, new OnIqPacketReceived() {
488
489 @Override
490 public void onIqPacketReceived(Account account, IqPacket packet) {
491 if (packet.getType()==IqPacket.TYPE_RESULT) {
492 account.setOption(Account.OPTION_REGISTER, false);
493 changeStatus(Account.STATUS_REGISTRATION_SUCCESSFULL);
494 } else if (packet.hasChild("error")&&(packet.findChild("error").hasChild("conflict"))){
495 changeStatus(Account.STATUS_REGISTRATION_CONFLICT);
496 } else {
497 changeStatus(Account.STATUS_REGISTRATION_FAILED);
498 Log.d(LOGTAG,packet.toString());
499 }
500 disconnect(true);
501 }
502 });
503 } else {
504 changeStatus(Account.STATUS_REGISTRATION_FAILED);
505 disconnect(true);
506 Log.d(LOGTAG,account.getJid()+": could not register. instructions are"+instructions.getContent());
507 }
508 }
509 });
510 }
511
512 private void sendInitialPresence() {
513 PresencePacket packet = new PresencePacket();
514 packet.setAttribute("from", account.getFullJid());
515 if (account.getKeys().has("pgp_signature")) {
516 try {
517 String signature = account.getKeys().getString("pgp_signature");
518 Element status = new Element("status");
519 status.setContent("online");
520 packet.addChild(status);
521 Element x = new Element("x");
522 x.setAttribute("xmlns", "jabber:x:signed");
523 x.setContent(signature);
524 packet.addChild(x);
525 } catch (JSONException e) {
526 //
527 }
528 }
529 this.sendPresencePacket(packet);
530 }
531
532 private void sendBindRequest() throws IOException {
533 IqPacket iq = new IqPacket(IqPacket.TYPE_SET);
534 Element bind = new Element("bind");
535 bind.setAttribute("xmlns", "urn:ietf:params:xml:ns:xmpp-bind");
536 Element resource = new Element("resource");
537 resource.setContent("Conversations");
538 bind.addChild(resource);
539 iq.addChild(bind);
540 this.sendIqPacket(iq, new OnIqPacketReceived() {
541 @Override
542 public void onIqPacketReceived(Account account, IqPacket packet) {
543 String resource = packet.findChild("bind").findChild("jid")
544 .getContent().split("/")[1];
545 account.setResource(resource);
546 if (bindListener !=null) {
547 bindListener.onBind(account);
548 }
549 account.setStatus(Account.STATUS_ONLINE);
550 if (streamFeatures.hasChild("sm")) {
551 EnablePacket enable = new EnablePacket();
552 tagWriter.writeStanzaAsync(enable);
553 }
554 sendInitialPresence();
555 sendServiceDiscoveryInfo();
556 sendServiceDiscoveryItems();
557 if (statusListener != null) {
558 statusListener.onStatusChanged(account);
559 }
560 }
561 });
562 }
563
564 private void sendServiceDiscoveryInfo() {
565 IqPacket iq = new IqPacket(IqPacket.TYPE_GET);
566 iq.setTo(account.getServer());
567 iq.query("http://jabber.org/protocol/disco#info");
568 this.sendIqPacket(iq, new OnIqPacketReceived() {
569
570 @Override
571 public void onIqPacketReceived(Account account, IqPacket packet) {
572 List<Element> elements = packet.query().getChildren();
573 for (int i = 0; i < elements.size(); ++i) {
574 if (elements.get(i).getName().equals("feature")) {
575 discoFeatures.add(elements.get(i).getAttribute(
576 "var"));
577 }
578 }
579 if (discoFeatures.contains("urn:xmpp:carbons:2")) {
580 sendEnableCarbons();
581 }
582 }
583 });
584 }
585 private void sendServiceDiscoveryItems() {
586 IqPacket iq = new IqPacket(IqPacket.TYPE_GET);
587 iq.setTo(account.getServer());
588 iq.query("http://jabber.org/protocol/disco#items");
589 this.sendIqPacket(iq, new OnIqPacketReceived() {
590
591 @Override
592 public void onIqPacketReceived(Account account, IqPacket packet) {
593 List<Element> elements = packet.query().getChildren();
594 for (int i = 0; i < elements.size(); ++i) {
595 if (elements.get(i).getName().equals("item")) {
596 discoItems.add(elements.get(i).getAttribute(
597 "jid"));
598 }
599 }
600 }
601 });
602 }
603
604 private void sendEnableCarbons() {
605 IqPacket iq = new IqPacket(IqPacket.TYPE_SET);
606 Element enable = new Element("enable");
607 enable.setAttribute("xmlns", "urn:xmpp:carbons:2");
608 iq.addChild(enable);
609 this.sendIqPacket(iq, new OnIqPacketReceived() {
610
611 @Override
612 public void onIqPacketReceived(Account account, IqPacket packet) {
613 if (!packet.hasChild("error")) {
614 Log.d(LOGTAG, account.getJid()
615 + ": successfully enabled carbons");
616 } else {
617 Log.d(LOGTAG, account.getJid()
618 + ": error enableing carbons " + packet.toString());
619 }
620 }
621 });
622 }
623
624 private void processStreamError(Tag currentTag) {
625 Log.d(LOGTAG, "processStreamError");
626 }
627
628 private void sendStartStream() throws IOException {
629 Tag stream = Tag.start("stream:stream");
630 stream.setAttribute("from", account.getJid());
631 stream.setAttribute("to", account.getServer());
632 stream.setAttribute("version", "1.0");
633 stream.setAttribute("xml:lang", "en");
634 stream.setAttribute("xmlns", "jabber:client");
635 stream.setAttribute("xmlns:stream", "http://etherx.jabber.org/streams");
636 tagWriter.writeTag(stream);
637 }
638
639 private String nextRandomId() {
640 return new BigInteger(50, random).toString(32);
641 }
642
643 public void sendIqPacket(IqPacket packet, OnIqPacketReceived callback) {
644 String id = nextRandomId();
645 packet.setAttribute("id", id);
646 this.sendPacket(packet, callback);
647 }
648
649 public void sendMessagePacket(MessagePacket packet) {
650 this.sendPacket(packet, null);
651 }
652
653 public void sendMessagePacket(MessagePacket packet,
654 OnMessagePacketReceived callback) {
655 this.sendPacket(packet, callback);
656 }
657
658 public void sendPresencePacket(PresencePacket packet) {
659 this.sendPacket(packet, null);
660 }
661
662 public void sendPresencePacket(PresencePacket packet,
663 OnPresencePacketReceived callback) {
664 this.sendPacket(packet, callback);
665 }
666
667 private synchronized void sendPacket(final AbstractStanza packet, PacketReceived callback) {
668 // TODO dont increment stanza count if packet = request packet or ack;
669 ++stanzasSent;
670 tagWriter.writeStanzaAsync(packet);
671 if (callback != null) {
672 if (packet.getId()==null) {
673 packet.setId(nextRandomId());
674 }
675 packetCallbacks.put(packet.getId(), callback);
676 }
677 }
678
679 public void sendPing() {
680 if (streamFeatures.hasChild("sm")) {
681 Log.d(LOGTAG,account.getJid()+": sending r as ping");
682 tagWriter.writeStanzaAsync(new RequestPacket());
683 } else {
684 Log.d(LOGTAG,account.getJid()+": sending iq as ping");
685 IqPacket iq = new IqPacket(IqPacket.TYPE_GET);
686 Element ping = new Element("ping");
687 iq.setAttribute("from",account.getFullJid());
688 ping.setAttribute("xmlns", "urn:xmpp:ping");
689 iq.addChild(ping);
690 this.sendIqPacket(iq, null);
691 }
692 }
693
694 public void setOnMessagePacketReceivedListener(
695 OnMessagePacketReceived listener) {
696 this.messageListener = listener;
697 }
698
699 public void setOnUnregisteredIqPacketReceivedListener(
700 OnIqPacketReceived listener) {
701 this.unregisteredIqListener = listener;
702 }
703
704 public void setOnPresencePacketReceivedListener(
705 OnPresencePacketReceived listener) {
706 this.presenceListener = listener;
707 }
708
709 public void setOnStatusChangedListener(OnStatusChanged listener) {
710 this.statusListener = listener;
711 }
712
713 public void setOnTLSExceptionReceivedListener(OnTLSExceptionReceived listener) {
714 this.tlsListener = listener;
715 }
716
717 public void setOnBindListener(OnBindListener listener) {
718 this.bindListener = listener;
719 }
720
721 public void disconnect(boolean force) {
722 changeStatus(Account.STATUS_OFFLINE);
723 Log.d(LOGTAG,"disconnecting");
724 try {
725 if (force) {
726 socket.close();
727 return;
728 }
729 tagWriter.finish();
730 while(!tagWriter.finished()) {
731 //Log.d(LOGTAG,"not yet finished");
732 Thread.sleep(100);
733 }
734 tagWriter.writeTag(Tag.end("stream:stream"));
735 } catch (IOException e) {
736 Log.d(LOGTAG,"io exception during disconnect");
737 } catch (InterruptedException e) {
738 Log.d(LOGTAG,"interupted while waiting for disconnect");
739 }
740 }
741
742 public boolean hasFeatureRosterManagment() {
743 if (this.streamFeatures==null) {
744 return false;
745 } else {
746 return this.streamFeatures.hasChild("ver");
747 }
748 }
749
750 public boolean hasFeatureStreamManagment() {
751 if (this.streamFeatures==null) {
752 return false;
753 } else {
754 return this.streamFeatures.hasChild("sm");
755 }
756 }
757
758 public boolean hasFeaturesCarbon() {
759 return discoFeatures.contains("urn:xmpp:carbons:2");
760 }
761
762 public void r() {
763 this.tagWriter.writeStanzaAsync(new RequestPacket());
764 }
765
766 public int getReceivedStanzas() {
767 return this.stanzasReceived;
768 }
769
770 public int getSentStanzas() {
771 return this.stanzasSent;
772 }
773
774 public String getMucServer() {
775 for(int i = 0; i < discoItems.size(); ++i) {
776 if (discoItems.get(i).contains("conference.")) {
777 return discoItems.get(i);
778 } else if (discoItems.get(i).contains("conf.")) {
779 return discoItems.get(i);
780 } else if (discoItems.get(i).contains("muc.")) {
781 return discoItems.get(i);
782 }
783 }
784 return null;
785 }
786}