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