XmppConnection.java

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