XmppConnection.java

   1package eu.siacs.conversations.xmpp;
   2
   3import android.graphics.Bitmap;
   4import android.graphics.BitmapFactory;
   5import android.os.Bundle;
   6import android.os.Parcelable;
   7import android.os.PowerManager;
   8import android.os.PowerManager.WakeLock;
   9import android.os.SystemClock;
  10import android.security.KeyChain;
  11import android.util.Base64;
  12import android.util.Log;
  13import android.util.Pair;
  14import android.util.SparseArray;
  15
  16import org.json.JSONException;
  17import org.json.JSONObject;
  18import org.xmlpull.v1.XmlPullParserException;
  19
  20import java.io.ByteArrayInputStream;
  21import java.io.IOException;
  22import java.io.InputStream;
  23import java.math.BigInteger;
  24import java.net.ConnectException;
  25import java.net.IDN;
  26import java.net.InetAddress;
  27import java.net.InetSocketAddress;
  28import java.net.Socket;
  29import java.net.UnknownHostException;
  30import java.net.URL;
  31import java.security.KeyManagementException;
  32import java.security.NoSuchAlgorithmException;
  33import java.security.Principal;
  34import java.security.PrivateKey;
  35import java.security.cert.X509Certificate;
  36import java.util.ArrayList;
  37import java.util.Arrays;
  38import java.util.HashMap;
  39import java.util.Hashtable;
  40import java.util.Iterator;
  41import java.util.List;
  42import java.util.Map.Entry;
  43import java.util.concurrent.atomic.AtomicBoolean;
  44import java.util.concurrent.atomic.AtomicInteger;
  45
  46import javax.net.ssl.HostnameVerifier;
  47import javax.net.ssl.KeyManager;
  48import javax.net.ssl.SSLContext;
  49import javax.net.ssl.SSLSocket;
  50import javax.net.ssl.SSLSocketFactory;
  51import javax.net.ssl.X509KeyManager;
  52import javax.net.ssl.X509TrustManager;
  53
  54import de.duenndns.ssl.MemorizingTrustManager;
  55import eu.siacs.conversations.Config;
  56import eu.siacs.conversations.crypto.XmppDomainVerifier;
  57import eu.siacs.conversations.crypto.sasl.DigestMd5;
  58import eu.siacs.conversations.crypto.sasl.External;
  59import eu.siacs.conversations.crypto.sasl.Plain;
  60import eu.siacs.conversations.crypto.sasl.SaslMechanism;
  61import eu.siacs.conversations.crypto.sasl.ScramSha1;
  62import eu.siacs.conversations.entities.Account;
  63import eu.siacs.conversations.entities.Message;
  64import eu.siacs.conversations.entities.ServiceDiscoveryResult;
  65import eu.siacs.conversations.generator.IqGenerator;
  66import eu.siacs.conversations.services.XmppConnectionService;
  67import eu.siacs.conversations.utils.DNSHelper;
  68import eu.siacs.conversations.utils.SSLSocketHelper;
  69import eu.siacs.conversations.utils.SocksSocketFactory;
  70import eu.siacs.conversations.utils.Xmlns;
  71import eu.siacs.conversations.xml.Element;
  72import eu.siacs.conversations.xml.Tag;
  73import eu.siacs.conversations.xml.TagWriter;
  74import eu.siacs.conversations.xml.XmlReader;
  75import eu.siacs.conversations.xmpp.forms.Data;
  76import eu.siacs.conversations.xmpp.forms.Field;
  77import eu.siacs.conversations.xmpp.jid.InvalidJidException;
  78import eu.siacs.conversations.xmpp.jid.Jid;
  79import eu.siacs.conversations.xmpp.jingle.OnJinglePacketReceived;
  80import eu.siacs.conversations.xmpp.jingle.stanzas.JinglePacket;
  81import eu.siacs.conversations.xmpp.stanzas.AbstractAcknowledgeableStanza;
  82import eu.siacs.conversations.xmpp.stanzas.AbstractStanza;
  83import eu.siacs.conversations.xmpp.stanzas.IqPacket;
  84import eu.siacs.conversations.xmpp.stanzas.MessagePacket;
  85import eu.siacs.conversations.xmpp.stanzas.PresencePacket;
  86import eu.siacs.conversations.xmpp.stanzas.csi.ActivePacket;
  87import eu.siacs.conversations.xmpp.stanzas.csi.InactivePacket;
  88import eu.siacs.conversations.xmpp.stanzas.streammgmt.AckPacket;
  89import eu.siacs.conversations.xmpp.stanzas.streammgmt.EnablePacket;
  90import eu.siacs.conversations.xmpp.stanzas.streammgmt.RequestPacket;
  91import eu.siacs.conversations.xmpp.stanzas.streammgmt.ResumePacket;
  92
  93public class XmppConnection implements Runnable {
  94
  95	private static final int PACKET_IQ = 0;
  96	private static final int PACKET_MESSAGE = 1;
  97	private static final int PACKET_PRESENCE = 2;
  98	protected Account account;
  99	private final WakeLock wakeLock;
 100	private Socket socket;
 101	private XmlReader tagReader;
 102	private TagWriter tagWriter;
 103	private final Features features = new Features(this);
 104	private boolean needsBinding = true;
 105	private boolean shouldAuthenticate = true;
 106	private Element streamFeatures;
 107	private final HashMap<Jid, ServiceDiscoveryResult> disco = new HashMap<>();
 108
 109	private String streamId = null;
 110	private int smVersion = 3;
 111	private final SparseArray<AbstractAcknowledgeableStanza> mStanzaQueue = new SparseArray<>();
 112
 113	private int stanzasReceived = 0;
 114	private int stanzasSent = 0;
 115	private long lastPacketReceived = 0;
 116	private long lastPingSent = 0;
 117	private long lastConnect = 0;
 118	private long lastSessionStarted = 0;
 119	private long lastDiscoStarted = 0;
 120	private AtomicInteger mPendingServiceDiscoveries = new AtomicInteger(0);
 121	private AtomicBoolean mWaitForDisco = new AtomicBoolean(true);
 122	private boolean mInteractive = false;
 123	private int attempt = 0;
 124	private final Hashtable<String, Pair<IqPacket, OnIqPacketReceived>> packetCallbacks = new Hashtable<>();
 125	private OnPresencePacketReceived presenceListener = null;
 126	private OnJinglePacketReceived jingleListener = null;
 127	private OnIqPacketReceived unregisteredIqListener = null;
 128	private OnMessagePacketReceived messageListener = null;
 129	private OnStatusChanged statusListener = null;
 130	private OnBindListener bindListener = null;
 131	private final ArrayList<OnAdvancedStreamFeaturesLoaded> advancedStreamFeaturesLoadedListeners = new ArrayList<>();
 132	private OnMessageAcknowledged acknowledgedListener = null;
 133	private XmppConnectionService mXmppConnectionService = null;
 134
 135	private SaslMechanism saslMechanism;
 136
 137	private X509KeyManager mKeyManager = new X509KeyManager() {
 138		@Override
 139		public String chooseClientAlias(String[] strings, Principal[] principals, Socket socket) {
 140			return account.getPrivateKeyAlias();
 141		}
 142
 143		@Override
 144		public String chooseServerAlias(String s, Principal[] principals, Socket socket) {
 145			return null;
 146		}
 147
 148		@Override
 149		public X509Certificate[] getCertificateChain(String alias) {
 150			try {
 151				return KeyChain.getCertificateChain(mXmppConnectionService, alias);
 152			} catch (Exception e) {
 153				return new X509Certificate[0];
 154			}
 155		}
 156
 157		@Override
 158		public String[] getClientAliases(String s, Principal[] principals) {
 159			return new String[0];
 160		}
 161
 162		@Override
 163		public String[] getServerAliases(String s, Principal[] principals) {
 164			return new String[0];
 165		}
 166
 167		@Override
 168		public PrivateKey getPrivateKey(String alias) {
 169			try {
 170				return KeyChain.getPrivateKey(mXmppConnectionService, alias);
 171			} catch (Exception e) {
 172				return null;
 173			}
 174		}
 175	};
 176	private Identity mServerIdentity = Identity.UNKNOWN;
 177
 178	public final OnIqPacketReceived registrationResponseListener =  new OnIqPacketReceived() {
 179		@Override
 180		public void onIqPacketReceived(Account account, IqPacket packet) {
 181			if (packet.getType() == IqPacket.TYPE.RESULT) {
 182				account.setOption(Account.OPTION_REGISTER, false);
 183				forceCloseSocket();
 184				changeStatus(Account.State.REGISTRATION_SUCCESSFUL);
 185			} else {
 186				final List<String> PASSWORD_TOO_WEAK_MSGS = Arrays.asList(
 187						"The password is too weak",
 188						"Please use a longer password.");
 189				Element error = packet.findChild("error");
 190				Account.State state = Account.State.REGISTRATION_FAILED;
 191				if (error != null) {
 192					if (error.hasChild("conflict")) {
 193						state = Account.State.REGISTRATION_CONFLICT;
 194					} else if (error.hasChild("resource-constraint")
 195							&& "wait".equals(error.getAttribute("type"))) {
 196						state = Account.State.REGISTRATION_PLEASE_WAIT;
 197					} else if (error.hasChild("not-acceptable")
 198							&& PASSWORD_TOO_WEAK_MSGS.contains(error.findChildContent("text"))) {
 199						state = Account.State.REGISTRATION_PASSWORD_TOO_WEAK;
 200					}
 201				}
 202				changeStatus(state);
 203				forceCloseSocket();
 204			}
 205		}
 206	};
 207
 208	public XmppConnection(final Account account, final XmppConnectionService service) {
 209		this.account = account;
 210		this.wakeLock = service.getPowerManager().newWakeLock(
 211				PowerManager.PARTIAL_WAKE_LOCK, account.getJid().toBareJid().toString());
 212		tagWriter = new TagWriter();
 213		mXmppConnectionService = service;
 214	}
 215
 216	protected void changeStatus(final Account.State nextStatus) {
 217		if (account.getStatus() != nextStatus) {
 218			if ((nextStatus == Account.State.OFFLINE)
 219					&& (account.getStatus() != Account.State.CONNECTING)
 220					&& (account.getStatus() != Account.State.ONLINE)
 221					&& (account.getStatus() != Account.State.DISABLED)) {
 222				return;
 223					}
 224			if (nextStatus == Account.State.ONLINE) {
 225				this.attempt = 0;
 226			}
 227			account.setStatus(nextStatus);
 228			if (statusListener != null) {
 229				statusListener.onStatusChanged(account);
 230			}
 231		}
 232	}
 233
 234	public void prepareNewConnection() {
 235		this.lastConnect = SystemClock.elapsedRealtime();
 236		this.lastPingSent = SystemClock.elapsedRealtime();
 237		this.lastDiscoStarted = Long.MAX_VALUE;
 238		this.changeStatus(Account.State.CONNECTING);
 239	}
 240
 241	protected void connect() {
 242		Log.d(Config.LOGTAG, account.getJid().toBareJid().toString() + ": connecting");
 243		features.encryptionEnabled = false;
 244		this.attempt++;
 245		switch (account.getJid().getDomainpart()) {
 246			case "chat.facebook.com":
 247				mServerIdentity = Identity.FACEBOOK;
 248				break;
 249			case "nimbuzz.com":
 250				mServerIdentity = Identity.NIMBUZZ;
 251				break;
 252			default:
 253				mServerIdentity = Identity.UNKNOWN;
 254				break;
 255		}
 256		try {
 257			shouldAuthenticate = needsBinding = !account.isOptionSet(Account.OPTION_REGISTER);
 258			tagReader = new XmlReader(wakeLock);
 259			tagWriter = new TagWriter();
 260			this.changeStatus(Account.State.CONNECTING);
 261			final boolean useTor = mXmppConnectionService.useTorToConnect() || account.isOnion();
 262			final boolean extended = mXmppConnectionService.showExtendedConnectionOptions();
 263			if (useTor) {
 264				String destination;
 265				if (account.getHostname() == null || account.getHostname().isEmpty()) {
 266					destination = account.getServer().toString();
 267				} else {
 268					destination = account.getHostname();
 269				}
 270				Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": connect to " + destination + " via TOR");
 271				socket = SocksSocketFactory.createSocketOverTor(destination, account.getPort());
 272				startXmpp();
 273			} else if (extended && account.getHostname() != null && !account.getHostname().isEmpty()) {
 274				socket = new Socket();
 275				try {
 276					socket.connect(new InetSocketAddress(account.getHostname(), account.getPort()), Config.SOCKET_TIMEOUT * 1000);
 277				} catch (IOException e) {
 278					throw new UnknownHostException();
 279				}
 280				startXmpp();
 281			} else if (DNSHelper.isIp(account.getServer().toString())) {
 282				socket = new Socket();
 283				try {
 284					socket.connect(new InetSocketAddress(account.getServer().toString(), 5222), Config.SOCKET_TIMEOUT * 1000);
 285				} catch (IOException e) {
 286					throw new UnknownHostException();
 287				}
 288				startXmpp();
 289			} else {
 290				final Bundle result = DNSHelper.getSRVRecord(account.getServer(), mXmppConnectionService);
 291				final ArrayList<Parcelable>values = result.getParcelableArrayList("values");
 292				for(Iterator<Parcelable> iterator = values.iterator(); iterator.hasNext();) {
 293					if (Thread.currentThread().isInterrupted()) {
 294						Log.d(Config.LOGTAG,account.getJid().toBareJid()+": Thread was interrupted");
 295						return;
 296					}
 297					final Bundle namePort = (Bundle) iterator.next();
 298					try {
 299						String srvRecordServer;
 300						try {
 301							srvRecordServer = IDN.toASCII(namePort.getString("name"));
 302						} catch (final IllegalArgumentException e) {
 303							// TODO: Handle me?`
 304							srvRecordServer = "";
 305						}
 306						final int srvRecordPort = namePort.getInt("port");
 307						final String srvIpServer = namePort.getString("ip");
 308						// if tls is true, encryption is implied and must not be started
 309						features.encryptionEnabled = namePort.getBoolean("tls");
 310						final InetSocketAddress addr;
 311						if (srvIpServer != null) {
 312							addr = new InetSocketAddress(srvIpServer, srvRecordPort);
 313							Log.d(Config.LOGTAG, account.getJid().toBareJid().toString()
 314									+ ": using values from dns " + srvRecordServer
 315									+ "[" + srvIpServer + "]:" + srvRecordPort + " tls: " + features.encryptionEnabled);
 316						} else {
 317							addr = new InetSocketAddress(srvRecordServer, srvRecordPort);
 318							Log.d(Config.LOGTAG, account.getJid().toBareJid().toString()
 319									+ ": using values from dns "
 320									+ srvRecordServer + ":" + srvRecordPort + " tls: " + features.encryptionEnabled);
 321						}
 322
 323						if (!features.encryptionEnabled) {
 324							socket = new Socket();
 325							socket.connect(addr, Config.SOCKET_TIMEOUT * 1000);
 326						} else {
 327							final TlsFactoryVerifier tlsFactoryVerifier = getTlsFactoryVerifier();
 328							socket = tlsFactoryVerifier.factory.createSocket();
 329
 330							if (socket == null) {
 331								throw new IOException("could not initialize ssl socket");
 332							}
 333
 334							SSLSocketHelper.setSecurity((SSLSocket) socket);
 335							SSLSocketHelper.setSNIHost(tlsFactoryVerifier.factory, (SSLSocket) socket, account.getServer().getDomainpart());
 336							SSLSocketHelper.setAlpnProtocol(tlsFactoryVerifier.factory, (SSLSocket) socket, "xmpp-client");
 337
 338							socket.connect(addr, Config.SOCKET_TIMEOUT * 1000);
 339
 340							if (!tlsFactoryVerifier.verifier.verify(account.getServer().getDomainpart(), ((SSLSocket) socket).getSession())) {
 341								Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": TLS certificate verification failed");
 342								throw new SecurityException();
 343							}
 344						}
 345
 346						if (startXmpp())
 347							break; // successfully connected to server that speaks xmpp
 348					} catch(final SecurityException e) {
 349						throw e;
 350					} catch (final Throwable e) {
 351						Log.d(Config.LOGTAG, account.getJid().toBareJid().toString() + ": " + e.getMessage() +"("+e.getClass().getName()+")");
 352						if (!iterator.hasNext()) {
 353							throw new UnknownHostException();
 354						}
 355					}
 356				}
 357			}
 358			processStream();
 359		} catch (final IncompatibleServerException e) {
 360			this.changeStatus(Account.State.INCOMPATIBLE_SERVER);
 361		} catch (final SecurityException e) {
 362			this.changeStatus(Account.State.SECURITY_ERROR);
 363		} catch (final UnauthorizedException e) {
 364			this.changeStatus(Account.State.UNAUTHORIZED);
 365		} catch (final UnknownHostException | ConnectException e) {
 366			this.changeStatus(Account.State.SERVER_NOT_FOUND);
 367		} catch (final SocksSocketFactory.SocksProxyNotFoundException e) {
 368			this.changeStatus(Account.State.TOR_NOT_AVAILABLE);
 369		} catch(final StreamErrorHostUnknown e) {
 370			this.changeStatus(Account.State.HOST_UNKNOWN);
 371		} catch(final StreamErrorPolicyViolation e) {
 372			this.changeStatus(Account.State.POLICY_VIOLATION);
 373		} catch(final StreamError e) {
 374			this.changeStatus(Account.State.STREAM_ERROR);
 375		} catch (final IOException | XmlPullParserException | NoSuchAlgorithmException e) {
 376			Log.d(Config.LOGTAG, account.getJid().toBareJid().toString() + ": " + e.getMessage());
 377			this.changeStatus(Account.State.OFFLINE);
 378			this.attempt = Math.max(0, this.attempt - 1);
 379		} finally {
 380			forceCloseSocket();
 381			if (wakeLock.isHeld()) {
 382				try {
 383					wakeLock.release();
 384				} catch (final RuntimeException ignored) {
 385				}
 386			}
 387		}
 388	}
 389
 390	/**
 391	 * Starts xmpp protocol, call after connecting to socket
 392	 * @return true if server returns with valid xmpp, false otherwise
 393	 * @throws IOException Unknown tag on connect
 394	 * @throws XmlPullParserException Bad Xml
 395	 * @throws NoSuchAlgorithmException Other error
 396     */
 397	private boolean startXmpp() throws IOException, XmlPullParserException, NoSuchAlgorithmException {
 398		tagWriter.setOutputStream(socket.getOutputStream());
 399		tagReader.setInputStream(socket.getInputStream());
 400		tagWriter.beginDocument();
 401		sendStartStream();
 402		Tag nextTag;
 403		while ((nextTag = tagReader.readTag()) != null) {
 404			if (nextTag.isStart("stream")) {
 405				return true;
 406			} else {
 407				throw new IOException("unknown tag on connect");
 408			}
 409		}
 410		if (socket.isConnected()) {
 411			socket.close();
 412		}
 413		return false;
 414	}
 415
 416	private static class TlsFactoryVerifier {
 417		private final SSLSocketFactory factory;
 418		private final HostnameVerifier verifier;
 419
 420		public TlsFactoryVerifier(final SSLSocketFactory factory, final HostnameVerifier verifier) throws IOException {
 421			this.factory = factory;
 422			this.verifier = verifier;
 423			if (factory == null || verifier == null) {
 424				throw new IOException("could not setup ssl");
 425			}
 426		}
 427	}
 428
 429	private TlsFactoryVerifier getTlsFactoryVerifier() throws NoSuchAlgorithmException, KeyManagementException, IOException {
 430		final SSLContext sc = SSLSocketHelper.getSSLContext();
 431		MemorizingTrustManager trustManager = this.mXmppConnectionService.getMemorizingTrustManager();
 432		KeyManager[] keyManager;
 433		if (account.getPrivateKeyAlias() != null && account.getPassword().isEmpty()) {
 434			keyManager = new KeyManager[]{mKeyManager};
 435		} else {
 436			keyManager = null;
 437		}
 438		sc.init(keyManager, new X509TrustManager[]{mInteractive ? trustManager : trustManager.getNonInteractive()}, mXmppConnectionService.getRNG());
 439		final SSLSocketFactory factory = sc.getSocketFactory();
 440		final HostnameVerifier verifier;
 441		if (mInteractive) {
 442			verifier = trustManager.wrapHostnameVerifier(new XmppDomainVerifier());
 443		} else {
 444			verifier = trustManager.wrapHostnameVerifierNonInteractive(new XmppDomainVerifier());
 445		}
 446
 447		return new TlsFactoryVerifier(factory, verifier);
 448	}
 449
 450	@Override
 451	public void run() {
 452		forceCloseSocket();
 453		connect();
 454	}
 455
 456	private void processStream() throws XmlPullParserException, IOException, NoSuchAlgorithmException {
 457		Tag nextTag = tagReader.readTag();
 458		while (nextTag != null && !nextTag.isEnd("stream")) {
 459			if (nextTag.isStart("error")) {
 460				processStreamError(nextTag);
 461			} else if (nextTag.isStart("features")) {
 462				processStreamFeatures(nextTag);
 463			} else if (nextTag.isStart("proceed")) {
 464				switchOverToTls(nextTag);
 465			} else if (nextTag.isStart("success")) {
 466				final String challenge = tagReader.readElement(nextTag).getContent();
 467				try {
 468					saslMechanism.getResponse(challenge);
 469				} catch (final SaslMechanism.AuthenticationException e) {
 470					disconnect(true);
 471					Log.e(Config.LOGTAG, String.valueOf(e));
 472				}
 473				Log.d(Config.LOGTAG, account.getJid().toBareJid().toString() + ": logged in");
 474				account.setKey(Account.PINNED_MECHANISM_KEY,
 475						String.valueOf(saslMechanism.getPriority()));
 476				tagReader.reset();
 477				sendStartStream();
 478				final Tag tag = tagReader.readTag();
 479				if (tag != null && tag.isStart("stream")) {
 480					processStream();
 481				} else {
 482					throw new IOException("server didn't restart stream after successful auth");
 483				}
 484				break;
 485			} else if (nextTag.isStart("failure")) {
 486				throw new UnauthorizedException();
 487			} else if (nextTag.isStart("challenge")) {
 488				final String challenge = tagReader.readElement(nextTag).getContent();
 489				final Element response = new Element("response");
 490				response.setAttribute("xmlns",
 491						"urn:ietf:params:xml:ns:xmpp-sasl");
 492				try {
 493					response.setContent(saslMechanism.getResponse(challenge));
 494				} catch (final SaslMechanism.AuthenticationException e) {
 495					// TODO: Send auth abort tag.
 496					Log.e(Config.LOGTAG, e.toString());
 497				}
 498				tagWriter.writeElement(response);
 499			} else if (nextTag.isStart("enabled")) {
 500				final Element enabled = tagReader.readElement(nextTag);
 501				if ("true".equals(enabled.getAttribute("resume"))) {
 502					this.streamId = enabled.getAttribute("id");
 503					Log.d(Config.LOGTAG, account.getJid().toBareJid().toString()
 504							+ ": stream management(" + smVersion
 505							+ ") enabled (resumable)");
 506				} else {
 507					Log.d(Config.LOGTAG, account.getJid().toBareJid().toString()
 508							+ ": stream management(" + smVersion + ") enabled");
 509				}
 510				this.stanzasReceived = 0;
 511				final RequestPacket r = new RequestPacket(smVersion);
 512				tagWriter.writeStanzaAsync(r);
 513			} else if (nextTag.isStart("resumed")) {
 514				lastPacketReceived = SystemClock.elapsedRealtime();
 515				final Element resumed = tagReader.readElement(nextTag);
 516				final String h = resumed.getAttribute("h");
 517				try {
 518					ArrayList<AbstractAcknowledgeableStanza> failedStanzas = new ArrayList<>();
 519					synchronized (this.mStanzaQueue) {
 520						final int serverCount = Integer.parseInt(h);
 521						if (serverCount != stanzasSent) {
 522							Log.d(Config.LOGTAG, account.getJid().toBareJid().toString()
 523									+ ": session resumed with lost packages");
 524							stanzasSent = serverCount;
 525						} else {
 526							Log.d(Config.LOGTAG, account.getJid().toBareJid().toString() + ": session resumed");
 527						}
 528						acknowledgeStanzaUpTo(serverCount);
 529						for (int i = 0; i < this.mStanzaQueue.size(); ++i) {
 530							failedStanzas.add(mStanzaQueue.valueAt(i));
 531						}
 532						mStanzaQueue.clear();
 533					}
 534					Log.d(Config.LOGTAG, "resending " + failedStanzas.size() + " stanzas");
 535					for (AbstractAcknowledgeableStanza packet : failedStanzas) {
 536						if (packet instanceof MessagePacket) {
 537							MessagePacket message = (MessagePacket) packet;
 538							mXmppConnectionService.markMessage(account,
 539									message.getTo().toBareJid(),
 540									message.getId(),
 541									Message.STATUS_UNSEND);
 542						}
 543						sendPacket(packet);
 544					}
 545				} catch (final NumberFormatException ignored) {
 546				}
 547				Log.d(Config.LOGTAG, account.getJid().toBareJid()+ ": online with resource " + account.getResource());
 548				changeStatus(Account.State.ONLINE);
 549			} else if (nextTag.isStart("r")) {
 550				tagReader.readElement(nextTag);
 551				if (Config.EXTENDED_SM_LOGGING) {
 552					Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": acknowledging stanza #" + this.stanzasReceived);
 553				}
 554				final AckPacket ack = new AckPacket(this.stanzasReceived, smVersion);
 555				tagWriter.writeStanzaAsync(ack);
 556			} else if (nextTag.isStart("a")) {
 557				final Element ack = tagReader.readElement(nextTag);
 558				lastPacketReceived = SystemClock.elapsedRealtime();
 559				try {
 560					synchronized (this.mStanzaQueue) {
 561						final int serverSequence = Integer.parseInt(ack.getAttribute("h"));
 562						acknowledgeStanzaUpTo(serverSequence);
 563					}
 564				} catch (NumberFormatException | NullPointerException e) {
 565					Log.d(Config.LOGTAG,account.getJid().toBareJid()+": server send ack without sequence number");
 566				}
 567			} else if (nextTag.isStart("failed")) {
 568				Element failed = tagReader.readElement(nextTag);
 569				try {
 570					final int serverCount = Integer.parseInt(failed.getAttribute("h"));
 571					Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": resumption failed but server acknowledged stanza #"+serverCount);
 572					synchronized (this.mStanzaQueue) {
 573						acknowledgeStanzaUpTo(serverCount);
 574					}
 575				} catch (NumberFormatException | NullPointerException e) {
 576					Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": resumption failed");
 577				}
 578				resetStreamId();
 579				if (account.getStatus() != Account.State.ONLINE) {
 580					sendBindRequest();
 581				}
 582			} else if (nextTag.isStart("iq")) {
 583				processIq(nextTag);
 584			} else if (nextTag.isStart("message")) {
 585				processMessage(nextTag);
 586			} else if (nextTag.isStart("presence")) {
 587				processPresence(nextTag);
 588			}
 589			nextTag = tagReader.readTag();
 590		}
 591	}
 592
 593	private void acknowledgeStanzaUpTo(int serverCount) {
 594		for (int i = 0; i < mStanzaQueue.size(); ++i) {
 595			if (serverCount >= mStanzaQueue.keyAt(i)) {
 596				if (Config.EXTENDED_SM_LOGGING) {
 597					Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": server acknowledged stanza #" + mStanzaQueue.keyAt(i));
 598				}
 599				AbstractAcknowledgeableStanza stanza = mStanzaQueue.valueAt(i);
 600				if (stanza instanceof MessagePacket && acknowledgedListener != null) {
 601					MessagePacket packet = (MessagePacket) stanza;
 602					acknowledgedListener.onMessageAcknowledged(account, packet.getId());
 603				}
 604				mStanzaQueue.removeAt(i);
 605				i--;
 606			}
 607		}
 608	}
 609
 610	private Element processPacket(final Tag currentTag, final int packetType)
 611		throws XmlPullParserException, IOException {
 612		Element element;
 613		switch (packetType) {
 614			case PACKET_IQ:
 615				element = new IqPacket();
 616				break;
 617			case PACKET_MESSAGE:
 618				element = new MessagePacket();
 619				break;
 620			case PACKET_PRESENCE:
 621				element = new PresencePacket();
 622				break;
 623			default:
 624				return null;
 625		}
 626		element.setAttributes(currentTag.getAttributes());
 627		Tag nextTag = tagReader.readTag();
 628		if (nextTag == null) {
 629			throw new IOException("interrupted mid tag");
 630		}
 631		while (!nextTag.isEnd(element.getName())) {
 632			if (!nextTag.isNo()) {
 633				final Element child = tagReader.readElement(nextTag);
 634				final String type = currentTag.getAttribute("type");
 635				if (packetType == PACKET_IQ
 636						&& "jingle".equals(child.getName())
 637						&& ("set".equalsIgnoreCase(type) || "get"
 638							.equalsIgnoreCase(type))) {
 639					element = new JinglePacket();
 640					element.setAttributes(currentTag.getAttributes());
 641							}
 642				element.addChild(child);
 643			}
 644			nextTag = tagReader.readTag();
 645			if (nextTag == null) {
 646				throw new IOException("interrupted mid tag");
 647			}
 648		}
 649		if (stanzasReceived == Integer.MAX_VALUE) {
 650			resetStreamId();
 651			throw new IOException("time to restart the session. cant handle >2 billion pcks");
 652		}
 653		++stanzasReceived;
 654		lastPacketReceived = SystemClock.elapsedRealtime();
 655		if (Config.BACKGROUND_STANZA_LOGGING && mXmppConnectionService.checkListeners()) {
 656			Log.d(Config.LOGTAG,"[background stanza] "+element);
 657		}
 658		return element;
 659	}
 660
 661	private void processIq(final Tag currentTag) throws XmlPullParserException, IOException {
 662		final IqPacket packet = (IqPacket) processPacket(currentTag, PACKET_IQ);
 663
 664		if (packet.getId() == null) {
 665			return; // an iq packet without id is definitely invalid
 666		}
 667
 668		if (packet instanceof JinglePacket) {
 669			if (this.jingleListener != null) {
 670				this.jingleListener.onJinglePacketReceived(account,(JinglePacket) packet);
 671			}
 672		} else {
 673			OnIqPacketReceived callback = null;
 674			synchronized (this.packetCallbacks) {
 675				if (packetCallbacks.containsKey(packet.getId())) {
 676					final Pair<IqPacket, OnIqPacketReceived> packetCallbackDuple = packetCallbacks.get(packet.getId());
 677					// Packets to the server should have responses from the server
 678					if (packetCallbackDuple.first.toServer(account)) {
 679						if (packet.fromServer(account) || mServerIdentity == Identity.FACEBOOK) {
 680							callback = packetCallbackDuple.second;
 681							packetCallbacks.remove(packet.getId());
 682						} else {
 683							Log.e(Config.LOGTAG, account.getJid().toBareJid().toString() + ": ignoring spoofed iq packet");
 684						}
 685					} else {
 686						if (packet.getFrom().equals(packetCallbackDuple.first.getTo())) {
 687							callback = packetCallbackDuple.second;
 688							packetCallbacks.remove(packet.getId());
 689						} else {
 690							Log.e(Config.LOGTAG, account.getJid().toBareJid().toString() + ": ignoring spoofed iq packet");
 691						}
 692					}
 693				} else if (packet.getType() == IqPacket.TYPE.GET || packet.getType() == IqPacket.TYPE.SET) {
 694					callback = this.unregisteredIqListener;
 695				}
 696			}
 697			if (callback != null) {
 698				callback.onIqPacketReceived(account,packet);
 699			}
 700		}
 701	}
 702
 703	private void processMessage(final Tag currentTag) throws XmlPullParserException, IOException {
 704		final MessagePacket packet = (MessagePacket) processPacket(currentTag,PACKET_MESSAGE);
 705		this.messageListener.onMessagePacketReceived(account, packet);
 706	}
 707
 708	private void processPresence(final Tag currentTag) throws XmlPullParserException, IOException {
 709		PresencePacket packet = (PresencePacket) processPacket(currentTag, PACKET_PRESENCE);
 710		this.presenceListener.onPresencePacketReceived(account, packet);
 711	}
 712
 713	private void sendStartTLS() throws IOException {
 714		final Tag startTLS = Tag.empty("starttls");
 715		startTLS.setAttribute("xmlns", "urn:ietf:params:xml:ns:xmpp-tls");
 716		tagWriter.writeTag(startTLS);
 717	}
 718
 719
 720
 721	private void switchOverToTls(final Tag currentTag) throws XmlPullParserException, IOException {
 722		tagReader.readTag();
 723		try {
 724			final TlsFactoryVerifier tlsFactoryVerifier = getTlsFactoryVerifier();
 725			final InetAddress address = socket == null ? null : socket.getInetAddress();
 726
 727			if (address == null) {
 728				throw new IOException("could not setup ssl");
 729			}
 730
 731			final SSLSocket sslSocket = (SSLSocket) tlsFactoryVerifier.factory.createSocket(socket, address.getHostAddress(), socket.getPort(), true);
 732
 733			if (sslSocket == null) {
 734				throw new IOException("could not initialize ssl socket");
 735			}
 736
 737			SSLSocketHelper.setSecurity(sslSocket);
 738
 739			if (!tlsFactoryVerifier.verifier.verify(account.getServer().getDomainpart(), sslSocket.getSession())) {
 740				Log.d(Config.LOGTAG,account.getJid().toBareJid()+": TLS certificate verification failed");
 741				throw new SecurityException();
 742			}
 743			tagReader.setInputStream(sslSocket.getInputStream());
 744			tagWriter.setOutputStream(sslSocket.getOutputStream());
 745			sendStartStream();
 746			Log.d(Config.LOGTAG, account.getJid().toBareJid()+ ": TLS connection established");
 747			features.encryptionEnabled = true;
 748			final Tag tag = tagReader.readTag();
 749			if (tag != null && tag.isStart("stream")) {
 750				processStream();
 751			} else {
 752				throw new IOException("server didn't restart stream after STARTTLS");
 753			}
 754			sslSocket.close();
 755		} catch (final NoSuchAlgorithmException | KeyManagementException e1) {
 756			Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": TLS certificate verification failed");
 757			throw new SecurityException();
 758		}
 759	}
 760
 761	private void processStreamFeatures(final Tag currentTag)
 762		throws XmlPullParserException, IOException {
 763		this.streamFeatures = tagReader.readElement(currentTag);
 764		if (this.streamFeatures.hasChild("starttls") && !features.encryptionEnabled) {
 765			sendStartTLS();
 766		} else if (this.streamFeatures.hasChild("register") && account.isOptionSet(Account.OPTION_REGISTER)) {
 767			if (features.encryptionEnabled || Config.ALLOW_NON_TLS_CONNECTIONS) {
 768				sendRegistryRequest();
 769			} else {
 770				throw new IncompatibleServerException();
 771			}
 772		} else if (!this.streamFeatures.hasChild("register")
 773				&& account.isOptionSet(Account.OPTION_REGISTER)) {
 774			forceCloseSocket();
 775			changeStatus(Account.State.REGISTRATION_NOT_SUPPORTED);
 776		} else if (this.streamFeatures.hasChild("mechanisms")
 777				&& shouldAuthenticate
 778				&& (features.encryptionEnabled || Config.ALLOW_NON_TLS_CONNECTIONS)) {
 779			authenticate();
 780		} else if (this.streamFeatures.hasChild("sm", "urn:xmpp:sm:" + smVersion) && streamId != null) {
 781			if (Config.EXTENDED_SM_LOGGING) {
 782				Log.d(Config.LOGTAG,account.getJid().toBareJid()+": resuming after stanza #"+stanzasReceived);
 783			}
 784			final ResumePacket resume = new ResumePacket(this.streamId, stanzasReceived, smVersion);
 785			this.tagWriter.writeStanzaAsync(resume);
 786		} else if (needsBinding) {
 787			if (this.streamFeatures.hasChild("bind")) {
 788				sendBindRequest();
 789			} else {
 790				throw new IncompatibleServerException();
 791			}
 792		}
 793	}
 794
 795	private void authenticate() throws IOException {
 796		final List<String> mechanisms = extractMechanisms(streamFeatures
 797				.findChild("mechanisms"));
 798		final Element auth = new Element("auth");
 799		auth.setAttribute("xmlns", "urn:ietf:params:xml:ns:xmpp-sasl");
 800		if (mechanisms.contains("EXTERNAL") && account.getPrivateKeyAlias() != null) {
 801			saslMechanism = new External(tagWriter, account, mXmppConnectionService.getRNG());
 802		} else if (mechanisms.contains("SCRAM-SHA-1")) {
 803			saslMechanism = new ScramSha1(tagWriter, account, mXmppConnectionService.getRNG());
 804		} else if (mechanisms.contains("PLAIN")) {
 805			saslMechanism = new Plain(tagWriter, account);
 806		} else if (mechanisms.contains("DIGEST-MD5")) {
 807			saslMechanism = new DigestMd5(tagWriter, account, mXmppConnectionService.getRNG());
 808		}
 809		if (saslMechanism != null) {
 810			final JSONObject keys = account.getKeys();
 811			try {
 812				if (keys.has(Account.PINNED_MECHANISM_KEY) &&
 813						keys.getInt(Account.PINNED_MECHANISM_KEY) > saslMechanism.getPriority()) {
 814					Log.e(Config.LOGTAG, "Auth failed. Authentication mechanism " + saslMechanism.getMechanism() +
 815							" has lower priority (" + String.valueOf(saslMechanism.getPriority()) +
 816							") than pinned priority (" + keys.getInt(Account.PINNED_MECHANISM_KEY) +
 817							"). Possible downgrade attack?");
 818					throw new SecurityException();
 819				}
 820			} catch (final JSONException e) {
 821				Log.d(Config.LOGTAG, "Parse error while checking pinned auth mechanism");
 822			}
 823			Log.d(Config.LOGTAG, account.getJid().toString() + ": Authenticating with " + saslMechanism.getMechanism());
 824			auth.setAttribute("mechanism", saslMechanism.getMechanism());
 825			if (!saslMechanism.getClientFirstMessage().isEmpty()) {
 826				auth.setContent(saslMechanism.getClientFirstMessage());
 827			}
 828			tagWriter.writeElement(auth);
 829		} else {
 830			throw new IncompatibleServerException();
 831		}
 832	}
 833
 834	private List<String> extractMechanisms(final Element stream) {
 835		final ArrayList<String> mechanisms = new ArrayList<>(stream
 836				.getChildren().size());
 837		for (final Element child : stream.getChildren()) {
 838			mechanisms.add(child.getContent());
 839		}
 840		return mechanisms;
 841	}
 842
 843	private void sendRegistryRequest() {
 844		final IqPacket register = new IqPacket(IqPacket.TYPE.GET);
 845		register.query("jabber:iq:register");
 846		register.setTo(account.getServer());
 847		sendIqPacket(register, new OnIqPacketReceived() {
 848
 849			@Override
 850			public void onIqPacketReceived(final Account account, final IqPacket packet) {
 851				boolean failed = false;
 852				if (packet.getType() == IqPacket.TYPE.RESULT
 853						&& packet.query().hasChild("username")
 854						&& (packet.query().hasChild("password"))) {
 855					final IqPacket register = new IqPacket(IqPacket.TYPE.SET);
 856					final Element username = new Element("username").setContent(account.getUsername());
 857					final Element password = new Element("password").setContent(account.getPassword());
 858					register.query("jabber:iq:register").addChild(username);
 859					register.query().addChild(password);
 860					sendIqPacket(register, registrationResponseListener);
 861				} else if (packet.getType() == IqPacket.TYPE.RESULT
 862						&& (packet.query().hasChild("x", "jabber:x:data"))) {
 863					final Data data = Data.parse(packet.query().findChild("x", "jabber:x:data"));
 864					final Element blob = packet.query().findChild("data", "urn:xmpp:bob");
 865					final String id = packet.getId();
 866
 867					Bitmap captcha = null;
 868					if (blob != null) {
 869						try {
 870							final String base64Blob = blob.getContent();
 871							final byte[] strBlob = Base64.decode(base64Blob, Base64.DEFAULT);
 872							InputStream stream = new ByteArrayInputStream(strBlob);
 873							captcha = BitmapFactory.decodeStream(stream);
 874						} catch (Exception e) {
 875							//ignored
 876						}
 877					} else {
 878						try {
 879							Field url = data.getFieldByName("url");
 880							String urlString = url.findChildContent("value");
 881							URL uri = new URL(urlString);
 882							captcha = BitmapFactory.decodeStream(uri.openConnection().getInputStream());
 883						} catch (IOException e) {
 884							Log.e(Config.LOGTAG, e.toString());
 885						}
 886					}
 887
 888					if (captcha != null) {
 889						failed = !mXmppConnectionService.displayCaptchaRequest(account, id, data, captcha);
 890					}
 891				} else {
 892					failed = true;
 893				}
 894
 895				if (failed) {
 896					final Element instructions = packet.query().findChild("instructions");
 897					setAccountCreationFailed((instructions != null) ? instructions.getContent() : "");
 898				}
 899			}
 900		});
 901	}
 902
 903	private void setAccountCreationFailed(String instructions) {
 904		changeStatus(Account.State.REGISTRATION_FAILED);
 905		disconnect(true);
 906		Log.d(Config.LOGTAG, account.getJid().toBareJid()
 907				+ ": could not register. instructions are"
 908				+ instructions);
 909	}
 910
 911	public void resetEverything() {
 912		resetAttemptCount();
 913		resetStreamId();
 914		clearIqCallbacks();
 915		mStanzaQueue.clear();
 916		synchronized (this.disco) {
 917			disco.clear();
 918		}
 919	}
 920
 921	private void sendBindRequest() {
 922		while(!mXmppConnectionService.areMessagesInitialized() && socket != null && !socket.isClosed()) {
 923			try {
 924				Thread.sleep(500);
 925			} catch (final InterruptedException ignored) {
 926			}
 927		}
 928		needsBinding = false;
 929		clearIqCallbacks();
 930		final IqPacket iq = new IqPacket(IqPacket.TYPE.SET);
 931		iq.addChild("bind", "urn:ietf:params:xml:ns:xmpp-bind")
 932				.addChild("resource").setContent(account.getResource());
 933		this.sendUnmodifiedIqPacket(iq, new OnIqPacketReceived() {
 934			@Override
 935			public void onIqPacketReceived(final Account account, final IqPacket packet) {
 936				if (packet.getType() == IqPacket.TYPE.TIMEOUT) {
 937					return;
 938				}
 939				final Element bind = packet.findChild("bind");
 940				if (bind != null && packet.getType() == IqPacket.TYPE.RESULT) {
 941					final Element jid = bind.findChild("jid");
 942					if (jid != null && jid.getContent() != null) {
 943						try {
 944							account.setResource(Jid.fromString(jid.getContent()).getResourcepart());
 945							if (streamFeatures.hasChild("session")
 946									&& !streamFeatures.findChild("session").hasChild("optional")) {
 947								sendStartSession();
 948							} else {
 949								sendPostBindInitialization();
 950							}
 951							return;
 952						} catch (final InvalidJidException e) {
 953							Log.d(Config.LOGTAG,account.getJid().toBareJid()+": server reported invalid jid ("+jid.getContent()+") on bind");
 954						}
 955					} else {
 956						Log.d(Config.LOGTAG, account.getJid() + ": disconnecting because of bind failure. (no jid)");
 957					}
 958				} else {
 959					Log.d(Config.LOGTAG, account.getJid() + ": disconnecting because of bind failure (" + packet.toString());
 960				}
 961				forceCloseSocket();
 962				changeStatus(Account.State.BIND_FAILURE);
 963			}
 964		});
 965	}
 966
 967	private void clearIqCallbacks() {
 968		final IqPacket failurePacket = new IqPacket(IqPacket.TYPE.TIMEOUT);
 969		final ArrayList<OnIqPacketReceived> callbacks = new ArrayList<>();
 970		synchronized (this.packetCallbacks) {
 971			if (this.packetCallbacks.size() == 0) {
 972				return;
 973			}
 974			Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": clearing "+this.packetCallbacks.size()+" iq callbacks");
 975			final Iterator<Pair<IqPacket, OnIqPacketReceived>> iterator = this.packetCallbacks.values().iterator();
 976			while (iterator.hasNext()) {
 977				Pair<IqPacket, OnIqPacketReceived> entry = iterator.next();
 978				callbacks.add(entry.second);
 979				iterator.remove();
 980			}
 981		}
 982		for(OnIqPacketReceived callback : callbacks) {
 983			callback.onIqPacketReceived(account,failurePacket);
 984		}
 985		Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": done clearing iq callbacks. " + this.packetCallbacks.size() + " left");
 986	}
 987
 988	public void sendDiscoTimeout() {
 989		if (mWaitForDisco.compareAndSet(true, false)) {
 990			finalizeBind();
 991		}
 992	}
 993
 994	private void sendStartSession() {
 995		Log.d(Config.LOGTAG,account.getJid().toBareJid()+": sending legacy session to outdated server");
 996		final IqPacket startSession = new IqPacket(IqPacket.TYPE.SET);
 997		startSession.addChild("session", "urn:ietf:params:xml:ns:xmpp-session");
 998		this.sendUnmodifiedIqPacket(startSession, new OnIqPacketReceived() {
 999			@Override
1000			public void onIqPacketReceived(Account account, IqPacket packet) {
1001				if (packet.getType() == IqPacket.TYPE.RESULT) {
1002					sendPostBindInitialization();
1003				} else if (packet.getType() != IqPacket.TYPE.TIMEOUT) {
1004					Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": could not init sessions");
1005					disconnect(true);
1006				}
1007			}
1008		});
1009	}
1010
1011	private void sendPostBindInitialization() {
1012		smVersion = 0;
1013		if (streamFeatures.hasChild("sm", "urn:xmpp:sm:3")) {
1014			smVersion = 3;
1015		} else if (streamFeatures.hasChild("sm", "urn:xmpp:sm:2")) {
1016			smVersion = 2;
1017		}
1018		if (smVersion != 0) {
1019			synchronized (this.mStanzaQueue) {
1020				final EnablePacket enable = new EnablePacket(smVersion);
1021				tagWriter.writeStanzaAsync(enable);
1022				stanzasSent = 0;
1023				mStanzaQueue.clear();
1024			}
1025		}
1026		features.carbonsEnabled = false;
1027		features.blockListRequested = false;
1028		synchronized (this.disco) {
1029			this.disco.clear();
1030		}
1031		mPendingServiceDiscoveries.set(0);
1032		mWaitForDisco.set(mServerIdentity != Identity.NIMBUZZ);
1033		lastDiscoStarted = SystemClock.elapsedRealtime();
1034		Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": starting service discovery");
1035		mXmppConnectionService.scheduleWakeUpCall(Config.CONNECT_DISCO_TIMEOUT, account.getUuid().hashCode());
1036		Element caps = streamFeatures.findChild("c");
1037		final String hash = caps == null ? null : caps.getAttribute("hash");
1038		final String ver = caps == null ? null : caps.getAttribute("ver");
1039		ServiceDiscoveryResult discoveryResult = null;
1040		if (hash != null && ver != null) {
1041			discoveryResult = mXmppConnectionService.getCachedServiceDiscoveryResult(new Pair<>(hash, ver));
1042		}
1043		if (discoveryResult == null) {
1044			sendServiceDiscoveryInfo(account.getServer());
1045		} else {
1046			Log.d(Config.LOGTAG,account.getJid().toBareJid()+": server caps came from cache");
1047			disco.put(account.getServer(), discoveryResult);
1048		}
1049		sendServiceDiscoveryInfo(account.getJid().toBareJid());
1050		sendServiceDiscoveryItems(account.getServer());
1051
1052		if (!mWaitForDisco.get()) {
1053			finalizeBind();
1054		}
1055		this.lastSessionStarted = SystemClock.elapsedRealtime();
1056	}
1057
1058	private void sendServiceDiscoveryInfo(final Jid jid) {
1059		mPendingServiceDiscoveries.incrementAndGet();
1060		final IqPacket iq = new IqPacket(IqPacket.TYPE.GET);
1061		iq.setTo(jid);
1062		iq.query("http://jabber.org/protocol/disco#info");
1063		this.sendIqPacket(iq, new OnIqPacketReceived() {
1064
1065			@Override
1066			public void onIqPacketReceived(final Account account, final IqPacket packet) {
1067				if (packet.getType() == IqPacket.TYPE.RESULT) {
1068					boolean advancedStreamFeaturesLoaded;
1069					synchronized (XmppConnection.this.disco) {
1070						ServiceDiscoveryResult result = new ServiceDiscoveryResult(packet);
1071						for (final ServiceDiscoveryResult.Identity id : result.getIdentities()) {
1072							if (mServerIdentity == Identity.UNKNOWN && id.getType().equals("im") &&
1073							    id.getCategory().equals("server") && id.getName() != null &&
1074							    jid.equals(account.getServer())) {
1075									switch (id.getName()) {
1076										case "Prosody":
1077											mServerIdentity = Identity.PROSODY;
1078											break;
1079										case "ejabberd":
1080											mServerIdentity = Identity.EJABBERD;
1081											break;
1082										case "Slack-XMPP":
1083											mServerIdentity = Identity.SLACK;
1084											break;
1085									}
1086									Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": server name: " + id.getName());
1087								}
1088						}
1089						if (jid.equals(account.getServer())) {
1090							mXmppConnectionService.databaseBackend.insertDiscoveryResult(result);
1091						}
1092						disco.put(jid, result);
1093						advancedStreamFeaturesLoaded = disco.containsKey(account.getServer())
1094								&& disco.containsKey(account.getJid().toBareJid());
1095					}
1096					if (advancedStreamFeaturesLoaded && (jid.equals(account.getServer()) || jid.equals(account.getJid().toBareJid()))) {
1097						enableAdvancedStreamFeatures();
1098					}
1099				} else {
1100					Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": could not query disco info for " + jid.toString());
1101				}
1102				if (packet.getType() != IqPacket.TYPE.TIMEOUT) {
1103					if (mPendingServiceDiscoveries.decrementAndGet() == 0
1104							&& mWaitForDisco.compareAndSet(true, false)) {
1105						finalizeBind();
1106					}
1107				}
1108			}
1109		});
1110	}
1111
1112	private void finalizeBind() {
1113		Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": online with resource " + account.getResource());
1114		if (bindListener != null) {
1115			bindListener.onBind(account);
1116		}
1117		changeStatus(Account.State.ONLINE);
1118	}
1119
1120	private void enableAdvancedStreamFeatures() {
1121		if (getFeatures().carbons() && !features.carbonsEnabled) {
1122			sendEnableCarbons();
1123		}
1124		if (getFeatures().blocking() && !features.blockListRequested) {
1125			Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": Requesting block list");
1126			this.sendIqPacket(getIqGenerator().generateGetBlockList(), mXmppConnectionService.getIqParser());
1127		}
1128		for (final OnAdvancedStreamFeaturesLoaded listener : advancedStreamFeaturesLoadedListeners) {
1129			listener.onAdvancedStreamFeaturesAvailable(account);
1130		}
1131	}
1132
1133	private void sendServiceDiscoveryItems(final Jid server) {
1134		mPendingServiceDiscoveries.incrementAndGet();
1135		final IqPacket iq = new IqPacket(IqPacket.TYPE.GET);
1136		iq.setTo(server.toDomainJid());
1137		iq.query("http://jabber.org/protocol/disco#items");
1138		this.sendIqPacket(iq, new OnIqPacketReceived() {
1139
1140			@Override
1141			public void onIqPacketReceived(final Account account, final IqPacket packet) {
1142				if (packet.getType() == IqPacket.TYPE.RESULT) {
1143					final List<Element> elements = packet.query().getChildren();
1144					for (final Element element : elements) {
1145						if (element.getName().equals("item")) {
1146							final Jid jid = element.getAttributeAsJid("jid");
1147							if (jid != null && !jid.equals(account.getServer())) {
1148								sendServiceDiscoveryInfo(jid);
1149							}
1150						}
1151					}
1152				} else {
1153					Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": could not query disco items of " + server);
1154				}
1155				if (packet.getType() != IqPacket.TYPE.TIMEOUT) {
1156					if (mPendingServiceDiscoveries.decrementAndGet() == 0
1157							&& mWaitForDisco.compareAndSet(true, false)) {
1158						finalizeBind();
1159					}
1160				}
1161			}
1162		});
1163	}
1164
1165	private void sendEnableCarbons() {
1166		final IqPacket iq = new IqPacket(IqPacket.TYPE.SET);
1167		iq.addChild("enable", "urn:xmpp:carbons:2");
1168		this.sendIqPacket(iq, new OnIqPacketReceived() {
1169
1170			@Override
1171			public void onIqPacketReceived(final Account account, final IqPacket packet) {
1172				if (!packet.hasChild("error")) {
1173					Log.d(Config.LOGTAG, account.getJid().toBareJid()
1174							+ ": successfully enabled carbons");
1175					features.carbonsEnabled = true;
1176				} else {
1177					Log.d(Config.LOGTAG, account.getJid().toBareJid()
1178							+ ": error enableing carbons " + packet.toString());
1179				}
1180			}
1181		});
1182	}
1183
1184	private void processStreamError(final Tag currentTag)
1185		throws XmlPullParserException, IOException {
1186		final Element streamError = tagReader.readElement(currentTag);
1187		if (streamError == null) {
1188			return;
1189		}
1190		if (streamError.hasChild("conflict")) {
1191			final String resource = account.getResource().split("\\.")[0];
1192			account.setResource(resource + "." + nextRandomId());
1193			Log.d(Config.LOGTAG,
1194					account.getJid().toBareJid() + ": switching resource due to conflict ("
1195					+ account.getResource() + ")");
1196			throw new IOException();
1197		} else if (streamError.hasChild("host-unknown")) {
1198			throw new StreamErrorHostUnknown();
1199		} else if (streamError.hasChild("policy-violation")) {
1200			throw new StreamErrorPolicyViolation();
1201		} else {
1202			Log.d(Config.LOGTAG,account.getJid().toBareJid()+": stream error "+streamError.toString());
1203			throw new StreamError();
1204		}
1205	}
1206
1207	private void sendStartStream() throws IOException {
1208		final Tag stream = Tag.start("stream:stream");
1209		stream.setAttribute("to", account.getServer().toString());
1210		stream.setAttribute("version", "1.0");
1211		stream.setAttribute("xml:lang", "en");
1212		stream.setAttribute("xmlns", "jabber:client");
1213		stream.setAttribute("xmlns:stream", "http://etherx.jabber.org/streams");
1214		tagWriter.writeTag(stream);
1215	}
1216
1217	private String nextRandomId() {
1218		return new BigInteger(50, mXmppConnectionService.getRNG()).toString(32);
1219	}
1220
1221	public String sendIqPacket(final IqPacket packet, final OnIqPacketReceived callback) {
1222		packet.setFrom(account.getJid());
1223		return this.sendUnmodifiedIqPacket(packet, callback);
1224	}
1225
1226	private synchronized String sendUnmodifiedIqPacket(final IqPacket packet, final OnIqPacketReceived callback) {
1227		if (packet.getId() == null) {
1228			final String id = nextRandomId();
1229			packet.setAttribute("id", id);
1230		}
1231		if (callback != null) {
1232			synchronized (this.packetCallbacks) {
1233				packetCallbacks.put(packet.getId(), new Pair<>(packet, callback));
1234			}
1235		}
1236		this.sendPacket(packet);
1237		return packet.getId();
1238	}
1239
1240	public void sendMessagePacket(final MessagePacket packet) {
1241		this.sendPacket(packet);
1242	}
1243
1244	public void sendPresencePacket(final PresencePacket packet) {
1245		this.sendPacket(packet);
1246	}
1247
1248	private synchronized void sendPacket(final AbstractStanza packet) {
1249		if (stanzasSent == Integer.MAX_VALUE) {
1250			resetStreamId();
1251			disconnect(true);
1252			return;
1253		}
1254		synchronized (this.mStanzaQueue) {
1255			tagWriter.writeStanzaAsync(packet);
1256			if (packet instanceof AbstractAcknowledgeableStanza) {
1257				AbstractAcknowledgeableStanza stanza = (AbstractAcknowledgeableStanza) packet;
1258				++stanzasSent;
1259				this.mStanzaQueue.append(stanzasSent, stanza);
1260				if (stanza instanceof MessagePacket && stanza.getId() != null && getFeatures().sm()) {
1261					if (Config.EXTENDED_SM_LOGGING) {
1262						Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": requesting ack for message stanza #" + stanzasSent);
1263					}
1264					tagWriter.writeStanzaAsync(new RequestPacket(this.smVersion));
1265				}
1266			}
1267		}
1268	}
1269
1270	public void sendPing() {
1271		if (!r()) {
1272			final IqPacket iq = new IqPacket(IqPacket.TYPE.GET);
1273			iq.setFrom(account.getJid());
1274			iq.addChild("ping", "urn:xmpp:ping");
1275			this.sendIqPacket(iq, null);
1276		}
1277		this.lastPingSent = SystemClock.elapsedRealtime();
1278	}
1279
1280	public void setOnMessagePacketReceivedListener(
1281			final OnMessagePacketReceived listener) {
1282		this.messageListener = listener;
1283			}
1284
1285	public void setOnUnregisteredIqPacketReceivedListener(
1286			final OnIqPacketReceived listener) {
1287		this.unregisteredIqListener = listener;
1288			}
1289
1290	public void setOnPresencePacketReceivedListener(
1291			final OnPresencePacketReceived listener) {
1292		this.presenceListener = listener;
1293			}
1294
1295	public void setOnJinglePacketReceivedListener(
1296			final OnJinglePacketReceived listener) {
1297		this.jingleListener = listener;
1298			}
1299
1300	public void setOnStatusChangedListener(final OnStatusChanged listener) {
1301		this.statusListener = listener;
1302	}
1303
1304	public void setOnBindListener(final OnBindListener listener) {
1305		this.bindListener = listener;
1306	}
1307
1308	public void setOnMessageAcknowledgeListener(final OnMessageAcknowledged listener) {
1309		this.acknowledgedListener = listener;
1310	}
1311
1312	public void addOnAdvancedStreamFeaturesAvailableListener(final OnAdvancedStreamFeaturesLoaded listener) {
1313		if (!this.advancedStreamFeaturesLoadedListeners.contains(listener)) {
1314			this.advancedStreamFeaturesLoadedListeners.add(listener);
1315		}
1316	}
1317
1318	public void waitForPush() {
1319		if (tagWriter.isActive()) {
1320			tagWriter.finish();
1321			new Thread(new Runnable() {
1322				@Override
1323				public void run() {
1324					try {
1325						while(!tagWriter.finished()) {
1326							Thread.sleep(10);
1327						}
1328						socket.close();
1329						Log.d(Config.LOGTAG,account.getJid().toBareJid()+": closed tcp without closing stream");
1330					} catch (IOException | InterruptedException e) {
1331						return;
1332					}
1333				}
1334			}).start();
1335		} else {
1336			forceCloseSocket();
1337			Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": closed tcp without closing stream (no waiting)");
1338		}
1339	}
1340
1341	private void forceCloseSocket() {
1342		if (socket != null) {
1343			try {
1344				socket.close();
1345			} catch (IOException e) {
1346				e.printStackTrace();
1347			}
1348		}
1349	}
1350
1351	public void interrupt() {
1352		Thread.currentThread().interrupt();
1353	}
1354
1355	public void disconnect(final boolean force) {
1356		interrupt();
1357		Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": disconnecting force="+Boolean.valueOf(force));
1358		if (force) {
1359			tagWriter.forceClose();
1360			forceCloseSocket();
1361		} else {
1362			if (tagWriter.isActive()) {
1363				tagWriter.finish();
1364				try {
1365					int i = 0;
1366					boolean warned = false;
1367					while (!tagWriter.finished() && socket.isConnected() && i <= 10) {
1368						if (!warned) {
1369							Log.d(Config.LOGTAG, account.getJid().toBareJid()+": waiting for tag writer to finish");
1370							warned = true;
1371						}
1372						Thread.sleep(200);
1373						i++;
1374					}
1375					if (warned) {
1376						Log.d(Config.LOGTAG,account.getJid().toBareJid()+": tag writer has finished");
1377					}
1378					Log.d(Config.LOGTAG,account.getJid().toBareJid()+": closing stream");
1379					tagWriter.writeTag(Tag.end("stream:stream"));
1380				} catch (final IOException e) {
1381					Log.d(Config.LOGTAG,account.getJid().toBareJid()+": io exception during disconnect ("+e.getMessage()+")");
1382				} catch (final InterruptedException e) {
1383					Log.d(Config.LOGTAG, "interrupted");
1384				}
1385			}
1386		}
1387	}
1388
1389	public void resetStreamId() {
1390		this.streamId = null;
1391	}
1392
1393	private List<Entry<Jid, ServiceDiscoveryResult>> findDiscoItemsByFeature(final String feature) {
1394		synchronized (this.disco) {
1395			final List<Entry<Jid, ServiceDiscoveryResult>> items = new ArrayList<>();
1396			for (final Entry<Jid, ServiceDiscoveryResult> cursor : this.disco.entrySet()) {
1397				if (cursor.getValue().getFeatures().contains(feature)) {
1398					items.add(cursor);
1399				}
1400			}
1401			return items;
1402		}
1403	}
1404
1405	public Jid findDiscoItemByFeature(final String feature) {
1406		final List<Entry<Jid, ServiceDiscoveryResult>> items = findDiscoItemsByFeature(feature);
1407		if (items.size() >= 1) {
1408			return items.get(0).getKey();
1409		}
1410		return null;
1411	}
1412
1413	public boolean r() {
1414		if (getFeatures().sm()) {
1415			this.tagWriter.writeStanzaAsync(new RequestPacket(smVersion));
1416			return true;
1417		} else {
1418			return false;
1419		}
1420	}
1421
1422	public String getMucServer() {
1423		synchronized (this.disco) {
1424			for (final Entry<Jid, ServiceDiscoveryResult> cursor : disco.entrySet()) {
1425				final ServiceDiscoveryResult value = cursor.getValue();
1426				if (value.getFeatures().contains("http://jabber.org/protocol/muc")
1427						&& !value.getFeatures().contains("jabber:iq:gateway")
1428						&& !value.hasIdentity("conference", "irc")) {
1429					return cursor.getKey().toString();
1430				}
1431			}
1432		}
1433		return null;
1434	}
1435
1436	public int getTimeToNextAttempt() {
1437		final int interval = Math.min((int) (25 * Math.pow(1.3, attempt)), 300);
1438		final int secondsSinceLast = (int) ((SystemClock.elapsedRealtime() - this.lastConnect) / 1000);
1439		return interval - secondsSinceLast;
1440	}
1441
1442	public int getAttempt() {
1443		return this.attempt;
1444	}
1445
1446	public Features getFeatures() {
1447		return this.features;
1448	}
1449
1450	public long getLastSessionEstablished() {
1451		final long diff = SystemClock.elapsedRealtime() - this.lastSessionStarted;
1452		return System.currentTimeMillis() - diff;
1453	}
1454
1455	public long getLastConnect() {
1456		return this.lastConnect;
1457	}
1458
1459	public long getLastPingSent() {
1460		return this.lastPingSent;
1461	}
1462
1463	public long getLastDiscoStarted() {
1464		return this.lastDiscoStarted;
1465	}
1466	public long getLastPacketReceived() {
1467		return this.lastPacketReceived;
1468	}
1469
1470	public void sendActive() {
1471		this.sendPacket(new ActivePacket());
1472	}
1473
1474	public void sendInactive() {
1475		this.sendPacket(new InactivePacket());
1476	}
1477
1478	public void resetAttemptCount() {
1479		this.attempt = 0;
1480		this.lastConnect = 0;
1481	}
1482
1483	public void setInteractive(boolean interactive) {
1484		this.mInteractive = interactive;
1485	}
1486
1487	public Identity getServerIdentity() {
1488		return mServerIdentity;
1489	}
1490
1491	private class UnauthorizedException extends IOException {
1492
1493	}
1494
1495	private class SecurityException extends IOException {
1496
1497	}
1498
1499	private class IncompatibleServerException extends IOException {
1500
1501	}
1502
1503	private class StreamErrorHostUnknown extends StreamError {
1504
1505	}
1506
1507	private class StreamErrorPolicyViolation extends StreamError {
1508
1509	}
1510
1511	private class StreamError extends IOException {
1512
1513	}
1514
1515	public enum Identity {
1516		FACEBOOK,
1517		SLACK,
1518		EJABBERD,
1519		PROSODY,
1520		NIMBUZZ,
1521		UNKNOWN
1522	}
1523
1524	public class Features {
1525		XmppConnection connection;
1526		private boolean carbonsEnabled = false;
1527		private boolean encryptionEnabled = false;
1528		private boolean blockListRequested = false;
1529
1530		public Features(final XmppConnection connection) {
1531			this.connection = connection;
1532		}
1533
1534		private boolean hasDiscoFeature(final Jid server, final String feature) {
1535			synchronized (XmppConnection.this.disco) {
1536				return connection.disco.containsKey(server) &&
1537						connection.disco.get(server).getFeatures().contains(feature);
1538			}
1539		}
1540
1541		public boolean carbons() {
1542			return hasDiscoFeature(account.getServer(), "urn:xmpp:carbons:2");
1543		}
1544
1545		public boolean blocking() {
1546			return hasDiscoFeature(account.getServer(), Xmlns.BLOCKING);
1547		}
1548
1549		public boolean register() {
1550			return hasDiscoFeature(account.getServer(), Xmlns.REGISTER);
1551		}
1552
1553		public boolean sm() {
1554			return streamId != null
1555					|| (connection.streamFeatures != null && connection.streamFeatures.hasChild("sm"));
1556		}
1557
1558		public boolean csi() {
1559			return connection.streamFeatures != null && connection.streamFeatures.hasChild("csi", "urn:xmpp:csi:0");
1560		}
1561
1562		public boolean pep() {
1563			synchronized (XmppConnection.this.disco) {
1564				ServiceDiscoveryResult info = disco.get(account.getJid().toBareJid());
1565				return info != null && info.hasIdentity("pubsub", "pep");
1566			}
1567		}
1568
1569		public boolean pepPersistent() {
1570			synchronized (XmppConnection.this.disco) {
1571				ServiceDiscoveryResult info = disco.get(account.getJid().toBareJid());
1572				return info != null && info.getFeatures().contains("http://jabber.org/protocol/pubsub#persistent-items");
1573			}
1574		}
1575
1576		public boolean mam() {
1577			return hasDiscoFeature(account.getJid().toBareJid(), "urn:xmpp:mam:0")
1578				|| hasDiscoFeature(account.getServer(), "urn:xmpp:mam:0");
1579		}
1580
1581		public boolean push() {
1582			return hasDiscoFeature(account.getJid().toBareJid(), "urn:xmpp:push:0")
1583					|| hasDiscoFeature(account.getServer(), "urn:xmpp:push:0");
1584		}
1585
1586		public boolean rosterVersioning() {
1587			return connection.streamFeatures != null && connection.streamFeatures.hasChild("ver");
1588		}
1589
1590		public void setBlockListRequested(boolean value) {
1591			this.blockListRequested = value;
1592		}
1593
1594		public boolean httpUpload(long filesize) {
1595			if (Config.DISABLE_HTTP_UPLOAD) {
1596				return false;
1597			} else {
1598				List<Entry<Jid, ServiceDiscoveryResult>> items = findDiscoItemsByFeature(Xmlns.HTTP_UPLOAD);
1599				if (items.size() > 0) {
1600					try {
1601						long maxsize = Long.parseLong(items.get(0).getValue().getExtendedDiscoInformation(Xmlns.HTTP_UPLOAD, "max-file-size"));
1602						if(filesize <= maxsize) {
1603							return true;
1604						} else {
1605							Log.d(Config.LOGTAG,account.getJid().toBareJid()+": http upload is not available for files with size "+filesize+" (max is "+maxsize+")");
1606							return false;
1607						}
1608					} catch (Exception e) {
1609						return true;
1610					}
1611				} else {
1612					return false;
1613				}
1614			}
1615		}
1616
1617		public long getMaxHttpUploadSize() {
1618			List<Entry<Jid, ServiceDiscoveryResult>> items = findDiscoItemsByFeature(Xmlns.HTTP_UPLOAD);
1619				if (items.size() > 0) {
1620					try {
1621						return Long.parseLong(items.get(0).getValue().getExtendedDiscoInformation(Xmlns.HTTP_UPLOAD, "max-file-size"));
1622					} catch (Exception e) {
1623						return -1;
1624					}
1625				} else {
1626					return -1;
1627				}
1628		}
1629	}
1630
1631	private IqGenerator getIqGenerator() {
1632		return mXmppConnectionService.getIqGenerator();
1633	}
1634}