Commit 7adf708d authored by guus's avatar guus

Update Client Capabilities implementation to match the XEP (OF-339).

git-svn-id: http://svn.igniterealtime.org/svn/repos/openfire/trunk@11580 b35dd754-fafc-0310-a699-88a17e54d16e
parent 75926ddb
...@@ -38,6 +38,7 @@ import java.util.Set; ...@@ -38,6 +38,7 @@ import java.util.Set;
* @author Armando Jagucki * @author Armando Jagucki
* *
*/ */
// TODO: Instances of this class should not be cached in distributed caches. The overhead of distributing data is a lot higher than recalculating the hash on every cluster node. We should remove the Externalizable interface, and turn this class into an immutable class.
public class EntityCapabilities implements Cacheable, Externalizable { public class EntityCapabilities implements Cacheable, Externalizable {
/** /**
...@@ -57,6 +58,11 @@ public class EntityCapabilities implements Cacheable, Externalizable { ...@@ -57,6 +58,11 @@ public class EntityCapabilities implements Cacheable, Externalizable {
*/ */
private String verAttribute; private String verAttribute;
/**
* The hash algorithm that was used to create the hash string.
*/
private String hashAttribute;
/** /**
* Adds an identity to the entity capabilities. * Adds an identity to the entity capabilities.
* *
...@@ -102,13 +108,22 @@ public class EntityCapabilities implements Cacheable, Externalizable { ...@@ -102,13 +108,22 @@ public class EntityCapabilities implements Cacheable, Externalizable {
return features.contains(feature); return features.contains(feature);
} }
/**
* @param verAttribute the verAttribute to set
*/
void setVerAttribute(String verAttribute) { void setVerAttribute(String verAttribute) {
this.verAttribute = verAttribute; this.verAttribute = verAttribute;
} }
String getVerAttribute() {
return this.verAttribute;
}
void setHashAttribute(String hashAttribute) {
this.hashAttribute = hashAttribute;
}
String getHashAttribute() {
return this.hashAttribute;
}
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
ExternalizableUtil.getInstance().readStrings(in, identities); ExternalizableUtil.getInstance().readStrings(in, identities);
ExternalizableUtil.getInstance().readStrings(in, features); ExternalizableUtil.getInstance().readStrings(in, features);
......
...@@ -22,11 +22,13 @@ package org.jivesoftware.openfire.entitycaps; ...@@ -22,11 +22,13 @@ package org.jivesoftware.openfire.entitycaps;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import org.dom4j.Element; import org.dom4j.Element;
import org.dom4j.QName;
import org.jivesoftware.openfire.IQRouter; import org.jivesoftware.openfire.IQRouter;
import org.jivesoftware.openfire.XMPPServer; import org.jivesoftware.openfire.XMPPServer;
import org.jivesoftware.openfire.event.UserEventListener; import org.jivesoftware.openfire.event.UserEventListener;
...@@ -101,19 +103,15 @@ public class EntityCapabilitiesManager implements IQResultListener, UserEventLis ...@@ -101,19 +103,15 @@ public class EntityCapabilitiesManager implements IQResultListener, UserEventLis
* correct 'ver' hash in the map, that was previously encountered in the * correct 'ver' hash in the map, that was previously encountered in the
* caps packet. * caps packet.
* *
* We use a cache for this map so it is cluster safe for remote users
* whose disco#info replies are handled by new nodes in the cluster (after
* an s2s disconnection for example).
*
* Key: Packet ID of our disco#info request. * Key: Packet ID of our disco#info request.
* Value: The 'ver' hash string from the original caps packet. * Value: The 'ver' hash string from the original caps packet.
*/ */
private Cache<String, String> verAttributes; private Map<String, EntityCapabilities> verAttributes;
private EntityCapabilitiesManager() { private EntityCapabilitiesManager() {
entityCapabilitiesMap = CacheFactory.createCache("Entity Capabilities"); entityCapabilitiesMap = CacheFactory.createCache("Entity Capabilities");
entityCapabilitiesUserMap = CacheFactory.createCache("Entity Capabilities Users"); entityCapabilitiesUserMap = CacheFactory.createCache("Entity Capabilities Users");
verAttributes = CacheFactory.createCache("Entity Capabilities Pending Hashes"); verAttributes = new HashMap<String, EntityCapabilities>();
} }
/** /**
...@@ -143,15 +141,15 @@ public class EntityCapabilitiesManager implements IQResultListener, UserEventLis ...@@ -143,15 +141,15 @@ public class EntityCapabilitiesManager implements IQResultListener, UserEventLis
// TODO: if this packet is in legacy format, we SHOULD check the 'node', // TODO: if this packet is in legacy format, we SHOULD check the 'node',
// 'ver', and 'ext' combinations as specified in the archived version // 'ver', and 'ext' combinations as specified in the archived version
// 1.3 of the specification, and cache the results. See JM-1447 // 1.3 of the specification, and cache the results. See JM-1447
String hashAttribute = capsElement.attributeValue("hash"); final String hashAttribute = capsElement.attributeValue("hash");
if (hashAttribute == null) { if (hashAttribute == null || hashAttribute.trim().length() == 0) {
return; return;
} }
// Examine the packet and check if it has and a 'ver' hash // Examine the packet and check if it has and a 'ver' hash
// if not -- do nothing by returning. // if not -- do nothing by returning.
String newVerAttribute = capsElement.attributeValue("ver"); final String newVerAttribute = capsElement.attributeValue("ver");
if (newVerAttribute == null) { if (newVerAttribute == null || newVerAttribute.trim().length() == 0) {
return; return;
} }
...@@ -174,9 +172,13 @@ public class EntityCapabilitiesManager implements IQResultListener, UserEventLis ...@@ -174,9 +172,13 @@ public class EntityCapabilitiesManager implements IQResultListener, UserEventLis
iq.setChildElement("query", "http://jabber.org/protocol/disco#info"); iq.setChildElement("query", "http://jabber.org/protocol/disco#info");
String packetId = iq.getID(); String packetId = iq.getID();
verAttributes.put(packetId, newVerAttribute);
final EntityCapabilities caps = new EntityCapabilities();
caps.setHashAttribute(hashAttribute);
caps.setVerAttribute(newVerAttribute);
verAttributes.put(packetId, caps);
IQRouter iqRouter = XMPPServer.getInstance().getIQRouter(); final IQRouter iqRouter = XMPPServer.getInstance().getIQRouter();
iqRouter.addIQResultListener(packetId, this); iqRouter.addIQResultListener(packetId, this);
iqRouter.route(iq); iqRouter.route(iq);
} }
...@@ -204,10 +206,13 @@ public class EntityCapabilitiesManager implements IQResultListener, UserEventLis ...@@ -204,10 +206,13 @@ public class EntityCapabilitiesManager implements IQResultListener, UserEventLis
* hash of the original caps packet. * hash of the original caps packet.
*/ */
private boolean isValid(IQ packet) { private boolean isValid(IQ packet) {
String newVerHash = generateVerHash(packet); final EntityCapabilities original = verAttributes.get(packet.getID());
if (original == null) {
return false;
}
final String newVerHash = generateVerHash(packet, original.getHashAttribute());
String originalVerAttribute = verAttributes.get(packet.getID()); return newVerHash.equals(original.getVerAttribute());
return originalVerAttribute.equals(newVerHash);
} }
/** /**
...@@ -220,43 +225,53 @@ public class EntityCapabilitiesManager implements IQResultListener, UserEventLis ...@@ -220,43 +225,53 @@ public class EntityCapabilitiesManager implements IQResultListener, UserEventLis
* outlined in XEP-0115. * outlined in XEP-0115.
* *
* @param packet IQ reply to the entity cap request. * @param packet IQ reply to the entity cap request.
* @param algorithm The hashing algorithm to use (e.g. SHA-1)
* @return the generated 'ver' hash * @return the generated 'ver' hash
*/ */
private String generateVerHash(IQ packet) { public static String generateVerHash(IQ packet, String algorithm) {
// Initialize an empty string S. // Initialize an empty string S.
String S = ""; final StringBuilder s = new StringBuilder();
// Sort the service discovery identities by category and then by type // Sort the service discovery identities by category and then by type
// (if it exists), formatted as 'category' '/' 'type'. // (if it exists), formatted as 'category' '/' 'type' / 'lang' / 'name'
List<String> discoIdentities = getIdentitiesFrom(packet); final List<String> discoIdentities = getIdentitiesFrom(packet);
Collections.sort(discoIdentities); Collections.sort(discoIdentities);
// For each identity, append the 'category/type' to S, followed by the // For each identity, append the 'category/type/lang/name' to S,
// '<' character. // followed by the '<' character.
for (String discoIdentity : discoIdentities) { for (String discoIdentity : discoIdentities) {
S += discoIdentity; s.append(discoIdentity);
S += '<'; s.append('<');
} }
// Sort the supported features. // Sort the supported service discovery features.
List<String> discoFeatures = getFeaturesFrom(packet); final List<String> discoFeatures = getFeaturesFrom(packet);
Collections.sort(discoFeatures); Collections.sort(discoFeatures);
// For each feature, append the feature to S, followed by the '<' // For each feature, append the feature to S, followed by the '<'
// character. // character.
for (String discoFeature : discoFeatures) { for (String discoFeature : discoFeatures) {
S += discoFeature; s.append(discoFeature);
S += '<'; s.append('<');
} }
// If the service discovery information response includes XEP-0128
// data forms, sort the forms by the FORM_TYPE (i.e., by the XML
// character data of the <value/> element).
final List<String> extendedDataForms = getExtendedDataForms(packet);
Collections.sort(extendedDataForms);
for (String extendedDataForm : extendedDataForms) {
s.append(extendedDataForm);
// no need to add '<', this is done in #getExtendedDataForms()
}
// Compute ver by hashing S using the SHA-1 algorithm as specified in // Compute ver by hashing S using the SHA-1 algorithm as specified in
// RFC 3174 (with binary output) and encoding the hash using Base64 as // RFC 3174 (with binary output) and encoding the hash using Base64 as
// specified in Section 4 of RFC 4648 (note: the Base64 output // specified in Section 4 of RFC 4648 (note: the Base64 output
// MUST NOT include whitespace and MUST set padding bits to zero). // MUST NOT include whitespace and MUST set padding bits to zero).
S = StringUtils.hash(S, "SHA-1"); final String hashed = StringUtils.hash(s.toString(), "SHA-1");
S = StringUtils.encodeBase64(StringUtils.decodeHex(S)); return StringUtils.encodeBase64(StringUtils.decodeHex(hashed));
return S;
} }
public void answerTimeout(String packetId) { public void answerTimeout(String packetId) {
...@@ -272,26 +287,25 @@ public class EntityCapabilitiesManager implements IQResultListener, UserEventLis ...@@ -272,26 +287,25 @@ public class EntityCapabilitiesManager implements IQResultListener, UserEventLis
// The packet was validated, so it can be added to the Entity // The packet was validated, so it can be added to the Entity
// Capabilities cache map. // Capabilities cache map.
// Create the entity capabilities object and add it to the cache map... // Add the resolved identities and features to the entity
EntityCapabilities entityCapabilities = new EntityCapabilities(); // EntityCapabilitiesManager.capabilities object and add it
// to the cache map...
EntityCapabilities caps = verAttributes.get(packetId);
// Store identities. // Store identities.
List<String> identities = getIdentitiesFrom(packet); List<String> identities = getIdentitiesFrom(packet);
for (String identity : identities) { for (String identity : identities) {
entityCapabilities.addIdentity(identity); caps.addIdentity(identity);
} }
// Store features. // Store features.
List<String> features = getFeaturesFrom(packet); List<String> features = getFeaturesFrom(packet);
for (String feature : features) { for (String feature : features) {
entityCapabilities.addFeature(feature); caps.addFeature(feature);
} }
String originalVerAttribute = verAttributes.get(packetId); entityCapabilitiesMap.put(caps.getVerAttribute(), caps);
entityCapabilities.setVerAttribute(originalVerAttribute); entityCapabilitiesUserMap.put(packet.getFrom(), caps.getVerAttribute());
entityCapabilitiesMap.put(originalVerAttribute, entityCapabilities);
entityCapabilitiesUserMap.put(packet.getFrom(), originalVerAttribute);
} }
// Remove cached 'ver' attribute. // Remove cached 'ver' attribute.
...@@ -316,19 +330,41 @@ public class EntityCapabilitiesManager implements IQResultListener, UserEventLis ...@@ -316,19 +330,41 @@ public class EntityCapabilitiesManager implements IQResultListener, UserEventLis
* @param packet the packet * @param packet the packet
* @return a list of identities * @return a list of identities
*/ */
private List<String> getIdentitiesFrom(IQ packet) { private static List<String> getIdentitiesFrom(IQ packet) {
List<String> discoIdentities = new ArrayList<String>(); List<String> discoIdentities = new ArrayList<String>();
Element query = packet.getChildElement(); Element query = packet.getChildElement();
Iterator identitiesIterator = query.elementIterator("identity"); Iterator<Element> identitiesIterator = query.elementIterator("identity");
if (identitiesIterator != null) { if (identitiesIterator != null) {
while (identitiesIterator.hasNext()) { while (identitiesIterator.hasNext()) {
Element identityElement = (Element) identitiesIterator.next(); Element identityElement = identitiesIterator.next();
String discoIdentity = identityElement.attributeValue("category"); StringBuilder discoIdentity = new StringBuilder();
discoIdentity += '/';
discoIdentity += identityElement.attributeValue("type"); String cat = identityElement.attributeValue("category");
String type = identityElement.attributeValue("type");
discoIdentities.add(discoIdentity); String lang = identityElement.attributeValue("xml:lang");
String name = identityElement.attributeValue("name");
if (cat != null) {
discoIdentity.append(cat);
}
discoIdentity.append('/');
if (type != null) {
discoIdentity.append(type);
}
discoIdentity.append('/');
if (lang != null) {
discoIdentity.append(lang);
}
discoIdentity.append('/');
if (name != null) {
discoIdentity.append(name);
}
discoIdentities.add(discoIdentity.toString());
} }
} }
return discoIdentities; return discoIdentities;
...@@ -340,13 +376,13 @@ public class EntityCapabilitiesManager implements IQResultListener, UserEventLis ...@@ -340,13 +376,13 @@ public class EntityCapabilitiesManager implements IQResultListener, UserEventLis
* @param packet the packet * @param packet the packet
* @return a list of features * @return a list of features
*/ */
private List<String> getFeaturesFrom(IQ packet) { private static List<String> getFeaturesFrom(IQ packet) {
List<String> discoFeatures = new ArrayList<String>(); List<String> discoFeatures = new ArrayList<String>();
Element query = packet.getChildElement(); Element query = packet.getChildElement();
Iterator featuresIterator = query.elementIterator("feature"); Iterator<Element> featuresIterator = query.elementIterator("feature");
if (featuresIterator != null) { if (featuresIterator != null) {
while (featuresIterator.hasNext()) { while (featuresIterator.hasNext()) {
Element featureElement = (Element) featuresIterator.next(); Element featureElement = featuresIterator.next();
String discoFeature = featureElement.attributeValue("var"); String discoFeature = featureElement.attributeValue("var");
discoFeatures.add(discoFeature); discoFeatures.add(discoFeature);
...@@ -355,6 +391,63 @@ public class EntityCapabilitiesManager implements IQResultListener, UserEventLis ...@@ -355,6 +391,63 @@ public class EntityCapabilitiesManager implements IQResultListener, UserEventLis
return discoFeatures; return discoFeatures;
} }
/**
* Extracts a list of extended service discovery information from an IQ
* packet.
*
* @param packet
* the packet
* @return a list of extended service discoverin information features.
*/
private static List<String> getExtendedDataForms(IQ packet) {
List<String> results = new ArrayList<String>();
Element query = packet.getChildElement();
Iterator<Element> extensionIterator = query.elementIterator(QName.get(
"x", "jabber:x:data"));
if (extensionIterator != null) {
while (extensionIterator.hasNext()) {
Element extensionElement = extensionIterator.next();
final StringBuilder formType = new StringBuilder();
Iterator<Element> fieldIterator = extensionElement
.elementIterator("field");
List<String> vars = new ArrayList<String>();
while (fieldIterator != null && fieldIterator.hasNext()) {
final Element fieldElement = fieldIterator.next();
if (fieldElement.attributeValue("var").equals("FORM_TYPE")) {
formType
.append(fieldElement.element("value").getText());
formType.append('<');
} else {
final StringBuilder var = new StringBuilder();
var.append(fieldElement.attributeValue("var"));
var.append('<');
Iterator<Element> valIter = fieldElement
.elementIterator("value");
List<String> values = new ArrayList<String>();
while (valIter != null && valIter.hasNext()) {
Element value = valIter.next();
values.add(value.getText());
}
Collections.sort(values);
for (String v : values) {
var.append(v);
var.append('<');
}
vars.add(var.toString());
}
}
Collections.sort(vars);
for (String v : vars) {
formType.append(v);
}
results.add(formType.toString());
}
}
return results;
}
public void userDeleting(User user, Map<String, Object> params) { public void userDeleting(User user, Map<String, Object> params) {
// Delete this user's association in entityCapabilitiesUserMap. // Delete this user's association in entityCapabilitiesUserMap.
JID jid = XMPPServer.getInstance().createJID(user.getUsername(), null, true); JID jid = XMPPServer.getInstance().createJID(user.getUsername(), null, true);
......
...@@ -126,7 +126,6 @@ public class CacheFactory { ...@@ -126,7 +126,6 @@ public class CacheFactory {
cacheNames.put("Remote Server Configurations", "serversConfigurations"); cacheNames.put("Remote Server Configurations", "serversConfigurations");
cacheNames.put("Entity Capabilities", "entityCapabilities"); cacheNames.put("Entity Capabilities", "entityCapabilities");
cacheNames.put("Entity Capabilities Users", "entityCapabilitiesUsers"); cacheNames.put("Entity Capabilities Users", "entityCapabilitiesUsers");
cacheNames.put("Entity Capabilities Pending Hashes", "entityCapabilitiesPendingHashes");
cacheNames.put("Clearspace SSO Nonce", "clearspaceSSONonce"); cacheNames.put("Clearspace SSO Nonce", "clearspaceSSONonce");
cacheNames.put("PEPServiceManager", "pepServiceManager"); cacheNames.put("PEPServiceManager", "pepServiceManager");
...@@ -194,8 +193,6 @@ public class CacheFactory { ...@@ -194,8 +193,6 @@ public class CacheFactory {
cacheProps.put("cache.entityCapabilities.maxLifetime", JiveConstants.DAY * 2); cacheProps.put("cache.entityCapabilities.maxLifetime", JiveConstants.DAY * 2);
cacheProps.put("cache.entityCapabilitiesUsers.size", -1l); cacheProps.put("cache.entityCapabilitiesUsers.size", -1l);
cacheProps.put("cache.entityCapabilitiesUsers.maxLifetime", JiveConstants.DAY * 2); cacheProps.put("cache.entityCapabilitiesUsers.maxLifetime", JiveConstants.DAY * 2);
cacheProps.put("cache.entityCapabilitiesPendingHashes.size", -1l);
cacheProps.put("cache.entityCapabilitiesPendingHashes.maxLifetime", JiveConstants.DAY * 2);
cacheProps.put("cache.pluginCacheInfo.size", -1l); cacheProps.put("cache.pluginCacheInfo.size", -1l);
cacheProps.put("cache.pluginCacheInfo.maxLifetime", -1l); cacheProps.put("cache.pluginCacheInfo.maxLifetime", -1l);
cacheProps.put("cache.clearspaceSSONonce.size", -1l); cacheProps.put("cache.clearspaceSSONonce.size", -1l);
......
...@@ -864,25 +864,6 @@ http://www.tangosol.com/UserGuide-Reference-CacheConfig.jsp ...@@ -864,25 +864,6 @@ http://www.tangosol.com/UserGuide-Reference-CacheConfig.jsp
</init-params> </init-params>
</cache-mapping> </cache-mapping>
<cache-mapping>
<cache-name>Entity Capabilities Pending Hashes</cache-name>
<scheme-name>near-distributed</scheme-name>
<init-params>
<init-param>
<param-name>back-size-high</param-name>
<param-value>0</param-value>
</init-param>
<init-param>
<param-name>back-expiry</param-name>
<param-value>48h</param-value>
</init-param>
<init-param>
<param-name>back-size-low</param-name>
<param-value>0</param-value>
</init-param>
</init-params>
</cache-mapping>
<cache-mapping> <cache-mapping>
<cache-name>PEPServiceManager</cache-name> <cache-name>PEPServiceManager</cache-name>
<scheme-name>near-distributed</scheme-name> <scheme-name>near-distributed</scheme-name>
......
...@@ -16,143 +16,156 @@ ...@@ -16,143 +16,156 @@
package org.jivesoftware.util; package org.jivesoftware.util;
import static org.junit.Assert.*; import static org.junit.Assert.assertEquals;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import org.dom4j.Element; import org.dom4j.Element;
import org.dom4j.QName;
import org.jivesoftware.openfire.entitycaps.EntityCapabilitiesManager;
import org.jivesoftware.util.cache.CacheFactory;
import org.junit.BeforeClass;
import org.junit.Test; import org.junit.Test;
import org.xmpp.packet.IQ; import org.xmpp.packet.IQ;
/** /**
* Test cases for the EntityCapabilitiesManager class. * Test cases for the {@link EntityCapabilitiesManager} class.
* *
* @author Armando Jagucki * @see <a
* href="http://xmpp.org/extensions/xep-0115.html">XEP-0115:&nbsp;Entity&nbsp;Capabilities</a>
* @author Guus der Kinderen, guus.der.kinderen@gmail.com
*/ */
public class EntityCapabilitiesManagerTest { public class EntityCapabilitiesManagerTest {
@Test @BeforeClass
public void testGenerateVerHash() { public static void setUp() throws Exception {
CacheFactory.initialize();
IQ iq = new IQ(IQ.Type.result); }
iq.setFrom("nurse@capulet.lit/chamber");
iq.setTo("juliet@capulet.lit"); /**
iq.setID("disco123"); * Tests the CAPS verification string generation based on the
* "Simple Generation Example" provided in section 5.2 of XEP-0115 (version
Element query = iq.setChildElement("query", "http://jabber.org/protocol/disco#info"); * 1.4 and later).
*/
Element identity = query.addElement("identity"); @Test
identity.addAttribute("category", "client"); public void testSimpleGenerationExample() throws Exception {
identity.addAttribute("type", "pc"); // formulate the result stanza
final IQ iq = new IQ(IQ.Type.result);
Element feature = query.addElement("feature"); iq.setFrom("nurse@capulet.lit/chamber");
feature.addAttribute("var", "http://jabber.org/protocol/disco#info"); iq.setTo("juliet@capulet.lit");
feature = query.addElement("feature"); iq.setID("simpleexample1");
feature.addAttribute("var", "http://jabber.org/protocol/disco#items");
feature = query.addElement("feature"); final Element query = iq.setChildElement("query",
feature.addAttribute("var", "http://jabber.org/protocol/muc"); "http://jabber.org/protocol/disco#info");
assertEquals("Generating ver Hash #1", "8RovUdtOmiAjzj+xI7SK5BCw3A8=", generateVerHash(iq)); // Consider an entity whose category is "client", whose service
// discovery type is "pc", service discovery name is "Exodus 0.9.1"
} // (...)
final Element identity = query.addElement("identity");
@Test identity.addAttribute("category", "client");
public void testGenerateVerHash2() { identity.addAttribute("type", "pc");
String S = "client/pc<http://jabber.org/protocol/disco#info<http://jabber.org/protocol/disco#items<http://jabber.org/protocol/muc<"; identity.addAttribute("name", "Exodus 0.9.1");
assertEquals("Generating ver Hash #2", "8RovUdtOmiAjzj+xI7SK5BCw3A8=", StringUtils.encodeBase64(StringUtils.decodeHex(StringUtils.hash(S, "SHA-1"))));
// (...) and whose supported features are
} // "http://jabber.org/protocol/disco#info",
// "http://jabber.org/protocol/disco#items",
@Test // "http://jabber.org/protocol/muc" and
public void testGenerateVerHash3() { // "http://jabber.org/protocol/caps"
String S = "client/pda<http://jabber.org/protocol/geoloc<http://jabber.org/protocol/geoloc+notify<http://jabber.org/protocol/tune<http://jabber.org/protocol/tune+notify<"; query.addElement("feature").addAttribute("var",
assertEquals("Generating ver Hash #3", "DqGwXvV/QC6X9QrPOFAwJoDwHkk=", StringUtils.encodeBase64(StringUtils.decodeHex(StringUtils.hash(S, "SHA-1")))); "http://jabber.org/protocol/disco#info");
query.addElement("feature").addAttribute("var",
} "http://jabber.org/protocol/disco#items");
query.addElement("feature").addAttribute("var",
@Test "http://jabber.org/protocol/muc");
public void testGenerateVerHash4() { query.addElement("feature").addAttribute("var",
String S = "client/pc<http://jabber.org/protocol/activity<http://jabber.org/protocol/activity+notify<http://jabber.org/protocol/geoloc<http://jabber.org/protocol/geoloc+notify<http://jabber.org/protocol/muc<http://jabber.org/protocol/tune<http://jabber.org/protocol/tune+notify<"; "http://jabber.org/protocol/caps");
assertEquals("Generating ver Hash #4", "Hm1UHUVZowSehEBlWo8lO8mPy/M=", StringUtils.encodeBase64(StringUtils.decodeHex(StringUtils.hash(S, "SHA-1"))));
// Using the SHA-1 algorithm (...)
} final String verification = EntityCapabilitiesManager.generateVerHash(
iq, "sha-1");
/**
* Generates a 'ver' hash attribute. // the verification string result must be QgayPKawpkPSDYmwT/WM94uAlu0=
* assertEquals("QgayPKawpkPSDYmwT/WM94uAlu0=", verification);
* In order to help prevent poisoning of entity capabilities information, }
* the value of the 'ver' attribute is generated according to the method
* outlined in XEP-0115. /**
* * Tests the CAPS verification string generation based on the
* @param packet * "Complex Generation Example" provided in section 5.3 of XEP-0115 (version
* @return the generated 'ver' hash * 1.4 and later).
*/ */
public String generateVerHash(IQ packet) { @Test
// Initialize an empty string S. public void testComplexGenerationExample() throws Exception {
String S = ""; // formulate the result stanza
final IQ iq = new IQ(IQ.Type.result);
/* iq.setFrom("nurse@capulet.lit/chamber");
* Sort the service discovery identities by category and then by type iq.setTo("juliet@capulet.lit");
* (if it exists), formatted as 'category' '/' 'type'. iq.setID("simpleexample1");
*/
List<String> discoIdentities = new ArrayList<String>(); final Element query = iq.setChildElement("query",
Element query = packet.getChildElement(); "http://jabber.org/protocol/disco#info");
Iterator identitiesIterator = query.elementIterator("identity"); query.addAttribute("node",
if (identitiesIterator != null) { "http://psi-im.org#q07IKJEyjvHSyhy//CH0CxmKi8w=");
while (identitiesIterator.hasNext()) {
Element identityElement = (Element) identitiesIterator.next(); // Two identities: "client/pc/Psi" and "client/pc/"
final Element identityA = query.addElement("identity");
String discoIdentity = identityElement.attributeValue("category"); identityA.addAttribute("category", "client");
discoIdentity += '/'; identityA.addAttribute("type", "pc");
discoIdentity += identityElement.attributeValue("type"); identityA.addAttribute("name", "Psi 0.11");
identityA.addAttribute("xml:lang", "en");
discoIdentities.add(discoIdentity);
} final Element identityB = query.addElement("identity");
Collections.sort(discoIdentities); identityB.addAttribute("category", "client");
} identityB.addAttribute("type", "pc");
identityB.addAttribute("name", "\u03a8 0.11");
/* identityB.addAttribute("xml:lang", "el");
* For each identity, append the 'category/type' to S, followed by the
* '<' character. // the features: "http://jabber.org/protocol/caps",
*/ // http://jabber.org/protocol/disco#info",
for (String discoIdentity : discoIdentities) { // "http://jabber.org/protocol/disco#items",
S += discoIdentity; // "http://jabber.org/protocol/muc".
S += '<'; query.addElement("feature").addAttribute("var",
} "http://jabber.org/protocol/disco#info");
query.addElement("feature").addAttribute("var",
// Sort the supported features. "http://jabber.org/protocol/disco#items");
List<String> discoFeatures = new ArrayList<String>(); query.addElement("feature").addAttribute("var",
Iterator featuresIterator = query.elementIterator("feature"); "http://jabber.org/protocol/muc");
if (featuresIterator != null) { query.addElement("feature").addAttribute("var",
while (featuresIterator.hasNext()) { "http://jabber.org/protocol/caps");
Element featureElement = (Element) featuresIterator.next();
String discoFeature = featureElement.attributeValue("var"); // extended service discovery forms
discoFeatures.add(discoFeature); final Element ext = query.addElement(QName.get("x", "jabber:x:data"));
} ext.addAttribute("type", "result");
Collections.sort(discoFeatures);
} final Element formField = ext.addElement("field");
formField.addAttribute("var", "FORM_TYPE");
/* formField.addAttribute("type", "hidden");
* For each feature, append the feature to S, followed by the '<' formField.addElement("value")
* character. .setText("urn:xmpp:dataforms:softwareinfo");
*/
for (String discoFeature : discoFeatures) { final Element ipField = ext.addElement("field");
S += discoFeature; ipField.addAttribute("var", "ip_version");
S += '<'; ipField.addElement("value").setText("ipv4");
} ipField.addElement("value").setText("ipv6");
/* final Element osField = ext.addElement("field");
* Compute ver by hashing S using the SHA-1 algorithm as specified in osField.addAttribute("var", "os");
* RFC 3174 (with binary output) and encoding the hash using Base64 as osField.addElement("value").setText("Mac");
* specified in Section 4 of RFC 4648 (note: the Base64 output
* MUST NOT include whitespace and MUST set padding bits to zero). final Element osvField = ext.addElement("field");
*/ osvField.addAttribute("var", "os_version");
S = StringUtils.hash(S, "SHA-1"); osvField.addElement("value").setText("10.5.1");
S = StringUtils.encodeBase64(StringUtils.decodeHex(S));
final Element softwareField = ext.addElement("field");
return S; softwareField.addAttribute("var", "software");
} softwareField.addElement("value").setText("Psi");
final Element softwarevField = ext.addElement("field");
softwarevField.addAttribute("var", "software_version");
softwarevField.addElement("value").setText("0.11");
// Using the SHA-1 algorithm (...)
final String verification = EntityCapabilitiesManager.generateVerHash(
iq, "SHA-1");
// the verification string result must be q07IKJEyjvHSyhy//CH0CxmKi8w=
assertEquals("q07IKJEyjvHSyhy//CH0CxmKi8w=", verification);
}
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment