remove XmppConnection.Features helper class in favor of DiscoManager
This commit is contained in:
parent
199a1cdc64
commit
873644f528
|
@ -4,6 +4,7 @@ import android.util.Log;
|
||||||
import android.util.Xml;
|
import android.util.Xml;
|
||||||
import eu.siacs.conversations.Config;
|
import eu.siacs.conversations.Config;
|
||||||
import im.conversations.android.xmpp.Extensions;
|
import im.conversations.android.xmpp.Extensions;
|
||||||
|
import im.conversations.android.xmpp.model.Extension;
|
||||||
import java.io.Closeable;
|
import java.io.Closeable;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
@ -91,6 +92,16 @@ public class XmlReader implements Closeable {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public <T extends Extension> T readElement(final Tag current, Class<T> clazz)
|
||||||
|
throws IOException {
|
||||||
|
final Element element = readElement(current);
|
||||||
|
if (clazz.isInstance(element)) {
|
||||||
|
return clazz.cast(element);
|
||||||
|
}
|
||||||
|
throw new IOException(
|
||||||
|
String.format("Read unexpected {%s}%s", element.getNamespace(), element.getName()));
|
||||||
|
}
|
||||||
|
|
||||||
public Element readElement(Tag currentTag) throws IOException {
|
public Element readElement(Tag currentTag) throws IOException {
|
||||||
final var attributes = currentTag.getAttributes();
|
final var attributes = currentTag.getAttributes();
|
||||||
final var namespace = attributes.get("xmlns");
|
final var namespace = attributes.get("xmlns");
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
package eu.siacs.conversations.xmpp.stanzas;
|
package eu.siacs.conversations.xmpp.stanzas;
|
||||||
|
|
||||||
public class PresencePacket extends AbstractAcknowledgeableStanza {
|
import im.conversations.android.xmpp.model.capabilties.EntityCapabilities;
|
||||||
|
|
||||||
public PresencePacket() {
|
public class PresencePacket extends AbstractAcknowledgeableStanza implements EntityCapabilities {
|
||||||
super("presence");
|
|
||||||
}
|
public PresencePacket() {
|
||||||
|
super("presence");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,7 +36,7 @@ public class ConnectionPool {
|
||||||
private final Context context;
|
private final Context context;
|
||||||
|
|
||||||
private final Executor reconfigurationExecutor = Executors.newSingleThreadExecutor();
|
private final Executor reconfigurationExecutor = Executors.newSingleThreadExecutor();
|
||||||
private final ScheduledExecutorService reconnectExecutor =
|
public static final ScheduledExecutorService CONNECTION_SCHEDULER =
|
||||||
Executors.newSingleThreadScheduledExecutor();
|
Executors.newSingleThreadScheduledExecutor();
|
||||||
|
|
||||||
private final List<XmppConnection> connections = new ArrayList<>();
|
private final List<XmppConnection> connections = new ArrayList<>();
|
||||||
|
@ -106,7 +106,7 @@ public class ConnectionPool {
|
||||||
ConversationsDatabase.getInstance(context)
|
ConversationsDatabase.getInstance(context)
|
||||||
.accountDao()
|
.accountDao()
|
||||||
.setShowErrorNotification(account.id, true);
|
.setShowErrorNotification(account.id, true);
|
||||||
if (connection.getFeatures().csi()) {
|
if (connection.supportsClientStateIndication()) {
|
||||||
// TODO send correct CSI state (connection.sendActive or connection.sendInactive)
|
// TODO send correct CSI state (connection.sendActive or connection.sendInactive)
|
||||||
}
|
}
|
||||||
scheduleWakeUpCall(Config.PING_MAX_INTERVAL);
|
scheduleWakeUpCall(Config.PING_MAX_INTERVAL);
|
||||||
|
@ -163,7 +163,7 @@ public class ConnectionPool {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void scheduleWakeUpCall(final int seconds) {
|
public void scheduleWakeUpCall(final int seconds) {
|
||||||
reconnectExecutor.schedule(
|
CONNECTION_SCHEDULER.schedule(
|
||||||
() -> {
|
() -> {
|
||||||
manageConnectionStates();
|
manageConnectionStates();
|
||||||
},
|
},
|
||||||
|
@ -272,9 +272,6 @@ public class ConnectionPool {
|
||||||
} else if (connection.getStatus() == ConnectionState.CONNECTING) {
|
} else if (connection.getStatus() == ConnectionState.CONNECTING) {
|
||||||
long secondsSinceLastConnect =
|
long secondsSinceLastConnect =
|
||||||
(SystemClock.elapsedRealtime() - connection.getLastConnect()) / 1000;
|
(SystemClock.elapsedRealtime() - connection.getLastConnect()) / 1000;
|
||||||
long secondsSinceLastDisco =
|
|
||||||
(SystemClock.elapsedRealtime() - connection.getLastDiscoStarted()) / 1000;
|
|
||||||
long discoTimeout = Config.CONNECT_DISCO_TIMEOUT - secondsSinceLastDisco;
|
|
||||||
long timeout = Config.CONNECT_TIMEOUT - secondsSinceLastConnect;
|
long timeout = Config.CONNECT_TIMEOUT - secondsSinceLastConnect;
|
||||||
if (timeout < 0) {
|
if (timeout < 0) {
|
||||||
Log.d(
|
Log.d(
|
||||||
|
@ -286,11 +283,6 @@ public class ConnectionPool {
|
||||||
+ ")");
|
+ ")");
|
||||||
connection.resetAttemptCount(false);
|
connection.resetAttemptCount(false);
|
||||||
reconnectAccount(connection);
|
reconnectAccount(connection);
|
||||||
} else if (discoTimeout < 0) {
|
|
||||||
connection.sendDiscoTimeout();
|
|
||||||
scheduleWakeUpCall(Ints.saturatedCast(discoTimeout));
|
|
||||||
} else {
|
|
||||||
scheduleWakeUpCall(Ints.saturatedCast(Math.min(timeout, discoTimeout)));
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (connection.getTimeToNextAttempt() <= 0) {
|
if (connection.getTimeToNextAttempt() <= 0) {
|
||||||
|
|
|
@ -35,6 +35,7 @@ public final class Extensions {
|
||||||
im.conversations.android.xmpp.model.disco.items.ItemsQuery.class,
|
im.conversations.android.xmpp.model.disco.items.ItemsQuery.class,
|
||||||
im.conversations.android.xmpp.model.roster.Query.class,
|
im.conversations.android.xmpp.model.roster.Query.class,
|
||||||
im.conversations.android.xmpp.model.roster.Item.class,
|
im.conversations.android.xmpp.model.roster.Item.class,
|
||||||
|
im.conversations.android.xmpp.model.streams.Features.class,
|
||||||
im.conversations.android.xmpp.model.Hash.class);
|
im.conversations.android.xmpp.model.Hash.class);
|
||||||
|
|
||||||
private static final BiMap<Id, Class<? extends Extension>> EXTENSION_CLASS_MAP;
|
private static final BiMap<Id, Class<? extends Extension>> EXTENSION_CLASS_MAP;
|
||||||
|
|
|
@ -17,13 +17,14 @@ import androidx.annotation.Nullable;
|
||||||
import com.google.common.base.Optional;
|
import com.google.common.base.Optional;
|
||||||
import com.google.common.base.Strings;
|
import com.google.common.base.Strings;
|
||||||
import com.google.common.collect.ClassToInstanceMap;
|
import com.google.common.collect.ClassToInstanceMap;
|
||||||
|
import com.google.common.util.concurrent.FutureCallback;
|
||||||
|
import com.google.common.util.concurrent.Futures;
|
||||||
import com.google.common.util.concurrent.ListenableFuture;
|
import com.google.common.util.concurrent.ListenableFuture;
|
||||||
|
import com.google.common.util.concurrent.MoreExecutors;
|
||||||
import com.google.common.util.concurrent.SettableFuture;
|
import com.google.common.util.concurrent.SettableFuture;
|
||||||
import eu.siacs.conversations.Config;
|
import eu.siacs.conversations.Config;
|
||||||
import eu.siacs.conversations.R;
|
import eu.siacs.conversations.R;
|
||||||
import eu.siacs.conversations.crypto.XmppDomainVerifier;
|
import eu.siacs.conversations.crypto.XmppDomainVerifier;
|
||||||
import eu.siacs.conversations.crypto.axolotl.AxolotlService;
|
|
||||||
import eu.siacs.conversations.entities.ServiceDiscoveryResult;
|
|
||||||
import eu.siacs.conversations.http.HttpConnectionManager;
|
import eu.siacs.conversations.http.HttpConnectionManager;
|
||||||
import eu.siacs.conversations.persistance.FileBackend;
|
import eu.siacs.conversations.persistance.FileBackend;
|
||||||
import eu.siacs.conversations.services.MemorizingTrustManager;
|
import eu.siacs.conversations.services.MemorizingTrustManager;
|
||||||
|
@ -41,7 +42,6 @@ import eu.siacs.conversations.xml.Namespace;
|
||||||
import eu.siacs.conversations.xml.Tag;
|
import eu.siacs.conversations.xml.Tag;
|
||||||
import eu.siacs.conversations.xml.TagWriter;
|
import eu.siacs.conversations.xml.TagWriter;
|
||||||
import eu.siacs.conversations.xml.XmlReader;
|
import eu.siacs.conversations.xml.XmlReader;
|
||||||
import eu.siacs.conversations.xmpp.InvalidJid;
|
|
||||||
import eu.siacs.conversations.xmpp.Jid;
|
import eu.siacs.conversations.xmpp.Jid;
|
||||||
import eu.siacs.conversations.xmpp.bind.Bind2;
|
import eu.siacs.conversations.xmpp.bind.Bind2;
|
||||||
import eu.siacs.conversations.xmpp.forms.Data;
|
import eu.siacs.conversations.xmpp.forms.Data;
|
||||||
|
@ -64,6 +64,8 @@ import im.conversations.android.database.model.Account;
|
||||||
import im.conversations.android.database.model.Connection;
|
import im.conversations.android.database.model.Connection;
|
||||||
import im.conversations.android.database.model.Credential;
|
import im.conversations.android.database.model.Credential;
|
||||||
import im.conversations.android.xmpp.manager.AbstractManager;
|
import im.conversations.android.xmpp.manager.AbstractManager;
|
||||||
|
import im.conversations.android.xmpp.manager.DiscoManager;
|
||||||
|
import im.conversations.android.xmpp.model.streams.Features;
|
||||||
import im.conversations.android.xmpp.processor.BindProcessor;
|
import im.conversations.android.xmpp.processor.BindProcessor;
|
||||||
import im.conversations.android.xmpp.processor.IqProcessor;
|
import im.conversations.android.xmpp.processor.IqProcessor;
|
||||||
import im.conversations.android.xmpp.processor.JingleProcessor;
|
import im.conversations.android.xmpp.processor.JingleProcessor;
|
||||||
|
@ -90,13 +92,10 @@ import java.security.cert.X509Certificate;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.Hashtable;
|
import java.util.Hashtable;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map.Entry;
|
|
||||||
import java.util.concurrent.CountDownLatch;
|
import java.util.concurrent.CountDownLatch;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.TimeoutException;
|
import java.util.concurrent.TimeoutException;
|
||||||
|
@ -121,8 +120,6 @@ public class XmppConnection implements Runnable {
|
||||||
private static final int PACKET_MESSAGE = 1;
|
private static final int PACKET_MESSAGE = 1;
|
||||||
private static final int PACKET_PRESENCE = 2;
|
private static final int PACKET_PRESENCE = 2;
|
||||||
protected final Account account;
|
protected final Account account;
|
||||||
private final Features features = new Features(this);
|
|
||||||
private final HashMap<Jid, ServiceDiscoveryResult> disco = new HashMap<>();
|
|
||||||
private final HashMap<String, Jid> commands = new HashMap<>();
|
private final HashMap<String, Jid> commands = new HashMap<>();
|
||||||
private final SparseArray<AbstractAcknowledgeableStanza> mStanzaQueue = new SparseArray<>();
|
private final SparseArray<AbstractAcknowledgeableStanza> mStanzaQueue = new SparseArray<>();
|
||||||
private final Hashtable<String, Pair<IqPacket, Consumer<IqPacket>>> packetCallbacks =
|
private final Hashtable<String, Pair<IqPacket, Consumer<IqPacket>>> packetCallbacks =
|
||||||
|
@ -131,11 +128,15 @@ public class XmppConnection implements Runnable {
|
||||||
private Socket socket;
|
private Socket socket;
|
||||||
private XmlReader tagReader;
|
private XmlReader tagReader;
|
||||||
private TagWriter tagWriter = new TagWriter();
|
private TagWriter tagWriter = new TagWriter();
|
||||||
|
|
||||||
|
private boolean encryptionEnabled = false;
|
||||||
|
|
||||||
|
private boolean carbonsEnabled = false;
|
||||||
private boolean shouldAuthenticate = true;
|
private boolean shouldAuthenticate = true;
|
||||||
private boolean inSmacksSession = false;
|
private boolean inSmacksSession = false;
|
||||||
private boolean quickStartInProgress = false;
|
private boolean quickStartInProgress = false;
|
||||||
private boolean isBound = false;
|
private boolean isBound = false;
|
||||||
private Element streamFeatures;
|
private Features streamFeatures;
|
||||||
private String streamId = null;
|
private String streamId = null;
|
||||||
private Jid connectionAddress;
|
private Jid connectionAddress;
|
||||||
private ConnectionState connectionState = ConnectionState.OFFLINE;
|
private ConnectionState connectionState = ConnectionState.OFFLINE;
|
||||||
|
@ -147,10 +148,7 @@ public class XmppConnection implements Runnable {
|
||||||
private long lastPingSent = 0;
|
private long lastPingSent = 0;
|
||||||
private long lastConnect = 0;
|
private long lastConnect = 0;
|
||||||
private long lastSessionStarted = 0;
|
private long lastSessionStarted = 0;
|
||||||
private long lastDiscoStarted = 0;
|
|
||||||
private boolean isMamPreferenceAlways = false;
|
private boolean isMamPreferenceAlways = false;
|
||||||
private final AtomicInteger mPendingServiceDiscoveries = new AtomicInteger(0);
|
|
||||||
private final AtomicBoolean mWaitForDisco = new AtomicBoolean(true);
|
|
||||||
private final AtomicBoolean mWaitingForSmCatchup = new AtomicBoolean(false);
|
private final AtomicBoolean mWaitingForSmCatchup = new AtomicBoolean(false);
|
||||||
private final AtomicInteger mSmCatchupMessageCounter = new AtomicInteger(0);
|
private final AtomicInteger mSmCatchupMessageCounter = new AtomicInteger(0);
|
||||||
private int attempt = 0;
|
private int attempt = 0;
|
||||||
|
@ -261,7 +259,6 @@ public class XmppConnection implements Runnable {
|
||||||
public void prepareNewConnection() {
|
public void prepareNewConnection() {
|
||||||
this.lastConnect = SystemClock.elapsedRealtime();
|
this.lastConnect = SystemClock.elapsedRealtime();
|
||||||
this.lastPingSent = SystemClock.elapsedRealtime();
|
this.lastPingSent = SystemClock.elapsedRealtime();
|
||||||
this.lastDiscoStarted = Long.MAX_VALUE;
|
|
||||||
this.mWaitingForSmCatchup.set(false);
|
this.mWaitingForSmCatchup.set(false);
|
||||||
this.changeStatus(ConnectionState.CONNECTING);
|
this.changeStatus(ConnectionState.CONNECTING);
|
||||||
}
|
}
|
||||||
|
@ -280,7 +277,7 @@ public class XmppConnection implements Runnable {
|
||||||
.accountDao()
|
.accountDao()
|
||||||
.getConnectionSettings(account.id);
|
.getConnectionSettings(account.id);
|
||||||
Log.d(Config.LOGTAG, account.address + ": connecting");
|
Log.d(Config.LOGTAG, account.address + ": connecting");
|
||||||
features.encryptionEnabled = false;
|
this.encryptionEnabled = false;
|
||||||
this.inSmacksSession = false;
|
this.inSmacksSession = false;
|
||||||
this.quickStartInProgress = false;
|
this.quickStartInProgress = false;
|
||||||
this.isBound = false;
|
this.isBound = false;
|
||||||
|
@ -323,7 +320,7 @@ public class XmppConnection implements Runnable {
|
||||||
|
|
||||||
if (directTls) {
|
if (directTls) {
|
||||||
localSocket = upgradeSocketToTls(localSocket);
|
localSocket = upgradeSocketToTls(localSocket);
|
||||||
features.encryptionEnabled = true;
|
this.encryptionEnabled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -377,7 +374,7 @@ public class XmppConnection implements Runnable {
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
// if tls is true, encryption is implied and must not be started
|
// if tls is true, encryption is implied and must not be started
|
||||||
features.encryptionEnabled = result.isDirectTls();
|
this.encryptionEnabled = result.isDirectTls();
|
||||||
verifiedHostname =
|
verifiedHostname =
|
||||||
result.isAuthenticated() ? result.getHostname().toString() : null;
|
result.isAuthenticated() ? result.getHostname().toString() : null;
|
||||||
Log.d(Config.LOGTAG, "verified hostname " + verifiedHostname);
|
Log.d(Config.LOGTAG, "verified hostname " + verifiedHostname);
|
||||||
|
@ -395,7 +392,7 @@ public class XmppConnection implements Runnable {
|
||||||
+ ":"
|
+ ":"
|
||||||
+ result.getPort()
|
+ result.getPort()
|
||||||
+ " tls: "
|
+ " tls: "
|
||||||
+ features.encryptionEnabled);
|
+ this.encryptionEnabled);
|
||||||
} else {
|
} else {
|
||||||
addr =
|
addr =
|
||||||
new InetSocketAddress(
|
new InetSocketAddress(
|
||||||
|
@ -409,13 +406,13 @@ public class XmppConnection implements Runnable {
|
||||||
+ ":"
|
+ ":"
|
||||||
+ result.getPort()
|
+ result.getPort()
|
||||||
+ " tls: "
|
+ " tls: "
|
||||||
+ features.encryptionEnabled);
|
+ this.encryptionEnabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
localSocket = new Socket();
|
localSocket = new Socket();
|
||||||
localSocket.connect(addr, Config.SOCKET_TIMEOUT * 1000);
|
localSocket.connect(addr, Config.SOCKET_TIMEOUT * 1000);
|
||||||
|
|
||||||
if (features.encryptionEnabled) {
|
if (this.encryptionEnabled) {
|
||||||
localSocket = upgradeSocketToTls(localSocket);
|
localSocket = upgradeSocketToTls(localSocket);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -776,20 +773,18 @@ public class XmppConnection implements Runnable {
|
||||||
final Element streamManagementEnabled =
|
final Element streamManagementEnabled =
|
||||||
bound.findChild("enabled", Namespace.STREAM_MANAGEMENT);
|
bound.findChild("enabled", Namespace.STREAM_MANAGEMENT);
|
||||||
final Element carbonsEnabled = bound.findChild("enabled", Namespace.CARBONS);
|
final Element carbonsEnabled = bound.findChild("enabled", Namespace.CARBONS);
|
||||||
final boolean waitForDisco;
|
|
||||||
if (streamManagementEnabled != null) {
|
if (streamManagementEnabled != null) {
|
||||||
resetOutboundStanzaQueue();
|
resetOutboundStanzaQueue();
|
||||||
processEnabled(streamManagementEnabled);
|
processEnabled(streamManagementEnabled);
|
||||||
waitForDisco = true;
|
|
||||||
} else {
|
} else {
|
||||||
// if we did not enable stream management in bind do it now
|
// if we did not enable stream management in bind do it now
|
||||||
waitForDisco = enableStreamManagement();
|
enableStreamManagement();
|
||||||
}
|
}
|
||||||
if (carbonsEnabled != null) {
|
if (carbonsEnabled != null) {
|
||||||
Log.d(Config.LOGTAG, account.address + ": successfully enabled carbons");
|
Log.d(Config.LOGTAG, account.address + ": successfully enabled carbons");
|
||||||
features.carbonsEnabled = true;
|
this.carbonsEnabled = true;
|
||||||
}
|
}
|
||||||
sendPostBindInitialization(waitForDisco, carbonsEnabled != null);
|
sendPostBindInitialization(carbonsEnabled != null);
|
||||||
processNopStreamFeatures = true;
|
processNopStreamFeatures = true;
|
||||||
} else {
|
} else {
|
||||||
processNopStreamFeatures = false;
|
processNopStreamFeatures = false;
|
||||||
|
@ -874,7 +869,7 @@ public class XmppConnection implements Runnable {
|
||||||
private void processNopStreamFeatures() throws IOException {
|
private void processNopStreamFeatures() throws IOException {
|
||||||
final Tag tag = tagReader.readTag();
|
final Tag tag = tagReader.readTag();
|
||||||
if (tag != null && tag.isStart("features", Namespace.STREAMS)) {
|
if (tag != null && tag.isStart("features", Namespace.STREAMS)) {
|
||||||
this.streamFeatures = tagReader.readElement(tag);
|
this.streamFeatures = tagReader.readElement(tag, Features.class);
|
||||||
Log.d(
|
Log.d(
|
||||||
Config.LOGTAG,
|
Config.LOGTAG,
|
||||||
account.address
|
account.address
|
||||||
|
@ -1103,7 +1098,7 @@ public class XmppConnection implements Runnable {
|
||||||
}
|
}
|
||||||
if (inSmacksSession) {
|
if (inSmacksSession) {
|
||||||
++stanzasReceived;
|
++stanzasReceived;
|
||||||
} else if (features.sm()) {
|
} else if (this.streamFeatures.streamManagement()) {
|
||||||
Log.d(
|
Log.d(
|
||||||
Config.LOGTAG,
|
Config.LOGTAG,
|
||||||
account.address
|
account.address
|
||||||
|
@ -1229,7 +1224,7 @@ public class XmppConnection implements Runnable {
|
||||||
if (quickStart) {
|
if (quickStart) {
|
||||||
this.quickStartInProgress = true;
|
this.quickStartInProgress = true;
|
||||||
}
|
}
|
||||||
features.encryptionEnabled = true;
|
this.encryptionEnabled = true;
|
||||||
final Tag tag = tagReader.readTag();
|
final Tag tag = tagReader.readTag();
|
||||||
if (tag != null && tag.isStart("stream", Namespace.STREAMS)) {
|
if (tag != null && tag.isStart("stream", Namespace.STREAMS)) {
|
||||||
SSLSockets.log(account.address, sslSocket);
|
SSLSockets.log(account.address, sslSocket);
|
||||||
|
@ -1280,7 +1275,7 @@ public class XmppConnection implements Runnable {
|
||||||
ConversationsDatabase.getInstance(context)
|
ConversationsDatabase.getInstance(context)
|
||||||
.accountDao()
|
.accountDao()
|
||||||
.pendingRegistration(account.id);
|
.pendingRegistration(account.id);
|
||||||
this.streamFeatures = tagReader.readElement(currentTag);
|
this.streamFeatures = tagReader.readElement(currentTag, Features.class);
|
||||||
final boolean isSecure = isSecure();
|
final boolean isSecure = isSecure();
|
||||||
final boolean needsBinding = !isBound && !pendingRegistration;
|
final boolean needsBinding = !isBound && !pendingRegistration;
|
||||||
if (this.quickStartInProgress) {
|
if (this.quickStartInProgress) {
|
||||||
|
@ -1312,8 +1307,7 @@ public class XmppConnection implements Runnable {
|
||||||
.setQuickStartAvailable(account.id, false);
|
.setQuickStartAvailable(account.id, false);
|
||||||
throw new StateChangingException(ConnectionState.INCOMPATIBLE_SERVER);
|
throw new StateChangingException(ConnectionState.INCOMPATIBLE_SERVER);
|
||||||
}
|
}
|
||||||
if (this.streamFeatures.hasChild("starttls", Namespace.TLS)
|
if (this.streamFeatures.hasChild("starttls", Namespace.TLS) && !this.encryptionEnabled) {
|
||||||
&& !features.encryptionEnabled) {
|
|
||||||
sendStartTLS();
|
sendStartTLS();
|
||||||
} else if (this.streamFeatures.hasChild("register", Namespace.REGISTER_STREAM_FEATURE)
|
} else if (this.streamFeatures.hasChild("register", Namespace.REGISTER_STREAM_FEATURE)
|
||||||
&& pendingRegistration) {
|
&& pendingRegistration) {
|
||||||
|
@ -1338,9 +1332,7 @@ public class XmppConnection implements Runnable {
|
||||||
&& shouldAuthenticate
|
&& shouldAuthenticate
|
||||||
&& isSecure) {
|
&& isSecure) {
|
||||||
authenticate(SaslMechanism.Version.SASL);
|
authenticate(SaslMechanism.Version.SASL);
|
||||||
} else if (this.streamFeatures.hasChild("sm", Namespace.STREAM_MANAGEMENT)
|
} else if (this.streamFeatures.streamManagement() && streamId != null && !inSmacksSession) {
|
||||||
&& streamId != null
|
|
||||||
&& !inSmacksSession) {
|
|
||||||
if (Config.EXTENDED_SM_LOGGING) {
|
if (Config.EXTENDED_SM_LOGGING) {
|
||||||
Log.d(
|
Log.d(
|
||||||
Config.LOGTAG,
|
Config.LOGTAG,
|
||||||
|
@ -1382,7 +1374,7 @@ public class XmppConnection implements Runnable {
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isSecure() {
|
private boolean isSecure() {
|
||||||
return features.encryptionEnabled || Config.ALLOW_NON_TLS_CONNECTIONS || account.isOnion();
|
return this.encryptionEnabled || Config.ALLOW_NON_TLS_CONNECTIONS || account.isOnion();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void authenticate(final SaslMechanism.Version version) throws IOException {
|
private void authenticate(final SaslMechanism.Version version) throws IOException {
|
||||||
|
@ -1555,7 +1547,7 @@ public class XmppConnection implements Runnable {
|
||||||
private void register() {
|
private void register() {
|
||||||
final Credential credential = CredentialStore.getInstance(context).get(account);
|
final Credential credential = CredentialStore.getInstance(context).get(account);
|
||||||
final String preAuth = credential.preAuthRegistrationToken;
|
final String preAuth = credential.preAuthRegistrationToken;
|
||||||
if (Strings.isNullOrEmpty(preAuth) || !features.invite()) {
|
if (Strings.isNullOrEmpty(preAuth) || !streamFeatures.invite()) {
|
||||||
sendRegistryRequest();
|
sendRegistryRequest();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -1712,9 +1704,6 @@ public class XmppConnection implements Runnable {
|
||||||
this.stanzasSent = 0;
|
this.stanzasSent = 0;
|
||||||
mStanzaQueue.clear();
|
mStanzaQueue.clear();
|
||||||
this.redirectionUrl = null;
|
this.redirectionUrl = null;
|
||||||
synchronized (this.disco) {
|
|
||||||
disco.clear();
|
|
||||||
}
|
|
||||||
synchronized (this.commands) {
|
synchronized (this.commands) {
|
||||||
this.commands.clear();
|
this.commands.clear();
|
||||||
}
|
}
|
||||||
|
@ -1765,8 +1754,8 @@ public class XmppConnection implements Runnable {
|
||||||
.hasChild("optional")) {
|
.hasChild("optional")) {
|
||||||
sendStartSession();
|
sendStartSession();
|
||||||
} else {
|
} else {
|
||||||
final boolean waitForDisco = enableStreamManagement();
|
enableStreamManagement();
|
||||||
sendPostBindInitialization(waitForDisco, false);
|
sendPostBindInitialization(false);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
} catch (final IllegalArgumentException e) {
|
} catch (final IllegalArgumentException e) {
|
||||||
|
@ -1856,13 +1845,6 @@ public class XmppConnection implements Runnable {
|
||||||
+ " left");
|
+ " left");
|
||||||
}
|
}
|
||||||
|
|
||||||
public void sendDiscoTimeout() {
|
|
||||||
if (mWaitForDisco.compareAndSet(true, false)) {
|
|
||||||
Log.d(Config.LOGTAG, account.address + ": finalizing bind after disco timeout");
|
|
||||||
finalizeBind();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void sendStartSession() {
|
private void sendStartSession() {
|
||||||
Log.d(Config.LOGTAG, account.address + ": sending legacy session to outdated server");
|
Log.d(Config.LOGTAG, account.address + ": sending legacy session to outdated server");
|
||||||
final IqPacket startSession = new IqPacket(IqPacket.TYPE.SET);
|
final IqPacket startSession = new IqPacket(IqPacket.TYPE.SET);
|
||||||
|
@ -1871,8 +1853,8 @@ public class XmppConnection implements Runnable {
|
||||||
startSession,
|
startSession,
|
||||||
(packet) -> {
|
(packet) -> {
|
||||||
if (packet.getType() == IqPacket.TYPE.RESULT) {
|
if (packet.getType() == IqPacket.TYPE.RESULT) {
|
||||||
final boolean waitForDisco = enableStreamManagement();
|
enableStreamManagement();
|
||||||
sendPostBindInitialization(waitForDisco, false);
|
sendPostBindInitialization(false);
|
||||||
} else if (packet.getType() != IqPacket.TYPE.TIMEOUT) {
|
} else if (packet.getType() != IqPacket.TYPE.TIMEOUT) {
|
||||||
throw new StateChangingError(ConnectionState.SESSION_FAILURE);
|
throw new StateChangingError(ConnectionState.SESSION_FAILURE);
|
||||||
}
|
}
|
||||||
|
@ -1880,9 +1862,9 @@ public class XmppConnection implements Runnable {
|
||||||
true);
|
true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO the return value is not used any more
|
||||||
private boolean enableStreamManagement() {
|
private boolean enableStreamManagement() {
|
||||||
final boolean streamManagement =
|
final boolean streamManagement = this.streamFeatures.streamManagement();
|
||||||
this.streamFeatures.hasChild("sm", Namespace.STREAM_MANAGEMENT);
|
|
||||||
if (streamManagement) {
|
if (streamManagement) {
|
||||||
synchronized (this.mStanzaQueue) {
|
synchronized (this.mStanzaQueue) {
|
||||||
final EnablePacket enable = new EnablePacket();
|
final EnablePacket enable = new EnablePacket();
|
||||||
|
@ -1896,51 +1878,45 @@ public class XmppConnection implements Runnable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void sendPostBindInitialization(
|
private void sendPostBindInitialization(final boolean carbonsEnabled) {
|
||||||
final boolean waitForDisco, final boolean carbonsEnabled) {
|
this.carbonsEnabled = carbonsEnabled;
|
||||||
features.carbonsEnabled = carbonsEnabled;
|
|
||||||
features.blockListRequested = false;
|
|
||||||
synchronized (this.disco) {
|
|
||||||
this.disco.clear();
|
|
||||||
}
|
|
||||||
Log.d(Config.LOGTAG, account.address + ": starting service discovery");
|
Log.d(Config.LOGTAG, account.address + ": starting service discovery");
|
||||||
mPendingServiceDiscoveries.set(0);
|
final ArrayList<ListenableFuture<?>> discoFutures = new ArrayList<>();
|
||||||
mWaitForDisco.set(waitForDisco);
|
final var discoManager = getManager(DiscoManager.class);
|
||||||
lastDiscoStarted = SystemClock.elapsedRealtime();
|
|
||||||
// TODO bring back disco timeout
|
|
||||||
// context.scheduleWakeUpCall(Config.CONNECT_DISCO_TIMEOUT, account);
|
|
||||||
final 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) {
|
|
||||||
// Bring back disco result caching
|
|
||||||
discoveryResult =
|
|
||||||
null; // context.getCachedServiceDiscoveryResult(new Pair<>(hash, ver));
|
|
||||||
}
|
|
||||||
// TODO from an older git commit "should make initial connect faster because code is not
|
|
||||||
// waiting for omemo code to run" - do we need to keep this?
|
|
||||||
final boolean requestDiscoItemsFirst =
|
|
||||||
!ConversationsDatabase.getInstance(context).accountDao().isInitialLogin(account.id);
|
|
||||||
|
|
||||||
if (requestDiscoItemsFirst) {
|
final var nodeHash = this.streamFeatures.getCapabilities();
|
||||||
sendServiceDiscoveryItems(account.address.getDomain());
|
if (nodeHash != null) {
|
||||||
}
|
discoFutures.add(
|
||||||
if (discoveryResult == null) {
|
discoManager.info(account.address.getDomain(), nodeHash.node, nodeHash.hash));
|
||||||
sendServiceDiscoveryInfo(account.address.getDomain());
|
|
||||||
} else {
|
} else {
|
||||||
Log.d(Config.LOGTAG, account.address + ": server caps came from cache");
|
discoFutures.add(discoManager.info(account.address.getDomain()));
|
||||||
disco.put(account.address.getDomain(), discoveryResult);
|
|
||||||
}
|
|
||||||
discoverMamPreferences();
|
|
||||||
sendServiceDiscoveryInfo(account.address);
|
|
||||||
if (!requestDiscoItemsFirst) {
|
|
||||||
sendServiceDiscoveryItems(account.address.getDomain());
|
|
||||||
}
|
}
|
||||||
|
discoFutures.add(discoManager.info(account.address));
|
||||||
|
discoFutures.add(discoManager.itemsWithInfo(account.address.getDomain()));
|
||||||
|
|
||||||
if (!mWaitForDisco.get()) {
|
final var discoFuture =
|
||||||
finalizeBind();
|
Futures.withTimeout(
|
||||||
}
|
Futures.allAsList(discoFutures),
|
||||||
|
Config.CONNECT_DISCO_TIMEOUT,
|
||||||
|
TimeUnit.SECONDS,
|
||||||
|
ConnectionPool.CONNECTION_SCHEDULER);
|
||||||
|
|
||||||
|
Futures.addCallback(
|
||||||
|
discoFuture,
|
||||||
|
new FutureCallback<>() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess(List<Object> result) {
|
||||||
|
// TODO enable advanced stream features like carbons
|
||||||
|
finalizeBind();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(@NonNull Throwable t) {
|
||||||
|
// TODO reset stream ID so we get a proper connect next time
|
||||||
|
finalizeBind();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
MoreExecutors.directExecutor());
|
||||||
this.lastSessionStarted = SystemClock.elapsedRealtime();
|
this.lastSessionStarted = SystemClock.elapsedRealtime();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1949,64 +1925,6 @@ public class XmppConnection implements Runnable {
|
||||||
return this.connectionState;
|
return this.connectionState;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void sendServiceDiscoveryInfo(final Jid jid) {
|
|
||||||
mPendingServiceDiscoveries.incrementAndGet();
|
|
||||||
final IqPacket iq = new IqPacket(IqPacket.TYPE.GET);
|
|
||||||
iq.setTo(jid);
|
|
||||||
iq.query("http://jabber.org/protocol/disco#info");
|
|
||||||
this.sendIqPacket(
|
|
||||||
iq,
|
|
||||||
(packet) -> {
|
|
||||||
if (packet.getType() == IqPacket.TYPE.RESULT) {
|
|
||||||
boolean advancedStreamFeaturesLoaded;
|
|
||||||
synchronized (XmppConnection.this.disco) {
|
|
||||||
ServiceDiscoveryResult result = new ServiceDiscoveryResult(packet);
|
|
||||||
if (jid.equals(account.address.getDomain())) {
|
|
||||||
// context.databaseBackend.insertDiscoveryResult(result);
|
|
||||||
}
|
|
||||||
disco.put(jid, result);
|
|
||||||
advancedStreamFeaturesLoaded =
|
|
||||||
disco.containsKey(account.address.getDomain())
|
|
||||||
&& disco.containsKey(account.address);
|
|
||||||
}
|
|
||||||
if (advancedStreamFeaturesLoaded
|
|
||||||
&& (jid.equals(account.address.getDomain())
|
|
||||||
|| jid.equals(account.address))) {
|
|
||||||
enableAdvancedStreamFeatures();
|
|
||||||
}
|
|
||||||
} else if (packet.getType() == IqPacket.TYPE.ERROR) {
|
|
||||||
Log.d(
|
|
||||||
Config.LOGTAG,
|
|
||||||
account.address
|
|
||||||
+ ": could not query disco info for "
|
|
||||||
+ jid.toString());
|
|
||||||
final boolean serverOrAccount =
|
|
||||||
jid.equals(account.address.getDomain())
|
|
||||||
|| jid.equals(account.address);
|
|
||||||
final boolean advancedStreamFeaturesLoaded;
|
|
||||||
if (serverOrAccount) {
|
|
||||||
synchronized (XmppConnection.this.disco) {
|
|
||||||
disco.put(jid, ServiceDiscoveryResult.empty());
|
|
||||||
advancedStreamFeaturesLoaded =
|
|
||||||
disco.containsKey(account.address.getDomain())
|
|
||||||
&& disco.containsKey(account.address);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
advancedStreamFeaturesLoaded = false;
|
|
||||||
}
|
|
||||||
if (advancedStreamFeaturesLoaded) {
|
|
||||||
enableAdvancedStreamFeatures();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (packet.getType() != IqPacket.TYPE.TIMEOUT) {
|
|
||||||
if (mPendingServiceDiscoveries.decrementAndGet() == 0
|
|
||||||
&& mWaitForDisco.compareAndSet(true, false)) {
|
|
||||||
finalizeBind();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void discoverMamPreferences() {
|
private void discoverMamPreferences() {
|
||||||
IqPacket request = new IqPacket(IqPacket.TYPE.GET);
|
IqPacket request = new IqPacket(IqPacket.TYPE.GET);
|
||||||
request.addChild("prefs", MessageArchiveService.Version.MAM_2.namespace);
|
request.addChild("prefs", MessageArchiveService.Version.MAM_2.namespace);
|
||||||
|
@ -2067,56 +1985,15 @@ public class XmppConnection implements Runnable {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void enableAdvancedStreamFeatures() {
|
private void enableAdvancedStreamFeatures() {
|
||||||
if (getFeatures().blocking() && !features.blockListRequested) {
|
if (getManager(DiscoManager.class)
|
||||||
Log.d(Config.LOGTAG, account.address + ": Requesting block list");
|
.isFeature(connectionAddress.getDomain(), Namespace.CARBONS)
|
||||||
// TODO actually request block list
|
&& !this.carbonsEnabled) {
|
||||||
/*this.sendIqPacket(
|
|
||||||
getIqGenerator().generateGetBlockList(), context.getIqParser());*/
|
|
||||||
}
|
|
||||||
if (getFeatures().carbons() && !features.carbonsEnabled) {
|
|
||||||
sendEnableCarbons();
|
sendEnableCarbons();
|
||||||
}
|
}
|
||||||
if (getFeatures().commands()) {
|
// TODO discover commands
|
||||||
|
/*if (getFeatures().commands()) {
|
||||||
discoverCommands();
|
discoverCommands();
|
||||||
}
|
}*/
|
||||||
}
|
|
||||||
|
|
||||||
private void sendServiceDiscoveryItems(final Jid server) {
|
|
||||||
mPendingServiceDiscoveries.incrementAndGet();
|
|
||||||
final IqPacket iq = new IqPacket(IqPacket.TYPE.GET);
|
|
||||||
iq.setTo(server.getDomain());
|
|
||||||
iq.query("http://jabber.org/protocol/disco#items");
|
|
||||||
this.sendIqPacket(
|
|
||||||
iq,
|
|
||||||
(packet) -> {
|
|
||||||
if (packet.getType() == IqPacket.TYPE.RESULT) {
|
|
||||||
final HashSet<Jid> items = new HashSet<>();
|
|
||||||
final List<Element> elements = packet.query().getChildren();
|
|
||||||
for (final Element element : elements) {
|
|
||||||
if (element.getName().equals("item")) {
|
|
||||||
final Jid jid =
|
|
||||||
InvalidJid.getNullForInvalid(
|
|
||||||
element.getAttributeAsJid("jid"));
|
|
||||||
if (jid != null && !jid.equals(account.address.getDomain())) {
|
|
||||||
items.add(jid);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (Jid jid : items) {
|
|
||||||
sendServiceDiscoveryInfo(jid);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Log.d(
|
|
||||||
Config.LOGTAG,
|
|
||||||
account.address + ": could not query disco items of " + server);
|
|
||||||
}
|
|
||||||
if (packet.getType() != IqPacket.TYPE.TIMEOUT) {
|
|
||||||
if (mPendingServiceDiscoveries.decrementAndGet() == 0
|
|
||||||
&& mWaitForDisco.compareAndSet(true, false)) {
|
|
||||||
finalizeBind();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void sendEnableCarbons() {
|
private void sendEnableCarbons() {
|
||||||
|
@ -2127,7 +2004,7 @@ public class XmppConnection implements Runnable {
|
||||||
(packet) -> {
|
(packet) -> {
|
||||||
if (packet.getType() == IqPacket.TYPE.RESULT) {
|
if (packet.getType() == IqPacket.TYPE.RESULT) {
|
||||||
Log.d(Config.LOGTAG, account.address + ": successfully enabled carbons");
|
Log.d(Config.LOGTAG, account.address + ": successfully enabled carbons");
|
||||||
features.carbonsEnabled = true;
|
this.carbonsEnabled = true;
|
||||||
} else {
|
} else {
|
||||||
Log.d(
|
Log.d(
|
||||||
Config.LOGTAG,
|
Config.LOGTAG,
|
||||||
|
@ -2412,28 +2289,8 @@ public class XmppConnection implements Runnable {
|
||||||
this.streamId = null;
|
this.streamId = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<Entry<Jid, ServiceDiscoveryResult>> findDiscoItemsByFeature(final String feature) {
|
|
||||||
synchronized (this.disco) {
|
|
||||||
final List<Entry<Jid, ServiceDiscoveryResult>> items = new ArrayList<>();
|
|
||||||
for (final Entry<Jid, ServiceDiscoveryResult> cursor : this.disco.entrySet()) {
|
|
||||||
if (cursor.getValue().getFeatures().contains(feature)) {
|
|
||||||
items.add(cursor);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return items;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Jid findDiscoItemByFeature(final String feature) {
|
|
||||||
final List<Entry<Jid, ServiceDiscoveryResult>> items = findDiscoItemsByFeature(feature);
|
|
||||||
if (items.size() >= 1) {
|
|
||||||
return items.get(0).getKey();
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean r() {
|
public boolean r() {
|
||||||
if (getFeatures().sm()) {
|
if (this.inSmacksSession) {
|
||||||
this.tagWriter.writeStanzaAsync(new RequestPacket());
|
this.tagWriter.writeStanzaAsync(new RequestPacket());
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
|
@ -2441,33 +2298,6 @@ public class XmppConnection implements Runnable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<String> getMucServersWithholdAccount() {
|
|
||||||
final List<String> servers = getMucServers();
|
|
||||||
servers.remove(account.address.getDomain().toEscapedString());
|
|
||||||
return servers;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<String> getMucServers() {
|
|
||||||
List<String> servers = new ArrayList<>();
|
|
||||||
synchronized (this.disco) {
|
|
||||||
for (final Entry<Jid, ServiceDiscoveryResult> cursor : disco.entrySet()) {
|
|
||||||
final ServiceDiscoveryResult value = cursor.getValue();
|
|
||||||
if (value.getFeatures().contains("http://jabber.org/protocol/muc")
|
|
||||||
&& value.hasIdentity("conference", "text")
|
|
||||||
&& !value.getFeatures().contains("jabber:iq:gateway")
|
|
||||||
&& !value.hasIdentity("conference", "irc")) {
|
|
||||||
servers.add(cursor.getKey().toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return servers;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getMucServer() {
|
|
||||||
List<String> servers = getMucServers();
|
|
||||||
return servers.size() > 0 ? servers.get(0) : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getTimeToNextAttempt() {
|
public int getTimeToNextAttempt() {
|
||||||
final int additionalTime =
|
final int additionalTime =
|
||||||
recentErrorConnectionState == ConnectionState.POLICY_VIOLATION ? 3 : 0;
|
recentErrorConnectionState == ConnectionState.POLICY_VIOLATION ? 3 : 0;
|
||||||
|
@ -2481,10 +2311,6 @@ public class XmppConnection implements Runnable {
|
||||||
return this.attempt;
|
return this.attempt;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Features getFeatures() {
|
|
||||||
return this.features;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getLastSessionEstablished() {
|
public long getLastSessionEstablished() {
|
||||||
final long diff = SystemClock.elapsedRealtime() - this.lastSessionStarted;
|
final long diff = SystemClock.elapsedRealtime() - this.lastSessionStarted;
|
||||||
return System.currentTimeMillis() - diff;
|
return System.currentTimeMillis() - diff;
|
||||||
|
@ -2498,10 +2324,6 @@ public class XmppConnection implements Runnable {
|
||||||
return this.lastPingSent;
|
return this.lastPingSent;
|
||||||
}
|
}
|
||||||
|
|
||||||
public long getLastDiscoStarted() {
|
|
||||||
return this.lastDiscoStarted;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getLastPacketReceived() {
|
public long getLastPacketReceived() {
|
||||||
return this.lastPacketReceived;
|
return this.lastPacketReceived;
|
||||||
}
|
}
|
||||||
|
@ -2542,6 +2364,10 @@ public class XmppConnection implements Runnable {
|
||||||
return from != null && from.asBareJid().equals(connectionAddress.asBareJid());
|
return from != null && from.asBareJid().equals(connectionAddress.asBareJid());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean supportsClientStateIndication() {
|
||||||
|
return this.streamFeatures != null && this.streamFeatures.clientStateIndication();
|
||||||
|
}
|
||||||
|
|
||||||
private static class MyKeyManager implements X509KeyManager {
|
private static class MyKeyManager implements X509KeyManager {
|
||||||
|
|
||||||
private final Context context;
|
private final Context context;
|
||||||
|
@ -2610,204 +2436,6 @@ public class XmppConnection implements Runnable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class Features {
|
|
||||||
XmppConnection connection;
|
|
||||||
private boolean carbonsEnabled = false;
|
|
||||||
private boolean encryptionEnabled = false;
|
|
||||||
private boolean blockListRequested = false;
|
|
||||||
|
|
||||||
public Features(final XmppConnection connection) {
|
|
||||||
this.connection = connection;
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean hasDiscoFeature(final Jid server, final String feature) {
|
|
||||||
synchronized (XmppConnection.this.disco) {
|
|
||||||
final ServiceDiscoveryResult sdr = connection.disco.get(server);
|
|
||||||
return sdr != null && sdr.getFeatures().contains(feature);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean carbons() {
|
|
||||||
return hasDiscoFeature(account.address.getDomain(), Namespace.CARBONS);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean commands() {
|
|
||||||
return hasDiscoFeature(account.address.getDomain(), Namespace.COMMANDS);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean easyOnboardingInvites() {
|
|
||||||
synchronized (commands) {
|
|
||||||
return commands.containsKey(Namespace.EASY_ONBOARDING_INVITE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean bookmarksConversion() {
|
|
||||||
return hasDiscoFeature(account.address, Namespace.BOOKMARKS_CONVERSION)
|
|
||||||
&& pepPublishOptions();
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean avatarConversion() {
|
|
||||||
return hasDiscoFeature(account.address, Namespace.AVATAR_CONVERSION)
|
|
||||||
&& pepPublishOptions();
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean blocking() {
|
|
||||||
return hasDiscoFeature(account.address.getDomain(), Namespace.BLOCKING);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean spamReporting() {
|
|
||||||
return hasDiscoFeature(account.address.getDomain(), "urn:xmpp:reporting:reason:spam:0");
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean flexibleOfflineMessageRetrieval() {
|
|
||||||
return hasDiscoFeature(
|
|
||||||
account.address.getDomain(), Namespace.FLEXIBLE_OFFLINE_MESSAGE_RETRIEVAL);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean register() {
|
|
||||||
return hasDiscoFeature(account.address.getDomain(), Namespace.REGISTER);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean invite() {
|
|
||||||
return connection.streamFeatures != null
|
|
||||||
&& connection.streamFeatures.hasChild("register", Namespace.INVITE);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean sm() {
|
|
||||||
return streamId != null
|
|
||||||
|| (connection.streamFeatures != null
|
|
||||||
&& connection.streamFeatures.hasChild(
|
|
||||||
"sm", Namespace.STREAM_MANAGEMENT));
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean csi() {
|
|
||||||
return connection.streamFeatures != null
|
|
||||||
&& connection.streamFeatures.hasChild("csi", Namespace.CSI);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean pep() {
|
|
||||||
synchronized (XmppConnection.this.disco) {
|
|
||||||
ServiceDiscoveryResult info = disco.get(account.address);
|
|
||||||
return info != null && info.hasIdentity("pubsub", "pep");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean pepPersistent() {
|
|
||||||
synchronized (XmppConnection.this.disco) {
|
|
||||||
ServiceDiscoveryResult info = disco.get(account.address);
|
|
||||||
return info != null
|
|
||||||
&& info.getFeatures()
|
|
||||||
.contains("http://jabber.org/protocol/pubsub#persistent-items");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean pepPublishOptions() {
|
|
||||||
return hasDiscoFeature(account.address, Namespace.PUBSUB_PUBLISH_OPTIONS);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean pepOmemoWhitelisted() {
|
|
||||||
return hasDiscoFeature(account.address, AxolotlService.PEP_OMEMO_WHITELISTED);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean mam() {
|
|
||||||
return MessageArchiveService.Version.has(getAccountFeatures());
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<String> getAccountFeatures() {
|
|
||||||
ServiceDiscoveryResult result = connection.disco.get(account.address);
|
|
||||||
return result == null ? Collections.emptyList() : result.getFeatures();
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean push() {
|
|
||||||
return hasDiscoFeature(account.address, Namespace.PUSH)
|
|
||||||
|| hasDiscoFeature(account.address.getDomain(), Namespace.PUSH);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean rosterVersioning() {
|
|
||||||
return connection.streamFeatures != null && connection.streamFeatures.hasChild("ver");
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setBlockListRequested(boolean value) {
|
|
||||||
this.blockListRequested = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean httpUpload(long filesize) {
|
|
||||||
if (Config.DISABLE_HTTP_UPLOAD) {
|
|
||||||
return false;
|
|
||||||
} else {
|
|
||||||
for (String namespace :
|
|
||||||
new String[] {Namespace.HTTP_UPLOAD, Namespace.HTTP_UPLOAD_LEGACY}) {
|
|
||||||
List<Entry<Jid, ServiceDiscoveryResult>> items =
|
|
||||||
findDiscoItemsByFeature(namespace);
|
|
||||||
if (items.size() > 0) {
|
|
||||||
try {
|
|
||||||
long maxsize =
|
|
||||||
Long.parseLong(
|
|
||||||
items.get(0)
|
|
||||||
.getValue()
|
|
||||||
.getExtendedDiscoInformation(
|
|
||||||
namespace, "max-file-size"));
|
|
||||||
if (filesize <= maxsize) {
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
Log.d(
|
|
||||||
Config.LOGTAG,
|
|
||||||
account.address
|
|
||||||
+ ": http upload is not available for files with"
|
|
||||||
+ " size "
|
|
||||||
+ filesize
|
|
||||||
+ " (max is "
|
|
||||||
+ maxsize
|
|
||||||
+ ")");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean useLegacyHttpUpload() {
|
|
||||||
return findDiscoItemByFeature(Namespace.HTTP_UPLOAD) == null
|
|
||||||
&& findDiscoItemByFeature(Namespace.HTTP_UPLOAD_LEGACY) != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getMaxHttpUploadSize() {
|
|
||||||
for (String namespace :
|
|
||||||
new String[] {Namespace.HTTP_UPLOAD, Namespace.HTTP_UPLOAD_LEGACY}) {
|
|
||||||
List<Entry<Jid, ServiceDiscoveryResult>> items = findDiscoItemsByFeature(namespace);
|
|
||||||
if (items.size() > 0) {
|
|
||||||
try {
|
|
||||||
return Long.parseLong(
|
|
||||||
items.get(0)
|
|
||||||
.getValue()
|
|
||||||
.getExtendedDiscoInformation(namespace, "max-file-size"));
|
|
||||||
} catch (Exception e) {
|
|
||||||
// ignored
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean stanzaIds() {
|
|
||||||
return hasDiscoFeature(account.address, Namespace.STANZA_IDS);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean bookmarks2() {
|
|
||||||
return Config
|
|
||||||
.USE_BOOKMARKS2 /* || hasDiscoFeature(account.address, Namespace.BOOKMARKS2_COMPAT)*/;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean externalServiceDiscovery() {
|
|
||||||
return hasDiscoFeature(
|
|
||||||
account.address.getDomain(), Namespace.EXTERNAL_SERVICE_DISCOVERY);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public abstract static class Delegate {
|
public abstract static class Delegate {
|
||||||
|
|
||||||
protected final Context context;
|
protected final Context context;
|
||||||
|
|
|
@ -91,10 +91,18 @@ public class DiscoManager extends AbstractManager {
|
||||||
|
|
||||||
public ListenableFuture<List<InfoQuery>> itemsWithInfo(final Jid entity) {
|
public ListenableFuture<List<InfoQuery>> itemsWithInfo(final Jid entity) {
|
||||||
final var itemsFutures = items(entity);
|
final var itemsFutures = items(entity);
|
||||||
return Futures.transformAsync(itemsFutures, items -> {
|
return Futures.transformAsync(
|
||||||
// TODO filter out items with empty jid
|
itemsFutures,
|
||||||
Collection<ListenableFuture<InfoQuery>> infoFutures = Collections2.transform(items, i -> info(i.getJid(), i.getNode()));
|
items -> {
|
||||||
return Futures.allAsList(infoFutures);
|
// TODO filter out items with empty jid
|
||||||
}, MoreExecutors.directExecutor());
|
Collection<ListenableFuture<InfoQuery>> infoFutures =
|
||||||
|
Collections2.transform(items, i -> info(i.getJid(), i.getNode()));
|
||||||
|
return Futures.allAsList(infoFutures);
|
||||||
|
},
|
||||||
|
MoreExecutors.directExecutor());
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isFeature(final Jid entity, final String feature) {
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,36 @@
|
||||||
|
package im.conversations.android.xmpp.model.capabilties;
|
||||||
|
|
||||||
|
import im.conversations.android.xmpp.model.Extension;
|
||||||
|
|
||||||
|
public interface EntityCapabilities {
|
||||||
|
|
||||||
|
<E extends Extension> E getExtension(final Class<E> clazz);
|
||||||
|
|
||||||
|
default NodeHash getCapabilities() {
|
||||||
|
final String node;
|
||||||
|
final im.conversations.android.xmpp.EntityCapabilities.Hash hash;
|
||||||
|
final var capabilities = this.getExtension(Capabilities.class);
|
||||||
|
final var legacyCapabilities = this.getExtension(LegacyCapabilities.class);
|
||||||
|
if (capabilities != null) {
|
||||||
|
node = null;
|
||||||
|
hash = capabilities.getHash();
|
||||||
|
} else if (legacyCapabilities != null) {
|
||||||
|
node = legacyCapabilities.getNode();
|
||||||
|
hash = legacyCapabilities.getHash();
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return new NodeHash(node, hash);
|
||||||
|
}
|
||||||
|
|
||||||
|
class NodeHash {
|
||||||
|
public final String node;
|
||||||
|
public final im.conversations.android.xmpp.EntityCapabilities.Hash hash;
|
||||||
|
|
||||||
|
private NodeHash(
|
||||||
|
String node, final im.conversations.android.xmpp.EntityCapabilities.Hash hash) {
|
||||||
|
this.node = node;
|
||||||
|
this.hash = hash;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
package im.conversations.android.xmpp.model.streams;
|
||||||
|
|
||||||
|
import eu.siacs.conversations.xml.Namespace;
|
||||||
|
import im.conversations.android.annotation.XmlElement;
|
||||||
|
import im.conversations.android.xmpp.model.Extension;
|
||||||
|
import im.conversations.android.xmpp.model.capabilties.EntityCapabilities;
|
||||||
|
|
||||||
|
@XmlElement
|
||||||
|
public class Features extends Extension implements EntityCapabilities {
|
||||||
|
public Features() {
|
||||||
|
super(Features.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean streamManagement() {
|
||||||
|
// TODO use hasExtension
|
||||||
|
return this.hasChild("sm", Namespace.STREAM_MANAGEMENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean invite() {
|
||||||
|
return this.hasChild("register", Namespace.INVITE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean clientStateIndication() {
|
||||||
|
return this.hasChild("csi", Namespace.CSI);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
@XmlPackage(namespace = Namespace.STREAMS)
|
||||||
|
package im.conversations.android.xmpp.model.streams;
|
||||||
|
|
||||||
|
import eu.siacs.conversations.xml.Namespace;
|
||||||
|
import im.conversations.android.annotation.XmlPackage;
|
|
@ -2,11 +2,8 @@ package im.conversations.android.xmpp.processor;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import eu.siacs.conversations.xmpp.stanzas.PresencePacket;
|
import eu.siacs.conversations.xmpp.stanzas.PresencePacket;
|
||||||
import im.conversations.android.xmpp.EntityCapabilities;
|
|
||||||
import im.conversations.android.xmpp.XmppConnection;
|
import im.conversations.android.xmpp.XmppConnection;
|
||||||
import im.conversations.android.xmpp.manager.DiscoManager;
|
import im.conversations.android.xmpp.manager.DiscoManager;
|
||||||
import im.conversations.android.xmpp.model.capabilties.Capabilities;
|
|
||||||
import im.conversations.android.xmpp.model.capabilties.LegacyCapabilities;
|
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
public class PresenceProcessor extends XmppConnection.Delegate implements Consumer<PresencePacket> {
|
public class PresenceProcessor extends XmppConnection.Delegate implements Consumer<PresencePacket> {
|
||||||
|
@ -23,22 +20,9 @@ public class PresenceProcessor extends XmppConnection.Delegate implements Consum
|
||||||
|
|
||||||
private void fetchCapabilities(final PresencePacket presencePacket) {
|
private void fetchCapabilities(final PresencePacket presencePacket) {
|
||||||
final var entity = presencePacket.getFrom();
|
final var entity = presencePacket.getFrom();
|
||||||
final String node;
|
final var nodeHash = presencePacket.getCapabilities();
|
||||||
final EntityCapabilities.Hash hash;
|
if (nodeHash != null) {
|
||||||
final var capabilities = presencePacket.getExtension(Capabilities.class);
|
getManager(DiscoManager.class).info(entity, nodeHash.node, nodeHash.hash);
|
||||||
final var legacyCapabilities = presencePacket.getExtension(LegacyCapabilities.class);
|
|
||||||
if (capabilities != null) {
|
|
||||||
node = null;
|
|
||||||
hash = capabilities.getHash();
|
|
||||||
} else if (legacyCapabilities != null) {
|
|
||||||
node = legacyCapabilities.getNode();
|
|
||||||
hash = legacyCapabilities.getHash();
|
|
||||||
} else {
|
|
||||||
node = null;
|
|
||||||
hash = null;
|
|
||||||
}
|
|
||||||
if (hash != null) {
|
|
||||||
getManager(DiscoManager.class).info(entity, node, hash);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue