check caps hash after retrieving them

This commit is contained in:
Daniel Gultsch 2023-01-19 20:53:42 +01:00
parent 1a09b3ed05
commit 1e6aed759b
No known key found for this signature in database
GPG key ID: F43D18AD2A0982C2
3 changed files with 51 additions and 16 deletions

View file

@ -1884,7 +1884,8 @@ public class XmppConnection implements Runnable {
final var nodeHash = this.streamFeatures.getCapabilities(); final var nodeHash = this.streamFeatures.getCapabilities();
final var domainDiscoItem = Entity.discoItem(account.address.getDomain()); final var domainDiscoItem = Entity.discoItem(account.address.getDomain());
if (nodeHash != null) { if (nodeHash != null) {
discoFutures.add(discoManager.info(domainDiscoItem, nodeHash.node, nodeHash.hash)); discoFutures.add(
discoManager.infoOrCache(domainDiscoItem, nodeHash.node, nodeHash.hash));
} else { } else {
discoFutures.add(discoManager.info(domainDiscoItem)); discoFutures.add(discoManager.info(domainDiscoItem));
} }

View file

@ -1,8 +1,10 @@
package im.conversations.android.xmpp.manager; package im.conversations.android.xmpp.manager;
import android.content.Context; import android.content.Context;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import com.google.common.collect.Collections2; import com.google.common.collect.Collections2;
import com.google.common.io.BaseEncoding;
import com.google.common.util.concurrent.Futures; 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.MoreExecutors;
@ -15,6 +17,7 @@ import im.conversations.android.xmpp.XmppConnection;
import im.conversations.android.xmpp.model.disco.info.InfoQuery; import im.conversations.android.xmpp.model.disco.info.InfoQuery;
import im.conversations.android.xmpp.model.disco.items.Item; 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 java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
@ -29,22 +32,30 @@ public class DiscoManager extends AbstractManager {
return info(entity, null); return info(entity, null);
} }
public ListenableFuture<Void> info( public ListenableFuture<Void> infoOrCache(
final Entity entity, @Nullable final String node, final EntityCapabilities.Hash hash) { final Entity entity, @Nullable final String node, final EntityCapabilities.Hash hash) {
final String capabilityNode = hash.capabilityNode(node); if (getDatabase().discoDao().set(getAccount(), entity, node, hash)) {
if (getDatabase().discoDao().set(getAccount(), entity, capabilityNode, hash)) {
return Futures.immediateFuture(null); return Futures.immediateFuture(null);
} }
return Futures.transform( return Futures.transform(
info(entity, capabilityNode), f -> null, MoreExecutors.directExecutor()); info(entity, node, hash), f -> null, MoreExecutors.directExecutor());
} }
public ListenableFuture<InfoQuery> info(final Entity entity, final String node) { public ListenableFuture<InfoQuery> info(
@NonNull final Entity entity, @Nullable final String node) {
return info(entity, node, null);
}
public ListenableFuture<InfoQuery> info(
final Entity entity,
@Nullable final String node,
@Nullable final EntityCapabilities.Hash hash) {
final var requestNode = hash != null && node != null ? hash.capabilityNode(node) : node;
final var iqRequest = new IqPacket(IqPacket.TYPE.GET); final var iqRequest = new IqPacket(IqPacket.TYPE.GET);
iqRequest.setTo(entity.address); iqRequest.setTo(entity.address);
final var infoQueryRequest = new InfoQuery(); final var infoQueryRequest = new InfoQuery();
if (node != null) { if (requestNode != null) {
infoQueryRequest.setNode(node); infoQueryRequest.setNode(requestNode);
} }
iqRequest.addChild(infoQueryRequest); iqRequest.addChild(infoQueryRequest);
final var future = connection.sendIqPacket(iqRequest); final var future = connection.sendIqPacket(iqRequest);
@ -57,20 +68,45 @@ public class DiscoManager extends AbstractManager {
if (infoQuery == null) { if (infoQuery == null) {
throw new IllegalStateException("Response did not have query child"); throw new IllegalStateException("Response did not have query child");
} }
if (!Objects.equals(node, infoQuery.getNode())) { if (!Objects.equals(requestNode, infoQuery.getNode())) {
throw new IllegalStateException( throw new IllegalStateException(
"Node in response did not match node in request"); "Node in response did not match node in request");
} }
final byte[] caps = EntityCapabilities.hash(infoQuery).hash; final var caps = EntityCapabilities.hash(infoQuery);
final byte[] caps2 = EntityCapabilities2.hash(infoQuery).hash; final var caps2 = EntityCapabilities2.hash(infoQuery);
if (hash instanceof EntityCapabilities.EntityCapsHash) {
checkMatch(
(EntityCapabilities.EntityCapsHash) hash,
caps,
EntityCapabilities.EntityCapsHash.class);
}
if (hash instanceof EntityCapabilities2.EntityCaps2Hash) {
checkMatch(
(EntityCapabilities2.EntityCaps2Hash) hash,
caps2,
EntityCapabilities2.EntityCaps2Hash.class);
}
getDatabase() getDatabase()
.discoDao() .discoDao()
.set(getAccount(), entity, node, caps, caps2, infoQuery); .set(getAccount(), entity, node, caps.hash, caps2.hash, infoQuery);
return infoQuery; return infoQuery;
}, },
MoreExecutors.directExecutor()); MoreExecutors.directExecutor());
} }
private <H extends EntityCapabilities.Hash> void checkMatch(
final H expected, final H was, final Class<H> clazz) {
if (Arrays.equals(expected.hash, was.hash)) {
return;
}
throw new IllegalStateException(
String.format(
"%s mismatch. Expected %s was %s",
clazz.getSimpleName(),
BaseEncoding.base64().encode(expected.hash),
BaseEncoding.base64().encode(was.hash)));
}
public ListenableFuture<Collection<Item>> items(final Entity.DiscoItem entity) { public ListenableFuture<Collection<Item>> items(final Entity.DiscoItem entity) {
final var iqPacket = new IqPacket(IqPacket.TYPE.GET); final var iqPacket = new IqPacket(IqPacket.TYPE.GET);
iqPacket.setTo(entity.address); iqPacket.setTo(entity.address);
@ -97,11 +133,9 @@ public class DiscoManager extends AbstractManager {
return Futures.transformAsync( return Futures.transformAsync(
itemsFutures, itemsFutures,
items -> { items -> {
final var filtered =
Collections2.filter(items, i -> Objects.nonNull(i.getJid()));
Collection<ListenableFuture<InfoQuery>> infoFutures = Collection<ListenableFuture<InfoQuery>> infoFutures =
Collections2.transform( Collections2.transform(
filtered, i -> info(Entity.discoItem(i.getJid()), i.getNode())); items, i -> info(Entity.discoItem(i.getJid()), i.getNode()));
return Futures.allAsList(infoFutures); return Futures.allAsList(infoFutures);
}, },
MoreExecutors.directExecutor()); MoreExecutors.directExecutor());

View file

@ -43,7 +43,7 @@ public class PresenceProcessor extends XmppConnection.Delegate implements Consum
final var nodeHash = presencePacket.getCapabilities(); final var nodeHash = presencePacket.getCapabilities();
if (nodeHash != null) { if (nodeHash != null) {
getManager(DiscoManager.class) getManager(DiscoManager.class)
.info(Entity.presence(entity), nodeHash.node, nodeHash.hash); .infoOrCache(Entity.presence(entity), nodeHash.node, nodeHash.hash);
} }
} }
} }