respond to software version requests

This commit is contained in:
Daniel Gultsch 2023-01-25 20:58:32 +01:00
parent e073f22ec0
commit f1e1cf9653
No known key found for this signature in database
GPG key ID: F43D18AD2A0982C2
3 changed files with 94 additions and 45 deletions

View file

@ -1,6 +1,7 @@
package im.conversations.android.xmpp.manager; package im.conversations.android.xmpp.manager;
import android.content.Context; import android.content.Context;
import android.os.Build;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import com.google.common.base.Strings; import com.google.common.base.Strings;
@ -25,8 +26,10 @@ import im.conversations.android.xmpp.model.disco.items.Item;
import im.conversations.android.xmpp.model.disco.items.ItemsQuery; import im.conversations.android.xmpp.model.disco.items.ItemsQuery;
import im.conversations.android.xmpp.model.error.Condition; import im.conversations.android.xmpp.model.error.Condition;
import im.conversations.android.xmpp.model.stanza.Iq; import im.conversations.android.xmpp.model.stanza.Iq;
import im.conversations.android.xmpp.model.version.Version;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
import org.slf4j.Logger; import org.slf4j.Logger;
@ -34,10 +37,8 @@ import org.slf4j.LoggerFactory;
public class DiscoManager extends AbstractManager { public class DiscoManager extends AbstractManager {
private static final Logger LOGGER = LoggerFactory.getLogger(DiscoManager.class);
public static final String CAPABILITY_NODE = "http://conversations.im"; public static final String CAPABILITY_NODE = "http://conversations.im";
private static final Logger LOGGER = LoggerFactory.getLogger(DiscoManager.class);
private static final Collection<String> FEATURES_BASE = private static final Collection<String> FEATURES_BASE =
Arrays.asList( Arrays.asList(
Namespace.JINGLE, Namespace.JINGLE,
@ -55,7 +56,6 @@ public class DiscoManager extends AbstractManager {
Namespace.ENTITY_CAPABILITIES_2, Namespace.ENTITY_CAPABILITIES_2,
Namespace.DISCO_INFO, Namespace.DISCO_INFO,
Namespace.PING, Namespace.PING,
Namespace.VERSION,
Namespace.CHAT_STATES, Namespace.CHAT_STATES,
Namespace.LAST_MESSAGE_CORRECTION, Namespace.LAST_MESSAGE_CORRECTION,
Namespace.DELIVERY_RECEIPTS); Namespace.DELIVERY_RECEIPTS);
@ -69,6 +69,9 @@ public class DiscoManager extends AbstractManager {
Namespace.JINGLE_APPS_DTLS, Namespace.JINGLE_APPS_DTLS,
Namespace.JINGLE_MESSAGE); Namespace.JINGLE_MESSAGE);
private static final Collection<String> FEATURES_IMPACTING_PRIVACY =
Collections.singleton(Namespace.VERSION);
private static final Collection<String> FEATURES_NOTIFY = private static final Collection<String> FEATURES_NOTIFY =
Arrays.asList(Namespace.NICK, Namespace.AVATAR_METADATA, Namespace.BOOKMARKS2); Arrays.asList(Namespace.NICK, Namespace.AVATAR_METADATA, Namespace.BOOKMARKS2);
@ -76,6 +79,38 @@ public class DiscoManager extends AbstractManager {
super(context, connection); super(context, connection);
} }
public static EntityCapabilities.Hash buildHashFromNode(final String node) {
final var capsPrefix = CAPABILITY_NODE + "#";
final var caps2Prefix = Namespace.ENTITY_CAPABILITIES_2 + "#";
if (node.startsWith(capsPrefix)) {
final String hash = node.substring(capsPrefix.length());
if (Strings.isNullOrEmpty(hash)) {
return null;
}
if (BaseEncoding.base64().canDecode(hash)) {
return EntityCapabilities.EntityCapsHash.of(hash);
}
} else if (node.startsWith(caps2Prefix)) {
final String caps = node.substring(caps2Prefix.length());
if (Strings.isNullOrEmpty(caps)) {
return null;
}
final int separator = caps.lastIndexOf('.');
if (separator < 0) {
return null;
}
final Hash.Algorithm algorithm = Hash.Algorithm.tryParse(caps.substring(0, separator));
final String hash = caps.substring(separator + 1);
if (algorithm == null || Strings.isNullOrEmpty(hash)) {
return null;
}
if (BaseEncoding.base64().canDecode(hash)) {
return EntityCapabilities2.EntityCaps2Hash.of(algorithm, hash);
}
}
return null;
}
public ListenableFuture<InfoQuery> info(final Entity entity) { public ListenableFuture<InfoQuery> info(final Entity entity) {
return info(entity, null); return info(entity, null);
} }
@ -210,30 +245,29 @@ public class DiscoManager extends AbstractManager {
} }
public ServiceDescription getServiceDescription() { public ServiceDescription getServiceDescription() {
return getServiceDescription(false); return getServiceDescription(isPrivacyModeEnabled());
} }
private ServiceDescription getServiceDescription(final boolean privacyMode) { private ServiceDescription getServiceDescription(final boolean privacyMode) {
final ImmutableList.Builder<String> stringFeatureBuilder = ImmutableList.builder(); final ImmutableList.Builder<String> builder = ImmutableList.builder();
stringFeatureBuilder.addAll(FEATURES_BASE); final List<String> features;
stringFeatureBuilder.addAll( builder.addAll(FEATURES_BASE);
builder.addAll(
Collections2.transform(FEATURES_NOTIFY, fn -> String.format("%s+notify", fn))); Collections2.transform(FEATURES_NOTIFY, fn -> String.format("%s+notify", fn)));
if (!privacyMode) { if (privacyMode) {
stringFeatureBuilder.addAll(FEATURES_AV_CALLS); features = builder.build();
} else {
features = builder.addAll(FEATURES_AV_CALLS).addAll(FEATURES_IMPACTING_PRIVACY).build();
} }
return new ServiceDescription( return new ServiceDescription(
stringFeatureBuilder.build(), features,
new ServiceDescription.Identity(getIdentityName(), "client", getIdentityType())); new ServiceDescription.Identity(BuildConfig.APP_NAME, "client", getIdentityType()));
} }
String getIdentityVersion() { String getIdentityVersion() {
return BuildConfig.VERSION_NAME; return BuildConfig.VERSION_NAME;
} }
String getIdentityName() {
return BuildConfig.APP_NAME;
}
String getIdentityType() { String getIdentityType() {
if ("chromium".equals(android.os.Build.BRAND)) { if ("chromium".equals(android.os.Build.BRAND)) {
return "pc"; return "pc";
@ -270,35 +304,19 @@ public class DiscoManager extends AbstractManager {
connection.sendResultFor(request, infoQuery); connection.sendResultFor(request, infoQuery);
} }
public static EntityCapabilities.Hash buildHashFromNode(final String node) { public void handleVersion(final Iq request) {
final var capsPrefix = CAPABILITY_NODE + "#"; if (isPrivacyModeEnabled()) {
final var caps2Prefix = Namespace.ENTITY_CAPABILITIES_2 + "#"; connection.sendErrorFor(request, new Condition.ServiceUnavailable());
if (node.startsWith(capsPrefix)) { } else {
final String hash = node.substring(capsPrefix.length()); final var version = new Version();
if (Strings.isNullOrEmpty(hash)) { version.setSoftwareName(BuildConfig.APP_NAME);
return null; version.setVersion(BuildConfig.VERSION_NAME);
} version.setOs(String.format("Android %s", Build.VERSION.RELEASE));
if (BaseEncoding.base64().canDecode(hash)) { connection.sendResultFor(request, version);
return EntityCapabilities.EntityCapsHash.of(hash);
}
} else if (node.startsWith(caps2Prefix)) {
final String caps = node.substring(caps2Prefix.length());
if (Strings.isNullOrEmpty(caps)) {
return null;
}
final int separator = caps.lastIndexOf('.');
if (separator < 0) {
return null;
}
final Hash.Algorithm algorithm = Hash.Algorithm.tryParse(caps.substring(0, separator));
final String hash = caps.substring(separator + 1);
if (algorithm == null || Strings.isNullOrEmpty(hash)) {
return null;
}
if (BaseEncoding.base64().canDecode(hash)) {
return EntityCapabilities2.EntityCaps2Hash.of(algorithm, hash);
}
} }
return null; }
private boolean isPrivacyModeEnabled() {
return false;
} }
} }

View file

@ -0,0 +1,25 @@
package im.conversations.android.xmpp.model.version;
import eu.siacs.conversations.xml.Namespace;
import im.conversations.android.annotation.XmlElement;
import im.conversations.android.xmpp.model.Extension;
@XmlElement(name = "query", namespace = Namespace.VERSION)
public class Version extends Extension {
public Version() {
super(Version.class);
}
public void setSoftwareName(final String name) {
this.addChild("name").setContent(name);
}
public void setVersion(final String version) {
this.addChild("version").setContent(version);
}
public void setOs(final String os) {
this.addChild("os").setContent(os);
}
}

View file

@ -13,6 +13,7 @@ import im.conversations.android.xmpp.model.error.Condition;
import im.conversations.android.xmpp.model.ping.Ping; import im.conversations.android.xmpp.model.ping.Ping;
import im.conversations.android.xmpp.model.roster.Query; import im.conversations.android.xmpp.model.roster.Query;
import im.conversations.android.xmpp.model.stanza.Iq; import im.conversations.android.xmpp.model.stanza.Iq;
import im.conversations.android.xmpp.model.version.Version;
import java.util.Arrays; import java.util.Arrays;
import java.util.function.Consumer; import java.util.function.Consumer;
import org.slf4j.Logger; import org.slf4j.Logger;
@ -62,6 +63,11 @@ public class IqProcessor extends XmppConnection.Delegate implements Consumer<Iq>
return; return;
} }
if (type == Iq.Type.GET && packet.hasExtension(Version.class)) {
getManager(DiscoManager.class).handleVersion(packet);
return;
}
final var extensionIds = packet.getExtensionIds(); final var extensionIds = packet.getExtensionIds();
LOGGER.info("Could not handle {}. Sending feature-not-implemented", extensionIds); LOGGER.info("Could not handle {}. Sending feature-not-implemented", extensionIds);
connection.sendErrorFor(packet, new Condition.FeatureNotImplemented()); connection.sendErrorFor(packet, new Condition.FeatureNotImplemented());