XmppConnection.java

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