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