From a2b21d97eba0aad69423688a53bdf69617efb799 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Tue, 17 Jan 2023 08:53:21 +0100 Subject: [PATCH] use dedicated hash object instead of byte[] for caps this way we can store the algo alongside the object --- .../android/xmpp/EntityCapabilities.java | 25 +++++++- .../android/xmpp/EntityCapabilities2.java | 60 +++++++++++++++++-- .../android/xmpp/manager/DiscoManager.java | 4 +- .../android/xmpp/EntityCapabilitiesTest.java | 9 ++- 4 files changed, 84 insertions(+), 14 deletions(-) diff --git a/src/main/java/im/conversations/android/xmpp/EntityCapabilities.java b/src/main/java/im/conversations/android/xmpp/EntityCapabilities.java index e9da963da..ae9fcc3a7 100644 --- a/src/main/java/im/conversations/android/xmpp/EntityCapabilities.java +++ b/src/main/java/im/conversations/android/xmpp/EntityCapabilities.java @@ -5,6 +5,7 @@ import com.google.common.collect.Collections2; import com.google.common.collect.ComparisonChain; import com.google.common.collect.Ordering; 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.Field; import im.conversations.android.xmpp.model.disco.info.Feature; @@ -15,7 +16,7 @@ import java.util.Comparator; import java.util.List; public final class EntityCapabilities { - public static byte[] hash(final InfoQuery info) { + public static EntityCapsHash hash(final InfoQuery info) { final StringBuilder s = new StringBuilder(); final List orderedIdentities = 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) { @@ -86,4 +88,23 @@ public final class EntityCapabilities { private static String blankNull(String 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); + } + } } diff --git a/src/main/java/im/conversations/android/xmpp/EntityCapabilities2.java b/src/main/java/im/conversations/android/xmpp/EntityCapabilities2.java index 2a58f6e39..1f03e08b1 100644 --- a/src/main/java/im/conversations/android/xmpp/EntityCapabilities2.java +++ b/src/main/java/im/conversations/android/xmpp/EntityCapabilities2.java @@ -1,5 +1,8 @@ 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.Strings; import com.google.common.collect.Collections2; @@ -25,13 +28,28 @@ public class EntityCapabilities2 { private static final char FILE_SEPARATOR = 0x1c; - public static byte[] hash(final InfoQuery info) { - return hash(Hashing.sha256(), info); + public static EntityCaps2Hash hash(final InfoQuery info) { + return hash(Algorithm.SHA_256, info); } - public static byte[] hash(HashFunction hashFunction, final InfoQuery info) { - final String algo = algorithm(info); - return hashFunction.hashString(algo, StandardCharsets.UTF_8).asBytes(); + public static EntityCaps2Hash hash(final Algorithm algorithm, final InfoQuery info) { + final String result = algorithm(info); + 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) { @@ -128,4 +146,36 @@ public class EntityCapabilities2 { EntityCapabilities2::extension))) + 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()); + } + } } diff --git a/src/main/java/im/conversations/android/xmpp/manager/DiscoManager.java b/src/main/java/im/conversations/android/xmpp/manager/DiscoManager.java index eba58cbeb..6fa592d36 100644 --- a/src/main/java/im/conversations/android/xmpp/manager/DiscoManager.java +++ b/src/main/java/im/conversations/android/xmpp/manager/DiscoManager.java @@ -46,8 +46,8 @@ public class DiscoManager extends AbstractManager { throw new IllegalStateException( "Node in response did not match node in request"); } - final byte[] caps = EntityCapabilities.hash(infoQuery); - final byte[] caps2 = EntityCapabilities2.hash(infoQuery); + final byte[] caps = EntityCapabilities.hash(infoQuery).hash; + final byte[] caps2 = EntityCapabilities2.hash(infoQuery).hash; getDatabase() .discoDao() .set(getAccount(), entity, node, caps, caps2, infoQuery); diff --git a/src/test/java/im/conversations/android/xmpp/EntityCapabilitiesTest.java b/src/test/java/im/conversations/android/xmpp/EntityCapabilitiesTest.java index ed645f28f..0cfb23c4b 100644 --- a/src/test/java/im/conversations/android/xmpp/EntityCapabilitiesTest.java +++ b/src/test/java/im/conversations/android/xmpp/EntityCapabilitiesTest.java @@ -3,7 +3,6 @@ package im.conversations.android.xmpp; import static org.hamcrest.CoreMatchers.instanceOf; import static org.hamcrest.MatcherAssert.assertThat; -import com.google.common.io.BaseEncoding; import eu.siacs.conversations.xml.Element; import eu.siacs.conversations.xml.XmlElementReader; 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)); assertThat(element, instanceOf(InfoQuery.class)); 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); } @@ -74,7 +73,7 @@ public class EntityCapabilitiesTest { final Element element = XmlElementReader.read(xml.getBytes(StandardCharsets.UTF_8)); assertThat(element, instanceOf(InfoQuery.class)); 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); } @@ -104,7 +103,7 @@ public class EntityCapabilitiesTest { final Element element = XmlElementReader.read(xml.getBytes(StandardCharsets.UTF_8)); assertThat(element, instanceOf(InfoQuery.class)); 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); } @@ -180,7 +179,7 @@ public class EntityCapabilitiesTest { final Element element = XmlElementReader.read(xml.getBytes(StandardCharsets.UTF_8)); assertThat(element, instanceOf(InfoQuery.class)); 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); } }