Introduce CapsVersionAndHash

Entity Capability versions are useless without the information which
hash algorithm was used to calculate those. Right now, only 'sha-1' is
used, but this may change in the feature. This commit makes the first
steps preparing for such a feature.

Also fixes a minor bug:

-        CAPS_CACHE.put(currentCapsVersion, discoverInfo);

currentCapsVersion is not a valid key for the cache, as it does cache
"node + '#' + ver" to disco infos.
This commit is contained in:
Florian Schmaus 2014-12-02 18:50:11 +01:00
parent 96f8bee78e
commit 5dd97a363c
3 changed files with 73 additions and 23 deletions

View File

@ -0,0 +1,27 @@
/**
*
* Copyright © 2014 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.caps;
public class CapsVersionAndHash {
public final String version;
public final String hash;
public CapsVersionAndHash(String version, String hash) {
this.version = version;
this.hash = hash;
}
}

View File

@ -34,6 +34,7 @@ import org.jivesoftware.smack.filter.PacketFilter;
import org.jivesoftware.smack.filter.AndFilter; import org.jivesoftware.smack.filter.AndFilter;
import org.jivesoftware.smack.filter.PacketTypeFilter; import org.jivesoftware.smack.filter.PacketTypeFilter;
import org.jivesoftware.smack.filter.PacketExtensionFilter; import org.jivesoftware.smack.filter.PacketExtensionFilter;
import org.jivesoftware.smack.util.StringUtils;
import org.jivesoftware.smack.util.stringencoder.Base64; import org.jivesoftware.smack.util.stringencoder.Base64;
import org.jivesoftware.smackx.caps.cache.EntityCapsPersistentCache; import org.jivesoftware.smackx.caps.cache.EntityCapsPersistentCache;
import org.jivesoftware.smackx.caps.packet.CapsExtension; import org.jivesoftware.smackx.caps.packet.CapsExtension;
@ -76,6 +77,12 @@ public class EntityCapsManager extends Manager {
public static final String ELEMENT = CapsExtension.ELEMENT; public static final String ELEMENT = CapsExtension.ELEMENT;
private static final Map<String, MessageDigest> SUPPORTED_HASHES = new HashMap<String, MessageDigest>(); private static final Map<String, MessageDigest> SUPPORTED_HASHES = new HashMap<String, MessageDigest>();
/**
* The default hash. Currently 'sha-1'.
*/
private static final String DEFAULT_HASH = StringUtils.SHA1;
private static String DEFAULT_ENTITY_NODE = "http://www.igniterealtime.org/projects/smack"; private static String DEFAULT_ENTITY_NODE = "http://www.igniterealtime.org/projects/smack";
protected static EntityCapsPersistentCache persistentCache; protected static EntityCapsPersistentCache persistentCache;
@ -92,7 +99,7 @@ public class EntityCapsManager extends Manager {
private static final PacketFilter PRESENCES = PacketTypeFilter.PRESENCE; private static final PacketFilter PRESENCES = PacketTypeFilter.PRESENCE;
/** /**
* Map of (node + '#" + hash algorithm) to DiscoverInfo data * Map of "node + '#' + hash" to DiscoverInfo data
*/ */
private static final LruCache<String, DiscoverInfo> CAPS_CACHE = new LruCache<String, DiscoverInfo>(1000); private static final LruCache<String, DiscoverInfo> CAPS_CACHE = new LruCache<String, DiscoverInfo>(1000);
@ -112,8 +119,8 @@ public class EntityCapsManager extends Manager {
}); });
try { try {
MessageDigest sha1MessageDigest = MessageDigest.getInstance("SHA-1"); MessageDigest sha1MessageDigest = MessageDigest.getInstance(DEFAULT_HASH);
SUPPORTED_HASHES.put("sha-1", sha1MessageDigest); SUPPORTED_HASHES.put(DEFAULT_HASH, sha1MessageDigest);
} catch (NoSuchAlgorithmException e) { } catch (NoSuchAlgorithmException e) {
// Ignore // Ignore
} }
@ -238,9 +245,12 @@ public class EntityCapsManager extends Manager {
} }
private static void addCapsExtensionInfo(String from, CapsExtension capsExtension) { private static void addCapsExtensionInfo(String from, CapsExtension capsExtension) {
String hash = capsExtension.getHash().toLowerCase(Locale.US); String capsExtensionHash = capsExtension.getHash();
if (!SUPPORTED_HASHES.containsKey(hash)) String hashInUppercase = capsExtensionHash.toUpperCase(Locale.US);
// SUPPORTED_HASHES uses the format of MessageDigest, which is uppercase, e.g. "SHA-1" instead of "sha-1"
if (!SUPPORTED_HASHES.containsKey(hashInUppercase))
return; return;
String hash = capsExtensionHash.toLowerCase(Locale.US);
String node = capsExtension.getNode(); String node = capsExtension.getNode();
String ver = capsExtension.getVer(); String ver = capsExtension.getVer();
@ -248,12 +258,12 @@ public class EntityCapsManager extends Manager {
JID_TO_NODEVER_CACHE.put(from, new NodeVerHash(node, ver, hash)); JID_TO_NODEVER_CACHE.put(from, new NodeVerHash(node, ver, hash));
} }
private final Queue<String> lastLocalCapsVersions = new ConcurrentLinkedQueue<String>(); private final Queue<CapsVersionAndHash> lastLocalCapsVersions = new ConcurrentLinkedQueue<>();
private final ServiceDiscoveryManager sdm; private final ServiceDiscoveryManager sdm;
private boolean entityCapsEnabled; private boolean entityCapsEnabled;
private String currentCapsVersion; private CapsVersionAndHash currentCapsVersion;
private boolean presenceSend = false; private boolean presenceSend = false;
/** /**
@ -346,8 +356,8 @@ public class EntityCapsManager extends Manager {
public void processPacket(Packet packet) { public void processPacket(Packet packet) {
if (!entityCapsEnabled) if (!entityCapsEnabled)
return; return;
CapsVersionAndHash capsVersionAndHash = getCapsVersion();
CapsExtension caps = new CapsExtension(entityNode, getCapsVersion(), "sha-1"); CapsExtension caps = new CapsExtension(entityNode, capsVersionAndHash.version, capsVersionAndHash.hash);
packet.addExtension(caps); packet.addExtension(caps);
} }
}; };
@ -407,7 +417,7 @@ public class EntityCapsManager extends Manager {
* *
* @return our own caps version * @return our own caps version
*/ */
public String getCapsVersion() { public CapsVersionAndHash getCapsVersion() {
return currentCapsVersion; return currentCapsVersion;
} }
@ -464,17 +474,16 @@ public class EntityCapsManager extends Manager {
discoverInfo.setFrom(connection.getUser()); discoverInfo.setFrom(connection.getUser());
sdm.addDiscoverInfoTo(discoverInfo); sdm.addDiscoverInfoTo(discoverInfo);
currentCapsVersion = generateVerificationString(discoverInfo, "sha-1"); currentCapsVersion = generateVerificationString(discoverInfo);
addDiscoverInfoByNode(entityNode + '#' + currentCapsVersion, discoverInfo); addDiscoverInfoByNode(entityNode + '#' + currentCapsVersion, discoverInfo);
if (lastLocalCapsVersions.size() > 10) { if (lastLocalCapsVersions.size() > 10) {
String oldCapsVersion = lastLocalCapsVersions.poll(); CapsVersionAndHash oldCapsVersion = lastLocalCapsVersions.poll();
sdm.removeNodeInformationProvider(entityNode + '#' + oldCapsVersion); sdm.removeNodeInformationProvider(entityNode + '#' + oldCapsVersion.version);
} }
lastLocalCapsVersions.add(currentCapsVersion); lastLocalCapsVersions.add(currentCapsVersion);
CAPS_CACHE.put(currentCapsVersion, discoverInfo);
if (connection != null) if (connection != null)
JID_TO_NODEVER_CACHE.put(connection.getUser(), new NodeVerHash(entityNode, currentCapsVersion, "sha-1")); JID_TO_NODEVER_CACHE.put(connection.getUser(), new NodeVerHash(entityNode, currentCapsVersion));
final List<Identity> identities = new LinkedList<Identity>(ServiceDiscoveryManager.getInstanceFor(connection).getIdentities()); final List<Identity> identities = new LinkedList<Identity>(ServiceDiscoveryManager.getInstanceFor(connection).getIdentities());
sdm.setNodeInformationProvider(entityNode + '#' + currentCapsVersion, new AbstractNodeInformationProvider() { sdm.setNodeInformationProvider(entityNode + '#' + currentCapsVersion, new AbstractNodeInformationProvider() {
@ -535,7 +544,7 @@ public class EntityCapsManager extends Manager {
if (verifyPacketExtensions(info)) if (verifyPacketExtensions(info))
return false; return false;
String calculatedVer = generateVerificationString(info, hash); String calculatedVer = generateVerificationString(info, hash).version;
if (!ver.equals(calculatedVer)) if (!ver.equals(calculatedVer))
return false; return false;
@ -567,6 +576,10 @@ public class EntityCapsManager extends Manager {
return false; return false;
} }
protected static CapsVersionAndHash generateVerificationString(DiscoverInfo discoverInfo) {
return generateVerificationString(discoverInfo, null);
}
/** /**
* Generates a XEP-115 Verification String * Generates a XEP-115 Verification String
* *
@ -575,12 +588,15 @@ public class EntityCapsManager extends Manager {
* *
* @param discoverInfo * @param discoverInfo
* @param hash * @param hash
* the used hash function * the used hash function, if null, default hash will be used
* @return The generated verification String or null if the hash is not * @return The generated verification String or null if the hash is not
* supported * supported
*/ */
protected static String generateVerificationString(DiscoverInfo discoverInfo, String hash) { protected static CapsVersionAndHash generateVerificationString(DiscoverInfo discoverInfo, String hash) {
MessageDigest md = SUPPORTED_HASHES.get(hash.toLowerCase(Locale.US)); if (hash == null) {
hash = DEFAULT_HASH;
}
MessageDigest md = SUPPORTED_HASHES.get(hash.toUpperCase(Locale.US));
if (md == null) if (md == null)
return null; return null;
@ -682,7 +698,8 @@ public class EntityCapsManager extends Manager {
synchronized(md) { synchronized(md) {
digest = md.digest(sb.toString().getBytes()); digest = md.digest(sb.toString().getBytes());
} }
return Base64.encodeToString(digest); String version = Base64.encodeToString(digest);
return new CapsVersionAndHash(version, hash);
} }
private static void formFieldValuesToCaps(List<String> i, StringBuilder sb) { private static void formFieldValuesToCaps(List<String> i, StringBuilder sb) {
@ -702,6 +719,10 @@ public class EntityCapsManager extends Manager {
private String ver; private String ver;
private String nodeVer; private String nodeVer;
NodeVerHash(String node, CapsVersionAndHash capsVersionAndHash) {
this(node, capsVersionAndHash.version, capsVersionAndHash.hash);
}
NodeVerHash(String node, String ver, String hash) { NodeVerHash(String node, String ver, String hash) {
this.node = node; this.node = node;
this.ver = ver; this.ver = ver;

View File

@ -27,6 +27,7 @@ import java.util.LinkedList;
import org.jivesoftware.smack.packet.IQ; import org.jivesoftware.smack.packet.IQ;
import org.jivesoftware.smack.test.util.SmackTestSuite; import org.jivesoftware.smack.test.util.SmackTestSuite;
import org.jivesoftware.smack.util.StringUtils;
import org.jivesoftware.smack.util.stringencoder.Base32; import org.jivesoftware.smack.util.stringencoder.Base32;
import org.jivesoftware.smack.util.stringencoder.StringEncoder; import org.jivesoftware.smack.util.stringencoder.StringEncoder;
import org.jivesoftware.smackx.InitExtensions; import org.jivesoftware.smackx.InitExtensions;
@ -54,8 +55,8 @@ public class EntityCapsManagerTest extends InitExtensions {
public void testComplexGenerationExample() { public void testComplexGenerationExample() {
DiscoverInfo di = createComplexSamplePacket(); DiscoverInfo di = createComplexSamplePacket();
String ver = EntityCapsManager.generateVerificationString(di, "sha-1"); CapsVersionAndHash versionAndHash = EntityCapsManager.generateVerificationString(di, StringUtils.SHA1);
assertEquals("q07IKJEyjvHSyhy//CH0CxmKi8w=", ver); assertEquals("q07IKJEyjvHSyhy//CH0CxmKi8w=", versionAndHash.version);
} }
@Test @Test
@ -82,7 +83,8 @@ public class EntityCapsManagerTest extends InitExtensions {
EntityCapsManager.setPersistentCache(cache); EntityCapsManager.setPersistentCache(cache);
DiscoverInfo di = createComplexSamplePacket(); DiscoverInfo di = createComplexSamplePacket();
String nodeVer = di.getNode() + "#" + EntityCapsManager.generateVerificationString(di, "sha-1"); CapsVersionAndHash versionAndHash = EntityCapsManager.generateVerificationString(di, StringUtils.SHA1);
String nodeVer = di.getNode() + "#" + versionAndHash.version;
// Save the data in EntityCapsManager // Save the data in EntityCapsManager
EntityCapsManager.addDiscoverInfoByNode(nodeVer, di); EntityCapsManager.addDiscoverInfoByNode(nodeVer, di);