summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRemko Tronçon <git@el-tramo.be>2010-09-06 15:48:27 (GMT)
committerRemko Tronçon <git@el-tramo.be>2010-09-11 21:53:43 (GMT)
commit6f31cc8a329d15767d54511edd14339ac3dfdd7a (patch)
treed41bdb8af487fed788f7a9bc88b1ab08ff567272 /Swiften/Disco
parentebccdadcae24932b299a8612a9c6e0cdc4e00249 (diff)
downloadswift-contrib-6f31cc8a329d15767d54511edd14339ac3dfdd7a.zip
swift-contrib-6f31cc8a329d15767d54511edd14339ac3dfdd7a.tar.bz2
Added support for Entity Capabilities.
Resolves: #94
Diffstat (limited to 'Swiften/Disco')
-rw-r--r--Swiften/Disco/CapsFileStorage.cpp61
-rw-r--r--Swiften/Disco/CapsFileStorage.h28
-rw-r--r--Swiften/Disco/CapsInfoGenerator.h5
-rw-r--r--Swiften/Disco/CapsManager.cpp80
-rw-r--r--Swiften/Disco/CapsManager.h42
-rw-r--r--Swiften/Disco/CapsMemoryStorage.h38
-rw-r--r--Swiften/Disco/CapsProvider.h22
-rw-r--r--Swiften/Disco/CapsStorage.cpp14
-rw-r--r--Swiften/Disco/CapsStorage.h23
-rw-r--r--Swiften/Disco/EntityCapsManager.cpp79
-rw-r--r--Swiften/Disco/EntityCapsManager.h35
-rw-r--r--Swiften/Disco/SConscript10
-rw-r--r--Swiften/Disco/UnitTest/CapsManagerTest.cpp271
-rw-r--r--Swiften/Disco/UnitTest/EntityCapsManagerTest.cpp188
14 files changed, 892 insertions, 4 deletions
diff --git a/Swiften/Disco/CapsFileStorage.cpp b/Swiften/Disco/CapsFileStorage.cpp
new file mode 100644
index 0000000..c5326a7
--- /dev/null
+++ b/Swiften/Disco/CapsFileStorage.cpp
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2010 Remko Tronçon
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#include "Swiften/Disco/CapsFileStorage.h"
+
+#include <iostream>
+#include <boost/filesystem/fstream.hpp>
+
+#include "Swiften/Base/ByteArray.h"
+#include "Swiften/Serializer/PayloadSerializers/DiscoInfoSerializer.h"
+#include "Swiften/Parser/PayloadParsers/UnitTest/PayloadParserTester.h"
+#include "Swiften/Parser/PayloadParsers/DiscoInfoParser.h"
+#include "Swiften/StringCodecs/Hexify.h"
+#include "Swiften/StringCodecs/Base64.h"
+
+namespace Swift {
+
+CapsFileStorage::CapsFileStorage(const boost::filesystem::path& path) : path(path) {
+}
+
+DiscoInfo::ref CapsFileStorage::getDiscoInfo(const String& hash) const {
+ boost::filesystem::path capsPath(getCapsPath(hash));
+ if (boost::filesystem::exists(capsPath)) {
+ ByteArray data;
+ data.readFromFile(capsPath.string());
+
+ DiscoInfoParser parser;
+ PayloadParserTester tester(&parser);
+ tester.parse(String(data.getData(), data.getSize()));
+ return DiscoInfo::cast(parser.getPayload());
+ }
+ else {
+ return DiscoInfo::ref();
+ }
+}
+
+void CapsFileStorage::setDiscoInfo(const String& hash, DiscoInfo::ref discoInfo) {
+ boost::filesystem::path capsPath(getCapsPath(hash));
+ if (!boost::filesystem::exists(capsPath.parent_path())) {
+ try {
+ boost::filesystem::create_directories(capsPath.parent_path());
+ }
+ catch (const boost::filesystem::filesystem_error& e) {
+ std::cerr << "ERROR: " << e.what() << std::endl;
+ }
+ }
+ DiscoInfo::ref bareDiscoInfo(new DiscoInfo(*discoInfo.get()));
+ bareDiscoInfo->setNode("");
+ boost::filesystem::ofstream file(capsPath);
+ file << DiscoInfoSerializer().serializePayload(bareDiscoInfo);
+ file.close();
+}
+
+boost::filesystem::path CapsFileStorage::getCapsPath(const String& hash) const {
+ return path / (Hexify::hexify(Base64::decode(hash)) + ".xml").getUTF8String();
+}
+
+}
diff --git a/Swiften/Disco/CapsFileStorage.h b/Swiften/Disco/CapsFileStorage.h
new file mode 100644
index 0000000..ea1b1a2
--- /dev/null
+++ b/Swiften/Disco/CapsFileStorage.h
@@ -0,0 +1,28 @@
+/*
+ * Copyright (c) 2010 Remko Tronçon
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#pragma once
+
+#include <boost/filesystem.hpp>
+
+#include "Swiften/Disco/CapsStorage.h"
+#include "Swiften/Base/String.h"
+
+namespace Swift {
+ class CapsFileStorage : public CapsStorage {
+ public:
+ CapsFileStorage(const boost::filesystem::path& path);
+
+ virtual DiscoInfo::ref getDiscoInfo(const String& hash) const;
+ virtual void setDiscoInfo(const String& hash, DiscoInfo::ref discoInfo);
+
+ private:
+ boost::filesystem::path getCapsPath(const String& hash) const;
+
+ private:
+ boost::filesystem::path path;
+ };
+}
diff --git a/Swiften/Disco/CapsInfoGenerator.h b/Swiften/Disco/CapsInfoGenerator.h
index d69c05e..cc32bbd 100644
--- a/Swiften/Disco/CapsInfoGenerator.h
+++ b/Swiften/Disco/CapsInfoGenerator.h
@@ -4,8 +4,7 @@
* See Documentation/Licenses/GPLv3.txt for more information.
*/
-#ifndef SWIFTEN_CapsInfoGenerator_H
-#define SWIFTEN_CapsInfoGenerator_H
+#pragma once
#include "Swiften/Base/String.h"
#include "Swiften/Elements/CapsInfo.h"
@@ -23,5 +22,3 @@ namespace Swift {
String node_;
};
}
-
-#endif
diff --git a/Swiften/Disco/CapsManager.cpp b/Swiften/Disco/CapsManager.cpp
new file mode 100644
index 0000000..185f8e6
--- /dev/null
+++ b/Swiften/Disco/CapsManager.cpp
@@ -0,0 +1,80 @@
+/*
+ * Copyright (c) 2010 Remko Tronçon
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#include "Swiften/Disco/CapsManager.h"
+
+#include <boost/bind.hpp>
+
+#include "Swiften/Client/StanzaChannel.h"
+#include "Swiften/Disco/CapsStorage.h"
+#include "Swiften/Disco/CapsInfoGenerator.h"
+#include "Swiften/Elements/CapsInfo.h"
+#include "Swiften/Queries/Requests/GetDiscoInfoRequest.h"
+
+namespace Swift {
+
+CapsManager::CapsManager(CapsStorage* capsStorage, StanzaChannel* stanzaChannel, IQRouter* iqRouter) : iqRouter(iqRouter), capsStorage(capsStorage) {
+ stanzaChannel->onPresenceReceived.connect(boost::bind(&CapsManager::handlePresenceReceived, this, _1));
+ stanzaChannel->onAvailableChanged.connect(boost::bind(&CapsManager::handleStanzaChannelAvailableChanged, this, _1));
+}
+
+void CapsManager::handlePresenceReceived(boost::shared_ptr<Presence> presence) {
+ boost::shared_ptr<CapsInfo> capsInfo = presence->getPayload<CapsInfo>();
+ if (!capsInfo || capsInfo->getHash() != "sha-1" || presence->getPayload<ErrorPayload>()) {
+ return;
+ }
+ String hash = capsInfo->getVersion();
+ if (capsStorage->getDiscoInfo(hash)) {
+ return;
+ }
+ if (failingCaps.find(std::make_pair(presence->getFrom(), hash)) != failingCaps.end()) {
+ return;
+ }
+ if (requestedDiscoInfos.find(hash) != requestedDiscoInfos.end()) {
+ fallbacks[hash].insert(std::make_pair(presence->getFrom(), capsInfo->getNode()));
+ return;
+ }
+ requestDiscoInfo(presence->getFrom(), capsInfo->getNode(), hash);
+}
+
+void CapsManager::handleStanzaChannelAvailableChanged(bool available) {
+ if (available) {
+ failingCaps.clear();
+ fallbacks.clear();
+ requestedDiscoInfos.clear();
+ }
+}
+
+void CapsManager::handleDiscoInfoReceived(const JID& from, const String& hash, DiscoInfo::ref discoInfo, const boost::optional<ErrorPayload>& error) {
+ requestedDiscoInfos.erase(hash);
+ if (error || CapsInfoGenerator("").generateCapsInfo(*discoInfo.get()).getVersion() != hash) {
+ failingCaps.insert(std::make_pair(from, hash));
+ std::map<String, std::set< std::pair<JID, String> > >::iterator i = fallbacks.find(hash);
+ if (i != fallbacks.end() && !i->second.empty()) {
+ std::pair<JID,String> fallbackAndNode = *i->second.begin();
+ i->second.erase(i->second.begin());
+ requestDiscoInfo(fallbackAndNode.first, fallbackAndNode.second, hash);
+ }
+ return;
+ }
+ fallbacks.erase(hash);
+ capsStorage->setDiscoInfo(hash, discoInfo);
+ onCapsAvailable(hash);
+}
+
+void CapsManager::requestDiscoInfo(const JID& jid, const String& node, const String& hash) {
+ boost::shared_ptr<GetDiscoInfoRequest> request(new GetDiscoInfoRequest(jid, node + "#" + hash, iqRouter));
+ request->onResponse.connect(boost::bind(&CapsManager::handleDiscoInfoReceived, this, jid, hash, _1, _2));
+ requestedDiscoInfos.insert(hash);
+ request->send();
+}
+
+DiscoInfo::ref CapsManager::getCaps(const String& hash) const {
+ return capsStorage->getDiscoInfo(hash);
+}
+
+
+}
diff --git a/Swiften/Disco/CapsManager.h b/Swiften/Disco/CapsManager.h
new file mode 100644
index 0000000..3188a07
--- /dev/null
+++ b/Swiften/Disco/CapsManager.h
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2010 Remko Tronçon
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#include <set>
+#include <map>
+
+#include "Swiften/Base/boost_bsignals.h"
+#include "Swiften/Elements/Presence.h"
+#include "Swiften/Elements/DiscoInfo.h"
+#include "Swiften/Elements/CapsInfo.h"
+#include "Swiften/Elements/ErrorPayload.h"
+#include "Swiften/Disco/CapsProvider.h"
+
+namespace Swift {
+ class StanzaChannel;
+ class IQRouter;
+ class JID;
+ class CapsStorage;
+
+ class CapsManager : public CapsProvider, public boost::bsignals::trackable {
+ public:
+ CapsManager(CapsStorage*, StanzaChannel*, IQRouter*);
+
+ DiscoInfo::ref getCaps(const String&) const;
+
+ private:
+ void handlePresenceReceived(boost::shared_ptr<Presence>);
+ void handleStanzaChannelAvailableChanged(bool);
+ void handleDiscoInfoReceived(const JID&, const String& hash, DiscoInfo::ref, const boost::optional<ErrorPayload>&);
+ void requestDiscoInfo(const JID& jid, const String& node, const String& hash);
+
+ private:
+ IQRouter* iqRouter;
+ CapsStorage* capsStorage;
+ std::set<String> requestedDiscoInfos;
+ std::set< std::pair<JID, String> > failingCaps;
+ std::map<String, std::set< std::pair<JID, String> > > fallbacks;
+ };
+}
diff --git a/Swiften/Disco/CapsMemoryStorage.h b/Swiften/Disco/CapsMemoryStorage.h
new file mode 100644
index 0000000..71bd5d5
--- /dev/null
+++ b/Swiften/Disco/CapsMemoryStorage.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2010 Remko Tronçon
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#pragma once
+
+#include <boost/shared_ptr.hpp>
+#include <map>
+
+#include "Swiften/Base/String.h"
+#include "Swiften/Disco/CapsStorage.h"
+
+namespace Swift {
+ class CapsMemoryStorage : public CapsStorage {
+ public:
+ CapsMemoryStorage() {}
+
+ virtual DiscoInfo::ref getDiscoInfo(const String& hash) const {
+ CapsMap::const_iterator i = caps.find(hash);
+ if (i != caps.end()) {
+ return i->second;
+ }
+ else {
+ return DiscoInfo::ref();
+ }
+ }
+
+ virtual void setDiscoInfo(const String& hash, DiscoInfo::ref discoInfo) {
+ caps[hash] = discoInfo;
+ }
+
+ private:
+ typedef std::map<String, DiscoInfo::ref> CapsMap;
+ CapsMap caps;
+ };
+}
diff --git a/Swiften/Disco/CapsProvider.h b/Swiften/Disco/CapsProvider.h
new file mode 100644
index 0000000..2aaad79
--- /dev/null
+++ b/Swiften/Disco/CapsProvider.h
@@ -0,0 +1,22 @@
+/*
+ * Copyright (c) 2010 Remko Tronçon
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#include "Swiften/Base/boost_bsignals.h"
+#include "Swiften/Elements/DiscoInfo.h"
+#include "Swiften/Elements/CapsInfo.h"
+
+namespace Swift {
+ class String;
+
+ class CapsProvider {
+ public:
+ virtual ~CapsProvider() {}
+
+ virtual DiscoInfo::ref getCaps(const String&) const = 0;
+
+ boost::signal<void (const String&)> onCapsAvailable;
+ };
+}
diff --git a/Swiften/Disco/CapsStorage.cpp b/Swiften/Disco/CapsStorage.cpp
new file mode 100644
index 0000000..acb58fe
--- /dev/null
+++ b/Swiften/Disco/CapsStorage.cpp
@@ -0,0 +1,14 @@
+/*
+ * Copyright (c) 2010 Remko Tronçon
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#include "Swiften/Disco/CapsStorage.h"
+
+namespace Swift {
+
+CapsStorage::~CapsStorage() {
+}
+
+}
diff --git a/Swiften/Disco/CapsStorage.h b/Swiften/Disco/CapsStorage.h
new file mode 100644
index 0000000..e4ff945
--- /dev/null
+++ b/Swiften/Disco/CapsStorage.h
@@ -0,0 +1,23 @@
+/*
+ * Copyright (c) 2010 Remko Tronçon
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#pragma once
+
+#include <boost/shared_ptr.hpp>
+
+#include "Swiften/Elements/DiscoInfo.h"
+
+namespace Swift {
+ class String;
+
+ class CapsStorage {
+ public:
+ virtual ~CapsStorage();
+
+ virtual DiscoInfo::ref getDiscoInfo(const String&) const = 0;
+ virtual void setDiscoInfo(const String&, DiscoInfo::ref) = 0;
+ };
+}
diff --git a/Swiften/Disco/EntityCapsManager.cpp b/Swiften/Disco/EntityCapsManager.cpp
new file mode 100644
index 0000000..26e0594
--- /dev/null
+++ b/Swiften/Disco/EntityCapsManager.cpp
@@ -0,0 +1,79 @@
+/*
+ * Copyright (c) 2010 Remko Tronçon
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#include "Swiften/Disco/EntityCapsManager.h"
+
+#include <boost/bind.hpp>
+
+#include "Swiften/Disco/CapsProvider.h"
+#include "Swiften/Client/StanzaChannel.h"
+
+namespace Swift {
+
+EntityCapsManager::EntityCapsManager(CapsProvider* capsProvider, StanzaChannel* stanzaChannel) : capsProvider(capsProvider) {
+ stanzaChannel->onPresenceReceived.connect(boost::bind(&EntityCapsManager::handlePresenceReceived, this, _1));
+ stanzaChannel->onAvailableChanged.connect(boost::bind(&EntityCapsManager::handleStanzaChannelAvailableChanged, this, _1));
+ capsProvider->onCapsAvailable.connect(boost::bind(&EntityCapsManager::handleCapsAvailable, this, _1));
+}
+
+void EntityCapsManager::handlePresenceReceived(boost::shared_ptr<Presence> presence) {
+ JID from = presence->getFrom();
+ if (presence->isAvailable()) {
+ boost::shared_ptr<CapsInfo> capsInfo = presence->getPayload<CapsInfo>();
+ if (!capsInfo || capsInfo->getHash() != "sha-1" || presence->getPayload<ErrorPayload>()) {
+ return;
+ }
+ String hash = capsInfo->getVersion();
+ std::map<JID, String>::iterator i = caps.find(from);
+ if (i == caps.end() || i->second != hash) {
+ caps.insert(std::make_pair(from, hash));
+ DiscoInfo::ref disco = capsProvider->getCaps(hash);
+ if (disco) {
+ onCapsChanged(from);
+ }
+ else if (i != caps.end()) {
+ caps.erase(i);
+ onCapsChanged(from);
+ }
+ }
+ }
+ else {
+ std::map<JID, String>::iterator i = caps.find(from);
+ if (i != caps.end()) {
+ caps.erase(i);
+ onCapsChanged(from);
+ }
+ }
+}
+
+void EntityCapsManager::handleStanzaChannelAvailableChanged(bool available) {
+ if (available) {
+ std::map<JID, String> capsCopy;
+ capsCopy.swap(caps);
+ for (std::map<JID,String>::const_iterator i = capsCopy.begin(); i != capsCopy.end(); ++i) {
+ onCapsChanged(i->first);
+ }
+ }
+}
+
+void EntityCapsManager::handleCapsAvailable(const String& hash) {
+ // TODO: Use Boost.Bimap ?
+ for (std::map<JID,String>::const_iterator i = caps.begin(); i != caps.end(); ++i) {
+ if (i->second == hash) {
+ onCapsChanged(i->first);
+ }
+ }
+}
+
+DiscoInfo::ref EntityCapsManager::getCaps(const JID& jid) const {
+ std::map<JID, String>::const_iterator i = caps.find(jid);
+ if (i != caps.end()) {
+ return capsProvider->getCaps(i->second);
+ }
+ return DiscoInfo::ref();
+}
+
+}
diff --git a/Swiften/Disco/EntityCapsManager.h b/Swiften/Disco/EntityCapsManager.h
new file mode 100644
index 0000000..810e260
--- /dev/null
+++ b/Swiften/Disco/EntityCapsManager.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2010 Remko Tronçon
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#include <map>
+
+#include "Swiften/Base/boost_bsignals.h"
+#include "Swiften/Elements/Presence.h"
+#include "Swiften/Elements/DiscoInfo.h"
+#include "Swiften/Elements/ErrorPayload.h"
+
+namespace Swift {
+ class StanzaChannel;
+ class CapsProvider;
+
+ class EntityCapsManager : public boost::bsignals::trackable {
+ public:
+ EntityCapsManager(CapsProvider*, StanzaChannel*);
+
+ DiscoInfo::ref getCaps(const JID&) const;
+
+ boost::signal<void (const JID&)> onCapsChanged;
+
+ private:
+ void handlePresenceReceived(boost::shared_ptr<Presence>);
+ void handleStanzaChannelAvailableChanged(bool);
+ void handleCapsAvailable(const String&);
+
+ private:
+ CapsProvider* capsProvider;
+ std::map<JID, String> caps;
+ };
+}
diff --git a/Swiften/Disco/SConscript b/Swiften/Disco/SConscript
new file mode 100644
index 0000000..9da3fb3
--- /dev/null
+++ b/Swiften/Disco/SConscript
@@ -0,0 +1,10 @@
+Import("swiften_env")
+
+objects = swiften_env.StaticObject([
+ "CapsInfoGenerator.cpp",
+ "CapsManager.cpp",
+ "EntityCapsManager.cpp",
+ "CapsStorage.cpp",
+ "CapsFileStorage.cpp",
+ ])
+swiften_env.Append(SWIFTEN_OBJECTS = [objects])
diff --git a/Swiften/Disco/UnitTest/CapsManagerTest.cpp b/Swiften/Disco/UnitTest/CapsManagerTest.cpp
new file mode 100644
index 0000000..2156c3e
--- /dev/null
+++ b/Swiften/Disco/UnitTest/CapsManagerTest.cpp
@@ -0,0 +1,271 @@
+/*
+ * Copyright (c) 2010 Remko Tronçon
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#include <cppunit/extensions/HelperMacros.h>
+#include <cppunit/extensions/TestFactoryRegistry.h>
+#include <vector>
+#include <boost/bind.hpp>
+
+#include "Swiften/Disco/CapsManager.h"
+#include "Swiften/Disco/CapsMemoryStorage.h"
+#include "Swiften/Disco/CapsInfoGenerator.h"
+#include "Swiften/Queries/IQRouter.h"
+#include "Swiften/Elements/CapsInfo.h"
+#include "Swiften/Elements/DiscoInfo.h"
+#include "Swiften/Client/DummyStanzaChannel.h"
+
+using namespace Swift;
+
+class CapsManagerTest : public CppUnit::TestFixture {
+ CPPUNIT_TEST_SUITE(CapsManagerTest);
+ CPPUNIT_TEST(testReceiveNewHashRequestsDisco);
+ CPPUNIT_TEST(testReceiveSameHashDoesNotRequestDisco);
+ CPPUNIT_TEST(testReceiveLegacyCapsDoesNotRequestDisco);
+ CPPUNIT_TEST(testReceiveSameHashFromSameUserAfterFailedDiscoDoesNotRequestDisco);
+ CPPUNIT_TEST(testReceiveSameHashFromDifferentUserAfterFailedDiscoRequestsDisco);
+ CPPUNIT_TEST(testReceiveSameHashFromDifferentUserAfterIncorrectVerificationRequestsDisco);
+ CPPUNIT_TEST(testReceiveDifferentHashFromSameUserAfterFailedDiscoDoesNotRequestDisco);
+ CPPUNIT_TEST(testReceiveSameHashAfterSuccesfulDiscoDoesNotRequestDisco);
+ CPPUNIT_TEST(testReceiveSuccesfulDiscoStoresCaps);
+ CPPUNIT_TEST(testReceiveIncorrectVerificationDiscoDoesNotStoreCaps);
+ CPPUNIT_TEST(testReceiveFailingDiscoFallsBack);
+ CPPUNIT_TEST(testReceiveFailingFallbackDiscoFallsBack);
+ CPPUNIT_TEST(testReceiveSameHashFromFailingUserAfterReconnectRequestsDisco);
+ CPPUNIT_TEST(testReconnectResetsFallback);
+ CPPUNIT_TEST(testReconnectResetsRequests);
+ CPPUNIT_TEST_SUITE_END();
+
+ public:
+ void setUp() {
+ stanzaChannel = new DummyStanzaChannel();
+ iqRouter = new IQRouter(stanzaChannel);
+ storage = new CapsMemoryStorage();
+ user1 = JID("user1@bar.com/bla");
+ discoInfo1 = boost::shared_ptr<DiscoInfo>(new DiscoInfo());
+ discoInfo1->addFeature("http://swift.im/feature1");
+ capsInfo1 = boost::shared_ptr<CapsInfo>(new CapsInfo(CapsInfoGenerator("http://node1.im").generateCapsInfo(*discoInfo1.get())));
+ capsInfo1alt = boost::shared_ptr<CapsInfo>(new CapsInfo(CapsInfoGenerator("http://node2.im").generateCapsInfo(*discoInfo1.get())));
+ user2 = JID("user2@foo.com/baz");
+ discoInfo2 = boost::shared_ptr<DiscoInfo>(new DiscoInfo());
+ discoInfo2->addFeature("http://swift.im/feature2");
+ capsInfo2 = boost::shared_ptr<CapsInfo>(new CapsInfo(CapsInfoGenerator("http://node2.im").generateCapsInfo(*discoInfo2.get())));
+ user3 = JID("user3@foo.com/baz");
+ legacyCapsInfo = boost::shared_ptr<CapsInfo>(new CapsInfo("http://swift.im", "ver1", ""));
+ }
+
+ void tearDown() {
+ delete iqRouter;
+ delete stanzaChannel;
+ }
+
+ void testReceiveNewHashRequestsDisco() {
+ std::auto_ptr<CapsManager> testling = createManager();
+ sendPresenceWithCaps(user1, capsInfo1);
+
+ CPPUNIT_ASSERT(stanzaChannel->isRequestAtIndex<DiscoInfo>(0, user1, IQ::Get));
+ boost::shared_ptr<DiscoInfo> discoInfo(stanzaChannel->sentStanzas[0]->getPayload<DiscoInfo>());
+ CPPUNIT_ASSERT(discoInfo);
+ CPPUNIT_ASSERT_EQUAL("http://node1.im#" + capsInfo1->getVersion(), discoInfo->getNode());
+ }
+
+ void testReceiveSameHashDoesNotRequestDisco() {
+ std::auto_ptr<CapsManager> testling = createManager();
+ sendPresenceWithCaps(user1, capsInfo1);
+ stanzaChannel->sentStanzas.clear();
+ sendPresenceWithCaps(user1, capsInfo1);
+
+ CPPUNIT_ASSERT_EQUAL(0, static_cast<int>(stanzaChannel->sentStanzas.size()));
+ }
+
+ void testReceiveLegacyCapsDoesNotRequestDisco() {
+ std::auto_ptr<CapsManager> testling = createManager();
+ sendPresenceWithCaps(user1, legacyCapsInfo);
+
+ CPPUNIT_ASSERT_EQUAL(0, static_cast<int>(stanzaChannel->sentStanzas.size()));
+ }
+
+ void testReceiveSameHashAfterSuccesfulDiscoDoesNotRequestDisco() {
+ std::auto_ptr<CapsManager> testling = createManager();
+ sendPresenceWithCaps(user1, capsInfo1);
+ sendDiscoInfoResult(discoInfo1);
+
+ stanzaChannel->sentStanzas.clear();
+ sendPresenceWithCaps(user1, capsInfo1);
+
+ CPPUNIT_ASSERT_EQUAL(0, static_cast<int>(stanzaChannel->sentStanzas.size()));
+ }
+
+ void testReceiveSameHashFromSameUserAfterFailedDiscoDoesNotRequestDisco() {
+ std::auto_ptr<CapsManager> testling = createManager();
+ sendPresenceWithCaps(user1, capsInfo1);
+ stanzaChannel->onIQReceived(IQ::createError(JID("baz@fum.com/foo"), stanzaChannel->sentStanzas[0]->getID()));
+
+ stanzaChannel->sentStanzas.clear();
+ sendPresenceWithCaps(user1, capsInfo1);
+
+ CPPUNIT_ASSERT_EQUAL(0, static_cast<int>(stanzaChannel->sentStanzas.size()));
+ }
+
+ void testReceiveSameHashFromSameUserAfterIncorrectVerificationDoesNotRequestDisco() {
+ std::auto_ptr<CapsManager> testling = createManager();
+ sendPresenceWithCaps(user1, capsInfo1);
+ sendDiscoInfoResult(discoInfo2);
+
+ stanzaChannel->sentStanzas.clear();
+ sendPresenceWithCaps(user1, capsInfo1);
+
+ CPPUNIT_ASSERT_EQUAL(0, static_cast<int>(stanzaChannel->sentStanzas.size()));
+ }
+
+ void testReceiveSameHashFromDifferentUserAfterFailedDiscoRequestsDisco() {
+ std::auto_ptr<CapsManager> testling = createManager();
+ sendPresenceWithCaps(user1, capsInfo1);
+ stanzaChannel->onIQReceived(IQ::createError(JID("baz@fum.com/foo"), stanzaChannel->sentStanzas[0]->getID()));
+
+ stanzaChannel->sentStanzas.clear();
+ sendPresenceWithCaps(user2, capsInfo1);
+ CPPUNIT_ASSERT(stanzaChannel->isRequestAtIndex<DiscoInfo>(0, user2, IQ::Get));
+ }
+
+ void testReceiveSameHashFromDifferentUserAfterIncorrectVerificationRequestsDisco() {
+ std::auto_ptr<CapsManager> testling = createManager();
+ sendPresenceWithCaps(user1, capsInfo1);
+ sendDiscoInfoResult(discoInfo2);
+
+ stanzaChannel->sentStanzas.clear();
+ sendPresenceWithCaps(user2, capsInfo1);
+ CPPUNIT_ASSERT(stanzaChannel->isRequestAtIndex<DiscoInfo>(0, user2, IQ::Get));
+ }
+
+ void testReceiveDifferentHashFromSameUserAfterFailedDiscoDoesNotRequestDisco() {
+ std::auto_ptr<CapsManager> testling = createManager();
+ sendPresenceWithCaps(user1, capsInfo1);
+ stanzaChannel->onIQReceived(IQ::createError(JID("baz@fum.com/foo"), stanzaChannel->sentStanzas[0]->getID()));
+
+ stanzaChannel->sentStanzas.clear();
+ sendPresenceWithCaps(user1, capsInfo2);
+
+ CPPUNIT_ASSERT(stanzaChannel->isRequestAtIndex<DiscoInfo>(0, user1, IQ::Get));
+ }
+
+ void testReceiveSuccesfulDiscoStoresCaps() {
+ std::auto_ptr<CapsManager> testling = createManager();
+ sendPresenceWithCaps(user1, capsInfo1);
+ sendDiscoInfoResult(discoInfo1);
+
+ boost::shared_ptr<DiscoInfo> discoInfo(storage->getDiscoInfo(capsInfo1->getVersion()));
+ CPPUNIT_ASSERT(discoInfo);
+ CPPUNIT_ASSERT(discoInfo->hasFeature("http://swift.im/feature1"));
+ }
+
+ void testReceiveIncorrectVerificationDiscoDoesNotStoreCaps() {
+ std::auto_ptr<CapsManager> testling = createManager();
+ sendPresenceWithCaps(user1, capsInfo1);
+ sendDiscoInfoResult(discoInfo2);
+
+ boost::shared_ptr<DiscoInfo> discoInfo(storage->getDiscoInfo(capsInfo1->getVersion()));
+ CPPUNIT_ASSERT(!discoInfo);
+ }
+
+ void testReceiveFailingDiscoFallsBack() {
+ std::auto_ptr<CapsManager> testling = createManager();
+ sendPresenceWithCaps(user1, capsInfo1);
+ sendPresenceWithCaps(user2, capsInfo1alt);
+ stanzaChannel->onIQReceived(IQ::createError(JID("baz@fum.com/foo"), stanzaChannel->sentStanzas[0]->getID()));
+
+ CPPUNIT_ASSERT(stanzaChannel->isRequestAtIndex<DiscoInfo>(1, user2, IQ::Get));
+ boost::shared_ptr<DiscoInfo> discoInfo(stanzaChannel->sentStanzas[1]->getPayload<DiscoInfo>());
+ CPPUNIT_ASSERT(discoInfo);
+ CPPUNIT_ASSERT_EQUAL("http://node2.im#" + capsInfo1alt->getVersion(), discoInfo->getNode());
+ }
+
+ void testReceiveFailingFallbackDiscoFallsBack() {
+ std::auto_ptr<CapsManager> testling = createManager();
+ sendPresenceWithCaps(user1, capsInfo1);
+ sendPresenceWithCaps(user2, capsInfo1alt);
+ sendPresenceWithCaps(user3, capsInfo1);
+ stanzaChannel->onIQReceived(IQ::createError(JID("baz@fum.com/foo"), stanzaChannel->sentStanzas[0]->getID()));
+ stanzaChannel->onIQReceived(IQ::createError(JID("baz@fum.com/foo"), stanzaChannel->sentStanzas[1]->getID()));
+
+ CPPUNIT_ASSERT(stanzaChannel->isRequestAtIndex<DiscoInfo>(2, user3, IQ::Get));
+ }
+
+ void testReceiveSameHashFromFailingUserAfterReconnectRequestsDisco() {
+ std::auto_ptr<CapsManager> testling = createManager();
+ sendPresenceWithCaps(user1, capsInfo1);
+ stanzaChannel->onIQReceived(IQ::createError(JID("baz@fum.com/foo"), stanzaChannel->sentStanzas[0]->getID()));
+ stanzaChannel->setAvailable(false);
+ stanzaChannel->setAvailable(true);
+ stanzaChannel->sentStanzas.clear();
+
+ sendPresenceWithCaps(user1, capsInfo1);
+
+ CPPUNIT_ASSERT(stanzaChannel->isRequestAtIndex<DiscoInfo>(0, user1, IQ::Get));
+ }
+
+ void testReconnectResetsFallback() {
+ std::auto_ptr<CapsManager> testling = createManager();
+ sendPresenceWithCaps(user1, capsInfo1);
+ sendPresenceWithCaps(user2, capsInfo1alt);
+ stanzaChannel->setAvailable(false);
+ stanzaChannel->setAvailable(true);
+ stanzaChannel->sentStanzas.clear();
+ sendPresenceWithCaps(user1, capsInfo1);
+ stanzaChannel->onIQReceived(IQ::createError(JID("baz@fum.com/foo"), stanzaChannel->sentStanzas[0]->getID()));
+
+ CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(stanzaChannel->sentStanzas.size()));
+ }
+
+ void testReconnectResetsRequests() {
+ std::auto_ptr<CapsManager> testling = createManager();
+ sendPresenceWithCaps(user1, capsInfo1);
+ stanzaChannel->sentStanzas.clear();
+ stanzaChannel->setAvailable(false);
+ stanzaChannel->setAvailable(true);
+ sendPresenceWithCaps(user1, capsInfo1);
+
+ CPPUNIT_ASSERT(stanzaChannel->isRequestAtIndex<DiscoInfo>(0, user1, IQ::Get));
+ }
+
+ private:
+ std::auto_ptr<CapsManager> createManager() {
+ std::auto_ptr<CapsManager> manager(new CapsManager(storage, stanzaChannel, iqRouter));
+ //manager->onCapsChanged.connect(boost::bind(&CapsManagerTest::handleCapsChanged, this, _1));
+ return manager;
+ }
+
+ void handleCapsChanged(const JID& jid) {
+ changes.push_back(jid);
+ }
+
+ void sendPresenceWithCaps(const JID& jid, boost::shared_ptr<CapsInfo> caps) {
+ boost::shared_ptr<Presence> presence(new Presence());
+ presence->setFrom(jid);
+ presence->addPayload(caps);
+ stanzaChannel->onPresenceReceived(presence);
+ }
+
+ void sendDiscoInfoResult(boost::shared_ptr<DiscoInfo> discoInfo) {
+ stanzaChannel->onIQReceived(IQ::createResult(JID("baz@fum.com/dum"), stanzaChannel->sentStanzas[0]->getID(), discoInfo));
+ }
+
+ private:
+ DummyStanzaChannel* stanzaChannel;
+ IQRouter* iqRouter;
+ CapsStorage* storage;
+ std::vector<JID> changes;
+ JID user1;
+ boost::shared_ptr<DiscoInfo> discoInfo1;
+ boost::shared_ptr<CapsInfo> capsInfo1;
+ boost::shared_ptr<CapsInfo> capsInfo1alt;
+ JID user2;
+ boost::shared_ptr<DiscoInfo> discoInfo2;
+ boost::shared_ptr<CapsInfo> capsInfo2;
+ boost::shared_ptr<CapsInfo> legacyCapsInfo;
+ JID user3;
+};
+
+CPPUNIT_TEST_SUITE_REGISTRATION(CapsManagerTest);
diff --git a/Swiften/Disco/UnitTest/EntityCapsManagerTest.cpp b/Swiften/Disco/UnitTest/EntityCapsManagerTest.cpp
new file mode 100644
index 0000000..0a498cf
--- /dev/null
+++ b/Swiften/Disco/UnitTest/EntityCapsManagerTest.cpp
@@ -0,0 +1,188 @@
+/*
+ * Copyright (c) 2010 Remko Tronçon
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#include <cppunit/extensions/HelperMacros.h>
+#include <cppunit/extensions/TestFactoryRegistry.h>
+#include <vector>
+#include <boost/bind.hpp>
+
+#include "Swiften/Disco/EntityCapsManager.h"
+#include "Swiften/Disco/CapsProvider.h"
+#include "Swiften/Elements/CapsInfo.h"
+#include "Swiften/Client/DummyStanzaChannel.h"
+#include "Swiften/Disco/CapsInfoGenerator.h"
+
+using namespace Swift;
+
+class EntityCapsManagerTest : public CppUnit::TestFixture {
+ CPPUNIT_TEST_SUITE(EntityCapsManagerTest);
+ CPPUNIT_TEST(testReceiveKnownHash);
+ CPPUNIT_TEST(testReceiveKnownHashTwiceDoesNotTriggerChange);
+ CPPUNIT_TEST(testReceiveUnknownHashDoesNotTriggerChange);
+ CPPUNIT_TEST(testReceiveUnknownHashAfterKnownHashTriggersChangeAndClearsCaps);
+ CPPUNIT_TEST(testReceiveUnavailablePresenceAfterKnownHashTriggersChangeAndClearsCaps);
+ CPPUNIT_TEST(testReconnectTriggersChangeAndClearsCaps);
+ CPPUNIT_TEST(testHashAvailable);
+ CPPUNIT_TEST_SUITE_END();
+
+ public:
+ void setUp() {
+ stanzaChannel = new DummyStanzaChannel();
+ capsProvider = new DummyCapsProvider();
+
+ user1 = JID("user1@bar.com/bla");
+ discoInfo1 = boost::shared_ptr<DiscoInfo>(new DiscoInfo());
+ discoInfo1->addFeature("http://swift.im/feature1");
+ capsInfo1 = boost::shared_ptr<CapsInfo>(new CapsInfo(CapsInfoGenerator("http://node1.im").generateCapsInfo(*discoInfo1.get())));
+ capsInfo1alt = boost::shared_ptr<CapsInfo>(new CapsInfo(CapsInfoGenerator("http://node2.im").generateCapsInfo(*discoInfo1.get())));
+ user2 = JID("user2@foo.com/baz");
+ discoInfo2 = boost::shared_ptr<DiscoInfo>(new DiscoInfo());
+ discoInfo2->addFeature("http://swift.im/feature2");
+ capsInfo2 = boost::shared_ptr<CapsInfo>(new CapsInfo(CapsInfoGenerator("http://node2.im").generateCapsInfo(*discoInfo2.get())));
+ user3 = JID("user3@foo.com/baz");
+ legacyCapsInfo = boost::shared_ptr<CapsInfo>(new CapsInfo("http://swift.im", "ver1", ""));
+ }
+
+ void tearDown() {
+ delete capsProvider;
+ delete stanzaChannel;
+ }
+
+ void testReceiveKnownHash() {
+ std::auto_ptr<EntityCapsManager> testling = createManager();
+ capsProvider->caps[capsInfo1->getVersion()] = discoInfo1;
+ sendPresenceWithCaps(user1, capsInfo1);
+
+ CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(changes.size()));
+ CPPUNIT_ASSERT_EQUAL(user1, changes[0]);
+ CPPUNIT_ASSERT_EQUAL(discoInfo1, testling->getCaps(user1));
+ }
+
+ void testReceiveKnownHashTwiceDoesNotTriggerChange() {
+ std::auto_ptr<EntityCapsManager> testling = createManager();
+ capsProvider->caps[capsInfo1->getVersion()] = discoInfo1;
+ sendPresenceWithCaps(user1, capsInfo1);
+ changes.clear();
+
+ sendPresenceWithCaps(user1, capsInfo1);
+
+ CPPUNIT_ASSERT_EQUAL(0, static_cast<int>(changes.size()));
+ }
+
+ void testReceiveUnknownHashDoesNotTriggerChange() {
+ std::auto_ptr<EntityCapsManager> testling = createManager();
+ sendPresenceWithCaps(user1, capsInfo1);
+
+ CPPUNIT_ASSERT_EQUAL(0, static_cast<int>(changes.size()));
+ }
+
+ void testHashAvailable() {
+ std::auto_ptr<EntityCapsManager> testling = createManager();
+ sendPresenceWithCaps(user1, capsInfo1);
+
+ capsProvider->caps[capsInfo1->getVersion()] = discoInfo1;
+ capsProvider->onCapsAvailable(capsInfo1->getVersion());
+
+ CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(changes.size()));
+ CPPUNIT_ASSERT_EQUAL(user1, changes[0]);
+ CPPUNIT_ASSERT_EQUAL(discoInfo1, testling->getCaps(user1));
+ }
+
+ void testReceiveUnknownHashAfterKnownHashTriggersChangeAndClearsCaps() {
+ std::auto_ptr<EntityCapsManager> testling = createManager();
+ capsProvider->caps[capsInfo1->getVersion()] = discoInfo1;
+ sendPresenceWithCaps(user1, capsInfo1);
+ changes.clear();
+ sendPresenceWithCaps(user1, capsInfo2);
+
+ CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(changes.size()));
+ CPPUNIT_ASSERT_EQUAL(user1, changes[0]);
+ CPPUNIT_ASSERT(!testling->getCaps(user1));
+ }
+
+ void testReceiveUnavailablePresenceAfterKnownHashTriggersChangeAndClearsCaps() {
+ std::auto_ptr<EntityCapsManager> testling = createManager();
+ capsProvider->caps[capsInfo1->getVersion()] = discoInfo1;
+ sendPresenceWithCaps(user1, capsInfo1);
+ changes.clear();
+ sendUnavailablePresence(user1);
+
+ CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(changes.size()));
+ CPPUNIT_ASSERT_EQUAL(user1, changes[0]);
+ CPPUNIT_ASSERT(!testling->getCaps(user1));
+ }
+
+ void testReconnectTriggersChangeAndClearsCaps() {
+ std::auto_ptr<EntityCapsManager> testling = createManager();
+ capsProvider->caps[capsInfo1->getVersion()] = discoInfo1;
+ capsProvider->caps[capsInfo2->getVersion()] = discoInfo2;
+ sendPresenceWithCaps(user1, capsInfo1);
+ sendPresenceWithCaps(user2, capsInfo2);
+ changes.clear();
+ stanzaChannel->setAvailable(false);
+ stanzaChannel->setAvailable(true);
+
+ CPPUNIT_ASSERT_EQUAL(2, static_cast<int>(changes.size()));
+ CPPUNIT_ASSERT_EQUAL(user1, changes[0]);
+ CPPUNIT_ASSERT(!testling->getCaps(user1));
+ CPPUNIT_ASSERT_EQUAL(user2, changes[1]);
+ CPPUNIT_ASSERT(!testling->getCaps(user2));
+ }
+
+ private:
+ std::auto_ptr<EntityCapsManager> createManager() {
+ std::auto_ptr<EntityCapsManager> manager(new EntityCapsManager(capsProvider, stanzaChannel));
+ manager->onCapsChanged.connect(boost::bind(&EntityCapsManagerTest::handleCapsChanged, this, _1));
+ return manager;
+ }
+
+ void handleCapsChanged(const JID& jid) {
+ changes.push_back(jid);
+ }
+
+ void sendPresenceWithCaps(const JID& jid, boost::shared_ptr<CapsInfo> caps) {
+ boost::shared_ptr<Presence> presence(new Presence());
+ presence->setFrom(jid);
+ presence->addPayload(caps);
+ stanzaChannel->onPresenceReceived(presence);
+ }
+
+ void sendUnavailablePresence(const JID& jid) {
+ boost::shared_ptr<Presence> presence(new Presence());
+ presence->setFrom(jid);
+ presence->setType(Presence::Unavailable);
+ stanzaChannel->onPresenceReceived(presence);
+ }
+
+ private:
+ struct DummyCapsProvider : public CapsProvider {
+ virtual DiscoInfo::ref getCaps(const String& hash) const {
+ std::map<String, DiscoInfo::ref>::const_iterator i = caps.find(hash);
+ if (i != caps.end()) {
+ return i->second;
+ }
+ return DiscoInfo::ref();
+ }
+
+ std::map<String, DiscoInfo::ref> caps;
+ };
+
+ private:
+ DummyStanzaChannel* stanzaChannel;
+ DummyCapsProvider* capsProvider;
+ JID user1;
+ boost::shared_ptr<DiscoInfo> discoInfo1;
+ boost::shared_ptr<CapsInfo> capsInfo1;
+ boost::shared_ptr<CapsInfo> capsInfo1alt;
+ JID user2;
+ boost::shared_ptr<DiscoInfo> discoInfo2;
+ boost::shared_ptr<CapsInfo> capsInfo2;
+ boost::shared_ptr<CapsInfo> legacyCapsInfo;
+ JID user3;
+ std::vector<JID> changes;
+};
+
+CPPUNIT_TEST_SUITE_REGISTRATION(EntityCapsManagerTest);