#286 : Initial implementation of XEP-0115 : Entity Capabilities
authorDa Risk <darisk972@gmail.com>
Thu, 22 Jul 2010 23:34:36 +0200
changeset 793 4fb9df09ffdf
parent 787 300eddc3be4b
child 794 5dd9d68b6ad3
#286 : Initial implementation of XEP-0115 : Entity Capabilities
doc/asmack-beem/beem_patches/50-public-info-features.patch
libs/asmack-android-7-beem.jar
src/com/beem/project/beem/BeemService.java
src/com/beem/project/beem/service/XmppConnectionAdapter.java
src/com/beem/project/beem/smack/caps/CapsExtension.java
src/com/beem/project/beem/smack/caps/CapsManager.java
src/com/beem/project/beem/smack/caps/CapsProvider.java
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/asmack-beem/beem_patches/50-public-info-features.patch	Thu Jul 22 23:34:36 2010 +0200
@@ -0,0 +1,18 @@
+--- org/jivesoftware/smackx/packet/DiscoverInfo.java	2010-07-22 22:16:27.000000000 +0200
++++ org/jivesoftware/smackx/packet/DiscoverInfo.java	2010-07-22 22:58:43.000000000 +0200
+@@ -62,7 +62,7 @@
+      *
+      * @return an Iterator on the discovered features of an XMPP entity
+      */
+-    Iterator<Feature> getFeatures() {
++    public Iterator<Feature> getFeatures() {
+         synchronized (features) {
+             return Collections.unmodifiableList(features).iterator();
+         }
+@@ -266,4 +266,4 @@
+             return buf.toString();
+         }
+     }
+-}
+\ Pas de fin de ligne à la fin du fichier.
++}
Binary file libs/asmack-android-7-beem.jar has changed
--- a/src/com/beem/project/beem/BeemService.java	Wed Jul 21 23:29:41 2010 +0200
+++ b/src/com/beem/project/beem/BeemService.java	Thu Jul 22 23:34:36 2010 +0200
@@ -51,6 +51,8 @@
 import org.jivesoftware.smack.provider.PrivacyProvider;
 import org.jivesoftware.smack.provider.ProviderManager;
 import org.jivesoftware.smackx.provider.DelayInfoProvider;
+import org.jivesoftware.smackx.provider.DiscoverInfoProvider;
+import org.jivesoftware.smackx.provider.DiscoverItemsProvider;
 import org.jivesoftware.smackx.packet.ChatStateExtension;
 import org.jivesoftware.smack.proxy.ProxyInfo;
 import org.jivesoftware.smack.proxy.ProxyInfo.ProxyType;
@@ -78,6 +80,7 @@
 import com.beem.project.beem.utils.BeemBroadcastReceiver;
 import com.beem.project.beem.utils.BeemConnectivity;
 import com.beem.project.beem.utils.Status;
+import com.beem.project.beem.smack.caps.CapsProvider;
 
 /**
  * This class is for the Beem service.
@@ -319,6 +322,11 @@
 	// Delayed Delivery only the new version
 	pm.addExtensionProvider("delay", "urn:xmpp:delay", new DelayInfoProvider());
 
+	// Service Discovery # Items
+	pm.addIQProvider("query", "http://jabber.org/protocol/disco#items", new DiscoverItemsProvider());
+	// Service Discovery # Info
+	pm.addIQProvider("query", "http://jabber.org/protocol/disco#info", new DiscoverInfoProvider());
+
 	// Chat State
 	ChatStateExtension.Provider chatState = new ChatStateExtension.Provider();
 	pm.addExtensionProvider("active", "http://jabber.org/protocol/chatstates", chatState);
@@ -327,6 +335,7 @@
 	pm.addExtensionProvider("paused", "http://jabber.org/protocol/chatstates", chatState);
 	pm.addExtensionProvider("inactive", "http://jabber.org/protocol/chatstates", chatState);
 	pm.addExtensionProvider("gone", "http://jabber.org/protocol/chatstates", chatState);
+	pm.addExtensionProvider("c", "http://jabber.org/protocol/caps", new CapsProvider());
 	/*
 	// Private Data Storage
 	pm.addIQProvider("query", "jabber:iq:private", new PrivateDataManager.PrivateDataIQProvider());
@@ -344,10 +353,6 @@
 	pm.addExtensionProvider("html", "http://jabber.org/protocol/xhtml-im", new XHTMLExtensionProvider());
 	// Group Chat Invitations
 	pm.addExtensionProvider("x", "jabber:x:conference", new GroupChatInvitation.Provider());
-	// Service Discovery # Items
-	pm.addIQProvider("query", "http://jabber.org/protocol/disco#items", new DiscoverItemsProvider());
-	// Service Discovery # Info
-	pm.addIQProvider("query", "http://jabber.org/protocol/disco#info", new DiscoverInfoProvider());
 	// Data Forms
 	pm.addExtensionProvider("x", "jabber:x:data", new DataFormProvider());
 	// MUC User
--- a/src/com/beem/project/beem/service/XmppConnectionAdapter.java	Wed Jul 21 23:29:41 2010 +0200
+++ b/src/com/beem/project/beem/service/XmppConnectionAdapter.java	Thu Jul 22 23:34:36 2010 +0200
@@ -76,6 +76,7 @@
 import com.beem.project.beem.ui.Subscription;
 import com.beem.project.beem.utils.BeemBroadcastReceiver;
 import com.beem.project.beem.utils.Status;
+import com.beem.project.beem.smack.caps.CapsManager;
 
 /**
  * This class implements an adapter for XMPPConnection.
@@ -205,9 +206,6 @@
 	if (!mAdaptee.isConnected())
 	    return false;
 	try {
-	    mAdaptee.login(mLogin, mPassword, mResource);
-	    mChatManager = new BeemChatManager(mAdaptee.getChatManager(), mService);
-	    mPrivacyListManager = new PrivacyListManagerAdapter(PrivacyListManager.getInstanceFor(mAdaptee));
 
 	    this.initFeatures(); // pour declarer les features xmpp qu'on
 	    // supporte
@@ -227,6 +225,9 @@
 
 	    mAdaptee.addPacketListener(mSubscribePacketListener, filter);
 
+	    mAdaptee.login(mLogin, mPassword, mResource);
+	    mChatManager = new BeemChatManager(mAdaptee.getChatManager(), mService);
+	    mPrivacyListManager = new PrivacyListManagerAdapter(PrivacyListManager.getInstanceFor(mAdaptee));
 	    mService.resetStatus();
 	    mService.initJingle(mAdaptee);
 
@@ -392,7 +393,9 @@
 	    sdm = new ServiceDiscoveryManager(mAdaptee);
 	sdm.addFeature("http://jabber.org/protocol/disco#info");
 	sdm.addFeature("jabber:iq:privacy");
+	sdm.addFeature("http://jabber.org/protocol/caps");
 	mChatStateManager = ChatStateManager.getInstance(mAdaptee);
+	CapsManager caps = new CapsManager(sdm, mAdaptee);
     }
 
     /**
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/com/beem/project/beem/smack/caps/CapsExtension.java	Thu Jul 22 23:34:36 2010 +0200
@@ -0,0 +1,78 @@
+package com.beem.project.beem.smack.caps;
+
+import org.jivesoftware.smack.packet.PacketExtension;
+
+public class CapsExtension implements PacketExtension {
+
+    private String mVer;
+    private String mHash;
+    private String mNode;
+    private String mExt;
+
+
+    public CapsExtension(String hash, String node, String ver) {
+	mHash = hash;
+	mNode = node;
+	mVer = ver;
+    }
+
+    public String getVer(){
+	return mVer;
+    }
+
+    public String getHash(){
+	return mHash;
+    }
+
+    public String getNode(){
+	return mNode;
+    }
+
+    public String getExt(){
+	return mExt;
+    }
+
+    public void setHash(String hash) {
+	mHash = hash;
+    }
+
+    public void setVer(String ver) {
+	mVer = ver;
+    }
+
+    public void setNode(String node) {
+	mNode = node;
+    }
+
+    public void setExt(String ext) {
+	mExt = ext;
+    }
+
+    @Override
+     public String getElementName() {
+	return "c";
+     }
+
+    @Override
+     public String getNamespace() {
+	 return "http://jabber.org/protocol/caps";
+     }
+		
+    @Override
+     public String toXML(){
+	 StringBuilder b = new StringBuilder("<");
+	 b.append(getElementName());
+	 b.append(" xmlns=\"").append(getNamespace()).append("\" ");
+	 if (mHash != null){
+	     b.append("hash=\"").append(mHash).append("\" ");
+	 }
+	 if (mNode != null)
+	     b.append("node=\"").append(mNode).append("\" ");
+	 if (mVer != null)
+	     b.append("ver=\"").append(mVer).append("\" ");
+	 if (mExt!= null)
+	     b.append("ext=\"").append(mExt).append("\" ");
+	 b.append("/>");
+	 return b.toString();
+     }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/com/beem/project/beem/smack/caps/CapsManager.java	Thu Jul 22 23:34:36 2010 +0200
@@ -0,0 +1,183 @@
+
+package com.beem.project.beem.smack.caps;
+
+import org.jivesoftware.smack.Connection;
+import org.jivesoftware.smack.XMPPException;
+import org.jivesoftware.smackx.packet.DiscoverInfo;
+import org.jivesoftware.smack.packet.Packet;
+import org.jivesoftware.smack.packet.PacketExtension;
+import org.jivesoftware.smackx.ServiceDiscoveryManager;
+import org.jivesoftware.smack.util.collections.ReferenceMap;
+import org.jivesoftware.smack.PacketListener;
+import org.jivesoftware.smack.filter.PacketFilter;
+import org.jivesoftware.smack.filter.PacketExtensionFilter;
+
+import java.util.Map;
+import java.util.Iterator;
+import java.util.Comparator;
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.security.NoSuchAlgorithmException;
+import java.security.MessageDigest;
+
+import org.jivesoftware.smack.util.StringUtils;
+
+public class CapsManager {
+    // the verCache should be stored on disk
+    Map<String, DiscoverInfo> mVerCache = new ReferenceMap<String, DiscoverInfo>();
+    Map<String, DiscoverInfo> mJidCache = new ReferenceMap<String, DiscoverInfo>();
+
+    private ServiceDiscoveryManager mSdm;
+    private Connection mConnection;
+    private List<String> mSupportedAlgorithm = new ArrayList<String>();
+
+    public CapsManager(ServiceDiscoveryManager sdm, Connection conn) {
+	mSdm = sdm;
+	mConnection = conn;
+	init();
+    }
+
+    private void init() {
+	initSupportedAlgorithm();
+	PacketFilter filter = new PacketExtensionFilter("c", "http://jabber.org/protocol/caps");
+	mConnection.addPacketListener( new PacketListener() {
+	    public void processPacket(Packet packet) {
+		PacketExtension p = packet.getExtension("c", "http://jabber.org/protocol/caps");
+		CapsExtension caps = (CapsExtension) p;
+		if (!mVerCache.containsKey(caps.getVer()))
+		    validate(packet.getFrom(), caps.getVer(), caps.getHash());
+	    }
+	}, filter);
+    }
+
+    public DiscoverInfo getDiscoverInfo(String ver) {
+	return mVerCache.get(ver);
+    }
+
+    public DiscoverInfo getDiscoverInfo(String jid, String ver) {
+	DiscoverInfo info = mVerCache.get(ver);
+	if (info == null)
+	    info = mJidCache.get(jid);
+	return info;
+    }
+
+    /** 
+     * 
+     * 
+     * @param jid 
+     * @param ver 
+     * @param hashMethod 
+     * @return 
+     */
+    private boolean validate(String jid, String ver, String hashMethod) {
+	try {
+	    DiscoverInfo info = mSdm.discoverInfo(jid);
+	    if (!mSupportedAlgorithm.contains(hashMethod)) {
+		mJidCache.put(jid, info);
+		return false;
+	    }
+	    String v = calculateVer(info, hashMethod);
+	    boolean res = v.equals(ver);
+	    if (res)
+		mVerCache.put(ver, info);
+	    return res;
+	} catch (XMPPException e) {
+	    e.printStackTrace();
+	    return false;
+	} catch (NoSuchAlgorithmException e) {
+	    e.printStackTrace();
+	    return false;
+	}
+    }
+
+    private String calculateVer(DiscoverInfo info, String hashMethod) throws NoSuchAlgorithmException {
+	StringBuilder S = new StringBuilder();
+	for(DiscoverInfo.Identity identity : getSortedIdentity(info)) {
+	    String c = identity.getCategory();
+	    if (c != null)
+		S.append(c);
+	    S.append('/');
+	    c = identity.getType();
+	    if (c != null)
+		S.append(c);
+	    S.append('/');
+	    // Should add lang but it is not available
+//             c = identity.getType();
+//             if (c != null)
+//                 S.append(c);
+	    S.append('/');
+	    c = identity.getName();
+	    if (c != null)
+		S.append(c);
+	    S.append('<');
+	}
+	for (String f : getSortedFeature(info)) {
+	    S.append(f);
+	    S.append('<');
+	}
+	// Should add data form (XEP 0128) but it is not available
+	byte[] hash = getHash(hashMethod, S.toString().getBytes());
+	return StringUtils.encodeBase64(hash);
+    }
+
+    private List<DiscoverInfo.Identity> getSortedIdentity(DiscoverInfo info) {
+	List<DiscoverInfo.Identity> result = new ArrayList<DiscoverInfo.Identity>();
+	Iterator<DiscoverInfo.Identity> it = info.getIdentities();
+	while (it.hasNext()) {
+	    DiscoverInfo.Identity id = it.next();
+	    result.add(id);
+	}
+	Collections.sort(result, new Comparator<DiscoverInfo.Identity>() {
+	    public int compare(DiscoverInfo.Identity o1, DiscoverInfo.Identity o2) {
+
+		String cat1 = o1.getCategory();
+		if (cat1 == null) cat1 = "";
+		String cat2 = o2.getCategory();
+		if (cat2 == null) cat2 = "";
+		int res = cat1.compareTo(cat2);
+		if (res != 0)
+		    return res;
+		String type1 = o1.getType();
+		if (type1 == null) type1 = "";
+		String type2 = o2.getCategory();
+		if (type2 == null) type2 = "";
+		res = type1.compareTo(type2);
+		if (res != 0)
+		    return res;
+		// should compare lang but not avalaible
+		return 0;
+	    }
+	});
+	return result;
+    }
+
+    private List<String> getSortedFeature(DiscoverInfo info) {
+	List<String> result = new ArrayList<String>();
+	Iterator<DiscoverInfo.Feature> it = info.getFeatures();
+	while (it.hasNext()) {
+	    DiscoverInfo.Feature feat = it.next();
+	    result.add(feat.getVar());
+	}
+	Collections.sort(result);
+	return result;
+    }
+
+    private byte[] getHash(String algo, byte[] data) throws NoSuchAlgorithmException {
+	    MessageDigest md = MessageDigest.getInstance(algo);
+	    return md.digest(data);
+    }
+
+    private void initSupportedAlgorithm() {
+	String algo[] = new String[] {"md2", "md5", "sha-1", "sha-224", "sha-256", "sha-384", "sha-512" };
+	for (String a : algo) {
+	    try {
+		MessageDigest md = MessageDigest.getInstance(a);
+		mSupportedAlgorithm.add(a);
+	    } catch(NoSuchAlgorithmException e) {
+		System.err.println("Hash algorithm " + a + " not supported");
+	    }
+	}
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/com/beem/project/beem/smack/caps/CapsProvider.java	Thu Jul 22 23:34:36 2010 +0200
@@ -0,0 +1,21 @@
+
+package com.beem.project.beem.smack.caps;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.jivesoftware.smack.provider.PacketExtensionProvider;
+import org.jivesoftware.smack.packet.PacketExtension;
+
+public class CapsProvider implements PacketExtensionProvider {
+
+    @Override
+    public PacketExtension parseExtension(XmlPullParser parser) {
+	String ver = parser.getAttributeValue("", "ver");
+	String hash = parser.getAttributeValue("", "hash");
+	String node = parser.getAttributeValue("", "node");
+	String ext = parser.getAttributeValue("", "ext");
+	CapsExtension e = new CapsExtension(hash, node, ver);
+	e.setExt(ext);
+	return e;
+    }
+
+}