diff options
author | Remko Tronçon <git@el-tramo.be> | 2010-09-06 15:48:27 (GMT) |
---|---|---|
committer | Remko Tronçon <git@el-tramo.be> | 2010-09-11 21:53:43 (GMT) |
commit | 6f31cc8a329d15767d54511edd14339ac3dfdd7a (patch) | |
tree | d41bdb8af487fed788f7a9bc88b1ab08ff567272 /Swiften/Disco | |
parent | ebccdadcae24932b299a8612a9c6e0cdc4e00249 (diff) | |
download | swift-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.cpp | 61 | ||||
-rw-r--r-- | Swiften/Disco/CapsFileStorage.h | 28 | ||||
-rw-r--r-- | Swiften/Disco/CapsInfoGenerator.h | 5 | ||||
-rw-r--r-- | Swiften/Disco/CapsManager.cpp | 80 | ||||
-rw-r--r-- | Swiften/Disco/CapsManager.h | 42 | ||||
-rw-r--r-- | Swiften/Disco/CapsMemoryStorage.h | 38 | ||||
-rw-r--r-- | Swiften/Disco/CapsProvider.h | 22 | ||||
-rw-r--r-- | Swiften/Disco/CapsStorage.cpp | 14 | ||||
-rw-r--r-- | Swiften/Disco/CapsStorage.h | 23 | ||||
-rw-r--r-- | Swiften/Disco/EntityCapsManager.cpp | 79 | ||||
-rw-r--r-- | Swiften/Disco/EntityCapsManager.h | 35 | ||||
-rw-r--r-- | Swiften/Disco/SConscript | 10 | ||||
-rw-r--r-- | Swiften/Disco/UnitTest/CapsManagerTest.cpp | 271 | ||||
-rw-r--r-- | Swiften/Disco/UnitTest/EntityCapsManagerTest.cpp | 188 |
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); |