migrate entity caps 1 calculation to new code
This commit is contained in:
parent
482dc8cfe9
commit
78af8cbd87
|
@ -0,0 +1,89 @@
|
||||||
|
package im.conversations.android.xmpp;
|
||||||
|
|
||||||
|
import com.google.common.base.Strings;
|
||||||
|
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 im.conversations.android.xmpp.model.data.Data;
|
||||||
|
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.Identity;
|
||||||
|
import im.conversations.android.xmpp.model.disco.info.InfoQuery;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public final class EntityCapabilities {
|
||||||
|
public static byte[] hash(final InfoQuery info) {
|
||||||
|
final StringBuilder s = new StringBuilder();
|
||||||
|
final List<Identity> orderedIdentities =
|
||||||
|
Ordering.from(
|
||||||
|
(Comparator<Identity>)
|
||||||
|
(a, b) ->
|
||||||
|
ComparisonChain.start()
|
||||||
|
.compare(
|
||||||
|
blankNull(a.getCategory()),
|
||||||
|
blankNull(b.getCategory()))
|
||||||
|
.compare(
|
||||||
|
blankNull(a.getType()),
|
||||||
|
blankNull(b.getType()))
|
||||||
|
.compare(
|
||||||
|
blankNull(a.getLang()),
|
||||||
|
blankNull(b.getLang()))
|
||||||
|
.compare(
|
||||||
|
blankNull(a.getIdentityName()),
|
||||||
|
blankNull(b.getIdentityName()))
|
||||||
|
.result())
|
||||||
|
.sortedCopy(info.getExtensions(Identity.class));
|
||||||
|
|
||||||
|
for (final Identity id : orderedIdentities) {
|
||||||
|
s.append(blankNull(id.getCategory()))
|
||||||
|
.append("/")
|
||||||
|
.append(blankNull(id.getType()))
|
||||||
|
.append("/")
|
||||||
|
.append(blankNull(id.getLang()))
|
||||||
|
.append("/")
|
||||||
|
.append(blankNull(id.getIdentityName()))
|
||||||
|
.append("<");
|
||||||
|
}
|
||||||
|
|
||||||
|
final List<String> features =
|
||||||
|
Ordering.natural()
|
||||||
|
.sortedCopy(
|
||||||
|
Collections2.transform(
|
||||||
|
info.getExtensions(Feature.class), Feature::getVar));
|
||||||
|
for (final String feature : features) {
|
||||||
|
s.append(clean(feature)).append("<");
|
||||||
|
}
|
||||||
|
|
||||||
|
final List<Data> extensions =
|
||||||
|
Ordering.from(Comparator.comparing(Data::getFormType))
|
||||||
|
.sortedCopy(info.getExtensions(Data.class));
|
||||||
|
|
||||||
|
for (final Data extension : extensions) {
|
||||||
|
s.append(clean(extension.getFormType())).append("<");
|
||||||
|
final List<Field> fields =
|
||||||
|
Ordering.from(
|
||||||
|
Comparator.comparing(
|
||||||
|
(Field lhs) -> Strings.nullToEmpty(lhs.getFieldName())))
|
||||||
|
.sortedCopy(extension.getFields());
|
||||||
|
for (final Field field : fields) {
|
||||||
|
s.append(Strings.nullToEmpty(field.getFieldName())).append("<");
|
||||||
|
final List<String> values = Ordering.natural().sortedCopy(field.getValues());
|
||||||
|
for (final String value : values) {
|
||||||
|
s.append(blankNull(value)).append("<");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Hashing.sha1().hashString(s.toString(), StandardCharsets.UTF_8).asBytes();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String clean(String s) {
|
||||||
|
return s.replace("<", "<");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String blankNull(String s) {
|
||||||
|
return s == null ? "" : clean(s);
|
||||||
|
}
|
||||||
|
}
|
|
@ -25,6 +25,7 @@ public final class Extensions {
|
||||||
im.conversations.android.xmpp.model.blocking.Unblock.class,
|
im.conversations.android.xmpp.model.blocking.Unblock.class,
|
||||||
im.conversations.android.xmpp.model.data.Data.class,
|
im.conversations.android.xmpp.model.data.Data.class,
|
||||||
im.conversations.android.xmpp.model.data.Field.class,
|
im.conversations.android.xmpp.model.data.Field.class,
|
||||||
|
im.conversations.android.xmpp.model.data.Value.class,
|
||||||
im.conversations.android.xmpp.model.disco.info.Feature.class,
|
im.conversations.android.xmpp.model.disco.info.Feature.class,
|
||||||
im.conversations.android.xmpp.model.disco.info.Identity.class,
|
im.conversations.android.xmpp.model.disco.info.Identity.class,
|
||||||
im.conversations.android.xmpp.model.disco.info.InfoQuery.class,
|
im.conversations.android.xmpp.model.disco.info.InfoQuery.class,
|
||||||
|
|
|
@ -1,11 +1,28 @@
|
||||||
package im.conversations.android.xmpp.model.data;
|
package im.conversations.android.xmpp.model.data;
|
||||||
|
|
||||||
|
import com.google.common.collect.Collections2;
|
||||||
|
import com.google.common.collect.Iterables;
|
||||||
import im.conversations.android.annotation.XmlElement;
|
import im.conversations.android.annotation.XmlElement;
|
||||||
import im.conversations.android.xmpp.model.Extension;
|
import im.conversations.android.xmpp.model.Extension;
|
||||||
|
import java.util.Collection;
|
||||||
|
|
||||||
@XmlElement(name = "x")
|
@XmlElement(name = "x")
|
||||||
public class Data extends Extension {
|
public class Data extends Extension {
|
||||||
|
|
||||||
|
private static final String FORM_TYPE = "FORM_TYPE";
|
||||||
|
|
||||||
public Data() {
|
public Data() {
|
||||||
super(Data.class);
|
super(Data.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getFormType() {
|
||||||
|
final var fields = this.getExtensions(Field.class);
|
||||||
|
final var formTypeField = Iterables.find(fields, f -> FORM_TYPE.equals(f.getFieldName()));
|
||||||
|
return Iterables.getFirst(formTypeField.getValues(), null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Collection<Field> getFields() {
|
||||||
|
return Collections2.filter(
|
||||||
|
this.getExtensions(Field.class), f -> !FORM_TYPE.equals(f.getFieldName()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,22 @@
|
||||||
package im.conversations.android.xmpp.model.data;
|
package im.conversations.android.xmpp.model.data;
|
||||||
|
|
||||||
|
import com.google.common.collect.Collections2;
|
||||||
|
import eu.siacs.conversations.xml.Element;
|
||||||
import im.conversations.android.annotation.XmlElement;
|
import im.conversations.android.annotation.XmlElement;
|
||||||
import im.conversations.android.xmpp.model.Extension;
|
import im.conversations.android.xmpp.model.Extension;
|
||||||
|
import java.util.Collection;
|
||||||
|
|
||||||
@XmlElement
|
@XmlElement
|
||||||
public class Field extends Extension {
|
public class Field extends Extension {
|
||||||
public Field() {
|
public Field() {
|
||||||
super(Field.class);
|
super(Field.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getFieldName() {
|
||||||
|
return getAttribute("var");
|
||||||
|
}
|
||||||
|
|
||||||
|
public Collection<String> getValues() {
|
||||||
|
return Collections2.transform(getExtensions(Value.class), Element::getContent);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
package im.conversations.android.xmpp.model.data;
|
||||||
|
|
||||||
|
import im.conversations.android.annotation.XmlElement;
|
||||||
|
import im.conversations.android.xmpp.model.Extension;
|
||||||
|
|
||||||
|
@XmlElement
|
||||||
|
public class Value extends Extension {
|
||||||
|
|
||||||
|
public Value() {
|
||||||
|
super(Value.class);
|
||||||
|
}
|
||||||
|
}
|
|
@ -8,4 +8,8 @@ public class Feature extends Extension {
|
||||||
public Feature() {
|
public Feature() {
|
||||||
super(Feature.class);
|
super(Feature.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getVar() {
|
||||||
|
return this.getAttribute("var");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,4 +8,20 @@ public class Identity extends Extension {
|
||||||
public Identity() {
|
public Identity() {
|
||||||
super(Identity.class);
|
super(Identity.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getCategory() {
|
||||||
|
return this.getAttribute("category");
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getType() {
|
||||||
|
return this.getAttribute("type");
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getLang() {
|
||||||
|
return this.getAttribute("xml:lang");
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getIdentityName() {
|
||||||
|
return this.getAttribute("name");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,80 @@
|
||||||
|
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;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.robolectric.RobolectricTestRunner;
|
||||||
|
import org.robolectric.annotation.ConscryptMode;
|
||||||
|
|
||||||
|
@RunWith(RobolectricTestRunner.class)
|
||||||
|
@ConscryptMode(ConscryptMode.Mode.OFF)
|
||||||
|
public class EntityCapabilitiesTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void entityCaps() throws IOException {
|
||||||
|
final String xml =
|
||||||
|
"<query xmlns='http://jabber.org/protocol/disco#info'\n"
|
||||||
|
+ " "
|
||||||
|
+ " node='http://code.google.com/p/exodus#QgayPKawpkPSDYmwT/WM94uAlu0='>\n"
|
||||||
|
+ " <identity category='client' name='Exodus 0.9.1' type='pc'/>\n"
|
||||||
|
+ " <feature var='http://jabber.org/protocol/caps'/>\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/muc'/>\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(EntityCapabilities.hash(info));
|
||||||
|
Assert.assertEquals("QgayPKawpkPSDYmwT/WM94uAlu0=", var);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void entityCapsComplexExample() throws IOException {
|
||||||
|
final String xml =
|
||||||
|
"<query xmlns='http://jabber.org/protocol/disco#info'\n"
|
||||||
|
+ " node='http://psi-im.org#q07IKJEyjvHSyhy//CH0CxmKi8w='>\n"
|
||||||
|
+ " <identity xml:lang='en' category='client' name='Psi 0.11' type='pc'/>\n"
|
||||||
|
+ " <identity xml:lang='el' category='client' name='Ψ 0.11' type='pc'/>\n"
|
||||||
|
+ " <feature var='http://jabber.org/protocol/caps'/>\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/muc'/>\n"
|
||||||
|
+ " <x xmlns='jabber:x:data' type='result'>\n"
|
||||||
|
+ " <field var='FORM_TYPE' type='hidden'>\n"
|
||||||
|
+ " <value>urn:xmpp:dataforms:softwareinfo</value>\n"
|
||||||
|
+ " </field>\n"
|
||||||
|
+ " <field var='ip_version' type='text-multi' >\n"
|
||||||
|
+ " <value>ipv4</value>\n"
|
||||||
|
+ " <value>ipv6</value>\n"
|
||||||
|
+ " </field>\n"
|
||||||
|
+ " <field var='os'>\n"
|
||||||
|
+ " <value>Mac</value>\n"
|
||||||
|
+ " </field>\n"
|
||||||
|
+ " <field var='os_version'>\n"
|
||||||
|
+ " <value>10.5.1</value>\n"
|
||||||
|
+ " </field>\n"
|
||||||
|
+ " <field var='software'>\n"
|
||||||
|
+ " <value>Psi</value>\n"
|
||||||
|
+ " </field>\n"
|
||||||
|
+ " <field var='software_version'>\n"
|
||||||
|
+ " <value>0.11</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(EntityCapabilities.hash(info));
|
||||||
|
Assert.assertEquals("q07IKJEyjvHSyhy//CH0CxmKi8w=", var);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue