use dedicated hash object instead of byte[] for caps

this way we can store the algo alongside the object
This commit is contained in:
Daniel Gultsch 2023-01-17 08:53:21 +01:00
parent 6458c6e9f9
commit a2b21d97eb
No known key found for this signature in database
GPG key ID: F43D18AD2A0982C2
4 changed files with 84 additions and 14 deletions

View file

@ -5,6 +5,7 @@ import com.google.common.collect.Collections2;
import com.google.common.collect.ComparisonChain; import com.google.common.collect.ComparisonChain;
import com.google.common.collect.Ordering; import com.google.common.collect.Ordering;
import com.google.common.hash.Hashing; import com.google.common.hash.Hashing;
import com.google.common.io.BaseEncoding;
import im.conversations.android.xmpp.model.data.Data; import im.conversations.android.xmpp.model.data.Data;
import im.conversations.android.xmpp.model.data.Field; import im.conversations.android.xmpp.model.data.Field;
import im.conversations.android.xmpp.model.disco.info.Feature; import im.conversations.android.xmpp.model.disco.info.Feature;
@ -15,7 +16,7 @@ import java.util.Comparator;
import java.util.List; import java.util.List;
public final class EntityCapabilities { public final class EntityCapabilities {
public static byte[] hash(final InfoQuery info) { public static EntityCapsHash hash(final InfoQuery info) {
final StringBuilder s = new StringBuilder(); final StringBuilder s = new StringBuilder();
final List<Identity> orderedIdentities = final List<Identity> orderedIdentities =
Ordering.from( Ordering.from(
@ -76,7 +77,8 @@ public final class EntityCapabilities {
} }
} }
} }
return Hashing.sha1().hashString(s.toString(), StandardCharsets.UTF_8).asBytes(); return new EntityCapsHash(
Hashing.sha1().hashString(s.toString(), StandardCharsets.UTF_8).asBytes());
} }
private static String clean(String s) { private static String clean(String s) {
@ -86,4 +88,23 @@ public final class EntityCapabilities {
private static String blankNull(String s) { private static String blankNull(String s) {
return s == null ? "" : clean(s); return s == null ? "" : clean(s);
} }
public abstract static class Hash {
public final byte[] hash;
protected Hash(byte[] hash) {
this.hash = hash;
}
public String encoded() {
return BaseEncoding.base64().encode(hash);
}
}
public static class EntityCapsHash extends Hash {
protected EntityCapsHash(byte[] hash) {
super(hash);
}
}
} }

View file

@ -1,5 +1,8 @@
package im.conversations.android.xmpp; package im.conversations.android.xmpp;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.google.common.base.CaseFormat;
import com.google.common.base.Joiner; import com.google.common.base.Joiner;
import com.google.common.base.Strings; import com.google.common.base.Strings;
import com.google.common.collect.Collections2; import com.google.common.collect.Collections2;
@ -25,13 +28,28 @@ public class EntityCapabilities2 {
private static final char FILE_SEPARATOR = 0x1c; private static final char FILE_SEPARATOR = 0x1c;
public static byte[] hash(final InfoQuery info) { public static EntityCaps2Hash hash(final InfoQuery info) {
return hash(Hashing.sha256(), info); return hash(Algorithm.SHA_256, info);
} }
public static byte[] hash(HashFunction hashFunction, final InfoQuery info) { public static EntityCaps2Hash hash(final Algorithm algorithm, final InfoQuery info) {
final String algo = algorithm(info); final String result = algorithm(info);
return hashFunction.hashString(algo, StandardCharsets.UTF_8).asBytes(); final var hashFunction = toHashFunction(algorithm);
return new EntityCaps2Hash(
algorithm, hashFunction.hashString(result, StandardCharsets.UTF_8).asBytes());
}
private static HashFunction toHashFunction(final Algorithm algorithm) {
switch (algorithm) {
case SHA_1:
return Hashing.sha1();
case SHA_256:
return Hashing.sha256();
case SHA_512:
return Hashing.sha512();
default:
throw new IllegalArgumentException("Unknown hash algorithm");
}
} }
private static String asHex(final String message) { private static String asHex(final String message) {
@ -128,4 +146,36 @@ public class EntityCapabilities2 {
EntityCapabilities2::extension))) EntityCapabilities2::extension)))
+ FILE_SEPARATOR; + FILE_SEPARATOR;
} }
public static class EntityCaps2Hash extends EntityCapabilities.Hash {
public final Algorithm algorithm;
protected EntityCaps2Hash(final Algorithm algorithm, byte[] hash) {
super(hash);
this.algorithm = algorithm;
}
}
public enum Algorithm {
SHA_1,
SHA_256,
SHA_512;
public static Algorithm tryParse(@Nullable final String name) {
try {
return valueOf(
CaseFormat.LOWER_HYPHEN.to(
CaseFormat.UPPER_UNDERSCORE, Strings.nullToEmpty(name)));
} catch (final IllegalArgumentException e) {
return null;
}
}
@NonNull
@Override
public String toString() {
return CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.LOWER_HYPHEN, super.toString());
}
}
} }

View file

@ -46,8 +46,8 @@ public class DiscoManager extends AbstractManager {
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); final byte[] caps = EntityCapabilities.hash(infoQuery).hash;
final byte[] caps2 = EntityCapabilities2.hash(infoQuery); final byte[] caps2 = EntityCapabilities2.hash(infoQuery).hash;
getDatabase() getDatabase()
.discoDao() .discoDao()
.set(getAccount(), entity, node, caps, caps2, infoQuery); .set(getAccount(), entity, node, caps, caps2, infoQuery);

View file

@ -3,7 +3,6 @@ package im.conversations.android.xmpp;
import static org.hamcrest.CoreMatchers.instanceOf; import static org.hamcrest.CoreMatchers.instanceOf;
import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.MatcherAssert.assertThat;
import com.google.common.io.BaseEncoding;
import eu.siacs.conversations.xml.Element; import eu.siacs.conversations.xml.Element;
import eu.siacs.conversations.xml.XmlElementReader; import eu.siacs.conversations.xml.XmlElementReader;
import im.conversations.android.xmpp.model.disco.info.InfoQuery; import im.conversations.android.xmpp.model.disco.info.InfoQuery;
@ -34,7 +33,7 @@ public class EntityCapabilitiesTest {
final Element element = XmlElementReader.read(xml.getBytes(StandardCharsets.UTF_8)); final Element element = XmlElementReader.read(xml.getBytes(StandardCharsets.UTF_8));
assertThat(element, instanceOf(InfoQuery.class)); assertThat(element, instanceOf(InfoQuery.class));
final InfoQuery info = (InfoQuery) element; final InfoQuery info = (InfoQuery) element;
final String var = BaseEncoding.base64().encode(EntityCapabilities.hash(info)); final String var = EntityCapabilities.hash(info).encoded();
Assert.assertEquals("QgayPKawpkPSDYmwT/WM94uAlu0=", var); Assert.assertEquals("QgayPKawpkPSDYmwT/WM94uAlu0=", var);
} }
@ -74,7 +73,7 @@ public class EntityCapabilitiesTest {
final Element element = XmlElementReader.read(xml.getBytes(StandardCharsets.UTF_8)); final Element element = XmlElementReader.read(xml.getBytes(StandardCharsets.UTF_8));
assertThat(element, instanceOf(InfoQuery.class)); assertThat(element, instanceOf(InfoQuery.class));
final InfoQuery info = (InfoQuery) element; final InfoQuery info = (InfoQuery) element;
final String var = BaseEncoding.base64().encode(EntityCapabilities.hash(info)); final String var = EntityCapabilities.hash(info).encoded();
Assert.assertEquals("q07IKJEyjvHSyhy//CH0CxmKi8w=", var); Assert.assertEquals("q07IKJEyjvHSyhy//CH0CxmKi8w=", var);
} }
@ -104,7 +103,7 @@ public class EntityCapabilitiesTest {
final Element element = XmlElementReader.read(xml.getBytes(StandardCharsets.UTF_8)); final Element element = XmlElementReader.read(xml.getBytes(StandardCharsets.UTF_8));
assertThat(element, instanceOf(InfoQuery.class)); assertThat(element, instanceOf(InfoQuery.class));
final InfoQuery info = (InfoQuery) element; final InfoQuery info = (InfoQuery) element;
final String var = BaseEncoding.base64().encode(EntityCapabilities2.hash(info)); final String var = EntityCapabilities2.hash(info).encoded();
Assert.assertEquals("kzBZbkqJ3ADrj7v08reD1qcWUwNGHaidNUgD7nHpiw8=", var); Assert.assertEquals("kzBZbkqJ3ADrj7v08reD1qcWUwNGHaidNUgD7nHpiw8=", var);
} }
@ -180,7 +179,7 @@ public class EntityCapabilitiesTest {
final Element element = XmlElementReader.read(xml.getBytes(StandardCharsets.UTF_8)); final Element element = XmlElementReader.read(xml.getBytes(StandardCharsets.UTF_8));
assertThat(element, instanceOf(InfoQuery.class)); assertThat(element, instanceOf(InfoQuery.class));
final InfoQuery info = (InfoQuery) element; final InfoQuery info = (InfoQuery) element;
final String var = BaseEncoding.base64().encode(EntityCapabilities2.hash(info)); final String var = EntityCapabilities2.hash(info).encoded();
Assert.assertEquals("u79ZroNJbdSWhdSp311mddz44oHHPsEBntQ5b1jqBSY=", var); Assert.assertEquals("u79ZroNJbdSWhdSp311mddz44oHHPsEBntQ5b1jqBSY=", var);
} }
} }