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