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