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