2014-02-28 17:46:01 +00:00
package eu.siacs.conversations.xmpp ;
2014-01-30 15:42:35 +00:00
2015-10-11 11:11:50 +00:00
import android.graphics.Bitmap ;
import android.graphics.BitmapFactory ;
2014-11-03 09:03:45 +00:00
import android.os.PowerManager ;
import android.os.PowerManager.WakeLock ;
import android.os.SystemClock ;
2015-10-11 18:45:01 +00:00
import android.security.KeyChain ;
2015-10-11 11:11:50 +00:00
import android.util.Base64 ;
2014-11-03 09:03:45 +00:00
import android.util.Log ;
2014-12-30 13:50:51 +00:00
import android.util.Pair ;
2014-11-03 09:03:45 +00:00
import android.util.SparseArray ;
import org.xmlpull.v1.XmlPullParserException ;
2015-10-11 11:11:50 +00:00
import java.io.ByteArrayInputStream ;
2014-01-30 15:42:35 +00:00
import java.io.IOException ;
import java.io.InputStream ;
import java.math.BigInteger ;
2014-11-18 00:48:16 +00:00
import java.net.ConnectException ;
2017-07-10 06:50:01 +00:00
import java.net.IDN ;
2014-11-16 11:00:53 +00:00
import java.net.InetAddress ;
2014-10-27 20:48:25 +00:00
import java.net.InetSocketAddress ;
2014-01-30 15:42:35 +00:00
import java.net.Socket ;
2015-10-11 11:11:50 +00:00
import java.net.URL ;
2017-01-12 20:26:58 +00:00
import java.net.UnknownHostException ;
2014-03-06 19:03:35 +00:00
import java.security.KeyManagementException ;
import java.security.NoSuchAlgorithmException ;
2015-10-11 18:45:01 +00:00
import java.security.Principal ;
import java.security.PrivateKey ;
import java.security.cert.X509Certificate ;
2014-03-15 03:59:18 +00:00
import java.util.ArrayList ;
2016-07-25 13:57:47 +00:00
import java.util.Arrays ;
2014-04-08 21:15:55 +00:00
import java.util.HashMap ;
2017-06-30 06:45:16 +00:00
import java.util.HashSet ;
2014-02-01 00:25:56 +00:00
import java.util.Hashtable ;
2015-08-06 12:54:37 +00:00
import java.util.Iterator ;
2014-02-09 13:10:52 +00:00
import java.util.List ;
2014-04-08 21:15:55 +00:00
import java.util.Map.Entry ;
2016-04-04 18:06:07 +00:00
import java.util.concurrent.atomic.AtomicBoolean ;
import java.util.concurrent.atomic.AtomicInteger ;
2017-06-19 18:02:41 +00:00
import java.util.regex.Matcher ;
2014-01-30 15:42:35 +00:00
2015-10-11 18:45:01 +00:00
import javax.net.ssl.KeyManager ;
2014-03-06 19:03:35 +00:00
import javax.net.ssl.SSLContext ;
2016-08-10 10:34:05 +00:00
import javax.net.ssl.SSLSession ;
2014-01-30 15:42:35 +00:00
import javax.net.ssl.SSLSocket ;
import javax.net.ssl.SSLSocketFactory ;
2015-10-11 18:45:01 +00:00
import javax.net.ssl.X509KeyManager ;
2014-03-06 19:03:35 +00:00
import javax.net.ssl.X509TrustManager ;
2014-01-30 15:42:35 +00:00
2017-06-17 17:54:09 +00:00
import de.duenndns.ssl.DomainHostnameVerifier ;
2015-09-29 17:24:52 +00:00
import de.duenndns.ssl.MemorizingTrustManager ;
2014-08-31 14:28:21 +00:00
import eu.siacs.conversations.Config ;
2015-10-15 15:08:38 +00:00
import eu.siacs.conversations.crypto.XmppDomainVerifier ;
2016-08-07 14:46:01 +00:00
import eu.siacs.conversations.crypto.sasl.Anonymous ;
2014-11-12 15:15:38 +00:00
import eu.siacs.conversations.crypto.sasl.DigestMd5 ;
2015-10-11 18:45:01 +00:00
import eu.siacs.conversations.crypto.sasl.External ;
2014-11-12 15:15:38 +00:00
import eu.siacs.conversations.crypto.sasl.Plain ;
import eu.siacs.conversations.crypto.sasl.SaslMechanism ;
2014-11-12 20:35:44 +00:00
import eu.siacs.conversations.crypto.sasl.ScramSha1 ;
2017-01-12 20:26:58 +00:00
import eu.siacs.conversations.crypto.sasl.ScramSha256 ;
2014-02-28 17:46:01 +00:00
import eu.siacs.conversations.entities.Account ;
2015-08-19 11:00:52 +00:00
import eu.siacs.conversations.entities.Message ;
2016-01-10 19:56:55 +00:00
import eu.siacs.conversations.entities.ServiceDiscoveryResult ;
2014-12-21 20:43:58 +00:00
import eu.siacs.conversations.generator.IqGenerator ;
2017-05-19 11:30:57 +00:00
import eu.siacs.conversations.services.NotificationService ;
2014-06-20 15:30:19 +00:00
import eu.siacs.conversations.services.XmppConnectionService ;
2017-06-21 21:28:01 +00:00
import eu.siacs.conversations.utils.IP ;
2017-06-19 18:02:41 +00:00
import eu.siacs.conversations.utils.Patterns ;
2017-06-21 21:28:01 +00:00
import eu.siacs.conversations.utils.Resolver ;
2016-01-12 15:33:15 +00:00
import eu.siacs.conversations.utils.SSLSocketHelper ;
2015-11-29 15:31:51 +00:00
import eu.siacs.conversations.utils.SocksSocketFactory ;
2014-02-28 17:46:01 +00:00
import eu.siacs.conversations.xml.Element ;
import eu.siacs.conversations.xml.Tag ;
import eu.siacs.conversations.xml.TagWriter ;
import eu.siacs.conversations.xml.XmlReader ;
2017-03-01 12:01:46 +00:00
import eu.siacs.conversations.xml.Namespace ;
2015-10-11 11:11:50 +00:00
import eu.siacs.conversations.xmpp.forms.Data ;
import eu.siacs.conversations.xmpp.forms.Field ;
2014-11-05 20:55:47 +00:00
import eu.siacs.conversations.xmpp.jid.InvalidJidException ;
import eu.siacs.conversations.xmpp.jid.Jid ;
2014-04-08 21:15:55 +00:00
import eu.siacs.conversations.xmpp.jingle.OnJinglePacketReceived ;
import eu.siacs.conversations.xmpp.jingle.stanzas.JinglePacket ;
2015-08-13 16:25:10 +00:00
import eu.siacs.conversations.xmpp.stanzas.AbstractAcknowledgeableStanza ;
2014-03-10 18:22:13 +00:00
import eu.siacs.conversations.xmpp.stanzas.AbstractStanza ;
import eu.siacs.conversations.xmpp.stanzas.IqPacket ;
import eu.siacs.conversations.xmpp.stanzas.MessagePacket ;
import eu.siacs.conversations.xmpp.stanzas.PresencePacket ;
2014-08-26 15:43:44 +00:00
import eu.siacs.conversations.xmpp.stanzas.csi.ActivePacket ;
import eu.siacs.conversations.xmpp.stanzas.csi.InactivePacket ;
2014-03-10 18:22:13 +00:00
import eu.siacs.conversations.xmpp.stanzas.streammgmt.AckPacket ;
import eu.siacs.conversations.xmpp.stanzas.streammgmt.EnablePacket ;
import eu.siacs.conversations.xmpp.stanzas.streammgmt.RequestPacket ;
import eu.siacs.conversations.xmpp.stanzas.streammgmt.ResumePacket ;
2014-01-30 15:42:35 +00:00
public class XmppConnection implements Runnable {
2014-11-08 19:52:02 +00:00
private static final int PACKET_IQ = 0 ;
private static final int PACKET_MESSAGE = 1 ;
private static final int PACKET_PRESENCE = 2 ;
2017-04-30 14:19:39 +00:00
protected final Account account ;
2014-12-21 20:43:58 +00:00
private final WakeLock wakeLock ;
2014-01-30 15:42:35 +00:00
private Socket socket ;
private XmlReader tagReader ;
2017-02-17 09:26:42 +00:00
private TagWriter tagWriter = new TagWriter ( ) ;
2014-12-21 20:43:58 +00:00
private final Features features = new Features ( this ) ;
2015-08-23 15:26:50 +00:00
private boolean needsBinding = true ;
2014-02-01 00:25:56 +00:00
private boolean shouldAuthenticate = true ;
private Element streamFeatures ;
2016-01-10 19:56:55 +00:00
private final HashMap < Jid , ServiceDiscoveryResult > disco = new HashMap < > ( ) ;
2014-11-05 20:55:47 +00:00
2014-03-10 18:22:13 +00:00
private String streamId = null ;
2014-04-05 10:08:35 +00:00
private int smVersion = 3 ;
2015-08-13 16:25:10 +00:00
private final SparseArray < AbstractAcknowledgeableStanza > mStanzaQueue = new SparseArray < > ( ) ;
2014-11-05 20:55:47 +00:00
2014-03-10 18:22:13 +00:00
private int stanzasReceived = 0 ;
private int stanzasSent = 0 ;
2015-01-05 15:17:05 +00:00
private long lastPacketReceived = 0 ;
2014-08-19 13:06:50 +00:00
private long lastPingSent = 0 ;
private long lastConnect = 0 ;
private long lastSessionStarted = 0 ;
2015-12-15 18:14:38 +00:00
private long lastDiscoStarted = 0 ;
2016-04-04 18:06:07 +00:00
private AtomicInteger mPendingServiceDiscoveries = new AtomicInteger ( 0 ) ;
2016-07-12 22:20:57 +00:00
private AtomicBoolean mWaitForDisco = new AtomicBoolean ( true ) ;
2017-03-21 16:58:08 +00:00
private AtomicBoolean mWaitingForSmCatchup = new AtomicBoolean ( false ) ;
private AtomicInteger mSmCatchupMessageCounter = new AtomicInteger ( 0 ) ;
2015-09-29 17:24:52 +00:00
private boolean mInteractive = false ;
2014-05-18 09:25:04 +00:00
private int attempt = 0 ;
2015-08-23 06:01:47 +00:00
private final Hashtable < String , Pair < IqPacket , OnIqPacketReceived > > packetCallbacks = new Hashtable < > ( ) ;
2014-02-01 14:07:20 +00:00
private OnPresencePacketReceived presenceListener = null ;
2014-03-27 01:02:59 +00:00
private OnJinglePacketReceived jingleListener = null ;
2014-02-01 14:07:20 +00:00
private OnIqPacketReceived unregisteredIqListener = null ;
private OnMessagePacketReceived messageListener = null ;
2014-02-04 14:09:50 +00:00
private OnStatusChanged statusListener = null ;
2014-03-14 19:43:54 +00:00
private OnBindListener bindListener = null ;
2014-12-21 20:43:58 +00:00
private final ArrayList < OnAdvancedStreamFeaturesLoaded > advancedStreamFeaturesLoadedListeners = new ArrayList < > ( ) ;
2014-08-26 14:52:42 +00:00
private OnMessageAcknowledged acknowledgedListener = null ;
2017-04-30 14:19:39 +00:00
private final XmppConnectionService mXmppConnectionService ;
2014-01-30 15:42:35 +00:00
2014-11-12 20:35:44 +00:00
private SaslMechanism saslMechanism ;
2017-06-19 18:02:41 +00:00
private String webRegistrationUrl = null ;
2017-06-17 17:54:09 +00:00
private String verifiedHostname = null ;
2014-11-12 15:15:38 +00:00
2016-10-07 08:04:36 +00:00
private class MyKeyManager implements X509KeyManager {
2015-10-11 18:45:01 +00:00
@Override
public String chooseClientAlias ( String [ ] strings , Principal [ ] principals , Socket socket ) {
return account . getPrivateKeyAlias ( ) ;
}
@Override
public String chooseServerAlias ( String s , Principal [ ] principals , Socket socket ) {
return null ;
}
@Override
public X509Certificate [ ] getCertificateChain ( String alias ) {
2016-10-07 08:04:36 +00:00
Log . d ( Config . LOGTAG , " getting certificate chain " ) ;
2015-10-11 18:45:01 +00:00
try {
return KeyChain . getCertificateChain ( mXmppConnectionService , alias ) ;
} catch ( Exception e ) {
2016-10-07 08:04:36 +00:00
Log . d ( Config . LOGTAG , e . getMessage ( ) ) ;
2015-10-11 18:45:01 +00:00
return new X509Certificate [ 0 ] ;
}
}
@Override
public String [ ] getClientAliases ( String s , Principal [ ] principals ) {
2017-05-18 09:31:31 +00:00
final String alias = account . getPrivateKeyAlias ( ) ;
return alias ! = null ? new String [ ] { alias } : new String [ 0 ] ;
2015-10-11 18:45:01 +00:00
}
@Override
public String [ ] getServerAliases ( String s , Principal [ ] principals ) {
return new String [ 0 ] ;
}
@Override
public PrivateKey getPrivateKey ( String alias ) {
try {
return KeyChain . getPrivateKey ( mXmppConnectionService , alias ) ;
} catch ( Exception e ) {
return null ;
}
}
2016-10-07 08:04:36 +00:00
}
2016-05-05 07:58:35 +00:00
public final OnIqPacketReceived registrationResponseListener = new OnIqPacketReceived ( ) {
@Override
public void onIqPacketReceived ( Account account , IqPacket packet ) {
if ( packet . getType ( ) = = IqPacket . TYPE . RESULT ) {
account . setOption ( Account . OPTION_REGISTER , false ) ;
forceCloseSocket ( ) ;
changeStatus ( Account . State . REGISTRATION_SUCCESSFUL ) ;
} else {
2016-07-25 13:57:47 +00:00
final List < String > PASSWORD_TOO_WEAK_MSGS = Arrays . asList (
" The password is too weak " ,
" Please use a longer password. " ) ;
2016-05-24 11:26:30 +00:00
Element error = packet . findChild ( " error " ) ;
2016-07-25 13:57:47 +00:00
Account . State state = Account . State . REGISTRATION_FAILED ;
if ( error ! = null ) {
if ( error . hasChild ( " conflict " ) ) {
state = Account . State . REGISTRATION_CONFLICT ;
} else if ( error . hasChild ( " resource-constraint " )
& & " wait " . equals ( error . getAttribute ( " type " ) ) ) {
state = Account . State . REGISTRATION_PLEASE_WAIT ;
} else if ( error . hasChild ( " not-acceptable " )
& & PASSWORD_TOO_WEAK_MSGS . contains ( error . findChildContent ( " text " ) ) ) {
state = Account . State . REGISTRATION_PASSWORD_TOO_WEAK ;
}
2016-05-24 11:26:30 +00:00
}
2016-07-25 13:57:47 +00:00
changeStatus ( state ) ;
forceCloseSocket ( ) ;
2015-10-11 11:11:50 +00:00
}
2016-05-05 07:58:35 +00:00
}
} ;
2015-10-11 11:11:50 +00:00
2014-12-21 20:43:58 +00:00
public XmppConnection ( final Account account , final XmppConnectionService service ) {
2014-01-30 15:42:35 +00:00
this . account = account ;
2014-08-31 14:28:21 +00:00
this . wakeLock = service . getPowerManager ( ) . newWakeLock (
2014-11-12 20:35:44 +00:00
PowerManager . PARTIAL_WAKE_LOCK , account . getJid ( ) . toBareJid ( ) . toString ( ) ) ;
2014-11-09 09:50:45 +00:00
mXmppConnectionService = service ;
2014-01-30 15:42:35 +00:00
}
2014-03-06 14:11:56 +00:00
2017-05-19 13:28:25 +00:00
protected void changeStatus ( final Account . State nextStatus ) {
synchronized ( this ) {
if ( Thread . currentThread ( ) . isInterrupted ( ) ) {
Log . d ( Config . LOGTAG , account . getJid ( ) . toBareJid ( ) + " : not changing status to " + nextStatus + " because thread was interrupted " ) ;
2014-03-13 16:29:22 +00:00
return ;
2014-05-18 09:25:04 +00:00
}
2017-05-19 13:28:25 +00:00
if ( account . getStatus ( ) ! = nextStatus ) {
if ( ( nextStatus = = Account . State . OFFLINE )
& & ( account . getStatus ( ) ! = Account . State . CONNECTING )
& & ( account . getStatus ( ) ! = Account . State . ONLINE )
& & ( account . getStatus ( ) ! = Account . State . DISABLED ) ) {
return ;
}
if ( nextStatus = = Account . State . ONLINE ) {
this . attempt = 0 ;
}
account . setStatus ( nextStatus ) ;
} else {
return ;
2014-03-11 15:27:33 +00:00
}
2014-03-06 02:57:29 +00:00
}
2017-05-19 13:28:25 +00:00
if ( statusListener ! = null ) {
statusListener . onStatusChanged ( account ) ;
}
2014-03-06 02:57:29 +00:00
}
2014-01-30 15:42:35 +00:00
2016-03-20 16:24:41 +00:00
public void prepareNewConnection ( ) {
this . lastConnect = SystemClock . elapsedRealtime ( ) ;
this . lastPingSent = SystemClock . elapsedRealtime ( ) ;
this . lastDiscoStarted = Long . MAX_VALUE ;
2017-03-21 16:58:08 +00:00
this . mWaitingForSmCatchup . set ( false ) ;
2016-03-20 16:24:41 +00:00
this . changeStatus ( Account . State . CONNECTING ) ;
}
2017-03-21 16:58:08 +00:00
public boolean isWaitingForSmCatchup ( ) {
return mWaitingForSmCatchup . get ( ) ;
}
public void incrementSmCatchupMessageCounter ( ) {
this . mSmCatchupMessageCounter . incrementAndGet ( ) ;
}
2014-01-30 15:42:35 +00:00
protected void connect ( ) {
2016-10-07 12:54:35 +00:00
if ( mXmppConnectionService . areMessagesInitialized ( ) ) {
mXmppConnectionService . resetSendingToWaiting ( account ) ;
}
2014-11-09 15:57:22 +00:00
Log . d ( Config . LOGTAG , account . getJid ( ) . toBareJid ( ) . toString ( ) + " : connecting " ) ;
2015-01-05 15:17:05 +00:00
features . encryptionEnabled = false ;
2014-05-18 09:25:04 +00:00
this . attempt + + ;
2017-06-17 17:54:09 +00:00
this . verifiedHostname = null ; //will be set if user entered hostname is being used or hostname was verified with dnssec
2014-01-30 15:42:35 +00:00
try {
2016-11-19 09:44:40 +00:00
Socket localSocket ;
2015-08-23 15:26:50 +00:00
shouldAuthenticate = needsBinding = ! account . isOptionSet ( Account . OPTION_REGISTER ) ;
2014-11-15 16:09:02 +00:00
this . changeStatus ( Account . State . CONNECTING ) ;
2015-11-29 14:44:45 +00:00
final boolean useTor = mXmppConnectionService . useTorToConnect ( ) | | account . isOnion ( ) ;
2016-01-25 20:17:53 +00:00
final boolean extended = mXmppConnectionService . showExtendedConnectionOptions ( ) ;
2015-11-29 14:44:45 +00:00
if ( useTor ) {
2015-11-29 15:31:51 +00:00
String destination ;
2017-06-17 17:54:09 +00:00
if ( account . getHostname ( ) . isEmpty ( ) ) {
2015-11-29 15:31:51 +00:00
destination = account . getServer ( ) . toString ( ) ;
2015-11-29 14:44:45 +00:00
} else {
2015-11-29 15:31:51 +00:00
destination = account . getHostname ( ) ;
2017-06-17 17:54:09 +00:00
this . verifiedHostname = destination ;
2015-11-29 14:44:45 +00:00
}
2016-09-10 19:16:14 +00:00
Log . d ( Config . LOGTAG , account . getJid ( ) . toBareJid ( ) + " : connect to " + destination + " via Tor " ) ;
2016-11-19 09:44:40 +00:00
localSocket = SocksSocketFactory . createSocketOverTor ( destination , account . getPort ( ) ) ;
try {
startXmpp ( localSocket ) ;
} catch ( InterruptedException e ) {
Log . d ( Config . LOGTAG , account . getJid ( ) . toBareJid ( ) + " : thread was interrupted before beginning stream " ) ;
return ;
} catch ( Exception e ) {
throw new IOException ( e . getMessage ( ) ) ;
}
2017-06-17 17:54:09 +00:00
} else if ( extended & & ! account . getHostname ( ) . isEmpty ( ) ) {
2016-08-10 10:34:05 +00:00
2017-06-17 17:54:09 +00:00
this . verifiedHostname = account . getHostname ( ) ;
2016-01-25 20:17:53 +00:00
try {
2017-07-16 19:02:56 +00:00
InetSocketAddress address = new InetSocketAddress ( this . verifiedHostname , account . getPort ( ) ) ;
features . encryptionEnabled = address . getPort ( ) = = 5223 ;
2016-08-10 10:34:05 +00:00
if ( features . encryptionEnabled ) {
try {
final TlsFactoryVerifier tlsFactoryVerifier = getTlsFactoryVerifier ( ) ;
2016-11-19 09:44:40 +00:00
localSocket = tlsFactoryVerifier . factory . createSocket ( ) ;
localSocket . connect ( address , Config . SOCKET_TIMEOUT * 1000 ) ;
final SSLSession session = ( ( SSLSocket ) localSocket ) . getSession ( ) ;
2017-06-17 17:54:09 +00:00
final String domain = account . getJid ( ) . getDomainpart ( ) ;
if ( ! tlsFactoryVerifier . verifier . verify ( domain , this . verifiedHostname , session ) ) {
2016-08-10 10:34:05 +00:00
Log . d ( Config . LOGTAG , account . getJid ( ) . toBareJid ( ) + " : TLS certificate verification failed " ) ;
2017-05-05 07:33:05 +00:00
throw new StateChangingException ( Account . State . TLS_ERROR ) ;
2016-08-10 10:34:05 +00:00
}
} catch ( KeyManagementException e ) {
2017-07-16 19:02:56 +00:00
throw new StateChangingException ( Account . State . TLS_ERROR ) ;
2016-08-10 10:34:05 +00:00
}
} else {
2016-11-19 09:44:40 +00:00
localSocket = new Socket ( ) ;
localSocket . connect ( address , Config . SOCKET_TIMEOUT * 1000 ) ;
2016-08-10 10:34:05 +00:00
}
2017-07-16 19:02:56 +00:00
} catch ( IOException | IllegalArgumentException e ) {
2016-01-25 20:17:53 +00:00
throw new UnknownHostException ( ) ;
}
2016-11-19 09:44:40 +00:00
try {
startXmpp ( localSocket ) ;
} catch ( InterruptedException e ) {
Log . d ( Config . LOGTAG , account . getJid ( ) . toBareJid ( ) + " : thread was interrupted before beginning stream " ) ;
return ;
} catch ( Exception e ) {
throw new IOException ( e . getMessage ( ) ) ;
}
2017-06-21 21:28:01 +00:00
} else if ( IP . matches ( account . getServer ( ) . toString ( ) ) ) {
2016-11-19 09:44:40 +00:00
localSocket = new Socket ( ) ;
2015-06-19 14:25:08 +00:00
try {
2016-11-19 09:44:40 +00:00
localSocket . connect ( new InetSocketAddress ( account . getServer ( ) . toString ( ) , 5222 ) , Config . SOCKET_TIMEOUT * 1000 ) ;
2015-06-19 14:25:08 +00:00
} catch ( IOException e ) {
throw new UnknownHostException ( ) ;
}
2016-11-19 09:44:40 +00:00
try {
startXmpp ( localSocket ) ;
} catch ( InterruptedException e ) {
Log . d ( Config . LOGTAG , account . getJid ( ) . toBareJid ( ) + " : thread was interrupted before beginning stream " ) ;
return ;
} catch ( Exception e ) {
throw new IOException ( e . getMessage ( ) ) ;
}
2015-06-19 14:25:08 +00:00
} else {
2017-06-21 21:28:01 +00:00
List < Resolver . Result > results = Resolver . resolve ( account . getJid ( ) . getDomainpart ( ) ) ;
for ( Iterator < Resolver . Result > iterator = results . iterator ( ) ; iterator . hasNext ( ) ; ) {
final Resolver . Result result = iterator . next ( ) ;
2016-05-12 19:54:42 +00:00
if ( Thread . currentThread ( ) . isInterrupted ( ) ) {
2016-08-19 19:47:08 +00:00
Log . d ( Config . LOGTAG , account . getJid ( ) . toBareJid ( ) + " : Thread was interrupted " ) ;
2016-05-12 19:54:42 +00:00
return ;
}
2015-09-19 15:31:24 +00:00
try {
2016-01-11 22:25:16 +00:00
// if tls is true, encryption is implied and must not be started
2017-06-21 21:28:01 +00:00
features . encryptionEnabled = result . isDirectTls ( ) ;
2017-06-17 17:54:09 +00:00
verifiedHostname = result . isAuthenticated ( ) ? result . getHostname ( ) . toString ( ) : null ;
2015-09-19 15:31:24 +00:00
final InetSocketAddress addr ;
2017-06-21 21:28:01 +00:00
if ( result . getIp ( ) ! = null ) {
addr = new InetSocketAddress ( result . getIp ( ) , result . getPort ( ) ) ;
2015-09-19 15:31:24 +00:00
Log . d ( Config . LOGTAG , account . getJid ( ) . toBareJid ( ) . toString ( )
2017-06-21 21:28:01 +00:00
+ " : using values from dns " + result . getHostname ( ) . toString ( )
2017-06-21 22:09:59 +00:00
+ " / " + result . getIp ( ) . getHostAddress ( ) + " : " + result . getPort ( ) + " tls: " + features . encryptionEnabled ) ;
2015-09-19 15:31:24 +00:00
} else {
2017-07-10 06:50:01 +00:00
addr = new InetSocketAddress ( IDN . toASCII ( result . getHostname ( ) . toString ( ) ) , result . getPort ( ) ) ;
2015-09-19 15:31:24 +00:00
Log . d ( Config . LOGTAG , account . getJid ( ) . toBareJid ( ) . toString ( )
+ " : using values from dns "
2017-06-21 21:28:01 +00:00
+ result . getHostname ( ) . toString ( ) + " : " + result . getPort ( ) + " tls: " + features . encryptionEnabled ) ;
2015-09-19 15:31:24 +00:00
}
2016-01-11 22:25:16 +00:00
if ( ! features . encryptionEnabled ) {
2016-11-19 09:44:40 +00:00
localSocket = new Socket ( ) ;
localSocket . connect ( addr , Config . SOCKET_TIMEOUT * 1000 ) ;
2016-01-11 22:25:16 +00:00
} else {
final TlsFactoryVerifier tlsFactoryVerifier = getTlsFactoryVerifier ( ) ;
2016-11-19 09:44:40 +00:00
localSocket = tlsFactoryVerifier . factory . createSocket ( ) ;
2016-01-11 22:25:16 +00:00
2016-11-19 09:44:40 +00:00
if ( localSocket = = null ) {
2016-01-11 22:25:16 +00:00
throw new IOException ( " could not initialize ssl socket " ) ;
}
2016-11-19 09:44:40 +00:00
SSLSocketHelper . setSecurity ( ( SSLSocket ) localSocket ) ;
SSLSocketHelper . setSNIHost ( tlsFactoryVerifier . factory , ( SSLSocket ) localSocket , account . getServer ( ) . getDomainpart ( ) ) ;
SSLSocketHelper . setAlpnProtocol ( tlsFactoryVerifier . factory , ( SSLSocket ) localSocket , " xmpp-client " ) ;
2016-01-11 22:25:16 +00:00
2016-11-19 09:44:40 +00:00
localSocket . connect ( addr , Config . SOCKET_TIMEOUT * 1000 ) ;
2016-01-11 22:25:16 +00:00
2017-07-13 17:27:36 +00:00
if ( ! tlsFactoryVerifier . verifier . verify ( account . getServer ( ) . getDomainpart ( ) , verifiedHostname , ( ( SSLSocket ) localSocket ) . getSession ( ) ) ) {
2016-01-11 22:25:16 +00:00
Log . d ( Config . LOGTAG , account . getJid ( ) . toBareJid ( ) + " : TLS certificate verification failed " ) ;
2017-07-11 21:25:59 +00:00
if ( ! iterator . hasNext ( ) ) {
throw new StateChangingException ( Account . State . TLS_ERROR ) ;
}
2016-01-11 22:25:16 +00:00
}
}
2016-11-19 11:20:31 +00:00
if ( startXmpp ( localSocket ) ) {
2016-01-11 22:25:16 +00:00
break ; // successfully connected to server that speaks xmpp
2016-11-19 11:20:31 +00:00
} else {
localSocket . close ( ) ;
}
2017-05-05 07:33:05 +00:00
} catch ( final StateChangingException e ) {
2016-01-12 22:42:47 +00:00
throw e ;
2016-11-19 09:44:40 +00:00
} catch ( InterruptedException e ) {
Log . d ( Config . LOGTAG , account . getJid ( ) . toBareJid ( ) + " : thread was interrupted before beginning stream " ) ;
return ;
2015-09-19 15:31:24 +00:00
} catch ( final Throwable e ) {
2016-08-19 19:47:08 +00:00
Log . d ( Config . LOGTAG , account . getJid ( ) . toBareJid ( ) . toString ( ) + " : " + e . getMessage ( ) + " ( " + e . getClass ( ) . getName ( ) + " ) " ) ;
2016-01-11 18:05:25 +00:00
if ( ! iterator . hasNext ( ) ) {
throw new UnknownHostException ( ) ;
}
2014-10-16 21:31:48 +00:00
}
2015-09-19 15:31:24 +00:00
}
2014-02-07 05:52:09 +00:00
}
2016-01-11 22:25:16 +00:00
processStream ( ) ;
2017-05-05 07:33:05 +00:00
} catch ( final SecurityException e ) {
2016-08-19 19:47:08 +00:00
this . changeStatus ( Account . State . MISSING_INTERNET_PERMISSION ) ;
2017-05-05 07:33:05 +00:00
} catch ( final StateChangingException e ) {
this . changeStatus ( e . state ) ;
2014-12-21 20:43:58 +00:00
} catch ( final UnknownHostException | ConnectException e ) {
2014-11-18 00:48:16 +00:00
this . changeStatus ( Account . State . SERVER_NOT_FOUND ) ;
2015-11-30 15:01:48 +00:00
} catch ( final SocksSocketFactory . SocksProxyNotFoundException e ) {
this . changeStatus ( Account . State . TOR_NOT_AVAILABLE ) ;
2014-12-21 20:43:58 +00:00
} catch ( final IOException | XmlPullParserException | NoSuchAlgorithmException e ) {
2014-11-09 15:57:22 +00:00
Log . d ( Config . LOGTAG , account . getJid ( ) . toBareJid ( ) . toString ( ) + " : " + e . getMessage ( ) ) ;
2014-11-15 16:09:02 +00:00
this . changeStatus ( Account . State . OFFLINE ) ;
2016-05-10 15:48:09 +00:00
this . attempt = Math . max ( 0 , this . attempt - 1 ) ;
2014-11-18 00:48:16 +00:00
} finally {
2016-11-19 09:44:40 +00:00
if ( ! Thread . currentThread ( ) . isInterrupted ( ) ) {
forceCloseSocket ( ) ;
if ( wakeLock . isHeld ( ) ) {
try {
wakeLock . release ( ) ;
} catch ( final RuntimeException ignored ) {
}
2014-08-31 14:28:21 +00:00
}
2016-11-19 09:44:40 +00:00
} else {
2017-06-19 18:02:41 +00:00
Log . d ( Config . LOGTAG , account . getJid ( ) . toBareJid ( ) + " : not force closing socket and releasing wake lock (is held= " + wakeLock . isHeld ( ) + " ) because thread was interrupted " ) ;
2014-03-06 02:30:03 +00:00
}
2014-11-12 20:35:44 +00:00
}
}
2014-02-01 00:25:56 +00:00
2016-01-11 22:25:16 +00:00
/ * *
* Starts xmpp protocol , call after connecting to socket
* @return true if server returns with valid xmpp , false otherwise
* /
2017-05-06 13:37:22 +00:00
private boolean startXmpp ( Socket socket ) throws Exception {
2016-11-19 09:44:40 +00:00
if ( Thread . currentThread ( ) . isInterrupted ( ) ) {
throw new InterruptedException ( ) ;
}
this . socket = socket ;
2017-02-13 12:01:00 +00:00
tagReader = new XmlReader ( wakeLock ) ;
2017-02-17 09:26:42 +00:00
if ( tagWriter ! = null ) {
tagWriter . forceClose ( ) ;
}
2017-02-13 12:01:00 +00:00
tagWriter = new TagWriter ( ) ;
2016-01-11 22:25:16 +00:00
tagWriter . setOutputStream ( socket . getOutputStream ( ) ) ;
tagReader . setInputStream ( socket . getInputStream ( ) ) ;
tagWriter . beginDocument ( ) ;
sendStartStream ( ) ;
2016-11-19 09:44:40 +00:00
final Tag tag = tagReader . readTag ( ) ;
return tag ! = null & & tag . isStart ( " stream " ) ;
2016-01-11 22:25:16 +00:00
}
private static class TlsFactoryVerifier {
private final SSLSocketFactory factory ;
2017-06-17 17:54:09 +00:00
private final DomainHostnameVerifier verifier ;
2016-01-11 22:25:16 +00:00
2017-06-17 17:54:09 +00:00
public TlsFactoryVerifier ( final SSLSocketFactory factory , final DomainHostnameVerifier verifier ) throws IOException {
2016-01-11 22:25:16 +00:00
this . factory = factory ;
this . verifier = verifier ;
if ( factory = = null | | verifier = = null ) {
throw new IOException ( " could not setup ssl " ) ;
}
}
}
private TlsFactoryVerifier getTlsFactoryVerifier ( ) throws NoSuchAlgorithmException , KeyManagementException , IOException {
2016-02-03 17:17:16 +00:00
final SSLContext sc = SSLSocketHelper . getSSLContext ( ) ;
2016-01-11 22:25:16 +00:00
MemorizingTrustManager trustManager = this . mXmppConnectionService . getMemorizingTrustManager ( ) ;
KeyManager [ ] keyManager ;
if ( account . getPrivateKeyAlias ( ) ! = null & & account . getPassword ( ) . isEmpty ( ) ) {
2016-10-07 08:04:36 +00:00
keyManager = new KeyManager [ ] { new MyKeyManager ( ) } ;
2016-01-11 22:25:16 +00:00
} else {
keyManager = null ;
}
2016-12-05 20:52:44 +00:00
String domain = account . getJid ( ) . getDomainpart ( ) ;
sc . init ( keyManager , new X509TrustManager [ ] { mInteractive ? trustManager . getInteractive ( domain ) : trustManager . getNonInteractive ( domain ) } , mXmppConnectionService . getRNG ( ) ) ;
2016-01-11 22:25:16 +00:00
final SSLSocketFactory factory = sc . getSocketFactory ( ) ;
2017-06-17 17:54:09 +00:00
final DomainHostnameVerifier verifier = trustManager . wrapHostnameVerifier ( new XmppDomainVerifier ( ) , mInteractive ) ;
2016-01-11 22:25:16 +00:00
return new TlsFactoryVerifier ( factory , verifier ) ;
}
2014-02-01 00:25:56 +00:00
@Override
public void run ( ) {
2016-02-14 12:20:23 +00:00
forceCloseSocket ( ) ;
2014-03-06 02:30:03 +00:00
connect ( ) ;
2014-01-30 15:42:35 +00:00
}
2015-10-13 21:34:09 +00:00
private void processStream ( ) throws XmlPullParserException , IOException , NoSuchAlgorithmException {
2015-12-17 14:19:58 +00:00
Tag nextTag = tagReader . readTag ( ) ;
while ( nextTag ! = null & & ! nextTag . isEnd ( " stream " ) ) {
if ( nextTag . isStart ( " error " ) ) {
processStreamError ( nextTag ) ;
} else if ( nextTag . isStart ( " features " ) ) {
processStreamFeatures ( nextTag ) ;
} else if ( nextTag . isStart ( " proceed " ) ) {
switchOverToTls ( nextTag ) ;
} else if ( nextTag . isStart ( " success " ) ) {
final String challenge = tagReader . readElement ( nextTag ) . getContent ( ) ;
try {
saslMechanism . getResponse ( challenge ) ;
} catch ( final SaslMechanism . AuthenticationException e ) {
Log . e ( Config . LOGTAG , String . valueOf ( e ) ) ;
2017-05-05 07:33:05 +00:00
throw new StateChangingException ( Account . State . UNAUTHORIZED ) ;
2015-12-17 14:19:58 +00:00
}
Log . d ( Config . LOGTAG , account . getJid ( ) . toBareJid ( ) . toString ( ) + " : logged in " ) ;
account . setKey ( Account . PINNED_MECHANISM_KEY ,
String . valueOf ( saslMechanism . getPriority ( ) ) ) ;
tagReader . reset ( ) ;
sendStartStream ( ) ;
final Tag tag = tagReader . readTag ( ) ;
if ( tag ! = null & & tag . isStart ( " stream " ) ) {
processStream ( ) ;
} else {
throw new IOException ( " server didn't restart stream after successful auth " ) ;
}
break ;
} else if ( nextTag . isStart ( " failure " ) ) {
2016-08-16 08:39:59 +00:00
final Element failure = tagReader . readElement ( nextTag ) ;
2017-05-13 06:08:05 +00:00
if ( Namespace . SASL . equals ( failure . getNamespace ( ) ) ) {
final String text = failure . findChildContent ( " text " ) ;
if ( failure . hasChild ( " account-disabled " )
& & text ! = null
& & text . contains ( " renew " )
& & Config . MAGIC_CREATE_DOMAIN ! = null
& & text . contains ( Config . MAGIC_CREATE_DOMAIN ) ) {
throw new StateChangingException ( Account . State . PAYMENT_REQUIRED ) ;
} else {
throw new StateChangingException ( Account . State . UNAUTHORIZED ) ;
}
} else if ( Namespace . TLS . equals ( failure . getNamespace ( ) ) ) {
throw new StateChangingException ( Account . State . TLS_ERROR ) ;
2016-08-16 08:39:59 +00:00
} else {
2017-05-13 06:08:05 +00:00
throw new StateChangingException ( Account . State . INCOMPATIBLE_SERVER ) ;
2016-08-16 08:39:59 +00:00
}
2015-12-17 14:19:58 +00:00
} else if ( nextTag . isStart ( " challenge " ) ) {
final String challenge = tagReader . readElement ( nextTag ) . getContent ( ) ;
2017-05-13 06:08:05 +00:00
final Element response = new Element ( " response " , Namespace . SASL ) ;
2015-12-17 14:19:58 +00:00
try {
response . setContent ( saslMechanism . getResponse ( challenge ) ) ;
} catch ( final SaslMechanism . AuthenticationException e ) {
// TODO: Send auth abort tag.
Log . e ( Config . LOGTAG , e . toString ( ) ) ;
}
tagWriter . writeElement ( response ) ;
} else if ( nextTag . isStart ( " enabled " ) ) {
final Element enabled = tagReader . readElement ( nextTag ) ;
if ( " true " . equals ( enabled . getAttribute ( " resume " ) ) ) {
this . streamId = enabled . getAttribute ( " id " ) ;
Log . d ( Config . LOGTAG , account . getJid ( ) . toBareJid ( ) . toString ( )
2016-05-04 08:29:29 +00:00
+ " : stream management( " + smVersion
2015-12-17 14:19:58 +00:00
+ " ) enabled (resumable) " ) ;
} else {
Log . d ( Config . LOGTAG , account . getJid ( ) . toBareJid ( ) . toString ( )
+ " : stream management( " + smVersion + " ) enabled " ) ;
}
this . stanzasReceived = 0 ;
final RequestPacket r = new RequestPacket ( smVersion ) ;
tagWriter . writeStanzaAsync ( r ) ;
} else if ( nextTag . isStart ( " resumed " ) ) {
2017-03-21 17:08:20 +00:00
this . tagWriter . writeStanzaAsync ( new RequestPacket ( smVersion ) ) ;
2015-12-17 14:19:58 +00:00
lastPacketReceived = SystemClock . elapsedRealtime ( ) ;
final Element resumed = tagReader . readElement ( nextTag ) ;
final String h = resumed . getAttribute ( " h " ) ;
try {
ArrayList < AbstractAcknowledgeableStanza > failedStanzas = new ArrayList < > ( ) ;
2016-06-14 08:17:37 +00:00
synchronized ( this . mStanzaQueue ) {
final int serverCount = Integer . parseInt ( h ) ;
if ( serverCount ! = stanzasSent ) {
Log . d ( Config . LOGTAG , account . getJid ( ) . toBareJid ( ) . toString ( )
+ " : session resumed with lost packages " ) ;
stanzasSent = serverCount ;
} else {
Log . d ( Config . LOGTAG , account . getJid ( ) . toBareJid ( ) . toString ( ) + " : session resumed " ) ;
}
acknowledgeStanzaUpTo ( serverCount ) ;
for ( int i = 0 ; i < this . mStanzaQueue . size ( ) ; + + i ) {
failedStanzas . add ( mStanzaQueue . valueAt ( i ) ) ;
}
mStanzaQueue . clear ( ) ;
2015-12-17 14:19:58 +00:00
}
2016-06-14 08:17:37 +00:00
Log . d ( Config . LOGTAG , " resending " + failedStanzas . size ( ) + " stanzas " ) ;
for ( AbstractAcknowledgeableStanza packet : failedStanzas ) {
2015-12-17 14:19:58 +00:00
if ( packet instanceof MessagePacket ) {
MessagePacket message = ( MessagePacket ) packet ;
mXmppConnectionService . markMessage ( account ,
message . getTo ( ) . toBareJid ( ) ,
message . getId ( ) ,
Message . STATUS_UNSEND ) ;
2014-08-27 12:51:00 +00:00
}
2015-12-17 14:19:58 +00:00
sendPacket ( packet ) ;
}
} catch ( final NumberFormatException ignored ) {
}
Log . d ( Config . LOGTAG , account . getJid ( ) . toBareJid ( ) + " : online with resource " + account . getResource ( ) ) ;
changeStatus ( Account . State . ONLINE ) ;
} else if ( nextTag . isStart ( " r " ) ) {
tagReader . readElement ( nextTag ) ;
if ( Config . EXTENDED_SM_LOGGING ) {
Log . d ( Config . LOGTAG , account . getJid ( ) . toBareJid ( ) + " : acknowledging stanza # " + this . stanzasReceived ) ;
}
final AckPacket ack = new AckPacket ( this . stanzasReceived , smVersion ) ;
tagWriter . writeStanzaAsync ( ack ) ;
} else if ( nextTag . isStart ( " a " ) ) {
2017-05-19 11:30:57 +00:00
boolean accountUiNeedsRefresh = false ;
synchronized ( NotificationService . CATCHUP_LOCK ) {
2017-04-30 14:19:39 +00:00
if ( mWaitingForSmCatchup . compareAndSet ( true , false ) ) {
int count = mSmCatchupMessageCounter . get ( ) ;
Log . d ( Config . LOGTAG , account . getJid ( ) . toBareJid ( ) + " : SM catchup complete ( " + count + " ) " ) ;
2017-05-19 11:30:57 +00:00
accountUiNeedsRefresh = true ;
2017-04-30 14:19:39 +00:00
if ( count > 0 ) {
mXmppConnectionService . getNotificationService ( ) . finishBacklog ( true , account ) ;
}
2017-03-21 16:58:08 +00:00
}
}
2017-05-19 11:30:57 +00:00
if ( accountUiNeedsRefresh ) {
mXmppConnectionService . updateAccountUi ( ) ;
}
2015-12-17 14:19:58 +00:00
final Element ack = tagReader . readElement ( nextTag ) ;
lastPacketReceived = SystemClock . elapsedRealtime ( ) ;
try {
2016-06-14 08:17:37 +00:00
synchronized ( this . mStanzaQueue ) {
final int serverSequence = Integer . parseInt ( ack . getAttribute ( " h " ) ) ;
acknowledgeStanzaUpTo ( serverSequence ) ;
}
2016-05-08 19:45:18 +00:00
} catch ( NumberFormatException | NullPointerException e ) {
2015-12-17 14:19:58 +00:00
Log . d ( Config . LOGTAG , account . getJid ( ) . toBareJid ( ) + " : server send ack without sequence number " ) ;
}
} else if ( nextTag . isStart ( " failed " ) ) {
2016-05-08 19:45:18 +00:00
Element failed = tagReader . readElement ( nextTag ) ;
try {
final int serverCount = Integer . parseInt ( failed . getAttribute ( " h " ) ) ;
2016-05-08 19:53:45 +00:00
Log . d ( Config . LOGTAG , account . getJid ( ) . toBareJid ( ) + " : resumption failed but server acknowledged stanza # " + serverCount ) ;
2016-06-14 08:17:37 +00:00
synchronized ( this . mStanzaQueue ) {
acknowledgeStanzaUpTo ( serverCount ) ;
}
2016-05-08 19:45:18 +00:00
} catch ( NumberFormatException | NullPointerException e ) {
Log . d ( Config . LOGTAG , account . getJid ( ) . toBareJid ( ) + " : resumption failed " ) ;
}
2016-01-29 11:09:55 +00:00
resetStreamId ( ) ;
2017-05-06 13:37:22 +00:00
sendBindRequest ( ) ;
2015-12-17 14:19:58 +00:00
} else if ( nextTag . isStart ( " iq " ) ) {
processIq ( nextTag ) ;
} else if ( nextTag . isStart ( " message " ) ) {
processMessage ( nextTag ) ;
} else if ( nextTag . isStart ( " presence " ) ) {
processPresence ( nextTag ) ;
}
nextTag = tagReader . readTag ( ) ;
}
2014-01-30 15:42:35 +00:00
}
2014-10-05 12:26:06 +00:00
2015-08-06 18:48:55 +00:00
private void acknowledgeStanzaUpTo ( int serverCount ) {
2015-08-13 16:25:10 +00:00
for ( int i = 0 ; i < mStanzaQueue . size ( ) ; + + i ) {
if ( serverCount > = mStanzaQueue . keyAt ( i ) ) {
2015-08-06 18:48:55 +00:00
if ( Config . EXTENDED_SM_LOGGING ) {
2015-08-13 16:25:10 +00:00
Log . d ( Config . LOGTAG , account . getJid ( ) . toBareJid ( ) + " : server acknowledged stanza # " + mStanzaQueue . keyAt ( i ) ) ;
2015-08-06 18:48:55 +00:00
}
2015-08-13 16:25:10 +00:00
AbstractAcknowledgeableStanza stanza = mStanzaQueue . valueAt ( i ) ;
if ( stanza instanceof MessagePacket & & acknowledgedListener ! = null ) {
MessagePacket packet = ( MessagePacket ) stanza ;
acknowledgedListener . onMessageAcknowledged ( account , packet . getId ( ) ) ;
2015-08-06 18:48:55 +00:00
}
2015-08-13 16:25:10 +00:00
mStanzaQueue . removeAt ( i ) ;
2015-08-06 18:48:55 +00:00
i - - ;
}
}
}
2014-12-21 20:43:58 +00:00
private Element processPacket ( final Tag currentTag , final int packetType )
2014-11-12 20:35:44 +00:00
throws XmlPullParserException , IOException {
2014-01-30 23:33:01 +00:00
Element element ;
switch ( packetType ) {
2014-11-03 09:03:45 +00:00
case PACKET_IQ :
element = new IqPacket ( ) ;
break ;
case PACKET_MESSAGE :
element = new MessagePacket ( ) ;
break ;
case PACKET_PRESENCE :
element = new PresencePacket ( ) ;
break ;
default :
return null ;
2014-01-30 15:42:35 +00:00
}
2014-01-30 23:33:01 +00:00
element . setAttributes ( currentTag . getAttributes ( ) ) ;
2014-01-30 15:42:35 +00:00
Tag nextTag = tagReader . readTag ( ) ;
2014-08-31 14:28:21 +00:00
if ( nextTag = = null ) {
2014-05-23 08:15:58 +00:00
throw new IOException ( " interrupted mid tag " ) ;
}
2014-02-09 13:10:52 +00:00
while ( ! nextTag . isEnd ( element . getName ( ) ) ) {
2014-01-30 23:33:01 +00:00
if ( ! nextTag . isNo ( ) ) {
2014-12-21 20:43:58 +00:00
final Element child = tagReader . readElement ( nextTag ) ;
final String type = currentTag . getAttribute ( " type " ) ;
2014-10-05 12:26:06 +00:00
if ( packetType = = PACKET_IQ
& & " jingle " . equals ( child . getName ( ) )
& & ( " set " . equalsIgnoreCase ( type ) | | " get "
2014-11-12 20:35:44 +00:00
. equalsIgnoreCase ( type ) ) ) {
2014-03-27 01:02:59 +00:00
element = new JinglePacket ( ) ;
element . setAttributes ( currentTag . getAttributes ( ) ) ;
2014-11-12 20:35:44 +00:00
}
2014-01-30 23:33:01 +00:00
element . addChild ( child ) ;
}
2014-01-30 15:42:35 +00:00
nextTag = tagReader . readTag ( ) ;
2014-08-31 14:28:21 +00:00
if ( nextTag = = null ) {
2014-05-23 08:15:58 +00:00
throw new IOException ( " interrupted mid tag " ) ;
}
2014-01-30 15:42:35 +00:00
}
2015-05-06 02:29:45 +00:00
if ( stanzasReceived = = Integer . MAX_VALUE ) {
resetStreamId ( ) ;
throw new IOException ( " time to restart the session. cant handle >2 billion pcks " ) ;
}
2014-03-10 18:22:13 +00:00
+ + stanzasReceived ;
2015-01-05 15:17:05 +00:00
lastPacketReceived = SystemClock . elapsedRealtime ( ) ;
2016-06-05 00:04:31 +00:00
if ( Config . BACKGROUND_STANZA_LOGGING & & mXmppConnectionService . checkListeners ( ) ) {
Log . d ( Config . LOGTAG , " [background stanza] " + element ) ;
}
2014-01-30 23:33:01 +00:00
return element ;
}
2015-01-04 14:40:09 +00:00
private void processIq ( final Tag currentTag ) throws XmlPullParserException , IOException {
final IqPacket packet = ( IqPacket ) processPacket ( currentTag , PACKET_IQ ) ;
2014-05-21 20:22:36 +00:00
2015-01-04 14:40:09 +00:00
if ( packet . getId ( ) = = null ) {
return ; // an iq packet without id is definitely invalid
}
2014-05-21 20:22:36 +00:00
2015-01-04 14:40:09 +00:00
if ( packet instanceof JinglePacket ) {
if ( this . jingleListener ! = null ) {
this . jingleListener . onJinglePacketReceived ( account , ( JinglePacket ) packet ) ;
}
} else {
2015-08-26 12:01:37 +00:00
OnIqPacketReceived callback = null ;
2015-08-16 10:12:22 +00:00
synchronized ( this . packetCallbacks ) {
if ( packetCallbacks . containsKey ( packet . getId ( ) ) ) {
final Pair < IqPacket , OnIqPacketReceived > packetCallbackDuple = packetCallbacks . get ( packet . getId ( ) ) ;
// Packets to the server should have responses from the server
if ( packetCallbackDuple . first . toServer ( account ) ) {
2017-01-12 22:17:52 +00:00
if ( packet . fromServer ( account ) ) {
2015-08-26 12:01:37 +00:00
callback = packetCallbackDuple . second ;
2015-08-16 10:12:22 +00:00
packetCallbacks . remove ( packet . getId ( ) ) ;
} else {
Log . e ( Config . LOGTAG , account . getJid ( ) . toBareJid ( ) . toString ( ) + " : ignoring spoofed iq packet " ) ;
}
2015-01-04 14:40:09 +00:00
} else {
2015-08-16 10:12:22 +00:00
if ( packet . getFrom ( ) . equals ( packetCallbackDuple . first . getTo ( ) ) ) {
2015-08-26 12:01:37 +00:00
callback = packetCallbackDuple . second ;
2015-08-16 10:12:22 +00:00
packetCallbacks . remove ( packet . getId ( ) ) ;
} else {
Log . e ( Config . LOGTAG , account . getJid ( ) . toBareJid ( ) . toString ( ) + " : ignoring spoofed iq packet " ) ;
}
2015-01-04 14:40:09 +00:00
}
2015-08-16 10:12:22 +00:00
} else if ( packet . getType ( ) = = IqPacket . TYPE . GET | | packet . getType ( ) = = IqPacket . TYPE . SET ) {
2015-08-26 12:01:37 +00:00
callback = this . unregisteredIqListener ;
2015-01-04 14:40:09 +00:00
}
}
2015-08-26 12:01:37 +00:00
if ( callback ! = null ) {
2017-05-05 07:33:05 +00:00
try {
callback . onIqPacketReceived ( account , packet ) ;
} catch ( StateChangingError error ) {
throw new StateChangingException ( error . state ) ;
}
2015-08-26 12:01:37 +00:00
}
2015-01-04 14:40:09 +00:00
}
2014-01-30 23:33:01 +00:00
}
2014-02-09 13:10:52 +00:00
2015-01-04 11:09:39 +00:00
private void processMessage ( final Tag currentTag ) throws XmlPullParserException , IOException {
final MessagePacket packet = ( MessagePacket ) processPacket ( currentTag , PACKET_MESSAGE ) ;
this . messageListener . onMessagePacketReceived ( account , packet ) ;
2014-01-30 23:33:01 +00:00
}
2014-02-09 13:10:52 +00:00
2015-01-04 11:09:39 +00:00
private void processPresence ( final Tag currentTag ) throws XmlPullParserException , IOException {
PresencePacket packet = ( PresencePacket ) processPacket ( currentTag , PACKET_PRESENCE ) ;
this . presenceListener . onPresencePacketReceived ( account , packet ) ;
2014-01-30 15:42:35 +00:00
}
2014-03-10 18:22:13 +00:00
private void sendStartTLS ( ) throws IOException {
2014-12-21 20:43:58 +00:00
final Tag startTLS = Tag . empty ( " starttls " ) ;
2017-05-13 06:08:05 +00:00
startTLS . setAttribute ( " xmlns " , Namespace . TLS ) ;
2014-02-04 14:09:50 +00:00
tagWriter . writeTag ( startTLS ) ;
2014-01-30 15:42:35 +00:00
}
2016-01-11 22:25:16 +00:00
2014-12-30 00:17:11 +00:00
private void switchOverToTls ( final Tag currentTag ) throws XmlPullParserException , IOException {
2014-12-30 13:16:25 +00:00
tagReader . readTag ( ) ;
try {
2016-01-11 22:25:16 +00:00
final TlsFactoryVerifier tlsFactoryVerifier = getTlsFactoryVerifier ( ) ;
2014-12-30 13:16:25 +00:00
final InetAddress address = socket = = null ? null : socket . getInetAddress ( ) ;
2014-12-30 00:17:11 +00:00
2016-01-11 22:25:16 +00:00
if ( address = = null ) {
2014-12-30 00:17:11 +00:00
throw new IOException ( " could not setup ssl " ) ;
}
2014-11-03 09:03:45 +00:00
2016-01-11 22:25:16 +00:00
final SSLSocket sslSocket = ( SSLSocket ) tlsFactoryVerifier . factory . createSocket ( socket , address . getHostAddress ( ) , socket . getPort ( ) , true ) ;
2014-11-08 19:52:02 +00:00
2014-12-30 00:17:11 +00:00
if ( sslSocket = = null ) {
throw new IOException ( " could not initialize ssl socket " ) ;
}
2014-11-16 11:00:53 +00:00
2016-01-12 15:33:15 +00:00
SSLSocketHelper . setSecurity ( sslSocket ) ;
2015-01-17 18:40:15 +00:00
2017-06-17 17:54:09 +00:00
if ( ! tlsFactoryVerifier . verifier . verify ( account . getServer ( ) . getDomainpart ( ) , this . verifiedHostname , sslSocket . getSession ( ) ) ) {
2014-12-30 13:16:25 +00:00
Log . d ( Config . LOGTAG , account . getJid ( ) . toBareJid ( ) + " : TLS certificate verification failed " ) ;
2017-05-05 07:33:05 +00:00
throw new StateChangingException ( Account . State . TLS_ERROR ) ;
2014-12-30 00:17:11 +00:00
}
tagReader . setInputStream ( sslSocket . getInputStream ( ) ) ;
tagWriter . setOutputStream ( sslSocket . getOutputStream ( ) ) ;
sendStartStream ( ) ;
Log . d ( Config . LOGTAG , account . getJid ( ) . toBareJid ( ) + " : TLS connection established " ) ;
2015-01-05 15:17:05 +00:00
features . encryptionEnabled = true ;
2015-10-29 13:38:35 +00:00
final Tag tag = tagReader . readTag ( ) ;
if ( tag ! = null & & tag . isStart ( " stream " ) ) {
2015-10-13 21:34:09 +00:00
processStream ( ) ;
} else {
throw new IOException ( " server didn't restart stream after STARTTLS " ) ;
}
2014-12-30 00:17:11 +00:00
sslSocket . close ( ) ;
2014-12-30 13:16:25 +00:00
} catch ( final NoSuchAlgorithmException | KeyManagementException e1 ) {
2015-10-11 18:45:01 +00:00
Log . d ( Config . LOGTAG , account . getJid ( ) . toBareJid ( ) + " : TLS certificate verification failed " ) ;
2017-05-05 07:33:05 +00:00
throw new StateChangingException ( Account . State . TLS_ERROR ) ;
2014-12-30 13:16:25 +00:00
}
2014-11-12 20:35:44 +00:00
}
2014-01-30 15:42:35 +00:00
2014-12-21 20:43:58 +00:00
private void processStreamFeatures ( final Tag currentTag )
2014-11-12 20:35:44 +00:00
throws XmlPullParserException , IOException {
2014-02-01 00:25:56 +00:00
this . streamFeatures = tagReader . readElement ( currentTag ) ;
2015-01-05 15:17:05 +00:00
if ( this . streamFeatures . hasChild ( " starttls " ) & & ! features . encryptionEnabled ) {
2014-02-01 00:25:56 +00:00
sendStartTLS ( ) ;
2016-01-15 13:26:23 +00:00
} else if ( this . streamFeatures . hasChild ( " register " ) & & account . isOptionSet ( Account . OPTION_REGISTER ) ) {
2016-01-22 10:20:31 +00:00
if ( features . encryptionEnabled | | Config . ALLOW_NON_TLS_CONNECTIONS ) {
2016-01-15 13:26:23 +00:00
sendRegistryRequest ( ) ;
} else {
2017-05-05 07:33:05 +00:00
throw new StateChangingException ( Account . State . INCOMPATIBLE_SERVER ) ;
2016-01-15 13:26:23 +00:00
}
2016-11-19 09:44:40 +00:00
} else if ( ! this . streamFeatures . hasChild ( " register " ) & & account . isOptionSet ( Account . OPTION_REGISTER ) ) {
2017-05-05 07:33:05 +00:00
throw new StateChangingException ( Account . State . REGISTRATION_NOT_SUPPORTED ) ;
2014-02-09 13:10:52 +00:00
} else if ( this . streamFeatures . hasChild ( " mechanisms " )
2016-01-22 10:20:31 +00:00
& & shouldAuthenticate
& & ( features . encryptionEnabled | | Config . ALLOW_NON_TLS_CONNECTIONS ) ) {
2016-04-09 22:18:14 +00:00
authenticate ( ) ;
2015-08-23 15:29:31 +00:00
} else if ( this . streamFeatures . hasChild ( " sm " , " urn:xmpp:sm: " + smVersion ) & & streamId ! = null ) {
2015-04-29 14:15:07 +00:00
if ( Config . EXTENDED_SM_LOGGING ) {
Log . d ( Config . LOGTAG , account . getJid ( ) . toBareJid ( ) + " : resuming after stanza # " + stanzasReceived ) ;
}
final ResumePacket resume = new ResumePacket ( this . streamId , stanzasReceived , smVersion ) ;
2017-03-21 16:58:08 +00:00
this . mSmCatchupMessageCounter . set ( 0 ) ;
this . mWaitingForSmCatchup . set ( true ) ;
2014-03-10 18:22:13 +00:00
this . tagWriter . writeStanzaAsync ( resume ) ;
2015-08-23 15:26:50 +00:00
} else if ( needsBinding ) {
if ( this . streamFeatures . hasChild ( " bind " ) ) {
sendBindRequest ( ) ;
} else {
2017-05-05 07:33:05 +00:00
throw new StateChangingException ( Account . State . INCOMPATIBLE_SERVER ) ;
2015-08-23 15:26:50 +00:00
}
2014-03-10 18:22:13 +00:00
}
}
2014-02-09 13:10:52 +00:00
2016-04-09 22:18:14 +00:00
private void authenticate ( ) throws IOException {
final List < String > mechanisms = extractMechanisms ( streamFeatures
. findChild ( " mechanisms " ) ) ;
2017-05-13 06:08:05 +00:00
final Element auth = new Element ( " auth " , Namespace . SASL ) ;
2016-04-09 22:18:14 +00:00
if ( mechanisms . contains ( " EXTERNAL " ) & & account . getPrivateKeyAlias ( ) ! = null ) {
saslMechanism = new External ( tagWriter , account , mXmppConnectionService . getRNG ( ) ) ;
2017-01-12 20:26:58 +00:00
} else if ( mechanisms . contains ( " SCRAM-SHA-256 " ) ) {
saslMechanism = new ScramSha256 ( tagWriter , account , mXmppConnectionService . getRNG ( ) ) ;
2016-04-09 22:18:14 +00:00
} else if ( mechanisms . contains ( " SCRAM-SHA-1 " ) ) {
saslMechanism = new ScramSha1 ( tagWriter , account , mXmppConnectionService . getRNG ( ) ) ;
} else if ( mechanisms . contains ( " PLAIN " ) ) {
saslMechanism = new Plain ( tagWriter , account ) ;
} else if ( mechanisms . contains ( " DIGEST-MD5 " ) ) {
saslMechanism = new DigestMd5 ( tagWriter , account , mXmppConnectionService . getRNG ( ) ) ;
2016-08-07 14:46:01 +00:00
} else if ( mechanisms . contains ( " ANONYMOUS " ) ) {
saslMechanism = new Anonymous ( tagWriter , account , mXmppConnectionService . getRNG ( ) ) ;
2016-04-09 22:18:14 +00:00
}
if ( saslMechanism ! = null ) {
2016-10-13 09:27:26 +00:00
final int pinnedMechanism = account . getKeyAsInt ( Account . PINNED_MECHANISM_KEY , - 1 ) ;
if ( pinnedMechanism > saslMechanism . getPriority ( ) ) {
Log . e ( Config . LOGTAG , " Auth failed. Authentication mechanism " + saslMechanism . getMechanism ( ) +
" has lower priority ( " + String . valueOf ( saslMechanism . getPriority ( ) ) +
" ) than pinned priority ( " + pinnedMechanism +
" ). Possible downgrade attack? " ) ;
2017-05-05 07:33:05 +00:00
throw new StateChangingException ( Account . State . DOWNGRADE_ATTACK ) ;
2016-04-09 22:18:14 +00:00
}
Log . d ( Config . LOGTAG , account . getJid ( ) . toString ( ) + " : Authenticating with " + saslMechanism . getMechanism ( ) ) ;
auth . setAttribute ( " mechanism " , saslMechanism . getMechanism ( ) ) ;
if ( ! saslMechanism . getClientFirstMessage ( ) . isEmpty ( ) ) {
auth . setContent ( saslMechanism . getClientFirstMessage ( ) ) ;
}
tagWriter . writeElement ( auth ) ;
} else {
2017-05-05 07:33:05 +00:00
throw new StateChangingException ( Account . State . INCOMPATIBLE_SERVER ) ;
2016-04-09 22:18:14 +00:00
}
}
2014-12-21 20:43:58 +00:00
private List < String > extractMechanisms ( final Element stream ) {
final ArrayList < String > mechanisms = new ArrayList < > ( stream
2014-05-21 20:22:36 +00:00
. getChildren ( ) . size ( ) ) ;
2014-12-21 20:43:58 +00:00
for ( final Element child : stream . getChildren ( ) ) {
2014-03-30 19:21:55 +00:00
mechanisms . add ( child . getContent ( ) ) ;
}
return mechanisms ;
}
2014-03-14 17:56:52 +00:00
private void sendRegistryRequest ( ) {
2014-12-30 13:16:25 +00:00
final IqPacket register = new IqPacket ( IqPacket . TYPE . GET ) ;
2014-03-14 17:56:52 +00:00
register . query ( " jabber:iq:register " ) ;
register . setTo ( account . getServer ( ) ) ;
2016-08-25 11:50:54 +00:00
sendUnmodifiedIqPacket ( register , new OnIqPacketReceived ( ) {
2014-05-21 20:22:36 +00:00
2014-03-14 17:56:52 +00:00
@Override
2014-12-21 20:43:58 +00:00
public void onIqPacketReceived ( final Account account , final IqPacket packet ) {
2015-10-11 11:11:50 +00:00
boolean failed = false ;
2015-08-23 06:27:05 +00:00
if ( packet . getType ( ) = = IqPacket . TYPE . RESULT
& & packet . query ( ) . hasChild ( " username " )
2014-05-21 20:22:36 +00:00
& & ( packet . query ( ) . hasChild ( " password " ) ) ) {
2014-12-30 13:16:25 +00:00
final IqPacket register = new IqPacket ( IqPacket . TYPE . SET ) ;
2015-05-06 02:33:21 +00:00
final Element username = new Element ( " username " ) . setContent ( account . getUsername ( ) ) ;
final Element password = new Element ( " password " ) . setContent ( account . getPassword ( ) ) ;
2014-03-20 14:49:53 +00:00
register . query ( " jabber:iq:register " ) . addChild ( username ) ;
register . query ( ) . addChild ( password ) ;
2016-08-25 11:50:54 +00:00
register . setFrom ( account . getJid ( ) . toBareJid ( ) ) ;
sendUnmodifiedIqPacket ( register , registrationResponseListener ) ;
2015-10-11 11:11:50 +00:00
} else if ( packet . getType ( ) = = IqPacket . TYPE . RESULT
& & ( packet . query ( ) . hasChild ( " x " , " jabber:x:data " ) ) ) {
final Data data = Data . parse ( packet . query ( ) . findChild ( " x " , " jabber:x:data " ) ) ;
final Element blob = packet . query ( ) . findChild ( " data " , " urn:xmpp:bob " ) ;
final String id = packet . getId ( ) ;
Bitmap captcha = null ;
if ( blob ! = null ) {
try {
final String base64Blob = blob . getContent ( ) ;
final byte [ ] strBlob = Base64 . decode ( base64Blob , Base64 . DEFAULT ) ;
InputStream stream = new ByteArrayInputStream ( strBlob ) ;
captcha = BitmapFactory . decodeStream ( stream ) ;
} catch ( Exception e ) {
2015-10-12 10:36:54 +00:00
//ignored
2014-03-14 17:56:52 +00:00
}
2015-10-11 11:11:50 +00:00
} else {
try {
Field url = data . getFieldByName ( " url " ) ;
String urlString = url . findChildContent ( " value " ) ;
URL uri = new URL ( urlString ) ;
captcha = BitmapFactory . decodeStream ( uri . openConnection ( ) . getInputStream ( ) ) ;
2015-10-16 07:58:31 +00:00
} catch ( IOException e ) {
2015-10-11 11:11:50 +00:00
Log . e ( Config . LOGTAG , e . toString ( ) ) ;
}
}
if ( captcha ! = null ) {
failed = ! mXmppConnectionService . displayCaptchaRequest ( account , id , data , captcha ) ;
}
2014-03-14 17:56:52 +00:00
} else {
2015-10-11 11:11:50 +00:00
failed = true ;
}
if ( failed ) {
2017-06-19 18:02:41 +00:00
final Element query = packet . query ( ) ;
final String instructions = query . findChildContent ( " instructions " ) ;
final Element oob = query . findChild ( " x " , Namespace . OOB ) ;
final String url = oob = = null ? null : oob . findChildContent ( " url " ) ;
if ( url = = null & & instructions ! = null ) {
Matcher matcher = Patterns . AUTOLINK_WEB_URL . matcher ( instructions ) ;
if ( matcher . find ( ) ) {
setAccountCreationFailed ( instructions . substring ( matcher . start ( ) , matcher . end ( ) ) ) ;
} else {
setAccountCreationFailed ( null ) ;
}
} else {
setAccountCreationFailed ( url ) ;
}
2014-03-14 17:56:52 +00:00
}
}
} ) ;
}
2017-06-19 18:02:41 +00:00
private void setAccountCreationFailed ( String url ) {
if ( url ! = null & & ( url . toLowerCase ( ) . startsWith ( " http:// " ) | | url . toLowerCase ( ) . startsWith ( " https:// " ) ) ) {
changeStatus ( Account . State . REGISTRATION_WEB ) ;
this . webRegistrationUrl = url ;
} else {
changeStatus ( Account . State . REGISTRATION_FAILED ) ;
}
2015-10-11 11:11:50 +00:00
disconnect ( true ) ;
2017-06-19 18:02:41 +00:00
Log . d ( Config . LOGTAG , account . getJid ( ) . toBareJid ( ) + " : could not register. url= " + url ) ;
}
public String getWebRegistrationUrl ( ) {
return this . webRegistrationUrl ;
2015-10-11 11:11:50 +00:00
}
2016-01-16 18:21:11 +00:00
public void resetEverything ( ) {
2016-11-18 12:47:39 +00:00
resetAttemptCount ( true ) ;
2016-01-16 18:21:11 +00:00
resetStreamId ( ) ;
clearIqCallbacks ( ) ;
2016-01-23 10:40:32 +00:00
mStanzaQueue . clear ( ) ;
2017-06-19 18:02:41 +00:00
this . webRegistrationUrl = null ;
2016-01-16 18:21:11 +00:00
synchronized ( this . disco ) {
disco . clear ( ) ;
}
}
2014-12-21 20:43:58 +00:00
private void sendBindRequest ( ) {
2015-10-19 21:03:19 +00:00
while ( ! mXmppConnectionService . areMessagesInitialized ( ) & & socket ! = null & & ! socket . isClosed ( ) ) {
2015-02-12 17:53:00 +00:00
try {
Thread . sleep ( 500 ) ;
} catch ( final InterruptedException ignored ) {
}
}
2015-08-23 15:26:50 +00:00
needsBinding = false ;
2015-08-06 12:54:37 +00:00
clearIqCallbacks ( ) ;
2014-12-30 13:16:25 +00:00
final IqPacket iq = new IqPacket ( IqPacket . TYPE . SET ) ;
2014-05-21 20:22:36 +00:00
iq . addChild ( " bind " , " urn:ietf:params:xml:ns:xmpp-bind " )
2015-05-06 02:33:21 +00:00
. addChild ( " resource " ) . setContent ( account . getResource ( ) ) ;
2015-01-04 11:09:39 +00:00
this . sendUnmodifiedIqPacket ( iq , new OnIqPacketReceived ( ) {
2014-02-01 00:25:56 +00:00
@Override
2014-12-21 20:43:58 +00:00
public void onIqPacketReceived ( final Account account , final IqPacket packet ) {
2015-08-26 18:47:08 +00:00
if ( packet . getType ( ) = = IqPacket . TYPE . TIMEOUT ) {
return ;
}
2014-12-21 20:43:58 +00:00
final Element bind = packet . findChild ( " bind " ) ;
2015-08-26 18:47:08 +00:00
if ( bind ! = null & & packet . getType ( ) = = IqPacket . TYPE . RESULT ) {
2014-11-05 20:55:47 +00:00
final Element jid = bind . findChild ( " jid " ) ;
2014-09-22 14:04:37 +00:00
if ( jid ! = null & & jid . getContent ( ) ! = null ) {
2014-11-12 20:35:44 +00:00
try {
2016-09-14 10:26:38 +00:00
if ( account . setJid ( Jid . fromString ( jid . getContent ( ) ) ) ) {
Log . d ( Config . LOGTAG , account . getJid ( ) . toBareJid ( ) + " : bare jid changed during bind. updating database " ) ;
mXmppConnectionService . databaseBackend . updateAccount ( account ) ;
}
2016-05-31 21:09:45 +00:00
if ( streamFeatures . hasChild ( " session " )
& & ! streamFeatures . findChild ( " session " ) . hasChild ( " optional " ) ) {
2016-05-05 18:22:47 +00:00
sendStartSession ( ) ;
} else {
sendPostBindInitialization ( ) ;
}
return ;
2014-11-12 20:35:44 +00:00
} catch ( final InvalidJidException e ) {
2016-05-05 18:22:47 +00:00
Log . d ( Config . LOGTAG , account . getJid ( ) . toBareJid ( ) + " : server reported invalid jid ( " + jid . getContent ( ) + " ) on bind " ) ;
2014-07-26 13:44:32 +00:00
}
} else {
2016-03-11 08:01:40 +00:00
Log . d ( Config . LOGTAG , account . getJid ( ) + " : disconnecting because of bind failure. (no jid) " ) ;
2014-07-26 13:44:32 +00:00
}
} else {
2016-04-04 18:06:07 +00:00
Log . d ( Config . LOGTAG , account . getJid ( ) + " : disconnecting because of bind failure ( " + packet . toString ( ) ) ;
2014-02-04 14:09:50 +00:00
}
2017-05-25 12:40:59 +00:00
final Element error = packet . findChild ( " error " ) ;
final String resource = account . getResource ( ) . split ( " \\ . " ) [ 0 ] ;
if ( packet . getType ( ) = = IqPacket . TYPE . ERROR & & error ! = null & & error . hasChild ( " conflict " ) ) {
account . setResource ( resource + " . " + nextRandomId ( ) ) ;
} else {
account . setResource ( resource ) ;
}
2017-05-05 07:33:05 +00:00
throw new StateChangingError ( Account . State . BIND_FAILURE ) ;
2014-02-09 13:10:52 +00:00
}
} ) ;
2015-03-20 20:48:45 +00:00
}
2015-08-06 12:54:37 +00:00
private void clearIqCallbacks ( ) {
2015-08-23 15:53:23 +00:00
final IqPacket failurePacket = new IqPacket ( IqPacket . TYPE . TIMEOUT ) ;
2015-08-23 06:01:47 +00:00
final ArrayList < OnIqPacketReceived > callbacks = new ArrayList < > ( ) ;
2015-08-16 10:12:22 +00:00
synchronized ( this . packetCallbacks ) {
2015-08-23 06:27:05 +00:00
if ( this . packetCallbacks . size ( ) = = 0 ) {
return ;
}
Log . d ( Config . LOGTAG , account . getJid ( ) . toBareJid ( ) + " : clearing " + this . packetCallbacks . size ( ) + " iq callbacks " ) ;
2015-08-23 06:01:47 +00:00
final Iterator < Pair < IqPacket , OnIqPacketReceived > > iterator = this . packetCallbacks . values ( ) . iterator ( ) ;
2015-08-16 10:12:22 +00:00
while ( iterator . hasNext ( ) ) {
2015-08-23 06:01:47 +00:00
Pair < IqPacket , OnIqPacketReceived > entry = iterator . next ( ) ;
callbacks . add ( entry . second ) ;
2015-08-16 10:12:22 +00:00
iterator . remove ( ) ;
}
2015-08-06 12:54:37 +00:00
}
2015-08-23 06:01:47 +00:00
for ( OnIqPacketReceived callback : callbacks ) {
2017-07-03 17:44:01 +00:00
try {
callback . onIqPacketReceived ( account , failurePacket ) ;
} catch ( StateChangingError error ) {
Log . d ( Config . LOGTAG , account . getJid ( ) . toBareJid ( ) + " : caught StateChangingError( " + error . state . toString ( ) + " ) while clearing callbacks " ) ;
//ignore
}
2015-08-06 12:54:37 +00:00
}
2015-08-28 09:42:11 +00:00
Log . d ( Config . LOGTAG , account . getJid ( ) . toBareJid ( ) + " : done clearing iq callbacks. " + this . packetCallbacks . size ( ) + " left " ) ;
2015-08-06 12:54:37 +00:00
}
2015-12-15 18:14:38 +00:00
public void sendDiscoTimeout ( ) {
2016-07-12 22:20:57 +00:00
if ( mWaitForDisco . compareAndSet ( true , false ) ) {
2017-06-30 06:45:16 +00:00
Log . d ( Config . LOGTAG , account . getJid ( ) . toBareJid ( ) + " : finalizing bind after disco timeout " ) ;
2016-07-12 22:20:57 +00:00
finalizeBind ( ) ;
2015-12-15 18:14:38 +00:00
}
}
2015-03-20 20:48:45 +00:00
private void sendStartSession ( ) {
2016-05-31 21:09:45 +00:00
Log . d ( Config . LOGTAG , account . getJid ( ) . toBareJid ( ) + " : sending legacy session to outdated server " ) ;
2015-03-20 20:48:45 +00:00
final IqPacket startSession = new IqPacket ( IqPacket . TYPE . SET ) ;
2015-08-06 18:48:55 +00:00
startSession . addChild ( " session " , " urn:ietf:params:xml:ns:xmpp-session " ) ;
2015-03-20 20:48:45 +00:00
this . sendUnmodifiedIqPacket ( startSession , new OnIqPacketReceived ( ) {
@Override
public void onIqPacketReceived ( Account account , IqPacket packet ) {
2015-10-12 10:36:54 +00:00
if ( packet . getType ( ) = = IqPacket . TYPE . RESULT ) {
2015-03-20 20:48:45 +00:00
sendPostBindInitialization ( ) ;
2015-10-16 07:58:31 +00:00
} else if ( packet . getType ( ) ! = IqPacket . TYPE . TIMEOUT ) {
2017-05-05 07:33:05 +00:00
throw new StateChangingError ( Account . State . SESSION_FAILURE ) ;
2015-03-20 20:48:45 +00:00
}
}
} ) ;
}
private void sendPostBindInitialization ( ) {
smVersion = 0 ;
if ( streamFeatures . hasChild ( " sm " , " urn:xmpp:sm:3 " ) ) {
smVersion = 3 ;
} else if ( streamFeatures . hasChild ( " sm " , " urn:xmpp:sm:2 " ) ) {
smVersion = 2 ;
}
if ( smVersion ! = 0 ) {
2016-06-14 08:17:37 +00:00
synchronized ( this . mStanzaQueue ) {
2016-06-22 10:21:33 +00:00
final EnablePacket enable = new EnablePacket ( smVersion ) ;
tagWriter . writeStanzaAsync ( enable ) ;
2016-06-14 08:17:37 +00:00
stanzasSent = 0 ;
mStanzaQueue . clear ( ) ;
}
2015-03-20 20:48:45 +00:00
}
features . carbonsEnabled = false ;
features . blockListRequested = false ;
2015-10-19 21:03:19 +00:00
synchronized ( this . disco ) {
this . disco . clear ( ) ;
}
2017-02-19 12:05:40 +00:00
Log . d ( Config . LOGTAG , account . getJid ( ) . toBareJid ( ) + " : starting service discovery " ) ;
2016-04-09 06:53:58 +00:00
mPendingServiceDiscoveries . set ( 0 ) ;
2017-02-19 12:05:40 +00:00
if ( smVersion = = 0 | | Patches . DISCO_EXCEPTIONS . contains ( account . getJid ( ) . getDomainpart ( ) ) ) {
Log . d ( Config . LOGTAG , account . getJid ( ) . toBareJid ( ) + " : do not wait for service discovery " ) ;
mWaitForDisco . set ( false ) ;
} else {
mWaitForDisco . set ( true ) ;
}
2015-12-15 18:14:38 +00:00
lastDiscoStarted = SystemClock . elapsedRealtime ( ) ;
mXmppConnectionService . scheduleWakeUpCall ( Config . CONNECT_DISCO_TIMEOUT , account . getUuid ( ) . hashCode ( ) ) ;
2016-02-03 16:19:05 +00:00
Element caps = streamFeatures . findChild ( " c " ) ;
final String hash = caps = = null ? null : caps . getAttribute ( " hash " ) ;
final String ver = caps = = null ? null : caps . getAttribute ( " ver " ) ;
ServiceDiscoveryResult discoveryResult = null ;
if ( hash ! = null & & ver ! = null ) {
2016-05-19 08:44:16 +00:00
discoveryResult = mXmppConnectionService . getCachedServiceDiscoveryResult ( new Pair < > ( hash , ver ) ) ;
2016-02-03 16:19:05 +00:00
}
if ( discoveryResult = = null ) {
sendServiceDiscoveryInfo ( account . getServer ( ) ) ;
} else {
Log . d ( Config . LOGTAG , account . getJid ( ) . toBareJid ( ) + " : server caps came from cache " ) ;
disco . put ( account . getServer ( ) , discoveryResult ) ;
}
2015-04-25 16:24:10 +00:00
sendServiceDiscoveryInfo ( account . getJid ( ) . toBareJid ( ) ) ;
2016-04-04 18:35:40 +00:00
sendServiceDiscoveryItems ( account . getServer ( ) ) ;
2016-07-12 22:20:57 +00:00
if ( ! mWaitForDisco . get ( ) ) {
2016-04-09 06:53:58 +00:00
finalizeBind ( ) ;
}
2015-08-25 09:11:32 +00:00
this . lastSessionStarted = SystemClock . elapsedRealtime ( ) ;
2014-02-09 13:10:52 +00:00
}
2015-04-25 16:24:10 +00:00
private void sendServiceDiscoveryInfo ( final Jid jid ) {
2016-04-09 06:53:58 +00:00
mPendingServiceDiscoveries . incrementAndGet ( ) ;
2015-10-19 21:03:19 +00:00
final IqPacket iq = new IqPacket ( IqPacket . TYPE . GET ) ;
iq . setTo ( jid ) ;
iq . query ( " http://jabber.org/protocol/disco#info " ) ;
2016-07-12 22:20:57 +00:00
this . sendIqPacket ( iq , new OnIqPacketReceived ( ) {
2014-02-09 13:10:52 +00:00
2015-10-19 21:03:19 +00:00
@Override
public void onIqPacketReceived ( final Account account , final IqPacket packet ) {
if ( packet . getType ( ) = = IqPacket . TYPE . RESULT ) {
2016-01-15 13:26:23 +00:00
boolean advancedStreamFeaturesLoaded ;
2015-10-19 21:03:19 +00:00
synchronized ( XmppConnection . this . disco ) {
2016-01-10 19:56:55 +00:00
ServiceDiscoveryResult result = new ServiceDiscoveryResult ( packet ) ;
2016-02-03 16:19:05 +00:00
if ( jid . equals ( account . getServer ( ) ) ) {
mXmppConnectionService . databaseBackend . insertDiscoveryResult ( result ) ;
}
2016-01-10 19:56:55 +00:00
disco . put ( jid , result ) ;
2015-10-19 21:03:19 +00:00
advancedStreamFeaturesLoaded = disco . containsKey ( account . getServer ( ) )
& & disco . containsKey ( account . getJid ( ) . toBareJid ( ) ) ;
}
if ( advancedStreamFeaturesLoaded & & ( jid . equals ( account . getServer ( ) ) | | jid . equals ( account . getJid ( ) . toBareJid ( ) ) ) ) {
enableAdvancedStreamFeatures ( ) ;
2014-11-15 16:24:06 +00:00
}
2015-10-19 21:03:19 +00:00
} else {
Log . d ( Config . LOGTAG , account . getJid ( ) . toBareJid ( ) + " : could not query disco info for " + jid . toString ( ) ) ;
2014-02-09 13:10:52 +00:00
}
2015-12-09 10:16:03 +00:00
if ( packet . getType ( ) ! = IqPacket . TYPE . TIMEOUT ) {
2016-04-09 06:53:58 +00:00
if ( mPendingServiceDiscoveries . decrementAndGet ( ) = = 0
2016-07-12 22:20:57 +00:00
& & mWaitForDisco . compareAndSet ( true , false ) ) {
2016-04-04 18:06:07 +00:00
finalizeBind ( ) ;
2015-12-09 10:16:03 +00:00
}
}
2015-10-19 21:03:19 +00:00
}
} ) ;
2014-02-09 13:10:52 +00:00
}
2014-05-21 20:22:36 +00:00
2016-04-04 18:06:07 +00:00
private void finalizeBind ( ) {
Log . d ( Config . LOGTAG , account . getJid ( ) . toBareJid ( ) + " : online with resource " + account . getResource ( ) ) ;
if ( bindListener ! = null ) {
bindListener . onBind ( account ) ;
}
changeStatus ( Account . State . ONLINE ) ;
}
2014-04-08 21:15:55 +00:00
private void enableAdvancedStreamFeatures ( ) {
2015-01-05 15:17:05 +00:00
if ( getFeatures ( ) . carbons ( ) & & ! features . carbonsEnabled ) {
sendEnableCarbons ( ) ;
2014-04-08 21:15:55 +00:00
}
2015-01-05 15:17:05 +00:00
if ( getFeatures ( ) . blocking ( ) & & ! features . blockListRequested ) {
2015-09-29 17:24:52 +00:00
Log . d ( Config . LOGTAG , account . getJid ( ) . toBareJid ( ) + " : Requesting block list " ) ;
2014-12-21 20:43:58 +00:00
this . sendIqPacket ( getIqGenerator ( ) . generateGetBlockList ( ) , mXmppConnectionService . getIqParser ( ) ) ;
}
2015-10-16 21:48:42 +00:00
for ( final OnAdvancedStreamFeaturesLoaded listener : advancedStreamFeaturesLoadedListeners ) {
listener . onAdvancedStreamFeaturesAvailable ( account ) ;
}
2014-04-08 21:15:55 +00:00
}
2014-05-21 20:22:36 +00:00
2014-11-05 20:55:47 +00:00
private void sendServiceDiscoveryItems ( final Jid server ) {
2016-07-12 22:20:57 +00:00
mPendingServiceDiscoveries . incrementAndGet ( ) ;
2014-12-30 13:16:25 +00:00
final IqPacket iq = new IqPacket ( IqPacket . TYPE . GET ) ;
2014-11-05 20:55:47 +00:00
iq . setTo ( server . toDomainJid ( ) ) ;
2014-03-15 03:59:18 +00:00
iq . query ( " http://jabber.org/protocol/disco#items " ) ;
2016-07-12 22:20:57 +00:00
this . sendIqPacket ( iq , new OnIqPacketReceived ( ) {
2014-03-15 03:59:18 +00:00
@Override
2014-12-21 20:43:58 +00:00
public void onIqPacketReceived ( final Account account , final IqPacket packet ) {
2015-08-23 06:27:05 +00:00
if ( packet . getType ( ) = = IqPacket . TYPE . RESULT ) {
2017-06-30 06:45:16 +00:00
HashSet < Jid > items = new HashSet < Jid > ( ) ;
2015-08-23 06:27:05 +00:00
final List < Element > elements = packet . query ( ) . getChildren ( ) ;
for ( final Element element : elements ) {
if ( element . getName ( ) . equals ( " item " ) ) {
final Jid jid = element . getAttributeAsJid ( " jid " ) ;
if ( jid ! = null & & ! jid . equals ( account . getServer ( ) ) ) {
2017-06-30 06:45:16 +00:00
items . add ( jid ) ;
2015-08-23 06:27:05 +00:00
}
2014-11-12 20:35:44 +00:00
}
}
2017-06-30 06:45:16 +00:00
for ( Jid jid : items ) {
sendServiceDiscoveryInfo ( jid ) ;
}
2015-08-23 06:27:05 +00:00
} else {
2015-12-15 18:14:38 +00:00
Log . d ( Config . LOGTAG , account . getJid ( ) . toBareJid ( ) + " : could not query disco items of " + server ) ;
2014-11-12 20:35:44 +00:00
}
2016-04-04 18:06:07 +00:00
if ( packet . getType ( ) ! = IqPacket . TYPE . TIMEOUT ) {
2016-07-12 22:20:57 +00:00
if ( mPendingServiceDiscoveries . decrementAndGet ( ) = = 0
& & mWaitForDisco . compareAndSet ( true , false ) ) {
2016-04-04 18:06:07 +00:00
finalizeBind ( ) ;
}
}
2014-03-15 03:59:18 +00:00
}
} ) ;
}
2014-03-06 14:11:56 +00:00
2014-02-09 13:10:52 +00:00
private void sendEnableCarbons ( ) {
2014-12-30 13:16:25 +00:00
final IqPacket iq = new IqPacket ( IqPacket . TYPE . SET ) ;
2014-05-21 20:22:36 +00:00
iq . addChild ( " enable " , " urn:xmpp:carbons:2 " ) ;
2014-02-09 13:10:52 +00:00
this . sendIqPacket ( iq , new OnIqPacketReceived ( ) {
2014-03-06 14:11:56 +00:00
2014-02-09 13:10:52 +00:00
@Override
2014-12-21 20:43:58 +00:00
public void onIqPacketReceived ( final Account account , final IqPacket packet ) {
2014-02-09 13:10:52 +00:00
if ( ! packet . hasChild ( " error " ) ) {
2014-11-09 15:57:22 +00:00
Log . d ( Config . LOGTAG , account . getJid ( ) . toBareJid ( )
2014-03-06 14:11:56 +00:00
+ " : successfully enabled carbons " ) ;
2015-01-05 15:17:05 +00:00
features . carbonsEnabled = true ;
2014-02-09 13:10:52 +00:00
} else {
2014-11-09 15:57:22 +00:00
Log . d ( Config . LOGTAG , account . getJid ( ) . toBareJid ( )
2014-03-06 14:11:56 +00:00
+ " : error enableing carbons " + packet . toString ( ) ) ;
2014-02-09 13:10:52 +00:00
}
2014-02-01 00:25:56 +00:00
}
} ) ;
2014-01-30 15:42:35 +00:00
}
2014-12-21 20:43:58 +00:00
private void processStreamError ( final Tag currentTag )
2014-11-12 20:35:44 +00:00
throws XmlPullParserException , IOException {
2014-12-21 20:43:58 +00:00
final Element streamError = tagReader . readElement ( currentTag ) ;
2016-05-10 08:29:02 +00:00
if ( streamError = = null ) {
return ;
}
if ( streamError . hasChild ( " conflict " ) ) {
2014-11-05 20:55:47 +00:00
final String resource = account . getResource ( ) . split ( " \\ . " ) [ 0 ] ;
2014-11-12 20:35:44 +00:00
account . setResource ( resource + " . " + nextRandomId ( ) ) ;
Log . d ( Config . LOGTAG ,
2014-11-09 15:57:22 +00:00
account . getJid ( ) . toBareJid ( ) + " : switching resource due to conflict ( "
2014-12-21 20:43:58 +00:00
+ account . getResource ( ) + " ) " ) ;
2016-07-14 15:05:43 +00:00
throw new IOException ( ) ;
2016-05-10 08:29:02 +00:00
} else if ( streamError . hasChild ( " host-unknown " ) ) {
2017-05-05 07:33:05 +00:00
throw new StateChangingException ( Account . State . HOST_UNKNOWN ) ;
2016-07-14 15:05:43 +00:00
} else if ( streamError . hasChild ( " policy-violation " ) ) {
2017-05-05 07:33:05 +00:00
throw new StateChangingException ( Account . State . POLICY_VIOLATION ) ;
2016-07-14 15:05:43 +00:00
} else {
Log . d ( Config . LOGTAG , account . getJid ( ) . toBareJid ( ) + " : stream error " + streamError . toString ( ) ) ;
2017-05-05 07:33:05 +00:00
throw new StateChangingException ( Account . State . STREAM_ERROR ) ;
2014-09-03 12:57:40 +00:00
}
2014-01-30 15:42:35 +00:00
}
2014-03-10 18:22:13 +00:00
private void sendStartStream ( ) throws IOException {
2014-12-21 20:43:58 +00:00
final Tag stream = Tag . start ( " stream:stream " ) ;
2014-11-05 20:55:47 +00:00
stream . setAttribute ( " to " , account . getServer ( ) . toString ( ) ) ;
2014-01-30 15:42:35 +00:00
stream . setAttribute ( " version " , " 1.0 " ) ;
stream . setAttribute ( " xml:lang " , " en " ) ;
stream . setAttribute ( " xmlns " , " jabber:client " ) ;
stream . setAttribute ( " xmlns:stream " , " http://etherx.jabber.org/streams " ) ;
2014-02-04 14:09:50 +00:00
tagWriter . writeTag ( stream ) ;
2014-01-30 15:42:35 +00:00
}
private String nextRandomId ( ) {
2017-01-26 17:39:25 +00:00
return new BigInteger ( 50 , mXmppConnectionService . getRNG ( ) ) . toString ( 36 ) ;
2014-01-30 15:42:35 +00:00
}
2014-02-09 13:10:52 +00:00
2015-12-15 18:14:38 +00:00
public String sendIqPacket ( final IqPacket packet , final OnIqPacketReceived callback ) {
2014-11-09 15:57:22 +00:00
packet . setFrom ( account . getJid ( ) ) ;
2015-12-15 18:14:38 +00:00
return this . sendUnmodifiedIqPacket ( packet , callback ) ;
2014-02-01 00:25:56 +00:00
}
2014-05-21 20:22:36 +00:00
2016-08-25 11:50:54 +00:00
public synchronized String sendUnmodifiedIqPacket ( final IqPacket packet , final OnIqPacketReceived callback ) {
2014-05-21 20:22:36 +00:00
if ( packet . getId ( ) = = null ) {
2017-07-03 17:44:01 +00:00
packet . setAttribute ( " id " , nextRandomId ( ) ) ;
2014-05-18 15:32:20 +00:00
}
2015-01-04 11:09:39 +00:00
if ( callback ! = null ) {
2015-08-16 10:12:22 +00:00
synchronized ( this . packetCallbacks ) {
packetCallbacks . put ( packet . getId ( ) , new Pair < > ( packet , callback ) ) ;
}
2015-01-04 11:09:39 +00:00
}
this . sendPacket ( packet ) ;
2015-12-15 18:14:38 +00:00
return packet . getId ( ) ;
2014-05-18 15:32:20 +00:00
}
2014-02-09 13:10:52 +00:00
2014-12-21 20:43:58 +00:00
public void sendMessagePacket ( final MessagePacket packet ) {
2015-01-04 11:09:39 +00:00
this . sendPacket ( packet ) ;
2014-03-03 04:01:02 +00:00
}
2014-03-06 14:11:56 +00:00
2014-12-21 20:43:58 +00:00
public void sendPresencePacket ( final PresencePacket packet ) {
2015-01-04 11:09:39 +00:00
this . sendPacket ( packet ) ;
2014-03-03 04:01:02 +00:00
}
2014-08-31 14:28:21 +00:00
2015-01-04 11:09:39 +00:00
private synchronized void sendPacket ( final AbstractStanza packet ) {
2015-05-06 02:29:45 +00:00
if ( stanzasSent = = Integer . MAX_VALUE ) {
resetStreamId ( ) ;
disconnect ( true ) ;
return ;
}
2016-06-22 10:21:33 +00:00
synchronized ( this . mStanzaQueue ) {
tagWriter . writeStanzaAsync ( packet ) ;
if ( packet instanceof AbstractAcknowledgeableStanza ) {
AbstractAcknowledgeableStanza stanza = ( AbstractAcknowledgeableStanza ) packet ;
2016-06-14 08:17:37 +00:00
+ + stanzasSent ;
this . mStanzaQueue . append ( stanzasSent , stanza ) ;
2016-06-22 10:21:33 +00:00
if ( stanza instanceof MessagePacket & & stanza . getId ( ) ! = null & & getFeatures ( ) . sm ( ) ) {
if ( Config . EXTENDED_SM_LOGGING ) {
Log . d ( Config . LOGTAG , account . getJid ( ) . toBareJid ( ) + " : requesting ack for message stanza # " + stanzasSent ) ;
}
tagWriter . writeStanzaAsync ( new RequestPacket ( this . smVersion ) ) ;
2015-08-13 16:25:10 +00:00
}
2015-04-29 14:15:07 +00:00
}
2014-03-03 04:01:02 +00:00
}
2014-02-01 14:07:20 +00:00
}
2014-05-21 20:22:36 +00:00
2014-03-11 14:44:22 +00:00
public void sendPing ( ) {
2015-09-30 21:42:02 +00:00
if ( ! r ( ) ) {
2014-12-30 13:16:25 +00:00
final IqPacket iq = new IqPacket ( IqPacket . TYPE . GET ) ;
2014-11-09 15:57:22 +00:00
iq . setFrom ( account . getJid ( ) ) ;
2014-05-21 20:22:36 +00:00
iq . addChild ( " ping " , " urn:xmpp:ping " ) ;
2014-03-11 14:44:22 +00:00
this . sendIqPacket ( iq , null ) ;
}
2014-08-19 13:06:50 +00:00
this . lastPingSent = SystemClock . elapsedRealtime ( ) ;
2014-03-11 14:44:22 +00:00
}
2014-02-09 13:10:52 +00:00
public void setOnMessagePacketReceivedListener (
2014-12-21 20:43:58 +00:00
final OnMessagePacketReceived listener ) {
2014-02-01 14:07:20 +00:00
this . messageListener = listener ;
2014-11-12 20:35:44 +00:00
}
2014-02-09 13:10:52 +00:00
public void setOnUnregisteredIqPacketReceivedListener (
2014-12-21 20:43:58 +00:00
final OnIqPacketReceived listener ) {
2014-02-01 14:07:20 +00:00
this . unregisteredIqListener = listener ;
2014-11-12 20:35:44 +00:00
}
2014-02-09 13:10:52 +00:00
public void setOnPresencePacketReceivedListener (
2014-12-21 20:43:58 +00:00
final OnPresencePacketReceived listener ) {
2014-02-01 14:07:20 +00:00
this . presenceListener = listener ;
2014-11-12 20:35:44 +00:00
}
2014-05-21 20:22:36 +00:00
public void setOnJinglePacketReceivedListener (
2014-12-21 20:43:58 +00:00
final OnJinglePacketReceived listener ) {
2014-03-27 01:02:59 +00:00
this . jingleListener = listener ;
2014-11-12 20:35:44 +00:00
}
2014-02-09 13:10:52 +00:00
2014-12-21 20:43:58 +00:00
public void setOnStatusChangedListener ( final OnStatusChanged listener ) {
2014-02-04 14:09:50 +00:00
this . statusListener = listener ;
}
2014-05-21 20:22:36 +00:00
2014-12-21 20:43:58 +00:00
public void setOnBindListener ( final OnBindListener listener ) {
2014-03-14 19:43:54 +00:00
this . bindListener = listener ;
}
2014-08-31 14:28:21 +00:00
2014-12-21 20:43:58 +00:00
public void setOnMessageAcknowledgeListener ( final OnMessageAcknowledged listener ) {
2014-08-26 14:52:42 +00:00
this . acknowledgedListener = listener ;
}
2014-02-04 14:09:50 +00:00
2014-12-21 20:43:58 +00:00
public void addOnAdvancedStreamFeaturesAvailableListener ( final OnAdvancedStreamFeaturesLoaded listener ) {
2014-12-08 20:59:14 +00:00
if ( ! this . advancedStreamFeaturesLoadedListeners . contains ( listener ) ) {
this . advancedStreamFeaturesLoadedListeners . add ( listener ) ;
}
2014-12-05 00:54:16 +00:00
}
2016-02-14 12:20:23 +00:00
private void forceCloseSocket ( ) {
if ( socket ! = null ) {
2015-10-16 07:58:31 +00:00
try {
2014-03-10 18:22:13 +00:00
socket . close ( ) ;
2016-02-14 12:20:23 +00:00
} catch ( IOException e ) {
2016-09-07 12:34:58 +00:00
Log . d ( Config . LOGTAG , account . getJid ( ) . toBareJid ( ) + " : io exception " + e . getMessage ( ) + " during force close " ) ;
2014-04-03 13:08:53 +00:00
}
2016-09-07 12:34:58 +00:00
} else {
Log . d ( Config . LOGTAG , account . getJid ( ) . toBareJid ( ) + " : socket was null during force close " ) ;
2016-02-14 12:20:23 +00:00
}
}
2016-05-12 19:54:42 +00:00
public void interrupt ( ) {
Thread . currentThread ( ) . interrupt ( ) ;
}
2016-02-14 12:20:23 +00:00
public void disconnect ( final boolean force ) {
2016-05-12 19:54:42 +00:00
interrupt ( ) ;
2016-02-14 12:20:23 +00:00
Log . d ( Config . LOGTAG , account . getJid ( ) . toBareJid ( ) + " : disconnecting force= " + Boolean . valueOf ( force ) ) ;
if ( force ) {
forceCloseSocket ( ) ;
2015-10-16 07:58:31 +00:00
} else {
if ( tagWriter . isActive ( ) ) {
tagWriter . finish ( ) ;
try {
int i = 0 ;
boolean warned = false ;
while ( ! tagWriter . finished ( ) & & socket . isConnected ( ) & & i < = 10 ) {
if ( ! warned ) {
Log . d ( Config . LOGTAG , account . getJid ( ) . toBareJid ( ) + " : waiting for tag writer to finish " ) ;
warned = true ;
2014-05-21 20:22:36 +00:00
}
2016-09-07 12:34:58 +00:00
try {
Thread . sleep ( 200 ) ;
} catch ( InterruptedException e ) {
Log . d ( Config . LOGTAG , account . getJid ( ) . toBareJid ( ) + " : sleep interrupted " ) ;
}
2015-10-16 07:58:31 +00:00
i + + ;
}
if ( warned ) {
Log . d ( Config . LOGTAG , account . getJid ( ) . toBareJid ( ) + " : tag writer has finished " ) ;
2014-05-21 20:22:36 +00:00
}
2015-10-16 07:58:31 +00:00
Log . d ( Config . LOGTAG , account . getJid ( ) . toBareJid ( ) + " : closing stream " ) ;
tagWriter . writeTag ( Tag . end ( " stream:stream " ) ) ;
} catch ( final IOException e ) {
Log . d ( Config . LOGTAG , account . getJid ( ) . toBareJid ( ) + " : io exception during disconnect ( " + e . getMessage ( ) + " ) " ) ;
2016-09-07 12:34:58 +00:00
} finally {
forceCloseSocket ( ) ;
2014-05-21 20:22:36 +00:00
}
2015-10-16 07:58:31 +00:00
}
2014-03-10 18:22:13 +00:00
}
2014-02-04 14:09:50 +00:00
}
2014-05-21 20:22:36 +00:00
2015-04-09 10:46:54 +00:00
public void resetStreamId ( ) {
this . streamId = null ;
}
2016-03-31 19:56:59 +00:00
private List < Entry < Jid , ServiceDiscoveryResult > > findDiscoItemsByFeature ( final String feature ) {
2015-10-19 21:03:19 +00:00
synchronized ( this . disco ) {
2016-03-31 19:56:59 +00:00
final List < Entry < Jid , ServiceDiscoveryResult > > items = new ArrayList < > ( ) ;
2016-01-10 19:56:55 +00:00
for ( final Entry < Jid , ServiceDiscoveryResult > cursor : this . disco . entrySet ( ) ) {
if ( cursor . getValue ( ) . getFeatures ( ) . contains ( feature ) ) {
2016-03-31 19:56:59 +00:00
items . add ( cursor ) ;
2015-10-19 21:03:19 +00:00
}
2014-05-21 20:22:36 +00:00
}
2015-10-19 21:03:19 +00:00
return items ;
2014-05-21 20:22:36 +00:00
}
2014-06-30 09:43:00 +00:00
}
2014-08-31 14:28:21 +00:00
2015-06-28 09:19:07 +00:00
public Jid findDiscoItemByFeature ( final String feature ) {
2016-03-31 19:56:59 +00:00
final List < Entry < Jid , ServiceDiscoveryResult > > items = findDiscoItemsByFeature ( feature ) ;
2014-08-31 14:28:21 +00:00
if ( items . size ( ) > = 1 ) {
2016-03-31 19:56:59 +00:00
return items . get ( 0 ) . getKey ( ) ;
2014-06-30 09:43:00 +00:00
}
2014-04-08 21:15:55 +00:00
return null ;
2014-03-13 16:29:22 +00:00
}
2014-03-10 18:22:13 +00:00
2015-09-30 21:42:02 +00:00
public boolean r ( ) {
if ( getFeatures ( ) . sm ( ) ) {
this . tagWriter . writeStanzaAsync ( new RequestPacket ( smVersion ) ) ;
return true ;
} else {
return false ;
}
2014-03-10 18:22:13 +00:00
}
2014-08-31 14:28:21 +00:00
2014-03-15 03:59:18 +00:00
public String getMucServer ( ) {
2015-10-19 21:03:19 +00:00
synchronized ( this . disco ) {
2016-01-10 19:56:55 +00:00
for ( final Entry < Jid , ServiceDiscoveryResult > cursor : disco . entrySet ( ) ) {
final ServiceDiscoveryResult value = cursor . getValue ( ) ;
if ( value . getFeatures ( ) . contains ( " http://jabber.org/protocol/muc " )
& & ! value . getFeatures ( ) . contains ( " jabber:iq:gateway " )
& & ! value . hasIdentity ( " conference " , " irc " ) ) {
2015-10-19 21:03:19 +00:00
return cursor . getKey ( ) . toString ( ) ;
}
2014-11-20 17:20:42 +00:00
}
}
return null ;
2014-03-15 03:59:18 +00:00
}
2014-05-21 20:22:36 +00:00
2014-05-18 09:25:04 +00:00
public int getTimeToNextAttempt ( ) {
2016-05-16 17:58:24 +00:00
final int interval = Math . min ( ( int ) ( 25 * Math . pow ( 1 . 3 , attempt ) ) , 300 ) ;
2014-12-21 20:43:58 +00:00
final int secondsSinceLast = ( int ) ( ( SystemClock . elapsedRealtime ( ) - this . lastConnect ) / 1000 ) ;
2014-05-18 09:25:04 +00:00
return interval - secondsSinceLast ;
}
2014-05-21 20:22:36 +00:00
2014-05-18 09:25:04 +00:00
public int getAttempt ( ) {
return this . attempt ;
}
2014-08-31 14:28:21 +00:00
2014-08-09 08:20:16 +00:00
public Features getFeatures ( ) {
return this . features ;
}
2014-08-31 14:28:21 +00:00
2014-11-08 19:52:02 +00:00
public long getLastSessionEstablished ( ) {
2015-08-25 09:11:32 +00:00
final long diff = SystemClock . elapsedRealtime ( ) - this . lastSessionStarted ;
2014-11-08 19:52:02 +00:00
return System . currentTimeMillis ( ) - diff ;
}
public long getLastConnect ( ) {
return this . lastConnect ;
}
public long getLastPingSent ( ) {
return this . lastPingSent ;
}
2015-12-15 18:14:38 +00:00
public long getLastDiscoStarted ( ) {
return this . lastDiscoStarted ;
}
2014-11-08 19:52:02 +00:00
public long getLastPacketReceived ( ) {
2015-01-05 15:17:05 +00:00
return this . lastPacketReceived ;
2014-11-08 19:52:02 +00:00
}
public void sendActive ( ) {
2015-01-04 11:09:39 +00:00
this . sendPacket ( new ActivePacket ( ) ) ;
2014-11-08 19:52:02 +00:00
}
public void sendInactive ( ) {
2015-01-04 11:09:39 +00:00
this . sendPacket ( new InactivePacket ( ) ) ;
2014-11-08 19:52:02 +00:00
}
2016-11-18 12:55:02 +00:00
public void resetAttemptCount ( boolean resetConnectTime ) {
2015-02-10 16:13:34 +00:00
this . attempt = 0 ;
2016-11-18 12:55:02 +00:00
if ( resetConnectTime ) {
this . lastConnect = 0 ;
}
2015-02-10 16:13:34 +00:00
}
2015-09-29 17:24:52 +00:00
public void setInteractive ( boolean interactive ) {
this . mInteractive = interactive ;
}
2015-11-01 13:50:06 +00:00
public Identity getServerIdentity ( ) {
2017-01-12 22:17:52 +00:00
synchronized ( this . disco ) {
ServiceDiscoveryResult result = disco . get ( account . getJid ( ) . toDomainJid ( ) ) ;
if ( result = = null ) {
return Identity . UNKNOWN ;
}
for ( final ServiceDiscoveryResult . Identity id : result . getIdentities ( ) ) {
if ( id . getType ( ) . equals ( " im " ) & & id . getCategory ( ) . equals ( " server " ) & & id . getName ( ) ! = null ) {
switch ( id . getName ( ) ) {
case " Prosody " :
return Identity . PROSODY ;
case " ejabberd " :
return Identity . EJABBERD ;
case " Slack-XMPP " :
return Identity . SLACK ;
}
}
}
}
return Identity . UNKNOWN ;
2015-11-01 13:50:06 +00:00
}
2017-05-05 07:33:05 +00:00
private class StateChangingError extends Error {
private final Account . State state ;
2016-08-16 08:39:59 +00:00
2017-05-05 07:33:05 +00:00
public StateChangingError ( Account . State state ) {
this . state = state ;
}
2016-08-16 08:39:59 +00:00
}
2017-05-05 07:33:05 +00:00
private class StateChangingException extends IOException {
private final Account . State state ;
2016-11-19 09:44:40 +00:00
2017-05-05 07:33:05 +00:00
public StateChangingException ( Account . State state ) {
this . state = state ;
}
2016-11-19 09:44:40 +00:00
}
2015-11-01 13:50:06 +00:00
public enum Identity {
FACEBOOK ,
SLACK ,
EJABBERD ,
PROSODY ,
2016-01-15 13:26:23 +00:00
NIMBUZZ ,
2015-11-01 13:50:06 +00:00
UNKNOWN
}
2015-09-01 20:37:52 +00:00
2014-08-09 08:20:16 +00:00
public class Features {
XmppConnection connection ;
2015-01-05 15:17:05 +00:00
private boolean carbonsEnabled = false ;
private boolean encryptionEnabled = false ;
private boolean blockListRequested = false ;
2014-08-31 14:28:21 +00:00
2014-12-21 20:43:58 +00:00
public Features ( final XmppConnection connection ) {
2014-08-09 08:20:16 +00:00
this . connection = connection ;
}
2014-08-31 14:28:21 +00:00
2014-11-05 20:55:47 +00:00
private boolean hasDiscoFeature ( final Jid server , final String feature ) {
2015-10-19 21:03:19 +00:00
synchronized ( XmppConnection . this . disco ) {
return connection . disco . containsKey ( server ) & &
2016-01-10 19:56:55 +00:00
connection . disco . get ( server ) . getFeatures ( ) . contains ( feature ) ;
2015-10-19 21:03:19 +00:00
}
2014-11-12 20:35:44 +00:00
}
2014-08-31 14:28:21 +00:00
2014-08-09 08:20:16 +00:00
public boolean carbons ( ) {
return hasDiscoFeature ( account . getServer ( ) , " urn:xmpp:carbons:2 " ) ;
}
2014-08-31 14:28:21 +00:00
2014-12-21 20:43:58 +00:00
public boolean blocking ( ) {
2017-03-01 12:01:46 +00:00
return hasDiscoFeature ( account . getServer ( ) , Namespace . BLOCKING ) ;
2014-12-21 20:43:58 +00:00
}
2016-09-18 21:21:05 +00:00
public boolean spamReporting ( ) {
return hasDiscoFeature ( account . getServer ( ) , " urn:xmpp:reporting:reason:spam:0 " ) ;
}
2014-12-23 22:19:00 +00:00
public boolean register ( ) {
2017-03-01 12:01:46 +00:00
return hasDiscoFeature ( account . getServer ( ) , Namespace . REGISTER ) ;
2014-12-23 22:19:00 +00:00
}
2014-08-09 08:20:16 +00:00
public boolean sm ( ) {
2015-04-25 16:24:10 +00:00
return streamId ! = null
| | ( connection . streamFeatures ! = null & & connection . streamFeatures . hasChild ( " sm " ) ) ;
2014-08-09 08:20:16 +00:00
}
2014-08-31 14:28:21 +00:00
2014-08-26 15:43:44 +00:00
public boolean csi ( ) {
2014-11-12 20:35:44 +00:00
return connection . streamFeatures ! = null & & connection . streamFeatures . hasChild ( " csi " , " urn:xmpp:csi:0 " ) ;
2014-08-26 15:43:44 +00:00
}
2014-08-31 14:28:21 +00:00
2015-04-25 16:24:10 +00:00
public boolean pep ( ) {
2015-10-19 21:03:19 +00:00
synchronized ( XmppConnection . this . disco ) {
2016-04-11 20:20:32 +00:00
ServiceDiscoveryResult info = disco . get ( account . getJid ( ) . toBareJid ( ) ) ;
return info ! = null & & info . hasIdentity ( " pubsub " , " pep " ) ;
}
}
public boolean pepPersistent ( ) {
synchronized ( XmppConnection . this . disco ) {
ServiceDiscoveryResult info = disco . get ( account . getJid ( ) . toBareJid ( ) ) ;
return info ! = null & & info . getFeatures ( ) . contains ( " http://jabber.org/protocol/pubsub#persistent-items " ) ;
2015-04-25 16:24:10 +00:00
}
2014-08-09 08:20:16 +00:00
}
2014-08-31 14:28:21 +00:00
2017-07-12 15:49:49 +00:00
public boolean pepPublishOptions ( ) {
synchronized ( XmppConnection . this . disco ) {
ServiceDiscoveryResult info = disco . get ( account . getJid ( ) . toBareJid ( ) ) ;
return info ! = null & & info . getFeatures ( ) . contains ( Namespace . PUBSUB_PUBLISH_OPTIONS ) ;
}
}
2014-11-08 19:52:02 +00:00
public boolean mam ( ) {
2017-03-01 12:01:46 +00:00
return hasDiscoFeature ( account . getJid ( ) . toBareJid ( ) , Namespace . MAM )
| | hasDiscoFeature ( account . getJid ( ) . toBareJid ( ) , Namespace . MAM_LEGACY ) ;
2017-02-15 15:42:35 +00:00
}
public boolean mamLegacy ( ) {
2017-03-01 12:01:46 +00:00
return ! hasDiscoFeature ( account . getJid ( ) . toBareJid ( ) , Namespace . MAM )
& & hasDiscoFeature ( account . getJid ( ) . toBareJid ( ) , Namespace . MAM_LEGACY ) ;
2014-11-08 19:52:02 +00:00
}
2016-02-12 10:39:27 +00:00
public boolean push ( ) {
return hasDiscoFeature ( account . getJid ( ) . toBareJid ( ) , " urn:xmpp:push:0 " )
| | hasDiscoFeature ( account . getServer ( ) , " urn:xmpp:push:0 " ) ;
2014-12-08 20:59:14 +00:00
}
2014-08-09 08:20:16 +00:00
public boolean rosterVersioning ( ) {
2014-11-12 20:35:44 +00:00
return connection . streamFeatures ! = null & & connection . streamFeatures . hasChild ( " ver " ) ;
2014-08-09 08:20:16 +00:00
}
2014-08-31 14:28:21 +00:00
2015-01-05 15:17:05 +00:00
public void setBlockListRequested ( boolean value ) {
this . blockListRequested = value ;
2014-08-19 13:06:50 +00:00
}
2015-06-28 09:19:07 +00:00
2016-03-31 19:56:59 +00:00
public boolean httpUpload ( long filesize ) {
if ( Config . DISABLE_HTTP_UPLOAD ) {
return false ;
} else {
2017-03-01 12:01:46 +00:00
List < Entry < Jid , ServiceDiscoveryResult > > items = findDiscoItemsByFeature ( Namespace . HTTP_UPLOAD ) ;
2016-03-31 19:56:59 +00:00
if ( items . size ( ) > 0 ) {
try {
2017-03-01 12:01:46 +00:00
long maxsize = Long . parseLong ( items . get ( 0 ) . getValue ( ) . getExtendedDiscoInformation ( Namespace . HTTP_UPLOAD , " max-file-size " ) ) ;
2016-05-05 17:34:44 +00:00
if ( filesize < = maxsize ) {
return true ;
} else {
Log . d ( Config . LOGTAG , account . getJid ( ) . toBareJid ( ) + " : http upload is not available for files with size " + filesize + " (max is " + maxsize + " ) " ) ;
return false ;
}
2016-03-31 19:56:59 +00:00
} catch ( Exception e ) {
2016-03-31 22:03:14 +00:00
return true ;
2016-03-31 19:56:59 +00:00
}
} else {
return false ;
}
}
2015-06-28 09:19:07 +00:00
}
2016-03-31 22:03:14 +00:00
public long getMaxHttpUploadSize ( ) {
2017-03-01 12:01:46 +00:00
List < Entry < Jid , ServiceDiscoveryResult > > items = findDiscoItemsByFeature ( Namespace . HTTP_UPLOAD ) ;
2016-03-31 22:03:14 +00:00
if ( items . size ( ) > 0 ) {
try {
2017-03-01 12:01:46 +00:00
return Long . parseLong ( items . get ( 0 ) . getValue ( ) . getExtendedDiscoInformation ( Namespace . HTTP_UPLOAD , " max-file-size " ) ) ;
2016-03-31 22:03:14 +00:00
} catch ( Exception e ) {
return - 1 ;
}
} else {
return - 1 ;
}
}
2016-10-31 11:07:08 +00:00
public boolean stanzaIds ( ) {
2017-03-01 12:01:46 +00:00
return hasDiscoFeature ( account . getJid ( ) . toBareJid ( ) , Namespace . STANZA_IDS ) ;
2016-10-31 11:07:08 +00:00
}
2014-08-19 13:06:50 +00:00
}
2014-12-21 20:43:58 +00:00
private IqGenerator getIqGenerator ( ) {
return mXmppConnectionService . getIqGenerator ( ) ;
}
2014-01-30 15:42:35 +00:00
}