rudimentary bind 2 implementation

This commit is contained in:
Daniel Gultsch 2022-09-03 20:17:29 +02:00
parent e204457c31
commit 052c58f377
3 changed files with 94 additions and 31 deletions

View file

@ -313,7 +313,7 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
private boolean handleErrorMessage(final Account account, final MessagePacket packet) { private boolean handleErrorMessage(final Account account, final MessagePacket packet) {
if (packet.getType() == MessagePacket.TYPE_ERROR) { if (packet.getType() == MessagePacket.TYPE_ERROR) {
if (packet.fromServer(account)) { if (packet.fromServer(account)) {
final Pair<MessagePacket, Long> forwarded = packet.getForwardedMessagePacket("received", "urn:xmpp:carbons:2"); final Pair<MessagePacket, Long> forwarded = packet.getForwardedMessagePacket("received", Namespace.CARBONS);
if (forwarded != null) { if (forwarded != null) {
return handleErrorMessage(account, forwarded.first); return handleErrorMessage(account, forwarded.first);
} }
@ -389,8 +389,8 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
return; return;
} else if (original.fromServer(account)) { } else if (original.fromServer(account)) {
Pair<MessagePacket, Long> f; Pair<MessagePacket, Long> f;
f = original.getForwardedMessagePacket("received", "urn:xmpp:carbons:2"); f = original.getForwardedMessagePacket("received", Namespace.CARBONS);
f = f == null ? original.getForwardedMessagePacket("sent", "urn:xmpp:carbons:2") : f; f = f == null ? original.getForwardedMessagePacket("sent", Namespace.CARBONS) : f;
packet = f != null ? f.first : original; packet = f != null ? f.first : original;
if (handleErrorMessage(account, packet)) { if (handleErrorMessage(account, packet)) {
return; return;

View file

@ -25,9 +25,10 @@ public final class Namespace {
public static final String NICK = "http://jabber.org/protocol/nick"; public static final String NICK = "http://jabber.org/protocol/nick";
public static final String FLEXIBLE_OFFLINE_MESSAGE_RETRIEVAL = "http://jabber.org/protocol/offline"; public static final String FLEXIBLE_OFFLINE_MESSAGE_RETRIEVAL = "http://jabber.org/protocol/offline";
public static final String BIND = "urn:ietf:params:xml:ns:xmpp-bind"; public static final String BIND = "urn:ietf:params:xml:ns:xmpp-bind";
public static final String BIND2 = "urn:xmpp:bind2:0"; public static final String BIND2 = "urn:xmpp:bind2:1";
public static final String STREAM_MANAGEMENT = "urn:xmpp:sm:3"; public static final String STREAM_MANAGEMENT = "urn:xmpp:sm:3";
public static final String CSI = "urn:xmpp:csi:0"; public static final String CSI = "urn:xmpp:csi:0";
public static final String CARBONS = "urn:xmpp:carbons:2";
public static final String BOOKMARKS_CONVERSION = "urn:xmpp:bookmarks-conversion:0"; public static final String BOOKMARKS_CONVERSION = "urn:xmpp:bookmarks-conversion:0";
public static final String BOOKMARKS = "storage:bookmarks"; public static final String BOOKMARKS = "storage:bookmarks";
public static final String SYNCHRONIZATION = "im.quicksy.synchronization:0"; public static final String SYNCHRONIZATION = "im.quicksy.synchronization:0";

View file

@ -13,6 +13,7 @@ import android.util.SparseArray;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import com.google.common.base.Strings; import com.google.common.base.Strings;
import com.google.common.collect.Collections2;
import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlPullParserException;
@ -32,6 +33,7 @@ import java.security.PrivateKey;
import java.security.cert.X509Certificate; 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.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
@ -274,7 +276,7 @@ public class XmppConnection implements Runnable {
this.attempt++; this.attempt++;
this.verifiedHostname = this.verifiedHostname =
null; // will be set if user entered hostname is being used or hostname was verified null; // will be set if user entered hostname is being used or hostname was verified
// with dnssec // with dnssec
try { try {
Socket localSocket; Socket localSocket;
shouldAuthenticate = !account.isOptionSet(Account.OPTION_REGISTER); shouldAuthenticate = !account.isOptionSet(Account.OPTION_REGISTER);
@ -409,7 +411,7 @@ public class XmppConnection implements Runnable {
if (startXmpp(localSocket)) { if (startXmpp(localSocket)) {
localSocket.setSoTimeout( localSocket.setSoTimeout(
0); // reset to 0; once the connection is established we dont 0); // reset to 0; once the connection is established we dont
// want this // want this
if (!hardcoded && !result.equals(storedBackupResult)) { if (!hardcoded && !result.equals(storedBackupResult)) {
mXmppConnectionService.databaseBackend.saveResolverResult( mXmppConnectionService.databaseBackend.saveResolverResult(
domain, result); domain, result);
@ -615,24 +617,9 @@ public class XmppConnection implements Runnable {
throw new StateChangingException(Account.State.UNAUTHORIZED); throw new StateChangingException(Account.State.UNAUTHORIZED);
} }
tagWriter.writeElement(response); tagWriter.writeElement(response);
} else if (nextTag.isStart("enabled")) { } else if (nextTag.isStart("enabled", Namespace.STREAM_MANAGEMENT)) {
final Element enabled = tagReader.readElement(nextTag); final Element enabled = tagReader.readElement(nextTag);
if (enabled.getAttributeAsBoolean("resume")) { processEnabled(enabled);
this.streamId = enabled.getAttribute("id");
Log.d(
Config.LOGTAG,
account.getJid().asBareJid().toString()
+ ": stream management enabled (resumable)");
} else {
Log.d(
Config.LOGTAG,
account.getJid().asBareJid().toString()
+ ": stream management enabled");
}
this.stanzasReceived = 0;
this.inSmacksSession = true;
final RequestPacket r = new RequestPacket();
tagWriter.writeStanzaAsync(r);
} else if (nextTag.isStart("resumed")) { } else if (nextTag.isStart("resumed")) {
final Element resumed = tagReader.readElement(nextTag); final Element resumed = tagReader.readElement(nextTag);
processResumed(resumed); processResumed(resumed);
@ -771,13 +758,31 @@ public class XmppConnection implements Runnable {
+ ": jid changed during SASL 2.0. updating database"); + ": jid changed during SASL 2.0. updating database");
mXmppConnectionService.databaseBackend.updateAccount(account); mXmppConnectionService.databaseBackend.updateAccount(account);
} }
final Element bound = success.findChild("bound", Namespace.BIND2);
final Element resumed = success.findChild("resumed", "urn:xmpp:sm:3"); final Element resumed = success.findChild("resumed", "urn:xmpp:sm:3");
final Element failed = success.findChild("failed", "urn:xmpp:sm:3"); final Element failed = success.findChild("failed", "urn:xmpp:sm:3");
// TODO check if resumed and bound exist and throw bind failure
if (resumed != null && streamId != null) { if (resumed != null && streamId != null) {
processResumed(resumed); processResumed(resumed);
} else if (failed != null) { } else if (failed != null) {
processFailed(failed, false); // wait for new stream features processFailed(failed, false); // wait for new stream features
} }
if (bound != null) {
this.isBound = true;
final Element streamManagementEnabled =
bound.findChild("enabled", Namespace.STREAM_MANAGEMENT);
final Element carbonsEnabled = bound.findChild("enabled", Namespace.CARBONS);
if (streamManagementEnabled != null) {
processEnabled(streamManagementEnabled);
}
if (carbonsEnabled != null) {
Log.d(
Config.LOGTAG,
account.getJid().asBareJid() + ": successfully enabled carbons");
features.carbonsEnabled = true;
}
sendPostBindInitialization(streamManagementEnabled != null, carbonsEnabled != null);
}
} }
if (version == SaslMechanism.Version.SASL) { if (version == SaslMechanism.Version.SASL) {
tagReader.reset(); tagReader.reset();
@ -794,6 +799,27 @@ public class XmppConnection implements Runnable {
} }
} }
private void processEnabled(final Element enabled) {
final String streamId;
if (enabled.getAttributeAsBoolean("resume")) {
streamId = enabled.getAttribute("id");
Log.d(
Config.LOGTAG,
account.getJid().asBareJid().toString()
+ ": stream management enabled (resumable)");
} else {
Log.d(
Config.LOGTAG,
account.getJid().asBareJid().toString() + ": stream management enabled");
streamId = null;
}
this.streamId = streamId;
this.stanzasReceived = 0;
this.inSmacksSession = true;
final RequestPacket r = new RequestPacket();
// tagWriter.writeStanzaAsync(r);
}
private void processResumed(final Element resumed) throws StateChangingException { private void processResumed(final Element resumed) throws StateChangingException {
this.inSmacksSession = true; this.inSmacksSession = true;
this.isBound = true; this.isBound = true;
@ -1241,6 +1267,16 @@ public class XmppConnection implements Runnable {
final boolean inlineStreamManagement = final boolean inlineStreamManagement =
inline != null && inline.hasChild("sm", "urn:xmpp:sm:3"); inline != null && inline.hasChild("sm", "urn:xmpp:sm:3");
final boolean inlineBind2 = inline != null && inline.hasChild("bind", Namespace.BIND2); final boolean inlineBind2 = inline != null && inline.hasChild("bind", Namespace.BIND2);
final Element inlineBindFeatures =
this.streamFeatures.findChild("inline", Namespace.BIND2);
if (inlineBind2 && inlineBindFeatures != null) {
final Element bind =
generateBindRequest(
Collections2.transform(
inlineBindFeatures.getChildren(),
c -> c == null ? null : c.getAttribute("var")));
authenticate.addChild(bind);
}
if (inlineStreamManagement && streamId != null) { if (inlineStreamManagement && streamId != null) {
final ResumePacket resume = new ResumePacket(this.streamId, stanzasReceived); final ResumePacket resume = new ResumePacket(this.streamId, stanzasReceived);
this.mSmCatchupMessageCounter.set(0); this.mSmCatchupMessageCounter.set(0);
@ -1259,9 +1295,26 @@ public class XmppConnection implements Runnable {
+ "/" + "/"
+ saslMechanism.getMechanism()); + saslMechanism.getMechanism());
authenticate.setAttribute("mechanism", saslMechanism.getMechanism()); authenticate.setAttribute("mechanism", saslMechanism.getMechanism());
Log.d(Config.LOGTAG, "authenticate " + authenticate);
tagWriter.writeElement(authenticate); tagWriter.writeElement(authenticate);
} }
private Element generateBindRequest(final Collection<String> bindFeatures) {
Log.d(Config.LOGTAG, "inline bind features: " + bindFeatures);
final Element bind = new Element("bind", Namespace.BIND2);
final Element clientId = bind.addChild("client-id");
clientId.setAttribute("tag", mXmppConnectionService.getString(R.string.app_name));
clientId.setContent(account.getUuid());
final Element features = bind.addChild("features");
if (bindFeatures.contains(Namespace.CARBONS)) {
features.addChild("enable", Namespace.CARBONS);
}
if (bindFeatures.contains(Namespace.STREAM_MANAGEMENT)) {
features.addChild("enable", Namespace.STREAM_MANAGEMENT);
}
return bind;
}
private static List<String> extractMechanisms(final Element stream) { private static List<String> extractMechanisms(final Element stream) {
final ArrayList<String> mechanisms = new ArrayList<>(stream.getChildren().size()); final ArrayList<String> mechanisms = new ArrayList<>(stream.getChildren().size());
for (final Element child : stream.getChildren()) { for (final Element child : stream.getChildren()) {
@ -1469,7 +1522,8 @@ public class XmppConnection implements Runnable {
.hasChild("optional")) { .hasChild("optional")) {
sendStartSession(); sendStartSession();
} else { } else {
sendPostBindInitialization(); final boolean waitForDisco = enableStreamManagement();
sendPostBindInitialization(waitForDisco, false);
} }
return; return;
} catch (final IllegalArgumentException e) { } catch (final IllegalArgumentException e) {
@ -1565,7 +1619,8 @@ public class XmppConnection implements Runnable {
startSession, startSession,
(account, packet) -> { (account, packet) -> {
if (packet.getType() == IqPacket.TYPE.RESULT) { if (packet.getType() == IqPacket.TYPE.RESULT) {
sendPostBindInitialization(); final boolean waitForDisco = enableStreamManagement();
sendPostBindInitialization(waitForDisco, false);
} else if (packet.getType() != IqPacket.TYPE.TIMEOUT) { } else if (packet.getType() != IqPacket.TYPE.TIMEOUT) {
throw new StateChangingError(Account.State.SESSION_FAILURE); throw new StateChangingError(Account.State.SESSION_FAILURE);
} }
@ -1573,7 +1628,7 @@ public class XmppConnection implements Runnable {
true); true);
} }
private void sendPostBindInitialization() { private boolean enableStreamManagement() {
final boolean streamManagement = final boolean streamManagement =
this.streamFeatures.hasChild("sm", Namespace.STREAM_MANAGEMENT); this.streamFeatures.hasChild("sm", Namespace.STREAM_MANAGEMENT);
if (streamManagement) { if (streamManagement) {
@ -1583,15 +1638,22 @@ public class XmppConnection implements Runnable {
stanzasSent = 0; stanzasSent = 0;
mStanzaQueue.clear(); mStanzaQueue.clear();
} }
return true;
} else {
return false;
} }
features.carbonsEnabled = false; }
private void sendPostBindInitialization(
final boolean waitForDisco, final boolean carbonsEnabled) {
features.carbonsEnabled = carbonsEnabled;
features.blockListRequested = false; features.blockListRequested = false;
synchronized (this.disco) { synchronized (this.disco) {
this.disco.clear(); this.disco.clear();
} }
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": starting service discovery"); Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": starting service discovery");
mPendingServiceDiscoveries.set(0); mPendingServiceDiscoveries.set(0);
if (!streamManagement if (!waitForDisco
|| Patches.DISCO_EXCEPTIONS.contains( || Patches.DISCO_EXCEPTIONS.contains(
account.getJid().getDomain().toEscapedString())) { account.getJid().getDomain().toEscapedString())) {
Log.d( Log.d(
@ -1819,11 +1881,11 @@ public class XmppConnection implements Runnable {
private void sendEnableCarbons() { private void sendEnableCarbons() {
final IqPacket iq = new IqPacket(IqPacket.TYPE.SET); final IqPacket iq = new IqPacket(IqPacket.TYPE.SET);
iq.addChild("enable", "urn:xmpp:carbons:2"); iq.addChild("enable", Namespace.CARBONS);
this.sendIqPacket( this.sendIqPacket(
iq, iq,
(account, packet) -> { (account, packet) -> {
if (!packet.hasChild("error")) { if (packet.getType() == IqPacket.TYPE.RESULT) {
Log.d( Log.d(
Config.LOGTAG, Config.LOGTAG,
account.getJid().asBareJid() + ": successfully enabled carbons"); account.getJid().asBareJid() + ": successfully enabled carbons");
@ -2309,7 +2371,7 @@ public class XmppConnection implements Runnable {
} }
public boolean carbons() { public boolean carbons() {
return hasDiscoFeature(account.getDomain(), "urn:xmpp:carbons:2"); return hasDiscoFeature(account.getDomain(), Namespace.CARBONS);
} }
public boolean commands() { public boolean commands() {