add Entity Caps 2 hash calculation

This commit is contained in:
Daniel Gultsch 2023-01-16 14:11:47 +01:00
parent 78af8cbd87
commit 1b438117a3
No known key found for this signature in database
GPG key ID: F43D18AD2A0982C2
2 changed files with 232 additions and 0 deletions

View file

@ -0,0 +1,126 @@
package im.conversations.android.xmpp;
import com.google.common.base.Joiner;
import com.google.common.base.Strings;
import com.google.common.collect.Collections2;
import com.google.common.collect.Ordering;
import com.google.common.hash.Hashing;
import com.google.common.primitives.Bytes;
import im.conversations.android.xmpp.model.data.Data;
import im.conversations.android.xmpp.model.data.Field;
import im.conversations.android.xmpp.model.data.Value;
import im.conversations.android.xmpp.model.disco.info.Feature;
import im.conversations.android.xmpp.model.disco.info.Identity;
import im.conversations.android.xmpp.model.disco.info.InfoQuery;
import java.nio.charset.StandardCharsets;
import java.util.Collection;
public class EntityCapabilities2 {
private static final char UNIT_SEPARATOR = 0x1f;
private static final char RECORD_SEPARATOR = 0x1e;
private static final char GROUP_SEPARATOR = 0x1d;
private static final char FILE_SEPARATOR = 0x1c;
public static byte[] hash(final InfoQuery info) {
final String algo = algorithm(info);
return Hashing.sha256().hashString(algo, StandardCharsets.UTF_8).asBytes();
}
private static String asHex(final String message) {
return Joiner.on(' ')
.join(
Collections2.transform(
Bytes.asList(message.getBytes(StandardCharsets.UTF_8)),
b -> String.format("%02x", b)));
}
private static String algorithm(final InfoQuery infoQuery) {
return features(infoQuery.getExtensions(Feature.class))
+ identities(infoQuery.getExtensions(Identity.class))
+ extensions(infoQuery.getExtensions(Data.class));
}
private static String identities(final Collection<Identity> identities) {
return Joiner.on("")
.join(
Ordering.natural()
.sortedCopy(
Collections2.transform(
identities, EntityCapabilities2::identity)))
+ FILE_SEPARATOR;
}
private static String identity(final Identity identity) {
return Strings.nullToEmpty(identity.getCategory())
+ UNIT_SEPARATOR
+ Strings.nullToEmpty(identity.getType())
+ UNIT_SEPARATOR
+ Strings.nullToEmpty(identity.getLang())
+ UNIT_SEPARATOR
+ Strings.nullToEmpty(identity.getIdentityName())
+ UNIT_SEPARATOR
+ RECORD_SEPARATOR;
}
private static String features(Collection<Feature> features) {
return Joiner.on("")
.join(
Ordering.natural()
.sortedCopy(
Collections2.transform(
features, EntityCapabilities2::feature)))
+ FILE_SEPARATOR;
}
private static String feature(final Feature feature) {
return Strings.nullToEmpty(feature.getVar()) + UNIT_SEPARATOR;
}
private static String value(final Value value) {
return Strings.nullToEmpty(value.getContent()) + UNIT_SEPARATOR;
}
private static String values(final Collection<Value> values) {
return Joiner.on("")
.join(
Ordering.natural()
.sortedCopy(
Collections2.transform(
values, EntityCapabilities2::value)));
}
private static String field(final Field field) {
return Strings.nullToEmpty(field.getFieldName())
+ UNIT_SEPARATOR
+ values(field.getExtensions(Value.class))
+ RECORD_SEPARATOR;
}
private static String fields(final Collection<Field> fields) {
return Joiner.on("")
.join(
Ordering.natural()
.sortedCopy(
Collections2.transform(
fields, EntityCapabilities2::field)))
+ GROUP_SEPARATOR;
}
private static String extension(final Data data) {
return fields(data.getExtensions(Field.class));
}
private static String extensions(final Collection<Data> extensions) {
return Joiner.on("")
.join(
Ordering.natural()
.sortedCopy(
Collections2.transform(
extensions,
EntityCapabilities2::extension)))
+ FILE_SEPARATOR;
}
}

View file

@ -77,4 +77,110 @@ public class EntityCapabilitiesTest {
final String var = BaseEncoding.base64().encode(EntityCapabilities.hash(info));
Assert.assertEquals("q07IKJEyjvHSyhy//CH0CxmKi8w=", var);
}
@Test
public void caps2() throws IOException {
final String xml =
"<query xmlns=\"http://jabber.org/protocol/disco#info\">\n"
+ " <identity category=\"client\" name=\"BombusMod\" type=\"mobile\"/>\n"
+ " <feature var=\"http://jabber.org/protocol/si\"/>\n"
+ " <feature var=\"http://jabber.org/protocol/bytestreams\"/>\n"
+ " <feature var=\"http://jabber.org/protocol/chatstates\"/>\n"
+ " <feature var=\"http://jabber.org/protocol/disco#info\"/>\n"
+ " <feature var=\"http://jabber.org/protocol/disco#items\"/>\n"
+ " <feature var=\"urn:xmpp:ping\"/>\n"
+ " <feature var=\"jabber:iq:time\"/>\n"
+ " <feature var=\"jabber:iq:privacy\"/>\n"
+ " <feature var=\"jabber:iq:version\"/>\n"
+ " <feature var=\"http://jabber.org/protocol/rosterx\"/>\n"
+ " <feature var=\"urn:xmpp:time\"/>\n"
+ " <feature var=\"jabber:x:oob\"/>\n"
+ " <feature var=\"http://jabber.org/protocol/ibb\"/>\n"
+ " <feature var=\"http://jabber.org/protocol/si/profile/file-transfer\"/>\n"
+ " <feature var=\"urn:xmpp:receipts\"/>\n"
+ " <feature var=\"jabber:iq:roster\"/>\n"
+ " <feature var=\"jabber:iq:last\"/>\n"
+ "</query>";
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));
Assert.assertEquals("kzBZbkqJ3ADrj7v08reD1qcWUwNGHaidNUgD7nHpiw8=", var);
}
@Test
public void caps2complex() throws IOException {
final String xml =
"<query xmlns=\"http://jabber.org/protocol/disco#info\">\n"
+ " <identity category=\"client\" name=\"Tkabber\" type=\"pc\""
+ " xml:lang=\"en\"/>\n"
+ " <identity category=\"client\" name=\"Ткаббер\" type=\"pc\""
+ " xml:lang=\"ru\"/>\n"
+ " <feature var=\"games:board\"/>\n"
+ " <feature var=\"http://jabber.org/protocol/activity\"/>\n"
+ " <feature var=\"http://jabber.org/protocol/activity+notify\"/>\n"
+ " <feature var=\"http://jabber.org/protocol/bytestreams\"/>\n"
+ " <feature var=\"http://jabber.org/protocol/chatstates\"/>\n"
+ " <feature var=\"http://jabber.org/protocol/commands\"/>\n"
+ " <feature var=\"http://jabber.org/protocol/disco#info\"/>\n"
+ " <feature var=\"http://jabber.org/protocol/disco#items\"/>\n"
+ " <feature var=\"http://jabber.org/protocol/evil\"/>\n"
+ " <feature var=\"http://jabber.org/protocol/feature-neg\"/>\n"
+ " <feature var=\"http://jabber.org/protocol/geoloc\"/>\n"
+ " <feature var=\"http://jabber.org/protocol/geoloc+notify\"/>\n"
+ " <feature var=\"http://jabber.org/protocol/ibb\"/>\n"
+ " <feature var=\"http://jabber.org/protocol/iqibb\"/>\n"
+ " <feature var=\"http://jabber.org/protocol/mood\"/>\n"
+ " <feature var=\"http://jabber.org/protocol/mood+notify\"/>\n"
+ " <feature var=\"http://jabber.org/protocol/rosterx\"/>\n"
+ " <feature var=\"http://jabber.org/protocol/si\"/>\n"
+ " <feature var=\"http://jabber.org/protocol/si/profile/file-transfer\"/>\n"
+ " <feature var=\"http://jabber.org/protocol/tune\"/>\n"
+ " <feature var=\"http://www.facebook.com/xmpp/messages\"/>\n"
+ " <feature"
+ " var=\"http://www.xmpp.org/extensions/xep-0084.html#ns-metadata+notify\"/>\n"
+ " <feature var=\"jabber:iq:avatar\"/>\n"
+ " <feature var=\"jabber:iq:browse\"/>\n"
+ " <feature var=\"jabber:iq:dtcp\"/>\n"
+ " <feature var=\"jabber:iq:filexfer\"/>\n"
+ " <feature var=\"jabber:iq:ibb\"/>\n"
+ " <feature var=\"jabber:iq:inband\"/>\n"
+ " <feature var=\"jabber:iq:jidlink\"/>\n"
+ " <feature var=\"jabber:iq:last\"/>\n"
+ " <feature var=\"jabber:iq:oob\"/>\n"
+ " <feature var=\"jabber:iq:privacy\"/>\n"
+ " <feature var=\"jabber:iq:roster\"/>\n"
+ " <feature var=\"jabber:iq:time\"/>\n"
+ " <feature var=\"jabber:iq:version\"/>\n"
+ " <feature var=\"jabber:x:data\"/>\n"
+ " <feature var=\"jabber:x:event\"/>\n"
+ " <feature var=\"jabber:x:oob\"/>\n"
+ " <feature var=\"urn:xmpp:avatar:metadata+notify\"/>\n"
+ " <feature var=\"urn:xmpp:ping\"/>\n"
+ " <feature var=\"urn:xmpp:receipts\"/>\n"
+ " <feature var=\"urn:xmpp:time\"/>\n"
+ " <x xmlns=\"jabber:x:data\" type=\"result\">\n"
+ " <field type=\"hidden\" var=\"FORM_TYPE\">\n"
+ " <value>urn:xmpp:dataforms:softwareinfo</value>\n"
+ " </field>\n"
+ " <field var=\"software\">\n"
+ " <value>Tkabber</value>\n"
+ " </field>\n"
+ " <field var=\"software_version\">\n"
+ " <value>0.11.1-svn-20111216-mod (Tcl/Tk 8.6b2)</value>\n"
+ " </field>\n"
+ " <field var=\"os\">\n"
+ " <value>Windows</value>\n"
+ " </field>\n"
+ " <field var=\"os_version\">\n"
+ " <value>XP</value>\n"
+ " </field>\n"
+ " </x>\n"
+ "</query>";
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));
Assert.assertEquals("u79ZroNJbdSWhdSp311mddz44oHHPsEBntQ5b1jqBSY=", var);
}
}