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