Skip to content

Commit

Permalink
Introduce CapsVersionAndHash
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
Flowdalic committed Dec 3, 2014
1 parent 96f8bee commit 5dd97a3
Show file tree
Hide file tree
Showing 3 changed files with 73 additions and 23 deletions.
Original file line number Diff line number Diff line change
@@ -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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
import org.jivesoftware.smack.filter.AndFilter;
import org.jivesoftware.smack.filter.PacketTypeFilter;
import org.jivesoftware.smack.filter.PacketExtensionFilter;
import org.jivesoftware.smack.util.StringUtils;
import org.jivesoftware.smack.util.stringencoder.Base64;
import org.jivesoftware.smackx.caps.cache.EntityCapsPersistentCache;
import org.jivesoftware.smackx.caps.packet.CapsExtension;
Expand Down Expand Up @@ -76,6 +77,12 @@ public class EntityCapsManager extends Manager {
public static final String ELEMENT = CapsExtension.ELEMENT;

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";

protected static EntityCapsPersistentCache persistentCache;
Expand All @@ -92,7 +99,7 @@ public class EntityCapsManager extends Manager {
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);

Expand All @@ -112,8 +119,8 @@ public void connectionCreated(XMPPConnection connection) {
});

try {
MessageDigest sha1MessageDigest = MessageDigest.getInstance("SHA-1");
SUPPORTED_HASHES.put("sha-1", sha1MessageDigest);
MessageDigest sha1MessageDigest = MessageDigest.getInstance(DEFAULT_HASH);
SUPPORTED_HASHES.put(DEFAULT_HASH, sha1MessageDigest);
} catch (NoSuchAlgorithmException e) {
// Ignore
}
Expand Down Expand Up @@ -238,22 +245,25 @@ public static void clearMemoryCache() {
}

private static void addCapsExtensionInfo(String from, CapsExtension capsExtension) {
String hash = capsExtension.getHash().toLowerCase(Locale.US);
if (!SUPPORTED_HASHES.containsKey(hash))
String capsExtensionHash = capsExtension.getHash();
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;
String hash = capsExtensionHash.toLowerCase(Locale.US);

String node = capsExtension.getNode();
String ver = capsExtension.getVer();

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 boolean entityCapsEnabled;
private String currentCapsVersion;
private CapsVersionAndHash currentCapsVersion;
private boolean presenceSend = false;

/**
Expand Down Expand Up @@ -346,8 +356,8 @@ public void processPacket(Packet packet) {
public void processPacket(Packet packet) {
if (!entityCapsEnabled)
return;

CapsExtension caps = new CapsExtension(entityNode, getCapsVersion(), "sha-1");
CapsVersionAndHash capsVersionAndHash = getCapsVersion();
CapsExtension caps = new CapsExtension(entityNode, capsVersionAndHash.version, capsVersionAndHash.hash);
packet.addExtension(caps);
}
};
Expand Down Expand Up @@ -407,7 +417,7 @@ public void removeUserCapsNode(String user) {
*
* @return our own caps version
*/
public String getCapsVersion() {
public CapsVersionAndHash getCapsVersion() {
return currentCapsVersion;
}

Expand Down Expand Up @@ -464,17 +474,16 @@ public void updateLocalEntityCaps() {
discoverInfo.setFrom(connection.getUser());
sdm.addDiscoverInfoTo(discoverInfo);

currentCapsVersion = generateVerificationString(discoverInfo, "sha-1");
currentCapsVersion = generateVerificationString(discoverInfo);
addDiscoverInfoByNode(entityNode + '#' + currentCapsVersion, discoverInfo);
if (lastLocalCapsVersions.size() > 10) {
String oldCapsVersion = lastLocalCapsVersions.poll();
sdm.removeNodeInformationProvider(entityNode + '#' + oldCapsVersion);
CapsVersionAndHash oldCapsVersion = lastLocalCapsVersions.poll();
sdm.removeNodeInformationProvider(entityNode + '#' + oldCapsVersion.version);
}
lastLocalCapsVersions.add(currentCapsVersion);

CAPS_CACHE.put(currentCapsVersion, discoverInfo);
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());
sdm.setNodeInformationProvider(entityNode + '#' + currentCapsVersion, new AbstractNodeInformationProvider() {
Expand Down Expand Up @@ -535,7 +544,7 @@ public static boolean verifyDiscoverInfoVersion(String ver, String hash, Discove
if (verifyPacketExtensions(info))
return false;

String calculatedVer = generateVerificationString(info, hash);
String calculatedVer = generateVerificationString(info, hash).version;

if (!ver.equals(calculatedVer))
return false;
Expand Down Expand Up @@ -567,6 +576,10 @@ protected static boolean verifyPacketExtensions(DiscoverInfo info) {
return false;
}

protected static CapsVersionAndHash generateVerificationString(DiscoverInfo discoverInfo) {
return generateVerificationString(discoverInfo, null);
}

/**
* Generates a XEP-115 Verification String
*
Expand All @@ -575,12 +588,15 @@ protected static boolean verifyPacketExtensions(DiscoverInfo info) {
*
* @param discoverInfo
* @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
* supported
*/
protected static String generateVerificationString(DiscoverInfo discoverInfo, String hash) {
MessageDigest md = SUPPORTED_HASHES.get(hash.toLowerCase(Locale.US));
protected static CapsVersionAndHash generateVerificationString(DiscoverInfo discoverInfo, String hash) {
if (hash == null) {
hash = DEFAULT_HASH;
}
MessageDigest md = SUPPORTED_HASHES.get(hash.toUpperCase(Locale.US));
if (md == null)
return null;

Expand Down Expand Up @@ -682,7 +698,8 @@ public int compare(FormField f1, FormField f2) {
synchronized(md) {
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) {
Expand All @@ -702,6 +719,10 @@ public static class NodeVerHash {
private String ver;
private String nodeVer;

NodeVerHash(String node, CapsVersionAndHash capsVersionAndHash) {
this(node, capsVersionAndHash.version, capsVersionAndHash.hash);
}

NodeVerHash(String node, String ver, String hash) {
this.node = node;
this.ver = ver;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@

import org.jivesoftware.smack.packet.IQ;
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.StringEncoder;
import org.jivesoftware.smackx.InitExtensions;
Expand Down Expand Up @@ -54,8 +55,8 @@ public void initSmackTestSuite() {
public void testComplexGenerationExample() {
DiscoverInfo di = createComplexSamplePacket();

String ver = EntityCapsManager.generateVerificationString(di, "sha-1");
assertEquals("q07IKJEyjvHSyhy//CH0CxmKi8w=", ver);
CapsVersionAndHash versionAndHash = EntityCapsManager.generateVerificationString(di, StringUtils.SHA1);
assertEquals("q07IKJEyjvHSyhy//CH0CxmKi8w=", versionAndHash.version);
}

@Test
Expand All @@ -82,7 +83,8 @@ private void testSimpleDirectoryCache(StringEncoder stringEncoder) throws IOExce
EntityCapsManager.setPersistentCache(cache);

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
EntityCapsManager.addDiscoverInfoByNode(nodeVer, di);
Expand Down

0 comments on commit 5dd97a3

Please sign in to comment.