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