diff options
Diffstat (limited to 'Swiften')
666 files changed, 27295 insertions, 0 deletions
diff --git a/Swiften/.gitignore b/Swiften/.gitignore new file mode 100644 index 0000000..9eca6c8 --- /dev/null +++ b/Swiften/.gitignore @@ -0,0 +1,2 @@ +*.a +*.o diff --git a/Swiften/Application/Application.cpp b/Swiften/Application/Application.cpp new file mode 100644 index 0000000..be77e95 --- /dev/null +++ b/Swiften/Application/Application.cpp @@ -0,0 +1,30 @@ +#include "Swiften/Application/Application.h" + +#include <boost/filesystem.hpp> +#include <stdlib.h> + +#include "Swiften/Application/ApplicationMessageDisplay.h" + +namespace Swift { + +Application::Application(const String& name) : name_(name) { +} + +Application::~Application() { +} + +boost::filesystem::path Application::getSettingsFileName() const { + return getSettingsDir() / "settings"; +} + +boost::filesystem::path Application::getAvatarDir() const { + return getSettingsDir() / "avatars"; +} + +boost::filesystem::path Application::getProfileDir(const String& profile) const { + boost::filesystem::path result(getHomeDir() / profile.getUTF8String()); + boost::filesystem::create_directory(result); + return result; +} + +} diff --git a/Swiften/Application/Application.h b/Swiften/Application/Application.h new file mode 100644 index 0000000..20d686c --- /dev/null +++ b/Swiften/Application/Application.h @@ -0,0 +1,33 @@ +#ifndef SWIFTEN_Application_H +#define SWIFTEN_Application_H + +#include <boost/filesystem.hpp> + +#include "Swiften/Base/String.h" + +namespace Swift { + class ApplicationMessageDisplay; + + class Application { + public: + Application(const String& name); + virtual ~Application(); + + boost::filesystem::path getSettingsFileName() const; + boost::filesystem::path getAvatarDir() const; + virtual boost::filesystem::path getHomeDir() const = 0; + virtual boost::filesystem::path getSettingsDir() const = 0; + boost::filesystem::path getProfileDir(const String& profile) const; + + const String& getName() const { + return name_; + } + + virtual ApplicationMessageDisplay* getApplicationMessageDisplay() = 0; + + private: + String name_; + }; +} + +#endif diff --git a/Swiften/Application/ApplicationMessageDisplay.cpp b/Swiften/Application/ApplicationMessageDisplay.cpp new file mode 100644 index 0000000..48db37d --- /dev/null +++ b/Swiften/Application/ApplicationMessageDisplay.cpp @@ -0,0 +1,8 @@ +#include "Swiften/Application/ApplicationMessageDisplay.h" + +namespace Swift { + +ApplicationMessageDisplay::~ApplicationMessageDisplay() { +} + +} diff --git a/Swiften/Application/ApplicationMessageDisplay.h b/Swiften/Application/ApplicationMessageDisplay.h new file mode 100644 index 0000000..df7f0cb --- /dev/null +++ b/Swiften/Application/ApplicationMessageDisplay.h @@ -0,0 +1,15 @@ +#ifndef SWIFTEN_ApplicationMessageDisplay_H +#define SWIFTEN_ApplicationMessageDisplay_H + +namespace Swift { + class String; + + class ApplicationMessageDisplay { + public: + virtual ~ApplicationMessageDisplay(); + + virtual void setMessage(const String& message) = 0; + }; +} + +#endif diff --git a/Swiften/Application/MacOSX/MacOSXApplication.cpp b/Swiften/Application/MacOSX/MacOSXApplication.cpp new file mode 100644 index 0000000..79d7586 --- /dev/null +++ b/Swiften/Application/MacOSX/MacOSXApplication.cpp @@ -0,0 +1,22 @@ +#include "Swiften/Application/MacOSX/MacOSXApplication.h" + +namespace Swift { + +MacOSXApplication::MacOSXApplication(const String& name) : Application(name) { +} + +ApplicationMessageDisplay* MacOSXApplication::getApplicationMessageDisplay() { + return &messageDisplay_; +} + +boost::filesystem::path MacOSXApplication::getSettingsDir() const { + boost::filesystem::path result(getHomeDir() / "Library/Application Support" / getName().getUTF8String()); + boost::filesystem::create_directory(result); + return result; +} + +boost::filesystem::path MacOSXApplication::getHomeDir() const { + return boost::filesystem::path(getenv("HOME")); +} + +} diff --git a/Swiften/Application/MacOSX/MacOSXApplication.h b/Swiften/Application/MacOSX/MacOSXApplication.h new file mode 100644 index 0000000..cd5e69f --- /dev/null +++ b/Swiften/Application/MacOSX/MacOSXApplication.h @@ -0,0 +1,25 @@ +#ifndef SWIFTEN_MacOSXApplication_H +#define SWIFTEN_MacOSXApplication_H + +#include "Swiften/Application/Application.h" +#include "Swiften/Application/MacOSX/MacOSXApplicationMessageDisplay.h" +#include "Swiften/Application/MacOSX/MacOSXApplicationInitializer.h" + +namespace Swift { + class ApplicationMessageDisplay; + + class MacOSXApplication : public Application { + public: + MacOSXApplication(const String& name); + + virtual boost::filesystem::path getHomeDir() const; + virtual ApplicationMessageDisplay* getApplicationMessageDisplay(); + boost::filesystem::path getSettingsDir() const; + + private: + MacOSXApplicationInitializer initializer_; + MacOSXApplicationMessageDisplay messageDisplay_; + }; +} + +#endif diff --git a/Swiften/Application/MacOSX/MacOSXApplicationInitializer.h b/Swiften/Application/MacOSX/MacOSXApplicationInitializer.h new file mode 100644 index 0000000..db551eb --- /dev/null +++ b/Swiften/Application/MacOSX/MacOSXApplicationInitializer.h @@ -0,0 +1,16 @@ +#ifndef SWIFTEN_MacOSXApplicationInitializer_H +#define SWIFTEN_MacOSXApplicationInitializer_H + +namespace Swift { + class MacOSXApplicationInitializer { + public: + MacOSXApplicationInitializer(); + ~MacOSXApplicationInitializer(); + + private: + class Private; + Private* d; + }; +} + +#endif diff --git a/Swiften/Application/MacOSX/MacOSXApplicationInitializer.mm b/Swiften/Application/MacOSX/MacOSXApplicationInitializer.mm new file mode 100644 index 0000000..e401697 --- /dev/null +++ b/Swiften/Application/MacOSX/MacOSXApplicationInitializer.mm @@ -0,0 +1,24 @@ +#include "Swiften/Application/MacOSX/MacOSXApplicationInitializer.h" + +#include <AppKit/AppKit.h> +#include <Cocoa/Cocoa.h> + +namespace Swift { + +class MacOSXApplicationInitializer::Private { + public: + NSAutoreleasePool* autoReleasePool_; +}; + +MacOSXApplicationInitializer::MacOSXApplicationInitializer() { + d = new MacOSXApplicationInitializer::Private(); + NSApplicationLoad(); + d->autoReleasePool_ = [[NSAutoreleasePool alloc] init]; +} + +MacOSXApplicationInitializer::~MacOSXApplicationInitializer() { + [d->autoReleasePool_ release]; + delete d; +} + +} diff --git a/Swiften/Application/MacOSX/MacOSXApplicationMessageDisplay.h b/Swiften/Application/MacOSX/MacOSXApplicationMessageDisplay.h new file mode 100644 index 0000000..98af1fa --- /dev/null +++ b/Swiften/Application/MacOSX/MacOSXApplicationMessageDisplay.h @@ -0,0 +1,17 @@ +#ifndef SWIFTEN_MacOSXApplicationMessageDisplay_H +#define SWIFTEN_MacOSXApplicationMessageDisplay_H + +#include "Swiften/Application/ApplicationMessageDisplay.h" + +namespace Swift { + class String; + + class MacOSXApplicationMessageDisplay : public ApplicationMessageDisplay { + public: + MacOSXApplicationMessageDisplay(); + + void setMessage(const String& label); + }; +} + +#endif diff --git a/Swiften/Application/MacOSX/MacOSXApplicationMessageDisplay.mm b/Swiften/Application/MacOSX/MacOSXApplicationMessageDisplay.mm new file mode 100644 index 0000000..c10c707 --- /dev/null +++ b/Swiften/Application/MacOSX/MacOSXApplicationMessageDisplay.mm @@ -0,0 +1,20 @@ +#include "Swiften/Application/MacOSX/MacOSXApplicationMessageDisplay.h" + +#include <AppKit/AppKit.h> +#include <Cocoa/Cocoa.h> + +#include "Swiften/Base/String.h" + +namespace Swift { + +MacOSXApplicationMessageDisplay::MacOSXApplicationMessageDisplay() { +} + +void MacOSXApplicationMessageDisplay::setMessage(const String& label) { + NSString *labelString = [[NSString alloc] initWithUTF8String: label.getUTF8Data()]; + [[NSApp dockTile] setBadgeLabel: labelString]; + [labelString release]; + [NSApp requestUserAttention: NSInformationalRequest]; +} + +} diff --git a/Swiften/Application/NullApplicationMessageDisplay.h b/Swiften/Application/NullApplicationMessageDisplay.h new file mode 100644 index 0000000..03e0b42 --- /dev/null +++ b/Swiften/Application/NullApplicationMessageDisplay.h @@ -0,0 +1,16 @@ +#ifndef SWIFTEN_NullApplicationMessageDisplay_H +#define SWIFTEN_NullApplicationMessageDisplay_H + +#include "Swiften/Application/ApplicationMessageDisplay.h" + +namespace Swift { + class NullApplicationMessageDisplay : public ApplicationMessageDisplay { + public: + NullApplicationMessageDisplay() {} + + virtual void setMessage(const String&) { + } + }; +} + +#endif diff --git a/Swiften/Application/Platform/PlatformApplication.h b/Swiften/Application/Platform/PlatformApplication.h new file mode 100644 index 0000000..749bce4 --- /dev/null +++ b/Swiften/Application/Platform/PlatformApplication.h @@ -0,0 +1,24 @@ +#ifndef SWIFTEN_PlatformApplication_H +#define SWIFTEN_PlatformApplication_H + +#include "Swiften/Base/Platform.h" + + +#if defined(SWIFTEN_PLATFORM_MACOSX) +#include "Swiften/Application/MacOSX/MacOSXApplication.h" +namespace Swift { + typedef MacOSXApplication PlatformApplication; +} +#elif defined(SWIFTEN_PLATFORM_WIN32) +#include "Swiften/Application/Windows/WindowsApplication.h" +namespace Swift { + typedef WindowsApplication PlatformApplication; +} +#else +#include "Swiften/Application/Unix/UnixApplication.h" +namespace Swift { + typedef UnixApplication PlatformApplication; +} +#endif + +#endif diff --git a/Swiften/Application/SConscript b/Swiften/Application/SConscript new file mode 100644 index 0000000..78b3a34 --- /dev/null +++ b/Swiften/Application/SConscript @@ -0,0 +1,16 @@ +Import("swiften_env") + +sources = [ + "Application.cpp", + "ApplicationMessageDisplay.cpp", + ] + +if swiften_env["PLATFORM"] == "darwin" and swiften_env["target"] == "native" : + sources += [ + "MacOSX/MacOSXApplication.cpp", + "MacOSX/MacOSXApplicationMessageDisplay.mm", + "MacOSX/MacOSXApplicationInitializer.mm", + ] + +objects = swiften_env.StaticObject(sources) +swiften_env.Append(SWIFTEN_OBJECTS = [objects]) diff --git a/Swiften/Application/UnitTest/ApplicationTest.cpp b/Swiften/Application/UnitTest/ApplicationTest.cpp new file mode 100644 index 0000000..0196755 --- /dev/null +++ b/Swiften/Application/UnitTest/ApplicationTest.cpp @@ -0,0 +1,39 @@ +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> + +#include "Swiften/Application/Application.h" +#include "Swiften/Application/Platform/PlatformApplication.h" + +using namespace Swift; + +class ApplicationTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(ApplicationTest); + CPPUNIT_TEST(testGetSettingsDir); + CPPUNIT_TEST_SUITE_END(); + + public: + ApplicationTest() {} + + void setUp() { + testling_ = new PlatformApplication("SwiftTest"); + } + + void tearDown() { + delete testling_; + } + + void testGetSettingsDir() { + boost::filesystem::path dir = testling_->getSettingsDir(); + + CPPUNIT_ASSERT(boost::filesystem::exists(dir)); + CPPUNIT_ASSERT(boost::filesystem::is_directory(dir)); + + boost::filesystem::remove(dir); + } + + private: + Application* testling_; +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(ApplicationTest); diff --git a/Swiften/Application/Unix/UnixApplication.h b/Swiften/Application/Unix/UnixApplication.h new file mode 100644 index 0000000..c2671d8 --- /dev/null +++ b/Swiften/Application/Unix/UnixApplication.h @@ -0,0 +1,32 @@ +#ifndef SWIFTEN_UnixApplication_H +#define SWIFTEN_UnixApplication_H + +#include "Swiften/Application/Application.h" +#include "Swiften/Application/NullApplicationMessageDisplay.h" + +namespace Swift { + class UnixApplication : public Application { + public: + UnixApplication(const String& name) : Application(name) { + } + + virtual ApplicationMessageDisplay* getApplicationMessageDisplay() { + return &messageDisplay_; + } + + virtual boost::filesystem::path getHomeDir() const { + return boost::filesystem::path(getenv("HOME")); + } + + boost::filesystem::path getSettingsDir() const { + boost::filesystem::path result(getHomeDir() / ("." + getName().getLowerCase().getUTF8String())); + boost::filesystem::create_directory(result); + return result; + } + + private: + NullApplicationMessageDisplay messageDisplay_; + }; +} + +#endif diff --git a/Swiften/Application/Windows/WindowsApplication.h b/Swiften/Application/Windows/WindowsApplication.h new file mode 100644 index 0000000..365157b --- /dev/null +++ b/Swiften/Application/Windows/WindowsApplication.h @@ -0,0 +1,36 @@ +#ifndef SWIFTEN_WindowsApplication_H +#define SWIFTEN_WindowsApplication_H + +#include "Swiften/Application/Application.h" +#include "Swiften/Application/NullApplicationMessageDisplay.h" + +namespace Swift { + class WindowsApplication : public Application { + public: + WindowsApplication(const String& name) : Application(name) { + } + + virtual ApplicationMessageDisplay* getApplicationMessageDisplay() { + return &messageDisplay_; + } + + boost::filesystem::path getSettingsDir() const { + char* appDirRaw = getenv("APPDATA"); + boost::filesystem::path result(boost::filesystem::path(appDirRaw) / getName().getUTF8String()); + boost::filesystem::create_directory(result); + return result; + } + + boost::filesystem::path getHomeDir() const { + //FIXME: This should be My Documents + + char* homeDirRaw = getenv("USERPROFILE"); + return boost::filesystem::path(homeDirRaw); + } + + private: + NullApplicationMessageDisplay messageDisplay_; + }; +} + +#endif diff --git a/Swiften/Avatars/AvatarFileStorage.cpp b/Swiften/Avatars/AvatarFileStorage.cpp new file mode 100644 index 0000000..1348018 --- /dev/null +++ b/Swiften/Avatars/AvatarFileStorage.cpp @@ -0,0 +1,26 @@ +#include "Swiften/Avatars/AvatarFileStorage.h" + +#include <iostream> +#include <boost/filesystem/fstream.hpp> + +namespace Swift { + +AvatarFileStorage::AvatarFileStorage(const boost::filesystem::path& path) : path_(path) { + boost::filesystem::create_directory(path_); +} + +bool AvatarFileStorage::hasAvatar(const String& hash) const { + return boost::filesystem::exists(getAvatarPath(hash)); +} + +void AvatarFileStorage::addAvatar(const String& hash, const ByteArray& avatar) { + boost::filesystem::ofstream file(getAvatarPath(hash), boost::filesystem::ofstream::binary|boost::filesystem::ofstream::out); + file.write(avatar.getData(), avatar.getSize()); + file.close(); +} + +boost::filesystem::path AvatarFileStorage::getAvatarPath(const String& hash) const { + return path_ / hash.getUTF8String(); +} + +} diff --git a/Swiften/Avatars/AvatarFileStorage.h b/Swiften/Avatars/AvatarFileStorage.h new file mode 100644 index 0000000..1afa703 --- /dev/null +++ b/Swiften/Avatars/AvatarFileStorage.h @@ -0,0 +1,24 @@ +#pragma once + +#include <map> +#include <boost/filesystem.hpp> + +#include "Swiften/Base/String.h" +#include "Swiften/Base/ByteArray.h" +#include "Swiften/Avatars/AvatarStorage.h" + +namespace Swift { + class AvatarFileStorage : public AvatarStorage { + public: + AvatarFileStorage(const boost::filesystem::path& path); + + virtual bool hasAvatar(const String& hash) const; + virtual void addAvatar(const String& hash, const ByteArray& avatar); + + virtual boost::filesystem::path getAvatarPath(const String& hash) const; + + private: + boost::filesystem::path path_; + }; + +} diff --git a/Swiften/Avatars/AvatarManager.cpp b/Swiften/Avatars/AvatarManager.cpp new file mode 100644 index 0000000..3825ffd --- /dev/null +++ b/Swiften/Avatars/AvatarManager.cpp @@ -0,0 +1,108 @@ +#include "Swiften/Avatars/AvatarManager.h" + +#include <boost/bind.hpp> + +#include "Swiften/Client/StanzaChannel.h" +#include "Swiften/Elements/VCardUpdate.h" +#include "Swiften/Queries/Requests/GetVCardRequest.h" +#include "Swiften/StringCodecs/SHA1.h" +#include "Swiften/StringCodecs/Hexify.h" +#include "Swiften/Avatars/AvatarStorage.h" +#include "Swiften/MUC/MUCRegistry.h" + +namespace Swift { + +AvatarManager::AvatarManager(StanzaChannel* stanzaChannel, IQRouter* iqRouter, AvatarStorage* avatarStorage, MUCRegistry* mucRegistry) : stanzaChannel_(stanzaChannel), iqRouter_(iqRouter), avatarStorage_(avatarStorage), mucRegistry_(mucRegistry) { + stanzaChannel->onPresenceReceived.connect(boost::bind(&AvatarManager::handlePresenceReceived, this, _1)); +} + +AvatarManager::AvatarManager() { + stanzaChannel_ = NULL; + iqRouter_ = NULL; + avatarStorage_ = NULL; + mucRegistry_ = NULL; +} + +AvatarManager::~AvatarManager() { + +} + +void AvatarManager::setMUCRegistry(MUCRegistry* mucRegistry) { + mucRegistry_ = mucRegistry; +} + +void AvatarManager::handlePresenceReceived(boost::shared_ptr<Presence> presence) { + boost::shared_ptr<VCardUpdate> update = presence->getPayload<VCardUpdate>(); + if (!update) { + return; + } + JID from = getAvatarJID(presence->getFrom()); + String& hash = avatarHashes_[from]; + if (hash != update->getPhotoHash()) { + String newHash = update->getPhotoHash(); + if (avatarStorage_->hasAvatar(newHash)) { + setAvatarHash(from, newHash); + } + else { + boost::shared_ptr<GetVCardRequest> request(new GetVCardRequest(from, iqRouter_)); + request->onResponse.connect(boost::bind(&AvatarManager::handleVCardReceived, this, from, newHash, _1, _2)); + request->send(); + } + } +} + +void AvatarManager::handleVCardReceived(const JID& from, const String& promisedHash, boost::shared_ptr<VCard> vCard, const boost::optional<ErrorPayload>& error) { + if (error) { + // FIXME: What to do here? + std::cerr << "Warning: " << from << ": Could not get vCard" << std::endl; + return; + } + if (!vCard) { + std::cerr << "Warning: " << from << ": null vcard payload" << std::endl; + //FIXME: Why could this happen? + return; + } + String realHash = Hexify::hexify(SHA1::getHash(vCard->getPhoto())); + if (promisedHash != realHash) { + std::cerr << "Warning: " << from << ": Got different vCard photo hash (" << promisedHash << " != " << realHash << ")" << std::endl; + } + avatarStorage_->addAvatar(realHash, vCard->getPhoto()); + setAvatarHash(from, realHash); +} + +void AvatarManager::setAvatar(const JID& jid, const ByteArray& avatar) { + String hash = Hexify::hexify(SHA1::getHash(avatar)); + avatarStorage_->addAvatar(hash, avatar); + setAvatarHash(getAvatarJID(jid), hash); +} + +void AvatarManager::setAvatarHash(const JID& from, const String& hash) { + avatarHashes_[from] = hash; + onAvatarChanged(from, hash); +} + +String AvatarManager::getAvatarHash(const JID& jid) const { + std::map<JID, String>::const_iterator i = avatarHashes_.find(getAvatarJID(jid)); + if (i != avatarHashes_.end()) { + return i->second; + } + else { + return ""; + } +} + +boost::filesystem::path AvatarManager::getAvatarPath(const JID& jid) const { + String hash = getAvatarHash(jid); + if (!hash.isEmpty()) { + return avatarStorage_->getAvatarPath(hash); + } + return boost::filesystem::path(); +} + +JID AvatarManager::getAvatarJID(const JID& jid) const { + JID bareFrom = jid.toBare(); + return (mucRegistry_ && mucRegistry_->isMUC(bareFrom)) ? jid : bareFrom; +} + + +} diff --git a/Swiften/Avatars/AvatarManager.h b/Swiften/Avatars/AvatarManager.h new file mode 100644 index 0000000..fd308d9 --- /dev/null +++ b/Swiften/Avatars/AvatarManager.h @@ -0,0 +1,51 @@ +#pragma once + +#include <boost/filesystem.hpp> +#include <boost/shared_ptr.hpp> +#include <boost/optional.hpp> +#include <boost/signal.hpp> +#include <map> + +#include "Swiften/JID/JID.h" +#include "Swiften/Elements/Presence.h" +#include "Swiften/Elements/VCard.h" +#include "Swiften/Elements/ErrorPayload.h" + +namespace Swift { + class MUCRegistry; + class AvatarStorage; + class StanzaChannel; + class IQRouter; + + class AvatarManager { + public: + AvatarManager(StanzaChannel*, IQRouter*, AvatarStorage*, MUCRegistry* = NULL); + virtual ~AvatarManager(); + + virtual void setMUCRegistry(MUCRegistry*); + + virtual String getAvatarHash(const JID&) const; + virtual boost::filesystem::path getAvatarPath(const JID&) const; + virtual void setAvatar(const JID&, const ByteArray& avatar); + + public: + boost::signal<void (const JID&, const String&)> onAvatarChanged; + + protected: + /** Used only for testing. Leads to a non-functional object. */ + AvatarManager(); + + private: + void handlePresenceReceived(boost::shared_ptr<Presence>); + void handleVCardReceived(const JID& from, const String& hash, boost::shared_ptr<VCard>, const boost::optional<ErrorPayload>&); + void setAvatarHash(const JID& from, const String& hash); + JID getAvatarJID(const JID& o) const; + + private: + StanzaChannel* stanzaChannel_; + IQRouter* iqRouter_; + AvatarStorage* avatarStorage_; + MUCRegistry* mucRegistry_; + std::map<JID, String> avatarHashes_; + }; +} diff --git a/Swiften/Avatars/AvatarStorage.cpp b/Swiften/Avatars/AvatarStorage.cpp new file mode 100644 index 0000000..4c98314 --- /dev/null +++ b/Swiften/Avatars/AvatarStorage.cpp @@ -0,0 +1,8 @@ +#include "Swiften/Avatars/AvatarStorage.h" + +namespace Swift { + +AvatarStorage::~AvatarStorage() { +} + +} diff --git a/Swiften/Avatars/AvatarStorage.h b/Swiften/Avatars/AvatarStorage.h new file mode 100644 index 0000000..b5c0f32 --- /dev/null +++ b/Swiften/Avatars/AvatarStorage.h @@ -0,0 +1,18 @@ +#pragma once + +#include <boost/filesystem.hpp> + +namespace Swift { + class String; + class ByteArray; + + class AvatarStorage { + public: + virtual ~AvatarStorage(); + + virtual bool hasAvatar(const String& hash) const = 0; + virtual void addAvatar(const String& hash, const ByteArray& avatar) = 0; + virtual boost::filesystem::path getAvatarPath(const String& hash) const = 0; + }; + +} diff --git a/Swiften/Avatars/UnitTest/AvatarManagerTest.cpp b/Swiften/Avatars/UnitTest/AvatarManagerTest.cpp new file mode 100644 index 0000000..3b4c5f9 --- /dev/null +++ b/Swiften/Avatars/UnitTest/AvatarManagerTest.cpp @@ -0,0 +1,110 @@ +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> + +#include "Swiften/Elements/VCardUpdate.h" +#include "Swiften/Avatars/AvatarManager.h" +#include "Swiften/Avatars/AvatarStorage.h" +#include "Swiften/MUC/MUCRegistry.h" +#include "Swiften/Queries/IQRouter.h" +#include "Swiften/Client/DummyStanzaChannel.h" + +using namespace Swift; + +class AvatarManagerTest : public CppUnit::TestFixture { + CPPUNIT_TEST_SUITE(AvatarManagerTest); + CPPUNIT_TEST(testUpdate_UpdateNewHash); + CPPUNIT_TEST(testUpdate_UpdateNewHashAlreadyHaveAvatar); + CPPUNIT_TEST(testUpdate_UpdateNewHashFromMUC); + CPPUNIT_TEST(testUpdate_UpdateSameHash); + CPPUNIT_TEST(testUpdate_UpdateNewHashSameThanOtherUser); + CPPUNIT_TEST(testReceiveVCard); + CPPUNIT_TEST(testGetAvatarPath); + CPPUNIT_TEST(testGetAvatarPathFromMUC); + CPPUNIT_TEST_SUITE_END(); + + public: + AvatarManagerTest() {} + + void setUp() { + stanzaChannel_ = new DummyStanzaChannel(); + iqRouter_ = new IQRouter(stanzaChannel_); + mucRegistry_ = new DummyMUCRegistry(); + avatarStorage_ = new DummyAvatarStorage(); + } + + void tearDown() { + delete avatarStorage_; + delete mucRegistry_; + delete iqRouter_; + delete stanzaChannel_; + } + + void testUpdate_UpdateNewHash() { + std::auto_ptr<AvatarManager> testling = createManager(); + stanzaChannel_->onPresenceReceived(createPresenceWithPhotoHash()); + + CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(stanzaChannel_->sentStanzas_.size())); + IQ* + CPPUNIT_ASSERT(stanzaChannel->isRequestAtIndex<VCardUpdate>(0, JID("foo@bar.com"), IQ::Get)); + } + + void testUpdate_UpdateNewHashAlreadyHaveAvatar() { + std::auto_ptr<AvatarManager> testling = createManager(); + } + + void testUpdate_UpdateNewHashFromMUC() { + std::auto_ptr<AvatarManager> testling = createManager(); + } + + void testUpdate_UpdateSameHash() { + std::auto_ptr<AvatarManager> testling = createManager(); + } + + void testUpdate_UpdateNewHashSameThanOtherUser() { + std::auto_ptr<AvatarManager> testling = createManager(); + } + + void testReceiveVCard() { + std::auto_ptr<AvatarManager> testling = createManager(); + } + + void testGetAvatarPath() { + std::auto_ptr<AvatarManager> testling = createManager(); + } + + void testGetAvatarPathFromMUC() { + std::auto_ptr<AvatarManager> testling = createManager(); + } + + private: + std::auto_ptr<AvatarManager> createManager() { + return std::auto_ptr<AvatarManager>(new AvatarManager(stanzaChannel_, iqRouter_, avatarStorage_, mucRegistry_)); + } + + boost::shared_ptr<Presence> createPresenceWithPhotoHash() { + boost::shared_ptr<Presence> presence(new Presence()); + presence->setFrom(JID("foo@bar.com/baz")); + presence->addPayload(boost::shared_ptr<VCardUpdate>(new VCardUpdate("aef56135bcce35eb24a43fcd684005b4ca286497"))); + return presence; + } + + private: + struct DummyMUCRegistry : public MUCRegistry { + bool isMUC(const JID& jid) const { return std::find(mucs_.begin(), mucs_.end(), jid) != mucs_.end(); } + std::vector<JID> mucs_; + }; + struct DummyAvatarStorage : public AvatarStorage { + virtual bool hasAvatar(const String& hash) const { return avatars.find(hash) != avatars.end(); } + virtual void addAvatar(const String& hash, const ByteArray& avatar) { avatars[hash] = avatar; } + virtual boost::filesystem::path getAvatarPath(const String& hash) const { + return boost::filesystem::path("/avatars") / hash.getUTF8String(); + } + std::map<String, ByteArray> avatars; + }; + DummyStanzaChannel* stanzaChannel_; + IQRouter* iqRouter_; + DummyMUCRegistry* mucRegistry_; + DummyAvatarStorage* avatarStorage_; +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(AvatarManagerTest); diff --git a/Swiften/Avatars/UnitTest/MockAvatarManager.cpp b/Swiften/Avatars/UnitTest/MockAvatarManager.cpp new file mode 100644 index 0000000..2d3d48a --- /dev/null +++ b/Swiften/Avatars/UnitTest/MockAvatarManager.cpp @@ -0,0 +1,26 @@ +#include "Swiften/Avatars/UnitTest/MockAvatarManager.h" + +namespace Swift { + +MockAvatarManager::MockAvatarManager() { + +} + +MockAvatarManager::~MockAvatarManager() { + +} + +String MockAvatarManager::getAvatarHash(const JID& jid) const { + return jid.toBare(); +} + +boost::filesystem::path MockAvatarManager::getAvatarPath(const JID& jid) const { + return jid.getResource().getUTF8String(); +} + +void MockAvatarManager::setAvatar(const JID&, const ByteArray&) { + +} + +} + diff --git a/Swiften/Avatars/UnitTest/MockAvatarManager.h b/Swiften/Avatars/UnitTest/MockAvatarManager.h new file mode 100644 index 0000000..416d921 --- /dev/null +++ b/Swiften/Avatars/UnitTest/MockAvatarManager.h @@ -0,0 +1,17 @@ +#pragma once + +#include "Swiften/Avatars/AvatarManager.h" +#include "Swiften/Client/DummyStanzaChannel.h" + +namespace Swift { + class MockAvatarManager : public AvatarManager { + public: + MockAvatarManager(); + virtual ~MockAvatarManager(); + virtual String getAvatarHash(const JID&) const; + virtual boost::filesystem::path getAvatarPath(const JID&) const; + virtual void setAvatar(const JID&, const ByteArray& avatar); + private: + DummyStanzaChannel channel_; + }; +} diff --git a/Swiften/Base/ByteArray.cpp b/Swiften/Base/ByteArray.cpp new file mode 100644 index 0000000..b3a5d8d --- /dev/null +++ b/Swiften/Base/ByteArray.cpp @@ -0,0 +1,34 @@ +#include "Swiften/Base/ByteArray.h" + +#include <fstream> + +std::ostream& operator<<(std::ostream& os, const Swift::ByteArray& s) { + std::ios::fmtflags oldFlags = os.flags(); + os << std::hex; + for (Swift::ByteArray::const_iterator i = s.begin(); i != s.end(); ++i) { + os << "0x" << static_cast<unsigned int>(static_cast<unsigned char>(*i)); + if (i + 1 < s.end()) { + os << " "; + } + } + os << std::endl; + os.flags(oldFlags); + return os; +} + +namespace Swift { + +static const int BUFFER_SIZE = 4096; + +void ByteArray::readFromFile(const String& file) { + std::ifstream input(file.getUTF8Data(), std::ios_base::in|std::ios_base::binary); + while (input.good()) { + size_t oldSize = data_.size(); + data_.resize(oldSize + BUFFER_SIZE); + input.read(&data_[oldSize], BUFFER_SIZE); + data_.resize(oldSize + input.gcount()); + } + input.close(); +} + +} diff --git a/Swiften/Base/ByteArray.h b/Swiften/Base/ByteArray.h new file mode 100644 index 0000000..ab256a4 --- /dev/null +++ b/Swiften/Base/ByteArray.h @@ -0,0 +1,103 @@ +#pragma once + +#include <cstring> +#include <vector> +#include <iostream> + +#include "Swiften/Base/String.h" + +namespace Swift { + class ByteArray + { + public: + typedef std::vector<char>::const_iterator const_iterator; + + ByteArray() : data_() {} + + ByteArray(const String& s) : data_(s.getUTF8String().begin(), s.getUTF8String().end()) {} + + ByteArray(const char* c) { + while (*c) { + data_.push_back(*c); + ++c; + } + } + + ByteArray(const char* c, size_t n) { + if (n > 0) { + data_.resize(n); + memcpy(&data_[0], c, n); + } + } + + const char* getData() const { + return data_.empty() ? NULL : &data_[0]; + } + + char* getData() { + return data_.empty() ? NULL : &data_[0]; + } + + size_t getSize() const { + return data_.size(); + } + + bool isEmpty() const { + return data_.empty(); + } + + void resize(size_t size) { + return data_.resize(size); + } + + friend ByteArray operator+(const ByteArray& a, const ByteArray&b) { + ByteArray result(a); + result.data_.insert(result.data_.end(), b.data_.begin(), b.data_.end()); + return result; + } + + friend ByteArray operator+(const ByteArray& a, char b) { + ByteArray x; + x.resize(1); + x[0] = b; + return a + x; + } + + ByteArray& operator+=(const ByteArray& b) { + data_.insert(data_.end(), b.data_.begin(), b.data_.end()); + return *this; + } + + friend bool operator==(const ByteArray& a, const ByteArray& b) { + return a.data_ == b.data_; + } + + + const char& operator[](size_t i) const { + return data_[i]; + } + + char& operator[](size_t i) { + return data_[i]; + } + + const_iterator begin() const { + return data_.begin(); + } + + const_iterator end() const { + return data_.end(); + } + + String toString() const { + return String(getData(), getSize()); + } + + void readFromFile(const String& file); + + private: + std::vector<char> data_; + }; +} + +std::ostream& operator<<(std::ostream& os, const Swift::ByteArray& s); diff --git a/Swiften/Base/Error.cpp b/Swiften/Base/Error.cpp new file mode 100644 index 0000000..597c155 --- /dev/null +++ b/Swiften/Base/Error.cpp @@ -0,0 +1,8 @@ +#include "Swiften/Base/Error.h" + +namespace Swift { + +Error::~Error() { +} + +} diff --git a/Swiften/Base/Error.h b/Swiften/Base/Error.h new file mode 100644 index 0000000..4c729ff --- /dev/null +++ b/Swiften/Base/Error.h @@ -0,0 +1,8 @@ +#pragma once + +namespace Swift { + class Error { + public: + virtual ~Error(); + }; +}; diff --git a/Swiften/Base/IDGenerator.cpp b/Swiften/Base/IDGenerator.cpp new file mode 100644 index 0000000..07ead43 --- /dev/null +++ b/Swiften/Base/IDGenerator.cpp @@ -0,0 +1,28 @@ +#include "Swiften/Base/IDGenerator.h" + +namespace Swift { + +IDGenerator::IDGenerator() { +} + +String IDGenerator::generateID() { + bool carry = true; + size_t i = 0; + while (carry && i < currentID_.getUTF8Size()) { + char c = currentID_.getUTF8String()[i]; + if (c >= 'z') { + currentID_.getUTF8String()[i] = 'a'; + } + else { + currentID_.getUTF8String()[i] = c+1; + carry = false; + } + ++i; + } + if (carry) { + currentID_ += 'a'; + } + return currentID_; +} + +} diff --git a/Swiften/Base/IDGenerator.h b/Swiften/Base/IDGenerator.h new file mode 100644 index 0000000..db7b80d --- /dev/null +++ b/Swiften/Base/IDGenerator.h @@ -0,0 +1,18 @@ +#ifndef SWIFTEN_IDGenerator_H +#define SWIFTEN_IDGenerator_H + +#include "Swiften/Base/String.h" + +namespace Swift { + class IDGenerator { + public: + IDGenerator(); + + String generateID(); + + private: + String currentID_; + }; +} + +#endif diff --git a/Swiften/Base/Platform.h b/Swiften/Base/Platform.h new file mode 100644 index 0000000..9e4c398 --- /dev/null +++ b/Swiften/Base/Platform.h @@ -0,0 +1,44 @@ +#ifndef SWIFTEN_Platform_H +#define SWIFTEN_Platform_H + +// Base platforms +#if defined(linux) || defined(__linux) || defined(__linux__) +#define SWIFTEN_PLATFORM_LINUX +#elif defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) || defined(__DragonFly__) +#define SWIFTEN_PLATFORM_BSD +#elif defined(sun) || defined(__sun) +#define SWIFTEN_PLATFORM_SOLARIS +#elif defined(__sgi) +#define SWIFTEN_PLATFORM_SGI +#elif defined(__hpux) +#define SWIFTEN_PLATFORM_HPUX +#elif defined(__CYGWIN__) +#define SWIFTEN_PLATFORM_CYGWIN +#elif defined(_WIN32) || defined(__WIN32__) || defined(WIN32) +#define SWIFTEN_PLATFORM_WIN32 +#elif defined(__BEOS__) +#define SWIFTEN_PLATFORM_BEOS +#elif defined(macintosh) || defined(__APPLE__) || defined(__APPLE_CC__) +#define SWIFTEN_PLATFORM_MACOSX +#elif defined(__IBMCPP__) || defined(_AIX) +#define SWIFTEN_PLATFORM_AIX +#elif defined(__amigaos__) +#define SWIFTEN_PLATFORM_AMIGAOS +#elif defined(__QNXNTO__) +#define SWIFTEN_PLATFORM_QNX +#endif + +// Derived platforms +#if defined(SWIFTEN_PLATFORM_CYGWIN) || defined(SWIFTEN_PLATFORM_WIN32) +#define SWIFTEN_PLATFORM_WINDOWS +#endif + +// Endianness +#include <boost/detail/endian.hpp> +#if defined(BOOST_LITTLE_ENDIAN) +#define SWIFTEN_LITTLE_ENDIAN +#elif defined(BOOST_BIG_ENDIAN) +#define SWIFTEN_BIG_ENDIAN +#endif + +#endif diff --git a/Swiften/Base/SConscript b/Swiften/Base/SConscript new file mode 100644 index 0000000..a0984e5 --- /dev/null +++ b/Swiften/Base/SConscript @@ -0,0 +1,10 @@ +Import("swiften_env") + +objects = swiften_env.StaticObject([ + "ByteArray.cpp", + "Error.cpp", + "IDGenerator.cpp", + "String.cpp", + "sleep.cpp", + ]) +swiften_env.Append(SWIFTEN_OBJECTS = [objects]) diff --git a/Swiften/Base/String.cpp b/Swiften/Base/String.cpp new file mode 100644 index 0000000..cc989f6 --- /dev/null +++ b/Swiften/Base/String.cpp @@ -0,0 +1,116 @@ +#include <cassert> +#include <algorithm> + +#include "Swiften/Base/String.h" + +namespace Swift { + +static inline size_t sequenceLength(char firstByte) { + if ((firstByte & 0x80) == 0) { + return 1; + } + if ((firstByte & 0xE0) == 0xC0) { + return 2; + } + if ((firstByte & 0xF0) == 0xE0) { + return 3; + } + if ((firstByte & 0xF8) == 0xF0) { + return 4; + } + if ((firstByte & 0xFC) == 0xF8) { + return 5; + } + if ((firstByte & 0xFE) == 0xFC) { + return 6; + } + assert(false); + return 1; +} + +std::vector<unsigned int> String::getUnicodeCodePoints() const { + std::vector<unsigned int> result; + for (size_t i = 0; i < data_.size();) { + unsigned int codePoint = 0; + char firstChar = data_[i]; + size_t length = sequenceLength(firstChar); + + // First character is special + size_t firstCharBitSize = 7 - length; + if (length == 1) { + firstCharBitSize = 7; + } + codePoint = firstChar & ((1<<(firstCharBitSize+1)) - 1); + + for (size_t j = 1; j < length; ++j) { + codePoint = (codePoint<<6) | (data_[i+j] & 0x3F); + } + result.push_back(codePoint); + i += length; + } + return result; +} + + +std::pair<String,String> String::getSplittedAtFirst(char c) const { + assert((c & 0x80) == 0); + size_t firstMatch = data_.find(c); + if (firstMatch != data_.npos) { + return std::make_pair(data_.substr(0,firstMatch),data_.substr(firstMatch+1,data_.npos)); + } + else { + return std::make_pair(*this, ""); + } +} + +size_t String::getLength() const { + size_t size = 0, current = 0, end = data_.size(); + while (current < end) { + size++; + current += sequenceLength(data_[current]); + } + return size; +} + +void String::removeAll(char c) { + size_t lastPos = 0; + size_t matchingIndex = 0; + while ((matchingIndex = data_.find(c, lastPos)) != data_.npos) { + data_.erase(matchingIndex, 1); + lastPos = matchingIndex; + } +} + +void String::replaceAll(char c, const String& s) { + size_t lastPos = 0; + size_t matchingIndex = 0; + while ((matchingIndex = data_.find(c, lastPos)) != data_.npos) { + data_.replace(matchingIndex, 1, s.data_); + lastPos = matchingIndex + s.data_.size(); + } +} + +String String::getLowerCase() const { + std::string lower(data_); + std::transform(lower.begin(), lower.end(), lower.begin(), ::tolower); + return String(lower); +} + +std::vector<String> String::split(char c) const { + assert((c & 0x80) == 0); + std::vector<String> result; + String accumulator; + for (size_t i = 0; i < data_.size(); ++i) { + if (data_[i] == c) { + result.push_back(accumulator); + accumulator = ""; + } + else { + accumulator += data_[i]; + } + } + result.push_back(accumulator); + return result; +} + +} diff --git a/Swiften/Base/String.h b/Swiften/Base/String.h new file mode 100644 index 0000000..7a6a9cc --- /dev/null +++ b/Swiften/Base/String.h @@ -0,0 +1,131 @@ +#ifndef SWIFTEN_STRING_H +#define SWIFTEN_STRING_H + +#include <ostream> +#include <string> +#include <utility> +#include <vector> +#include <cassert> + +#define SWIFTEN_STRING_TO_CFSTRING(a) \ + CFStringCreateWithBytes(NULL, reinterpret_cast<const UInt8*>(a.getUTF8Data()), a.getUTF8Size(), kCFStringEncodingUTF8, false) + +namespace Swift { + class ByteArray; + + class String { + friend class ByteArray; + + public: + String() {} + String(const char* data) : data_(data) {} + String(const char* data, size_t len) : data_(data, len) {} + String(const std::string& data) : data_(data) {} + + bool isEmpty() const { return data_.empty(); } + + const char* getUTF8Data() const { return data_.c_str(); } + const std::string& getUTF8String() const { return data_; } + std::string& getUTF8String() { return data_; } + size_t getUTF8Size() const { return data_.size(); } + std::vector<unsigned int> getUnicodeCodePoints() const; + + /** + * Returns the part before and after 'c'. + * If the given splitter does not occur in the string, the second + * component is the empty string. + */ + std::pair<String,String> getSplittedAtFirst(char c) const; + + std::vector<String> split(char c) const; + + size_t getLength() const; + String getLowerCase() const; + + void removeAll(char c); + + void replaceAll(char c, const String& s); + + bool beginsWith(char c) const { + return data_.size() > 0 && data_[0] == c; + } + + bool beginsWith(const String& s) const { + return data_.substr(0, s.data_.size()) == s; + } + + bool endsWith(char c) const { + return data_.size() > 0 && data_[data_.size()-1] == c; + } + + String getSubstring(size_t begin, size_t end) const { + return String(data_.substr(begin, end)); + } + + size_t find(char c) const { + assert((c & 0x80) == 0); + return data_.find(c); + } + + size_t npos() const { + return data_.npos; + } + + friend String operator+(const String& a, const String& b) { + return String(a.data_ + b.data_); + } + + friend String operator+(const String& a, char b) { + return String(a.data_ + b); + } + + String& operator+=(const String& o) { + data_ += o.data_; + return *this; + } + + String& operator+=(char c) { + data_ += c; + return *this; + } + + String& operator=(const String& o) { + data_ = o.data_; + return *this; + } + + bool contains(const String& o) { + return data_.find(o.data_) != std::string::npos; + } + + char operator[](size_t i) const { + return data_[i]; + } + + friend bool operator>(const String& a, const String& b) { + return a.data_ > b.data_; + } + + friend bool operator<(const String& a, const String& b) { + return a.data_ < b.data_; + } + + friend bool operator!=(const String& a, const String& b) { + return a.data_ != b.data_; + } + + friend bool operator==(const String& a, const String& b) { + return a.data_ == b.data_; + } + + friend std::ostream& operator<<(std::ostream& os, const String& s) { + os << s.data_; + return os; + } + + private: + std::string data_; + }; +} + +#endif diff --git a/Swiften/Base/UnitTest/ByteArrayTest.cpp b/Swiften/Base/UnitTest/ByteArrayTest.cpp new file mode 100644 index 0000000..bf893bd --- /dev/null +++ b/Swiften/Base/UnitTest/ByteArrayTest.cpp @@ -0,0 +1,21 @@ +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> + +#include "Swiften/Base/ByteArray.h" + +using namespace Swift; + +class ByteArrayTest : public CppUnit::TestFixture { + CPPUNIT_TEST_SUITE(ByteArrayTest); + CPPUNIT_TEST(testGetData_NoData); + CPPUNIT_TEST_SUITE_END(); + + public: + void testGetData_NoData() { + ByteArray testling; + + CPPUNIT_ASSERT_EQUAL(static_cast<const char*>(NULL), static_cast<const char*>(testling.getData())); + } +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(ByteArrayTest); diff --git a/Swiften/Base/UnitTest/IDGeneratorTest.cpp b/Swiften/Base/UnitTest/IDGeneratorTest.cpp new file mode 100644 index 0000000..bd96d91 --- /dev/null +++ b/Swiften/Base/UnitTest/IDGeneratorTest.cpp @@ -0,0 +1,34 @@ +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> +#include <set> + +#include "Swiften/Base/IDGenerator.h" + +using namespace Swift; + +class IDGeneratorTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(IDGeneratorTest); + CPPUNIT_TEST(testGenerate); + CPPUNIT_TEST_SUITE_END(); + + public: + IDGeneratorTest() {} + + void setUp() { + generatedIDs_.clear(); + } + + void testGenerate() { + IDGenerator testling; + for (unsigned int i = 0; i < 26*4; ++i) { + String id = testling.generateID(); + CPPUNIT_ASSERT(generatedIDs_.insert(id).second); + } + } + + private: + std::set<String> generatedIDs_; +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(IDGeneratorTest); diff --git a/Swiften/Base/UnitTest/StringTest.cpp b/Swiften/Base/UnitTest/StringTest.cpp new file mode 100644 index 0000000..87e1a99 --- /dev/null +++ b/Swiften/Base/UnitTest/StringTest.cpp @@ -0,0 +1,179 @@ +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> + +#include "Swiften/Base/String.h" + +using namespace Swift; + +class StringTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(StringTest); + CPPUNIT_TEST(testGetLength); + CPPUNIT_TEST(testGetLength_EncodedLength2); + CPPUNIT_TEST(testGetLength_EncodedLength3); + CPPUNIT_TEST(testGetLength_EncodedLength4); + CPPUNIT_TEST(testGetUnicodeCodePoints); + CPPUNIT_TEST(testGetSplittedAtFirst); + CPPUNIT_TEST(testGetSplittedAtFirst_CharacterAtEnd); + CPPUNIT_TEST(testGetSplittedAtFirst_NoSuchCharacter); + CPPUNIT_TEST(testRemoveAll); + CPPUNIT_TEST(testRemoveAll_LastChar); + CPPUNIT_TEST(testRemoveAll_ConsecutiveChars); + CPPUNIT_TEST(testReplaceAll); + CPPUNIT_TEST(testReplaceAll_LastChar); + CPPUNIT_TEST(testReplaceAll_ConsecutiveChars); + CPPUNIT_TEST(testReplaceAll_MatchingReplace); + CPPUNIT_TEST(testGetLowerCase); + CPPUNIT_TEST(testSplit); + CPPUNIT_TEST(testContains); + CPPUNIT_TEST(testContainsFalse); + CPPUNIT_TEST(testContainsExact); + CPPUNIT_TEST_SUITE_END(); + + public: + StringTest() {} + + void testGetLength() { + String testling("xyz$xyz"); + + CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(7), testling.getLength()); + } + + void testGetLength_EncodedLength2() { + String testling("xyz\xC2\xA2xyz"); + + CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(7), testling.getLength()); + } + + void testGetLength_EncodedLength3() { + String testling("xyz\xE2\x82\xACxyz"); + + CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(7), testling.getLength()); + } + + void testGetLength_EncodedLength4() { + String testling("xyz\xf4\x8a\xaf\x8dxyz"); + + CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(7), testling.getLength()); + } + + void testGetUnicodeCodePoints() { + String testling("$\xc2\xa2\xe2\x82\xac\xf4\x8a\xaf\x8d"); + std::vector<unsigned int> points = testling.getUnicodeCodePoints(); + + CPPUNIT_ASSERT_EQUAL(0x24U, points[0]); + CPPUNIT_ASSERT_EQUAL(0xA2U, points[1]); + CPPUNIT_ASSERT_EQUAL(0x20ACU, points[2]); + CPPUNIT_ASSERT_EQUAL(0x10ABCDU, points[3]); + } + + void testGetSplittedAtFirst() { + String testling("ab@cd@ef"); + + std::pair<String,String> result = testling.getSplittedAtFirst('@'); + CPPUNIT_ASSERT_EQUAL(String("ab"), result.first); + CPPUNIT_ASSERT_EQUAL(String("cd@ef"), result.second); + } + + void testGetSplittedAtFirst_CharacterAtEnd() { + String testling("ab@"); + + std::pair<String,String> result = testling.getSplittedAtFirst('@'); + CPPUNIT_ASSERT_EQUAL(String("ab"), result.first); + CPPUNIT_ASSERT(result.second.isEmpty()); + } + + void testGetSplittedAtFirst_NoSuchCharacter() { + String testling("ab"); + + std::pair<String,String> result = testling.getSplittedAtFirst('@'); + CPPUNIT_ASSERT_EQUAL(String("ab"), result.first); + CPPUNIT_ASSERT(result.second.isEmpty()); + } + + void testRemoveAll() { + String testling("ab c de"); + + testling.removeAll(' '); + + CPPUNIT_ASSERT_EQUAL(String("abcde"), testling); + } + + void testRemoveAll_LastChar() { + String testling("abcde "); + + testling.removeAll(' '); + + CPPUNIT_ASSERT_EQUAL(String("abcde"), testling); + } + + void testRemoveAll_ConsecutiveChars() { + String testling("ab cde"); + + testling.removeAll(' '); + + CPPUNIT_ASSERT_EQUAL(String("abcde"), testling); + } + + void testReplaceAll() { + String testling("abcbd"); + + testling.replaceAll('b', "xyz"); + + CPPUNIT_ASSERT_EQUAL(String("axyzcxyzd"), testling); + } + + void testReplaceAll_LastChar() { + String testling("abc"); + + testling.replaceAll('c', "xyz"); + + CPPUNIT_ASSERT_EQUAL(String("abxyz"), testling); + } + + void testReplaceAll_ConsecutiveChars() { + String testling("abbc"); + + testling.replaceAll('b',"xyz"); + + CPPUNIT_ASSERT_EQUAL(String("axyzxyzc"), testling); + } + + void testReplaceAll_MatchingReplace() { + String testling("abc"); + + testling.replaceAll('b',"bbb"); + + CPPUNIT_ASSERT_EQUAL(String("abbbc"), testling); + } + + void testGetLowerCase() { + String testling("aBcD e"); + + CPPUNIT_ASSERT_EQUAL(String("abcd e"), testling.getLowerCase()); + } + + void testSplit() { + std::vector<String> result = String("abc def ghi").split(' '); + + CPPUNIT_ASSERT_EQUAL(3, static_cast<int>(result.size())); + CPPUNIT_ASSERT_EQUAL(String("abc"), result[0]); + CPPUNIT_ASSERT_EQUAL(String("def"), result[1]); + CPPUNIT_ASSERT_EQUAL(String("ghi"), result[2]); + } + + void testContains() { + CPPUNIT_ASSERT(String("abcde").contains(String("bcd"))); + } + + void testContainsFalse() { + CPPUNIT_ASSERT(!String("abcde").contains(String("abcdef"))); + } + + void testContainsExact() { + CPPUNIT_ASSERT(String("abcde").contains(String("abcde"))); + } + +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(StringTest); diff --git a/Swiften/Base/foreach.h b/Swiften/Base/foreach.h new file mode 100644 index 0000000..b2bee66 --- /dev/null +++ b/Swiften/Base/foreach.h @@ -0,0 +1,9 @@ +#ifndef SWIFTEN_FOREACH_H +#define SWIFTEN_FOREACH_H + +#include <boost/foreach.hpp> + +#undef foreach +#define foreach BOOST_FOREACH + +#endif diff --git a/Swiften/Base/sleep.cpp b/Swiften/Base/sleep.cpp new file mode 100644 index 0000000..99d0fe6 --- /dev/null +++ b/Swiften/Base/sleep.cpp @@ -0,0 +1,14 @@ +#include "Swiften/Base/sleep.h" + +#include <boost/thread.hpp> + +namespace Swift { + +void sleep(unsigned int msecs) { + boost::xtime xt; + boost::xtime_get(&xt, boost::TIME_UTC); + xt.nsec += msecs*1000000; + boost::thread::sleep(xt); +} + +} diff --git a/Swiften/Base/sleep.h b/Swiften/Base/sleep.h new file mode 100644 index 0000000..bcebc4c --- /dev/null +++ b/Swiften/Base/sleep.h @@ -0,0 +1,8 @@ +#ifndef SWIFTEN_sleep_H +#define SWIFTEN_sleep_H + +namespace Swift { + void sleep(unsigned int msecs); +} + +#endif diff --git a/Swiften/Chat/ChatStateActionProvider.h b/Swiften/Chat/ChatStateActionProvider.h new file mode 100644 index 0000000..82bed3f --- /dev/null +++ b/Swiften/Chat/ChatStateActionProvider.h @@ -0,0 +1,7 @@ +#pragma once + +namespace Swift { + class ChatState { + + }; +} diff --git a/Swiften/Chat/ChatStateMessageSender.cpp b/Swiften/Chat/ChatStateMessageSender.cpp new file mode 100644 index 0000000..ad1495f --- /dev/null +++ b/Swiften/Chat/ChatStateMessageSender.cpp @@ -0,0 +1,22 @@ +#include "Swiften/Chat/ChatStateMessageSender.h" + +#include <boost/bind.hpp> + +#include "Swiften/Client/StanzaChannel.h" + +namespace Swift { + +ChatStateMessageSender::ChatStateMessageSender(ChatStateNotifier* notifier, StanzaChannel* stanzaChannel, const JID& contact) : contact_(contact) { + notifier_ = notifier; + stanzaChannel_ = stanzaChannel; + notifier_->onChatStateChanged.connect(boost::bind(&ChatStateMessageSender::handleChatStateChanged, this, _1)); +} + +void ChatStateMessageSender::handleChatStateChanged(ChatState::ChatStateType state) { + boost::shared_ptr<Message> message(new Message()); + message->setTo(contact_); + message->addPayload(boost::shared_ptr<Payload>(new ChatState(state))); + stanzaChannel_->sendMessage(message); +} + +} diff --git a/Swiften/Chat/ChatStateMessageSender.h b/Swiften/Chat/ChatStateMessageSender.h new file mode 100644 index 0000000..aff0791 --- /dev/null +++ b/Swiften/Chat/ChatStateMessageSender.h @@ -0,0 +1,20 @@ +#pragma once + +#include "Swiften/Chat/ChatStateNotifier.h" +#include "Swiften/Elements/ChatState.h" +#include "Swiften/JID/JID.h" + +namespace Swift { + class StanzaChannel; + class ChatStateMessageSender { + public: + ChatStateMessageSender(ChatStateNotifier* notifier, StanzaChannel* stanzaChannel, const JID& contact); + void setContact(const JID& contact) {contact_ = contact;}; + + private: + void handleChatStateChanged(ChatState::ChatStateType state); + ChatStateNotifier* notifier_; + StanzaChannel* stanzaChannel_; + JID contact_; + }; +} diff --git a/Swiften/Chat/ChatStateNotifier.cpp b/Swiften/Chat/ChatStateNotifier.cpp new file mode 100644 index 0000000..7c6560f --- /dev/null +++ b/Swiften/Chat/ChatStateNotifier.cpp @@ -0,0 +1,45 @@ +#include "Swiften/Chat/ChatStateNotifier.h" + +namespace Swift { + +ChatStateNotifier::ChatStateNotifier() { + contactHas85Caps_ = false; + isInConversation_ = false; + contactHasSentActive_ = false; + userIsTyping_ = false; +} + +void ChatStateNotifier::setContactHas85Caps(bool hasCaps) { + contactHas85Caps_ = hasCaps; +} + +void ChatStateNotifier::setUserIsTyping() { + if (contactShouldReceiveStates() && !userIsTyping_) { + userIsTyping_ = true; + onChatStateChanged(ChatState::Composing); + } +} + +void ChatStateNotifier::userSentMessage() { + userIsTyping_ = false; +} + +void ChatStateNotifier::userCancelledNewMessage() { + if (userIsTyping_) { + userIsTyping_ = false; + onChatStateChanged(ChatState::Active); + } +} + +void ChatStateNotifier::receivedMessageFromContact(bool hasActiveElement) { + isInConversation_ = true; + contactHasSentActive_ = hasActiveElement; +} + +bool ChatStateNotifier::contactShouldReceiveStates() { + /* So, yes, the XEP says to look at caps, but it also says that once you've + heard from the contact, the active state overrides this.*/ + return contactHasSentActive_ || (contactHas85Caps_ && !isInConversation_);; +} + +} diff --git a/Swiften/Chat/ChatStateNotifier.h b/Swiften/Chat/ChatStateNotifier.h new file mode 100644 index 0000000..0ef4255 --- /dev/null +++ b/Swiften/Chat/ChatStateNotifier.h @@ -0,0 +1,26 @@ +#pragma once + +#include <boost/signals.hpp> +#include <boost/shared_ptr.hpp> + +#include "Swiften/Elements/ChatState.h" + +namespace Swift { + class ChatStateNotifier { + public: + ChatStateNotifier(); + void setContactHas85Caps(bool hasCaps); + void setUserIsTyping(); + void userSentMessage(); + void userCancelledNewMessage(); + void receivedMessageFromContact(bool hasActiveElement); + bool contactShouldReceiveStates(); + + boost::signal<void (ChatState::ChatStateType)> onChatStateChanged; + private: + bool contactHas85Caps_; + bool isInConversation_; + bool contactHasSentActive_; + bool userIsTyping_; + }; +} diff --git a/Swiften/Chat/ChatStateTracker.cpp b/Swiften/Chat/ChatStateTracker.cpp new file mode 100644 index 0000000..3076845 --- /dev/null +++ b/Swiften/Chat/ChatStateTracker.cpp @@ -0,0 +1,28 @@ +#include "Swiften/Chat/ChatStateTracker.h" + +namespace Swift { +ChatStateTracker::ChatStateTracker() { + currentState_ = ChatState::Gone; +} + +void ChatStateTracker::handleMessageReceived(boost::shared_ptr<Message> message) { + boost::shared_ptr<ChatState> statePayload = message->getPayload<ChatState>(); + if (statePayload) { + changeState(statePayload->getChatState());; + } +} + +void ChatStateTracker::handlePresenceChange(boost::shared_ptr<Presence> newPresence, boost::shared_ptr<Presence>) { + if (newPresence->getType() == Presence::Unavailable) { + onChatStateChange(ChatState::Gone); + } +} + +void ChatStateTracker::changeState(ChatState::ChatStateType state) { + if (state != currentState_) { + currentState_ = state; + onChatStateChange(state); + } +} + +} diff --git a/Swiften/Chat/ChatStateTracker.h b/Swiften/Chat/ChatStateTracker.h new file mode 100644 index 0000000..e66bbae --- /dev/null +++ b/Swiften/Chat/ChatStateTracker.h @@ -0,0 +1,21 @@ +#pragma once + +#include <boost/signal.hpp> +#include <boost/shared_ptr.hpp> + +#include "Swiften/Elements/Message.h" +#include "Swiften/Elements/Presence.h" +#include "Swiften/Elements/ChatState.h" + +namespace Swift { + class ChatStateTracker { + public: + ChatStateTracker(); + void handleMessageReceived(boost::shared_ptr<Message> message); + void handlePresenceChange(boost::shared_ptr<Presence> newPresence, boost::shared_ptr<Presence> oldPresence); + boost::signal<void (ChatState::ChatStateType)> onChatStateChange; + private: + void changeState(ChatState::ChatStateType state); + ChatState::ChatStateType currentState_; + }; +} diff --git a/Swiften/Chat/UnitTest/ChatStateNotifierTest.cpp b/Swiften/Chat/UnitTest/ChatStateNotifierTest.cpp new file mode 100644 index 0000000..44ec9d8 --- /dev/null +++ b/Swiften/Chat/UnitTest/ChatStateNotifierTest.cpp @@ -0,0 +1,132 @@ +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> +#include <boost/bind.hpp> + +#include "Swiften/Chat/ChatStateNotifier.h" + +using namespace Swift; + + +class ChatStateMonitor { +public: + ChatStateMonitor(ChatStateNotifier* notifier) { + notifier_ = notifier; + composingCallCount = 0; + activeCallCount = 0; + notifier->onChatStateChanged.connect(boost::bind(&ChatStateMonitor::handleChatStateChanged, this, _1)); + }; + + int composingCallCount; + int activeCallCount; + ChatState::ChatStateType currentState; + +private: + void handleChatStateChanged(ChatState::ChatStateType newState) { + switch (newState) { + case ChatState::Composing: + composingCallCount++; + break; + case ChatState::Active: + activeCallCount++; + break; + default: + break; + } + currentState = newState; + }; + + ChatStateNotifier* notifier_; +}; + +class ChatStateNotifierTest : public CppUnit::TestFixture { + CPPUNIT_TEST_SUITE(ChatStateNotifierTest); + CPPUNIT_TEST(testStartTypingReply_CapsNotIncluded); + CPPUNIT_TEST(testStartTypingReply_CapsIncluded); + CPPUNIT_TEST(testCancelledNewMessage); + CPPUNIT_TEST(testContinueTypingReply_CapsIncluded); + CPPUNIT_TEST(testContactShouldReceiveStates_CapsOnly); + CPPUNIT_TEST(testContactShouldReceiveStates_CapsNorActive); + CPPUNIT_TEST(testContactShouldReceiveStates_ActiveOverrideOn); + CPPUNIT_TEST(testContactShouldReceiveStates_ActiveOverrideOff); + CPPUNIT_TEST_SUITE_END(); + +private: + ChatStateNotifier* notifier_; + ChatStateMonitor* monitor_; + +public: + void setUp() { + notifier_ = new ChatStateNotifier(); + monitor_ = new ChatStateMonitor(notifier_); + } + + void tearDown() { + delete notifier_; + delete monitor_; + } + + void testStartTypingReply_CapsNotIncluded() { + notifier_->setContactHas85Caps(false); + notifier_->setUserIsTyping(); + CPPUNIT_ASSERT_EQUAL(0, monitor_->composingCallCount); + } + + void testSendTwoMessages() { + notifier_->setContactHas85Caps(true); + notifier_->setUserIsTyping(); + notifier_->userSentMessage(); + notifier_->setUserIsTyping(); + notifier_->userSentMessage(); + CPPUNIT_ASSERT_EQUAL(2, monitor_->composingCallCount); + } + + void testCancelledNewMessage() { + notifier_->setContactHas85Caps(true); + notifier_->setUserIsTyping(); + notifier_->userCancelledNewMessage(); + CPPUNIT_ASSERT_EQUAL(1, monitor_->composingCallCount); + CPPUNIT_ASSERT_EQUAL(1, monitor_->activeCallCount); + CPPUNIT_ASSERT_EQUAL(ChatState::Active, monitor_->currentState); + } + + + void testContactShouldReceiveStates_CapsOnly() { + notifier_->setContactHas85Caps(true); + CPPUNIT_ASSERT_EQUAL(true, notifier_->contactShouldReceiveStates()); + } + + void testContactShouldReceiveStates_CapsNorActive() { + CPPUNIT_ASSERT_EQUAL(false, notifier_->contactShouldReceiveStates()); + } + + void testContactShouldReceiveStates_ActiveOverrideOn() { + notifier_->setContactHas85Caps(false); + notifier_->receivedMessageFromContact(true); + CPPUNIT_ASSERT_EQUAL(true, notifier_->contactShouldReceiveStates()); + } + + void testContactShouldReceiveStates_ActiveOverrideOff() { + notifier_->setContactHas85Caps(true); + notifier_->receivedMessageFromContact(false); + CPPUNIT_ASSERT_EQUAL(false, notifier_->contactShouldReceiveStates()); + } + + + void testStartTypingReply_CapsIncluded() { + notifier_->setContactHas85Caps(true); + notifier_->setUserIsTyping(); + CPPUNIT_ASSERT_EQUAL(1, monitor_->composingCallCount); + } + + void testContinueTypingReply_CapsIncluded() { + notifier_->setContactHas85Caps(true); + notifier_->setUserIsTyping(); + notifier_->setUserIsTyping(); + notifier_->setUserIsTyping(); + CPPUNIT_ASSERT_EQUAL(1, monitor_->composingCallCount); + } + + +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(ChatStateNotifierTest); diff --git a/Swiften/Client/Client.cpp b/Swiften/Client/Client.cpp new file mode 100644 index 0000000..c704248 --- /dev/null +++ b/Swiften/Client/Client.cpp @@ -0,0 +1,212 @@ +#include "Swiften/Client/Client.h" + +#include <boost/bind.hpp> + +#include "Swiften/Network/MainBoostIOServiceThread.h" +#include "Swiften/Network/BoostIOServiceThread.h" +#include "Swiften/Client/ClientSession.h" +#include "Swiften/StreamStack/PlatformTLSLayerFactory.h" +#include "Swiften/Network/Connector.h" +#include "Swiften/Network/BoostConnectionFactory.h" +#include "Swiften/Network/BoostTimerFactory.h" +#include "Swiften/TLS/PKCS12Certificate.h" +#include "Swiften/Session/BasicSessionStream.h" + +namespace Swift { + +Client::Client(const JID& jid, const String& password) : + IQRouter(this), jid_(jid), password_(password) { + connectionFactory_ = new BoostConnectionFactory(&MainBoostIOServiceThread::getInstance().getIOService()); + timerFactory_ = new BoostTimerFactory(&MainBoostIOServiceThread::getInstance().getIOService()); + tlsLayerFactory_ = new PlatformTLSLayerFactory(); +} + +Client::~Client() { + if (session_ || connection_) { + std::cerr << "Warning: Client not disconnected properly" << std::endl; + } + delete tlsLayerFactory_; + delete timerFactory_; + delete connectionFactory_; +} + +bool Client::isAvailable() { + return session_; +} + +void Client::connect() { + assert(!connector_); + connector_ = boost::shared_ptr<Connector>(new Connector(jid_.getDomain(), &resolver_, connectionFactory_, timerFactory_)); + connector_->onConnectFinished.connect(boost::bind(&Client::handleConnectorFinished, this, _1)); + connector_->setTimeoutMilliseconds(60*1000); + connector_->start(); +} + +void Client::handleConnectorFinished(boost::shared_ptr<Connection> connection) { + // TODO: Add domain name resolver error + connector_.reset(); + if (!connection) { + onError(ClientError::ConnectionError); + } + else { + assert(!connection_); + connection_ = connection; + + assert(!sessionStream_); + sessionStream_ = boost::shared_ptr<BasicSessionStream>(new BasicSessionStream(connection_, &payloadParserFactories_, &payloadSerializers_, tlsLayerFactory_, timerFactory_)); + if (!certificate_.isEmpty()) { + sessionStream_->setTLSCertificate(PKCS12Certificate(certificate_, password_)); + } + sessionStream_->onDataRead.connect(boost::bind(&Client::handleDataRead, this, _1)); + sessionStream_->onDataWritten.connect(boost::bind(&Client::handleDataWritten, this, _1)); + sessionStream_->initialize(); + + session_ = ClientSession::create(jid_, sessionStream_); + session_->onInitialized.connect(boost::bind(boost::ref(onConnected))); + session_->onFinished.connect(boost::bind(&Client::handleSessionFinished, this, _1)); + session_->onNeedCredentials.connect(boost::bind(&Client::handleNeedCredentials, this)); + session_->onElementReceived.connect(boost::bind(&Client::handleElement, this, _1)); + session_->start(); + } +} + +void Client::disconnect() { + if (session_) { + session_->finish(); + } + else { + closeConnection(); + } +} + +void Client::closeConnection() { + if (sessionStream_) { + sessionStream_.reset(); + } + if (connection_) { + connection_->disconnect(); + connection_.reset(); + } +} + +void Client::send(boost::shared_ptr<Stanza> stanza) { + if (!isAvailable()) { + std::cerr << "Warning: Client: Trying to send a stanza while disconnected." << std::endl; + return; + } + session_->sendElement(stanza); +} + +void Client::sendIQ(boost::shared_ptr<IQ> iq) { + send(iq); +} + +void Client::sendMessage(boost::shared_ptr<Message> message) { + send(message); +} + +void Client::sendPresence(boost::shared_ptr<Presence> presence) { + send(presence); +} + +String Client::getNewIQID() { + return idGenerator_.generateID(); +} + +void Client::handleElement(boost::shared_ptr<Element> element) { + boost::shared_ptr<Message> message = boost::dynamic_pointer_cast<Message>(element); + if (message) { + onMessageReceived(message); + return; + } + + boost::shared_ptr<Presence> presence = boost::dynamic_pointer_cast<Presence>(element); + if (presence) { + onPresenceReceived(presence); + return; + } + + boost::shared_ptr<IQ> iq = boost::dynamic_pointer_cast<IQ>(element); + if (iq) { + onIQReceived(iq); + return; + } +} + +void Client::setCertificate(const String& certificate) { + certificate_ = certificate; +} + +void Client::handleSessionFinished(boost::shared_ptr<Error> error) { + session_.reset(); + closeConnection(); + if (error) { + ClientError clientError; + if (boost::shared_ptr<ClientSession::Error> actualError = boost::dynamic_pointer_cast<ClientSession::Error>(error)) { + switch(actualError->type) { + case ClientSession::Error::AuthenticationFailedError: + clientError = ClientError(ClientError::AuthenticationFailedError); + break; + case ClientSession::Error::CompressionFailedError: + clientError = ClientError(ClientError::CompressionFailedError); + break; + case ClientSession::Error::ServerVerificationFailedError: + clientError = ClientError(ClientError::ServerVerificationFailedError); + break; + case ClientSession::Error::NoSupportedAuthMechanismsError: + clientError = ClientError(ClientError::NoSupportedAuthMechanismsError); + break; + case ClientSession::Error::UnexpectedElementError: + clientError = ClientError(ClientError::UnexpectedElementError); + break; + case ClientSession::Error::ResourceBindError: + clientError = ClientError(ClientError::ResourceBindError); + break; + case ClientSession::Error::SessionStartError: + clientError = ClientError(ClientError::SessionStartError); + break; + case ClientSession::Error::TLSError: + clientError = ClientError(ClientError::TLSError); + break; + case ClientSession::Error::TLSClientCertificateError: + clientError = ClientError(ClientError::ClientCertificateError); + break; + } + } + else if (boost::shared_ptr<SessionStream::Error> actualError = boost::dynamic_pointer_cast<SessionStream::Error>(error)) { + switch(actualError->type) { + case SessionStream::Error::ParseError: + clientError = ClientError(ClientError::XMLError); + break; + case SessionStream::Error::TLSError: + clientError = ClientError(ClientError::TLSError); + break; + case SessionStream::Error::InvalidTLSCertificateError: + clientError = ClientError(ClientError::ClientCertificateLoadError); + break; + case SessionStream::Error::ConnectionReadError: + clientError = ClientError(ClientError::ConnectionReadError); + break; + case SessionStream::Error::ConnectionWriteError: + clientError = ClientError(ClientError::ConnectionWriteError); + break; + } + } + onError(clientError); + } +} + +void Client::handleNeedCredentials() { + assert(session_); + session_->sendCredentials(password_); +} + +void Client::handleDataRead(const String& data) { + onDataRead(data); +} + +void Client::handleDataWritten(const String& data) { + onDataWritten(data); +} + +} diff --git a/Swiften/Client/Client.h b/Swiften/Client/Client.h new file mode 100644 index 0000000..444c136 --- /dev/null +++ b/Swiften/Client/Client.h @@ -0,0 +1,78 @@ +#pragma once + +#include <boost/signals.hpp> +#include <boost/shared_ptr.hpp> + +#include "Swiften/Network/PlatformDomainNameResolver.h" +#include "Swiften/Base/Error.h" +#include "Swiften/Client/ClientSession.h" +#include "Swiften/Client/ClientError.h" +#include "Swiften/Elements/Presence.h" +#include "Swiften/Elements/Message.h" +#include "Swiften/JID/JID.h" +#include "Swiften/Base/String.h" +#include "Swiften/Base/IDGenerator.h" +#include "Swiften/Client/StanzaChannel.h" +#include "Swiften/Queries/IQRouter.h" +#include "Swiften/Parser/PayloadParsers/FullPayloadParserFactoryCollection.h" +#include "Swiften/Serializer/PayloadSerializers/FullPayloadSerializerCollection.h" + +namespace Swift { + class TLSLayerFactory; + class ConnectionFactory; + class TimerFactory; + class ClientSession; + class BasicSessionStream; + class Connector; + + class Client : public StanzaChannel, public IQRouter, public boost::bsignals::trackable { + public: + Client(const JID& jid, const String& password); + ~Client(); + + void setCertificate(const String& certificate); + + void connect(); + void disconnect(); + + bool isAvailable(); + + virtual void sendIQ(boost::shared_ptr<IQ>); + virtual void sendMessage(boost::shared_ptr<Message>); + virtual void sendPresence(boost::shared_ptr<Presence>); + + public: + boost::signal<void (const ClientError&)> onError; + boost::signal<void ()> onConnected; + boost::signal<void (const String&)> onDataRead; + boost::signal<void (const String&)> onDataWritten; + + private: + void handleConnectorFinished(boost::shared_ptr<Connection>); + void send(boost::shared_ptr<Stanza>); + virtual String getNewIQID(); + void handleElement(boost::shared_ptr<Element>); + void handleSessionFinished(boost::shared_ptr<Error>); + void handleNeedCredentials(); + void handleDataRead(const String&); + void handleDataWritten(const String&); + + void closeConnection(); + + private: + PlatformDomainNameResolver resolver_; + JID jid_; + String password_; + IDGenerator idGenerator_; + boost::shared_ptr<Connector> connector_; + ConnectionFactory* connectionFactory_; + TimerFactory* timerFactory_; + TLSLayerFactory* tlsLayerFactory_; + FullPayloadParserFactoryCollection payloadParserFactories_; + FullPayloadSerializerCollection payloadSerializers_; + boost::shared_ptr<Connection> connection_; + boost::shared_ptr<BasicSessionStream> sessionStream_; + boost::shared_ptr<ClientSession> session_; + String certificate_; + }; +} diff --git a/Swiften/Client/ClientError.h b/Swiften/Client/ClientError.h new file mode 100644 index 0000000..a0557d4 --- /dev/null +++ b/Swiften/Client/ClientError.h @@ -0,0 +1,32 @@ +#pragma once + +namespace Swift { + class ClientError { + public: + enum Type { + UnknownError, + DomainNameResolveError, + ConnectionError, + ConnectionReadError, + ConnectionWriteError, + XMLError, + AuthenticationFailedError, + CompressionFailedError, + ServerVerificationFailedError, + NoSupportedAuthMechanismsError, + UnexpectedElementError, + ResourceBindError, + SessionStartError, + TLSError, + ClientCertificateLoadError, + ClientCertificateError + }; + + ClientError(Type type = UnknownError) : type_(type) {} + + Type getType() const { return type_; } + + private: + Type type_; + }; +} diff --git a/Swiften/Client/ClientSession.cpp b/Swiften/Client/ClientSession.cpp new file mode 100644 index 0000000..16bda40 --- /dev/null +++ b/Swiften/Client/ClientSession.cpp @@ -0,0 +1,276 @@ +#include "Swiften/Client/ClientSession.h" + +#include <boost/bind.hpp> + +#include "Swiften/Elements/ProtocolHeader.h" +#include "Swiften/Elements/StreamFeatures.h" +#include "Swiften/Elements/StartTLSRequest.h" +#include "Swiften/Elements/StartTLSFailure.h" +#include "Swiften/Elements/TLSProceed.h" +#include "Swiften/Elements/AuthRequest.h" +#include "Swiften/Elements/AuthSuccess.h" +#include "Swiften/Elements/AuthFailure.h" +#include "Swiften/Elements/AuthChallenge.h" +#include "Swiften/Elements/AuthResponse.h" +#include "Swiften/Elements/Compressed.h" +#include "Swiften/Elements/CompressFailure.h" +#include "Swiften/Elements/CompressRequest.h" +#include "Swiften/Elements/StartSession.h" +#include "Swiften/Elements/IQ.h" +#include "Swiften/Elements/ResourceBind.h" +#include "Swiften/SASL/PLAINClientAuthenticator.h" +#include "Swiften/SASL/SCRAMSHA1ClientAuthenticator.h" +#include "Swiften/Session/SessionStream.h" + +namespace Swift { + +ClientSession::ClientSession( + const JID& jid, + boost::shared_ptr<SessionStream> stream) : + localJID(jid), + state(Initial), + stream(stream), + needSessionStart(false), + authenticator(NULL) { +} + +ClientSession::~ClientSession() { +} + +void ClientSession::start() { + stream->onStreamStartReceived.connect(boost::bind(&ClientSession::handleStreamStart, shared_from_this(), _1)); + stream->onElementReceived.connect(boost::bind(&ClientSession::handleElement, shared_from_this(), _1)); + stream->onError.connect(boost::bind(&ClientSession::handleStreamError, shared_from_this(), _1)); + stream->onTLSEncrypted.connect(boost::bind(&ClientSession::handleTLSEncrypted, shared_from_this())); + + assert(state == Initial); + state = WaitingForStreamStart; + sendStreamHeader(); +} + +void ClientSession::sendStreamHeader() { + ProtocolHeader header; + header.setTo(getRemoteJID()); + stream->writeHeader(header); +} + +void ClientSession::sendElement(boost::shared_ptr<Element> element) { + stream->writeElement(element); +} + +void ClientSession::handleStreamStart(const ProtocolHeader&) { + checkState(WaitingForStreamStart); + state = Negotiating; +} + +void ClientSession::handleElement(boost::shared_ptr<Element> element) { + if (getState() == Initialized) { + onElementReceived(element); + } + else if (StreamFeatures* streamFeatures = dynamic_cast<StreamFeatures*>(element.get())) { + if (!checkState(Negotiating)) { + return; + } + + if (streamFeatures->hasStartTLS() && stream->supportsTLSEncryption()) { + state = WaitingForEncrypt; + stream->writeElement(boost::shared_ptr<StartTLSRequest>(new StartTLSRequest())); + } + else if (streamFeatures->hasCompressionMethod("zlib")) { + state = Compressing; + stream->writeElement(boost::shared_ptr<CompressRequest>(new CompressRequest("zlib"))); + } + else if (streamFeatures->hasAuthenticationMechanisms()) { + if (stream->hasTLSCertificate()) { + if (streamFeatures->hasAuthenticationMechanism("EXTERNAL")) { + state = Authenticating; + stream->writeElement(boost::shared_ptr<Element>(new AuthRequest("EXTERNAL", ""))); + } + else { + finishSession(Error::TLSClientCertificateError); + } + } + else if (streamFeatures->hasAuthenticationMechanism("SCRAM-SHA-1")) { + // FIXME: Use a real nonce + authenticator = new SCRAMSHA1ClientAuthenticator("ClientNonce"); + state = WaitingForCredentials; + onNeedCredentials(); + } + else if (streamFeatures->hasAuthenticationMechanism("PLAIN")) { + authenticator = new PLAINClientAuthenticator(); + state = WaitingForCredentials; + onNeedCredentials(); + } + else { + finishSession(Error::NoSupportedAuthMechanismsError); + } + } + else { + // Start the session + stream->setWhitespacePingEnabled(true); + + if (streamFeatures->hasSession()) { + needSessionStart = true; + } + + if (streamFeatures->hasResourceBind()) { + state = BindingResource; + boost::shared_ptr<ResourceBind> resourceBind(new ResourceBind()); + if (!localJID.getResource().isEmpty()) { + resourceBind->setResource(localJID.getResource()); + } + stream->writeElement(IQ::createRequest(IQ::Set, JID(), "session-bind", resourceBind)); + } + else if (needSessionStart) { + sendSessionStart(); + } + else { + state = Initialized; + onInitialized(); + } + } + } + else if (boost::dynamic_pointer_cast<Compressed>(element)) { + checkState(Compressing); + state = WaitingForStreamStart; + stream->addZLibCompression(); + stream->resetXMPPParser(); + sendStreamHeader(); + } + else if (boost::dynamic_pointer_cast<CompressFailure>(element)) { + finishSession(Error::CompressionFailedError); + } + else if (AuthChallenge* challenge = dynamic_cast<AuthChallenge*>(element.get())) { + checkState(Authenticating); + assert(authenticator); + if (authenticator->setChallenge(challenge->getValue())) { + stream->writeElement(boost::shared_ptr<AuthResponse>(new AuthResponse(authenticator->getResponse()))); + } + else { + finishSession(Error::AuthenticationFailedError); + } + } + else if (AuthSuccess* authSuccess = dynamic_cast<AuthSuccess*>(element.get())) { + checkState(Authenticating); + if (authenticator && !authenticator->setChallenge(authSuccess->getValue())) { + finishSession(Error::ServerVerificationFailedError); + } + else { + state = WaitingForStreamStart; + delete authenticator; + authenticator = NULL; + stream->resetXMPPParser(); + sendStreamHeader(); + } + } + else if (dynamic_cast<AuthFailure*>(element.get())) { + delete authenticator; + authenticator = NULL; + finishSession(Error::AuthenticationFailedError); + } + else if (dynamic_cast<TLSProceed*>(element.get())) { + checkState(WaitingForEncrypt); + state = Encrypting; + stream->addTLSEncryption(); + } + else if (dynamic_cast<StartTLSFailure*>(element.get())) { + finishSession(Error::TLSError); + } + else if (IQ* iq = dynamic_cast<IQ*>(element.get())) { + if (state == BindingResource) { + boost::shared_ptr<ResourceBind> resourceBind(iq->getPayload<ResourceBind>()); + if (iq->getType() == IQ::Error && iq->getID() == "session-bind") { + finishSession(Error::ResourceBindError); + } + else if (!resourceBind) { + finishSession(Error::UnexpectedElementError); + } + else if (iq->getType() == IQ::Result) { + localJID = resourceBind->getJID(); + if (!localJID.isValid()) { + finishSession(Error::ResourceBindError); + } + if (needSessionStart) { + sendSessionStart(); + } + else { + state = Initialized; + } + } + else { + finishSession(Error::UnexpectedElementError); + } + } + else if (state == StartingSession) { + if (iq->getType() == IQ::Result) { + state = Initialized; + onInitialized(); + } + else if (iq->getType() == IQ::Error) { + finishSession(Error::SessionStartError); + } + else { + finishSession(Error::UnexpectedElementError); + } + } + else { + finishSession(Error::UnexpectedElementError); + } + } + else { + // FIXME Not correct? + state = Initialized; + onInitialized(); + } +} + +void ClientSession::sendSessionStart() { + state = StartingSession; + stream->writeElement(IQ::createRequest(IQ::Set, JID(), "session-start", boost::shared_ptr<StartSession>(new StartSession()))); +} + +bool ClientSession::checkState(State state) { + if (state != state) { + finishSession(Error::UnexpectedElementError); + return false; + } + return true; +} + +void ClientSession::sendCredentials(const String& password) { + assert(WaitingForCredentials); + state = Authenticating; + authenticator->setCredentials(localJID.getNode(), password); + stream->writeElement(boost::shared_ptr<AuthRequest>(new AuthRequest(authenticator->getName(), authenticator->getResponse()))); +} + +void ClientSession::handleTLSEncrypted() { + checkState(WaitingForEncrypt); + state = WaitingForStreamStart; + stream->resetXMPPParser(); + sendStreamHeader(); +} + +void ClientSession::handleStreamError(boost::shared_ptr<Swift::Error> error) { + finishSession(error); +} + +void ClientSession::finish() { + if (stream->isAvailable()) { + stream->writeFooter(); + } + finishSession(boost::shared_ptr<Error>()); +} + +void ClientSession::finishSession(Error::Type error) { + finishSession(boost::shared_ptr<Swift::ClientSession::Error>(new Swift::ClientSession::Error(error))); +} + +void ClientSession::finishSession(boost::shared_ptr<Swift::Error> error) { + state = Finished; + stream->setWhitespacePingEnabled(false); + onFinished(error); +} + + +} diff --git a/Swiften/Client/ClientSession.h b/Swiften/Client/ClientSession.h new file mode 100644 index 0000000..685672e --- /dev/null +++ b/Swiften/Client/ClientSession.h @@ -0,0 +1,100 @@ +#pragma once + +#include <boost/signal.hpp> +#include <boost/shared_ptr.hpp> +#include <boost/enable_shared_from_this.hpp> + +#include "Swiften/Base/Error.h" +#include "Swiften/Session/SessionStream.h" +#include "Swiften/Session/BasicSessionStream.h" +#include "Swiften/Base/String.h" +#include "Swiften/JID/JID.h" +#include "Swiften/Elements/Element.h" + +namespace Swift { + class ClientAuthenticator; + + class ClientSession : public boost::enable_shared_from_this<ClientSession> { + public: + enum State { + Initial, + WaitingForStreamStart, + Negotiating, + Compressing, + WaitingForEncrypt, + Encrypting, + WaitingForCredentials, + Authenticating, + BindingResource, + StartingSession, + Initialized, + Finished + }; + + struct Error : public Swift::Error { + enum Type { + AuthenticationFailedError, + CompressionFailedError, + ServerVerificationFailedError, + NoSupportedAuthMechanismsError, + UnexpectedElementError, + ResourceBindError, + SessionStartError, + TLSClientCertificateError, + TLSError, + } type; + Error(Type type) : type(type) {} + }; + + ~ClientSession(); + static boost::shared_ptr<ClientSession> create(const JID& jid, boost::shared_ptr<SessionStream> stream) { + return boost::shared_ptr<ClientSession>(new ClientSession(jid, stream)); + } + + State getState() const { + return state; + } + + void start(); + void finish(); + + void sendCredentials(const String& password); + void sendElement(boost::shared_ptr<Element> element); + + public: + boost::signal<void ()> onNeedCredentials; + boost::signal<void ()> onInitialized; + boost::signal<void (boost::shared_ptr<Swift::Error>)> onFinished; + boost::signal<void (boost::shared_ptr<Element>)> onElementReceived; + + private: + ClientSession( + const JID& jid, + boost::shared_ptr<SessionStream>); + + void finishSession(Error::Type error); + void finishSession(boost::shared_ptr<Swift::Error> error); + + JID getRemoteJID() const { + return JID("", localJID.getDomain()); + } + + void sendStreamHeader(); + void sendSessionStart(); + + void handleElement(boost::shared_ptr<Element>); + void handleStreamStart(const ProtocolHeader&); + void handleStreamError(boost::shared_ptr<Swift::Error>); + + void handleTLSEncrypted(); + + bool checkState(State); + + private: + JID localJID; + State state; + boost::shared_ptr<SessionStream> stream; + bool needSessionStart; + ClientAuthenticator* authenticator; + }; +} diff --git a/Swiften/Client/ClientXMLTracer.h b/Swiften/Client/ClientXMLTracer.h new file mode 100644 index 0000000..85224c8 --- /dev/null +++ b/Swiften/Client/ClientXMLTracer.h @@ -0,0 +1,26 @@ +#pragma once + +#include "Swiften/Client/Client.h" + +namespace Swift { + class ClientXMLTracer { + public: + ClientXMLTracer(Client* client) { + client->onDataRead.connect(boost::bind(&ClientXMLTracer::printData, '<', _1)); + client->onDataWritten.connect(boost::bind(&ClientXMLTracer::printData, '>', _1)); + } + + private: + static void printData(char direction, const String& data) { + printLine(direction); + std::cerr << data << std::endl; + } + + static void printLine(char c) { + for (unsigned int i = 0; i < 80; ++i) { + std::cerr << c; + } + std::cerr << std::endl; + } + }; +} diff --git a/Swiften/Client/DummyStanzaChannel.h b/Swiften/Client/DummyStanzaChannel.h new file mode 100644 index 0000000..d618167 --- /dev/null +++ b/Swiften/Client/DummyStanzaChannel.h @@ -0,0 +1,38 @@ +#pragma once + +#include <vector> + +#include "Swiften/Client/StanzaChannel.h" + +namespace Swift { + class DummyStanzaChannel : public StanzaChannel { + public: + DummyStanzaChannel() {} + + virtual void sendStanza(boost::shared_ptr<Stanza> stanza) { + sentStanzas.push_back(stanza); + } + + virtual void sendIQ(boost::shared_ptr<IQ> iq) { + sentStanzas.push_back(iq); + } + + virtual void sendMessage(boost::shared_ptr<Message> message) { + sentStanzas.push_back(message); + } + + virtual void sendPresence(boost::shared_ptr<Presence> presence) { + sentStanzas.push_back(presence); + } + + virtual String getNewIQID() { + return "test-id"; + } + + virtual bool isAvailable() { + return true; + } + + std::vector<boost::shared_ptr<Stanza> > sentStanzas; + }; +} diff --git a/Swiften/Client/StanzaChannel.h b/Swiften/Client/StanzaChannel.h new file mode 100644 index 0000000..a0e291d --- /dev/null +++ b/Swiften/Client/StanzaChannel.h @@ -0,0 +1,20 @@ +#pragma once + +#include <boost/signal.hpp> +#include <boost/shared_ptr.hpp> + +#include "Swiften/Queries/IQChannel.h" +#include "Swiften/Elements/Message.h" +#include "Swiften/Elements/Presence.h" + +namespace Swift { + class StanzaChannel : public IQChannel { + public: + virtual void sendMessage(boost::shared_ptr<Message>) = 0; + virtual void sendPresence(boost::shared_ptr<Presence>) = 0; + virtual bool isAvailable() = 0; + + boost::signal<void (boost::shared_ptr<Message>)> onMessageReceived; + boost::signal<void (boost::shared_ptr<Presence>) > onPresenceReceived; + }; +} diff --git a/Swiften/Client/UnitTest/ClientSessionTest.cpp b/Swiften/Client/UnitTest/ClientSessionTest.cpp new file mode 100644 index 0000000..e035ba3 --- /dev/null +++ b/Swiften/Client/UnitTest/ClientSessionTest.cpp @@ -0,0 +1,498 @@ +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> +#include <deque> +#include <boost/bind.hpp> +#include <boost/optional.hpp> + +#include "Swiften/Session/SessionStream.h" +#include "Swiften/Client/ClientSession.h" +#include "Swiften/Elements/StartTLSRequest.h" +#include "Swiften/Elements/StreamFeatures.h" +#include "Swiften/Elements/TLSProceed.h" +#include "Swiften/Elements/StartTLSFailure.h" +#include "Swiften/Elements/AuthRequest.h" +#include "Swiften/Elements/AuthSuccess.h" +#include "Swiften/Elements/AuthFailure.h" + +using namespace Swift; + +class ClientSessionTest : public CppUnit::TestFixture { + CPPUNIT_TEST_SUITE(ClientSessionTest); + CPPUNIT_TEST(testStart_Error); + CPPUNIT_TEST(testStartTLS); + CPPUNIT_TEST(testStartTLS_ServerError); + CPPUNIT_TEST(testStartTLS_ConnectError); + CPPUNIT_TEST(testAuthenticate); + CPPUNIT_TEST(testAuthenticate_Unauthorized); + CPPUNIT_TEST(testAuthenticate_NoValidAuthMechanisms); + /* + CPPUNIT_TEST(testResourceBind); + CPPUNIT_TEST(testResourceBind_ChangeResource); + CPPUNIT_TEST(testResourceBind_EmptyResource); + CPPUNIT_TEST(testResourceBind_Error); + CPPUNIT_TEST(testSessionStart); + CPPUNIT_TEST(testSessionStart_Error); + CPPUNIT_TEST(testSessionStart_AfterResourceBind); + CPPUNIT_TEST(testWhitespacePing); + CPPUNIT_TEST(testReceiveElementAfterSessionStarted); + CPPUNIT_TEST(testSendElement); + */ + CPPUNIT_TEST_SUITE_END(); + + public: + void setUp() { + server = boost::shared_ptr<MockSessionStream>(new MockSessionStream()); + sessionFinishedReceived = false; + needCredentials = false; + } + + void testStart_Error() { + boost::shared_ptr<ClientSession> session(createSession()); + session->start(); + server->breakConnection(); + + CPPUNIT_ASSERT_EQUAL(ClientSession::Finished, session->getState()); + CPPUNIT_ASSERT(sessionFinishedReceived); + CPPUNIT_ASSERT(sessionFinishedError); + } + + void testStartTLS() { + boost::shared_ptr<ClientSession> session(createSession()); + session->start(); + server->receiveStreamStart(); + server->sendStreamStart(); + server->sendStreamFeaturesWithStartTLS(); + server->receiveStartTLS(); + CPPUNIT_ASSERT(!server->tlsEncrypted); + server->sendTLSProceed(); + CPPUNIT_ASSERT(server->tlsEncrypted); + server->onTLSEncrypted(); + server->receiveStreamStart(); + server->sendStreamStart(); + } + + void testStartTLS_ServerError() { + boost::shared_ptr<ClientSession> session(createSession()); + session->start(); + server->receiveStreamStart(); + server->sendStreamStart(); + server->sendStreamFeaturesWithStartTLS(); + server->receiveStartTLS(); + server->sendTLSFailure(); + + CPPUNIT_ASSERT(!server->tlsEncrypted); + CPPUNIT_ASSERT_EQUAL(ClientSession::Finished, session->getState()); + CPPUNIT_ASSERT(sessionFinishedReceived); + CPPUNIT_ASSERT(sessionFinishedError); + } + + void testStartTLS_ConnectError() { + boost::shared_ptr<ClientSession> session(createSession()); + session->start(); + server->receiveStreamStart(); + server->sendStreamStart(); + server->sendStreamFeaturesWithStartTLS(); + server->receiveStartTLS(); + server->sendTLSProceed(); + server->breakTLS(); + + CPPUNIT_ASSERT_EQUAL(ClientSession::Finished, session->getState()); + CPPUNIT_ASSERT(sessionFinishedReceived); + CPPUNIT_ASSERT(sessionFinishedError); + } + + void testAuthenticate() { + boost::shared_ptr<ClientSession> session(createSession()); + session->start(); + server->receiveStreamStart(); + server->sendStreamStart(); + server->sendStreamFeaturesWithPLAINAuthentication(); + CPPUNIT_ASSERT(needCredentials); + CPPUNIT_ASSERT_EQUAL(ClientSession::WaitingForCredentials, session->getState()); + session->sendCredentials("mypass"); + server->receiveAuthRequest("PLAIN"); + server->sendAuthSuccess(); + server->receiveStreamStart(); + } + + void testAuthenticate_Unauthorized() { + boost::shared_ptr<ClientSession> session(createSession()); + session->start(); + server->receiveStreamStart(); + server->sendStreamStart(); + server->sendStreamFeaturesWithPLAINAuthentication(); + CPPUNIT_ASSERT(needCredentials); + CPPUNIT_ASSERT_EQUAL(ClientSession::WaitingForCredentials, session->getState()); + session->sendCredentials("mypass"); + server->receiveAuthRequest("PLAIN"); + server->sendAuthFailure(); + + CPPUNIT_ASSERT_EQUAL(ClientSession::Finished, session->getState()); + CPPUNIT_ASSERT(sessionFinishedReceived); + CPPUNIT_ASSERT(sessionFinishedError); + } + + void testAuthenticate_NoValidAuthMechanisms() { + boost::shared_ptr<ClientSession> session(createSession()); + session->start(); + server->receiveStreamStart(); + server->sendStreamStart(); + server->sendStreamFeaturesWithUnknownAuthentication(); + + CPPUNIT_ASSERT_EQUAL(ClientSession::Finished, session->getState()); + CPPUNIT_ASSERT(sessionFinishedReceived); + CPPUNIT_ASSERT(sessionFinishedError); + } + + private: + boost::shared_ptr<ClientSession> createSession() { + boost::shared_ptr<ClientSession> session = ClientSession::create(JID("me@foo.com"), server); + session->onFinished.connect(boost::bind(&ClientSessionTest::handleSessionFinished, this, _1)); + session->onNeedCredentials.connect(boost::bind(&ClientSessionTest::handleSessionNeedCredentials, this)); + return session; + } + + void handleSessionFinished(boost::shared_ptr<Error> error) { + sessionFinishedReceived = true; + sessionFinishedError = error; + } + + void handleSessionNeedCredentials() { + needCredentials = true; + } + + class MockSessionStream : public SessionStream { + public: + struct Event { + Event(boost::shared_ptr<Element> element) : element(element), footer(false) {} + Event(const ProtocolHeader& header) : header(header), footer(false) {} + Event() : footer(true) {} + + boost::shared_ptr<Element> element; + boost::optional<ProtocolHeader> header; + bool footer; + }; + + MockSessionStream() : available(true), canTLSEncrypt(true), tlsEncrypted(false), compressed(false), whitespacePingEnabled(false), resetCount(0) { + } + + virtual bool isAvailable() { + return available; + } + + virtual void writeHeader(const ProtocolHeader& header) { + receivedEvents.push_back(Event(header)); + } + + virtual void writeFooter() { + receivedEvents.push_back(Event()); + } + + virtual void writeElement(boost::shared_ptr<Element> element) { + receivedEvents.push_back(Event(element)); + } + + virtual bool supportsTLSEncryption() { + return canTLSEncrypt; + } + + virtual void addTLSEncryption() { + tlsEncrypted = true; + } + + virtual void addZLibCompression() { + compressed = true; + } + + virtual void setWhitespacePingEnabled(bool enabled) { + whitespacePingEnabled = enabled; + } + + virtual void resetXMPPParser() { + resetCount++; + } + + void breakConnection() { + onError(boost::shared_ptr<SessionStream::Error>(new SessionStream::Error(SessionStream::Error::ConnectionReadError))); + } + + void breakTLS() { + onError(boost::shared_ptr<SessionStream::Error>(new SessionStream::Error(SessionStream::Error::TLSError))); + } + + + void sendStreamStart() { + ProtocolHeader header; + header.setTo("foo.com"); + return onStreamStartReceived(header); + } + + void sendStreamFeaturesWithStartTLS() { + boost::shared_ptr<StreamFeatures> streamFeatures(new StreamFeatures()); + streamFeatures->setHasStartTLS(); + onElementReceived(streamFeatures); + } + + void sendTLSProceed() { + onElementReceived(boost::shared_ptr<TLSProceed>(new TLSProceed())); + } + + void sendTLSFailure() { + onElementReceived(boost::shared_ptr<StartTLSFailure>(new StartTLSFailure())); + } + + void sendStreamFeaturesWithPLAINAuthentication() { + boost::shared_ptr<StreamFeatures> streamFeatures(new StreamFeatures()); + streamFeatures->addAuthenticationMechanism("PLAIN"); + onElementReceived(streamFeatures); + } + + void sendStreamFeaturesWithUnknownAuthentication() { + boost::shared_ptr<StreamFeatures> streamFeatures(new StreamFeatures()); + streamFeatures->addAuthenticationMechanism("UNKNOWN"); + onElementReceived(streamFeatures); + } + + void sendAuthSuccess() { + onElementReceived(boost::shared_ptr<AuthSuccess>(new AuthSuccess())); + } + + void sendAuthFailure() { + onElementReceived(boost::shared_ptr<AuthFailure>(new AuthFailure())); + } + + void receiveStreamStart() { + Event event = popEvent(); + CPPUNIT_ASSERT(event.header); + } + + void receiveStartTLS() { + Event event = popEvent(); + CPPUNIT_ASSERT(event.element); + CPPUNIT_ASSERT(boost::dynamic_pointer_cast<StartTLSRequest>(event.element)); + } + + void receiveAuthRequest(const String& mech) { + Event event = popEvent(); + CPPUNIT_ASSERT(event.element); + boost::shared_ptr<AuthRequest> request(boost::dynamic_pointer_cast<AuthRequest>(event.element)); + CPPUNIT_ASSERT(request); + CPPUNIT_ASSERT_EQUAL(mech, request->getMechanism()); + } + + Event popEvent() { + CPPUNIT_ASSERT(receivedEvents.size() > 0); + Event event = receivedEvents.front(); + receivedEvents.pop_front(); + return event; + } + + bool available; + bool canTLSEncrypt; + bool tlsEncrypted; + bool compressed; + bool whitespacePingEnabled; + int resetCount; + std::deque<Event> receivedEvents; + }; + + boost::shared_ptr<MockSessionStream> server; + bool sessionFinishedReceived; + bool needCredentials; + boost::shared_ptr<Error> sessionFinishedError; +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(ClientSessionTest); + +#if 0 + void testAuthenticate() { + boost::shared_ptr<MockSession> session(createSession("me@foo.com/Bar")); + session->onNeedCredentials.connect(boost::bind(&ClientSessionTest::setNeedCredentials, this)); + getMockServer()->expectStreamStart(); + getMockServer()->sendStreamStart(); + getMockServer()->sendStreamFeaturesWithAuthentication(); + session->startSession(); + processEvents(); + CPPUNIT_ASSERT_EQUAL(ClientSession::WaitingForCredentials, session->getState()); + CPPUNIT_ASSERT(needCredentials_); + + getMockServer()->expectAuth("me", "mypass"); + getMockServer()->sendAuthSuccess(); + getMockServer()->expectStreamStart(); + getMockServer()->sendStreamStart(); + session->sendCredentials("mypass"); + CPPUNIT_ASSERT_EQUAL(ClientSession::Authenticating, session->getState()); + processEvents(); + CPPUNIT_ASSERT_EQUAL(ClientSession::Negotiating, session->getState()); + } + + void testAuthenticate_Unauthorized() { + boost::shared_ptr<MockSession> session(createSession("me@foo.com/Bar")); + getMockServer()->expectStreamStart(); + getMockServer()->sendStreamStart(); + getMockServer()->sendStreamFeaturesWithAuthentication(); + session->startSession(); + processEvents(); + + getMockServer()->expectAuth("me", "mypass"); + getMockServer()->sendAuthFailure(); + session->sendCredentials("mypass"); + processEvents(); + + CPPUNIT_ASSERT_EQUAL(ClientSession::Error, session->getState()); + CPPUNIT_ASSERT_EQUAL(ClientSession::AuthenticationFailedError, *session->getError()); + } + + void testAuthenticate_NoValidAuthMechanisms() { + boost::shared_ptr<MockSession> session(createSession("me@foo.com/Bar")); + getMockServer()->expectStreamStart(); + getMockServer()->sendStreamStart(); + getMockServer()->sendStreamFeaturesWithUnsupportedAuthentication(); + session->startSession(); + processEvents(); + + CPPUNIT_ASSERT_EQUAL(ClientSession::Error, session->getState()); + CPPUNIT_ASSERT_EQUAL(ClientSession::NoSupportedAuthMechanismsError, *session->getError()); + } + + void testResourceBind() { + boost::shared_ptr<MockSession> session(createSession("me@foo.com/Bar")); + getMockServer()->expectStreamStart(); + getMockServer()->sendStreamStart(); + getMockServer()->sendStreamFeaturesWithResourceBind(); + getMockServer()->expectResourceBind("Bar", "session-bind"); + // FIXME: Check CPPUNIT_ASSERT_EQUAL(ClientSession::BindingResource, session->getState()); + getMockServer()->sendResourceBindResponse("me@foo.com/Bar", "session-bind"); + session->startSession(); + + processEvents(); + + CPPUNIT_ASSERT_EQUAL(ClientSession::SessionStarted, session->getState()); + CPPUNIT_ASSERT_EQUAL(JID("me@foo.com/Bar"), session->getLocalJID()); + } + + void testResourceBind_ChangeResource() { + boost::shared_ptr<MockSession> session(createSession("me@foo.com/Bar")); + getMockServer()->expectStreamStart(); + getMockServer()->sendStreamStart(); + getMockServer()->sendStreamFeaturesWithResourceBind(); + getMockServer()->expectResourceBind("Bar", "session-bind"); + getMockServer()->sendResourceBindResponse("me@foo.com/Bar123", "session-bind"); + session->startSession(); + processEvents(); + + CPPUNIT_ASSERT_EQUAL(ClientSession::SessionStarted, session->getState()); + CPPUNIT_ASSERT_EQUAL(JID("me@foo.com/Bar123"), session->getLocalJID()); + } + + void testResourceBind_EmptyResource() { + boost::shared_ptr<MockSession> session(createSession("me@foo.com")); + getMockServer()->expectStreamStart(); + getMockServer()->sendStreamStart(); + getMockServer()->sendStreamFeaturesWithResourceBind(); + getMockServer()->expectResourceBind("", "session-bind"); + getMockServer()->sendResourceBindResponse("me@foo.com/NewResource", "session-bind"); + session->startSession(); + processEvents(); + + CPPUNIT_ASSERT_EQUAL(ClientSession::SessionStarted, session->getState()); + CPPUNIT_ASSERT_EQUAL(JID("me@foo.com/NewResource"), session->getLocalJID()); + } + + void testResourceBind_Error() { + boost::shared_ptr<MockSession> session(createSession("me@foo.com")); + getMockServer()->expectStreamStart(); + getMockServer()->sendStreamStart(); + getMockServer()->sendStreamFeaturesWithResourceBind(); + getMockServer()->expectResourceBind("", "session-bind"); + getMockServer()->sendError("session-bind"); + session->startSession(); + processEvents(); + + CPPUNIT_ASSERT_EQUAL(ClientSession::Error, session->getState()); + CPPUNIT_ASSERT_EQUAL(ClientSession::ResourceBindError, *session->getError()); + } + + void testSessionStart() { + boost::shared_ptr<MockSession> session(createSession("me@foo.com/Bar")); + session->onSessionStarted.connect(boost::bind(&ClientSessionTest::setSessionStarted, this)); + getMockServer()->expectStreamStart(); + getMockServer()->sendStreamStart(); + getMockServer()->sendStreamFeaturesWithSession(); + getMockServer()->expectSessionStart("session-start"); + // FIXME: Check CPPUNIT_ASSERT_EQUAL(ClientSession::StartingSession, session->getState()); + getMockServer()->sendSessionStartResponse("session-start"); + session->startSession(); + processEvents(); + + CPPUNIT_ASSERT_EQUAL(ClientSession::SessionStarted, session->getState()); + CPPUNIT_ASSERT(sessionStarted_); + } + + void testSessionStart_Error() { + boost::shared_ptr<MockSession> session(createSession("me@foo.com/Bar")); + getMockServer()->expectStreamStart(); + getMockServer()->sendStreamStart(); + getMockServer()->sendStreamFeaturesWithSession(); + getMockServer()->expectSessionStart("session-start"); + getMockServer()->sendError("session-start"); + session->startSession(); + processEvents(); + + CPPUNIT_ASSERT_EQUAL(ClientSession::Error, session->getState()); + CPPUNIT_ASSERT_EQUAL(ClientSession::SessionStartError, *session->getError()); + } + + void testSessionStart_AfterResourceBind() { + boost::shared_ptr<MockSession> session(createSession("me@foo.com/Bar")); + session->onSessionStarted.connect(boost::bind(&ClientSessionTest::setSessionStarted, this)); + getMockServer()->expectStreamStart(); + getMockServer()->sendStreamStart(); + getMockServer()->sendStreamFeaturesWithResourceBindAndSession(); + getMockServer()->expectResourceBind("Bar", "session-bind"); + getMockServer()->sendResourceBindResponse("me@foo.com/Bar", "session-bind"); + getMockServer()->expectSessionStart("session-start"); + getMockServer()->sendSessionStartResponse("session-start"); + session->startSession(); + processEvents(); + + CPPUNIT_ASSERT_EQUAL(ClientSession::SessionStarted, session->getState()); + CPPUNIT_ASSERT(sessionStarted_); + } + + void testWhitespacePing() { + boost::shared_ptr<MockSession> session(createSession("me@foo.com/Bar")); + getMockServer()->expectStreamStart(); + getMockServer()->sendStreamStart(); + getMockServer()->sendStreamFeatures(); + session->startSession(); + processEvents(); + CPPUNIT_ASSERT(session->getWhitespacePingLayer()); + } + + void testReceiveElementAfterSessionStarted() { + boost::shared_ptr<MockSession> session(createSession("me@foo.com/Bar")); + getMockServer()->expectStreamStart(); + getMockServer()->sendStreamStart(); + getMockServer()->sendStreamFeatures(); + session->startSession(); + processEvents(); + + getMockServer()->expectMessage(); + session->sendElement(boost::shared_ptr<Message>(new Message())); + } + + void testSendElement() { + boost::shared_ptr<MockSession> session(createSession("me@foo.com/Bar")); + session->onElementReceived.connect(boost::bind(&ClientSessionTest::addReceivedElement, this, _1)); + getMockServer()->expectStreamStart(); + getMockServer()->sendStreamStart(); + getMockServer()->sendStreamFeatures(); + getMockServer()->sendMessage(); + session->startSession(); + processEvents(); + + CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(receivedElements_.size())); + CPPUNIT_ASSERT(boost::dynamic_pointer_cast<Message>(receivedElements_[0])); + } +#endif diff --git a/Swiften/Compress/UnitTest/ZLibCompressorTest.cpp b/Swiften/Compress/UnitTest/ZLibCompressorTest.cpp new file mode 100644 index 0000000..7235f8e --- /dev/null +++ b/Swiften/Compress/UnitTest/ZLibCompressorTest.cpp @@ -0,0 +1,35 @@ +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> + +#include "Swiften/Compress/ZLibCompressor.h" + +using namespace Swift; + + +class ZLibCompressorTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(ZLibCompressorTest); + CPPUNIT_TEST(testProcess); + CPPUNIT_TEST(testProcess_Twice); + CPPUNIT_TEST_SUITE_END(); + + public: + ZLibCompressorTest() {} + + void testProcess() { + ZLibCompressor testling; + ByteArray result = testling.process("foo"); + + CPPUNIT_ASSERT_EQUAL(ByteArray("\x78\xda\x4a\xcb\xcf\x07\x00\x00\x00\xff\xff", 11), result); + } + + void testProcess_Twice() { + ZLibCompressor testling; + testling.process("foo"); + ByteArray result = testling.process("bar"); + + CPPUNIT_ASSERT_EQUAL(ByteArray("\x4a\x4a\x2c\x02\x00\x00\x00\xff\xff",9), result); + } +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(ZLibCompressorTest); diff --git a/Swiften/Compress/UnitTest/ZLibDecompressorTest.cpp b/Swiften/Compress/UnitTest/ZLibDecompressorTest.cpp new file mode 100644 index 0000000..871a630 --- /dev/null +++ b/Swiften/Compress/UnitTest/ZLibDecompressorTest.cpp @@ -0,0 +1,71 @@ +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> + +#include "Swiften/Compress/ZLibDecompressor.h" +#include "Swiften/Compress/ZLibCompressor.h" +#include "Swiften/Compress/ZLibException.h" + +using namespace Swift; + + +class ZLibDecompressorTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(ZLibDecompressorTest); + CPPUNIT_TEST(testProcess); + CPPUNIT_TEST(testProcess_Twice); + CPPUNIT_TEST(testProcess_Invalid); + CPPUNIT_TEST(testProcess_Huge); + CPPUNIT_TEST(testProcess_ChunkSize); + CPPUNIT_TEST_SUITE_END(); + + public: + ZLibDecompressorTest() {} + + void testProcess() { + ZLibDecompressor testling; + ByteArray result = testling.process(ByteArray("\x78\xda\x4a\xcb\xcf\x07\x00\x00\x00\xff\xff", 11)); + + CPPUNIT_ASSERT_EQUAL(ByteArray("foo"), result); + } + + void testProcess_Twice() { + ZLibDecompressor testling; + testling.process(ByteArray("\x78\xda\x4a\xcb\xcf\x07\x00\x00\x00\xff\xff", 11)); + ByteArray result = testling.process(ByteArray("\x4a\x4a\x2c\x02\x00\x00\x00\xff\xff", 9)); + + CPPUNIT_ASSERT_EQUAL(ByteArray("bar"), result); + } + + void testProcess_Invalid() { + ZLibDecompressor testling; + CPPUNIT_ASSERT_THROW(testling.process(ByteArray("invalid")), ZLibException); + } + + void testProcess_Huge() { + std::vector<char> data; + data.reserve(2048); + for (unsigned int i = 0; i < 2048; ++i) { + data.push_back(static_cast<char>(i)); + } + ByteArray original(&data[0], data.size()); + ByteArray compressed = ZLibCompressor().process(original); + ByteArray decompressed = ZLibDecompressor().process(compressed); + + CPPUNIT_ASSERT_EQUAL(original, decompressed); + } + + void testProcess_ChunkSize() { + std::vector<char> data; + data.reserve(1024); + for (unsigned int i = 0; i < 1024; ++i) { + data.push_back(static_cast<char>(i)); + } + ByteArray original(&data[0], data.size()); + ByteArray compressed = ZLibCompressor().process(original); + ByteArray decompressed = ZLibDecompressor().process(compressed); + + CPPUNIT_ASSERT_EQUAL(original, decompressed); + } +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(ZLibDecompressorTest); diff --git a/Swiften/Compress/ZLibCodecompressor.cpp b/Swiften/Compress/ZLibCodecompressor.cpp new file mode 100644 index 0000000..a14f09d --- /dev/null +++ b/Swiften/Compress/ZLibCodecompressor.cpp @@ -0,0 +1,43 @@ +#include "Swiften/Compress/ZLibCodecompressor.h" + +#include <cassert> + +#include "Swiften/Compress/ZLibException.h" + +namespace Swift { + +static const int CHUNK_SIZE = 1024; // If you change this, also change the unittest + +ZLibCodecompressor::ZLibCodecompressor() { + stream_.zalloc = Z_NULL; + stream_.zfree = Z_NULL; + stream_.opaque = Z_NULL; +} + +ZLibCodecompressor::~ZLibCodecompressor() { +} + +ByteArray ZLibCodecompressor::process(const ByteArray& input) { + ByteArray output; + stream_.avail_in = input.getSize(); + stream_.next_in = reinterpret_cast<Bytef*>(const_cast<char*>(input.getData())); + int outputPosition = 0; + do { + output.resize(outputPosition + CHUNK_SIZE); + stream_.avail_out = CHUNK_SIZE; + stream_.next_out = reinterpret_cast<Bytef*>(output.getData() + outputPosition); + int result = processZStream(); + if (result != Z_OK && result != Z_BUF_ERROR) { + throw ZLibException(/* stream_.msg */); + } + outputPosition += CHUNK_SIZE; + } + while (stream_.avail_out == 0); + if (stream_.avail_in != 0) { + throw ZLibException(); + } + output.resize(outputPosition - stream_.avail_out); + return output; +} + +} diff --git a/Swiften/Compress/ZLibCodecompressor.h b/Swiften/Compress/ZLibCodecompressor.h new file mode 100644 index 0000000..dd032fa --- /dev/null +++ b/Swiften/Compress/ZLibCodecompressor.h @@ -0,0 +1,22 @@ +#ifndef SWIFTEN_ZLibCodecompressor_H +#define SWIFTEN_ZLibCodecompressor_H + +#include <zlib.h> + +#include "Swiften/Base/ByteArray.h" + +namespace Swift { + class ZLibCodecompressor { + public: + ZLibCodecompressor(); + virtual ~ZLibCodecompressor(); + + ByteArray process(const ByteArray& data); + virtual int processZStream() = 0; + + protected: + z_stream stream_; + }; +} + +#endif diff --git a/Swiften/Compress/ZLibCompressor.h b/Swiften/Compress/ZLibCompressor.h new file mode 100644 index 0000000..b5bace6 --- /dev/null +++ b/Swiften/Compress/ZLibCompressor.h @@ -0,0 +1,31 @@ +#ifndef SWIFTEN_ZLibCompressor_H +#define SWIFTEN_ZLibCompressor_H + +#include <cassert> + +#include "Swiften/Compress/ZLibCodecompressor.h" +#include "Swiften/Base/ByteArray.h" + +namespace Swift { + class ZLibCompressor : public ZLibCodecompressor { + public: + ZLibCompressor() { + int result = deflateInit(&stream_, COMPRESSION_LEVEL); + assert(result == Z_OK); + (void) result; + } + + ~ZLibCompressor() { + deflateEnd(&stream_); + } + + virtual int processZStream() { + return deflate(&stream_, Z_SYNC_FLUSH); + } + + private: + static const int COMPRESSION_LEVEL = 9; + }; +} + +#endif diff --git a/Swiften/Compress/ZLibDecompressor.h b/Swiften/Compress/ZLibDecompressor.h new file mode 100644 index 0000000..808feb2 --- /dev/null +++ b/Swiften/Compress/ZLibDecompressor.h @@ -0,0 +1,28 @@ +#ifndef SWIFTEN_ZLibDecompressor_H +#define SWIFTEN_ZLibDecompressor_H + +#include <cassert> + +#include "Swiften/Compress/ZLibCodecompressor.h" +#include "Swiften/Base/ByteArray.h" + +namespace Swift { + class ZLibDecompressor : public ZLibCodecompressor { + public: + ZLibDecompressor() { + int result = inflateInit(&stream_); + assert(result == Z_OK); + (void) result; + } + + ~ZLibDecompressor() { + inflateEnd(&stream_); + } + + virtual int processZStream() { + return inflate(&stream_, Z_SYNC_FLUSH); + } + }; +} + +#endif diff --git a/Swiften/Compress/ZLibException.h b/Swiften/Compress/ZLibException.h new file mode 100644 index 0000000..f16b4ed --- /dev/null +++ b/Swiften/Compress/ZLibException.h @@ -0,0 +1,11 @@ +#ifndef SWIFTEN_ZLIBEXCEPTION_H +#define SWIFTEN_ZLIBEXCEPTION_H + +namespace Swift { + class ZLibException { + public: + ZLibException() {} + }; +} + +#endif diff --git a/Swiften/Disco/CapsInfoGenerator.cpp b/Swiften/Disco/CapsInfoGenerator.cpp new file mode 100644 index 0000000..8d9e407 --- /dev/null +++ b/Swiften/Disco/CapsInfoGenerator.cpp @@ -0,0 +1,34 @@ +#include "Swiften/Disco/CapsInfoGenerator.h" + +#include <algorithm> + +#include "Swiften/Base/foreach.h" +#include "Swiften/Elements/DiscoInfo.h" +#include "Swiften/StringCodecs/SHA1.h" +#include "Swiften/StringCodecs/Base64.h" + +namespace Swift { + +CapsInfoGenerator::CapsInfoGenerator(const String& node) : node_(node) { +} + +CapsInfo CapsInfoGenerator::generateCapsInfo(const DiscoInfo& discoInfo) const { + String serializedCaps; + + std::vector<DiscoInfo::Identity> identities(discoInfo.getIdentities()); + std::sort(identities.begin(), identities.end()); + foreach (const DiscoInfo::Identity& identity, identities) { + serializedCaps += identity.getCategory() + "/" + identity.getType() + "/" + identity.getLanguage() + "/" + identity.getName() + "<"; + } + + std::vector<String> features(discoInfo.getFeatures()); + std::sort(features.begin(), features.end()); + foreach (const String& feature, features) { + serializedCaps += feature + "<"; + } + + String version(Base64::encode(SHA1::getHash(serializedCaps))); + return CapsInfo(node_, version, "sha-1"); +} + +} diff --git a/Swiften/Disco/CapsInfoGenerator.h b/Swiften/Disco/CapsInfoGenerator.h new file mode 100644 index 0000000..66949ab --- /dev/null +++ b/Swiften/Disco/CapsInfoGenerator.h @@ -0,0 +1,21 @@ +#ifndef SWIFTEN_CapsInfoGenerator_H +#define SWIFTEN_CapsInfoGenerator_H + +#include "Swiften/Base/String.h" +#include "Swiften/Elements/CapsInfo.h" + +namespace Swift { + class DiscoInfo; + + class CapsInfoGenerator { + public: + CapsInfoGenerator(const String& node); + + CapsInfo generateCapsInfo(const DiscoInfo& discoInfo) const; + + private: + String node_; + }; +} + +#endif diff --git a/Swiften/Disco/UnitTest/CapsInfoGeneratorTest.cpp b/Swiften/Disco/UnitTest/CapsInfoGeneratorTest.cpp new file mode 100644 index 0000000..94b9913 --- /dev/null +++ b/Swiften/Disco/UnitTest/CapsInfoGeneratorTest.cpp @@ -0,0 +1,35 @@ +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> + +#include "Swiften/Elements/DiscoInfo.h" +#include "Swiften/Disco/CapsInfoGenerator.h" + +using namespace Swift; + +class CapsInfoGeneratorTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(CapsInfoGeneratorTest); + CPPUNIT_TEST(testGenerate_XEP0115SimpleExample); + CPPUNIT_TEST_SUITE_END(); + + public: + CapsInfoGeneratorTest() {} + + void testGenerate_XEP0115SimpleExample() { + DiscoInfo discoInfo; + discoInfo.addIdentity(DiscoInfo::Identity("Exodus 0.9.1", "client", "pc")); + discoInfo.addFeature("http://jabber.org/protocol/disco#items"); + discoInfo.addFeature("http://jabber.org/protocol/caps"); + discoInfo.addFeature("http://jabber.org/protocol/disco#info"); + discoInfo.addFeature("http://jabber.org/protocol/muc"); + + CapsInfoGenerator testling("http://code.google.com/p/exodus"); + CapsInfo result = testling.generateCapsInfo(discoInfo); + + CPPUNIT_ASSERT_EQUAL(String("http://code.google.com/p/exodus"), result.getNode()); + CPPUNIT_ASSERT_EQUAL(String("sha-1"), result.getHash()); + CPPUNIT_ASSERT_EQUAL(String("QgayPKawpkPSDYmwT/WM94uAlu0="), result.getVersion()); + } +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(CapsInfoGeneratorTest); diff --git a/Swiften/Elements/AuthChallenge.h b/Swiften/Elements/AuthChallenge.h new file mode 100644 index 0000000..b8a9297 --- /dev/null +++ b/Swiften/Elements/AuthChallenge.h @@ -0,0 +1,23 @@ +#pragma once + +#include "Swiften/Base/ByteArray.h" +#include "Swiften/Elements/Element.h" + +namespace Swift { + class AuthChallenge : public Element { + public: + AuthChallenge(const ByteArray& value = "") : value(value) { + } + + const ByteArray& getValue() const { + return value; + } + + void setValue(const ByteArray& value) { + this->value = value; + } + + private: + ByteArray value; + }; +} diff --git a/Swiften/Elements/AuthFailure.h b/Swiften/Elements/AuthFailure.h new file mode 100644 index 0000000..b1857b3 --- /dev/null +++ b/Swiften/Elements/AuthFailure.h @@ -0,0 +1,13 @@ +#ifndef SWIFTEN_AuthFailure_H +#define SWIFTEN_AuthFailure_H + +#include "Swiften/Elements/Element.h" + +namespace Swift { + class AuthFailure : public Element { + public: + AuthFailure() {} + }; +} + +#endif diff --git a/Swiften/Elements/AuthRequest.h b/Swiften/Elements/AuthRequest.h new file mode 100644 index 0000000..1f49175 --- /dev/null +++ b/Swiften/Elements/AuthRequest.h @@ -0,0 +1,36 @@ +#ifndef SWIFTEN_AuthRequest_H +#define SWIFTEN_AuthRequest_H + +#include "Swiften/Base/ByteArray.h" +#include "Swiften/Elements/Element.h" + +namespace Swift { + class AuthRequest : public Element { + public: + AuthRequest(const String& mechanism = "", const ByteArray& message = "") : + mechanism_(mechanism), message_(message) { + } + + const ByteArray& getMessage() const { + return message_; + } + + void setMessage(const ByteArray& message) { + message_ = message; + } + + const String& getMechanism() const { + return mechanism_; + } + + void setMechanism(const String& mechanism) { + mechanism_ = mechanism; + } + + private: + String mechanism_; + ByteArray message_; + }; +} + +#endif diff --git a/Swiften/Elements/AuthResponse.h b/Swiften/Elements/AuthResponse.h new file mode 100644 index 0000000..8a0679f --- /dev/null +++ b/Swiften/Elements/AuthResponse.h @@ -0,0 +1,23 @@ +#pragma once + +#include "Swiften/Base/ByteArray.h" +#include "Swiften/Elements/Element.h" + +namespace Swift { + class AuthResponse : public Element { + public: + AuthResponse(const ByteArray& value = "") : value(value) { + } + + const ByteArray& getValue() const { + return value; + } + + void setValue(const ByteArray& value) { + this->value = value; + } + + private: + ByteArray value; + }; +} diff --git a/Swiften/Elements/AuthSuccess.h b/Swiften/Elements/AuthSuccess.h new file mode 100644 index 0000000..1db1729 --- /dev/null +++ b/Swiften/Elements/AuthSuccess.h @@ -0,0 +1,22 @@ +#pragma once + +#include "Swiften/Elements/Element.h" +#include "Swiften/Base/ByteArray.h" + +namespace Swift { + class AuthSuccess : public Element { + public: + AuthSuccess() {} + + const ByteArray& getValue() const { + return value; + } + + void setValue(const ByteArray& value) { + this->value = value; + } + + private: + ByteArray value; + }; +} diff --git a/Swiften/Elements/Body.h b/Swiften/Elements/Body.h new file mode 100644 index 0000000..d78cecf --- /dev/null +++ b/Swiften/Elements/Body.h @@ -0,0 +1,26 @@ +#ifndef SWIFTEN_Body_H +#define SWIFTEN_Body_H + +#include "Swiften/Elements/Payload.h" +#include "Swiften/Base/String.h" + +namespace Swift { + class Body : public Payload { + public: + Body(const String& text = "") : text_(text) { + } + + void setText(const String& text) { + text_ = text; + } + + const String& getText() const { + return text_; + } + + private: + String text_; + }; +} + +#endif diff --git a/Swiften/Elements/CapsInfo.h b/Swiften/Elements/CapsInfo.h new file mode 100644 index 0000000..4b478aa --- /dev/null +++ b/Swiften/Elements/CapsInfo.h @@ -0,0 +1,23 @@ +#ifndef SWIFTEN_CapsInfo_H +#define SWIFTEN_CapsInfo_H + +#include "Swiften/Base/String.h" +#include "Swiften/Elements/Payload.h" + +namespace Swift { + class CapsInfo : public Payload { + public: + CapsInfo(const String& node, const String& version, const String& hash = "sha-1") : node_(node), version_(version), hash_(hash) {} + + const String& getNode() const { return node_; } + const String& getVersion() const { return version_; } + const String& getHash() const { return hash_; } + + private: + String node_; + String version_; + String hash_; + }; +} + +#endif diff --git a/Swiften/Elements/ChatState.h b/Swiften/Elements/ChatState.h new file mode 100644 index 0000000..3378cc3 --- /dev/null +++ b/Swiften/Elements/ChatState.h @@ -0,0 +1,20 @@ +#pragma once + +#include "Swiften/Base/String.h" +#include "Swiften/Elements/Payload.h" + +namespace Swift { + class ChatState : public Payload { + public: + enum ChatStateType {Active, Composing, Paused, Inactive, Gone}; + ChatState(ChatStateType state = Active) { + state_ = state; + } + + ChatStateType getChatState() { return state_; } + void setChatState(ChatStateType state) {state_ = state;} + + private: + ChatStateType state_; + }; +} diff --git a/Swiften/Elements/CompressFailure.h b/Swiften/Elements/CompressFailure.h new file mode 100644 index 0000000..880f71a --- /dev/null +++ b/Swiften/Elements/CompressFailure.h @@ -0,0 +1,13 @@ +#ifndef SWIFTEN_CompressFailure_H +#define SWIFTEN_CompressFailure_H + +#include "Swiften/Elements/Element.h" + +namespace Swift { + class CompressFailure : public Element { + public: + CompressFailure() {} + }; +} + +#endif diff --git a/Swiften/Elements/CompressRequest.h b/Swiften/Elements/CompressRequest.h new file mode 100644 index 0000000..53c0805 --- /dev/null +++ b/Swiften/Elements/CompressRequest.h @@ -0,0 +1,25 @@ +#ifndef SWIFTEN_CompressRequest_H +#define SWIFTEN_CompressRequest_H + +#include "Swiften/Elements/Element.h" + +namespace Swift { + class CompressRequest : public Element + { + public: + CompressRequest(const String& method = "") : method_(method) {} + + const String& getMethod() const { + return method_; + } + + void setMethod(const String& method) { + method_ = method; + } + + private: + String method_; + }; +} + +#endif diff --git a/Swiften/Elements/Compressed.h b/Swiften/Elements/Compressed.h new file mode 100644 index 0000000..37113d8 --- /dev/null +++ b/Swiften/Elements/Compressed.h @@ -0,0 +1,14 @@ +#ifndef SWIFTEN_COMPRESSED_H +#define SWIFTEN_COMPRESSED_H + +#include "Swiften/Elements/Element.h" + +namespace Swift { + class Compressed : public Element + { + public: + Compressed() {} + }; +} + +#endif diff --git a/Swiften/Elements/DiscoInfo.cpp b/Swiften/Elements/DiscoInfo.cpp new file mode 100644 index 0000000..63ee051 --- /dev/null +++ b/Swiften/Elements/DiscoInfo.cpp @@ -0,0 +1,25 @@ +#include "Swiften/Elements/DiscoInfo.h" + +namespace Swift { + +bool DiscoInfo::Identity::operator<(const Identity& other) const { + if (category_ == other.category_) { + if (type_ == other.type_) { + if (lang_ == other.lang_) { + return name_ < other.name_; + } + else { + return lang_ < other.lang_; + } + } + else { + return type_ < other.type_; + } + } + else { + return category_ < other.category_; + } +} + +const std::string DiscoInfo::SecurityLabels = "urn:xmpp:sec-label:0"; +} diff --git a/Swiften/Elements/DiscoInfo.h b/Swiften/Elements/DiscoInfo.h new file mode 100644 index 0000000..8caeaf9 --- /dev/null +++ b/Swiften/Elements/DiscoInfo.h @@ -0,0 +1,83 @@ +#ifndef SWIFTEN_DiscoInfo_H +#define SWIFTEN_DiscoInfo_H + +#include <vector> +#include <algorithm> + +#include "Swiften/Elements/Payload.h" +#include "Swiften/Base/String.h" + +namespace Swift { + class DiscoInfo : public Payload { + public: + const static std::string SecurityLabels; + class Identity { + public: + Identity(const String& name, const String& category = "client", const String& type = "pc", const String& lang = "") : name_(name), category_(category), type_(type), lang_(lang) { + } + + const String& getCategory() const { + return category_; + } + + const String& getType() const { + return type_; + } + + const String& getLanguage() const { + return lang_; + } + + const String& getName() const { + return name_; + } + + // Sorted according to XEP-115 rules + bool operator<(const Identity& other) const; + + private: + String name_; + String category_; + String type_; + String lang_; + }; + + DiscoInfo() { + } + + const String& getNode() const { + return node_; + } + + void setNode(const String& node) { + node_ = node; + } + + const std::vector<Identity> getIdentities() const { + return identities_; + } + + void addIdentity(const Identity& identity) { + identities_.push_back(identity); + } + + const std::vector<String>& getFeatures() const { + return features_; + } + + void addFeature(const String& feature) { + features_.push_back(feature); + } + + bool hasFeature(const String& feature) const { + return std::find(features_.begin(), features_.end(), feature) != features_.end(); + } + + private: + String node_; + std::vector<Identity> identities_; + std::vector<String> features_; + }; +} + +#endif diff --git a/Swiften/Elements/Element.cpp b/Swiften/Elements/Element.cpp new file mode 100644 index 0000000..a62aad9 --- /dev/null +++ b/Swiften/Elements/Element.cpp @@ -0,0 +1,8 @@ +#include "Swiften/Elements/Element.h" + +namespace Swift { + +Element::~Element() { +} + +} diff --git a/Swiften/Elements/Element.h b/Swiften/Elements/Element.h new file mode 100644 index 0000000..d1e9c6a --- /dev/null +++ b/Swiften/Elements/Element.h @@ -0,0 +1,11 @@ +#ifndef SWIFTEN_ELEMENT_H +#define SWIFTEN_ELEMENT_H + +namespace Swift { + class Element { + public: + virtual ~Element(); + }; +} + +#endif diff --git a/Swiften/Elements/ErrorPayload.h b/Swiften/Elements/ErrorPayload.h new file mode 100644 index 0000000..32fd067 --- /dev/null +++ b/Swiften/Elements/ErrorPayload.h @@ -0,0 +1,67 @@ +#pragma once + +#include "Swiften/Elements/Payload.h" +#include "Swiften/Base/String.h" + +namespace Swift { + class ErrorPayload : public Payload { + public: + enum Type { Cancel, Continue, Modify, Auth, Wait }; + + enum Condition { + BadRequest, + Conflict, + FeatureNotImplemented, + Forbidden, + Gone, + InternalServerError, + ItemNotFound, + JIDMalformed, + NotAcceptable, + NotAllowed, + NotAuthorized, + PaymentRequired, + RecipientUnavailable, + Redirect, + RegistrationRequired, + RemoteServerNotFound, + RemoteServerTimeout, + ResourceConstraint, + ServiceUnavailable, + SubscriptionRequired, + UndefinedCondition, + UnexpectedRequest + }; + + ErrorPayload(Condition condition = UndefinedCondition, Type type = Cancel, const String& text = String()) : type_(type), condition_(condition), text_(text) { } + + Type getType() const { + return type_; + } + + void setType(Type type) { + type_ = type; + } + + Condition getCondition() const { + return condition_; + } + + void setCondition(Condition condition) { + condition_ = condition; + } + + void setText(const String& text) { + text_ = text; + } + + const String& getText() const { + return text_; + } + + private: + Type type_; + Condition condition_; + String text_; + }; +} diff --git a/Swiften/Elements/IQ.cpp b/Swiften/Elements/IQ.cpp new file mode 100644 index 0000000..53dec53 --- /dev/null +++ b/Swiften/Elements/IQ.cpp @@ -0,0 +1,37 @@ +#include "Swiften/Elements/IQ.h" + +namespace Swift { + +boost::shared_ptr<IQ> IQ::createRequest( + Type type, const JID& to, const String& id, boost::shared_ptr<Payload> payload) { + boost::shared_ptr<IQ> iq(new IQ(type)); + if (to.isValid()) { + iq->setTo(to); + } + iq->setID(id); + if (payload) { + iq->addPayload(payload); + } + return iq; +} + +boost::shared_ptr<IQ> IQ::createResult( + const JID& to, const String& id, boost::shared_ptr<Payload> payload) { + boost::shared_ptr<IQ> iq(new IQ(Result)); + iq->setTo(to); + iq->setID(id); + if (payload) { + iq->addPayload(payload); + } + return iq; +} + +boost::shared_ptr<IQ> IQ::createError(const JID& to, const String& id, ErrorPayload::Condition condition, ErrorPayload::Type type) { + boost::shared_ptr<IQ> iq(new IQ(IQ::Error)); + iq->setTo(to); + iq->setID(id); + iq->addPayload(boost::shared_ptr<Swift::ErrorPayload>(new Swift::ErrorPayload(condition, type))); + return iq; +} + +} diff --git a/Swiften/Elements/IQ.h b/Swiften/Elements/IQ.h new file mode 100644 index 0000000..80c2913 --- /dev/null +++ b/Swiften/Elements/IQ.h @@ -0,0 +1,36 @@ +#pragma once + +#include "Swiften/Elements/Stanza.h" +#include "Swiften/Elements/ErrorPayload.h" + +namespace Swift +{ + class IQ : public Stanza + { + public: + enum Type { Get, Set, Result, Error }; + + IQ(Type type = Get) : type_(type) { } + + Type getType() const { return type_; } + void setType(Type type) { type_ = type; } + + static boost::shared_ptr<IQ> createRequest( + Type type, + const JID& to, + const String& id, + boost::shared_ptr<Payload> payload); + static boost::shared_ptr<IQ> createResult( + const JID& to, + const String& id, + boost::shared_ptr<Payload> payload = boost::shared_ptr<Payload>()); + static boost::shared_ptr<IQ> createError( + const JID& to, + const String& id, + ErrorPayload::Condition condition, + ErrorPayload::Type type); + + private: + Type type_; + }; +} diff --git a/Swiften/Elements/MUCPayload.h b/Swiften/Elements/MUCPayload.h new file mode 100644 index 0000000..97932a1 --- /dev/null +++ b/Swiften/Elements/MUCPayload.h @@ -0,0 +1,11 @@ +#pragma once + +#include "Swiften/Elements/Payload.h" + +namespace Swift { + class MUCPayload : public Payload { + public: + MUCPayload() { + } + }; +} diff --git a/Swiften/Elements/Message.h b/Swiften/Elements/Message.h new file mode 100644 index 0000000..6d9171f --- /dev/null +++ b/Swiften/Elements/Message.h @@ -0,0 +1,43 @@ +#pragma once + +#include <boost/optional.hpp> + +#include "Swiften/Base/String.h" +#include "Swiften/Elements/Body.h" +#include "Swiften/Elements/ErrorPayload.h" +#include "Swiften/Elements/Stanza.h" + +namespace Swift +{ + class Message : public Stanza + { + public: + enum Type { Normal, Chat, Error, Groupchat, Headline }; + + Message() : type_(Chat) { } + + String getBody() const { + boost::shared_ptr<Body> body(getPayload<Body>()); + if (body) { + return body->getText(); + } + return ""; + } + + void setBody(const String& body) { + updatePayload(boost::shared_ptr<Body>(new Body(body))); + } + + bool isError() { + boost::shared_ptr<Swift::ErrorPayload> error(getPayload<Swift::ErrorPayload>()); + return getType() == Message::Error || error; + } + + Type getType() const { return type_; } + void setType(Type type) { type_ = type; } + + private: + String body_; + Type type_; + }; +} diff --git a/Swiften/Elements/Payload.cpp b/Swiften/Elements/Payload.cpp new file mode 100644 index 0000000..d929fad --- /dev/null +++ b/Swiften/Elements/Payload.cpp @@ -0,0 +1,8 @@ +#include "Swiften/Elements/Payload.h" + +namespace Swift { + +Payload::~Payload() { +} + +} diff --git a/Swiften/Elements/Payload.h b/Swiften/Elements/Payload.h new file mode 100644 index 0000000..829dc24 --- /dev/null +++ b/Swiften/Elements/Payload.h @@ -0,0 +1,11 @@ +#ifndef SWIFTEN_PAYLOAD_H +#define SWIFTEN_PAYLOAD_H + +namespace Swift { + class Payload { + public: + virtual ~Payload(); + }; +} + +#endif diff --git a/Swiften/Elements/Presence.h b/Swiften/Elements/Presence.h new file mode 100644 index 0000000..f748e44 --- /dev/null +++ b/Swiften/Elements/Presence.h @@ -0,0 +1,62 @@ +#pragma once + +#include "Swiften/Elements/Stanza.h" +#include "Swiften/Elements/Status.h" +#include "Swiften/Elements/StatusShow.h" +#include "Swiften/Elements/Priority.h" + +namespace Swift { + class Presence : public Stanza + { + public: + enum Type { Available, Error, Probe, Subscribe, Subscribed, Unavailable, Unsubscribe, Unsubscribed }; + + Presence() : type_(Available) /*, showType_(Online)*/ {} + Presence(const String& status) : type_(Available) { + setStatus(status); + } + + Type getType() const { return type_; } + void setType(Type type) { type_ = type; } + + StatusShow::Type getShow() const { + boost::shared_ptr<StatusShow> show(getPayload<StatusShow>()); + if (show) { + return show->getType(); + } + return type_ == Available ? StatusShow::Online : StatusShow::None; + } + + void setShow(const StatusShow::Type &show) { + updatePayload(boost::shared_ptr<StatusShow>(new StatusShow(show))); + } + + String getStatus() const { + boost::shared_ptr<Status> status(getPayload<Status>()); + if (status) { + return status->getText(); + } + return ""; + } + + void setStatus(const String& status) { + updatePayload(boost::shared_ptr<Status>(new Status(status))); + } + + int getPriority() const { + boost::shared_ptr<Priority> priority(getPayload<Priority>()); + return (priority ? priority->getPriority() : 0); + } + + void setPriority(int priority) { + updatePayload(boost::shared_ptr<Priority>(new Priority(priority))); + } + + boost::shared_ptr<Presence> clone() const { + return boost::shared_ptr<Presence>(new Presence(*this)); + } + + private: + Presence::Type type_; + }; +} diff --git a/Swiften/Elements/Priority.h b/Swiften/Elements/Priority.h new file mode 100644 index 0000000..fd6080e --- /dev/null +++ b/Swiften/Elements/Priority.h @@ -0,0 +1,25 @@ +#ifndef SWIFTEN_Priority_H +#define SWIFTEN_Priority_H + +#include "Swiften/Elements/Payload.h" + +namespace Swift { + class Priority : public Payload { + public: + Priority(int priority = 0) : priority_(priority) { + } + + void setPriority(int priority) { + priority_ = priority; + } + + int getPriority() const { + return priority_; + } + + private: + int priority_; + }; +} + +#endif diff --git a/Swiften/Elements/PrivateStorage.h b/Swiften/Elements/PrivateStorage.h new file mode 100644 index 0000000..93f1cfe --- /dev/null +++ b/Swiften/Elements/PrivateStorage.h @@ -0,0 +1,24 @@ +#pragma once + +#include <boost/shared_ptr.hpp> + +#include "Swiften/Elements/Payload.h" + +namespace Swift { + class PrivateStorage : public Payload { + public: + PrivateStorage(boost::shared_ptr<Payload> payload = boost::shared_ptr<Payload>()) : payload(payload) { + } + + boost::shared_ptr<Payload> getPayload() const { + return payload; + } + + void setPayload(boost::shared_ptr<Payload> p) { + payload = p; + } + + private: + boost::shared_ptr<Payload> payload; + }; +} diff --git a/Swiften/Elements/ProtocolHeader.h b/Swiften/Elements/ProtocolHeader.h new file mode 100644 index 0000000..da64811 --- /dev/null +++ b/Swiften/Elements/ProtocolHeader.h @@ -0,0 +1,36 @@ +#pragma once + +#include "Swiften/Base/String.h" + +namespace Swift { + class ProtocolHeader { + public: + ProtocolHeader() : version("1.0") {} + + const String& getTo() const { return to; } + void setTo(const String& a) { + to = a; + } + + const String& getFrom() const { return from; } + void setFrom(const String& a) { + from = a; + } + + const String& getVersion() const { return version; } + void setVersion(const String& a) { + version = a; + } + + const String& getID() const { return id; } + void setID(const String& a) { + id = a; + } + + private: + String to; + String from; + String id; + String version; + }; +} diff --git a/Swiften/Elements/RawXMLPayload.h b/Swiften/Elements/RawXMLPayload.h new file mode 100644 index 0000000..c2ee439 --- /dev/null +++ b/Swiften/Elements/RawXMLPayload.h @@ -0,0 +1,22 @@ +#pragma once + +#include "Swiften/Base/String.h" +#include "Swiften/Elements/Payload.h" + +namespace Swift { + class RawXMLPayload : public Payload { + public: + RawXMLPayload() {} + + void setRawXML(const String& data) { + rawXML_ = data; + } + + const String& getRawXML() const { + return rawXML_; + } + + private: + String rawXML_; + }; +} diff --git a/Swiften/Elements/ResourceBind.h b/Swiften/Elements/ResourceBind.h new file mode 100644 index 0000000..3b6c632 --- /dev/null +++ b/Swiften/Elements/ResourceBind.h @@ -0,0 +1,36 @@ +#ifndef SWIFTEN_ResourceBind_H +#define SWIFTEN_ResourceBind_H + +#include "Swiften/Base/String.h" +#include "Swiften/Elements/Payload.h" +#include "Swiften/JID/JID.h" + +namespace Swift { + class ResourceBind : public Payload + { + public: + ResourceBind() {} + + void setJID(const JID& jid) { + jid_ = jid; + } + + const JID& getJID() const { + return jid_; + } + + void setResource(const String& resource) { + resource_ = resource; + } + + const String& getResource() const { + return resource_; + } + + private: + JID jid_; + String resource_; + }; +} + +#endif diff --git a/Swiften/Elements/RosterItemPayload.h b/Swiften/Elements/RosterItemPayload.h new file mode 100644 index 0000000..3925117 --- /dev/null +++ b/Swiften/Elements/RosterItemPayload.h @@ -0,0 +1,43 @@ +#ifndef SWIFTEN_RosterItemPayloadPayload_H +#define SWIFTEN_RosterItemPayloadPayload_H + +#include <vector> + +#include "Swiften/JID/JID.h" +#include "Swiften/Base/String.h" + +namespace Swift { + class RosterItemPayload + { + public: + enum Subscription { None, To, From, Both, Remove }; + + RosterItemPayload() : subscription_(None), ask_(false) {} + RosterItemPayload(const JID& jid, const String& name, Subscription subscription) : jid_(jid), name_(name), subscription_(subscription), ask_(false) { } + + void setJID(const JID& jid) { jid_ = jid; } + const JID& getJID() const { return jid_; } + + void setName(const String& name) { name_ = name; } + const String& getName() const { return name_; } + + void setSubscription(Subscription subscription) { subscription_ = subscription; } + const Subscription& getSubscription() const { return subscription_; } + + void addGroup(const String& group) { groups_.push_back(group); } + void setGroups(const std::vector<String>& groups) { groups_ = groups; } + const std::vector<String>& getGroups() const { return groups_; } + + void setSubscriptionRequested() { ask_ = true; } + bool getSubscriptionRequested() const { return ask_; } + + private: + JID jid_; + String name_; + Subscription subscription_; + std::vector<String> groups_; + bool ask_; + }; +} + +#endif diff --git a/Swiften/Elements/RosterPayload.cpp b/Swiften/Elements/RosterPayload.cpp new file mode 100644 index 0000000..1aecde9 --- /dev/null +++ b/Swiften/Elements/RosterPayload.cpp @@ -0,0 +1,17 @@ +#include "Swiften/Elements/RosterPayload.h" +#include "Swiften/Base/foreach.h" + +namespace Swift { + +boost::optional<RosterItemPayload> RosterPayload::getItem(const JID& jid) const { + foreach(const RosterItemPayload& item, items_) { + // FIXME: MSVC rejects this. Find out why. + //if (item.getJID() == jid) { + if (item.getJID().equals(jid, JID::WithResource)) { + return boost::optional<RosterItemPayload>(item); + } + } + return boost::optional<RosterItemPayload>(); +} + +} diff --git a/Swiften/Elements/RosterPayload.h b/Swiften/Elements/RosterPayload.h new file mode 100644 index 0000000..afb68c2 --- /dev/null +++ b/Swiften/Elements/RosterPayload.h @@ -0,0 +1,33 @@ +#ifndef SWIFTEN_RosterPayload_H +#define SWIFTEN_RosterPayload_H + +#include <vector> +#include <boost/optional.hpp> + +#include "Swiften/Elements/RosterItemPayload.h" +#include "Swiften/Elements/Payload.h" + +namespace Swift { + class RosterPayload : public Payload { + public: + typedef std::vector<RosterItemPayload> RosterItemPayloads; + + public: + RosterPayload() {} + + boost::optional<RosterItemPayload> getItem(const JID& jid) const; + + void addItem(const RosterItemPayload& item) { + items_.push_back(item); + } + + const RosterItemPayloads& getItems() const { + return items_; + } + + private: + RosterItemPayloads items_; + }; +} + +#endif diff --git a/Swiften/Elements/SecurityLabel.h b/Swiften/Elements/SecurityLabel.h new file mode 100644 index 0000000..65bdb4f --- /dev/null +++ b/Swiften/Elements/SecurityLabel.h @@ -0,0 +1,57 @@ +#ifndef SWIFTEN_SecurityLabel_H +#define SWIFTEN_SecurityLabel_H + +#include <vector> + +#include "Swiften/Base/String.h" +#include "Swiften/Elements/Payload.h" + +namespace Swift { + class SecurityLabel : public Payload { + public: + SecurityLabel() {} + + const String& getDisplayMarking() const { return displayMarking_; } + + void setDisplayMarking(const String& displayMarking) { + displayMarking_ = displayMarking; + } + + const String& getForegroundColor() const { + return foregroundColor_; + } + + void setForegroundColor(const String& foregroundColor) { + foregroundColor_ = foregroundColor; + } + + const String& getBackgroundColor() const { + return backgroundColor_; + } + + void setBackgroundColor(const String& backgroundColor) { + backgroundColor_ = backgroundColor; + } + + const String& getLabel() const { return label_; } + + void setLabel(const String& label) { + label_ = label; + } + + const std::vector<String>& getEquivalentLabels() const { return equivalentLabels_; } + + void addEquivalentLabel(const String& label) { + equivalentLabels_.push_back(label); + } + + private: + String displayMarking_; + String foregroundColor_; + String backgroundColor_; + String label_; + std::vector<String> equivalentLabels_; + }; +} + +#endif diff --git a/Swiften/Elements/SecurityLabelsCatalog.h b/Swiften/Elements/SecurityLabelsCatalog.h new file mode 100644 index 0000000..611c26b --- /dev/null +++ b/Swiften/Elements/SecurityLabelsCatalog.h @@ -0,0 +1,56 @@ +#ifndef SWIFTEN_SecurityLabelsCatalog_H +#define SWIFTEN_SecurityLabelsCatalog_H + +#include <vector> + +#include "Swiften/JID/JID.h" +#include "Swiften/Base/String.h" +#include "Swiften/Elements/Payload.h" +#include "Swiften/Elements/SecurityLabel.h" + +namespace Swift { + class SecurityLabelsCatalog : public Payload { + public: + SecurityLabelsCatalog(const JID& to = JID()) : to_(to) {} + + const std::vector<SecurityLabel>& getLabels() const { + return labels_; + } + + void addLabel(const SecurityLabel& label) { + labels_.push_back(label); + } + + const JID& getTo() const { + return to_; + } + + void setTo(const JID& to) { + to_ = to; + } + + const String& getName() const { + return name_; + } + + void setName(const String& name) { + name_ = name; + } + + const String& getDescription() const { + return description_; + } + + void setDescription(const String& description) { + description_ = description; + } + + private: + JID to_; + String name_; + String description_; + std::vector<SecurityLabel> labels_; + }; +} + +#endif diff --git a/Swiften/Elements/SoftwareVersion.h b/Swiften/Elements/SoftwareVersion.h new file mode 100644 index 0000000..d064414 --- /dev/null +++ b/Swiften/Elements/SoftwareVersion.h @@ -0,0 +1,47 @@ +#ifndef SWIFTEN_SoftwareVersion_H +#define SWIFTEN_SoftwareVersion_H + +#include "Swiften/Elements/Payload.h" +#include "Swiften/Base/String.h" + +namespace Swift { + class SoftwareVersion : public Payload { + public: + SoftwareVersion( + const String& name = "", + const String& version = "", + const String& os = "") : + name_(name), version_(version), os_(os) {} + + const String& getName() const { + return name_; + } + + void setName(const String& name) { + name_ = name; + } + + const String& getVersion() const { + return version_; + } + + void setVersion(const String& version) { + version_ = version; + } + + const String& getOS() const { + return os_; + } + + void setOS(const String& os) { + os_ = os; + } + + private: + String name_; + String version_; + String os_; + }; +} + +#endif diff --git a/Swiften/Elements/Stanza.cpp b/Swiften/Elements/Stanza.cpp new file mode 100644 index 0000000..e644665 --- /dev/null +++ b/Swiften/Elements/Stanza.cpp @@ -0,0 +1,31 @@ +#include "Swiften/Elements/Stanza.h" + +#include <typeinfo> + +namespace Swift { + +Stanza::~Stanza() { + payloads_.clear(); +} + +void Stanza::updatePayload(boost::shared_ptr<Payload> payload) { + foreach (boost::shared_ptr<Payload>& i, payloads_) { + if (typeid(*i.get()) == typeid(*payload.get())) { + i = payload; + return; + } + } + addPayload(payload); +} + +boost::shared_ptr<Payload> Stanza::getPayloadOfSameType(boost::shared_ptr<Payload> payload) const { + foreach (const boost::shared_ptr<Payload>& i, payloads_) { + if (typeid(*i.get()) == typeid(*payload.get())) { + return i; + } + } + return boost::shared_ptr<Payload>(); +} + + +} diff --git a/Swiften/Elements/Stanza.h b/Swiften/Elements/Stanza.h new file mode 100644 index 0000000..e60ab21 --- /dev/null +++ b/Swiften/Elements/Stanza.h @@ -0,0 +1,60 @@ +#ifndef SWIFTEN_STANZAS_STANZA_H +#define SWIFTEN_STANZAS_STANZA_H + +#include <vector> +#include <boost/shared_ptr.hpp> + +#include "Swiften/Elements/Element.h" +#include "Swiften/Elements/Payload.h" +#include "Swiften/Base/String.h" +#include "Swiften/Base/foreach.h" +#include "Swiften/JID/JID.h" + +namespace Swift { + class Stanza : public Element { + public: + virtual ~Stanza(); + + template<typename T> + boost::shared_ptr<T> getPayload() const { + foreach (const boost::shared_ptr<Payload>& i, payloads_) { + boost::shared_ptr<T> result(boost::dynamic_pointer_cast<T>(i)); + if (result) { + return result; + } + } + return boost::shared_ptr<T>(); + } + + const std::vector< boost::shared_ptr<Payload> >& getPayloads() const { + return payloads_; + } + + void addPayload(boost::shared_ptr<Payload> payload) { + payloads_.push_back(payload); + } + + void updatePayload(boost::shared_ptr<Payload> payload); + + boost::shared_ptr<Payload> getPayloadOfSameType(boost::shared_ptr<Payload>) const; + + const JID& getFrom() const { return from_; } + void setFrom(const JID& from) { from_ = from; } + + const JID& getTo() const { return to_; } + void setTo(const JID& to) { to_ = to; } + + const String& getID() const { return id_; } + void setID(const String& id) { id_ = id; } + + private: + String id_; + JID from_; + JID to_; + + typedef std::vector< boost::shared_ptr<Payload> > Payloads; + Payloads payloads_; + }; +} + +#endif diff --git a/Swiften/Elements/StartSession.h b/Swiften/Elements/StartSession.h new file mode 100644 index 0000000..2b46d05 --- /dev/null +++ b/Swiften/Elements/StartSession.h @@ -0,0 +1,14 @@ +#ifndef SWIFTEN_StartSession_H +#define SWIFTEN_StartSession_H + +#include "Swiften/Base/String.h" +#include "Swiften/Elements/Payload.h" + +namespace Swift { + class StartSession : public Payload { + public: + StartSession() {} + }; +} + +#endif diff --git a/Swiften/Elements/StartTLSFailure.h b/Swiften/Elements/StartTLSFailure.h new file mode 100644 index 0000000..17a1750 --- /dev/null +++ b/Swiften/Elements/StartTLSFailure.h @@ -0,0 +1,13 @@ +#ifndef SWIFTEN_StartTLSFailure_H +#define SWIFTEN_StartTLSFailure_H + +#include "Swiften/Elements/Element.h" + +namespace Swift { + class StartTLSFailure : public Element { + public: + StartTLSFailure() {} + }; +} + +#endif diff --git a/Swiften/Elements/StartTLSRequest.h b/Swiften/Elements/StartTLSRequest.h new file mode 100644 index 0000000..c40499a --- /dev/null +++ b/Swiften/Elements/StartTLSRequest.h @@ -0,0 +1,14 @@ +#ifndef SWIFTEN_StartTLSRequest_H +#define SWIFTEN_StartTLSRequest_H + +#include "Swiften/Elements/Element.h" + +namespace Swift { + class StartTLSRequest : public Element + { + public: + StartTLSRequest() {} + }; +} + +#endif diff --git a/Swiften/Elements/Status.h b/Swiften/Elements/Status.h new file mode 100644 index 0000000..0b80682 --- /dev/null +++ b/Swiften/Elements/Status.h @@ -0,0 +1,26 @@ +#ifndef SWIFTEN_Status_H +#define SWIFTEN_Status_H + +#include "Swiften/Elements/Payload.h" +#include "Swiften/Base/String.h" + +namespace Swift { + class Status : public Payload { + public: + Status(const String& text = "") : text_(text) { + } + + void setText(const String& text) { + text_ = text; + } + + const String& getText() const { + return text_; + } + + private: + String text_; + }; +} + +#endif diff --git a/Swiften/Elements/StatusShow.h b/Swiften/Elements/StatusShow.h new file mode 100644 index 0000000..a001657 --- /dev/null +++ b/Swiften/Elements/StatusShow.h @@ -0,0 +1,27 @@ +#ifndef SWIFTEN_StatusShow_H +#define SWIFTEN_StatusShow_H + +#include "Swiften/Elements/Payload.h" + +namespace Swift { + class StatusShow : public Payload { + public: + enum Type { Online, Away, FFC, XA, DND, None }; + + StatusShow(const Type& type = Online) : type_(type) { + } + + void setType(const Type& type) { + type_ = type; + } + + const Type& getType() const { + return type_; + } + + private: + Type type_; + }; +} + +#endif diff --git a/Swiften/Elements/Storage.h b/Swiften/Elements/Storage.h new file mode 100644 index 0000000..1458836 --- /dev/null +++ b/Swiften/Elements/Storage.h @@ -0,0 +1,36 @@ +#pragma once + +#include <vector> + +#include "Swiften/Elements/Payload.h" +#include "Swiften/Base/String.h" +#include "Swiften/JID/JID.h" + +namespace Swift { + class Storage : public Payload { + public: + struct Conference { + Conference() : autoJoin(false) {} + + String name; + JID jid; + bool autoJoin; + String nick; + String password; + }; + + Storage() { + } + + const std::vector<Conference>& getConferences() const { + return conferences; + } + + void addConference(const Conference& conference) { + conferences.push_back(conference); + } + + private: + std::vector<Conference> conferences; + }; +} diff --git a/Swiften/Elements/StreamFeatures.h b/Swiften/Elements/StreamFeatures.h new file mode 100644 index 0000000..2d5f4d6 --- /dev/null +++ b/Swiften/Elements/StreamFeatures.h @@ -0,0 +1,77 @@ +#ifndef SWIFTEN_StreamFeatures_H +#define SWIFTEN_StreamFeatures_H + +#include <vector> +#include <algorithm> + +#include "Swiften/Base/String.h" +#include "Swiften/Elements/Element.h" + +namespace Swift { + class StreamFeatures : public Element + { + public: + StreamFeatures() : hasStartTLS_(false), hasResourceBind_(false), hasSession_(false) {} + + void setHasStartTLS() { + hasStartTLS_ = true; + } + + bool hasStartTLS() const { + return hasStartTLS_; + } + + void setHasSession() { + hasSession_ = true; + } + + bool hasSession() const { + return hasSession_; + } + + void setHasResourceBind() { + hasResourceBind_ = true; + } + + bool hasResourceBind() const { + return hasResourceBind_; + } + + const std::vector<String>& getCompressionMethods() const { + return compressionMethods_; + } + + void addCompressionMethod(const String& mechanism) { + compressionMethods_.push_back(mechanism); + } + + bool hasCompressionMethod(const String& mechanism) const { + return std::find(compressionMethods_.begin(), compressionMethods_.end(), mechanism) != compressionMethods_.end(); + } + + const std::vector<String>& getAuthenticationMechanisms() const { + return authenticationMechanisms_; + } + + void addAuthenticationMechanism(const String& mechanism) { + authenticationMechanisms_.push_back(mechanism); + } + + bool hasAuthenticationMechanism(const String& mechanism) const { + return std::find(authenticationMechanisms_.begin(), authenticationMechanisms_.end(), mechanism) != authenticationMechanisms_.end(); + } + + bool hasAuthenticationMechanisms() const { + return !authenticationMechanisms_.empty(); + } + + private: + bool hasStartTLS_; + std::vector<String> compressionMethods_; + std::vector<String> authenticationMechanisms_; + bool hasResourceBind_; + bool hasSession_; + }; +} + +#endif diff --git a/Swiften/Elements/TLSProceed.h b/Swiften/Elements/TLSProceed.h new file mode 100644 index 0000000..41f0341 --- /dev/null +++ b/Swiften/Elements/TLSProceed.h @@ -0,0 +1,14 @@ +#ifndef SWIFTEN_TLSProceed_H +#define SWIFTEN_TLSProceed_H + +#include "Swiften/Elements/Element.h" + +namespace Swift { + class TLSProceed : public Element + { + public: + TLSProceed() {} + }; +} + +#endif diff --git a/Swiften/Elements/UnitTest/IQTest.cpp b/Swiften/Elements/UnitTest/IQTest.cpp new file mode 100644 index 0000000..a5e6dc8 --- /dev/null +++ b/Swiften/Elements/UnitTest/IQTest.cpp @@ -0,0 +1,52 @@ +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> +#include <boost/shared_ptr.hpp> + +#include "Swiften/Elements/IQ.h" +#include "Swiften/Elements/SoftwareVersion.h" + +using namespace Swift; + +class IQTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(IQTest); + CPPUNIT_TEST(testCreateResult); + CPPUNIT_TEST(testCreateResult_WithoutPayload); + CPPUNIT_TEST(testCreateError); + CPPUNIT_TEST_SUITE_END(); + + public: + IQTest() {} + + void testCreateResult() { + boost::shared_ptr<Payload> payload(new SoftwareVersion("myclient")); + boost::shared_ptr<IQ> iq(IQ::createResult(JID("foo@bar/fum"), "myid", payload)); + + CPPUNIT_ASSERT_EQUAL(JID("foo@bar/fum"), iq->getTo()); + CPPUNIT_ASSERT_EQUAL(String("myid"), iq->getID()); + CPPUNIT_ASSERT(iq->getPayload<SoftwareVersion>()); + CPPUNIT_ASSERT(payload == iq->getPayload<SoftwareVersion>()); + } + + void testCreateResult_WithoutPayload() { + boost::shared_ptr<IQ> iq(IQ::createResult(JID("foo@bar/fum"), "myid")); + + CPPUNIT_ASSERT_EQUAL(JID("foo@bar/fum"), iq->getTo()); + CPPUNIT_ASSERT_EQUAL(String("myid"), iq->getID()); + CPPUNIT_ASSERT(!iq->getPayload<SoftwareVersion>()); + } + + void testCreateError() { + boost::shared_ptr<IQ> iq(IQ::createError(JID("foo@bar/fum"), "myid", ErrorPayload::BadRequest, ErrorPayload::Modify)); + + CPPUNIT_ASSERT_EQUAL(JID("foo@bar/fum"), iq->getTo()); + CPPUNIT_ASSERT_EQUAL(String("myid"), iq->getID()); + boost::shared_ptr<ErrorPayload> error(iq->getPayload<ErrorPayload>()); + CPPUNIT_ASSERT(error); + CPPUNIT_ASSERT_EQUAL(ErrorPayload::BadRequest, error->getCondition()); + CPPUNIT_ASSERT_EQUAL(ErrorPayload::Modify, error->getType()); + } + +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(IQTest); diff --git a/Swiften/Elements/UnitTest/StanzaTest.cpp b/Swiften/Elements/UnitTest/StanzaTest.cpp new file mode 100644 index 0000000..b905957 --- /dev/null +++ b/Swiften/Elements/UnitTest/StanzaTest.cpp @@ -0,0 +1,155 @@ +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> +#include <boost/shared_ptr.hpp> + +#include "Swiften/Elements/Stanza.h" +#include "Swiften/Elements/Payload.h" +#include "Swiften/Elements/Message.h" + +using namespace Swift; + +class StanzaTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(StanzaTest); + CPPUNIT_TEST(testConstructor_Copy); + CPPUNIT_TEST(testGetPayload); + CPPUNIT_TEST(testGetPayload_NoSuchPayload); + CPPUNIT_TEST(testDestructor); + CPPUNIT_TEST(testDestructor_Copy); + CPPUNIT_TEST(testUpdatePayload_ExistingPayload); + CPPUNIT_TEST(testUpdatePayload_NewPayload); + CPPUNIT_TEST(testGetPayloadOfSameType); + CPPUNIT_TEST(testGetPayloadOfSameType_NoSuchPayload); + CPPUNIT_TEST_SUITE_END(); + + public: + class MyPayload1 : public Payload { + public: + MyPayload1() {} + }; + + class MyPayload2 : public Payload { + public: + MyPayload2(const String& s = "") : text_(s) {} + + String text_; + }; + + class MyPayload3 : public Payload { + public: + MyPayload3() {} + }; + + class DestroyingPayload : public Payload { + public: + DestroyingPayload(bool* alive) : alive_(alive) { + } + + ~DestroyingPayload() { + (*alive_) = false; + } + + private: + bool* alive_; + }; + + StanzaTest() {} + + void testConstructor_Copy() { + Message m; + m.addPayload(boost::shared_ptr<MyPayload1>(new MyPayload1())); + m.addPayload(boost::shared_ptr<MyPayload2>(new MyPayload2())); + Message copy(m); + + CPPUNIT_ASSERT(copy.getPayload<MyPayload1>()); + CPPUNIT_ASSERT(copy.getPayload<MyPayload2>()); + } + + void testDestructor() { + bool payloadAlive = true; + { + Message m; + m.addPayload(boost::shared_ptr<DestroyingPayload>(new DestroyingPayload(&payloadAlive))); + } + + CPPUNIT_ASSERT(!payloadAlive); + } + + void testDestructor_Copy() { + bool payloadAlive = true; + Message* m1 = new Message(); + m1->addPayload(boost::shared_ptr<DestroyingPayload>(new DestroyingPayload(&payloadAlive))); + Message* m2 = new Message(*m1); + + delete m1; + CPPUNIT_ASSERT(payloadAlive); + + delete m2; + CPPUNIT_ASSERT(!payloadAlive); + } + + void testGetPayload() { + Message m; + m.addPayload(boost::shared_ptr<MyPayload1>(new MyPayload1())); + m.addPayload(boost::shared_ptr<MyPayload2>(new MyPayload2())); + m.addPayload(boost::shared_ptr<MyPayload3>(new MyPayload3())); + + boost::shared_ptr<MyPayload2> p(m.getPayload<MyPayload2>()); + CPPUNIT_ASSERT(p); + } + + void testGetPayload_NoSuchPayload() { + Message m; + m.addPayload(boost::shared_ptr<MyPayload1>(new MyPayload1())); + m.addPayload(boost::shared_ptr<MyPayload3>(new MyPayload3())); + + boost::shared_ptr<MyPayload2> p(m.getPayload<MyPayload2>()); + CPPUNIT_ASSERT(!p); + } + + void testUpdatePayload_ExistingPayload() { + Message m; + m.addPayload(boost::shared_ptr<MyPayload1>(new MyPayload1())); + m.addPayload(boost::shared_ptr<MyPayload2>(new MyPayload2("foo"))); + m.addPayload(boost::shared_ptr<MyPayload3>(new MyPayload3())); + + m.updatePayload(boost::shared_ptr<MyPayload2>(new MyPayload2("bar"))); + + CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(3), m.getPayloads().size()); + boost::shared_ptr<MyPayload2> p(m.getPayload<MyPayload2>()); + CPPUNIT_ASSERT_EQUAL(String("bar"), p->text_); + } + + void testUpdatePayload_NewPayload() { + Message m; + m.addPayload(boost::shared_ptr<MyPayload1>(new MyPayload1())); + m.addPayload(boost::shared_ptr<MyPayload3>(new MyPayload3())); + + m.updatePayload(boost::shared_ptr<MyPayload2>(new MyPayload2("bar"))); + + CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(3), m.getPayloads().size()); + boost::shared_ptr<MyPayload2> p(m.getPayload<MyPayload2>()); + CPPUNIT_ASSERT_EQUAL(String("bar"), p->text_); + } + + void testGetPayloadOfSameType() { + Message m; + m.addPayload(boost::shared_ptr<MyPayload1>(new MyPayload1())); + m.addPayload(boost::shared_ptr<MyPayload2>(new MyPayload2("foo"))); + m.addPayload(boost::shared_ptr<MyPayload3>(new MyPayload3())); + + boost::shared_ptr<MyPayload2> payload(boost::dynamic_pointer_cast<MyPayload2>(m.getPayloadOfSameType(boost::shared_ptr<MyPayload2>(new MyPayload2("bar"))))); + CPPUNIT_ASSERT(payload); + CPPUNIT_ASSERT_EQUAL(String("foo"), payload->text_); + } + + void testGetPayloadOfSameType_NoSuchPayload() { + Message m; + m.addPayload(boost::shared_ptr<MyPayload1>(new MyPayload1())); + m.addPayload(boost::shared_ptr<MyPayload3>(new MyPayload3())); + + CPPUNIT_ASSERT(!m.getPayloadOfSameType(boost::shared_ptr<MyPayload2>(new MyPayload2("bar")))); + } +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(StanzaTest); diff --git a/Swiften/Elements/UnitTest/StanzasTest.cpp b/Swiften/Elements/UnitTest/StanzasTest.cpp new file mode 100644 index 0000000..35b84e7 --- /dev/null +++ b/Swiften/Elements/UnitTest/StanzasTest.cpp @@ -0,0 +1,3 @@ +#include "Swiften/Elements/Message.h" +#include "Swiften/Elements/IQ.h" +#include "Swiften/Elements/Presence.h" diff --git a/Swiften/Elements/UnknownElement.h b/Swiften/Elements/UnknownElement.h new file mode 100644 index 0000000..3d2c219 --- /dev/null +++ b/Swiften/Elements/UnknownElement.h @@ -0,0 +1,13 @@ +#ifndef SWIFTEN_UnknownElement_H +#define SWIFTEN_UnknownElement_H + +#include "Swiften/Elements/Element.h" + +namespace Swift { + class UnknownElement : public Element { + public: + UnknownElement() {} + }; +} + +#endif diff --git a/Swiften/Elements/VCard.h b/Swiften/Elements/VCard.h new file mode 100644 index 0000000..a42d7e3 --- /dev/null +++ b/Swiften/Elements/VCard.h @@ -0,0 +1,42 @@ +#pragma once + +#include "Swiften/Base/String.h" +#include "Swiften/Base/ByteArray.h" +#include "Swiften/Elements/Payload.h" + +namespace Swift { + class VCard : public Payload { + public: + VCard() {} + + void setFullName(const String& fullName) { fullName_ = fullName; } + const String& getFullName() const { return fullName_; } + + void setFamilyName(const String& familyName) { familyName_ = familyName; } + const String& getFamilyName() const { return familyName_; } + + void setGivenName(const String& givenName) { givenName_ = givenName; } + const String& getGivenName() const { return givenName_; } + + void setEMail(const String& email) { email_ = email; } + const String& getEMail() const { return email_; } + + void setNickname(const String& nick) { nick_ = nick; } + const String& getNickname() const { return nick_; } + + void setPhoto(const ByteArray& photo) { photo_ = photo; } + const ByteArray& getPhoto() { return photo_; } + + void setPhotoType(const String& photoType) { photoType_ = photoType; } + const String& getPhotoType() { return photoType_; } + + private: + String fullName_; + String familyName_; + String givenName_; + String email_; + ByteArray photo_; + String photoType_; + String nick_; + }; +} diff --git a/Swiften/Elements/VCardUpdate.h b/Swiften/Elements/VCardUpdate.h new file mode 100644 index 0000000..6bb79cc --- /dev/null +++ b/Swiften/Elements/VCardUpdate.h @@ -0,0 +1,17 @@ +#pragma once + +#include "Swiften/Base/String.h" +#include "Swiften/Elements/Payload.h" + +namespace Swift { + class VCardUpdate : public Payload { + public: + VCardUpdate(const String& photoHash = "") : photoHash_(photoHash) {} + + void setPhotoHash(const String& photoHash) { photoHash_ = photoHash; } + const String& getPhotoHash() { return photoHash_; } + + private: + String photoHash_; + }; +} diff --git a/Swiften/Elements/Version.h b/Swiften/Elements/Version.h new file mode 100644 index 0000000..327178e --- /dev/null +++ b/Swiften/Elements/Version.h @@ -0,0 +1,24 @@ +#ifndef SWIFTEN_STANZAS_VERSION_H +#define SWIFTEN_STANZAS_VERSION_H + +#include "Swiften/Base/String.h" +#include "Swiften/Elements/Payload.h" + +namespace Swift { + class Version : public Payload + { + public: + Version(const String& name = "", const String& version = "", const String& os = "") : name_(name), version_(version), os_(os) { } + + const String& getName() const { return name_; } + const String& getVersion() const { return version_; } + const String& getOS() const { return os_; } + + private: + String name_; + String version_; + String os_; + }; +} + +#endif diff --git a/Swiften/EventLoop/Cocoa/CocoaEvent.h b/Swiften/EventLoop/Cocoa/CocoaEvent.h new file mode 100644 index 0000000..0ff4453 --- /dev/null +++ b/Swiften/EventLoop/Cocoa/CocoaEvent.h @@ -0,0 +1,20 @@ +#pragma once + +#include <Cocoa/Cocoa.h> + +namespace Swift { + class Event; + class CocoaEventLoop; +} + +@interface CocoaEvent : NSObject { + Swift::Event* event; + Swift::CocoaEventLoop* eventLoop; +} + +// Takes ownership of event +- (id) initWithEvent: (Swift::Event*) e eventLoop: (Swift::CocoaEventLoop*) el; +- (void) process; +- (void) dealloc; + +@end diff --git a/Swiften/EventLoop/Cocoa/CocoaEvent.mm b/Swiften/EventLoop/Cocoa/CocoaEvent.mm new file mode 100644 index 0000000..8a90983 --- /dev/null +++ b/Swiften/EventLoop/Cocoa/CocoaEvent.mm @@ -0,0 +1,25 @@ +#include "Swiften/EventLoop/Cocoa/CocoaEvent.h" +#include "Swiften/EventLoop/Event.h" +#include "Swiften/EventLoop/Cocoa/CocoaEventLoop.h" + +@implementation CocoaEvent + +- (id) initWithEvent: (Swift::Event*) e eventLoop: (Swift::CocoaEventLoop*) el { + self = [super init]; + if (self != nil) { + event = e; + eventLoop = el; + } + return self; +} + +- (void) process { + eventLoop->handleEvent(*event); +} + +- (void) dealloc { + delete event; + [super dealloc]; +} + +@end diff --git a/Swiften/EventLoop/Cocoa/CocoaEventLoop.h b/Swiften/EventLoop/Cocoa/CocoaEventLoop.h new file mode 100644 index 0000000..ad8e456 --- /dev/null +++ b/Swiften/EventLoop/Cocoa/CocoaEventLoop.h @@ -0,0 +1,14 @@ +#pragma once + +#include "Swiften/EventLoop/EventLoop.h" + +namespace Swift { + class CocoaEventLoop : public EventLoop { + public: + CocoaEventLoop(); + + virtual void post(const Event& event); + + using EventLoop::handleEvent; + }; +} diff --git a/Swiften/EventLoop/Cocoa/CocoaEventLoop.mm b/Swiften/EventLoop/Cocoa/CocoaEventLoop.mm new file mode 100644 index 0000000..b90f3c6 --- /dev/null +++ b/Swiften/EventLoop/Cocoa/CocoaEventLoop.mm @@ -0,0 +1,21 @@ +#include "Swiften/EventLoop/Cocoa/CocoaEventLoop.h" +#include "Swiften/EventLoop/Cocoa/CocoaEvent.h" + +#pragma GCC diagnostic ignored "-Wold-style-cast" + +namespace Swift { + +CocoaEventLoop::CocoaEventLoop() { +} + +void CocoaEventLoop::post(const Event& event) { + Event* eventCopy = new Event(event); + CocoaEvent* cocoaEvent = [[CocoaEvent alloc] initWithEvent: eventCopy eventLoop: this]; + [cocoaEvent + performSelectorOnMainThread:@selector(process) + withObject: nil + waitUntilDone: NO]; + [cocoaEvent release]; +} + +} diff --git a/Swiften/EventLoop/Deleter.h b/Swiften/EventLoop/Deleter.h new file mode 100644 index 0000000..217a17f --- /dev/null +++ b/Swiften/EventLoop/Deleter.h @@ -0,0 +1,23 @@ +#ifndef SWIFTEN_Deleter_H +#define SWIFTEN_Deleter_H + +#include <cassert> + +namespace Swift { + template<typename T> + class Deleter { + public: + Deleter(T* object) : object_(object) { + } + + void operator()() { + assert(object_); + delete object_; + object_ = 0; + } + + private: + T* object_; + }; +} +#endif diff --git a/Swiften/EventLoop/DummyEventLoop.h b/Swiften/EventLoop/DummyEventLoop.h new file mode 100644 index 0000000..7766bd4 --- /dev/null +++ b/Swiften/EventLoop/DummyEventLoop.h @@ -0,0 +1,41 @@ +#pragma once + +#include <deque> +#include <iostream> +#include <boost/function.hpp> + +#include "Swiften/EventLoop/EventLoop.h" +#include "Swiften/Base/foreach.h" + +namespace Swift { + class DummyEventLoop : public EventLoop { + public: + DummyEventLoop() { + } + + ~DummyEventLoop() { + if (!events_.empty()) { + std::cerr << "DummyEventLoop: Unhandled events at destruction time" << std::endl; + } + events_.clear(); + } + + void processEvents() { + while (!events_.empty()) { + handleEvent(events_[0]); + events_.pop_front(); + } + } + + bool hasEvents() { + return events_.size() > 0; + } + + virtual void post(const Event& event) { + events_.push_back(event); + } + + private: + std::deque<Event> events_; + }; +} diff --git a/Swiften/EventLoop/Event.h b/Swiften/EventLoop/Event.h new file mode 100644 index 0000000..edd35f4 --- /dev/null +++ b/Swiften/EventLoop/Event.h @@ -0,0 +1,22 @@ +#pragma once + +#include <boost/shared_ptr.hpp> +#include <boost/function.hpp> + +#include "Swiften/EventLoop/EventOwner.h" + +namespace Swift { + struct Event { + Event(boost::shared_ptr<EventOwner> owner, const boost::function<void()>& callback) : + owner(owner), callback(callback) { + } + + bool operator==(const Event& o) const { + return o.id == id; + } + + unsigned int id; + boost::shared_ptr<EventOwner> owner; + boost::function<void()> callback; + }; +} diff --git a/Swiften/EventLoop/EventLoop.cpp b/Swiften/EventLoop/EventLoop.cpp new file mode 100644 index 0000000..3c3c356 --- /dev/null +++ b/Swiften/EventLoop/EventLoop.cpp @@ -0,0 +1,49 @@ +#include "Swiften/EventLoop/EventLoop.h" + +#include <algorithm> +#include <boost/bind.hpp> + +#include "Swiften/EventLoop/MainEventLoop.h" + +namespace Swift { + +EventLoop::EventLoop() : nextEventID_(0) { + MainEventLoop::setInstance(this); +} + +EventLoop::~EventLoop() { + MainEventLoop::resetInstance(); +} + +void EventLoop::handleEvent(const Event& event) { + bool doCallback = false; + { + boost::lock_guard<boost::mutex> lock(eventsMutex_); + std::list<Event>::iterator i = std::find(events_.begin(), events_.end(), event); + if (i != events_.end()) { + doCallback = true; + events_.erase(i); + } + } + if (doCallback) { + event.callback(); + } +} + +void EventLoop::postEvent(boost::function<void ()> callback, boost::shared_ptr<EventOwner> owner) { + Event event(owner, callback); + { + boost::lock_guard<boost::mutex> lock(eventsMutex_); + event.id = nextEventID_; + nextEventID_++; + events_.push_back(event); + } + post(event); +} + +void EventLoop::removeEventsFromOwner(boost::shared_ptr<EventOwner> owner) { + boost::lock_guard<boost::mutex> lock(eventsMutex_); + events_.remove_if(HasOwner(owner)); +} + +} diff --git a/Swiften/EventLoop/EventLoop.h b/Swiften/EventLoop/EventLoop.h new file mode 100644 index 0000000..2b45288 --- /dev/null +++ b/Swiften/EventLoop/EventLoop.h @@ -0,0 +1,38 @@ +#pragma once + +#include <boost/function.hpp> +#include <boost/thread/mutex.hpp> +#include <list> + +#include "Swiften/EventLoop/Event.h" + +namespace Swift { + class EventOwner; + class EventLoop { + public: + EventLoop(); + virtual ~EventLoop(); + + void postEvent(boost::function<void ()> event, boost::shared_ptr<EventOwner> owner = boost::shared_ptr<EventOwner>()); + void removeEventsFromOwner(boost::shared_ptr<EventOwner> owner); + + protected: + /** + * Reimplement this to call handleEvent(event) from the thread in which + * the event loop is residing. + */ + virtual void post(const Event& event) = 0; + + void handleEvent(const Event& event); + + private: + struct HasOwner { + HasOwner(boost::shared_ptr<EventOwner> owner) : owner(owner) {} + bool operator()(const Event& event) { return event.owner == owner; } + boost::shared_ptr<EventOwner> owner; + }; + boost::mutex eventsMutex_; + unsigned int nextEventID_; + std::list<Event> events_; + }; +} diff --git a/Swiften/EventLoop/EventOwner.cpp b/Swiften/EventLoop/EventOwner.cpp new file mode 100644 index 0000000..4818b3c --- /dev/null +++ b/Swiften/EventLoop/EventOwner.cpp @@ -0,0 +1,8 @@ +#include "Swiften/EventLoop/EventOwner.h" + +namespace Swift { + +EventOwner::~EventOwner() { +} + +} diff --git a/Swiften/EventLoop/EventOwner.h b/Swiften/EventLoop/EventOwner.h new file mode 100644 index 0000000..8da95e0 --- /dev/null +++ b/Swiften/EventLoop/EventOwner.h @@ -0,0 +1,8 @@ +#pragma once + +namespace Swift { + class EventOwner { + public: + virtual ~EventOwner(); + }; +} diff --git a/Swiften/EventLoop/MainEventLoop.cpp b/Swiften/EventLoop/MainEventLoop.cpp new file mode 100644 index 0000000..d7de6c6 --- /dev/null +++ b/Swiften/EventLoop/MainEventLoop.cpp @@ -0,0 +1,36 @@ +#include "Swiften/EventLoop/MainEventLoop.h" + +#include <iostream> +#include <typeinfo> + +namespace Swift { + +EventLoop* MainEventLoop::getInstance() { + if (!instance_) { + std::cerr << "No main event loop instantiated. Please instantiate the appropriate subclass of EventLoop (e.g. SimpleEventLoop, QtEventLoop) at the start of your application." << std::endl; + exit(-1); + } + return instance_; +} + +void MainEventLoop::setInstance(EventLoop* loop) { + assert(!instance_); + instance_ = loop; +} + +void MainEventLoop::resetInstance() { + assert(instance_); + instance_ = 0; +} + +void MainEventLoop::postEvent(boost::function<void ()> event, boost::shared_ptr<EventOwner> owner) { + getInstance()->postEvent(event, owner); +} + +void MainEventLoop::removeEventsFromOwner(boost::shared_ptr<EventOwner> owner) { + getInstance()->removeEventsFromOwner(owner); +} + +EventLoop* MainEventLoop::instance_ = 0; + +} diff --git a/Swiften/EventLoop/MainEventLoop.h b/Swiften/EventLoop/MainEventLoop.h new file mode 100644 index 0000000..fbb7079 --- /dev/null +++ b/Swiften/EventLoop/MainEventLoop.h @@ -0,0 +1,41 @@ +#ifndef SWIFTEN_MainEventLoop_H +#define SWIFTEN_MainEventLoop_H + +#include <boost/function.hpp> + +#include "Swiften/EventLoop/Deleter.h" +#include "Swiften/EventLoop/EventLoop.h" + +namespace Swift { + class EventLoop; + class EventOwner; + + class MainEventLoop { + friend class EventLoop; + + public: + /** + * Post an event from the given owner to the event loop. + * If the owner is destroyed, all events should be removed from the + * loop using removeEventsFromOwner(). + */ + static void postEvent(boost::function<void ()> event, boost::shared_ptr<EventOwner> owner = boost::shared_ptr<EventOwner>()); + + static void removeEventsFromOwner(boost::shared_ptr<EventOwner> owner); + + template<typename T> + static void deleteLater(T* t) { + getInstance()->postEvent(Deleter<T>(t), 0); + } + + private: + static void setInstance(EventLoop*); + static void resetInstance(); + static EventLoop* getInstance(); + + private: + static EventLoop* instance_; + }; +} + +#endif diff --git a/Swiften/EventLoop/Qt/QtEventLoop.h b/Swiften/EventLoop/Qt/QtEventLoop.h new file mode 100644 index 0000000..40e927e --- /dev/null +++ b/Swiften/EventLoop/Qt/QtEventLoop.h @@ -0,0 +1,36 @@ +#pragma once + +#include <QObject> +#include <QEvent> +#include <QCoreApplication> + +#include "Swiften/EventLoop/EventLoop.h" + +class QtEventLoop : public QObject, public Swift::EventLoop { + public: + QtEventLoop() {} + + virtual void post(const Swift::Event& event) { + QCoreApplication::postEvent(this, new Event(event)); + } + + virtual bool event(QEvent* qevent) { + Event* event = dynamic_cast<Event*>(qevent); + if (event) { + handleEvent(event->event_); + //event->deleteLater(); FIXME: Leak? + return true; + } + + return false; + } + + private: + struct Event : public QEvent { + Event(const Swift::Event& event) : + QEvent(QEvent::User), event_(event) { + } + + Swift::Event event_; + }; +}; diff --git a/Swiften/EventLoop/SConscript b/Swiften/EventLoop/SConscript new file mode 100644 index 0000000..5d1c3cb --- /dev/null +++ b/Swiften/EventLoop/SConscript @@ -0,0 +1,17 @@ +Import("swiften_env") + +sources = [ + "EventLoop.cpp", + "EventOwner.cpp", + "MainEventLoop.cpp", + "SimpleEventLoop.cpp", + ] + +if swiften_env["PLATFORM"] == "darwin" and swiften_env["target"] == "native": + sources += [ + "Cocoa/CocoaEventLoop.mm", + "Cocoa/CocoaEvent.mm" + ] + +objects = swiften_env.StaticObject(sources) +swiften_env.Append(SWIFTEN_OBJECTS = [objects]) diff --git a/Swiften/EventLoop/SimpleEventLoop.cpp b/Swiften/EventLoop/SimpleEventLoop.cpp new file mode 100644 index 0000000..7c46ed3 --- /dev/null +++ b/Swiften/EventLoop/SimpleEventLoop.cpp @@ -0,0 +1,54 @@ +#include "Swiften/EventLoop/SimpleEventLoop.h" + +#include <boost/bind.hpp> + +#include "Swiften/Base/foreach.h" + + +namespace Swift { + +void nop() {} + +SimpleEventLoop::SimpleEventLoop() : isRunning_(true) { +} + +SimpleEventLoop::~SimpleEventLoop() { + if (!events_.empty()) { + std::cerr << "Warning: Pending events in SimpleEventLoop at destruction time" << std::endl; + } +} + +void SimpleEventLoop::run() { + while (isRunning_) { + std::vector<Event> events; + { + boost::unique_lock<boost::mutex> lock(eventsMutex_); + while (events_.size() == 0) { + eventsAvailable_.wait(lock); + } + events.swap(events_); + } + foreach(const Event& event, events) { + handleEvent(event); + } + } +} + +void SimpleEventLoop::stop() { + postEvent(boost::bind(&SimpleEventLoop::doStop, this)); +} + +void SimpleEventLoop::doStop() { + isRunning_ = false; +} + +void SimpleEventLoop::post(const Event& event) { + { + boost::lock_guard<boost::mutex> lock(eventsMutex_); + events_.push_back(event); + } + eventsAvailable_.notify_one(); +} + + +} diff --git a/Swiften/EventLoop/SimpleEventLoop.h b/Swiften/EventLoop/SimpleEventLoop.h new file mode 100644 index 0000000..bd0a07f --- /dev/null +++ b/Swiften/EventLoop/SimpleEventLoop.h @@ -0,0 +1,30 @@ +#pragma once + +#include <vector> +#include <boost/function.hpp> +#include <boost/thread/mutex.hpp> +#include <boost/thread/condition_variable.hpp> + +#include "Swiften/EventLoop/EventLoop.h" + +namespace Swift { + class SimpleEventLoop : public EventLoop { + public: + SimpleEventLoop(); + ~SimpleEventLoop(); + + void run(); + void stop(); + + virtual void post(const Event& event); + + private: + void doStop(); + + private: + bool isRunning_; + std::vector<Event> events_; + boost::mutex eventsMutex_; + boost::condition_variable eventsAvailable_; + }; +} diff --git a/Swiften/EventLoop/UnitTest/EventLoopTest.cpp b/Swiften/EventLoop/UnitTest/EventLoopTest.cpp new file mode 100644 index 0000000..9475ac9 --- /dev/null +++ b/Swiften/EventLoop/UnitTest/EventLoopTest.cpp @@ -0,0 +1,67 @@ +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> +#include <boost/thread.hpp> +#include <boost/bind.hpp> + +#include "Swiften/EventLoop/EventOwner.h" +#include "Swiften/EventLoop/SimpleEventLoop.h" +#include "Swiften/Base/sleep.h" + +using namespace Swift; + +class EventLoopTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(EventLoopTest); + CPPUNIT_TEST(testPost); + CPPUNIT_TEST(testRemove); + CPPUNIT_TEST_SUITE_END(); + + public: + EventLoopTest() {} + + void setUp() { + events_.clear(); + } + + void testPost() { + SimpleEventLoop testling; + + testling.postEvent(boost::bind(&EventLoopTest::logEvent, this, 1)); + testling.postEvent(boost::bind(&EventLoopTest::logEvent, this, 2)); + testling.stop(); + testling.run(); + + CPPUNIT_ASSERT_EQUAL(2, static_cast<int>(events_.size())); + CPPUNIT_ASSERT_EQUAL(1, events_[0]); + CPPUNIT_ASSERT_EQUAL(2, events_[1]); + } + + void testRemove() { + SimpleEventLoop testling; + boost::shared_ptr<MyEventOwner> eventOwner1(new MyEventOwner()); + boost::shared_ptr<MyEventOwner> eventOwner2(new MyEventOwner()); + + testling.postEvent(boost::bind(&EventLoopTest::logEvent, this, 1), eventOwner1); + testling.postEvent(boost::bind(&EventLoopTest::logEvent, this, 2), eventOwner2); + testling.postEvent(boost::bind(&EventLoopTest::logEvent, this, 3), eventOwner1); + testling.postEvent(boost::bind(&EventLoopTest::logEvent, this, 4), eventOwner2); + testling.removeEventsFromOwner(eventOwner2); + testling.stop(); + testling.run(); + + CPPUNIT_ASSERT_EQUAL(2, static_cast<int>(events_.size())); + CPPUNIT_ASSERT_EQUAL(1, events_[0]); + CPPUNIT_ASSERT_EQUAL(3, events_[1]); + } + + private: + struct MyEventOwner : public EventOwner {}; + void logEvent(int i) { + events_.push_back(i); + } + + private: + std::vector<int> events_; +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(EventLoopTest); diff --git a/Swiften/EventLoop/UnitTest/SimpleEventLoopTest.cpp b/Swiften/EventLoop/UnitTest/SimpleEventLoopTest.cpp new file mode 100644 index 0000000..14f24c7 --- /dev/null +++ b/Swiften/EventLoop/UnitTest/SimpleEventLoopTest.cpp @@ -0,0 +1,62 @@ +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> +#include <boost/thread.hpp> +#include <boost/bind.hpp> + +#include "Swiften/EventLoop/SimpleEventLoop.h" +#include "Swiften/Base/sleep.h" + +using namespace Swift; + +class SimpleEventLoopTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(SimpleEventLoopTest); + CPPUNIT_TEST(testRun); + CPPUNIT_TEST(testPostFromMainThread); + CPPUNIT_TEST_SUITE_END(); + + public: + SimpleEventLoopTest() {} + + void setUp() { + counter_ = 0; + } + + void testRun() { + SimpleEventLoop testling; + boost::thread thread(boost::bind(&SimpleEventLoopTest::runIncrementingThread, this, &testling)); + testling.run(); + + CPPUNIT_ASSERT_EQUAL(10, counter_); + } + + void testPostFromMainThread() { + SimpleEventLoop testling; + testling.postEvent(boost::bind(&SimpleEventLoopTest::incrementCounterAndStop, this, &testling)); + testling.run(); + + CPPUNIT_ASSERT_EQUAL(1, counter_); + } + + private: + void runIncrementingThread(SimpleEventLoop* loop) { + for (unsigned int i = 0; i < 10; ++i) { + Swift::sleep(1); + loop->postEvent(boost::bind(&SimpleEventLoopTest::incrementCounter, this)); + } + loop->stop(); + } + + void incrementCounter() { + counter_++; + } + + void incrementCounterAndStop(SimpleEventLoop* loop) { + counter_++; + loop->stop(); + } + + int counter_; +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(SimpleEventLoopTest); diff --git a/Swiften/Events/MessageEvent.h b/Swiften/Events/MessageEvent.h new file mode 100644 index 0000000..43174a1 --- /dev/null +++ b/Swiften/Events/MessageEvent.h @@ -0,0 +1,33 @@ +#ifndef SWIFTEN_MessageEvent_H +#define SWIFTEN_MessageEvent_H + +#include <cassert> + +#include <boost/signals.hpp> +#include <boost/shared_ptr.hpp> + +#include "Swiften/Events/StanzaEvent.h" +#include "Swiften/Elements/Message.h" + +namespace Swift { + class MessageEvent : public StanzaEvent { + public: + MessageEvent(boost::shared_ptr<Message> stanza) : stanza_(stanza){}; + virtual ~MessageEvent(){}; + boost::shared_ptr<Message> getStanza() {return stanza_;} + + bool isReadable() { + return getStanza()->isError() || !getStanza()->getBody().isEmpty(); + } + + void read() { + assert (isReadable()); + conclude(); + } + + private: + boost::shared_ptr<Message> stanza_; + }; +} + +#endif diff --git a/Swiften/Events/StanzaEvent.h b/Swiften/Events/StanzaEvent.h new file mode 100644 index 0000000..b1dc537 --- /dev/null +++ b/Swiften/Events/StanzaEvent.h @@ -0,0 +1,18 @@ +#pragma once + +#include <boost/signals.hpp> +#include <boost/shared_ptr.hpp> + +namespace Swift { + class StanzaEvent { + public: + StanzaEvent(){concluded_ = false;}; + virtual ~StanzaEvent() {}; + void conclude() {concluded_ = true; onConclusion();}; + /** Do not call this directly from outside the class */ + boost::signal<void()> onConclusion; + bool getConcluded() {return concluded_;}; + private: + bool concluded_; + }; +} diff --git a/Swiften/Events/SubscriptionRequestEvent.h b/Swiften/Events/SubscriptionRequestEvent.h new file mode 100644 index 0000000..ed063d7 --- /dev/null +++ b/Swiften/Events/SubscriptionRequestEvent.h @@ -0,0 +1,36 @@ +#pragma once + +#include <cassert> + +#include <boost/signals.hpp> +#include <boost/shared_ptr.hpp> + +#include "Swiften/Events/StanzaEvent.h" +#include "Swiften/Base/String.h" +#include "Swiften/JID/JID.h" + +namespace Swift { + class SubscriptionRequestEvent : public StanzaEvent { + public: + SubscriptionRequestEvent(const JID& jid, const String& reason) : jid_(jid), reason_(reason){}; + virtual ~SubscriptionRequestEvent(){}; + const JID& getJID() const {return jid_;}; + const String& getReason() const {return reason_;}; + boost::signal<void()> onAccept; + boost::signal<void()> onDecline; + void accept() { + onAccept(); + conclude(); + }; + + void decline() { + onDecline(); + conclude(); + }; + + private: + JID jid_; + String reason_; + }; +} + diff --git a/Swiften/Examples/EchoBot/.gitignore b/Swiften/Examples/EchoBot/.gitignore new file mode 100644 index 0000000..9200f42 --- /dev/null +++ b/Swiften/Examples/EchoBot/.gitignore @@ -0,0 +1 @@ +EchoBot diff --git a/Swiften/Examples/EchoBot/EchoBot.cpp b/Swiften/Examples/EchoBot/EchoBot.cpp new file mode 100644 index 0000000..872d901 --- /dev/null +++ b/Swiften/Examples/EchoBot/EchoBot.cpp @@ -0,0 +1,56 @@ +#include <boost/bind.hpp> + +#include "Swiften/Client/Client.h" +#include "Swiften/Client/ClientXMLTracer.h" +#include "Swiften/EventLoop/SimpleEventLoop.h" +#include "Swiften/Queries/Requests/GetRosterRequest.h" + +using namespace Swift; +using namespace boost; + +class EchoBot { + public: + EchoBot(const JID& jid, const String& pass) : tracer(0) { + client = new Client(jid, pass); + tracer = new ClientXMLTracer(client); + client->onConnected.connect(bind(&EchoBot::handleConnected, this)); + client->onMessageReceived.connect(bind(&EchoBot::handleMessageReceived, this, _1)); + client->connect(); + } + + ~EchoBot() { + delete tracer; + delete client; + } + + private: + void handleConnected() { + shared_ptr<GetRosterRequest> rosterRequest(new GetRosterRequest(client)); + rosterRequest->onResponse.connect(bind(&EchoBot::handleRosterReceived, this, _2)); + rosterRequest->send(); + } + + void handleRosterReceived(const optional<Error>& error) { + if (error) { + std::cerr << "Error receiving roster. Continuing anyway."; + } + client->sendPresence(shared_ptr<Presence>(new Presence("Send me a message"))); + } + + void handleMessageReceived(shared_ptr<Message> message) { + message->setTo(message->getFrom()); + message->setFrom(JID()); + client->sendMessage(message); + } + + private: + Client* client; + ClientXMLTracer* tracer; +}; + +int main(int, char**) { + SimpleEventLoop eventLoop; + EchoBot bot(JID("echobot@wonderland.lit"), "mypass"); + eventLoop.run(); + return 0; +} diff --git a/Swiften/Examples/SConscript b/Swiften/Examples/SConscript new file mode 100644 index 0000000..a4d5998 --- /dev/null +++ b/Swiften/Examples/SConscript @@ -0,0 +1,8 @@ +Import("swiften_env") + +myenv = swiften_env.Clone() + +if myenv["target"] == "native": + SConscript(dirs = [ + "SendMessage" + ]) diff --git a/Swiften/Examples/SendMessage/.gitignore b/Swiften/Examples/SendMessage/.gitignore new file mode 100644 index 0000000..3b8b4d2 --- /dev/null +++ b/Swiften/Examples/SendMessage/.gitignore @@ -0,0 +1 @@ +SendMessage diff --git a/Swiften/Examples/SendMessage/SConscript b/Swiften/Examples/SendMessage/SConscript new file mode 100644 index 0000000..0e0197e --- /dev/null +++ b/Swiften/Examples/SendMessage/SConscript @@ -0,0 +1,13 @@ +Import("env") + +myenv = env.Clone() +myenv.MergeFlags(myenv["SWIFTEN_FLAGS"]) +myenv.MergeFlags(myenv["CPPUNIT_FLAGS"]) +myenv.MergeFlags(myenv["LIBIDN_FLAGS"]) +myenv.MergeFlags(myenv["BOOST_FLAGS"]) +myenv.MergeFlags(myenv["SQLITE_FLAGS"]) +myenv.MergeFlags(myenv["ZLIB_FLAGS"]) +myenv.MergeFlags(myenv["OPENSSL_FLAGS"]) +myenv.MergeFlags(myenv.get("LIBXML_FLAGS", "")) +myenv.MergeFlags(myenv.get("EXPAT_FLAGS", "")) +tester = myenv.Program("SendMessage", ["SendMessage.cpp"]) diff --git a/Swiften/Examples/SendMessage/SendMessage.cpp b/Swiften/Examples/SendMessage/SendMessage.cpp new file mode 100644 index 0000000..b7a80dd --- /dev/null +++ b/Swiften/Examples/SendMessage/SendMessage.cpp @@ -0,0 +1,53 @@ +#include <boost/bind.hpp> +#include <boost/thread.hpp> + +#include "Swiften/Client/Client.h" +#include "Swiften/Network/BoostTimer.h" +#include "Swiften/EventLoop/MainEventLoop.h" +#include "Swiften/Client/ClientXMLTracer.h" +#include "Swiften/EventLoop/SimpleEventLoop.h" +#include "Swiften/Network/BoostIOServiceThread.h" +#include "Swiften/Network/MainBoostIOServiceThread.h" + +using namespace Swift; + +SimpleEventLoop eventLoop; + +Client* client = 0; +JID recipient; +std::string messageBody; + +void handleConnected() { + boost::shared_ptr<Message> message(new Message()); + message->setBody(messageBody); + message->setTo(recipient); + client->sendMessage(message); + client->disconnect(); + eventLoop.stop(); +} + +int main(int argc, char* argv[]) { + if (argc != 5) { + std::cerr << "Usage: " << argv[0] << " <jid> <password> <recipient> <message>" << std::endl; + return -1; + } + + recipient = JID(argv[3]); + messageBody = std::string(argv[4]); + + client = new Swift::Client(JID(argv[1]), String(argv[2])); + ClientXMLTracer* tracer = new ClientXMLTracer(client); + client->onConnected.connect(&handleConnected); + client->connect(); + + { + boost::shared_ptr<BoostTimer> timer(new BoostTimer(30000, &MainBoostIOServiceThread::getInstance().getIOService())); + timer->onTick.connect(boost::bind(&SimpleEventLoop::stop, &eventLoop)); + timer->start(); + + eventLoop.run(); + } + + delete tracer; + delete client; +} diff --git a/Swiften/History/HistoryManager.cpp b/Swiften/History/HistoryManager.cpp new file mode 100644 index 0000000..3d67c27 --- /dev/null +++ b/Swiften/History/HistoryManager.cpp @@ -0,0 +1,8 @@ +#include "Swiften/History/HistoryManager.h" + +namespace Swift { + +HistoryManager::~HistoryManager() { +} + +} diff --git a/Swiften/History/HistoryManager.h b/Swiften/History/HistoryManager.h new file mode 100644 index 0000000..2a7c040 --- /dev/null +++ b/Swiften/History/HistoryManager.h @@ -0,0 +1,16 @@ +#pragma once + +#include "Swiften/Base/String.h" +#include "Swiften/JID/JID.h" +#include "Swiften/History/HistoryMessage.h" + +namespace Swift { + class HistoryManager { + public: + virtual ~HistoryManager(); + + virtual void addMessage(const HistoryMessage& message) = 0; + + virtual std::vector<HistoryMessage> getMessages() const = 0; + }; +} diff --git a/Swiften/History/HistoryMessage.h b/Swiften/History/HistoryMessage.h new file mode 100644 index 0000000..3fda3e2 --- /dev/null +++ b/Swiften/History/HistoryMessage.h @@ -0,0 +1,37 @@ +#pragma once + +#include <boost/date_time/posix_time/posix_time_types.hpp> + +namespace Swift { + class HistoryMessage { + public: + HistoryMessage(const String& message, const JID& from, const JID& to, const boost::posix_time::ptime time) : message_(message), from_(from), to_(to), time_(time) { + } + + const String& getMessage() const { + return message_; + } + + const JID& getFrom() const { + return from_; + } + + const JID& getTo() const { + return to_; + } + + boost::posix_time::ptime getTime() const { + return time_; + } + + bool operator==(const HistoryMessage& o) const { + return message_ == o.message_ && from_ == o.from_ && to_ == o.to_ && time_ == o.time_; + } + + private: + String message_; + JID from_; + JID to_; + boost::posix_time::ptime time_; + }; +} diff --git a/Swiften/History/SConscript b/Swiften/History/SConscript new file mode 100644 index 0000000..bc80780 --- /dev/null +++ b/Swiften/History/SConscript @@ -0,0 +1,11 @@ +Import("swiften_env") + +myenv = swiften_env.Clone() +if myenv["target"] == "native": + myenv.MergeFlags(swiften_env["SQLITE_FLAGS"]) + +objects = myenv.StaticObject([ + "HistoryManager.cpp", + "SQLiteHistoryManager.cpp", + ]) +swiften_env.Append(SWIFTEN_OBJECTS = [objects]) diff --git a/Swiften/History/SQLiteHistoryManager.cpp b/Swiften/History/SQLiteHistoryManager.cpp new file mode 100644 index 0000000..865abba --- /dev/null +++ b/Swiften/History/SQLiteHistoryManager.cpp @@ -0,0 +1,134 @@ +#include <iostream> +#include <boost/lexical_cast.hpp> + +#include "sqlite3.h" +#include "Swiften/History/SQLiteHistoryManager.h" + +namespace { + +inline Swift::String getEscapedString(const Swift::String& s) { + Swift::String result(s); + result.replaceAll('\'', Swift::String("\\'")); + return result; +} + +} + + +namespace Swift { + +SQLiteHistoryManager::SQLiteHistoryManager(const String& file) : db_(0) { + sqlite3_open(file.getUTF8Data(), &db_); + if (!db_) { + std::cerr << "Error opening database " << file << std::endl; // FIXME + } + + char* errorMessage; + int result = sqlite3_exec(db_, "CREATE TABLE IF NOT EXISTS messages('from' INTEGER, 'to' INTEGER, 'message' STRING, 'time' INTEGER)", 0, 0, &errorMessage); + if (result != SQLITE_OK) { + std::cerr << "SQL Error: " << errorMessage << std::endl; + sqlite3_free(errorMessage); + } + + result = sqlite3_exec(db_, "CREATE TABLE IF NOT EXISTS jids('id' INTEGER PRIMARY KEY ASC AUTOINCREMENT, 'jid' STRING UNIQUE NOT NULL)", 0, 0, &errorMessage); + if (result != SQLITE_OK) { + std::cerr << "SQL Error: " << errorMessage << std::endl; + sqlite3_free(errorMessage); + } +} + +SQLiteHistoryManager::~SQLiteHistoryManager() { + sqlite3_close(db_); +} + +void SQLiteHistoryManager::addMessage(const HistoryMessage& message) { + int secondsSinceEpoch = (message.getTime() - boost::posix_time::ptime(boost::gregorian::date(1970, 1, 1))).total_seconds(); + String statement = String("INSERT INTO messages('from', 'to', 'message', 'time') VALUES(") + boost::lexical_cast<std::string>(getIDForJID(message.getFrom())) + ", " + boost::lexical_cast<std::string>(getIDForJID(message.getTo())) + ", '" + getEscapedString(message.getMessage()) + "', " + boost::lexical_cast<std::string>(secondsSinceEpoch) + ")"; + char* errorMessage; + int result = sqlite3_exec(db_, statement.getUTF8Data(), 0, 0, &errorMessage); + if (result != SQLITE_OK) { + std::cerr << "SQL Error: " << errorMessage << std::endl; + sqlite3_free(errorMessage); + } +} + +std::vector<HistoryMessage> SQLiteHistoryManager::getMessages() const { + std::vector<HistoryMessage> result; + sqlite3_stmt* selectStatement; + String selectQuery("SELECT messages.'from', messages.'to', messages.'message', messages.'time' FROM messages"); + int r = sqlite3_prepare(db_, selectQuery.getUTF8Data(), selectQuery.getUTF8Size(), &selectStatement, NULL); + if (r != SQLITE_OK) { + std::cout << "Error: " << sqlite3_errmsg(db_) << std::endl; + } + r = sqlite3_step(selectStatement); + while (r == SQLITE_ROW) { + boost::optional<JID> from(getJIDFromID(sqlite3_column_int(selectStatement, 0))); + boost::optional<JID> to(getJIDFromID(sqlite3_column_int(selectStatement, 1))); + String message(reinterpret_cast<const char*>(sqlite3_column_text(selectStatement, 2))); + int secondsSinceEpoch(sqlite3_column_int(selectStatement, 3)); + boost::posix_time::ptime time(boost::gregorian::date(1970, 1, 1), boost::posix_time::seconds(secondsSinceEpoch)); + + result.push_back(HistoryMessage(message, (from ? *from : JID()), (to ? *to : JID()), time)); + r = sqlite3_step(selectStatement); + } + if (r != SQLITE_DONE) { + std::cout << "Error: " << sqlite3_errmsg(db_) << std::endl; + } + sqlite3_finalize(selectStatement); + return result; +} + +int SQLiteHistoryManager::getIDForJID(const JID& jid) { + boost::optional<int> id = getIDFromJID(jid); + if (id) { + return *id; + } + else { + return addJID(jid); + } +} + +int SQLiteHistoryManager::addJID(const JID& jid) { + String statement = String("INSERT INTO jids('jid') VALUES('") + getEscapedString(jid.toString()) + "')"; + char* errorMessage; + int result = sqlite3_exec(db_, statement.getUTF8Data(), 0, 0, &errorMessage); + if (result != SQLITE_OK) { + std::cerr << "SQL Error: " << errorMessage << std::endl; + sqlite3_free(errorMessage); + } + return sqlite3_last_insert_rowid(db_); +} + +boost::optional<JID> SQLiteHistoryManager::getJIDFromID(int id) const { + boost::optional<JID> result; + sqlite3_stmt* selectStatement; + String selectQuery("SELECT jid FROM jids WHERE id=" + boost::lexical_cast<std::string>(id)); + int r = sqlite3_prepare(db_, selectQuery.getUTF8Data(), selectQuery.getUTF8Size(), &selectStatement, NULL); + if (r != SQLITE_OK) { + std::cout << "Error: " << sqlite3_errmsg(db_) << std::endl; + } + r = sqlite3_step(selectStatement); + if (r == SQLITE_ROW) { + result = boost::optional<JID>(reinterpret_cast<const char*>(sqlite3_column_text(selectStatement, 0))); + } + sqlite3_finalize(selectStatement); + return result; +} + +boost::optional<int> SQLiteHistoryManager::getIDFromJID(const JID& jid) const { + boost::optional<int> result; + sqlite3_stmt* selectStatement; + String selectQuery("SELECT id FROM jids WHERE jid='" + jid.toString() + "'"); + int r = sqlite3_prepare(db_, selectQuery.getUTF8Data(), selectQuery.getUTF8Size(), &selectStatement, NULL); + if (r != SQLITE_OK) { + std::cout << "Error: " << sqlite3_errmsg(db_) << std::endl; + } + r = sqlite3_step(selectStatement); + if (r == SQLITE_ROW) { + result = boost::optional<int>(sqlite3_column_int(selectStatement, 0)); + } + sqlite3_finalize(selectStatement); + return result; +} + +} diff --git a/Swiften/History/SQLiteHistoryManager.h b/Swiften/History/SQLiteHistoryManager.h new file mode 100644 index 0000000..ace42f7 --- /dev/null +++ b/Swiften/History/SQLiteHistoryManager.h @@ -0,0 +1,27 @@ +#pragma once + +#include <boost/optional.hpp> + +#include "Swiften/History/HistoryManager.h" + +struct sqlite3; + +namespace Swift { + class SQLiteHistoryManager : public HistoryManager { + public: + SQLiteHistoryManager(const String& file); + ~SQLiteHistoryManager(); + + virtual void addMessage(const HistoryMessage& message); + virtual std::vector<HistoryMessage> getMessages() const; + + int getIDForJID(const JID&); + int addJID(const JID&); + + boost::optional<JID> getJIDFromID(int id) const; + boost::optional<int> getIDFromJID(const JID& jid) const; + + private: + sqlite3* db_; + }; +} diff --git a/Swiften/History/UnitTest/SQLiteHistoryManagerTest.cpp b/Swiften/History/UnitTest/SQLiteHistoryManagerTest.cpp new file mode 100644 index 0000000..e738a6e --- /dev/null +++ b/Swiften/History/UnitTest/SQLiteHistoryManagerTest.cpp @@ -0,0 +1,109 @@ +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> +#include <boost/date_time/posix_time/posix_time.hpp> + +#include "Swiften/History/SQLiteHistoryManager.h" + +using namespace Swift; + +class SQLiteHistoryManagerTest : public CppUnit::TestFixture { + CPPUNIT_TEST_SUITE(SQLiteHistoryManagerTest); + CPPUNIT_TEST(testAddMessage); + CPPUNIT_TEST(testAddMessage_TwoMessages); + CPPUNIT_TEST(testGetIDForJID_SameJID); + CPPUNIT_TEST(testGetIDForJID_DifferentJIDs); + CPPUNIT_TEST(getJIDFromID); + CPPUNIT_TEST(getJIDFromID_UnexistingID); + CPPUNIT_TEST(getIDFromJID); + CPPUNIT_TEST(getIDFromJID_UnexistingJID); + CPPUNIT_TEST_SUITE_END(); + + public: + SQLiteHistoryManagerTest() {} + + void setUp() { + } + + void tearDown() { + } + + void testAddMessage() { + std::auto_ptr<SQLiteHistoryManager> testling(createHistoryManager()); + HistoryMessage testMessage("Test", JID("foo@bar.com"), JID("fum@baz.org"), boost::posix_time::time_from_string("1980-01-21 22:03")); + testling->addMessage(testMessage); + + std::vector<HistoryMessage> messages = testling->getMessages(); + CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(messages.size())); + CPPUNIT_ASSERT(testMessage == messages[0]); + } + + void testAddMessage_TwoMessages() { + std::auto_ptr<SQLiteHistoryManager> testling(createHistoryManager()); + HistoryMessage testMessage1("Test1", JID("foo@bar.com"), JID("fum@baz.org"), boost::posix_time::time_from_string("1980-01-21 22:03")); + testling->addMessage(testMessage1); + HistoryMessage testMessage2("Test2", JID("fum@baz.org"), JID("foo@bar.com"), boost::posix_time::time_from_string("1975-03-09 22:04")); + testling->addMessage(testMessage2); + + std::vector<HistoryMessage> messages = testling->getMessages(); + CPPUNIT_ASSERT_EQUAL(2, static_cast<int>(messages.size())); + CPPUNIT_ASSERT(testMessage1 == messages[0]); + CPPUNIT_ASSERT(testMessage2 == messages[1]); + } + + void testGetIDForJID_SameJID() { + std::auto_ptr<SQLiteHistoryManager> testling(createHistoryManager()); + int id1 = testling->getIDForJID(JID("foo@bar.com")); + int id2 = testling->getIDForJID(JID("foo@bar.com")); + + CPPUNIT_ASSERT_EQUAL(id1, id2); + } + + void testGetIDForJID_DifferentJIDs() { + std::auto_ptr<SQLiteHistoryManager> testling(createHistoryManager()); + int id1 = testling->getIDForJID(JID("foo@bar.com")); + int id2 = testling->getIDForJID(JID("foo@baz.com")); + + CPPUNIT_ASSERT(id1 != id2); + } + + void getJIDFromID() { + std::auto_ptr<SQLiteHistoryManager> testling(createHistoryManager()); + int id = testling->addJID(JID("foo@bar.com")); + + boost::optional<JID> result(testling->getJIDFromID(id)); + CPPUNIT_ASSERT(result); + CPPUNIT_ASSERT_EQUAL(JID("foo@bar.com"), *result); + } + + void getJIDFromID_UnexistingID() { + std::auto_ptr<SQLiteHistoryManager> testling(createHistoryManager()); + + boost::optional<JID> result(testling->getJIDFromID(1)); + + CPPUNIT_ASSERT(!result); + } + + void getIDFromJID() { + std::auto_ptr<SQLiteHistoryManager> testling(createHistoryManager()); + int id = testling->addJID(JID("foo@bar.com")); + + boost::optional<int> result(testling->getIDFromJID(JID("foo@bar.com"))); + CPPUNIT_ASSERT(result); + CPPUNIT_ASSERT_EQUAL(id, *result); + } + + void getIDFromJID_UnexistingJID() { + std::auto_ptr<SQLiteHistoryManager> testling(createHistoryManager()); + + boost::optional<int> result(testling->getIDFromJID(JID("foo@bar.com"))); + + CPPUNIT_ASSERT(!result); + } + + private: + SQLiteHistoryManager* createHistoryManager() { + return new SQLiteHistoryManager(":memory:"); + } +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(SQLiteHistoryManagerTest); diff --git a/Swiften/JID/JID.cpp b/Swiften/JID/JID.cpp new file mode 100644 index 0000000..3be8386 --- /dev/null +++ b/Swiften/JID/JID.cpp @@ -0,0 +1,86 @@ +#include <stringprep.h> +#include <vector> +#include <iostream> + +#include "Swiften/JID/JID.h" +#include "Swiften/StringPrep/StringPrep.h" + +namespace Swift { + +JID::JID(const char* jid) { + initializeFromString(String(jid)); +} + +JID::JID(const String& jid) { + initializeFromString(jid); +} + +JID::JID(const String& node, const String& domain) : hasResource_(false) { + nameprepAndSetComponents(node, domain, ""); +} + +JID::JID(const String& node, const String& domain, const String& resource) : hasResource_(true) { + nameprepAndSetComponents(node, domain, resource); +} + +void JID::initializeFromString(const String& jid) { + if (jid.beginsWith('@')) { + return; + } + + String bare, resource; + size_t slashIndex = jid.find('/'); + if (slashIndex != jid.npos()) { + hasResource_ = true; + bare = jid.getSubstring(0, slashIndex); + resource = jid.getSubstring(slashIndex + 1, jid.npos()); + } + else { + hasResource_ = false; + bare = jid; + } + std::pair<String,String> nodeAndDomain = bare.getSplittedAtFirst('@'); + if (nodeAndDomain.second.isEmpty()) { + nameprepAndSetComponents("", nodeAndDomain.first, resource); + } + else { + nameprepAndSetComponents(nodeAndDomain.first, nodeAndDomain.second, resource); + } +} + + +void JID::nameprepAndSetComponents(const String& node, const String& domain, const String& resource) { + node_ = StringPrep::getPrepared(node, StringPrep::NamePrep); + domain_ = StringPrep::getPrepared(domain, StringPrep::XMPPNodePrep); + resource_ = StringPrep::getPrepared(resource, StringPrep::XMPPResourcePrep); +} + +String JID::toString() const { + String string; + if (!node_.isEmpty()) { + string += node_ + "@"; + } + string += domain_; + if (!isBare()) { + string += "/" + resource_; + } + return string; +} + +int JID::compare(const Swift::JID& o, CompareType compareType) const { + if (node_ < o.node_) { return -1; } + if (node_ > o.node_) { return 1; } + if (domain_ < o.domain_) { return -1; } + if (domain_ > o.domain_) { return 1; } + if (compareType == WithResource) { + if (hasResource_ != o.hasResource_) { + return hasResource_ ? 1 : -1; + } + if (resource_ < o.resource_) { return -1; } + if (resource_ > o.resource_) { return 1; } + } + return 0; +} + +} // namespace Swift + diff --git a/Swiften/JID/JID.h b/Swiften/JID/JID.h new file mode 100644 index 0000000..0bfb858 --- /dev/null +++ b/Swiften/JID/JID.h @@ -0,0 +1,80 @@ +#pragma once + +#include "Swiften/Base/String.h" + +namespace Swift { + class JID { + public: + enum CompareType { + WithResource, WithoutResource + }; + + explicit JID(const String& = String()); + explicit JID(const char*); + JID(const String& node, const String& domain); + JID(const String& node, const String& domain, const String& resource); + + bool isValid() const { + return !domain_.isEmpty(); /* FIXME */ + } + + const String& getNode() const { + return node_; + } + const String& getDomain() const { + return domain_; + } + const String& getResource() const { + return resource_; + } + bool isBare() const { + return !hasResource_; + } + + JID toBare() const { + return JID(getNode(), getDomain()); /* FIXME: Duplicate unnecessary nameprepping. Probably ok. */ + } + + String toString() const; + + bool equals(const JID& o, CompareType compareType) const { + return compare(o, compareType) == 0; + } + + int compare(const JID& o, CompareType compareType) const; + + operator String() const { + return toString(); + } + + bool operator<(const Swift::JID& b) const { + return compare(b, Swift::JID::WithResource) < 0; + } + + friend std::ostream& operator<<(std::ostream& os, const Swift::JID& j) { + os << j.toString(); + return os; + } + + friend bool operator==(const Swift::JID& a, const Swift::JID& b) { + return a.compare(b, Swift::JID::WithResource) == 0; + } + + friend bool operator!=(const Swift::JID& a, const Swift::JID& b) { + return a.compare(b, Swift::JID::WithResource) != 0; + } + + protected: + void nameprepAndSetComponents(const String& node, const String& domain, + const String& resource); + + private: + void initializeFromString(const String&); + + private: + String node_; + String domain_; + bool hasResource_; + String resource_; + }; +} diff --git a/Swiften/JID/SConscript b/Swiften/JID/SConscript new file mode 100644 index 0000000..d48fbb0 --- /dev/null +++ b/Swiften/JID/SConscript @@ -0,0 +1,9 @@ +Import("swiften_env") + +myenv = swiften_env.Clone() +myenv.MergeFlags(swiften_env["LIBIDN_FLAGS"]) + +objects = myenv.StaticObject([ + "JID.cpp", + ]) +swiften_env.Append(SWIFTEN_OBJECTS = [objects]) diff --git a/Swiften/JID/UnitTest/JIDTest.cpp b/Swiften/JID/UnitTest/JIDTest.cpp new file mode 100644 index 0000000..917f89f --- /dev/null +++ b/Swiften/JID/UnitTest/JIDTest.cpp @@ -0,0 +1,310 @@ +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> + +#include "Swiften/JID/JID.h" + +using namespace Swift; + +class JIDTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(JIDTest); + CPPUNIT_TEST(testConstructorWithString); + CPPUNIT_TEST(testConstructorWithString_NoResource); + CPPUNIT_TEST(testConstructorWithString_NoNode); + CPPUNIT_TEST(testConstructorWithString_EmptyResource); + CPPUNIT_TEST(testConstructorWithString_OnlyDomain); + CPPUNIT_TEST(testConstructorWithString_UpperCaseNode); + CPPUNIT_TEST(testConstructorWithString_UpperCaseDomain); + CPPUNIT_TEST(testConstructorWithString_UpperCaseResource); + CPPUNIT_TEST(testConstructorWithString_EmptyNode); + CPPUNIT_TEST(testConstructorWithStrings); + CPPUNIT_TEST(testIsBare); + CPPUNIT_TEST(testIsBare_NotBare); + CPPUNIT_TEST(testToBare); + CPPUNIT_TEST(testToBare_EmptyNode); + CPPUNIT_TEST(testToBare_EmptyResource); + CPPUNIT_TEST(testToString); + CPPUNIT_TEST(testToString_EmptyNode); + CPPUNIT_TEST(testToString_EmptyResource); + CPPUNIT_TEST(testToString_NoResource); + CPPUNIT_TEST(testCompare_SmallerNode); + CPPUNIT_TEST(testCompare_LargerNode); + CPPUNIT_TEST(testCompare_SmallerDomain); + CPPUNIT_TEST(testCompare_LargerDomain); + CPPUNIT_TEST(testCompare_SmallerResource); + CPPUNIT_TEST(testCompare_LargerResource); + CPPUNIT_TEST(testCompare_Equal); + CPPUNIT_TEST(testCompare_EqualWithoutResource); + CPPUNIT_TEST(testCompare_NoResourceAndEmptyResource); + CPPUNIT_TEST(testCompare_EmptyResourceAndNoResource); + CPPUNIT_TEST(testEquals); + CPPUNIT_TEST(testEquals_NotEqual); + CPPUNIT_TEST(testEquals_WithoutResource); + CPPUNIT_TEST(testSmallerThan); + CPPUNIT_TEST(testSmallerThan_Equal); + CPPUNIT_TEST(testSmallerThan_Larger); + CPPUNIT_TEST(testHasResource); + CPPUNIT_TEST(testHasResource_NoResource); + CPPUNIT_TEST_SUITE_END(); + + public: + JIDTest() {} + + void testConstructorWithString() { + JID testling("foo@bar/baz"); + + CPPUNIT_ASSERT_EQUAL(String("foo"), testling.getNode()); + CPPUNIT_ASSERT_EQUAL(String("bar"), testling.getDomain()); + CPPUNIT_ASSERT_EQUAL(String("baz"), testling.getResource()); + CPPUNIT_ASSERT(!testling.isBare()); + } + + void testConstructorWithString_NoResource() { + JID testling("foo@bar"); + + CPPUNIT_ASSERT_EQUAL(String("foo"), testling.getNode()); + CPPUNIT_ASSERT_EQUAL(String("bar"), testling.getDomain()); + CPPUNIT_ASSERT_EQUAL(String(""), testling.getResource()); + CPPUNIT_ASSERT(testling.isBare()); + } + + void testConstructorWithString_EmptyResource() { + JID testling("bar/"); + + CPPUNIT_ASSERT(testling.isValid()); + CPPUNIT_ASSERT(!testling.isBare()); + } + + void testConstructorWithString_NoNode() { + JID testling("bar/baz"); + + CPPUNIT_ASSERT_EQUAL(String(""), testling.getNode()); + CPPUNIT_ASSERT_EQUAL(String("bar"), testling.getDomain()); + CPPUNIT_ASSERT_EQUAL(String("baz"), testling.getResource()); + CPPUNIT_ASSERT(!testling.isBare()); + } + + void testConstructorWithString_OnlyDomain() { + JID testling("bar"); + + CPPUNIT_ASSERT_EQUAL(String(""), testling.getNode()); + CPPUNIT_ASSERT_EQUAL(String("bar"), testling.getDomain()); + CPPUNIT_ASSERT_EQUAL(String(""), testling.getResource()); + CPPUNIT_ASSERT(testling.isBare()); + } + + void testConstructorWithString_UpperCaseNode() { + JID testling("Fo\xCE\xA9@bar"); + + CPPUNIT_ASSERT_EQUAL(String("fo\xCF\x89"), testling.getNode()); + CPPUNIT_ASSERT_EQUAL(String("bar"), testling.getDomain()); + } + + void testConstructorWithString_UpperCaseDomain() { + JID testling("Fo\xCE\xA9"); + + CPPUNIT_ASSERT_EQUAL(String("fo\xCF\x89"), testling.getDomain()); + } + + void testConstructorWithString_UpperCaseResource() { + JID testling("bar/Fo\xCE\xA9"); + + CPPUNIT_ASSERT_EQUAL(testling.getResource(), String("Fo\xCE\xA9")); + } + + void testConstructorWithString_EmptyNode() { + JID testling("@bar"); + + CPPUNIT_ASSERT(!testling.isValid()); + } + + void testConstructorWithStrings() { + JID testling("foo", "bar", "baz"); + + CPPUNIT_ASSERT_EQUAL(String("foo"), testling.getNode()); + CPPUNIT_ASSERT_EQUAL(String("bar"), testling.getDomain()); + CPPUNIT_ASSERT_EQUAL(String("baz"), testling.getResource()); + } + + void testIsBare() { + CPPUNIT_ASSERT(JID("foo@bar").isBare()); + } + + void testIsBare_NotBare() { + CPPUNIT_ASSERT(!JID("foo@bar/baz").isBare()); + } + + void testToBare() { + JID testling("foo@bar/baz"); + + CPPUNIT_ASSERT_EQUAL(String("foo"), testling.toBare().getNode()); + CPPUNIT_ASSERT_EQUAL(String("bar"), testling.toBare().getDomain()); + CPPUNIT_ASSERT(testling.toBare().isBare()); + } + + void testToBare_EmptyNode() { + JID testling("bar/baz"); + + CPPUNIT_ASSERT_EQUAL(String(""), testling.toBare().getNode()); + CPPUNIT_ASSERT_EQUAL(String("bar"), testling.toBare().getDomain()); + CPPUNIT_ASSERT(testling.toBare().isBare()); + } + + void testToBare_EmptyResource() { + JID testling("bar/"); + + CPPUNIT_ASSERT_EQUAL(String(""), testling.toBare().getNode()); + CPPUNIT_ASSERT_EQUAL(String("bar"), testling.toBare().getDomain()); + CPPUNIT_ASSERT(testling.toBare().isBare()); + } + + void testToString() { + JID testling("foo@bar/baz"); + + CPPUNIT_ASSERT_EQUAL(String("foo@bar/baz"), testling.toString()); + } + + void testToString_EmptyNode() { + JID testling("bar/baz"); + + CPPUNIT_ASSERT_EQUAL(String("bar/baz"), testling.toString()); + } + + void testToString_NoResource() { + JID testling("foo@bar"); + + CPPUNIT_ASSERT_EQUAL(String("foo@bar"), testling.toString()); + } + + void testToString_EmptyResource() { + JID testling("foo@bar/"); + + CPPUNIT_ASSERT_EQUAL(String("foo@bar/"), testling.toString()); + } + + void testCompare_SmallerNode() { + JID testling1("a@c"); + JID testling2("b@b"); + + CPPUNIT_ASSERT_EQUAL(-1, testling1.compare(testling2, JID::WithResource)); + } + + void testCompare_LargerNode() { + JID testling1("c@a"); + JID testling2("b@b"); + + CPPUNIT_ASSERT_EQUAL(1, testling1.compare(testling2, JID::WithResource)); + } + + void testCompare_SmallerDomain() { + JID testling1("x@a/c"); + JID testling2("x@b/b"); + + CPPUNIT_ASSERT_EQUAL(-1, testling1.compare(testling2, JID::WithResource)); + } + + void testCompare_LargerDomain() { + JID testling1("x@b/b"); + JID testling2("x@a/c"); + + CPPUNIT_ASSERT_EQUAL(1, testling1.compare(testling2, JID::WithResource)); + } + + void testCompare_SmallerResource() { + JID testling1("x@y/a"); + JID testling2("x@y/b"); + + CPPUNIT_ASSERT_EQUAL(-1, testling1.compare(testling2, JID::WithResource)); + } + + void testCompare_LargerResource() { + JID testling1("x@y/b"); + JID testling2("x@y/a"); + + CPPUNIT_ASSERT_EQUAL(1, testling1.compare(testling2, JID::WithResource)); + } + + void testCompare_Equal() { + JID testling1("x@y/z"); + JID testling2("x@y/z"); + + CPPUNIT_ASSERT_EQUAL(0, testling1.compare(testling2, JID::WithResource)); + } + + void testCompare_EqualWithoutResource() { + JID testling1("x@y/a"); + JID testling2("x@y/b"); + + CPPUNIT_ASSERT_EQUAL(0, testling1.compare(testling2, JID::WithoutResource)); + } + + void testCompare_NoResourceAndEmptyResource() { + JID testling1("x@y/"); + JID testling2("x@y"); + + CPPUNIT_ASSERT_EQUAL(1, testling1.compare(testling2, JID::WithResource)); + } + + void testCompare_EmptyResourceAndNoResource() { + JID testling1("x@y"); + JID testling2("x@y/"); + + CPPUNIT_ASSERT_EQUAL(-1, testling1.compare(testling2, JID::WithResource)); + } + + void testEquals() { + JID testling1("x@y/c"); + JID testling2("x@y/c"); + + CPPUNIT_ASSERT(testling1.equals(testling2, JID::WithResource)); + } + + void testEquals_NotEqual() { + JID testling1("x@y/c"); + JID testling2("x@y/d"); + + CPPUNIT_ASSERT(!testling1.equals(testling2, JID::WithResource)); + } + + void testEquals_WithoutResource() { + JID testling1("x@y/c"); + JID testling2("x@y/d"); + + CPPUNIT_ASSERT(testling1.equals(testling2, JID::WithoutResource)); + } + + void testSmallerThan() { + JID testling1("x@y/c"); + JID testling2("x@y/d"); + + CPPUNIT_ASSERT(testling1 < testling2); + } + + void testSmallerThan_Equal() { + JID testling1("x@y/d"); + JID testling2("x@y/d"); + + CPPUNIT_ASSERT(!(testling1 < testling2)); + } + + void testSmallerThan_Larger() { + JID testling1("x@y/d"); + JID testling2("x@y/c"); + + CPPUNIT_ASSERT(!(testling1 < testling2)); + } + + void testHasResource() { + JID testling("x@y/d"); + + CPPUNIT_ASSERT(!testling.isBare()); + } + + void testHasResource_NoResource() { + JID testling("x@y"); + + CPPUNIT_ASSERT(testling.isBare()); + } +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(JIDTest); diff --git a/Swiften/LinkLocal/DNSSD/Avahi/AvahiBrowseQuery.h b/Swiften/LinkLocal/DNSSD/Avahi/AvahiBrowseQuery.h new file mode 100644 index 0000000..229f97e --- /dev/null +++ b/Swiften/LinkLocal/DNSSD/Avahi/AvahiBrowseQuery.h @@ -0,0 +1,64 @@ +#pragma once + +#include <boost/bind.hpp> + +#include "Swiften/LinkLocal/DNSSD/Avahi/AvahiQuery.h" +#include "Swiften/LinkLocal/DNSSD/DNSSDBrowseQuery.h" +#include "Swiften/EventLoop/MainEventLoop.h" + +namespace Swift { + class AvahiQuerier; + + class AvahiBrowseQuery : public DNSSDBrowseQuery, public AvahiQuery { + public: + AvahiBrowseQuery(boost::shared_ptr<AvahiQuerier> q) : AvahiQuery(q) { + } + + void startBrowsing() { + std::cout << "Start browsing" << std::endl; + avahi_threaded_poll_lock(querier->getThreadedPoll()); + std::cout << "Creating browser" << std::endl; + AvahiServiceBrowser* browser = avahi_service_browser_new(querier->getClient(), AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, "_presence._tcp", NULL, (AvahiLookupFlags) 0, &handleServiceDiscoveredStatic, this); + if (!browser) { + std::cout << "Error" << std::endl; + MainEventLoop::postEvent(boost::bind(boost::ref(onError)), shared_from_this()); + } + std::cout << "Unlocking" << std::endl; + avahi_threaded_poll_unlock(querier->getThreadedPoll()); + std::cout << "Browse started" << std::endl; + } + + void stopBrowsing() { + // TODO + } + + private: + static void handleServiceDiscoveredStatic(AvahiServiceBrowser *b, AvahiIfIndex interfaceIndex, AvahiProtocol protocol, AvahiBrowserEvent event, const char *name, const char *type, const char *domain, AvahiLookupResultFlags flags, void* context) { + static_cast<AvahiBrowseQuery*>(context)->handleServiceDiscovered(b, interfaceIndex, protocol, event, name, type, domain, flags); + } + + void handleServiceDiscovered(AvahiServiceBrowser *, AvahiIfIndex interfaceIndex, AvahiProtocol, AvahiBrowserEvent event, const char *name, const char *type, const char *domain, AvahiLookupResultFlags) { + switch (event) { + case AVAHI_BROWSER_FAILURE: + std::cout << "Service browse error" << std::endl; + MainEventLoop::postEvent(boost::bind(boost::ref(onError)), shared_from_this()); + break; + case AVAHI_BROWSER_NEW: { + DNSSDServiceID service(name, domain, type, interfaceIndex); + std::cout << "Service discovered " << name << " " << type << " " << domain << std::endl; + MainEventLoop::postEvent(boost::bind(boost::ref(onServiceAdded), service), shared_from_this()); + break; + } + case AVAHI_BROWSER_REMOVE: { + std::cout << "Service went away " << name << " " << type << " " << domain << std::endl; + DNSSDServiceID service(name, domain, type, interfaceIndex); + MainEventLoop::postEvent(boost::bind(boost::ref(onServiceRemoved), service), shared_from_this()); + break; + } + case AVAHI_BROWSER_ALL_FOR_NOW: + case AVAHI_BROWSER_CACHE_EXHAUSTED: + break; + } + } + }; +} diff --git a/Swiften/LinkLocal/DNSSD/Avahi/AvahiQuerier.cpp b/Swiften/LinkLocal/DNSSD/Avahi/AvahiQuerier.cpp new file mode 100644 index 0000000..c4bfcb4 --- /dev/null +++ b/Swiften/LinkLocal/DNSSD/Avahi/AvahiQuerier.cpp @@ -0,0 +1,60 @@ +#include "Swiften/LinkLocal/DNSSD/Avahi/AvahiQuerier.h" + +#include <iostream> + +#include "Swiften/LinkLocal/DNSSD/Avahi/AvahiBrowseQuery.h" +#include "Swiften/LinkLocal/DNSSD/Avahi/AvahiResolveServiceQuery.h" +#include "Swiften/LinkLocal/DNSSD/Avahi/AvahiResolveHostnameQuery.h" +#include "Swiften/LinkLocal/DNSSD/Avahi/AvahiRegisterQuery.h" + +namespace Swift { + +AvahiQuerier::AvahiQuerier() : client(NULL), threadedPoll(NULL) { +} + +AvahiQuerier::~AvahiQuerier() { +} + +boost::shared_ptr<DNSSDBrowseQuery> AvahiQuerier::createBrowseQuery() { + return boost::shared_ptr<DNSSDBrowseQuery>(new AvahiBrowseQuery(shared_from_this())); +} + +boost::shared_ptr<DNSSDRegisterQuery> AvahiQuerier::createRegisterQuery(const String& name, int port, const ByteArray& info) { + return boost::shared_ptr<DNSSDRegisterQuery>(new AvahiRegisterQuery(name, port, info, shared_from_this())); +} + +boost::shared_ptr<DNSSDResolveServiceQuery> AvahiQuerier::createResolveServiceQuery(const DNSSDServiceID& service) { + return boost::shared_ptr<DNSSDResolveServiceQuery>(new AvahiResolveServiceQuery(service, shared_from_this())); +} + +boost::shared_ptr<DNSSDResolveHostnameQuery> AvahiQuerier::createResolveHostnameQuery(const String& hostname, int interfaceIndex) { + return boost::shared_ptr<DNSSDResolveHostnameQuery>(new AvahiResolveHostnameQuery(hostname, interfaceIndex, shared_from_this())); +} + +void AvahiQuerier::start() { + std::cout << "Starrting querier" << std::endl; + assert(!threadedPoll); + threadedPoll = avahi_threaded_poll_new(); + int error; + assert(!client); + client = avahi_client_new( + avahi_threaded_poll_get(threadedPoll), + static_cast<AvahiClientFlags>(0), NULL, this, &error); // TODO + if (!client) { + // TODO + std::cerr << "Avahi Error: " << avahi_strerror(error) << std::endl; + return; + } + std::cout << "Starrting event loop" << std::endl; + avahi_threaded_poll_start(threadedPoll); +} + +void AvahiQuerier::stop() { + assert(threadedPoll); + avahi_threaded_poll_stop(threadedPoll); + assert(client); + avahi_client_free(client); + avahi_threaded_poll_free(threadedPoll); +} + +} diff --git a/Swiften/LinkLocal/DNSSD/Avahi/AvahiQuerier.h b/Swiften/LinkLocal/DNSSD/Avahi/AvahiQuerier.h new file mode 100644 index 0000000..ffb8441 --- /dev/null +++ b/Swiften/LinkLocal/DNSSD/Avahi/AvahiQuerier.h @@ -0,0 +1,47 @@ +#pragma once + +#include <boost/shared_ptr.hpp> +#include <boost/enable_shared_from_this.hpp> +#include <avahi-client/client.h> +#include <avahi-client/lookup.h> +#include <avahi-common/thread-watch.h> +#include <avahi-common/watch.h> +#include <avahi-common/malloc.h> +#include <avahi-common/error.h> + +#include "Swiften/LinkLocal/DNSSD/DNSSDQuerier.h" + +namespace Swift { + class ByteArray; + + class AvahiQuerier : + public DNSSDQuerier, + public boost::enable_shared_from_this<AvahiQuerier> { + public: + AvahiQuerier(); + ~AvahiQuerier(); + + boost::shared_ptr<DNSSDBrowseQuery> createBrowseQuery(); + boost::shared_ptr<DNSSDRegisterQuery> createRegisterQuery( + const String& name, int port, const ByteArray& info); + boost::shared_ptr<DNSSDResolveServiceQuery> createResolveServiceQuery( + const DNSSDServiceID&); + boost::shared_ptr<DNSSDResolveHostnameQuery> createResolveHostnameQuery( + const String& hostname, int interfaceIndex); + + void start(); + void stop(); + + AvahiThreadedPoll* getThreadedPoll() const { + return threadedPoll; + } + + AvahiClient* getClient() const { + return client; + } + + private: + AvahiClient* client; + AvahiThreadedPoll* threadedPoll; + }; +} diff --git a/Swiften/LinkLocal/DNSSD/Avahi/AvahiQuery.cpp b/Swiften/LinkLocal/DNSSD/Avahi/AvahiQuery.cpp new file mode 100644 index 0000000..6b7c7f9 --- /dev/null +++ b/Swiften/LinkLocal/DNSSD/Avahi/AvahiQuery.cpp @@ -0,0 +1,13 @@ +#include "Swiften/LinkLocal/DNSSD/Avahi/AvahiQuery.h" +#include "Swiften/LinkLocal/DNSSD/Avahi/AvahiQuerier.h" + +namespace Swift { + +AvahiQuery::AvahiQuery(boost::shared_ptr<AvahiQuerier> q) : querier(q) { +} + +AvahiQuery::~AvahiQuery() { +} + +} + diff --git a/Swiften/LinkLocal/DNSSD/Avahi/AvahiQuery.h b/Swiften/LinkLocal/DNSSD/Avahi/AvahiQuery.h new file mode 100644 index 0000000..847f3f7 --- /dev/null +++ b/Swiften/LinkLocal/DNSSD/Avahi/AvahiQuery.h @@ -0,0 +1,22 @@ +#pragma once + +#include <boost/shared_ptr.hpp> +#include <boost/enable_shared_from_this.hpp> + +#include "Swiften/EventLoop/EventOwner.h" + +namespace Swift { + class AvahiQuerier; + + class AvahiQuery : + public EventOwner, + public boost::enable_shared_from_this<AvahiQuery> { + public: + AvahiQuery(boost::shared_ptr<AvahiQuerier>); + virtual ~AvahiQuery(); + + protected: + boost::shared_ptr<AvahiQuerier> querier; + }; +} + diff --git a/Swiften/LinkLocal/DNSSD/Avahi/AvahiRegisterQuery.h b/Swiften/LinkLocal/DNSSD/Avahi/AvahiRegisterQuery.h new file mode 100644 index 0000000..f42950e --- /dev/null +++ b/Swiften/LinkLocal/DNSSD/Avahi/AvahiRegisterQuery.h @@ -0,0 +1,123 @@ +#pragma once + +#include <avahi-client/publish.h> + +#include "Swiften/LinkLocal/DNSSD/Avahi/AvahiQuery.h" +#include "Swiften/LinkLocal/DNSSD/DNSSDRegisterQuery.h" +#include "Swiften/Base/ByteArray.h" +#include "Swiften/EventLoop/MainEventLoop.h" + +namespace Swift { + class AvahiQuerier; + + class AvahiRegisterQuery : public DNSSDRegisterQuery, public AvahiQuery { + public: + AvahiRegisterQuery(const String& name, int port, const ByteArray& txtRecord, boost::shared_ptr<AvahiQuerier> querier) : AvahiQuery(querier), name(name), port(port), txtRecord(txtRecord), group(0) { + } + + void registerService() { + std::cout << "Registering service " << name << ":" << port << std::endl; + avahi_threaded_poll_lock(querier->getThreadedPoll()); + if (!group) { + std::cout << "Creating entry group" << std::endl; + group = avahi_entry_group_new(querier->getClient(), handleEntryGroupChange, this); + if (!group) { + std::cout << "Error ceating entry group" << std::endl; + MainEventLoop::postEvent(boost::bind(boost::ref(onRegisterFinished), boost::optional<DNSSDServiceID>()), shared_from_this()); + } + } + + doRegisterService(); + avahi_threaded_poll_unlock(querier->getThreadedPoll()); + } + + void unregisterService() { + } + + void updateServiceInfo(const ByteArray& txtRecord) { + this->txtRecord = txtRecord; + avahi_threaded_poll_lock(querier->getThreadedPoll()); + assert(group); + avahi_entry_group_reset(group); + doRegisterService(); + avahi_threaded_poll_unlock(querier->getThreadedPoll()); + } + + private: + void doRegisterService() { + AvahiStringList* txtList; + avahi_string_list_parse(txtRecord.getData(), txtRecord.getSize(), &txtList); + + int result = avahi_entry_group_add_service_strlst(group, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, (AvahiPublishFlags) 0, name.getUTF8Data(), "_presence._tcp", NULL, NULL, port, txtList); + if (result < 0) { + std::cout << "Error registering service: " << avahi_strerror(result) << std::endl; + MainEventLoop::postEvent(boost::bind(boost::ref(onRegisterFinished), boost::optional<DNSSDServiceID>()), shared_from_this()); + } + result = avahi_entry_group_commit(group); + if (result < 0) { + std::cout << "Error registering service: " << avahi_strerror(result) << std::endl; + } + } + + static void handleEntryGroupChange(AvahiEntryGroup *g, AvahiEntryGroupState state, void *userdata) { + static_cast<AvahiRegisterQuery*>(userdata)->handleEntryGroupChange(g, state); + } + + void handleEntryGroupChange(AvahiEntryGroup* g, AvahiEntryGroupState state) { + std::cout << "ENtry group callback: " << state << std::endl; + switch (state) { + case AVAHI_ENTRY_GROUP_ESTABLISHED : + // Domain is a hack! + MainEventLoop::postEvent(boost::bind(boost::ref(onRegisterFinished), boost::optional<DNSSDServiceID>(DNSSDServiceID(name, "local", "_presence._tcp", 0))), shared_from_this()); + std::cout << "Entry group established" << std::endl; + break; + case AVAHI_ENTRY_GROUP_COLLISION : { + std::cout << "Entry group collision" << std::endl; + /*char *n; + n = avahi_alternative_service_name(name); + avahi_free(name); + name = n;*/ + break; + } + + case AVAHI_ENTRY_GROUP_FAILURE : + std::cout << "Entry group failure " << avahi_strerror(avahi_client_errno(avahi_entry_group_get_client(g))) << std::endl; + break; + + case AVAHI_ENTRY_GROUP_UNCOMMITED: + case AVAHI_ENTRY_GROUP_REGISTERING: + ; + + /* + DNSServiceErrorType result = DNSServiceRegister( + &sdRef, 0, 0, name.getUTF8Data(), "_presence._tcp", NULL, NULL, port, + txtRecord.getSize(), txtRecord.getData(), + &AvahiRegisterQuery::handleServiceRegisteredStatic, this); + if (result != kDNSServiceErr_NoError) { + sdRef = NULL; + }*/ + //MainEventLoop::postEvent(boost::bind(boost::ref(onRegisterFinished), boost::optional<DNSSDServiceID>()), shared_from_this()); + } + } + +/* + static void handleServiceRegisteredStatic(DNSServiceRef, DNSServiceFlags, DNSServiceErrorType errorCode, const char *name, const char *regtype, const char *domain, void *context) { + static_cast<AvahiRegisterQuery*>(context)->handleServiceRegistered(errorCode, name, regtype, domain); + } + + void handleServiceRegistered(DNSServiceErrorType errorCode, const char *name, const char *regtype, const char *domain) { + if (errorCode != kDNSServiceErr_NoError) { + MainEventLoop::postEvent(boost::bind(boost::ref(onRegisterFinished), boost::optional<DNSSDServiceID>()), shared_from_this()); + } + else { + } + } + */ + + private: + String name; + int port; + ByteArray txtRecord; + AvahiEntryGroup* group; + }; +} diff --git a/Swiften/LinkLocal/DNSSD/Avahi/AvahiResolveHostnameQuery.h b/Swiften/LinkLocal/DNSSD/Avahi/AvahiResolveHostnameQuery.h new file mode 100644 index 0000000..ee0e837 --- /dev/null +++ b/Swiften/LinkLocal/DNSSD/Avahi/AvahiResolveHostnameQuery.h @@ -0,0 +1,31 @@ +#pragma once + +#include "Swiften/Base/String.h" +#include "Swiften/LinkLocal/DNSSD/Avahi/AvahiQuery.h" +#include "Swiften/LinkLocal/DNSSD/DNSSDResolveHostnameQuery.h" +#include "Swiften/EventLoop/MainEventLoop.h" +#include "Swiften/Network/HostAddress.h" + +#include <netinet/in.h> + +namespace Swift { + class AvahiQuerier; + + class AvahiResolveHostnameQuery : public DNSSDResolveHostnameQuery, public AvahiQuery { + public: + AvahiResolveHostnameQuery(const String& hostname, int, boost::shared_ptr<AvahiQuerier> querier) : AvahiQuery(querier), hostname(hostname) { + std::cout << "Resolving hostname " << hostname << std::endl; + } + + void run() { + MainEventLoop::postEvent(boost::bind(boost::ref(onHostnameResolved), boost::optional<HostAddress>(HostAddress(hostname))), shared_from_this()); + } + + void finish() { + } + + private: + HostAddress hostAddress; + String hostname; + }; +} diff --git a/Swiften/LinkLocal/DNSSD/Avahi/AvahiResolveServiceQuery.h b/Swiften/LinkLocal/DNSSD/Avahi/AvahiResolveServiceQuery.h new file mode 100644 index 0000000..8577837 --- /dev/null +++ b/Swiften/LinkLocal/DNSSD/Avahi/AvahiResolveServiceQuery.h @@ -0,0 +1,73 @@ +#pragma once + +#include "Swiften/LinkLocal/DNSSD/Avahi/AvahiQuery.h" +#include "Swiften/LinkLocal/DNSSD/DNSSDResolveServiceQuery.h" +#include "Swiften/LinkLocal/LinkLocalServiceInfo.h" +#include "Swiften/Base/ByteArray.h" +#include "Swiften/EventLoop/MainEventLoop.h" + +namespace Swift { + class AvahiQuerier; + + class AvahiResolveServiceQuery : public DNSSDResolveServiceQuery, public AvahiQuery { + public: + AvahiResolveServiceQuery(const DNSSDServiceID& service, boost::shared_ptr<AvahiQuerier> querier) : AvahiQuery(querier), service(service), resolver(NULL) { + } + + void start() { + std::cout << "Resolving " << service.getName() << std::endl; + avahi_threaded_poll_lock(querier->getThreadedPoll()); + assert(!resolver); + resolver = avahi_service_resolver_new(querier->getClient(), service.getNetworkInterfaceID(), AVAHI_PROTO_UNSPEC, service.getName().getUTF8Data(), service.getType().getUTF8Data(), service.getDomain().getUTF8Data(), AVAHI_PROTO_UNSPEC, (AvahiLookupFlags) 0, handleServiceResolvedStatic, this); + if (!resolver) { + std::cout << "Error starting resolver" << std::endl; + MainEventLoop::postEvent(boost::bind(boost::ref(onServiceResolved), boost::optional<Result>()), shared_from_this()); + } + avahi_threaded_poll_unlock(querier->getThreadedPoll()); + } + + void stop() { + avahi_threaded_poll_lock(querier->getThreadedPoll()); + avahi_service_resolver_free(resolver); + resolver = NULL; + avahi_threaded_poll_unlock(querier->getThreadedPoll()); + } + + private: + static void handleServiceResolvedStatic(AvahiServiceResolver* resolver, AvahiIfIndex interfaceIndex, AvahiProtocol protocol, AvahiResolverEvent event, const char *name, const char *type, const char *domain, const char *host_name, const AvahiAddress *address, uint16_t port, AvahiStringList *txt, AvahiLookupResultFlags flags, void* context) { + static_cast<AvahiResolveServiceQuery*>(context)->handleServiceResolved(resolver, interfaceIndex, protocol, event, name, type, domain, host_name, address, port, txt, flags); + } + + void handleServiceResolved(AvahiServiceResolver* resolver, AvahiIfIndex, AvahiProtocol, AvahiResolverEvent event, const char *name, const char * type, const char* domain, const char * /*host_name*/, const AvahiAddress *address, uint16_t port, AvahiStringList *txt, AvahiLookupResultFlags) { + std::cout << "Resolve finished" << std::endl; + switch(event) { + case AVAHI_RESOLVER_FAILURE: + std::cout << "Resolve error " << avahi_strerror(avahi_client_errno(avahi_service_resolver_get_client(resolver))) << std::endl; + MainEventLoop::postEvent(boost::bind(boost::ref(onServiceResolved), boost::optional<Result>()), shared_from_this()); + break; + case AVAHI_RESOLVER_FOUND: { + char a[AVAHI_ADDRESS_STR_MAX]; + avahi_address_snprint(a, sizeof(a), address); + + ByteArray txtRecord; + txtRecord.resize(1024); + avahi_string_list_serialize(txt, txtRecord.getData(), txtRecord.getSize()); + + // FIXME: Probably not accurate + String fullname = String(name) + "." + String(type) + "." + String(domain) + "."; + std::cout << "Result: " << fullname << "->" << String(a) << ":" << port << std::endl; + MainEventLoop::postEvent( + boost::bind( + boost::ref(onServiceResolved), + Result(fullname, String(a), port, txtRecord)), + shared_from_this()); + break; + } + } + } + + private: + DNSSDServiceID service; + AvahiServiceResolver* resolver; + }; +} diff --git a/Swiften/LinkLocal/DNSSD/Bonjour/BonjourBrowseQuery.h b/Swiften/LinkLocal/DNSSD/Bonjour/BonjourBrowseQuery.h new file mode 100644 index 0000000..2dec2fb --- /dev/null +++ b/Swiften/LinkLocal/DNSSD/Bonjour/BonjourBrowseQuery.h @@ -0,0 +1,55 @@ +#pragma once + +#include "Swiften/LinkLocal/DNSSD/Bonjour/BonjourQuery.h" +#include "Swiften/LinkLocal/DNSSD/DNSSDBrowseQuery.h" +#include "Swiften/EventLoop/MainEventLoop.h" + +namespace Swift { + class BonjourQuerier; + + class BonjourBrowseQuery : public DNSSDBrowseQuery, public BonjourQuery { + public: + BonjourBrowseQuery(boost::shared_ptr<BonjourQuerier> q) : BonjourQuery(q) { + DNSServiceErrorType result = DNSServiceBrowse( + &sdRef, 0, 0, "_presence._tcp", 0, + &BonjourBrowseQuery::handleServiceDiscoveredStatic, this); + if (result != kDNSServiceErr_NoError) { + sdRef = NULL; + } + } + + void startBrowsing() { + if (!sdRef) { + MainEventLoop::postEvent(boost::bind(boost::ref(onError)), shared_from_this()); + } + else { + run(); + } + } + + void stopBrowsing() { + finish(); + } + + private: + static void handleServiceDiscoveredStatic(DNSServiceRef, DNSServiceFlags flags, uint32_t interfaceIndex, DNSServiceErrorType errorCode, const char *name, const char *type, const char *domain, void *context) { + static_cast<BonjourBrowseQuery*>(context)->handleServiceDiscovered(flags, interfaceIndex, errorCode, name, type, domain); + } + + void handleServiceDiscovered(DNSServiceFlags flags, uint32_t interfaceIndex, DNSServiceErrorType errorCode, const char *name, const char *type, const char *domain) { + if (errorCode != kDNSServiceErr_NoError) { + MainEventLoop::postEvent(boost::bind(boost::ref(onError)), shared_from_this()); + } + else { + //std::cout << "Discovered service: name:" << name << " domain:" << domain << " type: " << type << std::endl; + DNSSDServiceID service(name, domain, type, interfaceIndex); + if (flags & kDNSServiceFlagsAdd) { + MainEventLoop::postEvent(boost::bind(boost::ref(onServiceAdded), service), shared_from_this()); + } + else { + MainEventLoop::postEvent(boost::bind(boost::ref(onServiceRemoved), service), shared_from_this()); + } + } + } + }; +} diff --git a/Swiften/LinkLocal/DNSSD/Bonjour/BonjourQuerier.cpp b/Swiften/LinkLocal/DNSSD/Bonjour/BonjourQuerier.cpp new file mode 100644 index 0000000..9c9e64e --- /dev/null +++ b/Swiften/LinkLocal/DNSSD/Bonjour/BonjourQuerier.cpp @@ -0,0 +1,129 @@ +#include "Swiften/LinkLocal/DNSSD/Bonjour/BonjourQuerier.h" + +#include <unistd.h> +#include <sys/socket.h> +#include <fcntl.h> + +#include "Swiften/LinkLocal/DNSSD/Bonjour/BonjourBrowseQuery.h" +#include "Swiften/LinkLocal/DNSSD/Bonjour/BonjourRegisterQuery.h" +#include "Swiften/LinkLocal/DNSSD/Bonjour/BonjourResolveServiceQuery.h" +#include "Swiften/LinkLocal/DNSSD/Bonjour/BonjourResolveHostnameQuery.h" +#include "Swiften/Base/foreach.h" + +namespace Swift { + +BonjourQuerier::BonjourQuerier() : stopRequested(false), thread(0) { + int fds[2]; + int result = pipe(fds); + assert(result == 0); + interruptSelectReadSocket = fds[0]; + fcntl(interruptSelectReadSocket, F_SETFL, fcntl(interruptSelectReadSocket, F_GETFL)|O_NONBLOCK); + interruptSelectWriteSocket = fds[1]; +} + +BonjourQuerier::~BonjourQuerier() { + assert(!thread); +} + +boost::shared_ptr<DNSSDBrowseQuery> BonjourQuerier::createBrowseQuery() { + return boost::shared_ptr<DNSSDBrowseQuery>(new BonjourBrowseQuery(shared_from_this())); +} + +boost::shared_ptr<DNSSDRegisterQuery> BonjourQuerier::createRegisterQuery(const String& name, int port, const ByteArray& info) { + return boost::shared_ptr<DNSSDRegisterQuery>(new BonjourRegisterQuery(name, port, info, shared_from_this())); +} + +boost::shared_ptr<DNSSDResolveServiceQuery> BonjourQuerier::createResolveServiceQuery(const DNSSDServiceID& service) { + return boost::shared_ptr<DNSSDResolveServiceQuery>(new BonjourResolveServiceQuery(service, shared_from_this())); +} + +boost::shared_ptr<DNSSDResolveHostnameQuery> BonjourQuerier::createResolveHostnameQuery(const String& hostname, int interfaceIndex) { + return boost::shared_ptr<DNSSDResolveHostnameQuery>(new BonjourResolveHostnameQuery(hostname, interfaceIndex, shared_from_this())); +} + +void BonjourQuerier::addRunningQuery(boost::shared_ptr<BonjourQuery> query) { + { + boost::lock_guard<boost::mutex> lock(runningQueriesMutex); + runningQueries.push_back(query); + } + runningQueriesAvailableEvent.notify_one(); + interruptSelect(); +} + +void BonjourQuerier::removeRunningQuery(boost::shared_ptr<BonjourQuery> query) { + { + boost::lock_guard<boost::mutex> lock(runningQueriesMutex); + runningQueries.erase(std::remove( + runningQueries.begin(), runningQueries.end(), query), runningQueries.end()); + } +} + +void BonjourQuerier::interruptSelect() { + char c = 0; + write(interruptSelectWriteSocket, &c, 1); +} + +void BonjourQuerier::start() { + assert(!thread); + thread = new boost::thread(boost::bind(&BonjourQuerier::run, shared_from_this())); +} + +void BonjourQuerier::stop() { + if (thread) { + stopRequested = true; + assert(runningQueries.empty()); + runningQueriesAvailableEvent.notify_one(); + interruptSelect(); + thread->join(); + delete thread; + thread = NULL; + stopRequested = false; + } +} + +void BonjourQuerier::run() { + while (!stopRequested) { + fd_set fdSet; + int maxSocket; + { + boost::unique_lock<boost::mutex> lock(runningQueriesMutex); + if (runningQueries.empty()) { + runningQueriesAvailableEvent.wait(lock); + if (runningQueries.empty()) { + continue; + } + } + + // Run all running queries + FD_ZERO(&fdSet); + maxSocket = interruptSelectReadSocket; + FD_SET(interruptSelectReadSocket, &fdSet); + + foreach(const boost::shared_ptr<BonjourQuery>& query, runningQueries) { + int socketID = query->getSocketID(); + maxSocket = std::max(maxSocket, socketID); + FD_SET(socketID, &fdSet); + } + } + + if (select(maxSocket+1, &fdSet, NULL, NULL, 0) <= 0) { + continue; + } + + if (FD_ISSET(interruptSelectReadSocket, &fdSet)) { + char dummy; + while (read(interruptSelectReadSocket, &dummy, 1) > 0) {} + } + + { + boost::lock_guard<boost::mutex> lock(runningQueriesMutex); + foreach(boost::shared_ptr<BonjourQuery> query, runningQueries) { + if (FD_ISSET(query->getSocketID(), &fdSet)) { + query->processResult(); + } + } + } + } +} + +} diff --git a/Swiften/LinkLocal/DNSSD/Bonjour/BonjourQuerier.h b/Swiften/LinkLocal/DNSSD/Bonjour/BonjourQuerier.h new file mode 100644 index 0000000..d12f94f --- /dev/null +++ b/Swiften/LinkLocal/DNSSD/Bonjour/BonjourQuerier.h @@ -0,0 +1,50 @@ +#pragma once + +#include <boost/shared_ptr.hpp> +#include <boost/enable_shared_from_this.hpp> +#include <list> +#include <boost/thread.hpp> +#include <boost/thread/mutex.hpp> + +#include "Swiften/LinkLocal/DNSSD/DNSSDQuerier.h" +#include "Swiften/LinkLocal/DNSSD/Bonjour/BonjourQuery.h" + +namespace Swift { + class ByteArray; + + class BonjourQuerier : + public DNSSDQuerier, + public boost::enable_shared_from_this<BonjourQuerier> { + public: + BonjourQuerier(); + ~BonjourQuerier(); + + boost::shared_ptr<DNSSDBrowseQuery> createBrowseQuery(); + boost::shared_ptr<DNSSDRegisterQuery> createRegisterQuery( + const String& name, int port, const ByteArray& info); + boost::shared_ptr<DNSSDResolveServiceQuery> createResolveServiceQuery( + const DNSSDServiceID&); + boost::shared_ptr<DNSSDResolveHostnameQuery> createResolveHostnameQuery( + const String& hostname, int interfaceIndex); + + void start(); + void stop(); + + private: + friend class BonjourQuery; + + void addRunningQuery(boost::shared_ptr<BonjourQuery>); + void removeRunningQuery(boost::shared_ptr<BonjourQuery>); + void interruptSelect(); + void run(); + + private: + bool stopRequested; + boost::thread* thread; + boost::mutex runningQueriesMutex; + std::list< boost::shared_ptr<BonjourQuery> > runningQueries; + int interruptSelectReadSocket; + int interruptSelectWriteSocket; + boost::condition_variable runningQueriesAvailableEvent; + }; +} diff --git a/Swiften/LinkLocal/DNSSD/Bonjour/BonjourQuery.cpp b/Swiften/LinkLocal/DNSSD/Bonjour/BonjourQuery.cpp new file mode 100644 index 0000000..c1c481b --- /dev/null +++ b/Swiften/LinkLocal/DNSSD/Bonjour/BonjourQuery.cpp @@ -0,0 +1,31 @@ +#include "Swiften/LinkLocal/DNSSD/Bonjour/BonjourQuery.h" +#include "Swiften/LinkLocal/DNSSD/Bonjour/BonjourQuerier.h" + +namespace Swift { + +BonjourQuery::BonjourQuery(boost::shared_ptr<BonjourQuerier> q) : querier(q), sdRef(0) { +} + +BonjourQuery::~BonjourQuery() { + DNSServiceRefDeallocate(sdRef); +} + +void BonjourQuery::processResult() { + boost::lock_guard<boost::mutex> lock(sdRefMutex); + DNSServiceProcessResult(sdRef); +} + +int BonjourQuery::getSocketID() const { + boost::lock_guard<boost::mutex> lock(sdRefMutex); + return DNSServiceRefSockFD(sdRef); +} + +void BonjourQuery::run() { + querier->addRunningQuery(shared_from_this()); +} + +void BonjourQuery::finish() { + querier->removeRunningQuery(shared_from_this()); +} + +} diff --git a/Swiften/LinkLocal/DNSSD/Bonjour/BonjourQuery.h b/Swiften/LinkLocal/DNSSD/Bonjour/BonjourQuery.h new file mode 100644 index 0000000..bdb91a4 --- /dev/null +++ b/Swiften/LinkLocal/DNSSD/Bonjour/BonjourQuery.h @@ -0,0 +1,32 @@ +#pragma once + +#include <dns_sd.h> +#include <boost/shared_ptr.hpp> +#include <boost/enable_shared_from_this.hpp> +#include <boost/thread/mutex.hpp> + +#include "Swiften/EventLoop/EventOwner.h" + +namespace Swift { + class BonjourQuerier; + + class BonjourQuery : + public EventOwner, + public boost::enable_shared_from_this<BonjourQuery> { + public: + BonjourQuery(boost::shared_ptr<BonjourQuerier>); + virtual ~BonjourQuery(); + + void processResult(); + int getSocketID() const; + + protected: + void run(); + void finish(); + + protected: + boost::shared_ptr<BonjourQuerier> querier; + mutable boost::mutex sdRefMutex; + DNSServiceRef sdRef; + }; +} diff --git a/Swiften/LinkLocal/DNSSD/Bonjour/BonjourRegisterQuery.h b/Swiften/LinkLocal/DNSSD/Bonjour/BonjourRegisterQuery.h new file mode 100644 index 0000000..ddc2788 --- /dev/null +++ b/Swiften/LinkLocal/DNSSD/Bonjour/BonjourRegisterQuery.h @@ -0,0 +1,55 @@ +#pragma once + +#include "Swiften/LinkLocal/DNSSD/Bonjour/BonjourQuery.h" +#include "Swiften/LinkLocal/DNSSD/DNSSDRegisterQuery.h" +#include "Swiften/Base/ByteArray.h" +#include "Swiften/EventLoop/MainEventLoop.h" + +namespace Swift { + class BonjourQuerier; + + class BonjourRegisterQuery : public DNSSDRegisterQuery, public BonjourQuery { + public: + BonjourRegisterQuery(const String& name, int port, const ByteArray& txtRecord, boost::shared_ptr<BonjourQuerier> querier) : BonjourQuery(querier) { + DNSServiceErrorType result = DNSServiceRegister( + &sdRef, 0, 0, name.getUTF8Data(), "_presence._tcp", NULL, NULL, port, + txtRecord.getSize(), txtRecord.getData(), + &BonjourRegisterQuery::handleServiceRegisteredStatic, this); + if (result != kDNSServiceErr_NoError) { + sdRef = NULL; + } + } + + void registerService() { + if (sdRef) { + run(); + } + else { + MainEventLoop::postEvent(boost::bind(boost::ref(onRegisterFinished), boost::optional<DNSSDServiceID>()), shared_from_this()); + } + } + + void unregisterService() { + finish(); + } + + void updateServiceInfo(const ByteArray& txtRecord) { + boost::lock_guard<boost::mutex> lock(sdRefMutex); + DNSServiceUpdateRecord(sdRef, NULL, NULL, txtRecord.getSize(), txtRecord.getData(), 0); + } + + private: + static void handleServiceRegisteredStatic(DNSServiceRef, DNSServiceFlags, DNSServiceErrorType errorCode, const char *name, const char *regtype, const char *domain, void *context) { + static_cast<BonjourRegisterQuery*>(context)->handleServiceRegistered(errorCode, name, regtype, domain); + } + + void handleServiceRegistered(DNSServiceErrorType errorCode, const char *name, const char *regtype, const char *domain) { + if (errorCode != kDNSServiceErr_NoError) { + MainEventLoop::postEvent(boost::bind(boost::ref(onRegisterFinished), boost::optional<DNSSDServiceID>()), shared_from_this()); + } + else { + MainEventLoop::postEvent(boost::bind(boost::ref(onRegisterFinished), boost::optional<DNSSDServiceID>(DNSSDServiceID(name, domain, regtype, 0))), shared_from_this()); + } + } + }; +} diff --git a/Swiften/LinkLocal/DNSSD/Bonjour/BonjourResolveHostnameQuery.h b/Swiften/LinkLocal/DNSSD/Bonjour/BonjourResolveHostnameQuery.h new file mode 100644 index 0000000..7b5f19a --- /dev/null +++ b/Swiften/LinkLocal/DNSSD/Bonjour/BonjourResolveHostnameQuery.h @@ -0,0 +1,63 @@ +#pragma once + +#include "Swiften/Base/String.h" +#include "Swiften/LinkLocal/DNSSD/Bonjour/BonjourQuery.h" +#include "Swiften/LinkLocal/DNSSD/DNSSDResolveHostnameQuery.h" +#include "Swiften/EventLoop/MainEventLoop.h" +#include "Swiften/Network/HostAddress.h" + +#include <netinet/in.h> + +namespace Swift { + class BonjourQuerier; + + class BonjourResolveHostnameQuery : public DNSSDResolveHostnameQuery, public BonjourQuery { + public: + BonjourResolveHostnameQuery(const String& hostname, int interfaceIndex, boost::shared_ptr<BonjourQuerier> querier) : BonjourQuery(querier) { + DNSServiceErrorType result = DNSServiceGetAddrInfo( + &sdRef, 0, interfaceIndex, kDNSServiceProtocol_IPv4, + hostname.getUTF8Data(), + &BonjourResolveHostnameQuery::handleHostnameResolvedStatic, this); + if (result != kDNSServiceErr_NoError) { + sdRef = NULL; + } + } + + //void DNSSDResolveHostnameQuery::run() { + void run() { + if (sdRef) { + BonjourQuery::run(); + } + else { + MainEventLoop::postEvent(boost::bind(boost::ref(onHostnameResolved), boost::optional<HostAddress>()), shared_from_this()); + } + } + + void finish() { + BonjourQuery::finish(); + } + + private: + static void handleHostnameResolvedStatic(DNSServiceRef, DNSServiceFlags, uint32_t, DNSServiceErrorType errorCode, const char*, const struct sockaddr *address, uint32_t, void *context) { + static_cast<BonjourResolveHostnameQuery*>(context)->handleHostnameResolved(errorCode, address); + } + + void handleHostnameResolved(DNSServiceErrorType errorCode, const struct sockaddr *rawAddress) { + if (errorCode) { + MainEventLoop::postEvent( + boost::bind(boost::ref(onHostnameResolved), + boost::optional<HostAddress>()), + shared_from_this()); + } + else { + assert(rawAddress->sa_family == AF_INET); + const sockaddr_in* sa = reinterpret_cast<const sockaddr_in*>(rawAddress); + uint32_t address = ntohl(sa->sin_addr.s_addr); + MainEventLoop::postEvent(boost::bind( + boost::ref(onHostnameResolved), + HostAddress(reinterpret_cast<unsigned char*>(&address), 4)), + shared_from_this()); + } + } + }; +} diff --git a/Swiften/LinkLocal/DNSSD/Bonjour/BonjourResolveServiceQuery.h b/Swiften/LinkLocal/DNSSD/Bonjour/BonjourResolveServiceQuery.h new file mode 100644 index 0000000..1c38179 --- /dev/null +++ b/Swiften/LinkLocal/DNSSD/Bonjour/BonjourResolveServiceQuery.h @@ -0,0 +1,58 @@ +#pragma once + +#include "Swiften/LinkLocal/DNSSD/Bonjour/BonjourQuery.h" +#include "Swiften/LinkLocal/DNSSD/DNSSDResolveServiceQuery.h" +#include "Swiften/LinkLocal/LinkLocalServiceInfo.h" +#include "Swiften/Base/ByteArray.h" +#include "Swiften/EventLoop/MainEventLoop.h" + +namespace Swift { + class BonjourQuerier; + + class BonjourResolveServiceQuery : public DNSSDResolveServiceQuery, public BonjourQuery { + public: + BonjourResolveServiceQuery(const DNSSDServiceID& service, boost::shared_ptr<BonjourQuerier> querier) : BonjourQuery(querier) { + DNSServiceErrorType result = DNSServiceResolve( + &sdRef, 0, service.getNetworkInterfaceID(), + service.getName().getUTF8Data(), service.getType().getUTF8Data(), + service.getDomain().getUTF8Data(), + &BonjourResolveServiceQuery::handleServiceResolvedStatic, this); + if (result != kDNSServiceErr_NoError) { + sdRef = NULL; + } + } + + void start() { + if (sdRef) { + run(); + } + else { + MainEventLoop::postEvent(boost::bind(boost::ref(onServiceResolved), boost::optional<Result>()), shared_from_this()); + } + } + + void stop() { + finish(); + } + + private: + static void handleServiceResolvedStatic(DNSServiceRef, DNSServiceFlags, uint32_t, DNSServiceErrorType errorCode, const char *fullname, const char *hosttarget, uint16_t port, uint16_t txtLen, const unsigned char *txtRecord, void *context) { + static_cast<BonjourResolveServiceQuery*>(context)->handleServiceResolved(errorCode, fullname, hosttarget, port, txtLen, txtRecord); + } + + void handleServiceResolved(DNSServiceErrorType errorCode, const char* fullName, const char* host, uint16_t port, uint16_t txtLen, const unsigned char *txtRecord) { + if (errorCode != kDNSServiceErr_NoError) { + MainEventLoop::postEvent(boost::bind(boost::ref(onServiceResolved), boost::optional<Result>()), shared_from_this()); + } + else { + //std::cout << "Service resolved: name:" << fullName << " host:" << host << " port:" << port << std::endl; + MainEventLoop::postEvent( + boost::bind( + boost::ref(onServiceResolved), + Result(String(fullName), String(host), port, + ByteArray(reinterpret_cast<const char*>(txtRecord), txtLen))), + shared_from_this()); + } + } + }; +} diff --git a/Swiften/LinkLocal/DNSSD/DNSSDBrowseQuery.cpp b/Swiften/LinkLocal/DNSSD/DNSSDBrowseQuery.cpp new file mode 100644 index 0000000..1dbff2c --- /dev/null +++ b/Swiften/LinkLocal/DNSSD/DNSSDBrowseQuery.cpp @@ -0,0 +1,8 @@ +#include "Swiften/LinkLocal/DNSSD/DNSSDBrowseQuery.h" + +namespace Swift { + +DNSSDBrowseQuery::~DNSSDBrowseQuery() { +} + +} diff --git a/Swiften/LinkLocal/DNSSD/DNSSDBrowseQuery.h b/Swiften/LinkLocal/DNSSD/DNSSDBrowseQuery.h new file mode 100644 index 0000000..9b30858 --- /dev/null +++ b/Swiften/LinkLocal/DNSSD/DNSSDBrowseQuery.h @@ -0,0 +1,19 @@ +#pragma once + +#include <boost/signal.hpp> + +#include "Swiften/LinkLocal/DNSSD/DNSSDServiceID.h" + +namespace Swift { + class DNSSDBrowseQuery { + public: + virtual ~DNSSDBrowseQuery(); + + virtual void startBrowsing() = 0; + virtual void stopBrowsing() = 0; + + boost::signal<void (const DNSSDServiceID&)> onServiceAdded; + boost::signal<void (const DNSSDServiceID&)> onServiceRemoved; + boost::signal<void ()> onError; + }; +} diff --git a/Swiften/LinkLocal/DNSSD/DNSSDQuerier.cpp b/Swiften/LinkLocal/DNSSD/DNSSDQuerier.cpp new file mode 100644 index 0000000..cc8d6ef --- /dev/null +++ b/Swiften/LinkLocal/DNSSD/DNSSDQuerier.cpp @@ -0,0 +1,8 @@ +#include "Swiften/LinkLocal/DNSSD/DNSSDQuerier.h" + +namespace Swift { + +DNSSDQuerier::~DNSSDQuerier() { +} + +} diff --git a/Swiften/LinkLocal/DNSSD/DNSSDQuerier.h b/Swiften/LinkLocal/DNSSD/DNSSDQuerier.h new file mode 100644 index 0000000..efcc140 --- /dev/null +++ b/Swiften/LinkLocal/DNSSD/DNSSDQuerier.h @@ -0,0 +1,29 @@ +#pragma once + +#include <boost/shared_ptr.hpp> + +namespace Swift { + class String; + class ByteArray; + class DNSSDServiceID; + class DNSSDBrowseQuery; + class DNSSDRegisterQuery; + class DNSSDResolveServiceQuery; + class DNSSDResolveHostnameQuery; + + class DNSSDQuerier { + public: + virtual ~DNSSDQuerier(); + + virtual void start() = 0; + virtual void stop() = 0; + + virtual boost::shared_ptr<DNSSDBrowseQuery> createBrowseQuery() = 0; + virtual boost::shared_ptr<DNSSDRegisterQuery> createRegisterQuery( + const String& name, int port, const ByteArray& info) = 0; + virtual boost::shared_ptr<DNSSDResolveServiceQuery> createResolveServiceQuery( + const DNSSDServiceID&) = 0; + virtual boost::shared_ptr<DNSSDResolveHostnameQuery> createResolveHostnameQuery( + const String& hostname, int interfaceIndex) = 0; + }; +} diff --git a/Swiften/LinkLocal/DNSSD/DNSSDRegisterQuery.cpp b/Swiften/LinkLocal/DNSSD/DNSSDRegisterQuery.cpp new file mode 100644 index 0000000..bbb8692 --- /dev/null +++ b/Swiften/LinkLocal/DNSSD/DNSSDRegisterQuery.cpp @@ -0,0 +1,8 @@ +#include "Swiften/LinkLocal/DNSSD/DNSSDRegisterQuery.h" + +namespace Swift { + +DNSSDRegisterQuery::~DNSSDRegisterQuery() { +} + +} diff --git a/Swiften/LinkLocal/DNSSD/DNSSDRegisterQuery.h b/Swiften/LinkLocal/DNSSD/DNSSDRegisterQuery.h new file mode 100644 index 0000000..4a04fa9 --- /dev/null +++ b/Swiften/LinkLocal/DNSSD/DNSSDRegisterQuery.h @@ -0,0 +1,21 @@ +#pragma once + +#include <boost/signal.hpp> +#include <boost/optional.hpp> + +#include "Swiften/LinkLocal/DNSSD/DNSSDServiceID.h" + +namespace Swift { + class ByteArray; + + class DNSSDRegisterQuery { + public: + virtual ~DNSSDRegisterQuery(); + + virtual void registerService() = 0; + virtual void unregisterService() = 0; + virtual void updateServiceInfo(const ByteArray& info) = 0; + + boost::signal<void (boost::optional<DNSSDServiceID>)> onRegisterFinished; + }; +} diff --git a/Swiften/LinkLocal/DNSSD/DNSSDResolveHostnameQuery.cpp b/Swiften/LinkLocal/DNSSD/DNSSDResolveHostnameQuery.cpp new file mode 100644 index 0000000..e247e39 --- /dev/null +++ b/Swiften/LinkLocal/DNSSD/DNSSDResolveHostnameQuery.cpp @@ -0,0 +1,8 @@ +#include "Swiften/LinkLocal/DNSSD/DNSSDResolveHostnameQuery.h" + +namespace Swift { + +DNSSDResolveHostnameQuery::~DNSSDResolveHostnameQuery() { +} + +} diff --git a/Swiften/LinkLocal/DNSSD/DNSSDResolveHostnameQuery.h b/Swiften/LinkLocal/DNSSD/DNSSDResolveHostnameQuery.h new file mode 100644 index 0000000..1b9f291 --- /dev/null +++ b/Swiften/LinkLocal/DNSSD/DNSSDResolveHostnameQuery.h @@ -0,0 +1,18 @@ +#pragma once + +#include <boost/signal.hpp> +#include <boost/optional.hpp> + +#include "Swiften/Network/HostAddress.h" + +namespace Swift { + class DNSSDResolveHostnameQuery { + public: + virtual ~DNSSDResolveHostnameQuery(); + + virtual void run() = 0; + virtual void finish() = 0; + + boost::signal<void (const boost::optional<HostAddress>&)> onHostnameResolved; + }; +} diff --git a/Swiften/LinkLocal/DNSSD/DNSSDResolveServiceQuery.cpp b/Swiften/LinkLocal/DNSSD/DNSSDResolveServiceQuery.cpp new file mode 100644 index 0000000..67a5d66 --- /dev/null +++ b/Swiften/LinkLocal/DNSSD/DNSSDResolveServiceQuery.cpp @@ -0,0 +1,8 @@ +#include "Swiften/LinkLocal/DNSSD/DNSSDResolveServiceQuery.h" + +namespace Swift { + +DNSSDResolveServiceQuery::~DNSSDResolveServiceQuery() { +} + +} diff --git a/Swiften/LinkLocal/DNSSD/DNSSDResolveServiceQuery.h b/Swiften/LinkLocal/DNSSD/DNSSDResolveServiceQuery.h new file mode 100644 index 0000000..217e396 --- /dev/null +++ b/Swiften/LinkLocal/DNSSD/DNSSDResolveServiceQuery.h @@ -0,0 +1,28 @@ +#pragma once + +#include <boost/signal.hpp> +#include <boost/optional.hpp> + +#include "Swiften/LinkLocal/DNSSD/DNSSDServiceID.h" +#include "Swiften/Base/ByteArray.h" + +namespace Swift { + class DNSSDResolveServiceQuery { + public: + struct Result { + Result(const String& fullName, const String& host, int port, const ByteArray& info) : + fullName(fullName), host(host), port(port), info(info) {} + String fullName; + String host; + int port; + ByteArray info; + }; + + virtual ~DNSSDResolveServiceQuery(); + + virtual void start() = 0; + virtual void stop() = 0; + + boost::signal<void (const boost::optional<Result>&)> onServiceResolved; + }; +} diff --git a/Swiften/LinkLocal/DNSSD/DNSSDServiceID.cpp b/Swiften/LinkLocal/DNSSD/DNSSDServiceID.cpp new file mode 100644 index 0000000..b360ee5 --- /dev/null +++ b/Swiften/LinkLocal/DNSSD/DNSSDServiceID.cpp @@ -0,0 +1,7 @@ +#include "Swiften/LinkLocal/DNSSD/DNSSDServiceID.h" + +namespace Swift { + +const char* DNSSDServiceID::PresenceServiceType = "_presence._tcp"; + +} diff --git a/Swiften/LinkLocal/DNSSD/DNSSDServiceID.h b/Swiften/LinkLocal/DNSSD/DNSSDServiceID.h new file mode 100644 index 0000000..ba7828b --- /dev/null +++ b/Swiften/LinkLocal/DNSSD/DNSSDServiceID.h @@ -0,0 +1,66 @@ +#pragma once + +#include "Swiften/Base/String.h" + +namespace Swift { + class DNSSDServiceID { + public: + static const char* PresenceServiceType; + + DNSSDServiceID( + const String& name, + const String& domain, + const String& type = PresenceServiceType, + int networkInterface = 0) : + name(name), + domain(domain), + type(type), + networkInterface(networkInterface) { + } + + bool operator==(const DNSSDServiceID& o) const { + return name == o.name && domain == o.domain && type == o.type && (networkInterface != 0 && o.networkInterface != 0 ? networkInterface == o.networkInterface : true); + } + + bool operator<(const DNSSDServiceID& o) const { + if (o.name == name) { + if (o.domain == domain) { + if (o.type == type) { + return networkInterface < o.networkInterface; + } + else { + return type < o.type; + } + } + else { + return domain < o.domain; + } + } + else { + return o.name < name; + } + } + + const String& getName() const { + return name; + } + + const String& getDomain() const { + return domain; + } + + const String& getType() const { + return type; + } + + int getNetworkInterfaceID() const { + return networkInterface; + } + + private: + String name; + String domain; + String type; + int networkInterface; + }; +} diff --git a/Swiften/LinkLocal/DNSSD/Fake/FakeDNSSDBrowseQuery.h b/Swiften/LinkLocal/DNSSD/Fake/FakeDNSSDBrowseQuery.h new file mode 100644 index 0000000..5a0b93b --- /dev/null +++ b/Swiften/LinkLocal/DNSSD/Fake/FakeDNSSDBrowseQuery.h @@ -0,0 +1,22 @@ +#pragma once + +#include "Swiften/LinkLocal/DNSSD/Fake/FakeDNSSDQuery.h" +#include "Swiften/LinkLocal/DNSSD/DNSSDBrowseQuery.h" + +namespace Swift { + class FakeDNSSDQuerier; + + class FakeDNSSDBrowseQuery : public DNSSDBrowseQuery, public FakeDNSSDQuery { + public: + FakeDNSSDBrowseQuery(boost::shared_ptr<FakeDNSSDQuerier> querier) : FakeDNSSDQuery(querier) { + } + + void startBrowsing() { + run(); + } + + void stopBrowsing() { + finish(); + } + }; +} diff --git a/Swiften/LinkLocal/DNSSD/Fake/FakeDNSSDQuerier.cpp b/Swiften/LinkLocal/DNSSD/Fake/FakeDNSSDQuerier.cpp new file mode 100644 index 0000000..c26f8ee --- /dev/null +++ b/Swiften/LinkLocal/DNSSD/Fake/FakeDNSSDQuerier.cpp @@ -0,0 +1,130 @@ +#include "Swiften/LinkLocal/DNSSD/Fake/FakeDNSSDQuerier.h" + +#include <boost/bind.hpp> + +#include "Swiften/LinkLocal/DNSSD/Fake/FakeDNSSDBrowseQuery.h" +#include "Swiften/LinkLocal/DNSSD/Fake/FakeDNSSDRegisterQuery.h" +#include "Swiften/LinkLocal/DNSSD/Fake/FakeDNSSDResolveServiceQuery.h" +#include "Swiften/LinkLocal/DNSSD/Fake/FakeDNSSDResolveHostnameQuery.h" +#include "Swiften/EventLoop/MainEventLoop.h" + +namespace Swift { + +FakeDNSSDQuerier::FakeDNSSDQuerier(const String& domain) : domain(domain) { +} + +FakeDNSSDQuerier::~FakeDNSSDQuerier() { + if (!runningQueries.empty()) { + std::cerr << "FakeDNSSDQuerier: Running queries not empty at destruction time" << std::endl; + } +} + +boost::shared_ptr<DNSSDBrowseQuery> FakeDNSSDQuerier::createBrowseQuery() { + return boost::shared_ptr<DNSSDBrowseQuery>(new FakeDNSSDBrowseQuery(shared_from_this())); +} + +boost::shared_ptr<DNSSDRegisterQuery> FakeDNSSDQuerier::createRegisterQuery(const String& name, int port, const ByteArray& info) { + return boost::shared_ptr<DNSSDRegisterQuery>(new FakeDNSSDRegisterQuery(name, port, info, shared_from_this())); +} + +boost::shared_ptr<DNSSDResolveServiceQuery> FakeDNSSDQuerier::createResolveServiceQuery(const DNSSDServiceID& service) { + return boost::shared_ptr<DNSSDResolveServiceQuery>(new FakeDNSSDResolveServiceQuery(service, shared_from_this())); +} + +boost::shared_ptr<DNSSDResolveHostnameQuery> FakeDNSSDQuerier::createResolveHostnameQuery(const String& hostname, int interfaceIndex) { + return boost::shared_ptr<DNSSDResolveHostnameQuery>(new FakeDNSSDResolveHostnameQuery(hostname, interfaceIndex, shared_from_this())); +} + +void FakeDNSSDQuerier::addRunningQuery(boost::shared_ptr<FakeDNSSDQuery> query) { + runningQueries.push_back(query); + if (boost::shared_ptr<FakeDNSSDBrowseQuery> browseQuery = boost::dynamic_pointer_cast<FakeDNSSDBrowseQuery>(query)) { + foreach(const DNSSDServiceID& service, services) { + MainEventLoop::postEvent(boost::bind(boost::ref(browseQuery->onServiceAdded), service), shared_from_this()); + } + } + else if (boost::shared_ptr<FakeDNSSDResolveServiceQuery> resolveQuery = boost::dynamic_pointer_cast<FakeDNSSDResolveServiceQuery>(query)) { + for(ServiceInfoMap::const_iterator i = serviceInfo.begin(); i != serviceInfo.end(); ++i) { + if (i->first == resolveQuery->service) { + MainEventLoop::postEvent(boost::bind(boost::ref(resolveQuery->onServiceResolved), i->second), shared_from_this()); + } + } + } + else if (boost::shared_ptr<FakeDNSSDRegisterQuery> registerQuery = boost::dynamic_pointer_cast<FakeDNSSDRegisterQuery>(query)) { + DNSSDServiceID service(registerQuery->name, domain); + MainEventLoop::postEvent(boost::bind(boost::ref(registerQuery->onRegisterFinished), service), shared_from_this()); + } + else if (boost::shared_ptr<FakeDNSSDResolveHostnameQuery> resolveHostnameQuery = boost::dynamic_pointer_cast<FakeDNSSDResolveHostnameQuery>(query)) { + std::map<String,boost::optional<HostAddress> >::const_iterator i = addresses.find(resolveHostnameQuery->hostname); + if (i != addresses.end()) { + MainEventLoop::postEvent( + boost::bind( + boost::ref(resolveHostnameQuery->onHostnameResolved), i->second), + shared_from_this()); + } + } +} + +void FakeDNSSDQuerier::removeRunningQuery(boost::shared_ptr<FakeDNSSDQuery> query) { + runningQueries.erase(std::remove( + runningQueries.begin(), runningQueries.end(), query), runningQueries.end()); +} + +void FakeDNSSDQuerier::addService(const DNSSDServiceID& id) { + services.insert(id); + foreach(const boost::shared_ptr<FakeDNSSDBrowseQuery>& query, getQueries<FakeDNSSDBrowseQuery>()) { + MainEventLoop::postEvent(boost::bind(boost::ref(query->onServiceAdded), id), shared_from_this()); + } +} + +void FakeDNSSDQuerier::removeService(const DNSSDServiceID& id) { + services.erase(id); + serviceInfo.erase(id); + foreach(const boost::shared_ptr<FakeDNSSDBrowseQuery>& query, getQueries<FakeDNSSDBrowseQuery>()) { + MainEventLoop::postEvent(boost::bind(boost::ref(query->onServiceRemoved), id), shared_from_this()); + } +} + +void FakeDNSSDQuerier::setServiceInfo(const DNSSDServiceID& id, const DNSSDResolveServiceQuery::Result& info) { + std::pair<ServiceInfoMap::iterator, bool> r = serviceInfo.insert(std::make_pair(id, info)); + if (!r.second) { + r.first->second = info; + } + foreach(const boost::shared_ptr<FakeDNSSDResolveServiceQuery>& query, getQueries<FakeDNSSDResolveServiceQuery>()) { + if (query->service == id) { + MainEventLoop::postEvent(boost::bind(boost::ref(query->onServiceResolved), info), shared_from_this()); + } + } +} + +bool FakeDNSSDQuerier::isServiceRegistered(const String& name, int port, const ByteArray& info) { + foreach(const boost::shared_ptr<FakeDNSSDRegisterQuery>& query, getQueries<FakeDNSSDRegisterQuery>()) { + if (query->name == name && query->port == port && query->info == info) { + return true; + } + } + return false; +} + +void FakeDNSSDQuerier::setBrowseError() { + foreach(const boost::shared_ptr<FakeDNSSDBrowseQuery>& query, getQueries<FakeDNSSDBrowseQuery>()) { + MainEventLoop::postEvent(boost::ref(query->onError), shared_from_this()); + } +} + +void FakeDNSSDQuerier::setRegisterError() { + foreach(const boost::shared_ptr<FakeDNSSDRegisterQuery>& query, getQueries<FakeDNSSDRegisterQuery>()) { + MainEventLoop::postEvent(boost::bind(boost::ref(query->onRegisterFinished), boost::optional<DNSSDServiceID>()), shared_from_this()); + } +} + +void FakeDNSSDQuerier::setAddress(const String& hostname, boost::optional<HostAddress> address) { + addresses[hostname] = address; + foreach(const boost::shared_ptr<FakeDNSSDResolveHostnameQuery>& query, getQueries<FakeDNSSDResolveHostnameQuery>()) { + if (query->hostname == hostname) { + MainEventLoop::postEvent(boost::bind( + boost::ref(query->onHostnameResolved), address), shared_from_this()); + } + } +} + +} diff --git a/Swiften/LinkLocal/DNSSD/Fake/FakeDNSSDQuerier.h b/Swiften/LinkLocal/DNSSD/Fake/FakeDNSSDQuerier.h new file mode 100644 index 0000000..22bca0c --- /dev/null +++ b/Swiften/LinkLocal/DNSSD/Fake/FakeDNSSDQuerier.h @@ -0,0 +1,71 @@ +#pragma once + +#include <boost/shared_ptr.hpp> +#include <boost/enable_shared_from_this.hpp> +#include <list> +#include <set> + +#include "Swiften/Base/foreach.h" +#include "Swiften/Base/String.h" +#include "Swiften/EventLoop/EventOwner.h" +#include "Swiften/LinkLocal/DNSSD/DNSSDQuerier.h" +#include "Swiften/LinkLocal/DNSSD/DNSSDResolveServiceQuery.h" +#include "Swiften/Network/HostAddress.h" + +namespace Swift { + class ByteArray; + class FakeDNSSDQuery; + class FakeDNSSDBrowseQuery; + + class FakeDNSSDQuerier : + public DNSSDQuerier, + public EventOwner, + public boost::enable_shared_from_this<FakeDNSSDQuerier> { + public: + FakeDNSSDQuerier(const String& domain); + ~FakeDNSSDQuerier(); + + void start() {} + void stop() {} + + boost::shared_ptr<DNSSDBrowseQuery> createBrowseQuery(); + boost::shared_ptr<DNSSDRegisterQuery> createRegisterQuery( + const String& name, int port, const ByteArray& info); + boost::shared_ptr<DNSSDResolveServiceQuery> createResolveServiceQuery( + const DNSSDServiceID&); + boost::shared_ptr<DNSSDResolveHostnameQuery> createResolveHostnameQuery( + const String& hostname, int interfaceIndex); + + void addRunningQuery(boost::shared_ptr<FakeDNSSDQuery>); + void removeRunningQuery(boost::shared_ptr<FakeDNSSDQuery>); + + void addService(const DNSSDServiceID& id); + void removeService(const DNSSDServiceID& id); + void setServiceInfo(const DNSSDServiceID& id, const DNSSDResolveServiceQuery::Result& info); + bool isServiceRegistered(const String& name, int port, const ByteArray& info); + void setAddress(const String& hostname, boost::optional<HostAddress> address); + + void setBrowseError(); + void setRegisterError(); + + private: + template<typename T> + std::vector< boost::shared_ptr<T> > getQueries() const { + std::vector< boost::shared_ptr<T> > result; + foreach(const boost::shared_ptr<FakeDNSSDQuery>& query, runningQueries) { + if (boost::shared_ptr<T> resultQuery = boost::dynamic_pointer_cast<T>(query)) { + result.push_back(resultQuery); + } + } + return result; + } + + private: + String domain; + std::list< boost::shared_ptr<FakeDNSSDQuery> > runningQueries; + std::set<DNSSDServiceID> services; + typedef std::map<DNSSDServiceID,DNSSDResolveServiceQuery::Result> ServiceInfoMap; + ServiceInfoMap serviceInfo; + std::map<String, boost::optional<HostAddress> > addresses; + }; +} diff --git a/Swiften/LinkLocal/DNSSD/Fake/FakeDNSSDQuery.cpp b/Swiften/LinkLocal/DNSSD/Fake/FakeDNSSDQuery.cpp new file mode 100644 index 0000000..ced7850 --- /dev/null +++ b/Swiften/LinkLocal/DNSSD/Fake/FakeDNSSDQuery.cpp @@ -0,0 +1,20 @@ +#include "Swiften/LinkLocal/DNSSD/Fake/FakeDNSSDQuery.h" +#include "Swiften/LinkLocal/DNSSD/Fake/FakeDNSSDQuerier.h" + +namespace Swift { + +FakeDNSSDQuery::FakeDNSSDQuery(boost::shared_ptr<FakeDNSSDQuerier> querier) : querier(querier) { +} + +FakeDNSSDQuery::~FakeDNSSDQuery() { +} + +void FakeDNSSDQuery::run() { + querier->addRunningQuery(shared_from_this()); +} + +void FakeDNSSDQuery::finish() { + querier->removeRunningQuery(shared_from_this()); +} + +} diff --git a/Swiften/LinkLocal/DNSSD/Fake/FakeDNSSDQuery.h b/Swiften/LinkLocal/DNSSD/Fake/FakeDNSSDQuery.h new file mode 100644 index 0000000..9fca1d4 --- /dev/null +++ b/Swiften/LinkLocal/DNSSD/Fake/FakeDNSSDQuery.h @@ -0,0 +1,25 @@ +#pragma once + +#include <boost/shared_ptr.hpp> +#include <boost/enable_shared_from_this.hpp> + +#include "Swiften/EventLoop/EventOwner.h" + +namespace Swift { + class FakeDNSSDQuerier; + + class FakeDNSSDQuery : + public EventOwner, + public boost::enable_shared_from_this<FakeDNSSDQuery> { + public: + FakeDNSSDQuery(boost::shared_ptr<FakeDNSSDQuerier>); + virtual ~FakeDNSSDQuery(); + + protected: + void run(); + void finish(); + + protected: + boost::shared_ptr<FakeDNSSDQuerier> querier; + }; +} diff --git a/Swiften/LinkLocal/DNSSD/Fake/FakeDNSSDRegisterQuery.h b/Swiften/LinkLocal/DNSSD/Fake/FakeDNSSDRegisterQuery.h new file mode 100644 index 0000000..82ec623 --- /dev/null +++ b/Swiften/LinkLocal/DNSSD/Fake/FakeDNSSDRegisterQuery.h @@ -0,0 +1,32 @@ +#pragma once + +#include "Swiften/LinkLocal/DNSSD/Fake/FakeDNSSDQuery.h" +#include "Swiften/LinkLocal/DNSSD/DNSSDRegisterQuery.h" +#include "Swiften/Base/ByteArray.h" +#include "Swiften/Base/String.h" + +namespace Swift { + class FakeDNSSDQuerier; + + class FakeDNSSDRegisterQuery : public DNSSDRegisterQuery, public FakeDNSSDQuery { + public: + FakeDNSSDRegisterQuery(const String& name, int port, const ByteArray& info, boost::shared_ptr<FakeDNSSDQuerier> querier) : FakeDNSSDQuery(querier), name(name), port(port), info(info) { + } + + void registerService() { + run(); + } + + void updateServiceInfo(const ByteArray& i) { + info = i; + } + + void unregisterService() { + finish(); + } + + String name; + int port; + ByteArray info; + }; +} diff --git a/Swiften/LinkLocal/DNSSD/Fake/FakeDNSSDResolveHostnameQuery.h b/Swiften/LinkLocal/DNSSD/Fake/FakeDNSSDResolveHostnameQuery.h new file mode 100644 index 0000000..5ed42af --- /dev/null +++ b/Swiften/LinkLocal/DNSSD/Fake/FakeDNSSDResolveHostnameQuery.h @@ -0,0 +1,27 @@ +#pragma once + +#include "Swiften/Base/String.h" +#include "Swiften/LinkLocal/DNSSD/Fake/FakeDNSSDQuery.h" +#include "Swiften/LinkLocal/DNSSD/DNSSDResolveHostnameQuery.h" +#include "Swiften/Network/HostAddress.h" + +namespace Swift { + class FakeDNSSDQuerier; + + class FakeDNSSDResolveHostnameQuery : public DNSSDResolveHostnameQuery, public FakeDNSSDQuery { + public: + FakeDNSSDResolveHostnameQuery(const String& hostname, int interfaceIndex, boost::shared_ptr<FakeDNSSDQuerier> querier) : FakeDNSSDQuery(querier), hostname(hostname), interfaceIndex(interfaceIndex) { + } + + void run() { + FakeDNSSDQuery::run(); + } + + void finish() { + FakeDNSSDQuery::finish(); + } + + String hostname; + int interfaceIndex; + }; +} diff --git a/Swiften/LinkLocal/DNSSD/Fake/FakeDNSSDResolveServiceQuery.h b/Swiften/LinkLocal/DNSSD/Fake/FakeDNSSDResolveServiceQuery.h new file mode 100644 index 0000000..60d35e5 --- /dev/null +++ b/Swiften/LinkLocal/DNSSD/Fake/FakeDNSSDResolveServiceQuery.h @@ -0,0 +1,25 @@ +#pragma once + +#include "Swiften/LinkLocal/DNSSD/Fake/FakeDNSSDQuery.h" +#include "Swiften/LinkLocal/DNSSD/DNSSDResolveServiceQuery.h" +#include "Swiften/LinkLocal/LinkLocalServiceInfo.h" + +namespace Swift { + class FakeDNSSDQuerier; + + class FakeDNSSDResolveServiceQuery : public DNSSDResolveServiceQuery, public FakeDNSSDQuery { + public: + FakeDNSSDResolveServiceQuery(const DNSSDServiceID& service, boost::shared_ptr<FakeDNSSDQuerier> querier) : FakeDNSSDQuery(querier), service(service) { + } + + void start() { + run(); + } + + void stop() { + finish(); + } + + DNSSDServiceID service; + }; +} diff --git a/Swiften/LinkLocal/DNSSD/PlatformDNSSDQuerierFactory.cpp b/Swiften/LinkLocal/DNSSD/PlatformDNSSDQuerierFactory.cpp new file mode 100644 index 0000000..7b06350 --- /dev/null +++ b/Swiften/LinkLocal/DNSSD/PlatformDNSSDQuerierFactory.cpp @@ -0,0 +1,22 @@ +#include "Swiften/LinkLocal/DNSSD/PlatformDNSSDQuerierFactory.h" + +#if defined(HAVE_BONJOUR) +#include "Swiften/LinkLocal/DNSSD/Bonjour/BonjourQuerier.h" +#elif defined(HAVE_AVAHI) +#include "Swiften/LinkLocal/DNSSD/Avahi/AvahiQuerier.h" +#endif + + +namespace Swift { + +boost::shared_ptr<DNSSDQuerier> PlatformDNSSDQuerierFactory::createQuerier() { +#if defined(HAVE_BONJOUR) + return boost::shared_ptr<DNSSDQuerier>(new BonjourQuerier()); +#elif defined(HAVE_AVAHI) + return boost::shared_ptr<DNSSDQuerier>(new AvahiQuerier()); +#else + return boost::shared_ptr<DNSSDQuerier>(); +#endif +} + +} diff --git a/Swiften/LinkLocal/DNSSD/PlatformDNSSDQuerierFactory.h b/Swiften/LinkLocal/DNSSD/PlatformDNSSDQuerierFactory.h new file mode 100644 index 0000000..b52814b --- /dev/null +++ b/Swiften/LinkLocal/DNSSD/PlatformDNSSDQuerierFactory.h @@ -0,0 +1,12 @@ +#pragma once + +#include <boost/shared_ptr.hpp> + +namespace Swift { + class DNSSDQuerier; + + class PlatformDNSSDQuerierFactory { + public: + boost::shared_ptr<DNSSDQuerier> createQuerier(); + }; +} diff --git a/Swiften/LinkLocal/IncomingLinkLocalSession.cpp b/Swiften/LinkLocal/IncomingLinkLocalSession.cpp new file mode 100644 index 0000000..77910e6 --- /dev/null +++ b/Swiften/LinkLocal/IncomingLinkLocalSession.cpp @@ -0,0 +1,62 @@ +#include "Swiften/LinkLocal/IncomingLinkLocalSession.h" + +#include <boost/bind.hpp> + +#include "Swiften/Elements/ProtocolHeader.h" +#include "Swiften/Network/Connection.h" +#include "Swiften/StreamStack/StreamStack.h" +#include "Swiften/StreamStack/ConnectionLayer.h" +#include "Swiften/StreamStack/XMPPLayer.h" +#include "Swiften/Elements/StreamFeatures.h" +#include "Swiften/Elements/IQ.h" + +namespace Swift { + +IncomingLinkLocalSession::IncomingLinkLocalSession( + const JID& localJID, + boost::shared_ptr<Connection> connection, + PayloadParserFactoryCollection* payloadParserFactories, + PayloadSerializerCollection* payloadSerializers) : + Session(connection, payloadParserFactories, payloadSerializers), + initialized(false) { + setLocalJID(localJID); +} + +void IncomingLinkLocalSession::handleStreamStart(const ProtocolHeader& incomingHeader) { + setRemoteJID(JID(incomingHeader.getFrom())); + if (!getRemoteJID().isValid()) { + finishSession(); + return; + } + + ProtocolHeader header; + header.setFrom(getLocalJID()); + getXMPPLayer()->writeHeader(header); + + if (incomingHeader.getVersion() == "1.0") { + getXMPPLayer()->writeElement(boost::shared_ptr<StreamFeatures>(new StreamFeatures())); + } + else { + setInitialized(); + } +} + +void IncomingLinkLocalSession::handleElement(boost::shared_ptr<Element> element) { + boost::shared_ptr<Stanza> stanza = boost::dynamic_pointer_cast<Stanza>(element); + // If we get our first stanza before streamfeatures, our session is implicitly + // initialized + if (stanza && !isInitialized()) { + setInitialized(); + } + + onElementReceived(element); +} + +void IncomingLinkLocalSession::setInitialized() { + initialized = true; + onSessionStarted(); +} + + + +} diff --git a/Swiften/LinkLocal/IncomingLinkLocalSession.h b/Swiften/LinkLocal/IncomingLinkLocalSession.h new file mode 100644 index 0000000..e3f0460 --- /dev/null +++ b/Swiften/LinkLocal/IncomingLinkLocalSession.h @@ -0,0 +1,37 @@ +#pragma once + +#include <boost/shared_ptr.hpp> +#include <boost/signal.hpp> + +#include "Swiften/Session/Session.h" +#include "Swiften/JID/JID.h" +#include "Swiften/Network/Connection.h" + +namespace Swift { + class ProtocolHeader; + class String; + class Element; + class PayloadParserFactoryCollection; + class PayloadSerializerCollection; + + class IncomingLinkLocalSession : public Session { + public: + IncomingLinkLocalSession( + const JID& localJID, + boost::shared_ptr<Connection> connection, + PayloadParserFactoryCollection* payloadParserFactories, + PayloadSerializerCollection* payloadSerializers); + + boost::signal<void ()> onSessionStarted; + + private: + void handleElement(boost::shared_ptr<Element>); + void handleStreamStart(const ProtocolHeader&); + void setInitialized(); + bool isInitialized() const { + return initialized; + } + + bool initialized; + }; +} diff --git a/Swiften/LinkLocal/LinkLocalConnector.cpp b/Swiften/LinkLocal/LinkLocalConnector.cpp new file mode 100644 index 0000000..fba4a4e --- /dev/null +++ b/Swiften/LinkLocal/LinkLocalConnector.cpp @@ -0,0 +1,68 @@ +#include "Swiften/LinkLocal/LinkLocalConnector.h" + +#include <boost/bind.hpp> + +#include "Swiften/Network/Connection.h" +#include "Swiften/Network/ConnectionFactory.h" +#include "Swiften/Network/HostAddress.h" +#include "Swiften/Network/HostAddressPort.h" +#include "Swiften/LinkLocal/DNSSD/DNSSDQuerier.h" +#include "Swiften/LinkLocal/DNSSD/DNSSDResolveHostnameQuery.h" + +namespace Swift { + +LinkLocalConnector::LinkLocalConnector( + const LinkLocalService& service, + boost::shared_ptr<DNSSDQuerier> querier, + boost::shared_ptr<Connection> connection) : + service(service), + querier(querier), + connection(connection) { +} + +LinkLocalConnector::~LinkLocalConnector() { + assert(!resolveQuery); +} + +void LinkLocalConnector::connect() { + resolveQuery = querier->createResolveHostnameQuery( + service.getHostname(), + service.getID().getNetworkInterfaceID()); + resolveQuery->onHostnameResolved.connect(boost::bind( + &LinkLocalConnector::handleHostnameResolved, + boost::dynamic_pointer_cast<LinkLocalConnector>(shared_from_this()), + _1)); + resolveQuery->run(); +} + +void LinkLocalConnector::cancel() { + if (resolveQuery) { + resolveQuery->finish(); + } + resolveQuery.reset(); + connection->disconnect(); +} + +void LinkLocalConnector::handleHostnameResolved(const boost::optional<HostAddress>& address) { + resolveQuery->finish(); + resolveQuery.reset(); + if (address) { + connection->onConnectFinished.connect( + boost::bind(boost::ref(onConnectFinished), _1)); + connection->connect(HostAddressPort(*address, service.getPort())); + } + else { + onConnectFinished(true); + } +} + +void LinkLocalConnector::handleConnected(bool error) { + onConnectFinished(error); +} + +void LinkLocalConnector::queueElement(boost::shared_ptr<Element> element) { + queuedElements.push_back(element); +} + + +} diff --git a/Swiften/LinkLocal/LinkLocalConnector.h b/Swiften/LinkLocal/LinkLocalConnector.h new file mode 100644 index 0000000..0b6baef --- /dev/null +++ b/Swiften/LinkLocal/LinkLocalConnector.h @@ -0,0 +1,57 @@ +#pragma once + +#include <boost/shared_ptr.hpp> +#include <boost/signal.hpp> +#include <boost/enable_shared_from_this.hpp> +#include <vector> + +#include "Swiften/Network/Connection.h" +#include "Swiften/LinkLocal/LinkLocalService.h" + +namespace Swift { + class ConnectionFactory; + class HostAddress; + class Element; + class PayloadParserFactoryCollection; + class PayloadSerializerCollection; + class DNSSDQuerier; + class DNSSDResolveHostnameQuery; + + class LinkLocalConnector : public boost::enable_shared_from_this<LinkLocalConnector> { + public: + LinkLocalConnector( + const LinkLocalService& service, + boost::shared_ptr<DNSSDQuerier> querier, + boost::shared_ptr<Connection> connection); + ~LinkLocalConnector(); + + const LinkLocalService& getService() const { + return service; + } + + void connect(); + void cancel(); + void queueElement(boost::shared_ptr<Element> element); + + const std::vector<boost::shared_ptr<Element> >& getQueuedElements() const { + return queuedElements; + } + + boost::shared_ptr<Connection> getConnection() const { + return connection; + } + + boost::signal<void (bool)> onConnectFinished; + + private: + void handleHostnameResolved(const boost::optional<HostAddress>& address); + void handleConnected(bool error); + + private: + LinkLocalService service; + boost::shared_ptr<DNSSDQuerier> querier; + boost::shared_ptr<DNSSDResolveHostnameQuery> resolveQuery; + boost::shared_ptr<Connection> connection; + std::vector<boost::shared_ptr<Element> > queuedElements; + }; +} diff --git a/Swiften/LinkLocal/LinkLocalService.cpp b/Swiften/LinkLocal/LinkLocalService.cpp new file mode 100644 index 0000000..f1114ed --- /dev/null +++ b/Swiften/LinkLocal/LinkLocalService.cpp @@ -0,0 +1,27 @@ +#include "Swiften/LinkLocal/LinkLocalService.h" + +namespace Swift { + +String LinkLocalService::getDescription() const { + LinkLocalServiceInfo info = getInfo(); + if (!info.getNick().isEmpty()) { + return info.getNick(); + } + else if (!info.getFirstName().isEmpty()) { + String result = info.getFirstName(); + if (!info.getLastName().isEmpty()) { + result += " " + info.getLastName(); + } + return result; + } + else if (!info.getLastName().isEmpty()) { + return info.getLastName(); + } + return getName(); +} + +JID LinkLocalService::getJID() const { + return JID(getName()); +} + +} diff --git a/Swiften/LinkLocal/LinkLocalService.h b/Swiften/LinkLocal/LinkLocalService.h new file mode 100644 index 0000000..8ae593c --- /dev/null +++ b/Swiften/LinkLocal/LinkLocalService.h @@ -0,0 +1,46 @@ +#pragma once + +#include "Swiften/Base/String.h" +#include "Swiften/JID/JID.h" +#include "Swiften/LinkLocal/DNSSD/DNSSDServiceID.h" +#include "Swiften/LinkLocal/DNSSD/DNSSDResolveServiceQuery.h" +#include "Swiften/LinkLocal/LinkLocalServiceInfo.h" + +namespace Swift { + class LinkLocalService { + public: + LinkLocalService( + const DNSSDServiceID& id, + const DNSSDResolveServiceQuery::Result& info) : + id(id), + info(info) {} + + const DNSSDServiceID& getID() const { + return id; + } + + const String& getName() const { + return id.getName(); + } + + int getPort() const { + return info.port; + } + + const String& getHostname() const { + return info.host; + } + + LinkLocalServiceInfo getInfo() const { + return LinkLocalServiceInfo::createFromTXTRecord(info.info); + } + + String getDescription() const; + + JID getJID() const; + + private: + DNSSDServiceID id; + DNSSDResolveServiceQuery::Result info; + }; +} diff --git a/Swiften/LinkLocal/LinkLocalServiceBrowser.cpp b/Swiften/LinkLocal/LinkLocalServiceBrowser.cpp new file mode 100644 index 0000000..061bf2c --- /dev/null +++ b/Swiften/LinkLocal/LinkLocalServiceBrowser.cpp @@ -0,0 +1,147 @@ +#include <boost/bind.hpp> +#include <iostream> + +#include "Swiften/LinkLocal/LinkLocalServiceBrowser.h" +#include "Swiften/LinkLocal/DNSSD/DNSSDBrowseQuery.h" +#include "Swiften/Network/HostAddress.h" + +namespace Swift { + +LinkLocalServiceBrowser::LinkLocalServiceBrowser(boost::shared_ptr<DNSSDQuerier> querier) : querier(querier), haveError(false) { +} + +LinkLocalServiceBrowser::~LinkLocalServiceBrowser() { + if (isRunning()) { + std::cerr << "WARNING: LinkLocalServiceBrowser still running on destruction" << std::endl; + } +} + + +void LinkLocalServiceBrowser::start() { + assert(!isRunning()); + haveError = false; + browseQuery = querier->createBrowseQuery(); + browseQuery->onServiceAdded.connect( + boost::bind(&LinkLocalServiceBrowser::handleServiceAdded, this, _1)); + browseQuery->onServiceRemoved.connect( + boost::bind(&LinkLocalServiceBrowser::handleServiceRemoved, this, _1)); + browseQuery->onError.connect( + boost::bind(&LinkLocalServiceBrowser::handleBrowseError, this)); + browseQuery->startBrowsing(); +} + +void LinkLocalServiceBrowser::stop() { + assert(isRunning()); + if (isRegistered()) { + unregisterService(); + } + for (ResolveQueryMap::const_iterator i = resolveQueries.begin(); i != resolveQueries.end(); ++i) { + i->second->stop(); + } + resolveQueries.clear(); + services.clear(); + browseQuery->stopBrowsing(); + browseQuery.reset(); + onStopped(haveError); +} + +bool LinkLocalServiceBrowser::isRunning() const { + return browseQuery; +} + +bool LinkLocalServiceBrowser::hasError() const { + return haveError; +} + +bool LinkLocalServiceBrowser::isRegistered() const { + return registerQuery; +} + +void LinkLocalServiceBrowser::registerService(const String& name, int port, const LinkLocalServiceInfo& info) { + assert(!registerQuery); + registerQuery = querier->createRegisterQuery(name, port, info.toTXTRecord()); + registerQuery->onRegisterFinished.connect( + boost::bind(&LinkLocalServiceBrowser::handleRegisterFinished, this, _1)); + registerQuery->registerService(); +} + +void LinkLocalServiceBrowser::updateService(const LinkLocalServiceInfo& info) { + assert(registerQuery); + registerQuery->updateServiceInfo(info.toTXTRecord()); +} + +void LinkLocalServiceBrowser::unregisterService() { + assert(registerQuery); + registerQuery->unregisterService(); + registerQuery.reset(); + selfService.reset(); +} + +std::vector<LinkLocalService> LinkLocalServiceBrowser::getServices() const { + std::vector<LinkLocalService> result; + for (ServiceMap::const_iterator i = services.begin(); i != services.end(); ++i) { + result.push_back(LinkLocalService(i->first, i->second)); + } + return result; +} + +void LinkLocalServiceBrowser::handleServiceAdded(const DNSSDServiceID& service) { + if (selfService && service == *selfService) { + return; + } + boost::shared_ptr<DNSSDResolveServiceQuery> resolveQuery = querier->createResolveServiceQuery(service); + resolveQuery->onServiceResolved.connect( + boost::bind(&LinkLocalServiceBrowser::handleServiceResolved, this, service, _1)); + std::pair<ResolveQueryMap::iterator, bool> r = resolveQueries.insert(std::make_pair(service, resolveQuery)); + if (!r.second) { + r.first->second = resolveQuery; + } + resolveQuery->start(); +} + +void LinkLocalServiceBrowser::handleServiceRemoved(const DNSSDServiceID& service) { + ResolveQueryMap::iterator i = resolveQueries.find(service); + if (i == resolveQueries.end()) { + // Can happen after an unregister(), when getting the old 'self' + // service remove notification. + return; + } + i->second->stop(); + resolveQueries.erase(i); + ServiceMap::iterator j = services.find(service); + assert(j != services.end()); + LinkLocalService linkLocalService(j->first, j->second); + services.erase(j); + onServiceRemoved(linkLocalService); +} + +void LinkLocalServiceBrowser::handleServiceResolved(const DNSSDServiceID& service, const boost::optional<DNSSDResolveServiceQuery::Result>& result) { + if (result) { + std::pair<ServiceMap::iterator, bool> r = services.insert(std::make_pair(service, *result)); + if (r.second) { + onServiceAdded(LinkLocalService(r.first->first, r.first->second)); + } + else { + r.first->second = *result; + onServiceChanged(LinkLocalService(r.first->first, r.first->second)); + } + } +} + +void LinkLocalServiceBrowser::handleRegisterFinished(const boost::optional<DNSSDServiceID>& result) { + if (result) { + selfService = result; + onServiceRegistered(*result); + } + else { + haveError = true; + stop(); + } +} + +void LinkLocalServiceBrowser::handleBrowseError() { + haveError = true; + stop(); +} + +} diff --git a/Swiften/LinkLocal/LinkLocalServiceBrowser.h b/Swiften/LinkLocal/LinkLocalServiceBrowser.h new file mode 100644 index 0000000..66973d5 --- /dev/null +++ b/Swiften/LinkLocal/LinkLocalServiceBrowser.h @@ -0,0 +1,68 @@ +#pragma once + +#include <boost/signal.hpp> +#include <boost/shared_ptr.hpp> +#include <boost/optional.hpp> +#include <map> +#include <vector> + +#include "Swiften/Base/String.h" +#include "Swiften/LinkLocal/DNSSD/DNSSDQuerier.h" +#include "Swiften/LinkLocal/DNSSD/DNSSDResolveServiceQuery.h" +#include "Swiften/LinkLocal/DNSSD/DNSSDRegisterQuery.h" +#include "Swiften/LinkLocal/DNSSD/DNSSDServiceID.h" +#include "Swiften/LinkLocal/LinkLocalService.h" +#include "Swiften/LinkLocal/LinkLocalServiceInfo.h" + +namespace Swift { + class LinkLocalServiceBrowser { + public: + LinkLocalServiceBrowser(boost::shared_ptr<DNSSDQuerier> querier); + ~LinkLocalServiceBrowser(); + + void start(); + void stop(); + bool isRunning() const; + bool hasError() const; + + void registerService( + const String& name, + int port, + const LinkLocalServiceInfo& info = LinkLocalServiceInfo()); + void updateService( + const LinkLocalServiceInfo& info = LinkLocalServiceInfo()); + void unregisterService(); + bool isRegistered() const; + + std::vector<LinkLocalService> getServices() const; + + // FIXME: Ugly that we need this + boost::shared_ptr<DNSSDQuerier> getQuerier() const { + return querier; + } + + boost::signal<void (const LinkLocalService&)> onServiceAdded; + boost::signal<void (const LinkLocalService&)> onServiceChanged; + boost::signal<void (const LinkLocalService&)> onServiceRemoved; + boost::signal<void (const DNSSDServiceID&)> onServiceRegistered; + boost::signal<void (bool)> onStopped; + + private: + void handleServiceAdded(const DNSSDServiceID&); + void handleServiceRemoved(const DNSSDServiceID&); + void handleServiceResolved(const DNSSDServiceID& service, const boost::optional<DNSSDResolveServiceQuery::Result>& result); + void handleRegisterFinished(const boost::optional<DNSSDServiceID>&); + void handleBrowseError(); + + private: + boost::shared_ptr<DNSSDQuerier> querier; + boost::optional<DNSSDServiceID> selfService; + boost::shared_ptr<DNSSDBrowseQuery> browseQuery; + boost::shared_ptr<DNSSDRegisterQuery> registerQuery; + typedef std::map<DNSSDServiceID, boost::shared_ptr<DNSSDResolveServiceQuery> > ResolveQueryMap; + ResolveQueryMap resolveQueries; + typedef std::map<DNSSDServiceID, DNSSDResolveServiceQuery::Result> ServiceMap; + ServiceMap services; + bool haveError; + }; +} diff --git a/Swiften/LinkLocal/LinkLocalServiceInfo.cpp b/Swiften/LinkLocal/LinkLocalServiceInfo.cpp new file mode 100644 index 0000000..8ee7ae0 --- /dev/null +++ b/Swiften/LinkLocal/LinkLocalServiceInfo.cpp @@ -0,0 +1,114 @@ +#include "Swiften/LinkLocal/LinkLocalServiceInfo.h" + +#include <boost/lexical_cast.hpp> + +namespace Swift { + +ByteArray LinkLocalServiceInfo::toTXTRecord() const { + ByteArray result(getEncoded("txtvers=1")); + if (!firstName.isEmpty()) { + result += getEncoded("1st=" + firstName); + } + if (!lastName.isEmpty()) { + result += getEncoded("last=" + lastName); + } + if (!email.isEmpty()) { + result += getEncoded("email=" + email); + } + if (jid.isValid()) { + result += getEncoded("jid=" + jid.toString()); + } + if (!message.isEmpty()) { + result += getEncoded("msg=" + message); + } + if (!nick.isEmpty()) { + result += getEncoded("nick=" + nick); + } + if (port) { + result += getEncoded("port.p2pj=" + String(boost::lexical_cast<std::string>(*port))); + } + + switch (status) { + case Available: result += getEncoded("status=avail"); break; + case Away: result += getEncoded("status=away"); break; + case DND: result += getEncoded("status=dnd"); break; + } + + return result; +} + +ByteArray LinkLocalServiceInfo::getEncoded(const String& s) { + ByteArray sizeByte; + sizeByte.resize(1); + assert(s.getLength() < 256); + sizeByte[0] = s.getUTF8Size(); + return sizeByte + ByteArray(s); +} + +LinkLocalServiceInfo LinkLocalServiceInfo::createFromTXTRecord(const ByteArray& record) { + LinkLocalServiceInfo info; + size_t i = 0; + while (i < record.getSize()) { + std::pair<String,String> entry = readEntry(record, &i); + if (entry.first.isEmpty()) { + break; + } + else if (entry.first == "1st") { + info.setFirstName(entry.second); + } + else if (entry.first == "last") { + info.setLastName(entry.second); + } + else if (entry.first == "email") { + info.setEMail(entry.second); + } + else if (entry.first == "jid") { + info.setJID(JID(entry.second)); + } + else if (entry.first == "msg") { + info.setMessage(entry.second); + } + else if (entry.first == "nick") { + info.setNick(entry.second); + } + else if (entry.first == "port.p2pj") { + info.setPort(boost::lexical_cast<int>(entry.second)); + } + else if (entry.first == "status") { + if (entry.second == "away") { + info.setStatus(Away); + } + else if (entry.second == "dnd") { + info.setStatus(DND); + } + } + } + return info; +} + +std::pair<String,String> LinkLocalServiceInfo::readEntry(const ByteArray& record, size_t* index) { + size_t& i = *index; + String key; + String value; + + size_t entryEnd = i + 1 + record[i]; + ++i; + bool inKey = true; + while (i < entryEnd && i < record.getSize()) { + if (inKey) { + if (record[i] == '=') { + inKey = false; + } + else { + key += record[i]; + } + } + else { + value += record[i]; + } + ++i; + } + return std::make_pair(key, value); +} + +} diff --git a/Swiften/LinkLocal/LinkLocalServiceInfo.h b/Swiften/LinkLocal/LinkLocalServiceInfo.h new file mode 100644 index 0000000..d78b70c --- /dev/null +++ b/Swiften/LinkLocal/LinkLocalServiceInfo.h @@ -0,0 +1,59 @@ +#pragma once + +#include <boost/optional.hpp> + +#include "Swiften/Base/ByteArray.h" +#include "Swiften/Base/String.h" +#include "Swiften/JID/JID.h" + +namespace Swift { + + class LinkLocalServiceInfo { + public: + enum Status { Available, Away, DND }; + + LinkLocalServiceInfo() : status(Available) {} + + const String& getFirstName() const { return firstName; } + void setFirstName(const String& f) { firstName = f; } + + const String& getLastName() const { return lastName; } + void setLastName(const String& l) { lastName = l; } + + const String& getEMail() const { return email; } + void setEMail(const String& e) { email = e; } + + const JID& getJID() const { return jid; } + void setJID(const JID& j) { jid = j; } + + const String& getMessage() const { return message; } + void setMessage(const String& m) { message = m; } + + const String& getNick() const { return nick; } + void setNick(const String& n) { nick = n; } + + Status getStatus() const { return status; } + void setStatus(Status s) { status = s; } + + boost::optional<int> getPort() const { return port; } + void setPort(int p) { port = p; } + + ByteArray toTXTRecord() const; + + static LinkLocalServiceInfo createFromTXTRecord(const ByteArray& record); + + private: + static ByteArray getEncoded(const String&); + static std::pair<String,String> readEntry(const ByteArray&, size_t*); + + private: + String firstName; + String lastName; + String email; + JID jid; + String message; + String nick; + Status status; + boost::optional<int> port; + }; +} diff --git a/Swiften/LinkLocal/OutgoingLinkLocalSession.cpp b/Swiften/LinkLocal/OutgoingLinkLocalSession.cpp new file mode 100644 index 0000000..7b71f82 --- /dev/null +++ b/Swiften/LinkLocal/OutgoingLinkLocalSession.cpp @@ -0,0 +1,45 @@ +#include "Swiften/LinkLocal/OutgoingLinkLocalSession.h" + +#include <boost/bind.hpp> + +#include "Swiften/StreamStack/XMPPLayer.h" +#include "Swiften/Elements/ProtocolHeader.h" +#include "Swiften/Elements/StreamFeatures.h" +#include "Swiften/Elements/IQ.h" + +namespace Swift { + +OutgoingLinkLocalSession::OutgoingLinkLocalSession( + const JID& localJID, + const JID& remoteJID, + boost::shared_ptr<Connection> connection, + PayloadParserFactoryCollection* payloadParserFactories, + PayloadSerializerCollection* payloadSerializers) : + Session(connection, payloadParserFactories, payloadSerializers) { + setLocalJID(localJID); + setRemoteJID(remoteJID); +} + +void OutgoingLinkLocalSession::handleSessionStarted() { + ProtocolHeader header; + header.setFrom(getLocalJID()); + getXMPPLayer()->writeHeader(header); +} + +void OutgoingLinkLocalSession::handleStreamStart(const ProtocolHeader&) { + foreach(const boost::shared_ptr<Element>& stanza, queuedElements_) { + sendElement(stanza); + } + queuedElements_.clear(); +} + +void OutgoingLinkLocalSession::handleElement(boost::shared_ptr<Element> element) { + onElementReceived(element); +} + +void OutgoingLinkLocalSession::queueElement(boost::shared_ptr<Element> element) { + queuedElements_.push_back(element); +} + + +} diff --git a/Swiften/LinkLocal/OutgoingLinkLocalSession.h b/Swiften/LinkLocal/OutgoingLinkLocalSession.h new file mode 100644 index 0000000..32f78e4 --- /dev/null +++ b/Swiften/LinkLocal/OutgoingLinkLocalSession.h @@ -0,0 +1,37 @@ +#pragma once + +#include <boost/shared_ptr.hpp> +#include <boost/signal.hpp> +#include <boost/enable_shared_from_this.hpp> +#include <vector> + +#include "Swiften/Session/Session.h" +#include "Swiften/JID/JID.h" + +namespace Swift { + class ConnectionFactory; + class String; + class Element; + class PayloadParserFactoryCollection; + class PayloadSerializerCollection; + + class OutgoingLinkLocalSession : public Session { + public: + OutgoingLinkLocalSession( + const JID& localJID, + const JID& remoteJID, + boost::shared_ptr<Connection> connection, + PayloadParserFactoryCollection* payloadParserFactories, + PayloadSerializerCollection* payloadSerializers); + + void queueElement(boost::shared_ptr<Element> element); + + private: + void handleSessionStarted(); + void handleElement(boost::shared_ptr<Element>); + void handleStreamStart(const ProtocolHeader&); + + private: + std::vector<boost::shared_ptr<Element> > queuedElements_; + }; +} diff --git a/Swiften/LinkLocal/SConscript b/Swiften/LinkLocal/SConscript new file mode 100644 index 0000000..b929db1 --- /dev/null +++ b/Swiften/LinkLocal/SConscript @@ -0,0 +1,37 @@ +Import("swiften_env") + +myenv = swiften_env.Clone() +myenv.MergeFlags(swiften_env.get("BONJOUR_FLAGS", "")) + +sources = [ + "DNSSD/DNSSDBrowseQuery.cpp", + "DNSSD/DNSSDQuerier.cpp", + "DNSSD/DNSSDRegisterQuery.cpp", + "DNSSD/DNSSDResolveHostnameQuery.cpp", + "DNSSD/DNSSDResolveServiceQuery.cpp", + "DNSSD/DNSSDServiceID.cpp", + "DNSSD/Fake/FakeDNSSDQuerier.cpp", + "DNSSD/Fake/FakeDNSSDQuery.cpp", + "DNSSD/PlatformDNSSDQuerierFactory.cpp", + "IncomingLinkLocalSession.cpp", + "LinkLocalConnector.cpp", + "LinkLocalService.cpp", + "LinkLocalServiceBrowser.cpp", + "LinkLocalServiceInfo.cpp", + "OutgoingLinkLocalSession.cpp", + ] + +if myenv.get("HAVE_BONJOUR", 0) : + myenv.Append(CPPDEFINES = "HAVE_BONJOUR") + sources += [ + "DNSSD/Bonjour/BonjourQuerier.cpp", + "DNSSD/Bonjour/BonjourQuery.cpp", + ] +elif myenv.get("HAVE_AVAHI", 0) : + sources += [ + "DNSSD/Avahi/AvahiQuerier.cpp", + "DNSSD/Avahi/AvahiQuery.cpp" + ] + +objects = myenv.StaticObject(sources) +swiften_env.Append(SWIFTEN_OBJECTS = [objects]) diff --git a/Swiften/LinkLocal/UnitTest/LinkLocalConnectorTest.cpp b/Swiften/LinkLocal/UnitTest/LinkLocalConnectorTest.cpp new file mode 100644 index 0000000..ee5e414 --- /dev/null +++ b/Swiften/LinkLocal/UnitTest/LinkLocalConnectorTest.cpp @@ -0,0 +1,135 @@ +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> + +#include "Swiften/LinkLocal/LinkLocalConnector.h" +#include "Swiften/LinkLocal/LinkLocalService.h" +#include "Swiften/LinkLocal/DNSSD/DNSSDServiceID.h" +#include "Swiften/LinkLocal/DNSSD/DNSSDResolveHostnameQuery.h" +#include "Swiften/LinkLocal/DNSSD/Fake/FakeDNSSDQuerier.h" +#include "Swiften/EventLoop/DummyEventLoop.h" +#include "Swiften/Network/FakeConnection.h" + +using namespace Swift; + +class LinkLocalConnectorTest : public CppUnit::TestFixture { + CPPUNIT_TEST_SUITE(LinkLocalConnectorTest); + CPPUNIT_TEST(testConnect); + CPPUNIT_TEST(testConnect_UnableToResolve); + CPPUNIT_TEST(testConnect_UnableToConnect); + CPPUNIT_TEST(testCancel_DuringResolve); + CPPUNIT_TEST(testCancel_DuringConnect); + CPPUNIT_TEST_SUITE_END(); + + public: + void setUp() { + eventLoop = new DummyEventLoop(); + querier = boost::shared_ptr<FakeDNSSDQuerier>( + new FakeDNSSDQuerier("rabbithole.local")); + connection = boost::shared_ptr<FakeConnection>(new FakeConnection()); + connectFinished = false; + } + + void tearDown() { + delete eventLoop; + } + + void testConnect() { + boost::shared_ptr<LinkLocalConnector> + testling(createConnector("rabbithole.local", 1234)); + querier->setAddress("rabbithole.local", HostAddress("192.168.1.1")); + + testling->connect(); + eventLoop->processEvents(); + + CPPUNIT_ASSERT(connectFinished); + CPPUNIT_ASSERT(!connectError); + CPPUNIT_ASSERT(connection->connectedTo); + CPPUNIT_ASSERT_EQUAL(String(connection->connectedTo->getAddress().toString()), String("192.168.1.1")); + CPPUNIT_ASSERT_EQUAL(connection->connectedTo->getPort(), 1234); + } + + void testConnect_UnableToResolve() { + boost::shared_ptr<LinkLocalConnector> + testling(createConnector("rabbithole.local", 1234)); + querier->setAddress("rabbithole.local", boost::optional<HostAddress>()); + + testling->connect(); + eventLoop->processEvents(); + + CPPUNIT_ASSERT(connectFinished); + CPPUNIT_ASSERT(connectError); + CPPUNIT_ASSERT(!connection->connectedTo); + } + + void testConnect_UnableToConnect() { + boost::shared_ptr<LinkLocalConnector> + testling(createConnector("rabbithole.local", 1234)); + querier->setAddress("rabbithole.local", HostAddress("192.168.1.1")); + connection->setError(Connection::ReadError); + + testling->connect(); + eventLoop->processEvents(); + + CPPUNIT_ASSERT(connectFinished); + CPPUNIT_ASSERT(connectError); + CPPUNIT_ASSERT(!connection->connectedTo); + } + + void testCancel_DuringResolve() { + boost::shared_ptr<LinkLocalConnector> + testling(createConnector("rabbithole.local", 1234)); + testling->connect(); + eventLoop->processEvents(); + CPPUNIT_ASSERT(!connectFinished); + + testling->cancel(); + eventLoop->processEvents(); + querier->setAddress("rabbithole.local", HostAddress("192.168.1.1")); + eventLoop->processEvents(); + + CPPUNIT_ASSERT(FakeConnection::Disconnected == connection->state); + } + + void testCancel_DuringConnect() { + boost::shared_ptr<LinkLocalConnector> + testling(createConnector("rabbithole.local", 1234)); + querier->setAddress("rabbithole.local", HostAddress("192.168.1.1")); + connection->setDelayConnect(); + testling->connect(); + eventLoop->processEvents(); + CPPUNIT_ASSERT(FakeConnection::Connecting == connection->state); + + testling->cancel(); + eventLoop->processEvents(); + + CPPUNIT_ASSERT(FakeConnection::Disconnected == connection->state); + } + + private: + boost::shared_ptr<LinkLocalConnector> createConnector(const String& hostname, int port) { + LinkLocalService service( + DNSSDServiceID("myname", "local."), + DNSSDResolveServiceQuery::Result( + "myname._presence._tcp.local", hostname, port, + LinkLocalServiceInfo().toTXTRecord())); + boost::shared_ptr<LinkLocalConnector> result( + new LinkLocalConnector(service, querier, connection)); + result->onConnectFinished.connect( + boost::bind(&LinkLocalConnectorTest::handleConnected, this, _1)); + return result; + } + + void handleConnected(bool e) { + connectFinished = true; + connectError = e; + } + + private: + DummyEventLoop* eventLoop; + boost::shared_ptr<FakeDNSSDQuerier> querier; + boost::shared_ptr<FakeConnection> connection; + bool connectFinished; + bool connectError; +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(LinkLocalConnectorTest); diff --git a/Swiften/LinkLocal/UnitTest/LinkLocalServiceBrowserTest.cpp b/Swiften/LinkLocal/UnitTest/LinkLocalServiceBrowserTest.cpp new file mode 100644 index 0000000..9f91269 --- /dev/null +++ b/Swiften/LinkLocal/UnitTest/LinkLocalServiceBrowserTest.cpp @@ -0,0 +1,379 @@ +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> +#include <boost/bind.hpp> +#include <map> + +#include "Swiften/LinkLocal/LinkLocalServiceBrowser.h" +#include "Swiften/LinkLocal/LinkLocalService.h" +#include "Swiften/LinkLocal/DNSSD/DNSSDServiceID.h" +#include "Swiften/LinkLocal/DNSSD/DNSSDResolveServiceQuery.h" +#include "Swiften/LinkLocal/DNSSD/Fake/FakeDNSSDQuerier.h" +#include "Swiften/EventLoop/DummyEventLoop.h" + +using namespace Swift; + +class LinkLocalServiceBrowserTest : public CppUnit::TestFixture { + CPPUNIT_TEST_SUITE(LinkLocalServiceBrowserTest); + CPPUNIT_TEST(testConstructor); + CPPUNIT_TEST(testStart); + CPPUNIT_TEST(testServiceAdded); + CPPUNIT_TEST(testServiceAdded_NoServiceInfo); + CPPUNIT_TEST(testServiceAdded_RegisteredService); + CPPUNIT_TEST(testServiceAdded_UnregisteredService); + CPPUNIT_TEST(testServiceChanged); + CPPUNIT_TEST(testServiceRemoved); + CPPUNIT_TEST(testServiceRemoved_UnregisteredService); + CPPUNIT_TEST(testError_BrowseErrorAfterStart); + CPPUNIT_TEST(testError_BrowseErrorAfterResolve); + CPPUNIT_TEST(testRegisterService); + CPPUNIT_TEST(testRegisterService_Error); + CPPUNIT_TEST(testRegisterService_Reregister); + CPPUNIT_TEST(testUpdateService); + CPPUNIT_TEST_SUITE_END(); + + public: + LinkLocalServiceBrowserTest() {} + + void setUp() { + eventLoop = new DummyEventLoop(); + querier = boost::shared_ptr<FakeDNSSDQuerier>(new FakeDNSSDQuerier("wonderland.lit")); + aliceServiceID = new DNSSDServiceID("alice", "wonderland.lit"); + aliceServiceInfo = new DNSSDResolveServiceQuery::Result("_presence._tcp.wonderland.lit", "xmpp.wonderland.lit", 1234, LinkLocalServiceInfo().toTXTRecord()); + testServiceID = new DNSSDServiceID("foo", "bar.local"); + testServiceInfo = new DNSSDResolveServiceQuery::Result("_presence._tcp.bar.local", "xmpp.bar.local", 1234, LinkLocalServiceInfo().toTXTRecord()); + testServiceInfo2 = new DNSSDResolveServiceQuery::Result("_presence.tcp.bar.local", "xmpp.foo.local", 2345, LinkLocalServiceInfo().toTXTRecord()); + errorStopReceived = false; + normalStopReceived = false; + } + + void tearDown() { + addedServices.clear(); + removedServices.clear(); + changedServices.clear(); + + delete aliceServiceID; + delete aliceServiceInfo; + delete testServiceInfo2; + delete testServiceInfo; + delete testServiceID; + delete eventLoop; + } + + void testConstructor() { + boost::shared_ptr<LinkLocalServiceBrowser> testling = createTestling(); + + CPPUNIT_ASSERT(!testling->isRunning()); + CPPUNIT_ASSERT(!testling->hasError()); + } + + void testStart() { + boost::shared_ptr<LinkLocalServiceBrowser> testling = createTestling(); + testling->start(); + + CPPUNIT_ASSERT(testling->isRunning()); + CPPUNIT_ASSERT(!testling->hasError()); + + testling->stop(); + } + + void testServiceAdded() { + boost::shared_ptr<LinkLocalServiceBrowser> testling = createTestling(); + testling->start(); + eventLoop->processEvents(); + + querier->setServiceInfo(*testServiceID,*testServiceInfo); + querier->addService(*testServiceID); + eventLoop->processEvents(); + + CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(addedServices.size())); + CPPUNIT_ASSERT(addedServices[0].getID() == *testServiceID); + CPPUNIT_ASSERT_EQUAL(0, static_cast<int>(changedServices.size())); + CPPUNIT_ASSERT_EQUAL(0, static_cast<int>(removedServices.size())); + std::vector<LinkLocalService> services = testling->getServices(); + CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(services.size())); + CPPUNIT_ASSERT(*testServiceID == services[0].getID()); + CPPUNIT_ASSERT(testServiceInfo->port == services[0].getPort()); + CPPUNIT_ASSERT(testServiceInfo->host == services[0].getHostname()); + + testling->stop(); + } + + void testServiceAdded_NoServiceInfo() { + boost::shared_ptr<LinkLocalServiceBrowser> testling = createTestling(); + testling->start(); + eventLoop->processEvents(); + + querier->addService(*testServiceID); + eventLoop->processEvents(); + + CPPUNIT_ASSERT_EQUAL(0, static_cast<int>(addedServices.size())); + CPPUNIT_ASSERT_EQUAL(0, static_cast<int>(testling->getServices().size())); + + testling->stop(); + } + + void testServiceAdded_RegisteredService() { + boost::shared_ptr<LinkLocalServiceBrowser> testling = createTestling(); + testling->start(); + eventLoop->processEvents(); + + testling->registerService("alice", 1234, LinkLocalServiceInfo()); + eventLoop->processEvents(); + querier->setServiceInfo(*aliceServiceID, *aliceServiceInfo); + querier->addService(*aliceServiceID); + eventLoop->processEvents(); + + CPPUNIT_ASSERT_EQUAL(0, static_cast<int>(addedServices.size())); + CPPUNIT_ASSERT_EQUAL(0, static_cast<int>(testling->getServices().size())); + + testling->stop(); + } + + void testServiceAdded_UnregisteredService() { + boost::shared_ptr<LinkLocalServiceBrowser> testling = createTestling(); + testling->start(); + eventLoop->processEvents(); + testling->registerService("alice", 1234, LinkLocalServiceInfo()); + eventLoop->processEvents(); + testling->unregisterService(); + eventLoop->processEvents(); + + querier->setServiceInfo(*aliceServiceID, *aliceServiceInfo); + querier->addService(*aliceServiceID); + eventLoop->processEvents(); + + CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(addedServices.size())); + CPPUNIT_ASSERT(addedServices[0].getID() == *aliceServiceID); + CPPUNIT_ASSERT_EQUAL(0, static_cast<int>(changedServices.size())); + CPPUNIT_ASSERT_EQUAL(0, static_cast<int>(removedServices.size())); + std::vector<LinkLocalService> services = testling->getServices(); + CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(services.size())); + CPPUNIT_ASSERT(*aliceServiceID == services[0].getID()); + CPPUNIT_ASSERT(aliceServiceInfo->port == services[0].getPort()); + CPPUNIT_ASSERT(aliceServiceInfo->host == services[0].getHostname()); + + testling->stop(); + } + + void testServiceRemoved_UnregisteredService() { + boost::shared_ptr<LinkLocalServiceBrowser> testling = createTestling(); + testling->start(); + eventLoop->processEvents(); + testling->registerService("alice", 1234, LinkLocalServiceInfo()); + eventLoop->processEvents(); + testling->unregisterService(); + eventLoop->processEvents(); + + querier->removeService(*aliceServiceID); + eventLoop->processEvents(); + + CPPUNIT_ASSERT_EQUAL(0, static_cast<int>(removedServices.size())); + + testling->stop(); + } + + void testServiceChanged() { + boost::shared_ptr<LinkLocalServiceBrowser> testling = createTestling(); + testling->start(); + querier->setServiceInfo(*testServiceID,*testServiceInfo); + querier->addService(*testServiceID); + eventLoop->processEvents(); + + querier->setServiceInfo(*testServiceID,*testServiceInfo2); + eventLoop->processEvents(); + + CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(addedServices.size())); + CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(changedServices.size())); + CPPUNIT_ASSERT_EQUAL(0, static_cast<int>(removedServices.size())); + CPPUNIT_ASSERT(changedServices[0].getID() == *testServiceID); + std::vector<LinkLocalService> services = testling->getServices(); + CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(services.size())); + CPPUNIT_ASSERT(*testServiceID == services[0].getID()); + CPPUNIT_ASSERT(testServiceInfo2->port == services[0].getPort()); + CPPUNIT_ASSERT(testServiceInfo2->host == services[0].getHostname()); + + testling->stop(); + } + + void testServiceRemoved() { + boost::shared_ptr<LinkLocalServiceBrowser> testling = createTestling(); + testling->start(); + querier->setServiceInfo(*testServiceID,*testServiceInfo); + querier->addService(*testServiceID); + eventLoop->processEvents(); + + querier->removeService(*testServiceID); + eventLoop->processEvents(); + querier->setServiceInfo(*testServiceID,*testServiceInfo2); + eventLoop->processEvents(); + + CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(addedServices.size())); + CPPUNIT_ASSERT_EQUAL(0, static_cast<int>(changedServices.size())); + CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(removedServices.size())); + CPPUNIT_ASSERT(removedServices[0].getID() == *testServiceID); + CPPUNIT_ASSERT_EQUAL(0, static_cast<int>(testling->getServices().size())); + + testling->stop(); + } + + void testError_BrowseErrorAfterStart() { + boost::shared_ptr<LinkLocalServiceBrowser> testling = createTestling(); + testling->start(); + + querier->setBrowseError(); + eventLoop->processEvents(); + + CPPUNIT_ASSERT(!testling->isRunning()); + CPPUNIT_ASSERT(testling->hasError()); + CPPUNIT_ASSERT(errorStopReceived); + } + + void testError_BrowseErrorAfterResolve() { + boost::shared_ptr<LinkLocalServiceBrowser> testling = createTestling(); + testling->start(); + querier->setServiceInfo(*testServiceID,*testServiceInfo); + querier->addService(*testServiceID); + eventLoop->processEvents(); + + querier->setBrowseError(); + eventLoop->processEvents(); + querier->setServiceInfo(*testServiceID,*testServiceInfo2); + eventLoop->processEvents(); + + CPPUNIT_ASSERT(!testling->isRunning()); + CPPUNIT_ASSERT(testling->hasError()); + CPPUNIT_ASSERT_EQUAL(0, static_cast<int>(testling->getServices().size())); + CPPUNIT_ASSERT_EQUAL(0, static_cast<int>(changedServices.size())); + CPPUNIT_ASSERT(errorStopReceived); + } + + void testRegisterService() { + boost::shared_ptr<LinkLocalServiceBrowser> testling = createTestling(); + testling->start(); + eventLoop->processEvents(); + + LinkLocalServiceInfo info; + info.setFirstName("Foo"); + testling->registerService("foo@bar", 1234, info); + eventLoop->processEvents(); + + CPPUNIT_ASSERT(querier->isServiceRegistered("foo@bar", 1234, info.toTXTRecord())); + CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(registeredServices.size())); + CPPUNIT_ASSERT(registeredServices[0] == DNSSDServiceID("foo@bar", "wonderland.lit")); + testling->stop(); + } + + void testRegisterService_Error() { + boost::shared_ptr<LinkLocalServiceBrowser> testling = createTestling(); + testling->start(); + LinkLocalServiceInfo info; + testling->registerService("foo@bar", 1234, info); + eventLoop->processEvents(); + + querier->setRegisterError(); + eventLoop->processEvents(); + + CPPUNIT_ASSERT(!testling->isRunning()); + CPPUNIT_ASSERT(testling->hasError()); + CPPUNIT_ASSERT(errorStopReceived); + CPPUNIT_ASSERT(!querier->isServiceRegistered("foo@bar", 1234, info.toTXTRecord())); + } + + void testRegisterService_Reregister() { + boost::shared_ptr<LinkLocalServiceBrowser> testling = createTestling(); + testling->start(); + eventLoop->processEvents(); + LinkLocalServiceInfo info; + info.setFirstName("Foo"); + testling->registerService("foo@bar", 1234, info); + eventLoop->processEvents(); + testling->unregisterService(); + eventLoop->processEvents(); + + info.setFirstName("Bar"); + testling->registerService("bar@baz", 3456, info); + eventLoop->processEvents(); + + CPPUNIT_ASSERT(querier->isServiceRegistered("bar@baz", 3456, info.toTXTRecord())); + + testling->stop(); + } + + void testUpdateService() { + boost::shared_ptr<LinkLocalServiceBrowser> testling = createTestling(); + testling->start(); + eventLoop->processEvents(); + + LinkLocalServiceInfo info; + info.setFirstName("Foo"); + testling->registerService("foo@bar", 1234, info); + eventLoop->processEvents(); + info.setFirstName("Bar"); + testling->updateService(info); + + CPPUNIT_ASSERT(querier->isServiceRegistered("foo@bar", 1234, info.toTXTRecord())); + + testling->stop(); + } + + private: + boost::shared_ptr<LinkLocalServiceBrowser> createTestling() { + boost::shared_ptr<LinkLocalServiceBrowser> testling( + new LinkLocalServiceBrowser(querier)); + testling->onServiceAdded.connect(boost::bind( + &LinkLocalServiceBrowserTest::handleServiceAdded, this, _1)); + testling->onServiceChanged.connect(boost::bind( + &LinkLocalServiceBrowserTest::handleServiceChanged, this, _1)); + testling->onServiceRemoved.connect(boost::bind( + &LinkLocalServiceBrowserTest::handleServiceRemoved, this, _1)); + testling->onServiceRegistered.connect(boost::bind( + &LinkLocalServiceBrowserTest::handleServiceRegistered, this, _1)); + testling->onStopped.connect(boost::bind( + &LinkLocalServiceBrowserTest::handleStopped, this, _1)); + return testling; + } + + void handleServiceAdded(const LinkLocalService& service) { + addedServices.push_back(service); + } + + void handleServiceRemoved(const LinkLocalService& service) { + removedServices.push_back(service); + } + + void handleServiceChanged(const LinkLocalService& service) { + changedServices.push_back(service); + } + + void handleServiceRegistered(const DNSSDServiceID& service) { + registeredServices.push_back(service); + } + + void handleStopped(bool error) { + CPPUNIT_ASSERT(!errorStopReceived); + CPPUNIT_ASSERT(!normalStopReceived); + if (error) { + errorStopReceived = true; + } + else { + normalStopReceived = true; + } + } + + private: + DummyEventLoop* eventLoop; + boost::shared_ptr<FakeDNSSDQuerier> querier; + std::vector<LinkLocalService> addedServices; + std::vector<LinkLocalService> changedServices; + std::vector<LinkLocalService> removedServices; + std::vector<DNSSDServiceID> registeredServices; + DNSSDServiceID* aliceServiceID; + DNSSDResolveServiceQuery::Result* aliceServiceInfo; + DNSSDServiceID* testServiceID; + DNSSDResolveServiceQuery::Result* testServiceInfo; + DNSSDResolveServiceQuery::Result* testServiceInfo2; + bool errorStopReceived; + bool normalStopReceived; +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(LinkLocalServiceBrowserTest); diff --git a/Swiften/LinkLocal/UnitTest/LinkLocalServiceInfoTest.cpp b/Swiften/LinkLocal/UnitTest/LinkLocalServiceInfoTest.cpp new file mode 100644 index 0000000..b5d7ef5 --- /dev/null +++ b/Swiften/LinkLocal/UnitTest/LinkLocalServiceInfoTest.cpp @@ -0,0 +1,63 @@ +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> + +#include "Swiften/LinkLocal/LinkLocalServiceInfo.h" + +using namespace Swift; + +class LinkLocalServiceInfoTest : public CppUnit::TestFixture { + CPPUNIT_TEST_SUITE(LinkLocalServiceInfoTest); + CPPUNIT_TEST(testGetTXTRecord); + CPPUNIT_TEST(testCreateFromTXTRecord); + CPPUNIT_TEST(testCreateFromTXTRecord_InvalidSize); + CPPUNIT_TEST(testGetTXTRecordCreateFromTXTRecord_RoundTrip); + CPPUNIT_TEST_SUITE_END(); + + public: + void testGetTXTRecord() { + LinkLocalServiceInfo info; + info.setFirstName("Remko"); + info.setLastName("Tron\xc3\xe7on"); + info.setStatus(LinkLocalServiceInfo::Away); + + CPPUNIT_ASSERT_EQUAL(ByteArray("\x09txtvers=1\x09" + String("1st=Remko\x0dlast=Tron\xc3\xe7on\x0bstatus=away")), info.toTXTRecord()); + } + + void testCreateFromTXTRecord() { + LinkLocalServiceInfo info = LinkLocalServiceInfo::createFromTXTRecord(ByteArray("\x09txtvers=1\x09" + String("1st=Remko\x0dlast=Tron\xc3\xe7on\x0bstatus=away"))); + + CPPUNIT_ASSERT_EQUAL(String("Remko"), info.getFirstName()); + CPPUNIT_ASSERT_EQUAL(String("Tron\xc3\xe7on"), info.getLastName()); + CPPUNIT_ASSERT_EQUAL(LinkLocalServiceInfo::Away, info.getStatus()); + } + + void testCreateFromTXTRecord_InvalidSize() { + LinkLocalServiceInfo info = LinkLocalServiceInfo::createFromTXTRecord(ByteArray("\x10last=a")); + + CPPUNIT_ASSERT_EQUAL(String("a"), info.getLastName()); + } + + void testGetTXTRecordCreateFromTXTRecord_RoundTrip() { + LinkLocalServiceInfo info; + info.setFirstName("Remko"); + info.setLastName("Tron\xc3\xe7on"); + info.setEMail("remko-email@swift.im"); + info.setJID(JID("remko-jid@swift.im")); + info.setMessage("I'm busy"); + info.setNick("el-tramo"); + info.setStatus(LinkLocalServiceInfo::DND); + info.setPort(1234); + + LinkLocalServiceInfo info2 = LinkLocalServiceInfo::createFromTXTRecord(info.toTXTRecord()); + CPPUNIT_ASSERT_EQUAL(info.getFirstName(), info2.getFirstName()); + CPPUNIT_ASSERT_EQUAL(info.getLastName(), info2.getLastName()); + CPPUNIT_ASSERT_EQUAL(info.getEMail(), info2.getEMail()); + CPPUNIT_ASSERT_EQUAL(info.getJID(), info2.getJID()); + CPPUNIT_ASSERT_EQUAL(info.getMessage(), info2.getMessage()); + CPPUNIT_ASSERT_EQUAL(info.getNick(), info2.getNick()); + CPPUNIT_ASSERT(info.getStatus() == info2.getStatus()); + CPPUNIT_ASSERT(info.getPort() == info2.getPort()); + } +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(LinkLocalServiceInfoTest); diff --git a/Swiften/LinkLocal/UnitTest/LinkLocalServiceTest.cpp b/Swiften/LinkLocal/UnitTest/LinkLocalServiceTest.cpp new file mode 100644 index 0000000..69ec718 --- /dev/null +++ b/Swiften/LinkLocal/UnitTest/LinkLocalServiceTest.cpp @@ -0,0 +1,62 @@ +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> + +#include "Swiften/LinkLocal/LinkLocalService.h" + +using namespace Swift; + +class LinkLocalServiceTest : public CppUnit::TestFixture { + CPPUNIT_TEST_SUITE(LinkLocalServiceTest); + CPPUNIT_TEST(testGetDescription_WithNick); + CPPUNIT_TEST(testGetDescription_WithFirstName); + CPPUNIT_TEST(testGetDescription_WithLastName); + CPPUNIT_TEST(testGetDescription_WithFirstAndLastName); + CPPUNIT_TEST(testGetDescription_NoInfo); + CPPUNIT_TEST_SUITE_END(); + + public: + void testGetDescription_WithNick() { + LinkLocalService testling = createService("alice@wonderland", "Alice", "Alice In", "Wonderland"); + + CPPUNIT_ASSERT_EQUAL(String("Alice"), testling.getDescription()); + } + + void testGetDescription_WithFirstName() { + LinkLocalService testling = createService("alice@wonderland", "", "Alice In"); + + CPPUNIT_ASSERT_EQUAL(String("Alice In"), testling.getDescription()); + } + + void testGetDescription_WithLastName() { + LinkLocalService testling = createService("alice@wonderland", "", "", "Wonderland"); + + CPPUNIT_ASSERT_EQUAL(String("Wonderland"), testling.getDescription()); + } + + void testGetDescription_WithFirstAndLastName() { + LinkLocalService testling = createService("alice@wonderland", "", "Alice In", "Wonderland"); + + CPPUNIT_ASSERT_EQUAL(String("Alice In Wonderland"), testling.getDescription()); + } + + void testGetDescription_NoInfo() { + LinkLocalService testling = createService("alice@wonderland"); + + CPPUNIT_ASSERT_EQUAL(String("alice@wonderland"), testling.getDescription()); + } + + private: + LinkLocalService createService(const String& name, const String& nickName = String(), const String& firstName = String(), const String& lastName = String()) { + DNSSDServiceID service(name, "local."); + LinkLocalServiceInfo info; + info.setFirstName(firstName); + info.setLastName(lastName); + info.setNick(nickName); + return LinkLocalService(service, + DNSSDResolveServiceQuery::Result( + name + "._presence._tcp.local", "rabbithole.local", 1234, + info.toTXTRecord())); + } +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(LinkLocalServiceTest); diff --git a/Swiften/MUC/MUC.cpp b/Swiften/MUC/MUC.cpp new file mode 100644 index 0000000..3c06c7a --- /dev/null +++ b/Swiften/MUC/MUC.cpp @@ -0,0 +1,70 @@ +#include "Swiften/MUC/MUC.h" + +#include <boost/bind.hpp> +#include <boost/shared_ptr.hpp> + +#include "Swiften/Presence/PresenceSender.h" +#include "Swiften/Client/StanzaChannel.h" +#include "Swiften/Elements/IQ.h" +#include "Swiften/Elements/MUCPayload.h" + +namespace Swift { + +typedef std::pair<String, MUCOccupant> StringMUCOccupantPair; + +MUC::MUC(StanzaChannel* stanzaChannel, PresenceSender* presenceSender, const JID &muc) : ownMUCJID(muc), stanzaChannel(stanzaChannel), presenceSender(presenceSender) { + scopedConnection_ = stanzaChannel->onPresenceReceived.connect(boost::bind(&MUC::handleIncomingPresence, this, _1)); +} + +void MUC::joinAs(const String &nick) { + firstPresenceSeen = false; + + ownMUCJID = JID(ownMUCJID.getNode(), ownMUCJID.getDomain(), nick); + + boost::shared_ptr<Presence> joinPresence(new Presence()); + joinPresence->setTo(ownMUCJID); + joinPresence->addPayload(boost::shared_ptr<Payload>(new MUCPayload())); + presenceSender->sendPresence(joinPresence); +} + +void MUC::part() { + presenceSender->removeDirectedPresenceReceiver(ownMUCJID); +} + +void MUC::handleIncomingPresence(boost::shared_ptr<Presence> presence) { + if (!isFromMUC(presence->getFrom())) { + return; + } + + if (!firstPresenceSeen) { + if (presence->getType() == Presence::Error) { + onJoinComplete(JoinFailed); + return; + } + firstPresenceSeen = true; + onJoinComplete(JoinSucceeded); + presenceSender->addDirectedPresenceReceiver(ownMUCJID); + } + + String nick = presence->getFrom().getResource(); + if (nick.isEmpty()) { + return; + } + if (presence->getType() == Presence::Unavailable) { + std::map<String,MUCOccupant>::iterator i = occupants.find(nick); + if (i != occupants.end()) { + onOccupantLeft(i->second, Part, ""); + occupants.erase(i); + } + } + else if (presence->getType() == Presence::Available) { + std::pair<std::map<String,MUCOccupant>::iterator, bool> result = occupants.insert(std::make_pair(nick, MUCOccupant(nick))); + if (result.second) { + onOccupantJoined(result.first->second); + } + onOccupantPresenceChange(presence); + } +} + + +} diff --git a/Swiften/MUC/MUC.h b/Swiften/MUC/MUC.h new file mode 100644 index 0000000..1ef974f --- /dev/null +++ b/Swiften/MUC/MUC.h @@ -0,0 +1,59 @@ +#pragma once + +#include "Swiften/JID/JID.h" +#include "Swiften/Base/String.h" +#include "Swiften/Elements/Message.h" +#include "Swiften/Elements/Presence.h" +#include "Swiften/MUC/MUCOccupant.h" + +#include <boost/shared_ptr.hpp> +#include <boost/signals.hpp> +#include <boost/signals/connection.hpp> + +#include <map> + +namespace Swift { + class StanzaChannel; + class PresenceSender; + + class MUC { + public: + enum JoinResult { JoinSucceeded, JoinFailed }; + enum LeavingType { Part }; + + public: + MUC(StanzaChannel* stanzaChannel, PresenceSender* presenceSender, const JID &muc); + + void joinAs(const String &nick); + String getCurrentNick(); + void part(); + void handleIncomingMessage(boost::shared_ptr<Message> message); + + public: + boost::signal<void (JoinResult)> onJoinComplete; + boost::signal<void (boost::shared_ptr<Presence>)> onOccupantPresenceChange; + boost::signal<void (const MUCOccupant&)> onOccupantJoined; + /**Occupant, type, and reason. */ + boost::signal<void (const MUCOccupant&, LeavingType, const String&)> onOccupantLeft; + + private: + bool isFromMUC(const JID& j) const { + return ownMUCJID.equals(j, JID::WithoutResource); + } + + const String& getOwnNick() const { + return ownMUCJID.getResource(); + } + + private: + void handleIncomingPresence(boost::shared_ptr<Presence> presence); + + private: + JID ownMUCJID; + StanzaChannel* stanzaChannel; + PresenceSender* presenceSender; + std::map<String, MUCOccupant> occupants; + bool firstPresenceSeen; + boost::bsignals::scoped_connection scopedConnection_; + }; +} diff --git a/Swiften/MUC/MUCBookmark.h b/Swiften/MUC/MUCBookmark.h new file mode 100644 index 0000000..439e716 --- /dev/null +++ b/Swiften/MUC/MUCBookmark.h @@ -0,0 +1,28 @@ +#pragma once + +#include <boost/optional.hpp> + +#include "Swiften/Base/String.h" +#include "Swiften/JID/JID.h" + +namespace Swift { + class MUCBookmark { + public: + MUCBookmark(const JID& room, const String& bookmarkName) : room_(room), name_(bookmarkName){}; + void setAutojoin(bool enabled) {autojoin_ = enabled;}; + void setNick(const boost::optional<String>& nick) {nick_ = nick;}; + void setPassword(const boost::optional<String>& password) {password_ = password;}; + bool getAutojoin() const {return autojoin_;}; + const boost::optional<String>& getNick() const {return nick_;}; + const boost::optional<String>& getPassword() const {return password_;}; + const String& getName() const {return name_;}; + const JID& getRoom() const {return room_;}; + private: + JID room_; + String name_; + boost::optional<String> nick_; + boost::optional<String> password_; + bool autojoin_; + }; +} + diff --git a/Swiften/MUC/MUCBookmarkManager.cpp b/Swiften/MUC/MUCBookmarkManager.cpp new file mode 100644 index 0000000..f9295e2 --- /dev/null +++ b/Swiften/MUC/MUCBookmarkManager.cpp @@ -0,0 +1,39 @@ +#include "MUCBookmarkManager.h" + +#include "Swiften/Queries/IQRouter.h" + +namespace Swift { + +MUCBookmarkManager::MUCBookmarkManager(IQRouter* iqRouter) { + iqRouter_ = iqRouter; +} + +void MUCBookmarkManager::addBookmark(boost::shared_ptr<MUCBookmark> bookmark) { + bookmarks_.push_back(bookmark); + flush(); + onBookmarkAdded(bookmark); +} + + +void MUCBookmarkManager::removeBookmark(boost::shared_ptr<MUCBookmark> bookmark) { + std::vector<boost::shared_ptr<MUCBookmark> >::iterator it; + for (it = bookmarks_.begin(); it != bookmarks_.end(); it++) { + if ((*it).get() == bookmark.get()) { + bookmarks_.erase(it); + onBookmarkRemoved(bookmark); + return; + } + } + assert(false); + flush(); +} + +void MUCBookmarkManager::flush() { + //FIXME: some code may be useful +} + +const std::vector<boost::shared_ptr<MUCBookmark> >& MUCBookmarkManager::getBookmarks() { + return bookmarks_; +} + +} diff --git a/Swiften/MUC/MUCBookmarkManager.h b/Swiften/MUC/MUCBookmarkManager.h new file mode 100644 index 0000000..ade2e3e --- /dev/null +++ b/Swiften/MUC/MUCBookmarkManager.h @@ -0,0 +1,28 @@ +#pragma once + +#include <vector> + +#include <boost/shared_ptr.hpp> +#include <boost/signals.hpp> + +#include "Swiften/MUC/MUCBookmark.h" + +namespace Swift { + class IQRouter; + class MUCBookmarkManager { + public: + MUCBookmarkManager(IQRouter* iqRouter); + void addBookmark(boost::shared_ptr<MUCBookmark> bookmark); + void removeBookmark(boost::shared_ptr<MUCBookmark> bookmark); + /** Call flush after editing an existing bookmark. */ + void flush(); + /** Returns pointers to the bookmarks. These can be edited, and then flush()ed.*/ + const std::vector<boost::shared_ptr<MUCBookmark> >& getBookmarks(); + boost::signal<void (boost::shared_ptr<MUCBookmark>)> onBookmarkAdded; + boost::signal<void (boost::shared_ptr<MUCBookmark>)> onBookmarkRemoved; + private: + + std::vector<boost::shared_ptr<MUCBookmark> > bookmarks_; + IQRouter* iqRouter_; + }; +} diff --git a/Swiften/MUC/MUCOccupant.cpp b/Swiften/MUC/MUCOccupant.cpp new file mode 100644 index 0000000..6ed8591 --- /dev/null +++ b/Swiften/MUC/MUCOccupant.cpp @@ -0,0 +1,15 @@ +#include "Swiften/MUC/MUCOccupant.h" + +namespace Swift { + +MUCOccupant::MUCOccupant(const String &nick) : nick_(nick) { +} + +MUCOccupant::~MUCOccupant() { +} + +String MUCOccupant::getNick() const { + return nick_; +} + +} diff --git a/Swiften/MUC/MUCOccupant.h b/Swiften/MUC/MUCOccupant.h new file mode 100644 index 0000000..22e58ac --- /dev/null +++ b/Swiften/MUC/MUCOccupant.h @@ -0,0 +1,21 @@ +#ifndef SWIFTEN_MUCOccupant_H +#define SWIFTEN_MUCOccupant_H + +#include "Swiften/Base/String.h" + +namespace Swift { + class Client; + + class MUCOccupant { + public: + MUCOccupant(const String &nick); + ~MUCOccupant(); + + String getNick() const; + + private: + String nick_; + }; +} + +#endif diff --git a/Swiften/MUC/MUCRegistry.cpp b/Swiften/MUC/MUCRegistry.cpp new file mode 100644 index 0000000..95bab08 --- /dev/null +++ b/Swiften/MUC/MUCRegistry.cpp @@ -0,0 +1,8 @@ +#include "Swiften/MUC/MUCRegistry.h" + +namespace Swift { + +MUCRegistry::~MUCRegistry() { +} + +} diff --git a/Swiften/MUC/MUCRegistry.h b/Swiften/MUC/MUCRegistry.h new file mode 100644 index 0000000..a843abb --- /dev/null +++ b/Swiften/MUC/MUCRegistry.h @@ -0,0 +1,12 @@ +#pragma once + +namespace Swift { + class JID; + + class MUCRegistry { + public: + virtual ~MUCRegistry(); + + virtual bool isMUC(const JID&) const = 0; + }; +} diff --git a/Swiften/Network/BoostConnection.cpp b/Swiften/Network/BoostConnection.cpp new file mode 100644 index 0000000..0d62300 --- /dev/null +++ b/Swiften/Network/BoostConnection.cpp @@ -0,0 +1,109 @@ +#include "Swiften/Network/BoostConnection.h" + +#include <iostream> +#include <boost/bind.hpp> +#include <boost/thread.hpp> + +#include "Swiften/EventLoop/MainEventLoop.h" +#include "Swiften/Base/String.h" +#include "Swiften/Base/ByteArray.h" +#include "Swiften/Network/HostAddressPort.h" + +namespace Swift { + +static const size_t BUFFER_SIZE = 4096; + +// ----------------------------------------------------------------------------- + +// A reference-counted non-modifiable buffer class. +class SharedBuffer { + public: + SharedBuffer(const ByteArray& data) : + data_(new std::vector<char>(data.begin(), data.end())), + buffer_(boost::asio::buffer(*data_)) { + } + + // ConstBufferSequence requirements. + typedef boost::asio::const_buffer value_type; + typedef const boost::asio::const_buffer* const_iterator; + const boost::asio::const_buffer* begin() const { return &buffer_; } + const boost::asio::const_buffer* end() const { return &buffer_ + 1; } + + private: + boost::shared_ptr< std::vector<char> > data_; + boost::asio::const_buffer buffer_; +}; + +// ----------------------------------------------------------------------------- + +BoostConnection::BoostConnection(boost::asio::io_service* ioService) : + socket_(*ioService), readBuffer_(BUFFER_SIZE) { +} + +BoostConnection::~BoostConnection() { +} + +void BoostConnection::listen() { + doRead(); +} + +void BoostConnection::connect(const HostAddressPort& addressPort) { + boost::asio::ip::tcp::endpoint endpoint( + boost::asio::ip::address::from_string(addressPort.getAddress().toString()), addressPort.getPort()); + socket_.async_connect( + endpoint, + boost::bind(&BoostConnection::handleConnectFinished, shared_from_this(), boost::asio::placeholders::error)); +} + +void BoostConnection::disconnect() { + //MainEventLoop::removeEventsFromOwner(shared_from_this()); + socket_.close(); +} + +void BoostConnection::write(const ByteArray& data) { + boost::asio::async_write(socket_, SharedBuffer(data), + boost::bind(&BoostConnection::handleDataWritten, shared_from_this(), boost::asio::placeholders::error)); +} + +void BoostConnection::handleConnectFinished(const boost::system::error_code& error) { + if (!error) { + MainEventLoop::postEvent(boost::bind(boost::ref(onConnectFinished), false), shared_from_this()); + doRead(); + } + else if (error != boost::asio::error::operation_aborted) { + MainEventLoop::postEvent(boost::bind(boost::ref(onConnectFinished), true), shared_from_this()); + } +} + +void BoostConnection::doRead() { + socket_.async_read_some( + boost::asio::buffer(readBuffer_), + boost::bind(&BoostConnection::handleSocketRead, shared_from_this(), boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred)); +} + +void BoostConnection::handleSocketRead(const boost::system::error_code& error, size_t bytesTransferred) { + if (!error) { + MainEventLoop::postEvent(boost::bind(boost::ref(onDataRead), ByteArray(&readBuffer_[0], bytesTransferred)), shared_from_this()); + doRead(); + } + else if (error == boost::asio::error::eof) { + MainEventLoop::postEvent(boost::bind(boost::ref(onDisconnected), boost::optional<Error>()), shared_from_this()); + } + else if (error != boost::asio::error::operation_aborted) { + MainEventLoop::postEvent(boost::bind(boost::ref(onDisconnected), ReadError), shared_from_this()); + } +} + +void BoostConnection::handleDataWritten(const boost::system::error_code& error) { + if (!error) { + return; + } + if (error == boost::asio::error::eof) { + MainEventLoop::postEvent(boost::bind(boost::ref(onDisconnected), boost::optional<Error>()), shared_from_this()); + } + else if (error && error != boost::asio::error::operation_aborted) { + MainEventLoop::postEvent(boost::bind(boost::ref(onDisconnected), WriteError), shared_from_this()); + } +} + +} diff --git a/Swiften/Network/BoostConnection.h b/Swiften/Network/BoostConnection.h new file mode 100644 index 0000000..ae09fb8 --- /dev/null +++ b/Swiften/Network/BoostConnection.h @@ -0,0 +1,42 @@ +#pragma once + +#include <boost/asio.hpp> +#include <boost/enable_shared_from_this.hpp> + +#include "Swiften/Network/Connection.h" +#include "Swiften/EventLoop/EventOwner.h" + +namespace boost { + class thread; + namespace system { + class error_code; + } +} + +namespace Swift { + class BoostConnection : public Connection, public EventOwner, public boost::enable_shared_from_this<BoostConnection> { + public: + BoostConnection(boost::asio::io_service* ioService); + ~BoostConnection(); + + virtual void listen(); + virtual void connect(const HostAddressPort& address); + virtual void disconnect(); + virtual void write(const ByteArray& data); + + boost::asio::ip::tcp::socket& getSocket() { + return socket_; + } + + private: + void handleConnectFinished(const boost::system::error_code& error); + void handleSocketRead(const boost::system::error_code& error, size_t bytesTransferred); + void handleDataWritten(const boost::system::error_code& error); + void doRead(); + + private: + boost::asio::ip::tcp::socket socket_; + std::vector<char> readBuffer_; + bool disconnecting_; + }; +} diff --git a/Swiften/Network/BoostConnectionFactory.cpp b/Swiften/Network/BoostConnectionFactory.cpp new file mode 100644 index 0000000..3f62730 --- /dev/null +++ b/Swiften/Network/BoostConnectionFactory.cpp @@ -0,0 +1,13 @@ +#include "Swiften/Network/BoostConnectionFactory.h" +#include "Swiften/Network/BoostConnection.h" + +namespace Swift { + +BoostConnectionFactory::BoostConnectionFactory(boost::asio::io_service* ioService) : ioService(ioService) { +} + +boost::shared_ptr<Connection> BoostConnectionFactory::createConnection() { + return boost::shared_ptr<Connection>(new BoostConnection(ioService)); +} + +} diff --git a/Swiften/Network/BoostConnectionFactory.h b/Swiften/Network/BoostConnectionFactory.h new file mode 100644 index 0000000..5695c6c --- /dev/null +++ b/Swiften/Network/BoostConnectionFactory.h @@ -0,0 +1,20 @@ +#pragma once + +#include <boost/asio.hpp> + +#include "Swiften/Network/ConnectionFactory.h" +#include "Swiften/Network/BoostConnection.h" + +namespace Swift { + class BoostConnection; + + class BoostConnectionFactory : public ConnectionFactory { + public: + BoostConnectionFactory(boost::asio::io_service*); + + virtual boost::shared_ptr<Connection> createConnection(); + + private: + boost::asio::io_service* ioService; + }; +} diff --git a/Swiften/Network/BoostConnectionServer.cpp b/Swiften/Network/BoostConnectionServer.cpp new file mode 100644 index 0000000..cea016d --- /dev/null +++ b/Swiften/Network/BoostConnectionServer.cpp @@ -0,0 +1,68 @@ +#include "Swiften/Network/BoostConnectionServer.h" + +#include <boost/bind.hpp> +#include <boost/system/system_error.hpp> + +#include "Swiften/EventLoop/MainEventLoop.h" + +namespace Swift { + +BoostConnectionServer::BoostConnectionServer(int port, boost::asio::io_service* ioService) : port_(port), ioService_(ioService), acceptor_(NULL) { +} + + +void BoostConnectionServer::start() { + try { + assert(!acceptor_); + acceptor_ = new boost::asio::ip::tcp::acceptor( + *ioService_, + boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), port_)); + acceptNextConnection(); + } + catch (const boost::system::system_error& e) { + if (e.code() == boost::asio::error::address_in_use) { + MainEventLoop::postEvent(boost::bind(boost::ref(onStopped), Conflict), shared_from_this()); + } + else { + MainEventLoop::postEvent(boost::bind(boost::ref(onStopped), UnknownError), shared_from_this()); + } + } +} + + +void BoostConnectionServer::stop() { + stop(boost::optional<Error>()); +} + +void BoostConnectionServer::stop(boost::optional<Error> e) { + if (acceptor_) { + acceptor_->close(); + delete acceptor_; + acceptor_ = NULL; + } + MainEventLoop::postEvent(boost::bind(boost::ref(onStopped), e), shared_from_this()); +} + +void BoostConnectionServer::acceptNextConnection() { + boost::shared_ptr<BoostConnection> newConnection(new BoostConnection(&acceptor_->io_service())); + acceptor_->async_accept(newConnection->getSocket(), + boost::bind(&BoostConnectionServer::handleAccept, shared_from_this(), newConnection, boost::asio::placeholders::error)); +} + +void BoostConnectionServer::handleAccept(boost::shared_ptr<BoostConnection> newConnection, const boost::system::error_code& error) { + if (error) { + MainEventLoop::postEvent( + boost::bind( + &BoostConnectionServer::stop, shared_from_this(), UnknownError), + shared_from_this()); + } + else { + MainEventLoop::postEvent( + boost::bind(boost::ref(onNewConnection), newConnection), + shared_from_this()); + newConnection->listen(); + acceptNextConnection(); + } +} + +} diff --git a/Swiften/Network/BoostConnectionServer.h b/Swiften/Network/BoostConnectionServer.h new file mode 100644 index 0000000..d8e5eb4 --- /dev/null +++ b/Swiften/Network/BoostConnectionServer.h @@ -0,0 +1,36 @@ +#pragma once + +#include <boost/shared_ptr.hpp> +#include <boost/enable_shared_from_this.hpp> +#include <boost/asio.hpp> +#include <boost/signal.hpp> + +#include "Swiften/Network/BoostConnection.h" +#include "Swiften/Network/ConnectionServer.h" +#include "Swiften/EventLoop/EventOwner.h" + +namespace Swift { + class BoostConnectionServer : public ConnectionServer, public EventOwner, public boost::enable_shared_from_this<BoostConnectionServer> { + public: + enum Error { + Conflict, + UnknownError + }; + BoostConnectionServer(int port, boost::asio::io_service* ioService); + + void start(); + void stop(); + + boost::signal<void (boost::optional<Error>)> onStopped; + + private: + void stop(boost::optional<Error> e); + void acceptNextConnection(); + void handleAccept(boost::shared_ptr<BoostConnection> newConnection, const boost::system::error_code& error); + + private: + int port_; + boost::asio::io_service* ioService_; + boost::asio::ip::tcp::acceptor* acceptor_; + }; +} diff --git a/Swiften/Network/BoostIOServiceThread.cpp b/Swiften/Network/BoostIOServiceThread.cpp new file mode 100644 index 0000000..01c3bf3 --- /dev/null +++ b/Swiften/Network/BoostIOServiceThread.cpp @@ -0,0 +1,18 @@ +#include "Swiften/Network/BoostIOServiceThread.h" + +namespace Swift { + +BoostIOServiceThread::BoostIOServiceThread() : thread_(boost::bind(&BoostIOServiceThread::doRun, this)) { +} + +BoostIOServiceThread::~BoostIOServiceThread() { + ioService_.stop(); + thread_.join(); +} + +void BoostIOServiceThread::doRun() { + boost::asio::io_service::work work(ioService_); + ioService_.run(); +} + +} diff --git a/Swiften/Network/BoostIOServiceThread.h b/Swiften/Network/BoostIOServiceThread.h new file mode 100644 index 0000000..ddc90bf --- /dev/null +++ b/Swiften/Network/BoostIOServiceThread.h @@ -0,0 +1,23 @@ +#pragma once + +#include <boost/asio.hpp> +#include <boost/thread.hpp> + +namespace Swift { + class BoostIOServiceThread { + public: + BoostIOServiceThread(); + ~BoostIOServiceThread(); + + boost::asio::io_service& getIOService() { + return ioService_; + } + + private: + void doRun(); + + private: + boost::asio::io_service ioService_; + boost::thread thread_; + }; +} diff --git a/Swiften/Network/BoostTimer.cpp b/Swiften/Network/BoostTimer.cpp new file mode 100644 index 0000000..fdbd45d --- /dev/null +++ b/Swiften/Network/BoostTimer.cpp @@ -0,0 +1,34 @@ +#include "Swiften/Network/BoostTimer.h" + +#include <boost/date_time/posix_time/posix_time.hpp> +#include <boost/asio.hpp> + +#include "Swiften/EventLoop/MainEventLoop.h" + +namespace Swift { + +BoostTimer::BoostTimer(int milliseconds, boost::asio::io_service* service) : + timeout(milliseconds), timer(*service) { +} + +void BoostTimer::start() { + timer.expires_from_now(boost::posix_time::milliseconds(timeout)); + timer.async_wait(boost::bind(&BoostTimer::handleTimerTick, shared_from_this(), boost::asio::placeholders::error)); +} + +void BoostTimer::stop() { + timer.cancel(); +} + +void BoostTimer::handleTimerTick(const boost::system::error_code& error) { + if (error) { + assert(error == boost::asio::error::operation_aborted); + } + else { + MainEventLoop::postEvent(boost::bind(boost::ref(onTick)), shared_from_this()); + timer.expires_from_now(boost::posix_time::milliseconds(timeout)); + timer.async_wait(boost::bind(&BoostTimer::handleTimerTick, shared_from_this(), boost::asio::placeholders::error)); + } +} + +} diff --git a/Swiften/Network/BoostTimer.h b/Swiften/Network/BoostTimer.h new file mode 100644 index 0000000..9b27cf9 --- /dev/null +++ b/Swiften/Network/BoostTimer.h @@ -0,0 +1,25 @@ +#pragma once + +#include <boost/asio.hpp> +#include <boost/thread.hpp> +#include <boost/enable_shared_from_this.hpp> + +#include "Swiften/EventLoop/EventOwner.h" +#include "Swiften/Network/Timer.h" + +namespace Swift { + class BoostTimer : public Timer, public EventOwner, public boost::enable_shared_from_this<BoostTimer> { + public: + BoostTimer(int milliseconds, boost::asio::io_service* service); + + virtual void start(); + virtual void stop(); + + private: + void handleTimerTick(const boost::system::error_code& error); + + private: + int timeout; + boost::asio::deadline_timer timer; + }; +} diff --git a/Swiften/Network/BoostTimerFactory.cpp b/Swiften/Network/BoostTimerFactory.cpp new file mode 100644 index 0000000..bbcd83f --- /dev/null +++ b/Swiften/Network/BoostTimerFactory.cpp @@ -0,0 +1,13 @@ +#include "Swiften/Network/BoostTimerFactory.h" +#include "Swiften/Network/BoostTimer.h" + +namespace Swift { + +BoostTimerFactory::BoostTimerFactory(boost::asio::io_service* ioService) : ioService(ioService) { +} + +boost::shared_ptr<Timer> BoostTimerFactory::createTimer(int milliseconds) { + return boost::shared_ptr<Timer>(new BoostTimer(milliseconds, ioService)); +} + +} diff --git a/Swiften/Network/BoostTimerFactory.h b/Swiften/Network/BoostTimerFactory.h new file mode 100644 index 0000000..e98c9de --- /dev/null +++ b/Swiften/Network/BoostTimerFactory.h @@ -0,0 +1,20 @@ +#pragma once + +#include <boost/asio.hpp> + +#include "Swiften/Network/TimerFactory.h" +#include "Swiften/Network/BoostTimer.h" + +namespace Swift { + class BoostTimer; + + class BoostTimerFactory : public TimerFactory { + public: + BoostTimerFactory(boost::asio::io_service*); + + virtual boost::shared_ptr<Timer> createTimer(int milliseconds); + + private: + boost::asio::io_service* ioService; + }; +} diff --git a/Swiften/Network/CAresDomainNameResolver.cpp b/Swiften/Network/CAresDomainNameResolver.cpp new file mode 100644 index 0000000..c0bf8a0 --- /dev/null +++ b/Swiften/Network/CAresDomainNameResolver.cpp @@ -0,0 +1,162 @@ +// TODO: Check the second param of postEvent. We sometimes omit it. Same +// goes for the PlatformDomainNameResolver. + +#include "Swiften/Network/CAresDomainNameResolver.h" +#include "Swiften/Base/Platform.h" + +#ifndef SWIFTEN_PLATFORM_WINDOWS +#include <netdb.h> +#include <arpa/inet.h> +#endif +#include <algorithm> + +#include "Swiften/Network/DomainNameServiceQuery.h" +#include "Swiften/Network/DomainNameAddressQuery.h" +#include "Swiften/Base/ByteArray.h" +#include "Swiften/EventLoop/MainEventLoop.h" +#include "Swiften/Base/foreach.h" + +namespace Swift { + +class CAresQuery : public boost::enable_shared_from_this<CAresQuery>, public EventOwner { + public: + CAresQuery(const String& query, int dnsclass, int type, CAresDomainNameResolver* resolver) : query(query), dnsclass(dnsclass), type(type), resolver(resolver) { + } + + virtual ~CAresQuery() { + } + + void addToQueue() { + resolver->addToQueue(shared_from_this()); + } + + void doRun(ares_channel* channel) { + ares_query(*channel, query.getUTF8Data(), dnsclass, type, &CAresQuery::handleResult, this); + } + + static void handleResult(void* arg, int status, int timeouts, unsigned char* buffer, int len) { + reinterpret_cast<CAresQuery*>(arg)->handleResult(status, timeouts, buffer, len); + } + + virtual void handleResult(int status, int, unsigned char* buffer, int len) = 0; + + private: + String query; + int dnsclass; + int type; + CAresDomainNameResolver* resolver; +}; + +class CAresDomainNameServiceQuery : public DomainNameServiceQuery, public CAresQuery { + public: + CAresDomainNameServiceQuery(const String& service, CAresDomainNameResolver* resolver) : CAresQuery(service, 1, 33, resolver) { + } + + virtual void run() { + addToQueue(); + } + + void handleResult(int status, int, unsigned char* buffer, int len) { + if (status == ARES_SUCCESS) { + std::vector<DomainNameServiceQuery::Result> records; + ares_srv_reply* rawRecords; + if (ares_parse_srv_reply(buffer, len, &rawRecords) == ARES_SUCCESS) { + for( ; rawRecords != NULL; rawRecords = rawRecords->next) { + DomainNameServiceQuery::Result record; + record.priority = rawRecords->priority; + record.weight = rawRecords->weight; + record.port = rawRecords->port; + record.hostname = String(rawRecords->host); + records.push_back(record); + } + } + std::sort(records.begin(), records.end(), ResultPriorityComparator()); + MainEventLoop::postEvent(boost::bind(boost::ref(onResult), records)); + } + else if (status != ARES_EDESTRUCTION) { + MainEventLoop::postEvent(boost::bind(boost::ref(onResult), std::vector<DomainNameServiceQuery::Result>()), shared_from_this()); + } + } +}; + +class CAresDomainNameAddressQuery : public DomainNameAddressQuery, public CAresQuery { + public: + CAresDomainNameAddressQuery(const String& host, CAresDomainNameResolver* resolver) : CAresQuery(host, 1, 1, resolver) { + } + + virtual void run() { + addToQueue(); + } + + void handleResult(int status, int, unsigned char* buffer, int len) { + if (status == ARES_SUCCESS) { + struct hostent* hosts; + if (ares_parse_a_reply(buffer, len, &hosts, NULL, NULL) == ARES_SUCCESS) { + // Check whether the different fields are what we expect them to be + struct in_addr addr; + addr.s_addr = *(unsigned int*)hosts->h_addr_list[0]; + HostAddress result(inet_ntoa(addr)); + MainEventLoop::postEvent(boost::bind(boost::ref(onResult), result, boost::optional<DomainNameResolveError>()), boost::dynamic_pointer_cast<CAresDomainNameAddressQuery>(shared_from_this())); + ares_free_hostent(hosts); + } + else { + MainEventLoop::postEvent(boost::bind(boost::ref(onResult), HostAddress(), boost::optional<DomainNameResolveError>(DomainNameResolveError())), shared_from_this()); + } + } + else if (status != ARES_EDESTRUCTION) { + MainEventLoop::postEvent(boost::bind(boost::ref(onResult), HostAddress(), boost::optional<DomainNameResolveError>(DomainNameResolveError())), shared_from_this()); + } + } +}; + +CAresDomainNameResolver::CAresDomainNameResolver() : stopRequested(false) { + ares_init(&channel); + thread = new boost::thread(boost::bind(&CAresDomainNameResolver::run, this)); +} + +CAresDomainNameResolver::~CAresDomainNameResolver() { + stopRequested = true; + thread->join(); + ares_destroy(channel); +} + +boost::shared_ptr<DomainNameServiceQuery> CAresDomainNameResolver::createServiceQuery(const String& name) { + return boost::shared_ptr<DomainNameServiceQuery>(new CAresDomainNameServiceQuery(getNormalized(name), this)); +} + +boost::shared_ptr<DomainNameAddressQuery> CAresDomainNameResolver::createAddressQuery(const String& name) { + return boost::shared_ptr<DomainNameAddressQuery>(new CAresDomainNameAddressQuery(getNormalized(name), this)); +} + +void CAresDomainNameResolver::addToQueue(boost::shared_ptr<CAresQuery> query) { + boost::lock_guard<boost::mutex> lock(pendingQueriesMutex); + pendingQueries.push_back(query); +} + +void CAresDomainNameResolver::run() { + fd_set readers, writers; + struct timeval timeout; + timeout.tv_sec = 0; + timeout.tv_usec = 100000; + while(!stopRequested) { + { + boost::unique_lock<boost::mutex> lock(pendingQueriesMutex); + foreach(const boost::shared_ptr<CAresQuery>& query, pendingQueries) { + query->doRun(&channel); + } + pendingQueries.clear(); + } + FD_ZERO(&readers); + FD_ZERO(&writers); + int nfds = ares_fds(channel, &readers, &writers); + //if (nfds) { + // break; + //} + struct timeval tv; + struct timeval* tvp = ares_timeout(channel, &timeout, &tv); + select(nfds, &readers, &writers, NULL, tvp); + ares_process(channel, &readers, &writers); + } +} + +} diff --git a/Swiften/Network/CAresDomainNameResolver.h b/Swiften/Network/CAresDomainNameResolver.h new file mode 100644 index 0000000..0cdd163 --- /dev/null +++ b/Swiften/Network/CAresDomainNameResolver.h @@ -0,0 +1,34 @@ +#pragma once + +#include <ares.h> +#include <boost/thread.hpp> +#include <boost/thread/mutex.hpp> +#include <list> + +#include "Swiften/Network/DomainNameResolver.h" + +namespace Swift { + class CAresQuery; + + class CAresDomainNameResolver : public DomainNameResolver { + public: + CAresDomainNameResolver(); + ~CAresDomainNameResolver(); + + virtual boost::shared_ptr<DomainNameServiceQuery> createServiceQuery(const String& name); + virtual boost::shared_ptr<DomainNameAddressQuery> createAddressQuery(const String& name); + + private: + friend class CAresQuery; + + void run(); + void addToQueue(boost::shared_ptr<CAresQuery>); + + private: + bool stopRequested; + ares_channel channel; + boost::thread* thread; + boost::mutex pendingQueriesMutex; + std::list< boost::shared_ptr<CAresQuery> > pendingQueries; + }; +} diff --git a/Swiften/Network/Connection.h b/Swiften/Network/Connection.h new file mode 100644 index 0000000..a995774 --- /dev/null +++ b/Swiften/Network/Connection.h @@ -0,0 +1,31 @@ +#pragma once + +#include <boost/signals.hpp> + +#include "Swiften/Base/ByteArray.h" +#include "Swiften/Base/String.h" + +namespace Swift { + class HostAddressPort; + + class Connection { + public: + enum Error { + ReadError, + WriteError + }; + + Connection() {} + virtual ~Connection() {} + + virtual void listen() = 0; + virtual void connect(const HostAddressPort& address) = 0; + virtual void disconnect() = 0; + virtual void write(const ByteArray& data) = 0; + + public: + boost::signal<void (bool /* error */)> onConnectFinished; + boost::signal<void (const boost::optional<Error>&)> onDisconnected; + boost::signal<void (const ByteArray&)> onDataRead; + }; +} diff --git a/Swiften/Network/ConnectionFactory.cpp b/Swiften/Network/ConnectionFactory.cpp new file mode 100644 index 0000000..686a165 --- /dev/null +++ b/Swiften/Network/ConnectionFactory.cpp @@ -0,0 +1,8 @@ +#include "Swiften/Network/ConnectionFactory.h" + +namespace Swift { + +ConnectionFactory::~ConnectionFactory() { +} + +} diff --git a/Swiften/Network/ConnectionFactory.h b/Swiften/Network/ConnectionFactory.h new file mode 100644 index 0000000..e78f6ab --- /dev/null +++ b/Swiften/Network/ConnectionFactory.h @@ -0,0 +1,14 @@ +#pragma once + +#include <boost/shared_ptr.hpp> + +namespace Swift { + class Connection; + + class ConnectionFactory { + public: + virtual ~ConnectionFactory(); + + virtual boost::shared_ptr<Connection> createConnection() = 0; + }; +} diff --git a/Swiften/Network/ConnectionServer.cpp b/Swiften/Network/ConnectionServer.cpp new file mode 100644 index 0000000..7f63fee --- /dev/null +++ b/Swiften/Network/ConnectionServer.cpp @@ -0,0 +1,8 @@ +#include "Swiften/Network/ConnectionServer.h" + +namespace Swift { + +ConnectionServer::~ConnectionServer() { +} + +} diff --git a/Swiften/Network/ConnectionServer.h b/Swiften/Network/ConnectionServer.h new file mode 100644 index 0000000..539367d --- /dev/null +++ b/Swiften/Network/ConnectionServer.h @@ -0,0 +1,15 @@ +#pragma once + +#include <boost/shared_ptr.hpp> +#include <boost/signal.hpp> + +#include "Swiften/Network/Connection.h" + +namespace Swift { + class ConnectionServer { + public: + virtual ~ConnectionServer(); + + boost::signal<void (boost::shared_ptr<Connection>)> onNewConnection; + }; +} diff --git a/Swiften/Network/Connector.cpp b/Swiften/Network/Connector.cpp new file mode 100644 index 0000000..d372bf2 --- /dev/null +++ b/Swiften/Network/Connector.cpp @@ -0,0 +1,126 @@ +#include "Swiften/Network/Connector.h" + +#include <boost/bind.hpp> +#include <iostream> + +#include "Swiften/Network/ConnectionFactory.h" +#include "Swiften/Network/DomainNameResolver.h" +#include "Swiften/Network/DomainNameAddressQuery.h" +#include "Swiften/Network/TimerFactory.h" + +namespace Swift { + +Connector::Connector(const String& hostname, DomainNameResolver* resolver, ConnectionFactory* connectionFactory, TimerFactory* timerFactory) : hostname(hostname), resolver(resolver), connectionFactory(connectionFactory), timerFactory(timerFactory), timeoutMilliseconds(0), queriedAllHosts(true) { +} + +void Connector::setTimeoutMilliseconds(int milliseconds) { + timeoutMilliseconds = milliseconds; +} + +void Connector::start() { + //std::cout << "Connector::start()" << std::endl; + assert(!currentConnection); + assert(!serviceQuery); + assert(!timer); + queriedAllHosts = false; + serviceQuery = resolver->createServiceQuery("_xmpp-client._tcp." + hostname); + serviceQuery->onResult.connect(boost::bind(&Connector::handleServiceQueryResult, this, _1)); + if (timeoutMilliseconds > 0) { + timer = timerFactory->createTimer(timeoutMilliseconds); + timer->onTick.connect(boost::bind(&Connector::handleTimeout, this)); + timer->start(); + } + serviceQuery->run(); +} + +void Connector::queryAddress(const String& hostname) { + assert(!addressQuery); + addressQuery = resolver->createAddressQuery(hostname); + addressQuery->onResult.connect(boost::bind(&Connector::handleAddressQueryResult, this, _1, _2)); + addressQuery->run(); +} + +void Connector::handleServiceQueryResult(const std::vector<DomainNameServiceQuery::Result>& result) { + //std::cout << "Received SRV results" << std::endl; + serviceQueryResults = std::deque<DomainNameServiceQuery::Result>(result.begin(), result.end()); + serviceQuery.reset(); + tryNextHostname(); +} + +void Connector::tryNextHostname() { + if (queriedAllHosts) { + //std::cout << "Connector::tryNextHostName(): Queried all hosts. Error." << std::endl; + finish(boost::shared_ptr<Connection>()); + } + else if (serviceQueryResults.empty()) { + //std::cout << "Connector::tryNextHostName(): Falling back on A resolution" << std::endl; + // Fall back on simple address resolving + queriedAllHosts = true; + queryAddress(hostname); + } + else { + //std::cout << "Connector::tryNextHostName(): Querying next address" << std::endl; + queryAddress(serviceQueryResults.front().hostname); + } +} + +void Connector::handleAddressQueryResult(const HostAddress& address, boost::optional<DomainNameResolveError> error) { + //std::cout << "Connector::handleAddressQueryResult(): Start" << std::endl; + addressQuery.reset(); + if (!serviceQueryResults.empty()) { + DomainNameServiceQuery::Result serviceQueryResult = serviceQueryResults.front(); + serviceQueryResults.pop_front(); + if (error) { + //std::cout << "Connector::handleAddressQueryResult(): A lookup for SRV host " << serviceQueryResult.hostname << " failed." << std::endl; + tryNextHostname(); + } + else { + //std::cout << "Connector::handleAddressQueryResult(): A lookup for SRV host " << serviceQueryResult.hostname << " succeeded: " << address.toString() << std::endl; + tryConnect(HostAddressPort(address, serviceQueryResult.port)); + } + } + else if (error) { + //std::cout << "Connector::handleAddressQueryResult(): Fallback address query failed. Giving up" << std::endl; + // The fallback address query failed + assert(queriedAllHosts); + finish(boost::shared_ptr<Connection>()); + } + else { + //std::cout << "Connector::handleAddressQueryResult(): Fallback address query succeeded: " << address.toString() << std::endl; + // The fallback query succeeded + tryConnect(HostAddressPort(address, 5222)); + } +} + +void Connector::tryConnect(const HostAddressPort& target) { + assert(!currentConnection); + //std::cout << "Connector::tryConnect() " << target.getAddress().toString() << " " << target.getPort() << std::endl; + currentConnection = connectionFactory->createConnection(); + currentConnection->onConnectFinished.connect(boost::bind(&Connector::handleConnectionConnectFinished, this, _1)); + currentConnection->connect(target); +} + +void Connector::handleConnectionConnectFinished(bool error) { + //std::cout << "Connector::handleConnectionConnectFinished() " << error << std::endl; + if (error) { + currentConnection.reset(); + tryNextHostname(); + } + else { + finish(currentConnection); + } +} + +void Connector::finish(boost::shared_ptr<Connection> connection) { + if (timer) { + timer->stop(); + timer.reset(); + } + onConnectFinished(connection); +} + +void Connector::handleTimeout() { + finish(boost::shared_ptr<Connection>()); +} + +}; diff --git a/Swiften/Network/Connector.h b/Swiften/Network/Connector.h new file mode 100644 index 0000000..507f085 --- /dev/null +++ b/Swiften/Network/Connector.h @@ -0,0 +1,54 @@ +#pragma once + +#include <deque> +#include <boost/signal.hpp> +#include <boost/shared_ptr.hpp> + +#include "Swiften/Network/DomainNameServiceQuery.h" +#include "Swiften/Network/Connection.h" +#include "Swiften/Network/Timer.h" +#include "Swiften/Network/HostAddressPort.h" +#include "Swiften/Base/String.h" +#include "Swiften/Network/DomainNameResolveError.h" + +namespace Swift { + class DomainNameAddressQuery; + class DomainNameResolver; + class ConnectionFactory; + class TimerFactory; + + class Connector : public boost::bsignals::trackable { + public: + Connector(const String& hostname, DomainNameResolver*, ConnectionFactory*, TimerFactory*); + + void setTimeoutMilliseconds(int milliseconds); + void start(); + + boost::signal<void (boost::shared_ptr<Connection>)> onConnectFinished; + + private: + void handleServiceQueryResult(const std::vector<DomainNameServiceQuery::Result>& result); + void handleAddressQueryResult(const HostAddress& address, boost::optional<DomainNameResolveError> error); + void queryAddress(const String& hostname); + + void tryNextHostname(); + void tryConnect(const HostAddressPort& target); + + void handleConnectionConnectFinished(bool error); + void finish(boost::shared_ptr<Connection>); + void handleTimeout(); + + private: + String hostname; + DomainNameResolver* resolver; + ConnectionFactory* connectionFactory; + TimerFactory* timerFactory; + int timeoutMilliseconds; + boost::shared_ptr<Timer> timer; + boost::shared_ptr<DomainNameServiceQuery> serviceQuery; + std::deque<DomainNameServiceQuery::Result> serviceQueryResults; + boost::shared_ptr<DomainNameAddressQuery> addressQuery; + bool queriedAllHosts; + boost::shared_ptr<Connection> currentConnection; + }; +}; diff --git a/Swiften/Network/DomainNameAddressQuery.cpp b/Swiften/Network/DomainNameAddressQuery.cpp new file mode 100644 index 0000000..5e77cd7 --- /dev/null +++ b/Swiften/Network/DomainNameAddressQuery.cpp @@ -0,0 +1,8 @@ +#include "Swiften/Network/DomainNameAddressQuery.h" + +namespace Swift { + +DomainNameAddressQuery::~DomainNameAddressQuery() { +} + +} diff --git a/Swiften/Network/DomainNameAddressQuery.h b/Swiften/Network/DomainNameAddressQuery.h new file mode 100644 index 0000000..66a79db --- /dev/null +++ b/Swiften/Network/DomainNameAddressQuery.h @@ -0,0 +1,19 @@ +#pragma once + +#include <boost/signals.hpp> +#include <boost/optional.hpp> +#include <boost/shared_ptr.hpp> + +#include "Swiften/Network/DomainNameResolveError.h" +#include "Swiften/Network/HostAddress.h" + +namespace Swift { + class DomainNameAddressQuery { + public: + virtual ~DomainNameAddressQuery(); + + virtual void run() = 0; + + boost::signal<void (const HostAddress&, boost::optional<DomainNameResolveError>)> onResult; + }; +} diff --git a/Swiften/Network/DomainNameResolveError.h b/Swiften/Network/DomainNameResolveError.h new file mode 100644 index 0000000..860ea23 --- /dev/null +++ b/Swiften/Network/DomainNameResolveError.h @@ -0,0 +1,10 @@ +#pragma once + +#include "Swiften/Base/Error.h" + +namespace Swift { + class DomainNameResolveError : public Error { + public: + DomainNameResolveError() {} + }; +} diff --git a/Swiften/Network/DomainNameResolver.cpp b/Swiften/Network/DomainNameResolver.cpp new file mode 100644 index 0000000..63ed881 --- /dev/null +++ b/Swiften/Network/DomainNameResolver.cpp @@ -0,0 +1,22 @@ +#include "Swiften/Network/DomainNameResolver.h" + +#include <idna.h> + +namespace Swift { + +DomainNameResolver::~DomainNameResolver() { +} + +String DomainNameResolver::getNormalized(const String& domain) { + char* output; + if (idna_to_ascii_8z(domain.getUTF8Data(), &output, 0) == IDNA_SUCCESS) { + String result(output); + free(output); + return result; + } + else { + return domain; + } +} + +} diff --git a/Swiften/Network/DomainNameResolver.h b/Swiften/Network/DomainNameResolver.h new file mode 100644 index 0000000..d3dab26 --- /dev/null +++ b/Swiften/Network/DomainNameResolver.h @@ -0,0 +1,22 @@ +#pragma once + +#include <boost/shared_ptr.hpp> + +#include "Swiften/Base/String.h" + +namespace Swift { + class DomainNameServiceQuery; + class DomainNameAddressQuery; + class String; + + class DomainNameResolver { + public: + virtual ~DomainNameResolver(); + + virtual boost::shared_ptr<DomainNameServiceQuery> createServiceQuery(const String& name) = 0; + virtual boost::shared_ptr<DomainNameAddressQuery> createAddressQuery(const String& name) = 0; + + protected: + static String getNormalized(const String& domain); + }; +} diff --git a/Swiften/Network/DomainNameServiceQuery.cpp b/Swiften/Network/DomainNameServiceQuery.cpp new file mode 100644 index 0000000..7dfd353 --- /dev/null +++ b/Swiften/Network/DomainNameServiceQuery.cpp @@ -0,0 +1,8 @@ +#include "Swiften/Network/DomainNameServiceQuery.h" + +namespace Swift { + +DomainNameServiceQuery::~DomainNameServiceQuery() { +} + +} diff --git a/Swiften/Network/DomainNameServiceQuery.h b/Swiften/Network/DomainNameServiceQuery.h new file mode 100644 index 0000000..57e48d3 --- /dev/null +++ b/Swiften/Network/DomainNameServiceQuery.h @@ -0,0 +1,33 @@ +#pragma once + +#include <boost/signals.hpp> +#include <boost/optional.hpp> +#include <vector> + +#include "Swiften/Base/String.h" +#include "Swiften/Network/DomainNameResolveError.h" + +namespace Swift { + class DomainNameServiceQuery { + public: + struct Result { + Result(const String& hostname = "", int port = -1, int priority = -1, int weight = -1) : hostname(hostname), port(port), priority(priority), weight(weight) {} + String hostname; + int port; + int priority; + int weight; + }; + + struct ResultPriorityComparator { + bool operator()(const DomainNameServiceQuery::Result& a, const DomainNameServiceQuery::Result& b) const { + return a.priority < b.priority; + } + }; + + virtual ~DomainNameServiceQuery(); + + virtual void run() = 0; + + boost::signal<void (const std::vector<Result>&)> onResult; + }; +} diff --git a/Swiften/Network/DummyConnection.h b/Swiften/Network/DummyConnection.h new file mode 100644 index 0000000..11281b3 --- /dev/null +++ b/Swiften/Network/DummyConnection.h @@ -0,0 +1,40 @@ +#pragma once + +#include <cassert> +#include <boost/bind.hpp> +#include <boost/enable_shared_from_this.hpp> + +#include "Swiften/Network/Connection.h" +#include "Swiften/EventLoop/MainEventLoop.h" +#include "Swiften/EventLoop/EventOwner.h" + +namespace Swift { + class DummyConnection : + public Connection, + public EventOwner, + public boost::enable_shared_from_this<DummyConnection> { + + void listen() { + assert(false); + } + + void connect(const HostAddressPort&) { + assert(false); + } + + void disconnect() { + assert(false); + } + + void write(const ByteArray& data) { + onDataWritten(data); + } + + void receive(const ByteArray& data) { + MainEventLoop::postEvent(boost::bind( + boost::ref(onDataRead), ByteArray(data)), shared_from_this()); + } + + boost::signal<void (const ByteArray&)> onDataWritten; + }; +} diff --git a/Swiften/Network/DummyTimerFactory.cpp b/Swiften/Network/DummyTimerFactory.cpp new file mode 100644 index 0000000..7626584 --- /dev/null +++ b/Swiften/Network/DummyTimerFactory.cpp @@ -0,0 +1,60 @@ +#include "Swiften/Network/DummyTimerFactory.h" + +#include <algorithm> + +#include "Swiften/Base/foreach.h" +#include "Swiften/Network/Timer.h" + +namespace Swift { + +class DummyTimerFactory::DummyTimer : public Timer { + public: + DummyTimer(int timeout) : timeout(timeout), isRunning(false) { + } + + virtual void start() { + isRunning = true; + } + + virtual void stop() { + isRunning = false; + } + + int timeout; + bool isRunning; +}; + + +DummyTimerFactory::DummyTimerFactory() : currentTime(0) { +} + +boost::shared_ptr<Timer> DummyTimerFactory::createTimer(int milliseconds) { + boost::shared_ptr<DummyTimer> timer(new DummyTimer(milliseconds)); + timers.push_back(timer); + return timer; +} + +static bool hasZeroTimeout(boost::shared_ptr<DummyTimerFactory::DummyTimer> timer) { + return timer->timeout == 0; +} + +void DummyTimerFactory::setTime(int time) { + assert(time > currentTime); + int increment = time - currentTime; + std::vector< boost::shared_ptr<DummyTimer> > notifyTimers(timers.begin(), timers.end()); + foreach(boost::shared_ptr<DummyTimer> timer, notifyTimers) { + if (increment >= timer->timeout) { + if (timer->isRunning) { + timer->onTick(); + } + timer->timeout = 0; + } + else { + timer->timeout -= increment; + } + } + timers.erase(std::remove_if(timers.begin(), timers.end(), hasZeroTimeout), timers.end()); + currentTime = time; +} + +} diff --git a/Swiften/Network/DummyTimerFactory.h b/Swiften/Network/DummyTimerFactory.h new file mode 100644 index 0000000..feac029 --- /dev/null +++ b/Swiften/Network/DummyTimerFactory.h @@ -0,0 +1,22 @@ +#pragma once + +#include <list> + +#include "Swiften/Network/TimerFactory.h" + +namespace Swift { + class DummyTimerFactory : public TimerFactory { + public: + class DummyTimer; + + DummyTimerFactory(); + + virtual boost::shared_ptr<Timer> createTimer(int milliseconds); + void setTime(int time); + + private: + friend class DummyTimer; + int currentTime; + std::list<boost::shared_ptr<DummyTimer> > timers; + }; +} diff --git a/Swiften/Network/FakeConnection.h b/Swiften/Network/FakeConnection.h new file mode 100644 index 0000000..92a03c3 --- /dev/null +++ b/Swiften/Network/FakeConnection.h @@ -0,0 +1,88 @@ +#pragma once + +#include <boost/optional.hpp> +#include <boost/bind.hpp> +#include <boost/enable_shared_from_this.hpp> +#include <vector> + +#include "Swiften/Network/Connection.h" +#include "Swiften/Network/HostAddressPort.h" +#include "Swiften/EventLoop/EventOwner.h" +#include "Swiften/EventLoop/MainEventLoop.h" + +namespace Swift { + class FakeConnection : + public Connection, + public EventOwner, + public boost::enable_shared_from_this<FakeConnection> { + public: + enum State { + Initial, + Connecting, + Connected, + Disconnected, + DisconnectedWithError + }; + + FakeConnection() : state(Initial), delayConnect(false) {} + + virtual void listen() { + assert(false); + } + + void setError(const Error& e) { + error = boost::optional<Error>(e); + state = DisconnectedWithError; + if (connectedTo) { + MainEventLoop::postEvent( + boost::bind(boost::ref(onDisconnected), error), + shared_from_this()); + } + } + + virtual void connect(const HostAddressPort& address) { + if (delayConnect) { + state = Connecting; + } + else { + if (!error) { + connectedTo = address; + state = Connected; + } + else { + state = DisconnectedWithError; + } + MainEventLoop::postEvent( + boost::bind(boost::ref(onConnectFinished), error), + shared_from_this()); + } + } + + virtual void disconnect() { + if (!error) { + state = Disconnected; + } + else { + state = DisconnectedWithError; + } + connectedTo.reset(); + MainEventLoop::postEvent( + boost::bind(boost::ref(onDisconnected), error), + shared_from_this()); + } + + virtual void write(const ByteArray& data) { + dataWritten.push_back(data); + } + + void setDelayConnect() { + delayConnect = true; + } + + boost::optional<HostAddressPort> connectedTo; + std::vector<ByteArray> dataWritten; + boost::optional<Error> error; + State state; + bool delayConnect; + }; +} diff --git a/Swiften/Network/HostAddress.cpp b/Swiften/Network/HostAddress.cpp new file mode 100644 index 0000000..8ac66bb --- /dev/null +++ b/Swiften/Network/HostAddress.cpp @@ -0,0 +1,61 @@ +#include "Swiften/Network/HostAddress.h" + +#include <boost/numeric/conversion/cast.hpp> +#include <boost/lexical_cast.hpp> +#include <cassert> +#include <sstream> +#include <iomanip> + +#include "Swiften/Base/foreach.h" +#include "Swiften/Base/String.h" + +namespace Swift { + +HostAddress::HostAddress() { + for (int i = 0; i < 4; ++i) { + address_.push_back(0); + } +} + +HostAddress::HostAddress(const String& address) { + std::vector<String> components = address.split('.'); + assert(components.size() == 4); + foreach(const String& component, components) { + address_.push_back(boost::lexical_cast<int>(component.getUTF8String())); + } +} + +HostAddress::HostAddress(const unsigned char* address, int length) { + assert(length == 4 || length == 16); + address_.reserve(length); + for (int i = 0; i < length; ++i) { + address_.push_back(address[i]); + } +} + +std::string HostAddress::toString() const { + if (address_.size() == 4) { + std::ostringstream result; + for (size_t i = 0; i < address_.size() - 1; ++i) { + result << boost::numeric_cast<unsigned int>(address_[i]) << "."; + } + result << boost::numeric_cast<unsigned int>(address_[address_.size() - 1]); + return result.str(); + } + else if (address_.size() == 16) { + std::ostringstream result; + result << std::hex; + result.fill('0'); + for (size_t i = 0; i < (address_.size() / 2) - 1; ++i) { + result << std::setw(2) << boost::numeric_cast<unsigned int>(address_[2*i]) << std::setw(2) << boost::numeric_cast<unsigned int>(address_[(2*i)+1]) << ":"; + } + result << std::setw(2) << boost::numeric_cast<unsigned int>(address_[address_.size() - 2]) << std::setw(2) << boost::numeric_cast<unsigned int>(address_[address_.size() - 1]); + return result.str(); + } + else { + assert(false); + return ""; + } +} + +} diff --git a/Swiften/Network/HostAddress.h b/Swiften/Network/HostAddress.h new file mode 100644 index 0000000..11f8a2b --- /dev/null +++ b/Swiften/Network/HostAddress.h @@ -0,0 +1,28 @@ +#pragma once + +#include <string> +#include <vector> + +namespace Swift { + class String; + + class HostAddress { + public: + HostAddress(); + HostAddress(const String&); + HostAddress(const unsigned char* address, int length); + + const std::vector<unsigned char>& getRawAddress() const { + return address_; + } + + std::string toString() const; + + bool operator==(const HostAddress& o) const { + return address_ == o.address_; + } + + private: + std::vector<unsigned char> address_; + }; +} diff --git a/Swiften/Network/HostAddressPort.h b/Swiften/Network/HostAddressPort.h new file mode 100644 index 0000000..d632058 --- /dev/null +++ b/Swiften/Network/HostAddressPort.h @@ -0,0 +1,30 @@ +#ifndef SWIFTEN_HostAddressPort_H +#define SWIFTEN_HostAddressPort_H + +#include "Swiften/Network/HostAddress.h" + +namespace Swift { + class HostAddressPort { + public: + HostAddressPort(const HostAddress& address, int port) : address_(address), port_(port) { + } + + const HostAddress& getAddress() const { + return address_; + } + + int getPort() const { + return port_; + } + + bool operator==(const HostAddressPort& o) const { + return address_ == o.address_ && port_ == o.port_; + } + + private: + HostAddress address_; + int port_; + }; +} + +#endif diff --git a/Swiften/Network/MainBoostIOServiceThread.cpp b/Swiften/Network/MainBoostIOServiceThread.cpp new file mode 100644 index 0000000..672bb07 --- /dev/null +++ b/Swiften/Network/MainBoostIOServiceThread.cpp @@ -0,0 +1,12 @@ +#include "Swiften/Network/MainBoostIOServiceThread.h" + +#include "Swiften/Network/BoostIOServiceThread.h" + +namespace Swift { + +BoostIOServiceThread& MainBoostIOServiceThread::getInstance() { + static BoostIOServiceThread instance; + return instance; +} + +} diff --git a/Swiften/Network/MainBoostIOServiceThread.h b/Swiften/Network/MainBoostIOServiceThread.h new file mode 100644 index 0000000..cca7c2e --- /dev/null +++ b/Swiften/Network/MainBoostIOServiceThread.h @@ -0,0 +1,10 @@ +#pragma once + +namespace Swift { + class BoostIOServiceThread; + + class MainBoostIOServiceThread { + public: + static BoostIOServiceThread& getInstance(); + }; +} diff --git a/Swiften/Network/PlatformDomainNameResolver.cpp b/Swiften/Network/PlatformDomainNameResolver.cpp new file mode 100644 index 0000000..7b8a6d5 --- /dev/null +++ b/Swiften/Network/PlatformDomainNameResolver.cpp @@ -0,0 +1,94 @@ +#include "Swiften/Network/PlatformDomainNameResolver.h" + +// Putting this early on, because some system types conflict with thread +#include "Swiften/Network/PlatformDomainNameServiceQuery.h" + +#include <string> +#include <vector> +#include <boost/asio.hpp> +#include <boost/bind.hpp> +#include <boost/thread.hpp> +#include <boost/enable_shared_from_this.hpp> +#include <algorithm> + +#include "Swiften/Base/String.h" +#include "Swiften/Network/HostAddress.h" +#include "Swiften/EventLoop/MainEventLoop.h" +#include "Swiften/Network/HostAddressPort.h" +#include "Swiften/Network/DomainNameAddressQuery.h" + +using namespace Swift; + +namespace { + struct AddressQuery : public DomainNameAddressQuery, public boost::enable_shared_from_this<AddressQuery>, public EventOwner { + AddressQuery(const String& host) : hostname(host), thread(NULL), safeToJoin(false) {} + + ~AddressQuery() { + if (safeToJoin) { + thread->join(); + } + else { + // FIXME: UGLYYYYY + } + delete thread; + } + + void run() { + safeToJoin = false; + thread = new boost::thread(boost::bind(&AddressQuery::doRun, shared_from_this())); + } + + void doRun() { + //std::cout << "PlatformDomainNameResolver::doRun()" << std::endl; + boost::asio::ip::tcp::resolver resolver(ioService); + boost::asio::ip::tcp::resolver::query query(hostname.getUTF8String(), "5222"); + try { + //std::cout << "PlatformDomainNameResolver::doRun(): Resolving" << std::endl; + boost::asio::ip::tcp::resolver::iterator endpointIterator = resolver.resolve(query); + //std::cout << "PlatformDomainNameResolver::doRun(): Resolved" << std::endl; + if (endpointIterator == boost::asio::ip::tcp::resolver::iterator()) { + //std::cout << "PlatformDomainNameResolver::doRun(): Error 1" << std::endl; + emitError(); + } + else { + boost::asio::ip::address address = (*endpointIterator).endpoint().address(); + HostAddress result = (address.is_v4() ? HostAddress(&address.to_v4().to_bytes()[0], 4) : HostAddress(&address.to_v6().to_bytes()[0], 16)); + //std::cout << "PlatformDomainNameResolver::doRun(): Success" << std::endl; + MainEventLoop::postEvent( + boost::bind(boost::ref(onResult), result, boost::optional<DomainNameResolveError>()), + shared_from_this()); + } + } + catch (...) { + //std::cout << "PlatformDomainNameResolver::doRun(): Error 2" << std::endl; + emitError(); + } + safeToJoin = true; + } + + void emitError() { + MainEventLoop::postEvent(boost::bind(boost::ref(onResult), HostAddress(), boost::optional<DomainNameResolveError>(DomainNameResolveError())), shared_from_this()); + } + + boost::asio::io_service ioService; + String hostname; + boost::thread* thread; + bool safeToJoin; + }; + +} + +namespace Swift { + +PlatformDomainNameResolver::PlatformDomainNameResolver() { +} + +boost::shared_ptr<DomainNameServiceQuery> PlatformDomainNameResolver::createServiceQuery(const String& name) { + return boost::shared_ptr<DomainNameServiceQuery>(new PlatformDomainNameServiceQuery(getNormalized(name))); +} + +boost::shared_ptr<DomainNameAddressQuery> PlatformDomainNameResolver::createAddressQuery(const String& name) { + return boost::shared_ptr<DomainNameAddressQuery>(new AddressQuery(getNormalized(name))); +} + +} diff --git a/Swiften/Network/PlatformDomainNameResolver.h b/Swiften/Network/PlatformDomainNameResolver.h new file mode 100644 index 0000000..4617b15 --- /dev/null +++ b/Swiften/Network/PlatformDomainNameResolver.h @@ -0,0 +1,15 @@ +#pragma once + +#include "Swiften/Network/DomainNameResolver.h" + +namespace Swift { + class String; + + class PlatformDomainNameResolver : public DomainNameResolver { + public: + PlatformDomainNameResolver(); + + virtual boost::shared_ptr<DomainNameServiceQuery> createServiceQuery(const String& name); + virtual boost::shared_ptr<DomainNameAddressQuery> createAddressQuery(const String& name); + }; +} diff --git a/Swiften/Network/PlatformDomainNameServiceQuery.cpp b/Swiften/Network/PlatformDomainNameServiceQuery.cpp new file mode 100644 index 0000000..bde851b --- /dev/null +++ b/Swiften/Network/PlatformDomainNameServiceQuery.cpp @@ -0,0 +1,170 @@ +#include "Swiften/Network/PlatformDomainNameServiceQuery.h" + +#include "Swiften/Base/Platform.h" +#include <stdlib.h> +#ifdef SWIFTEN_PLATFORM_WINDOWS +#undef UNICODE +#include <windows.h> +#include <windns.h> +#ifndef DNS_TYPE_SRV +#define DNS_TYPE_SRV 33 +#endif +#else +#include <arpa/nameser.h> +#include <arpa/nameser_compat.h> +#include <resolv.h> +#endif +#include <boost/bind.hpp> + +#include "Swiften/Base/ByteArray.h" +#include "Swiften/EventLoop/MainEventLoop.h" +#include "Swiften/Base/foreach.h" + +using namespace Swift; + +namespace Swift { + +PlatformDomainNameServiceQuery::PlatformDomainNameServiceQuery(const String& service) : thread(NULL), service(service), safeToJoin(true) { +} + +PlatformDomainNameServiceQuery::~PlatformDomainNameServiceQuery() { + if (safeToJoin) { + thread->join(); + } + else { + // FIXME: UGLYYYYY + } + delete thread; +} + +void PlatformDomainNameServiceQuery::run() { + safeToJoin = false; + thread = new boost::thread(boost::bind(&PlatformDomainNameServiceQuery::doRun, shared_from_this())); +} + +void PlatformDomainNameServiceQuery::doRun() { + std::vector<DomainNameServiceQuery::Result> records; + +#if defined(SWIFTEN_PLATFORM_WINDOWS) + DNS_RECORD* responses; + // FIXME: This conversion doesn't work if unicode is deffed above + if (DnsQuery(service.getUTF8Data(), DNS_TYPE_SRV, DNS_QUERY_STANDARD, NULL, &responses, NULL) != ERROR_SUCCESS) { + emitError(); + return; + } + + DNS_RECORD* currentEntry = responses; + while (currentEntry) { + if (currentEntry->wType == DNS_TYPE_SRV) { + DomainNameServiceQuery::Result record; + record.priority = currentEntry->Data.SRV.wPriority; + record.weight = currentEntry->Data.SRV.wWeight; + record.port = currentEntry->Data.SRV.wPort; + + // The pNameTarget is actually a PCWSTR, so I would have expected this + // conversion to not work at all, but it does. + // Actually, it doesn't. Fix this and remove explicit cast + // Remove unicode undef above as well + record.hostname = String((const char*) currentEntry->Data.SRV.pNameTarget); + records.push_back(record); + } + currentEntry = currentEntry->pNext; + } + DnsRecordListFree(responses, DnsFreeRecordList); + +#else + // Make sure we reinitialize the domain list every time + res_init(); + + //std::cout << "SRV: Querying " << service << std::endl; + ByteArray response; + response.resize(NS_PACKETSZ); + int responseLength = res_query(const_cast<char*>(service.getUTF8Data()), ns_c_in, ns_t_srv, reinterpret_cast<u_char*>(response.getData()), response.getSize()); + //std::cout << "res_query done " << (responseLength != -1) << std::endl; + if (responseLength == -1) { + emitError(); + return; + } + + // Parse header + HEADER* header = reinterpret_cast<HEADER*>(response.getData()); + unsigned char* messageStart = reinterpret_cast<unsigned char*>(response.getData()); + unsigned char* messageEnd = messageStart + responseLength; + unsigned char* currentEntry = messageStart + NS_HFIXEDSZ; + + // Skip over the queries + int queriesCount = ntohs(header->qdcount); + while (queriesCount > 0) { + int entryLength = dn_skipname(currentEntry, messageEnd); + if (entryLength < 0) { + emitError(); + return; + } + currentEntry += entryLength + NS_QFIXEDSZ; + queriesCount--; + } + + // Process the SRV answers + int answersCount = ntohs(header->ancount); + while (answersCount > 0) { + DomainNameServiceQuery::Result record; + + int entryLength = dn_skipname(currentEntry, messageEnd); + currentEntry += entryLength; + currentEntry += NS_RRFIXEDSZ; + + // Priority + if (currentEntry + 2 >= messageEnd) { + emitError(); + return; + } + record.priority = ns_get16(currentEntry); + currentEntry += 2; + + // Weight + if (currentEntry + 2 >= messageEnd) { + emitError(); + return; + } + record.weight = ns_get16(currentEntry); + currentEntry += 2; + + // Port + if (currentEntry + 2 >= messageEnd) { + emitError(); + return; + } + record.port = ns_get16(currentEntry); + currentEntry += 2; + + // Hostname + if (currentEntry >= messageEnd) { + emitError(); + return; + } + ByteArray entry; + entry.resize(NS_MAXDNAME); + entryLength = dn_expand(messageStart, messageEnd, currentEntry, entry.getData(), entry.getSize()); + if (entryLength < 0) { + emitError(); + return; + } + record.hostname = String(entry.getData()); + records.push_back(record); + currentEntry += entryLength; + answersCount--; + } +#endif + + safeToJoin = true; + std::sort(records.begin(), records.end(), ResultPriorityComparator()); + //std::cout << "Sending out " << records.size() << " SRV results " << std::endl; + MainEventLoop::postEvent(boost::bind(boost::ref(onResult), records)); +} + +void PlatformDomainNameServiceQuery::emitError() { + safeToJoin = true; + MainEventLoop::postEvent(boost::bind(boost::ref(onResult), std::vector<DomainNameServiceQuery::Result>()), shared_from_this()); +} + +} diff --git a/Swiften/Network/PlatformDomainNameServiceQuery.h b/Swiften/Network/PlatformDomainNameServiceQuery.h new file mode 100644 index 0000000..753e2c6 --- /dev/null +++ b/Swiften/Network/PlatformDomainNameServiceQuery.h @@ -0,0 +1,27 @@ +#pragma once + +#include <boost/thread.hpp> +#include <boost/enable_shared_from_this.hpp> + +#include "Swiften/Network/DomainNameServiceQuery.h" +#include "Swiften/EventLoop/EventOwner.h" +#include "Swiften/Base/String.h" + +namespace Swift { + class PlatformDomainNameServiceQuery : public DomainNameServiceQuery, public boost::enable_shared_from_this<PlatformDomainNameServiceQuery>, public EventOwner { + public: + PlatformDomainNameServiceQuery(const String& service); + ~PlatformDomainNameServiceQuery(); + + virtual void run(); + + private: + void doRun(); + void emitError(); + + private: + boost::thread* thread; + String service; + bool safeToJoin; + }; +} diff --git a/Swiften/Network/SConscript b/Swiften/Network/SConscript new file mode 100644 index 0000000..937ab0c --- /dev/null +++ b/Swiften/Network/SConscript @@ -0,0 +1,34 @@ +Import("swiften_env") + +myenv = swiften_env.Clone() +myenv.MergeFlags(myenv["LIBIDN_FLAGS"]) +if myenv["target"] == "native": + myenv.MergeFlags(myenv["CARES_FLAGS"]) + +sourceList = [ + "BoostConnection.cpp", + "BoostConnectionFactory.cpp", + "BoostConnectionServer.cpp", + "MainBoostIOServiceThread.cpp", + "BoostIOServiceThread.cpp", + "ConnectionFactory.cpp", + "ConnectionServer.cpp", + "Connector.cpp", + "TimerFactory.cpp", + "DummyTimerFactory.cpp", + "BoostTimerFactory.cpp", + "DomainNameResolver.cpp", + "DomainNameAddressQuery.cpp", + "DomainNameServiceQuery.cpp", + "PlatformDomainNameResolver.cpp", + "PlatformDomainNameServiceQuery.cpp", + "StaticDomainNameResolver.cpp", + "HostAddress.cpp", + "Timer.cpp", + "BoostTimer.cpp"] +if myenv["target"] == "native": + sourceList.append("CAresDomainNameResolver.cpp") + + +objects = myenv.StaticObject(sourceList) +swiften_env.Append(SWIFTEN_OBJECTS = [objects]) diff --git a/Swiften/Network/StaticDomainNameResolver.cpp b/Swiften/Network/StaticDomainNameResolver.cpp new file mode 100644 index 0000000..a7275d2 --- /dev/null +++ b/Swiften/Network/StaticDomainNameResolver.cpp @@ -0,0 +1,85 @@ +#include "Swiften/Network/StaticDomainNameResolver.h" + +#include <boost/bind.hpp> +#include <boost/lexical_cast.hpp> + +#include "Swiften/Network/DomainNameResolveError.h" +#include "Swiften/Base/String.h" + +using namespace Swift; + +namespace { + struct ServiceQuery : public DomainNameServiceQuery, public EventOwner { + ServiceQuery(const String& service, Swift::StaticDomainNameResolver* resolver) : service(service), resolver(resolver) {} + + virtual void run() { + if (!resolver->getIsResponsive()) { + return; + } + std::vector<DomainNameServiceQuery::Result> results; + for(StaticDomainNameResolver::ServicesCollection::const_iterator i = resolver->getServices().begin(); i != resolver->getServices().end(); ++i) { + if (i->first == service) { + results.push_back(i->second); + } + } + MainEventLoop::postEvent(boost::bind(boost::ref(onResult), results)); + } + + String service; + StaticDomainNameResolver* resolver; + }; + + struct AddressQuery : public DomainNameAddressQuery, public EventOwner { + AddressQuery(const String& host, StaticDomainNameResolver* resolver) : host(host), resolver(resolver) {} + + virtual void run() { + if (!resolver->getIsResponsive()) { + return; + } + StaticDomainNameResolver::AddressesMap::const_iterator i = resolver->getAddresses().find(host); + if (i != resolver->getAddresses().end()) { + MainEventLoop::postEvent( + boost::bind(boost::ref(onResult), i->second, boost::optional<DomainNameResolveError>())); + } + else { + MainEventLoop::postEvent(boost::bind(boost::ref(onResult), HostAddress(), boost::optional<DomainNameResolveError>(DomainNameResolveError()))); + } + + } + + String host; + StaticDomainNameResolver* resolver; + }; +} + +namespace Swift { + +StaticDomainNameResolver::StaticDomainNameResolver() : isResponsive(true) { +} + +void StaticDomainNameResolver::addAddress(const String& domain, const HostAddress& address) { + addresses[domain] = address; +} + +void StaticDomainNameResolver::addService(const String& service, const DomainNameServiceQuery::Result& result) { + services.push_back(std::make_pair(service, result)); +} + +void StaticDomainNameResolver::addXMPPClientService(const String& domain, const HostAddressPort& address) { + static int hostid = 0; + String hostname(std::string("host-") + boost::lexical_cast<std::string>(hostid)); + hostid++; + + addService("_xmpp-client._tcp." + domain, ServiceQuery::Result(hostname, address.getPort(), 0, 0)); + addAddress(hostname, address.getAddress()); +} + +boost::shared_ptr<DomainNameServiceQuery> StaticDomainNameResolver::createServiceQuery(const String& name) { + return boost::shared_ptr<DomainNameServiceQuery>(new ServiceQuery(name, this)); +} + +boost::shared_ptr<DomainNameAddressQuery> StaticDomainNameResolver::createAddressQuery(const String& name) { + return boost::shared_ptr<DomainNameAddressQuery>(new AddressQuery(name, this)); +} + +} diff --git a/Swiften/Network/StaticDomainNameResolver.h b/Swiften/Network/StaticDomainNameResolver.h new file mode 100644 index 0000000..d7e7ba4 --- /dev/null +++ b/Swiften/Network/StaticDomainNameResolver.h @@ -0,0 +1,52 @@ +#pragma once + +#include <vector> +#include <map> + +#include "Swiften/Network/HostAddress.h" +#include "Swiften/Network/HostAddressPort.h" +#include "Swiften/Network/DomainNameResolver.h" +#include "Swiften/Network/DomainNameServiceQuery.h" +#include "Swiften/Network/DomainNameAddressQuery.h" +#include "Swiften/EventLoop/MainEventLoop.h" + +namespace Swift { + class String; + + class StaticDomainNameResolver : public DomainNameResolver { + public: + typedef std::map<String, HostAddress> AddressesMap; + typedef std::vector< std::pair<String, DomainNameServiceQuery::Result> > ServicesCollection; + + public: + StaticDomainNameResolver(); + + void addAddress(const String& domain, const HostAddress& address); + void addService(const String& service, const DomainNameServiceQuery::Result& result); + void addXMPPClientService(const String& domain, const HostAddressPort&); + + const AddressesMap& getAddresses() const { + return addresses; + } + + const ServicesCollection& getServices() const { + return services; + } + + bool getIsResponsive() const { + return isResponsive; + } + + void setIsResponsive(bool b) { + isResponsive = b; + } + + virtual boost::shared_ptr<DomainNameServiceQuery> createServiceQuery(const String& name); + virtual boost::shared_ptr<DomainNameAddressQuery> createAddressQuery(const String& name); + + private: + bool isResponsive; + AddressesMap addresses; + ServicesCollection services; + }; +} diff --git a/Swiften/Network/Timer.cpp b/Swiften/Network/Timer.cpp new file mode 100644 index 0000000..a8d17c3 --- /dev/null +++ b/Swiften/Network/Timer.cpp @@ -0,0 +1,8 @@ +#include "Swiften/Network/Timer.h" + +namespace Swift { + +Timer::~Timer() { +} + +} diff --git a/Swiften/Network/Timer.h b/Swiften/Network/Timer.h new file mode 100644 index 0000000..9b01a0d --- /dev/null +++ b/Swiften/Network/Timer.h @@ -0,0 +1,15 @@ +#pragma once + +#include <boost/signals.hpp> + +namespace Swift { + class Timer { + public: + virtual ~Timer(); + + virtual void start() = 0; + virtual void stop() = 0; + + boost::signal<void ()> onTick; + }; +} diff --git a/Swiften/Network/TimerFactory.cpp b/Swiften/Network/TimerFactory.cpp new file mode 100644 index 0000000..642ac52 --- /dev/null +++ b/Swiften/Network/TimerFactory.cpp @@ -0,0 +1,8 @@ +#include "Swiften/Network/TimerFactory.h" + +namespace Swift { + +TimerFactory::~TimerFactory() { +} + +} diff --git a/Swiften/Network/TimerFactory.h b/Swiften/Network/TimerFactory.h new file mode 100644 index 0000000..f72a8fc --- /dev/null +++ b/Swiften/Network/TimerFactory.h @@ -0,0 +1,14 @@ +#pragma once + +#include <boost/shared_ptr.hpp> + +namespace Swift { + class Timer; + + class TimerFactory { + public: + virtual ~TimerFactory(); + + virtual boost::shared_ptr<Timer> createTimer(int milliseconds) = 0; + }; +} diff --git a/Swiften/Network/UnitTest/ConnectorTest.cpp b/Swiften/Network/UnitTest/ConnectorTest.cpp new file mode 100644 index 0000000..663011c --- /dev/null +++ b/Swiften/Network/UnitTest/ConnectorTest.cpp @@ -0,0 +1,245 @@ +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> + +#include <boost/optional.hpp> +#include <boost/bind.hpp> + +#include "Swiften/Network/Connector.h" +#include "Swiften/Network/Connection.h" +#include "Swiften/Network/ConnectionFactory.h" +#include "Swiften/Network/HostAddressPort.h" +#include "Swiften/Network/StaticDomainNameResolver.h" +#include "Swiften/Network/DummyTimerFactory.h" +#include "Swiften/EventLoop/MainEventLoop.h" +#include "Swiften/EventLoop/DummyEventLoop.h" + +using namespace Swift; + +class ConnectorTest : public CppUnit::TestFixture { + CPPUNIT_TEST_SUITE(ConnectorTest); + CPPUNIT_TEST(testConnect); + CPPUNIT_TEST(testConnect_NoSRVHost); + CPPUNIT_TEST(testConnect_NoHosts); + CPPUNIT_TEST(testConnect_FirstSRVHostFails); + CPPUNIT_TEST(testConnect_AllSRVHostsFailWithoutFallbackHost); + CPPUNIT_TEST(testConnect_AllSRVHostsFailWithFallbackHost); + CPPUNIT_TEST(testConnect_SRVAndFallbackHostsFail); + CPPUNIT_TEST(testConnect_TimeoutDuringResolve); + CPPUNIT_TEST(testConnect_TimeoutDuringConnect); + CPPUNIT_TEST(testConnect_NoTimeout); + CPPUNIT_TEST_SUITE_END(); + + public: + ConnectorTest() : host1(HostAddress("1.1.1.1"), 1234), host2(HostAddress("2.2.2.2"), 2345), host3(HostAddress("3.3.3.3"), 5222) { + } + + void setUp() { + eventLoop = new DummyEventLoop(); + resolver = new StaticDomainNameResolver(); + connectionFactory = new MockConnectionFactory(); + timerFactory = new DummyTimerFactory(); + } + + void tearDown() { + delete timerFactory; + delete connectionFactory; + delete resolver; + delete eventLoop; + } + + void testConnect() { + std::auto_ptr<Connector> testling(createConnector()); + resolver->addXMPPClientService("foo.com", host1); + resolver->addXMPPClientService("foo.com", host2); + resolver->addAddress("foo.com", host3.getAddress()); + + testling->start(); + eventLoop->processEvents(); + + CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(connections.size())); + CPPUNIT_ASSERT(connections[0]); + CPPUNIT_ASSERT(host1 == *(connections[0]->hostAddressPort)); + } + + void testConnect_NoSRVHost() { + std::auto_ptr<Connector> testling(createConnector()); + resolver->addAddress("foo.com", host3.getAddress()); + + testling->start(); + eventLoop->processEvents(); + + CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(connections.size())); + CPPUNIT_ASSERT(connections[0]); + CPPUNIT_ASSERT(host3 == *(connections[0]->hostAddressPort)); + } + + void testConnect_NoHosts() { + std::auto_ptr<Connector> testling(createConnector()); + + testling->start(); + eventLoop->processEvents(); + + CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(connections.size())); + CPPUNIT_ASSERT(!connections[0]); + } + + void testConnect_FirstSRVHostFails() { + std::auto_ptr<Connector> testling(createConnector()); + resolver->addXMPPClientService("foo.com", host1); + resolver->addXMPPClientService("foo.com", host2); + connectionFactory->failingPorts.push_back(host1); + + testling->start(); + eventLoop->processEvents(); + + CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(connections.size())); + CPPUNIT_ASSERT(host2 == *(connections[0]->hostAddressPort)); + } + + void testConnect_AllSRVHostsFailWithoutFallbackHost() { + std::auto_ptr<Connector> testling(createConnector()); + resolver->addXMPPClientService("foo.com", host1); + resolver->addXMPPClientService("foo.com", host2); + connectionFactory->failingPorts.push_back(host1); + connectionFactory->failingPorts.push_back(host2); + + testling->start(); + eventLoop->processEvents(); + + CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(connections.size())); + CPPUNIT_ASSERT(!connections[0]); + } + + void testConnect_AllSRVHostsFailWithFallbackHost() { + std::auto_ptr<Connector> testling(createConnector()); + resolver->addXMPPClientService("foo.com", host1); + resolver->addXMPPClientService("foo.com", host2); + resolver->addAddress("foo.com", host3.getAddress()); + connectionFactory->failingPorts.push_back(host1); + connectionFactory->failingPorts.push_back(host2); + + testling->start(); + eventLoop->processEvents(); + + CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(connections.size())); + CPPUNIT_ASSERT(connections[0]); + CPPUNIT_ASSERT(host3 == *(connections[0]->hostAddressPort)); + } + + void testConnect_SRVAndFallbackHostsFail() { + std::auto_ptr<Connector> testling(createConnector()); + resolver->addXMPPClientService("foo.com", host1); + resolver->addAddress("foo.com", host3.getAddress()); + connectionFactory->failingPorts.push_back(host1); + connectionFactory->failingPorts.push_back(host3); + + testling->start(); + eventLoop->processEvents(); + + CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(connections.size())); + CPPUNIT_ASSERT(!connections[0]); + } + + void testConnect_TimeoutDuringResolve() { + std::auto_ptr<Connector> testling(createConnector()); + testling->setTimeoutMilliseconds(10); + resolver->setIsResponsive(false); + + testling->start(); + eventLoop->processEvents(); + timerFactory->setTime(10); + eventLoop->processEvents(); + + CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(connections.size())); + CPPUNIT_ASSERT(!connections[0]); + } + + void testConnect_TimeoutDuringConnect() { + std::auto_ptr<Connector> testling(createConnector()); + testling->setTimeoutMilliseconds(10); + resolver->addXMPPClientService("foo.com", host1); + connectionFactory->isResponsive = false; + + testling->start(); + eventLoop->processEvents(); + timerFactory->setTime(10); + eventLoop->processEvents(); + + CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(connections.size())); + CPPUNIT_ASSERT(!connections[0]); + } + + void testConnect_NoTimeout() { + std::auto_ptr<Connector> testling(createConnector()); + testling->setTimeoutMilliseconds(10); + resolver->addXMPPClientService("foo.com", host1); + + testling->start(); + eventLoop->processEvents(); + timerFactory->setTime(10); + eventLoop->processEvents(); + + CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(connections.size())); + CPPUNIT_ASSERT(connections[0]); + } + + + private: + Connector* createConnector() { + Connector* connector = new Connector("foo.com", resolver, connectionFactory, timerFactory); + connector->onConnectFinished.connect(boost::bind(&ConnectorTest::handleConnectorFinished, this, _1)); + return connector; + } + + void handleConnectorFinished(boost::shared_ptr<Connection> connection) { + boost::shared_ptr<MockConnection> c(boost::dynamic_pointer_cast<MockConnection>(connection)); + if (connection) { + assert(c); + } + connections.push_back(c); + } + + struct MockConnection : public Connection { + public: + MockConnection(const std::vector<HostAddressPort>& failingPorts, bool isResponsive) : failingPorts(failingPorts), isResponsive(isResponsive) {} + + void listen() { assert(false); } + void connect(const HostAddressPort& address) { + hostAddressPort = address; + if (isResponsive) { + MainEventLoop::postEvent(boost::bind(boost::ref(onConnectFinished), std::find(failingPorts.begin(), failingPorts.end(), address) != failingPorts.end())); + } + } + + void disconnect() { assert(false); } + void write(const ByteArray&) { assert(false); } + + boost::optional<HostAddressPort> hostAddressPort; + std::vector<HostAddressPort> failingPorts; + bool isResponsive; + }; + + struct MockConnectionFactory : public ConnectionFactory { + MockConnectionFactory() : isResponsive(true) { + } + + boost::shared_ptr<Connection> createConnection() { + return boost::shared_ptr<Connection>(new MockConnection(failingPorts, isResponsive)); + } + + bool isResponsive; + std::vector<HostAddressPort> failingPorts; + }; + + private: + HostAddressPort host1; + HostAddressPort host2; + HostAddressPort host3; + DummyEventLoop* eventLoop; + StaticDomainNameResolver* resolver; + MockConnectionFactory* connectionFactory; + DummyTimerFactory* timerFactory; + std::vector< boost::shared_ptr<MockConnection> > connections; +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(ConnectorTest); diff --git a/Swiften/Network/UnitTest/HostAddressTest.cpp b/Swiften/Network/UnitTest/HostAddressTest.cpp new file mode 100644 index 0000000..50e9198 --- /dev/null +++ b/Swiften/Network/UnitTest/HostAddressTest.cpp @@ -0,0 +1,38 @@ +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> + +#include "Swiften/Network/HostAddress.h" +#include "Swiften/Base/String.h" + +using namespace Swift; + +class HostAddressTest : public CppUnit::TestFixture { + CPPUNIT_TEST_SUITE(HostAddressTest); + CPPUNIT_TEST(testConstructor); + CPPUNIT_TEST(testToString); + CPPUNIT_TEST(testToString_IPv6); + CPPUNIT_TEST_SUITE_END(); + + public: + void testConstructor() { + HostAddress testling("192.168.1.254"); + + CPPUNIT_ASSERT_EQUAL(std::string("192.168.1.254"), testling.toString()); + } + + void testToString() { + unsigned char address[4] = {10, 0, 1, 253}; + HostAddress testling(address, 4); + + CPPUNIT_ASSERT_EQUAL(std::string("10.0.1.253"), testling.toString()); + } + + void testToString_IPv6() { + unsigned char address[16] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 17}; + HostAddress testling(address, 16); + + CPPUNIT_ASSERT_EQUAL(std::string("0102:0304:0506:0708:090a:0b0c:0d0e:0f11"), testling.toString()); + } +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(HostAddressTest); diff --git a/Swiften/Notifier/GrowlNotifier.cpp b/Swiften/Notifier/GrowlNotifier.cpp new file mode 100644 index 0000000..13d3e6d --- /dev/null +++ b/Swiften/Notifier/GrowlNotifier.cpp @@ -0,0 +1,91 @@ +// FIXME: Is it safe to pass boost::function<void()> by raw values? +// FIXME: Should we release the strings created in the constructor? + +#include <cassert> + +#include "Swiften/Base/ByteArray.h" +#include "Swiften/Notifier/GrowlNotifier.h" + +#pragma GCC diagnostic ignored "-Wold-style-cast" + +namespace { + struct Context { + Context() {} + Context(const boost::function<void()>& callback) : callback(callback) {} + + boost::function<void()> callback; + }; + + void notificationClicked(CFPropertyListRef growlContext) { + Context context; + + CFDataRef growlContextData = (CFDataRef) CFArrayGetValueAtIndex((CFArrayRef) growlContext, 0); + assert(CFDataGetLength(growlContextData) == sizeof(Context)); + CFDataGetBytes(growlContextData, CFRangeMake(0, CFDataGetLength(growlContextData)), (UInt8*) &context); + + context.callback(); + } + + void notificationTimedout(CFPropertyListRef) { + } +} + +namespace Swift { + +GrowlNotifier::GrowlNotifier(const String& name) { + // All notifications + CFMutableArrayRef allNotifications = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks); + CFArrayAppendValue(allNotifications, SWIFTEN_STRING_TO_CFSTRING(typeToString(ContactAvailable))); + CFArrayAppendValue(allNotifications, SWIFTEN_STRING_TO_CFSTRING(typeToString(ContactUnavailable))); + CFArrayAppendValue(allNotifications, SWIFTEN_STRING_TO_CFSTRING(typeToString(ContactStatusChange))); + CFArrayAppendValue(allNotifications, SWIFTEN_STRING_TO_CFSTRING(typeToString(IncomingMessage))); + + // Default Notifications + CFMutableArrayRef defaultNotifications = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks); + CFArrayAppendValue(defaultNotifications, SWIFTEN_STRING_TO_CFSTRING(typeToString(ContactAvailable))); + CFArrayAppendValue(defaultNotifications, SWIFTEN_STRING_TO_CFSTRING(typeToString(IncomingMessage))); + + // Initialize delegate + InitGrowlDelegate(&delegate_); + delegate_.applicationName = SWIFTEN_STRING_TO_CFSTRING(name); + CFTypeRef keys[] = { GROWL_NOTIFICATIONS_ALL, GROWL_NOTIFICATIONS_DEFAULT }; + CFTypeRef values[] = { allNotifications, defaultNotifications }; + delegate_.registrationDictionary = CFDictionaryCreate(kCFAllocatorDefault, keys, values, 2, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); + delegate_.growlNotificationWasClicked = ¬ificationClicked; + delegate_.growlNotificationTimedOut = ¬ificationTimedout; + Growl_SetDelegate(&delegate_); +} + +void GrowlNotifier::showMessage(Type type, const String& subject, const String& description, const ByteArray& picture, boost::function<void()> callback) { + CFStringRef cfSubject = SWIFTEN_STRING_TO_CFSTRING(subject); + CFStringRef cfDescription = SWIFTEN_STRING_TO_CFSTRING(description); + CFStringRef cfName = SWIFTEN_STRING_TO_CFSTRING(typeToString(type)); + CFDataRef cfIcon = CFDataCreate( NULL, (UInt8*) picture.getData(), picture.getSize()); + + Context context(callback); + CFDataRef cfContextData[1]; + cfContextData[0] = CFDataCreate(kCFAllocatorDefault, (const UInt8*) &context, sizeof(Context)); + CFArrayRef cfContext = CFArrayCreate( kCFAllocatorDefault, (const void **) cfContextData, 1, &kCFTypeArrayCallBacks ); + CFRelease(cfContextData[0]); + + Growl_NotifyWithTitleDescriptionNameIconPriorityStickyClickContext(cfSubject, cfDescription, cfName, cfIcon, 0, false, cfContext); + + CFRelease(cfContext); + CFRelease(cfIcon); + CFRelease(cfName); + CFRelease(cfDescription); + CFRelease(cfSubject); +} + +String GrowlNotifier::typeToString(Type type) { + switch (type) { + case ContactAvailable: return "Contact Becomes Available"; + case ContactUnavailable: return "Contact Becomes Unavailable"; + case ContactStatusChange: return "Contact Changes Status"; + case IncomingMessage: return "Incoming Message"; + } + assert(false); + return ""; +} + +} diff --git a/Swiften/Notifier/GrowlNotifier.h b/Swiften/Notifier/GrowlNotifier.h new file mode 100644 index 0000000..3bf53be --- /dev/null +++ b/Swiften/Notifier/GrowlNotifier.h @@ -0,0 +1,28 @@ +#pragma once + +#include <CoreFoundation/CoreFoundation.h> +#include <Growl/Growl.h> + +#include "Swiften/Notifier/Notifier.h" + +namespace Swift { + /** + * Preconditions for using growlnotifier: + * - Must be part a bundle. + * - The Carbon/Cocoa application loop must be running (e.g. through QApplication) + * such that notifications are coming through. + * TODO: Find out what the easiest way is to do this without a QApplication. + */ + class GrowlNotifier : public Notifier { + public: + GrowlNotifier(const String& name); + + virtual void showMessage(Type type, const String& subject, const String& description, const ByteArray& picture, boost::function<void()> callback); + + private: + String typeToString(Type type); + + private: + Growl_Delegate delegate_; + }; +} diff --git a/Swiften/Notifier/Notifier.cpp b/Swiften/Notifier/Notifier.cpp new file mode 100644 index 0000000..88cb0ee --- /dev/null +++ b/Swiften/Notifier/Notifier.cpp @@ -0,0 +1,8 @@ +#include "Swiften/Notifier/Notifier.h" + +namespace Swift { + +Notifier::~Notifier() { +} + +} diff --git a/Swiften/Notifier/Notifier.h b/Swiften/Notifier/Notifier.h new file mode 100644 index 0000000..2ab3ba8 --- /dev/null +++ b/Swiften/Notifier/Notifier.h @@ -0,0 +1,24 @@ +#pragma once + +#include <boost/function.hpp> + +#include "Swiften/Base/String.h" + +namespace Swift { + class Notifier { + public: + virtual ~Notifier(); + + enum Type { ContactAvailable, ContactUnavailable, ContactStatusChange, IncomingMessage }; + + /** + * Picture is a PNG image. + */ + virtual void showMessage( + Type type, + const String& subject, + const String& description, + const ByteArray& picture, + boost::function<void()> callback) = 0; + }; +} diff --git a/Swiften/Parser/AttributeMap.h b/Swiften/Parser/AttributeMap.h new file mode 100644 index 0000000..82c839a --- /dev/null +++ b/Swiften/Parser/AttributeMap.h @@ -0,0 +1,35 @@ +#ifndef ATTRIBUTEMAP_H +#define ATTRIBUTEMAP_H + +#include <map> + +#include "Swiften/Base/String.h" + +namespace Swift { + class AttributeMap : public std::map<String,String> { + public: + AttributeMap() {} + + String getAttribute(const String& attribute) const { + AttributeMap::const_iterator i = find(attribute); + if (i == end()) { + return ""; + } + else { + return i->second; + } + } + + bool getBoolAttribute(const String& attribute, bool defaultValue = false) const { + AttributeMap::const_iterator i = find(attribute); + if (i == end()) { + return defaultValue; + } + else { + return i->second == "true" || i->second == "1"; + } + } + }; +} + +#endif diff --git a/Swiften/Parser/AuthChallengeParser.cpp b/Swiften/Parser/AuthChallengeParser.cpp new file mode 100644 index 0000000..c83cf7d --- /dev/null +++ b/Swiften/Parser/AuthChallengeParser.cpp @@ -0,0 +1,24 @@ +#include "Swiften/Parser/AuthChallengeParser.h" +#include "Swiften/StringCodecs/Base64.h" + +namespace Swift { + +AuthChallengeParser::AuthChallengeParser() : GenericElementParser<AuthChallenge>(), depth(0) { +} + +void AuthChallengeParser::handleStartElement(const String&, const String&, const AttributeMap&) { + ++depth; +} + +void AuthChallengeParser::handleEndElement(const String&, const String&) { + --depth; + if (depth == 0) { + getElementGeneric()->setValue(Base64::decode(text)); + } +} + +void AuthChallengeParser::handleCharacterData(const String& text) { + this->text += text; +} + +} diff --git a/Swiften/Parser/AuthChallengeParser.h b/Swiften/Parser/AuthChallengeParser.h new file mode 100644 index 0000000..be44b96 --- /dev/null +++ b/Swiften/Parser/AuthChallengeParser.h @@ -0,0 +1,20 @@ +#pragma once + +#include "Swiften/Parser/GenericElementParser.h" +#include "Swiften/Elements/AuthChallenge.h" +#include "Swiften/Base/String.h" + +namespace Swift { + class AuthChallengeParser : public GenericElementParser<AuthChallenge> { + public: + AuthChallengeParser(); + + virtual void handleStartElement(const String&, const String& ns, const AttributeMap&); + virtual void handleEndElement(const String&, const String& ns); + virtual void handleCharacterData(const String&); + + private: + int depth; + String text; + }; +} diff --git a/Swiften/Parser/AuthFailureParser.h b/Swiften/Parser/AuthFailureParser.h new file mode 100644 index 0000000..3a950ef --- /dev/null +++ b/Swiften/Parser/AuthFailureParser.h @@ -0,0 +1,14 @@ +#ifndef SWIFTEN_AuthFailureParser_H +#define SWIFTEN_AuthFailureParser_H + +#include "Swiften/Parser/GenericElementParser.h" +#include "Swiften/Elements/AuthFailure.h" + +namespace Swift { + class AuthFailureParser : public GenericElementParser<AuthFailure> { + public: + AuthFailureParser() : GenericElementParser<AuthFailure>() {} + }; +} + +#endif diff --git a/Swiften/Parser/AuthRequestParser.cpp b/Swiften/Parser/AuthRequestParser.cpp new file mode 100644 index 0000000..5338b88 --- /dev/null +++ b/Swiften/Parser/AuthRequestParser.cpp @@ -0,0 +1,27 @@ +#include "Swiften/Parser/AuthRequestParser.h" +#include "Swiften/StringCodecs/Base64.h" + +namespace Swift { + +AuthRequestParser::AuthRequestParser() : GenericElementParser<AuthRequest>(), depth_(0) { +} + +void AuthRequestParser::handleStartElement(const String&, const String&, const AttributeMap& attribute) { + if (depth_ == 0) { + getElementGeneric()->setMechanism(attribute.getAttribute("mechanism")); + } + ++depth_; +} + +void AuthRequestParser::handleEndElement(const String&, const String&) { + --depth_; + if (depth_ == 0) { + getElementGeneric()->setMessage(Base64::decode(text_)); + } +} + +void AuthRequestParser::handleCharacterData(const String& text) { + text_ += text; +} + +} diff --git a/Swiften/Parser/AuthRequestParser.h b/Swiften/Parser/AuthRequestParser.h new file mode 100644 index 0000000..8916922 --- /dev/null +++ b/Swiften/Parser/AuthRequestParser.h @@ -0,0 +1,23 @@ +#ifndef SWIFTEN_AuthRequestParser_H +#define SWIFTEN_AuthRequestParser_H + +#include "Swiften/Parser/GenericElementParser.h" +#include "Swiften/Elements/AuthRequest.h" +#include "Swiften/Base/String.h" + +namespace Swift { + class AuthRequestParser : public GenericElementParser<AuthRequest> { + public: + AuthRequestParser(); + + virtual void handleStartElement(const String&, const String& ns, const AttributeMap&); + virtual void handleEndElement(const String&, const String& ns); + virtual void handleCharacterData(const String&); + + private: + String text_; + int depth_; + }; +} + +#endif diff --git a/Swiften/Parser/AuthResponseParser.cpp b/Swiften/Parser/AuthResponseParser.cpp new file mode 100644 index 0000000..b5976a5 --- /dev/null +++ b/Swiften/Parser/AuthResponseParser.cpp @@ -0,0 +1,24 @@ +#include "Swiften/Parser/AuthResponseParser.h" +#include "Swiften/StringCodecs/Base64.h" + +namespace Swift { + +AuthResponseParser::AuthResponseParser() : GenericElementParser<AuthResponse>(), depth(0) { +} + +void AuthResponseParser::handleStartElement(const String&, const String&, const AttributeMap&) { + ++depth; +} + +void AuthResponseParser::handleEndElement(const String&, const String&) { + --depth; + if (depth == 0) { + getElementGeneric()->setValue(Base64::decode(text)); + } +} + +void AuthResponseParser::handleCharacterData(const String& text) { + this->text += text; +} + +} diff --git a/Swiften/Parser/AuthResponseParser.h b/Swiften/Parser/AuthResponseParser.h new file mode 100644 index 0000000..f2b3a9e --- /dev/null +++ b/Swiften/Parser/AuthResponseParser.h @@ -0,0 +1,20 @@ +#pragma once + +#include "Swiften/Parser/GenericElementParser.h" +#include "Swiften/Elements/AuthResponse.h" +#include "Swiften/Base/String.h" + +namespace Swift { + class AuthResponseParser : public GenericElementParser<AuthResponse> { + public: + AuthResponseParser(); + + virtual void handleStartElement(const String&, const String& ns, const AttributeMap&); + virtual void handleEndElement(const String&, const String& ns); + virtual void handleCharacterData(const String&); + + private: + int depth; + String text; + }; +} diff --git a/Swiften/Parser/AuthSuccessParser.cpp b/Swiften/Parser/AuthSuccessParser.cpp new file mode 100644 index 0000000..2dc2aa2 --- /dev/null +++ b/Swiften/Parser/AuthSuccessParser.cpp @@ -0,0 +1,24 @@ +#include "Swiften/Parser/AuthSuccessParser.h" +#include "Swiften/StringCodecs/Base64.h" + +namespace Swift { + +AuthSuccessParser::AuthSuccessParser() : GenericElementParser<AuthSuccess>(), depth(0) { +} + +void AuthSuccessParser::handleStartElement(const String&, const String&, const AttributeMap&) { + ++depth; +} + +void AuthSuccessParser::handleEndElement(const String&, const String&) { + --depth; + if (depth == 0) { + getElementGeneric()->setValue(Base64::decode(text)); + } +} + +void AuthSuccessParser::handleCharacterData(const String& text) { + this->text += text; +} + +} diff --git a/Swiften/Parser/AuthSuccessParser.h b/Swiften/Parser/AuthSuccessParser.h new file mode 100644 index 0000000..5d987c5 --- /dev/null +++ b/Swiften/Parser/AuthSuccessParser.h @@ -0,0 +1,20 @@ +#pragma once + +#include "Swiften/Parser/GenericElementParser.h" +#include "Swiften/Elements/AuthSuccess.h" +#include "Swiften/Base/String.h" + +namespace Swift { + class AuthSuccessParser : public GenericElementParser<AuthSuccess> { + public: + AuthSuccessParser(); + + virtual void handleStartElement(const String&, const String& ns, const AttributeMap&); + virtual void handleEndElement(const String&, const String& ns); + virtual void handleCharacterData(const String&); + + private: + int depth; + String text; + }; +} diff --git a/Swiften/Parser/CompressFailureParser.h b/Swiften/Parser/CompressFailureParser.h new file mode 100644 index 0000000..d53e0ef --- /dev/null +++ b/Swiften/Parser/CompressFailureParser.h @@ -0,0 +1,14 @@ +#ifndef SWIFTEN_CompressFailureParser_H +#define SWIFTEN_CompressFailureParser_H + +#include "Swiften/Parser/GenericElementParser.h" +#include "Swiften/Elements/CompressFailure.h" + +namespace Swift { + class CompressFailureParser : public GenericElementParser<CompressFailure> { + public: + CompressFailureParser() : GenericElementParser<CompressFailure>() {} + }; +} + +#endif diff --git a/Swiften/Parser/CompressParser.cpp b/Swiften/Parser/CompressParser.cpp new file mode 100644 index 0000000..7ca752d --- /dev/null +++ b/Swiften/Parser/CompressParser.cpp @@ -0,0 +1,28 @@ +#include "Swiften/Parser/CompressParser.h" + +namespace Swift { + +CompressParser::CompressParser() : GenericElementParser<CompressRequest>(), currentDepth_(0), inMethod_(false) { +} + +void CompressParser::handleStartElement(const String& element, const String&, const AttributeMap&) { + if (currentDepth_ == 1 && element == "method") { + inMethod_ = true; + currentText_ = ""; + } + ++currentDepth_; +} + +void CompressParser::handleEndElement(const String&, const String&) { + --currentDepth_; + if (currentDepth_ == 1 && inMethod_) { + getElementGeneric()->setMethod(currentText_); + inMethod_ = false; + } +} + +void CompressParser::handleCharacterData(const String& data) { + currentText_ += data; +} + +} diff --git a/Swiften/Parser/CompressParser.h b/Swiften/Parser/CompressParser.h new file mode 100644 index 0000000..8931778 --- /dev/null +++ b/Swiften/Parser/CompressParser.h @@ -0,0 +1,25 @@ +#ifndef SWIFTEN_CompressParser_H +#define SWIFTEN_CompressParser_H + +#include "Swiften/Base/String.h" +#include "Swiften/Parser/GenericElementParser.h" +#include "Swiften/Elements/CompressRequest.h" + +namespace Swift { + class CompressParser : public GenericElementParser<CompressRequest> { + public: + CompressParser(); + + private: + void handleStartElement(const String& element, const String& ns, const AttributeMap& attributes); + void handleEndElement(const String& element, const String& ns); + void handleCharacterData(const String& data); + + private: + int currentDepth_; + String currentText_; + bool inMethod_; + }; +} + +#endif diff --git a/Swiften/Parser/CompressedParser.h b/Swiften/Parser/CompressedParser.h new file mode 100644 index 0000000..365f619 --- /dev/null +++ b/Swiften/Parser/CompressedParser.h @@ -0,0 +1,14 @@ +#ifndef SWIFTEN_COMPRESSEDPARSER_H +#define SWIFTEN_COMPRESSEDPARSER_H + +#include "Swiften/Parser/GenericElementParser.h" +#include "Swiften/Elements/Compressed.h" + +namespace Swift { + class CompressedParser : public GenericElementParser<Compressed> { + public: + CompressedParser() : GenericElementParser<Compressed>() {} + }; +} + +#endif diff --git a/Swiften/Parser/ElementParser.cpp b/Swiften/Parser/ElementParser.cpp new file mode 100644 index 0000000..1c04d92 --- /dev/null +++ b/Swiften/Parser/ElementParser.cpp @@ -0,0 +1,8 @@ +#include "Swiften/Parser/ElementParser.h" + +namespace Swift { + +ElementParser::~ElementParser() { +} + +} diff --git a/Swiften/Parser/ElementParser.h b/Swiften/Parser/ElementParser.h new file mode 100644 index 0000000..3848f0c --- /dev/null +++ b/Swiften/Parser/ElementParser.h @@ -0,0 +1,23 @@ +#ifndef SWIFTEN_ElementParser_H +#define SWIFTEN_ElementParser_H + +#include <boost/shared_ptr.hpp> + +#include "Swiften/Base/String.h" +#include "Swiften/Elements/Element.h" +#include "Swiften/Parser/AttributeMap.h" + +namespace Swift { + class ElementParser { + public: + virtual ~ElementParser(); + + virtual void handleStartElement(const String& element, const String& ns, const AttributeMap& attributes) = 0; + virtual void handleEndElement(const String& element, const String& ns) = 0; + virtual void handleCharacterData(const String& data) = 0; + + virtual boost::shared_ptr<Element> getElement() const = 0; + }; +} + +#endif diff --git a/Swiften/Parser/ExpatParser.cpp b/Swiften/Parser/ExpatParser.cpp new file mode 100644 index 0000000..6f7ff86 --- /dev/null +++ b/Swiften/Parser/ExpatParser.cpp @@ -0,0 +1,70 @@ +#include "Swiften/Parser/ExpatParser.h" + +#include <iostream> + +#include "Swiften/Base/String.h" +#include "Swiften/Parser/XMLParserClient.h" + +namespace Swift { + +static const char NAMESPACE_SEPARATOR = '\x01'; + +static void handleStartElement(void* client, const XML_Char* name, const XML_Char** attributes) { + std::pair<String,String> nsTagPair = String(name).getSplittedAtFirst(NAMESPACE_SEPARATOR); + if (nsTagPair.second == "") { + nsTagPair.second = nsTagPair.first; + nsTagPair.first = ""; + } + AttributeMap attributeValues; + const XML_Char** currentAttribute = attributes; + while (*currentAttribute) { + std::pair<String,String> nsAttributePair = String(*currentAttribute).getSplittedAtFirst(NAMESPACE_SEPARATOR); + if (nsAttributePair.second == "") { + nsAttributePair.second = nsAttributePair.first; + nsAttributePair.first = ""; + } + attributeValues[nsAttributePair.second] = String(*(currentAttribute+1)); + currentAttribute += 2; + } + + static_cast<XMLParserClient*>(client)->handleStartElement(nsTagPair.second, nsTagPair.first, attributeValues); +} + +static void handleEndElement(void* client, const XML_Char* name) { + std::pair<String,String> nsTagPair = String(name).getSplittedAtFirst(NAMESPACE_SEPARATOR); + if (nsTagPair.second == "") { + nsTagPair.second = nsTagPair.first; + nsTagPair.first = ""; + } + static_cast<XMLParserClient*>(client)->handleEndElement(nsTagPair.second, nsTagPair.first); +} + +static void handleCharacterData(void* client, const XML_Char* data, int len) { + static_cast<XMLParserClient*>(client)->handleCharacterData(String(data, len)); +} + +static void handleXMLDeclaration(void*, const XML_Char*, const XML_Char*, int) { +} + + +ExpatParser::ExpatParser(XMLParserClient* client) : XMLParser(client) { + parser_ = XML_ParserCreateNS("UTF-8", NAMESPACE_SEPARATOR); + XML_SetUserData(parser_, client); + XML_SetElementHandler(parser_, handleStartElement, handleEndElement); + XML_SetCharacterDataHandler(parser_, handleCharacterData); + XML_SetXmlDeclHandler(parser_, handleXMLDeclaration); +} + +ExpatParser::~ExpatParser() { + XML_ParserFree(parser_); +} + +bool ExpatParser::parse(const String& data) { + bool success = XML_Parse(parser_, data.getUTF8Data(), data.getUTF8Size(), false) == XML_STATUS_OK; + /*if (!success) { + std::cout << "ERROR: " << XML_ErrorString(XML_GetErrorCode(parser_)) << " while parsing " << data << std::endl; + }*/ + return success; +} + +} diff --git a/Swiften/Parser/ExpatParser.h b/Swiften/Parser/ExpatParser.h new file mode 100644 index 0000000..2b5e646 --- /dev/null +++ b/Swiften/Parser/ExpatParser.h @@ -0,0 +1,22 @@ +#ifndef SWIFTEN_ExpatParser_H +#define SWIFTEN_ExpatParser_H + +#include <expat.h> +#include <boost/noncopyable.hpp> + +#include "Swiften/Parser/XMLParser.h" + +namespace Swift { + class ExpatParser : public XMLParser, public boost::noncopyable { + public: + ExpatParser(XMLParserClient* client); + ~ExpatParser(); + + bool parse(const String& data); + + private: + XML_Parser parser_; + }; +} + +#endif diff --git a/Swiften/Parser/GenericElementParser.h b/Swiften/Parser/GenericElementParser.h new file mode 100644 index 0000000..e1b9cf7 --- /dev/null +++ b/Swiften/Parser/GenericElementParser.h @@ -0,0 +1,43 @@ +#ifndef SWIFTEN_GenericElementParser_H +#define SWIFTEN_GenericElementParser_H + +#include <boost/shared_ptr.hpp> + +#include "Swiften/Parser/ElementParser.h" + +namespace Swift { + class String; + class PayloadParserFactoryCollection; + + template<typename ElementType> + class GenericElementParser : public ElementParser { + public: + GenericElementParser() { + stanza_ = boost::shared_ptr<ElementType>(new ElementType()); + } + + virtual boost::shared_ptr<Element> getElement() const { + return stanza_; + } + + protected: + virtual boost::shared_ptr<ElementType> getElementGeneric() const { + return stanza_; + } + + private: + virtual void handleStartElement(const String&, const String&, const AttributeMap&) { + } + + virtual void handleEndElement(const String&, const String&) { + } + + virtual void handleCharacterData(const String&) { + } + + private: + boost::shared_ptr<ElementType> stanza_; + }; +} + +#endif diff --git a/Swiften/Parser/GenericPayloadParser.h b/Swiften/Parser/GenericPayloadParser.h new file mode 100644 index 0000000..a07b795 --- /dev/null +++ b/Swiften/Parser/GenericPayloadParser.h @@ -0,0 +1,32 @@ +#ifndef GENERICPAYLOADPARSER_H +#define GENERICPAYLOADPARSER_H + +#include <boost/shared_ptr.hpp> + +#include "Swiften/Parser/PayloadParser.h" + +namespace Swift { + class String; + + template<typename PAYLOAD_TYPE> + class GenericPayloadParser : public PayloadParser { + public: + GenericPayloadParser() : PayloadParser() { + payload_ = boost::shared_ptr<PAYLOAD_TYPE>(new PAYLOAD_TYPE()); + } + + virtual boost::shared_ptr<Payload> getPayload() const { + return payload_; + } + + protected: + virtual boost::shared_ptr<PAYLOAD_TYPE> getPayloadInternal() const { + return payload_; + } + + private: + boost::shared_ptr<PAYLOAD_TYPE> payload_; + }; +} + +#endif diff --git a/Swiften/Parser/GenericPayloadParserFactory.h b/Swiften/Parser/GenericPayloadParserFactory.h new file mode 100644 index 0000000..d537b46 --- /dev/null +++ b/Swiften/Parser/GenericPayloadParserFactory.h @@ -0,0 +1,28 @@ +#ifndef SWIFTEN_GENERICPAYLOADPARSERFACTORY_H +#define SWIFTEN_GENERICPAYLOADPARSERFACTORY_H + +#include "Swiften/Parser/PayloadParserFactory.h" +#include "Swiften/Base/String.h" + +namespace Swift { + + template<typename PARSER_TYPE> + class GenericPayloadParserFactory : public PayloadParserFactory { + public: + GenericPayloadParserFactory(const String& tag, const String& xmlns = "") : tag_(tag), xmlns_(xmlns) {} + + virtual bool canParse(const String& element, const String& ns, const AttributeMap&) const { + return element == tag_ && (xmlns_.isEmpty() ? true : xmlns_ == ns); + } + + virtual PayloadParser* createPayloadParser() { + return new PARSER_TYPE(); + } + + private: + String tag_; + String xmlns_; + }; +} + +#endif diff --git a/Swiften/Parser/GenericStanzaParser.h b/Swiften/Parser/GenericStanzaParser.h new file mode 100644 index 0000000..ba1807a --- /dev/null +++ b/Swiften/Parser/GenericStanzaParser.h @@ -0,0 +1,33 @@ +#ifndef SWIFTEN_GenericStanzaParser_H +#define SWIFTEN_GenericStanzaParser_H + +#include <boost/shared_ptr.hpp> + +#include "Swiften/Parser/StanzaParser.h" + +namespace Swift { + class String; + class PayloadParserFactoryCollection; + + template<typename STANZA_TYPE> + class GenericStanzaParser : public StanzaParser { + public: + GenericStanzaParser(PayloadParserFactoryCollection* collection) : + StanzaParser(collection) { + stanza_ = boost::shared_ptr<STANZA_TYPE>(new STANZA_TYPE()); + } + + virtual boost::shared_ptr<Element> getElement() const { + return stanza_; + } + + virtual boost::shared_ptr<STANZA_TYPE> getStanzaGeneric() const { + return stanza_; + } + + private: + boost::shared_ptr<STANZA_TYPE> stanza_; + }; +} + +#endif diff --git a/Swiften/Parser/IQParser.cpp b/Swiften/Parser/IQParser.cpp new file mode 100644 index 0000000..2b4b364 --- /dev/null +++ b/Swiften/Parser/IQParser.cpp @@ -0,0 +1,33 @@ +#include <iostream> + +#include "Swiften/Parser/IQParser.h" + +namespace Swift { + +IQParser::IQParser(PayloadParserFactoryCollection* factories) : + GenericStanzaParser<IQ>(factories) { +} + +void IQParser::handleStanzaAttributes(const AttributeMap& attributes) { + AttributeMap::const_iterator type = attributes.find("type"); + if (type != attributes.end()) { + if (type->second == "set") { + getStanzaGeneric()->setType(IQ::Set); + } + else if (type->second == "get") { + getStanzaGeneric()->setType(IQ::Get); + } + else if (type->second == "result") { + getStanzaGeneric()->setType(IQ::Result); + } + else if (type->second == "error") { + getStanzaGeneric()->setType(IQ::Error); + } + else { + std::cerr << "Unknown IQ type: " << type->second << std::endl; + getStanzaGeneric()->setType(IQ::Get); + } + } +} + +} diff --git a/Swiften/Parser/IQParser.h b/Swiften/Parser/IQParser.h new file mode 100644 index 0000000..cd2fc05 --- /dev/null +++ b/Swiften/Parser/IQParser.h @@ -0,0 +1,17 @@ +#ifndef SWIFTEN_IQParser_H +#define SWIFTEN_IQParser_H + +#include "Swiften/Parser/GenericStanzaParser.h" +#include "Swiften/Elements/IQ.h" + +namespace Swift { + class IQParser : public GenericStanzaParser<IQ> { + public: + IQParser(PayloadParserFactoryCollection* factories); + + private: + virtual void handleStanzaAttributes(const AttributeMap&); + }; +} + +#endif diff --git a/Swiften/Parser/LibXMLParser.cpp b/Swiften/Parser/LibXMLParser.cpp new file mode 100644 index 0000000..f43ed00 --- /dev/null +++ b/Swiften/Parser/LibXMLParser.cpp @@ -0,0 +1,65 @@ +#include "Swiften/Parser/LibXMLParser.h" + +#include <iostream> +#include <cassert> +#include <cstring> + +#include "Swiften/Base/String.h" +#include "Swiften/Parser/XMLParserClient.h" + +namespace Swift { + +static void handleStartElement(void *client, const xmlChar* name, const xmlChar*, const xmlChar* xmlns, int, const xmlChar**, int nbAttributes, int, const xmlChar ** attributes) { + AttributeMap attributeValues; + for (int i = 0; i < nbAttributes*5; i += 5) { + attributeValues[String(reinterpret_cast<const char*>(attributes[i]))] = String(reinterpret_cast<const char*>(attributes[i+3]), attributes[i+4]-attributes[i+3]); + } + static_cast<XMLParserClient*>(client)->handleStartElement(reinterpret_cast<const char*>(name), (xmlns ? reinterpret_cast<const char*>(xmlns) : String()), attributeValues); +} + +static void handleEndElement(void *client, const xmlChar* name, const xmlChar*, const xmlChar* xmlns) { + static_cast<XMLParserClient*>(client)->handleEndElement(reinterpret_cast<const char*>(name), (xmlns ? reinterpret_cast<const char*>(xmlns) : String())); +} + +static void handleCharacterData(void* client, const xmlChar* data, int len) { + static_cast<XMLParserClient*>(client)->handleCharacterData(String(reinterpret_cast<const char*>(data), len)); +} + +static void handleError(void*, const char*, ... ) { + /* + va_list args; + va_start(args, m); + vfprintf(stdout, m, args); + va_end(args); + */ +} + +static void handleWarning(void*, const char*, ... ) { +} + + + +LibXMLParser::LibXMLParser(XMLParserClient* client) : XMLParser(client) { + memset(&handler_, 0, sizeof(handler_) ); + handler_.initialized = XML_SAX2_MAGIC; + handler_.startElementNs = &handleStartElement; + handler_.endElementNs = &handleEndElement; + handler_.characters = &handleCharacterData; + handler_.warning = &handleWarning; + handler_.error = &handleError; + + context_ = xmlCreatePushParserCtxt(&handler_, client, 0, 0, 0); + assert(context_); +} + +LibXMLParser::~LibXMLParser() { + if (context_) { + xmlFreeParserCtxt(context_); + } +} + +bool LibXMLParser::parse(const String& data) { + return xmlParseChunk(context_, data.getUTF8Data(), data.getUTF8Size(), false) == XML_ERR_OK; +} + +} diff --git a/Swiften/Parser/LibXMLParser.h b/Swiften/Parser/LibXMLParser.h new file mode 100644 index 0000000..58a65f1 --- /dev/null +++ b/Swiften/Parser/LibXMLParser.h @@ -0,0 +1,20 @@ +#pragma once + +#include <libxml/parser.h> +#include <boost/noncopyable.hpp> + +#include "Swiften/Parser/XMLParser.h" + +namespace Swift { + class LibXMLParser : public XMLParser, public boost::noncopyable { + public: + LibXMLParser(XMLParserClient* client); + ~LibXMLParser(); + + bool parse(const String& data); + + private: + xmlSAXHandler handler_; + xmlParserCtxtPtr context_; + }; +} diff --git a/Swiften/Parser/MessageParser.cpp b/Swiften/Parser/MessageParser.cpp new file mode 100644 index 0000000..5e83fad --- /dev/null +++ b/Swiften/Parser/MessageParser.cpp @@ -0,0 +1,33 @@ +#include <iostream> + +#include "Swiften/Parser/MessageParser.h" + +namespace Swift { + +MessageParser::MessageParser(PayloadParserFactoryCollection* factories) : + GenericStanzaParser<Message>(factories) { + getStanzaGeneric()->setType(Message::Normal); +} + +void MessageParser::handleStanzaAttributes(const AttributeMap& attributes) { + AttributeMap::const_iterator type = attributes.find("type"); + if (type != attributes.end()) { + if (type->second == "chat") { + getStanzaGeneric()->setType(Message::Chat); + } + else if (type->second == "error") { + getStanzaGeneric()->setType(Message::Error); + } + else if (type->second == "groupchat") { + getStanzaGeneric()->setType(Message::Groupchat); + } + else if (type->second == "headline") { + getStanzaGeneric()->setType(Message::Headline); + } + else { + getStanzaGeneric()->setType(Message::Normal); + } + } +} + +} diff --git a/Swiften/Parser/MessageParser.h b/Swiften/Parser/MessageParser.h new file mode 100644 index 0000000..05c9280 --- /dev/null +++ b/Swiften/Parser/MessageParser.h @@ -0,0 +1,17 @@ +#ifndef SWIFTEN_MESSAGEPARSER_H +#define SWIFTEN_MESSAGEPARSER_H + +#include "Swiften/Parser/GenericStanzaParser.h" +#include "Swiften/Elements/Message.h" + +namespace Swift { + class MessageParser : public GenericStanzaParser<Message> { + public: + MessageParser(PayloadParserFactoryCollection* factories); + + private: + virtual void handleStanzaAttributes(const AttributeMap&); + }; +} + +#endif diff --git a/Swiften/Parser/PayloadParser.cpp b/Swiften/Parser/PayloadParser.cpp new file mode 100644 index 0000000..072edef --- /dev/null +++ b/Swiften/Parser/PayloadParser.cpp @@ -0,0 +1,8 @@ +#include "Swiften/Parser/PayloadParser.h" + +namespace Swift { + +PayloadParser::~PayloadParser() { +} + +} diff --git a/Swiften/Parser/PayloadParser.h b/Swiften/Parser/PayloadParser.h new file mode 100644 index 0000000..fc1e1c8 --- /dev/null +++ b/Swiften/Parser/PayloadParser.h @@ -0,0 +1,24 @@ +#ifndef SWIFTEN_PAYLOADPARSER_H +#define SWIFTEN_PAYLOADPARSER_H + +#include <boost/shared_ptr.hpp> +#include "Swiften/Parser/AttributeMap.h" + +#include "Swiften/Elements/Payload.h" + +namespace Swift { + class String; + + class PayloadParser { + public: + virtual ~PayloadParser(); + + virtual void handleStartElement(const String& element, const String& ns, const AttributeMap& attributes) = 0; + virtual void handleEndElement(const String& element, const String& ns) = 0; + virtual void handleCharacterData(const String& data) = 0; + + virtual boost::shared_ptr<Payload> getPayload() const = 0; + }; +} + +#endif diff --git a/Swiften/Parser/PayloadParserFactory.cpp b/Swiften/Parser/PayloadParserFactory.cpp new file mode 100644 index 0000000..b31f8ae --- /dev/null +++ b/Swiften/Parser/PayloadParserFactory.cpp @@ -0,0 +1,8 @@ +#include "Swiften/Parser/PayloadParserFactory.h" + +namespace Swift { + +PayloadParserFactory::~PayloadParserFactory() { +} + +} diff --git a/Swiften/Parser/PayloadParserFactory.h b/Swiften/Parser/PayloadParserFactory.h new file mode 100644 index 0000000..728b4e8 --- /dev/null +++ b/Swiften/Parser/PayloadParserFactory.h @@ -0,0 +1,19 @@ +#ifndef SWIFTEN_PAYLOADPARSERFACTORY_H +#define SWIFTEN_PAYLOADPARSERFACTORY_H + +#include "Swiften/Parser/AttributeMap.h" + +namespace Swift { + class String; + class PayloadParser; + + class PayloadParserFactory { + public: + virtual ~PayloadParserFactory(); + + virtual bool canParse(const String& element, const String& ns, const AttributeMap& attributes) const = 0; + virtual PayloadParser* createPayloadParser() = 0; + }; +} + +#endif diff --git a/Swiften/Parser/PayloadParserFactoryCollection.cpp b/Swiften/Parser/PayloadParserFactoryCollection.cpp new file mode 100644 index 0000000..6d3a4cf --- /dev/null +++ b/Swiften/Parser/PayloadParserFactoryCollection.cpp @@ -0,0 +1,31 @@ +#include <boost/bind.hpp> +#include <algorithm> + +#include "Swiften/Parser/PayloadParserFactoryCollection.h" +#include "Swiften/Parser/PayloadParserFactory.h" + +namespace Swift { + +PayloadParserFactoryCollection::PayloadParserFactoryCollection() : defaultFactory_(NULL) { +} + +void PayloadParserFactoryCollection::addFactory(PayloadParserFactory* factory) { + factories_.push_back(factory); +} + +void PayloadParserFactoryCollection::removeFactory(PayloadParserFactory* factory) { + factories_.erase(remove(factories_.begin(), factories_.end(), factory), factories_.end()); +} + +void PayloadParserFactoryCollection::setDefaultFactory(PayloadParserFactory* factory) { + defaultFactory_ = factory; +} + +PayloadParserFactory* PayloadParserFactoryCollection::getPayloadParserFactory(const String& element, const String& ns, const AttributeMap& attributes) { + std::vector<PayloadParserFactory*>::reverse_iterator i = std::find_if( + factories_.rbegin(), factories_.rend(), + boost::bind(&PayloadParserFactory::canParse, _1, element, ns, attributes)); + return (i != factories_.rend() ? *i : defaultFactory_); +} + +} diff --git a/Swiften/Parser/PayloadParserFactoryCollection.h b/Swiften/Parser/PayloadParserFactoryCollection.h new file mode 100644 index 0000000..80a763b --- /dev/null +++ b/Swiften/Parser/PayloadParserFactoryCollection.h @@ -0,0 +1,28 @@ +#ifndef SWIFTEN_PAYLOADPARSERFACTORYCOLLECTION_H +#define SWIFTEN_PAYLOADPARSERFACTORYCOLLECTION_H + +#include <vector> + +#include "Swiften/Parser/AttributeMap.h" + +namespace Swift { + class PayloadParserFactory; + class String; + + class PayloadParserFactoryCollection { + public: + PayloadParserFactoryCollection(); + + void addFactory(PayloadParserFactory* factory); + void removeFactory(PayloadParserFactory* factory); + void setDefaultFactory(PayloadParserFactory* factory); + + PayloadParserFactory* getPayloadParserFactory(const String& element, const String& ns, const AttributeMap& attributes); + + private: + std::vector<PayloadParserFactory*> factories_; + PayloadParserFactory* defaultFactory_; + }; +} + +#endif diff --git a/Swiften/Parser/PayloadParsers/BodyParser.cpp b/Swiften/Parser/PayloadParsers/BodyParser.cpp new file mode 100644 index 0000000..e5898ff --- /dev/null +++ b/Swiften/Parser/PayloadParsers/BodyParser.cpp @@ -0,0 +1,23 @@ +#include "Swiften/Parser/PayloadParsers/BodyParser.h" + +namespace Swift { + +BodyParser::BodyParser() : level_(0) { +} + +void BodyParser::handleStartElement(const String&, const String&, const AttributeMap&) { + ++level_; +} + +void BodyParser::handleEndElement(const String&, const String&) { + --level_; + if (level_ == 0) { + getPayloadInternal()->setText(text_); + } +} + +void BodyParser::handleCharacterData(const String& data) { + text_ += data; +} + +} diff --git a/Swiften/Parser/PayloadParsers/BodyParser.h b/Swiften/Parser/PayloadParsers/BodyParser.h new file mode 100644 index 0000000..2d272ea --- /dev/null +++ b/Swiften/Parser/PayloadParsers/BodyParser.h @@ -0,0 +1,22 @@ +#ifndef SWIFTEN_BodyParser_H +#define SWIFTEN_BodyParser_H + +#include "Swiften/Elements/Body.h" +#include "Swiften/Parser/GenericPayloadParser.h" + +namespace Swift { + class BodyParser : public GenericPayloadParser<Body> { + public: + BodyParser(); + + virtual void handleStartElement(const String& element, const String&, const AttributeMap& attributes); + virtual void handleEndElement(const String& element, const String&); + virtual void handleCharacterData(const String& data); + + private: + int level_; + String text_; + }; +} + +#endif diff --git a/Swiften/Parser/PayloadParsers/BodyParserFactory.h b/Swiften/Parser/PayloadParsers/BodyParserFactory.h new file mode 100644 index 0000000..3da7393 --- /dev/null +++ b/Swiften/Parser/PayloadParsers/BodyParserFactory.h @@ -0,0 +1,14 @@ +#ifndef SWIFTEN_BodyParserFACTORY_H +#define SWIFTEN_BodyParserFACTORY_H + +#include "Swiften/Parser/GenericPayloadParserFactory.h" +#include "Swiften/Parser/PayloadParsers/BodyParser.h" + +namespace Swift { + class BodyParserFactory : public GenericPayloadParserFactory<BodyParser> { + public: + BodyParserFactory() : GenericPayloadParserFactory<BodyParser>("body") {} + }; +} + +#endif diff --git a/Swiften/Parser/PayloadParsers/ChatStateParser.cpp b/Swiften/Parser/PayloadParsers/ChatStateParser.cpp new file mode 100644 index 0000000..52d860a --- /dev/null +++ b/Swiften/Parser/PayloadParsers/ChatStateParser.cpp @@ -0,0 +1,35 @@ +#include "Swiften/Parser/PayloadParsers/ChatStateParser.h" + +namespace Swift { + +ChatStateParser::ChatStateParser() : level_(0) { +} + +void ChatStateParser::handleStartElement(const String& element, const String&, const AttributeMap&) { + if (level_ == 0) { + ChatState::ChatStateType state = ChatState::Active; + if (element == "active") { + state = ChatState::Active; + } else if (element == "composing") { + state = ChatState::Composing; + } else if (element == "inactive") { + state = ChatState::Inactive; + } else if (element == "paused") { + state = ChatState::Paused; + } else if (element == "gone") { + state = ChatState::Gone; + } + getPayloadInternal()->setChatState(state); + } + ++level_; +} + +void ChatStateParser::handleEndElement(const String&, const String&) { + --level_; +} + +void ChatStateParser::handleCharacterData(const String&) { + +} + +} diff --git a/Swiften/Parser/PayloadParsers/ChatStateParser.h b/Swiften/Parser/PayloadParsers/ChatStateParser.h new file mode 100644 index 0000000..cd212c0 --- /dev/null +++ b/Swiften/Parser/PayloadParsers/ChatStateParser.h @@ -0,0 +1,18 @@ +#pragma once + +#include "Swiften/Elements/ChatState.h" +#include "Swiften/Parser/GenericPayloadParser.h" + +namespace Swift { + class ChatStateParser : public GenericPayloadParser<ChatState> { + public: + ChatStateParser(); + + virtual void handleStartElement(const String& element, const String&, const AttributeMap& attributes); + virtual void handleEndElement(const String& element, const String&); + virtual void handleCharacterData(const String& data); + + private: + int level_; + }; +} diff --git a/Swiften/Parser/PayloadParsers/ChatStateParserFactory.h b/Swiften/Parser/PayloadParsers/ChatStateParserFactory.h new file mode 100644 index 0000000..1582d09 --- /dev/null +++ b/Swiften/Parser/PayloadParsers/ChatStateParserFactory.h @@ -0,0 +1,25 @@ +#pragma once + +#include "Swiften/Parser/PayloadParserFactory.h" +#include "Swiften/Parser/PayloadParsers/ChatStateParser.h" + +namespace Swift { + class PayloadParserFactoryCollection; + + class ChatStateParserFactory : public PayloadParserFactory { + public: + ChatStateParserFactory() { + } + + virtual bool canParse(const String& element, const String& ns, const AttributeMap&) const { + return ns == "http://jabber.org/protocol/chatstates" && + (element == "active" || element == "composing" + || element == "paused" || element == "inactive" || element == "gone"); + } + + virtual PayloadParser* createPayloadParser() { + return new ChatStateParser(); + } + + }; +} diff --git a/Swiften/Parser/PayloadParsers/DiscoInfoParser.cpp b/Swiften/Parser/PayloadParsers/DiscoInfoParser.cpp new file mode 100644 index 0000000..ffa24ad --- /dev/null +++ b/Swiften/Parser/PayloadParsers/DiscoInfoParser.cpp @@ -0,0 +1,27 @@ +#include "Swiften/Parser/PayloadParsers/DiscoInfoParser.h" + +namespace Swift { + +DiscoInfoParser::DiscoInfoParser() : level_(TopLevel) { +} + +void DiscoInfoParser::handleStartElement(const String& element, const String&, const AttributeMap& attributes) { + if (level_ == PayloadLevel) { + if (element == "identity") { + getPayloadInternal()->addIdentity(DiscoInfo::Identity(attributes.getAttribute("name"), attributes.getAttribute("category"), attributes.getAttribute("type"), attributes.getAttribute("lang"))); + } + else if (element == "feature") { + getPayloadInternal()->addFeature(attributes.getAttribute("var")); + } + } + ++level_; +} + +void DiscoInfoParser::handleEndElement(const String&, const String&) { + --level_; +} + +void DiscoInfoParser::handleCharacterData(const String&) { +} + +} diff --git a/Swiften/Parser/PayloadParsers/DiscoInfoParser.h b/Swiften/Parser/PayloadParsers/DiscoInfoParser.h new file mode 100644 index 0000000..b7be972 --- /dev/null +++ b/Swiften/Parser/PayloadParsers/DiscoInfoParser.h @@ -0,0 +1,25 @@ +#ifndef SWIFTEN_DiscoInfoParser_H +#define SWIFTEN_DiscoInfoParser_H + +#include "Swiften/Elements/DiscoInfo.h" +#include "Swiften/Parser/GenericPayloadParser.h" + +namespace Swift { + class DiscoInfoParser : public GenericPayloadParser<DiscoInfo> { + public: + DiscoInfoParser(); + + virtual void handleStartElement(const String& element, const String&, const AttributeMap& attributes); + virtual void handleEndElement(const String& element, const String&); + virtual void handleCharacterData(const String& data); + + private: + enum Level { + TopLevel = 0, + PayloadLevel = 1 + }; + int level_; + }; +} + +#endif diff --git a/Swiften/Parser/PayloadParsers/DiscoInfoParserFactory.h b/Swiften/Parser/PayloadParsers/DiscoInfoParserFactory.h new file mode 100644 index 0000000..ef1c31c --- /dev/null +++ b/Swiften/Parser/PayloadParsers/DiscoInfoParserFactory.h @@ -0,0 +1,14 @@ +#ifndef SWIFTEN_DiscoInfoParserFactory_H +#define SWIFTEN_DiscoInfoParserFactory_H + +#include "Swiften/Parser/GenericPayloadParserFactory.h" +#include "Swiften/Parser/PayloadParsers/DiscoInfoParser.h" + +namespace Swift { + class DiscoInfoParserFactory : public GenericPayloadParserFactory<DiscoInfoParser> { + public: + DiscoInfoParserFactory() : GenericPayloadParserFactory<DiscoInfoParser>("query", "http://jabber.org/protocol/disco#info") {} + }; +} + +#endif diff --git a/Swiften/Parser/PayloadParsers/ErrorParser.cpp b/Swiften/Parser/PayloadParsers/ErrorParser.cpp new file mode 100644 index 0000000..ae85265 --- /dev/null +++ b/Swiften/Parser/PayloadParsers/ErrorParser.cpp @@ -0,0 +1,109 @@ +#include "Swiften/Parser/PayloadParsers/ErrorParser.h" + +namespace Swift { + +ErrorParser::ErrorParser() : level_(TopLevel) { +} + +void ErrorParser::handleStartElement(const String&, const String&, const AttributeMap& attributes) { + if (level_ == TopLevel) { + String type = attributes.getAttribute("type"); + if (type == "continue") { + getPayloadInternal()->setType(ErrorPayload::Continue); + } + else if (type == "modify") { + getPayloadInternal()->setType(ErrorPayload::Modify); + } + else if (type == "auth") { + getPayloadInternal()->setType(ErrorPayload::Auth); + } + else if (type == "wait") { + getPayloadInternal()->setType(ErrorPayload::Wait); + } + else { + getPayloadInternal()->setType(ErrorPayload::Cancel); + } + } + ++level_; +} + +void ErrorParser::handleEndElement(const String& element, const String&) { + --level_; + if (level_ == PayloadLevel) { + if (element == "text") { + getPayloadInternal()->setText(currentText_); + } + else if (element == "bad-request") { + getPayloadInternal()->setCondition(ErrorPayload::BadRequest); + } + else if (element == "conflict") { + getPayloadInternal()->setCondition(ErrorPayload::Conflict); + } + else if (element == "feature-not-implemented") { + getPayloadInternal()->setCondition(ErrorPayload::FeatureNotImplemented); + } + else if (element == "forbidden") { + getPayloadInternal()->setCondition(ErrorPayload::Forbidden); + } + else if (element == "gone") { + getPayloadInternal()->setCondition(ErrorPayload::Gone); + } + else if (element == "internal-server-error") { + getPayloadInternal()->setCondition(ErrorPayload::InternalServerError); + } + else if (element == "item-not-found") { + getPayloadInternal()->setCondition(ErrorPayload::ItemNotFound); + } + else if (element == "jid-malformed") { + getPayloadInternal()->setCondition(ErrorPayload::JIDMalformed); + } + else if (element == "not-acceptable") { + getPayloadInternal()->setCondition(ErrorPayload::NotAcceptable); + } + else if (element == "not-allowed") { + getPayloadInternal()->setCondition(ErrorPayload::NotAllowed); + } + else if (element == "not-authorized") { + getPayloadInternal()->setCondition(ErrorPayload::NotAuthorized); + } + else if (element == "payment-required") { + getPayloadInternal()->setCondition(ErrorPayload::PaymentRequired); + } + else if (element == "recipient-unavailable") { + getPayloadInternal()->setCondition(ErrorPayload::RecipientUnavailable); + } + else if (element == "redirect") { + getPayloadInternal()->setCondition(ErrorPayload::Redirect); + } + else if (element == "registration-required") { + getPayloadInternal()->setCondition(ErrorPayload::RegistrationRequired); + } + else if (element == "remote-server-not-found") { + getPayloadInternal()->setCondition(ErrorPayload::RemoteServerNotFound); + } + else if (element == "remote-server-timeout") { + getPayloadInternal()->setCondition(ErrorPayload::RemoteServerTimeout); + } + else if (element == "resource-constraint") { + getPayloadInternal()->setCondition(ErrorPayload::ResourceConstraint); + } + else if (element == "service-unavailable") { + getPayloadInternal()->setCondition(ErrorPayload::ServiceUnavailable); + } + else if (element == "subscription-required") { + getPayloadInternal()->setCondition(ErrorPayload::SubscriptionRequired); + } + else if (element == "unexpected-request") { + getPayloadInternal()->setCondition(ErrorPayload::UnexpectedRequest); + } + else { + getPayloadInternal()->setCondition(ErrorPayload::UndefinedCondition); + } + } +} + +void ErrorParser::handleCharacterData(const String& data) { + currentText_ += data; +} + +} diff --git a/Swiften/Parser/PayloadParsers/ErrorParser.h b/Swiften/Parser/PayloadParsers/ErrorParser.h new file mode 100644 index 0000000..17b78b9 --- /dev/null +++ b/Swiften/Parser/PayloadParsers/ErrorParser.h @@ -0,0 +1,26 @@ +#ifndef SWIFTEN_ErrorParser_H +#define SWIFTEN_ErrorParser_H + +#include "Swiften/Elements/ErrorPayload.h" +#include "Swiften/Parser/GenericPayloadParser.h" + +namespace Swift { + class ErrorParser : public GenericPayloadParser<ErrorPayload> { + public: + ErrorParser(); + + virtual void handleStartElement(const String& element, const String&, const AttributeMap& attributes); + virtual void handleEndElement(const String& element, const String&); + virtual void handleCharacterData(const String& data); + + private: + enum Level { + TopLevel = 0, + PayloadLevel = 1 + }; + int level_; + String currentText_; + }; +} + +#endif diff --git a/Swiften/Parser/PayloadParsers/ErrorParserFactory.h b/Swiften/Parser/PayloadParsers/ErrorParserFactory.h new file mode 100644 index 0000000..36d1f55 --- /dev/null +++ b/Swiften/Parser/PayloadParsers/ErrorParserFactory.h @@ -0,0 +1,14 @@ +#ifndef SWIFTEN_ErrorParserFactory_H +#define SWIFTEN_ErrorParserFactory_H + +#include "Swiften/Parser/GenericPayloadParserFactory.h" +#include "Swiften/Parser/PayloadParsers/ErrorParser.h" + +namespace Swift { + class ErrorParserFactory : public GenericPayloadParserFactory<ErrorParser> { + public: + ErrorParserFactory() : GenericPayloadParserFactory<ErrorParser>("error") {} + }; +} + +#endif diff --git a/Swiften/Parser/PayloadParsers/FullPayloadParserFactoryCollection.cpp b/Swiften/Parser/PayloadParsers/FullPayloadParserFactoryCollection.cpp new file mode 100644 index 0000000..0857f64 --- /dev/null +++ b/Swiften/Parser/PayloadParsers/FullPayloadParserFactoryCollection.cpp @@ -0,0 +1,61 @@ +#include "Swiften/Parser/PayloadParsers/FullPayloadParserFactoryCollection.h" +#include "Swiften/Base/foreach.h" +#include "Swiften/Parser/GenericPayloadParser.h" +#include "Swiften/Parser/PayloadParserFactory.h" +#include "Swiften/Parser/PayloadParsers/ErrorParserFactory.h" +#include "Swiften/Parser/PayloadParsers/BodyParserFactory.h" +#include "Swiften/Parser/PayloadParsers/ChatStateParserFactory.h" +#include "Swiften/Parser/PayloadParsers/PriorityParserFactory.h" +#include "Swiften/Parser/PayloadParsers/ResourceBindParserFactory.h" +#include "Swiften/Parser/PayloadParsers/StartSessionParserFactory.h" +#include "Swiften/Parser/PayloadParsers/StatusParserFactory.h" +#include "Swiften/Parser/PayloadParsers/StatusShowParserFactory.h" +#include "Swiften/Parser/PayloadParsers/RosterParserFactory.h" +#include "Swiften/Parser/PayloadParsers/SoftwareVersionParserFactory.h" +#include "Swiften/Parser/PayloadParsers/StorageParserFactory.h" +#include "Swiften/Parser/PayloadParsers/DiscoInfoParserFactory.h" +#include "Swiften/Parser/PayloadParsers/SecurityLabelParserFactory.h" +#include "Swiften/Parser/PayloadParsers/SecurityLabelsCatalogParserFactory.h" +#include "Swiften/Parser/PayloadParsers/VCardUpdateParserFactory.h" +#include "Swiften/Parser/PayloadParsers/VCardParserFactory.h" +#include "Swiften/Parser/PayloadParsers/RawXMLPayloadParserFactory.h" +#include "Swiften/Parser/PayloadParsers/PrivateStorageParserFactory.h" + +using namespace boost; + +namespace Swift { + +FullPayloadParserFactoryCollection::FullPayloadParserFactoryCollection() { + factories_.push_back(shared_ptr<PayloadParserFactory>(new StatusParserFactory())); + factories_.push_back(shared_ptr<PayloadParserFactory>(new StatusShowParserFactory())); + factories_.push_back(shared_ptr<PayloadParserFactory>(new BodyParserFactory())); + factories_.push_back(shared_ptr<PayloadParserFactory>(new PriorityParserFactory())); + factories_.push_back(shared_ptr<PayloadParserFactory>(new ErrorParserFactory())); + factories_.push_back(shared_ptr<PayloadParserFactory>(new SoftwareVersionParserFactory())); + factories_.push_back(shared_ptr<PayloadParserFactory>(new StorageParserFactory())); + factories_.push_back(shared_ptr<PayloadParserFactory>(new RosterParserFactory())); + factories_.push_back(shared_ptr<PayloadParserFactory>(new DiscoInfoParserFactory())); + factories_.push_back(shared_ptr<PayloadParserFactory>(new ResourceBindParserFactory())); + factories_.push_back(shared_ptr<PayloadParserFactory>(new StartSessionParserFactory())); + factories_.push_back(shared_ptr<PayloadParserFactory>(new SecurityLabelParserFactory())); + factories_.push_back(shared_ptr<PayloadParserFactory>(new SecurityLabelsCatalogParserFactory())); + factories_.push_back(shared_ptr<PayloadParserFactory>(new VCardUpdateParserFactory())); + factories_.push_back(shared_ptr<PayloadParserFactory>(new VCardParserFactory())); + factories_.push_back(shared_ptr<PayloadParserFactory>(new PrivateStorageParserFactory(this))); + factories_.push_back(shared_ptr<PayloadParserFactory>(new ChatStateParserFactory())); + foreach(shared_ptr<PayloadParserFactory> factory, factories_) { + addFactory(factory.get()); + } + defaultFactory_ = new RawXMLPayloadParserFactory(); + setDefaultFactory(defaultFactory_); +} + +FullPayloadParserFactoryCollection::~FullPayloadParserFactoryCollection() { + setDefaultFactory(NULL); + delete defaultFactory_; + foreach(shared_ptr<PayloadParserFactory> factory, factories_) { + removeFactory(factory.get()); + } +} + +} diff --git a/Swiften/Parser/PayloadParsers/FullPayloadParserFactoryCollection.h b/Swiften/Parser/PayloadParsers/FullPayloadParserFactoryCollection.h new file mode 100644 index 0000000..82e5a56 --- /dev/null +++ b/Swiften/Parser/PayloadParsers/FullPayloadParserFactoryCollection.h @@ -0,0 +1,19 @@ +#pragma once + +#include <boost/shared_ptr.hpp> +#include <vector> + +#include "Swiften/Parser/PayloadParserFactoryCollection.h" +#include "Swiften/Parser/PayloadParserFactory.h" + +namespace Swift { + class FullPayloadParserFactoryCollection : public PayloadParserFactoryCollection { + public: + FullPayloadParserFactoryCollection(); + ~FullPayloadParserFactoryCollection(); + + private: + std::vector< boost::shared_ptr<PayloadParserFactory> > factories_; + PayloadParserFactory* defaultFactory_; + }; +} diff --git a/Swiften/Parser/PayloadParsers/PriorityParser.cpp b/Swiften/Parser/PayloadParsers/PriorityParser.cpp new file mode 100644 index 0000000..3dcca51 --- /dev/null +++ b/Swiften/Parser/PayloadParsers/PriorityParser.cpp @@ -0,0 +1,25 @@ +#include "Swiften/Parser/PayloadParsers/PriorityParser.h" + +#include <boost/lexical_cast.hpp> + +namespace Swift { + +PriorityParser::PriorityParser() : level_(0) { +} + +void PriorityParser::handleStartElement(const String&, const String&, const AttributeMap&) { + ++level_; +} + +void PriorityParser::handleEndElement(const String&, const String&) { + --level_; + if (level_ == 0) { + getPayloadInternal()->setPriority(boost::lexical_cast<int>(text_)); + } +} + +void PriorityParser::handleCharacterData(const String& data) { + text_ += data; +} + +} diff --git a/Swiften/Parser/PayloadParsers/PriorityParser.h b/Swiften/Parser/PayloadParsers/PriorityParser.h new file mode 100644 index 0000000..7f3836f --- /dev/null +++ b/Swiften/Parser/PayloadParsers/PriorityParser.h @@ -0,0 +1,22 @@ +#ifndef SWIFTEN_PriorityParser_H +#define SWIFTEN_PriorityParser_H + +#include "Swiften/Elements/Priority.h" +#include "Swiften/Parser/GenericPayloadParser.h" + +namespace Swift { + class PriorityParser : public GenericPayloadParser<Priority> { + public: + PriorityParser(); + + virtual void handleStartElement(const String& element, const String&, const AttributeMap& attributes); + virtual void handleEndElement(const String& element, const String&); + virtual void handleCharacterData(const String& data); + + private: + int level_; + String text_; + }; +} + +#endif diff --git a/Swiften/Parser/PayloadParsers/PriorityParserFactory.h b/Swiften/Parser/PayloadParsers/PriorityParserFactory.h new file mode 100644 index 0000000..5386326 --- /dev/null +++ b/Swiften/Parser/PayloadParsers/PriorityParserFactory.h @@ -0,0 +1,14 @@ +#ifndef SWIFTEN_PriorityParserFactory_H +#define SWIFTEN_PriorityParserFactory_H + +#include "Swiften/Parser/GenericPayloadParserFactory.h" +#include "Swiften/Parser/PayloadParsers/PriorityParser.h" + +namespace Swift { + class PriorityParserFactory : public GenericPayloadParserFactory<PriorityParser> { + public: + PriorityParserFactory() : GenericPayloadParserFactory<PriorityParser>("priority") {} + }; +} + +#endif diff --git a/Swiften/Parser/PayloadParsers/PrivateStorageParser.cpp b/Swiften/Parser/PayloadParsers/PrivateStorageParser.cpp new file mode 100644 index 0000000..5c3af26 --- /dev/null +++ b/Swiften/Parser/PayloadParsers/PrivateStorageParser.cpp @@ -0,0 +1,43 @@ +#include "Swiften/Parser/PayloadParsers/PrivateStorageParser.h" +#include "Swiften/Parser/PayloadParserFactoryCollection.h" +#include "Swiften/Parser/PayloadParserFactory.h" + +namespace Swift { + +PrivateStorageParser::PrivateStorageParser(PayloadParserFactoryCollection* factories) : factories(factories), level(0) { +} + +void PrivateStorageParser::handleStartElement(const String& element, const String& ns, const AttributeMap& attributes) { + if (level == 1) { + PayloadParserFactory* payloadParserFactory = factories->getPayloadParserFactory(element, ns, attributes); + if (payloadParserFactory) { + currentPayloadParser.reset(payloadParserFactory->createPayloadParser()); + } + } + + if (level >= 1 && currentPayloadParser.get()) { + currentPayloadParser->handleStartElement(element, ns, attributes); + } + ++level; +} + +void PrivateStorageParser::handleEndElement(const String& element, const String& ns) { + --level; + if (currentPayloadParser.get()) { + if (level >= 1) { + currentPayloadParser->handleEndElement(element, ns); + } + + if (level == 1) { + getPayloadInternal()->setPayload(currentPayloadParser->getPayload()); + } + } +} + +void PrivateStorageParser::handleCharacterData(const String& data) { + if (level > 1 && currentPayloadParser.get()) { + currentPayloadParser->handleCharacterData(data); + } +} + +} diff --git a/Swiften/Parser/PayloadParsers/PrivateStorageParser.h b/Swiften/Parser/PayloadParsers/PrivateStorageParser.h new file mode 100644 index 0000000..fae0f10 --- /dev/null +++ b/Swiften/Parser/PayloadParsers/PrivateStorageParser.h @@ -0,0 +1,25 @@ +#pragma once + +#include <boost/optional.hpp> + +#include "Swiften/Elements/PrivateStorage.h" +#include "Swiften/Parser/GenericPayloadParser.h" + +namespace Swift { + class PayloadParserFactoryCollection; + + class PrivateStorageParser : public GenericPayloadParser<PrivateStorage> { + public: + PrivateStorageParser(PayloadParserFactoryCollection* factories); + + private: + virtual void handleStartElement(const String& element, const String&, const AttributeMap& attributes); + virtual void handleEndElement(const String& element, const String&); + virtual void handleCharacterData(const String& data); + + private: + PayloadParserFactoryCollection* factories; + int level; + std::auto_ptr<PayloadParser> currentPayloadParser; + }; +} diff --git a/Swiften/Parser/PayloadParsers/PrivateStorageParserFactory.h b/Swiften/Parser/PayloadParsers/PrivateStorageParserFactory.h new file mode 100644 index 0000000..4d9c02b --- /dev/null +++ b/Swiften/Parser/PayloadParsers/PrivateStorageParserFactory.h @@ -0,0 +1,26 @@ +#pragma once + +#include "Swiften/Parser/PayloadParserFactory.h" +#include "Swiften/Parser/PayloadParsers/PrivateStorageParser.h" + +namespace Swift { + class PayloadParserFactoryCollection; + + class PrivateStorageParserFactory : public PayloadParserFactory { + public: + PrivateStorageParserFactory(PayloadParserFactoryCollection* factories) : factories(factories) { + } + + virtual bool canParse(const String& element, const String& ns, const AttributeMap&) const { + return element == "query" && ns == "jabber:iq:private"; + } + + virtual PayloadParser* createPayloadParser() { + return new PrivateStorageParser(factories); + } + + private: + PayloadParserFactoryCollection* factories; + + }; +} diff --git a/Swiften/Parser/PayloadParsers/RawXMLPayloadParser.cpp b/Swiften/Parser/PayloadParsers/RawXMLPayloadParser.cpp new file mode 100644 index 0000000..c49af3e --- /dev/null +++ b/Swiften/Parser/PayloadParsers/RawXMLPayloadParser.cpp @@ -0,0 +1,26 @@ +#include "Swiften/Parser/PayloadParsers/RawXMLPayloadParser.h" +#include "Swiften/Parser/SerializingParser.h" + +namespace Swift { + +RawXMLPayloadParser::RawXMLPayloadParser() : level_(0) { +} + +void RawXMLPayloadParser::handleStartElement(const String& element, const String& ns, const AttributeMap& attributes) { + ++level_; + serializingParser_.handleStartElement(element, ns, attributes); +} + +void RawXMLPayloadParser::handleEndElement(const String& element, const String& ns) { + serializingParser_.handleEndElement(element, ns); + --level_; + if (level_ == 0) { + getPayloadInternal()->setRawXML(serializingParser_.getResult()); + } +} + +void RawXMLPayloadParser::handleCharacterData(const String& data) { + serializingParser_.handleCharacterData(data); +} + +} diff --git a/Swiften/Parser/PayloadParsers/RawXMLPayloadParser.h b/Swiften/Parser/PayloadParsers/RawXMLPayloadParser.h new file mode 100644 index 0000000..f636486 --- /dev/null +++ b/Swiften/Parser/PayloadParsers/RawXMLPayloadParser.h @@ -0,0 +1,22 @@ +#pragma once + +#include "Swiften/Elements/RawXMLPayload.h" +#include "Swiften/Parser/GenericPayloadParser.h" +#include "Swiften/Parser/SerializingParser.h" + +namespace Swift { + class SerializingParser; + + class RawXMLPayloadParser : public GenericPayloadParser<RawXMLPayload> { + public: + RawXMLPayloadParser(); + + virtual void handleStartElement(const String& element, const String&, const AttributeMap& attributes); + virtual void handleEndElement(const String& element, const String&); + virtual void handleCharacterData(const String& data); + + private: + int level_; + SerializingParser serializingParser_; + }; +} diff --git a/Swiften/Parser/PayloadParsers/RawXMLPayloadParserFactory.h b/Swiften/Parser/PayloadParsers/RawXMLPayloadParserFactory.h new file mode 100644 index 0000000..46b1183 --- /dev/null +++ b/Swiften/Parser/PayloadParsers/RawXMLPayloadParserFactory.h @@ -0,0 +1,20 @@ +#pragma once + +#include "Swiften/Parser/PayloadParserFactory.h" +#include "Swiften/Parser/PayloadParsers/RawXMLPayloadParser.h" +#include "Swiften/Base/String.h" + +namespace Swift { + class RawXMLPayloadParserFactory : public PayloadParserFactory { + public: + RawXMLPayloadParserFactory() {} + + virtual bool canParse(const String&, const String&, const AttributeMap&) const { + return true; + } + + virtual PayloadParser* createPayloadParser() { + return new RawXMLPayloadParser(); + } + }; +} diff --git a/Swiften/Parser/PayloadParsers/ResourceBindParser.cpp b/Swiften/Parser/PayloadParsers/ResourceBindParser.cpp new file mode 100644 index 0000000..c5ca787 --- /dev/null +++ b/Swiften/Parser/PayloadParsers/ResourceBindParser.cpp @@ -0,0 +1,37 @@ +#include "Swiften/Parser/PayloadParsers/ResourceBindParser.h" + +namespace Swift { + +ResourceBindParser::ResourceBindParser() : level_(0), inJID_(false), inResource_(false) { +} + +void ResourceBindParser::handleStartElement(const String& element, const String&, const AttributeMap&) { + if (level_ == 1) { + text_ = ""; + if (element == "resource") { + inResource_ = true; + } + if (element == "jid") { + inJID_ = true; + } + } + ++level_; +} + +void ResourceBindParser::handleEndElement(const String&, const String&) { + --level_; + if (level_ == 1) { + if (inJID_) { + getPayloadInternal()->setJID(JID(text_)); + } + else if (inResource_) { + getPayloadInternal()->setResource(text_); + } + } +} + +void ResourceBindParser::handleCharacterData(const String& data) { + text_ += data; +} + +} diff --git a/Swiften/Parser/PayloadParsers/ResourceBindParser.h b/Swiften/Parser/PayloadParsers/ResourceBindParser.h new file mode 100644 index 0000000..1341140 --- /dev/null +++ b/Swiften/Parser/PayloadParsers/ResourceBindParser.h @@ -0,0 +1,24 @@ +#ifndef SWIFTEN_ResourceBindParser_H +#define SWIFTEN_ResourceBindParser_H + +#include "Swiften/Elements/ResourceBind.h" +#include "Swiften/Parser/GenericPayloadParser.h" + +namespace Swift { + class ResourceBindParser : public GenericPayloadParser<ResourceBind> { + public: + ResourceBindParser(); + + virtual void handleStartElement(const String& element, const String&, const AttributeMap& attributes); + virtual void handleEndElement(const String& element, const String&); + virtual void handleCharacterData(const String& data); + + private: + int level_; + bool inJID_; + bool inResource_; + String text_; + }; +} + +#endif diff --git a/Swiften/Parser/PayloadParsers/ResourceBindParserFactory.h b/Swiften/Parser/PayloadParsers/ResourceBindParserFactory.h new file mode 100644 index 0000000..54af9c9 --- /dev/null +++ b/Swiften/Parser/PayloadParsers/ResourceBindParserFactory.h @@ -0,0 +1,14 @@ +#ifndef SWIFTEN_ResourceBindParserFactory_H +#define SWIFTEN_ResourceBindParserFactory_H + +#include "Swiften/Parser/GenericPayloadParserFactory.h" +#include "Swiften/Parser/PayloadParsers/ResourceBindParser.h" + +namespace Swift { + class ResourceBindParserFactory : public GenericPayloadParserFactory<ResourceBindParser> { + public: + ResourceBindParserFactory() : GenericPayloadParserFactory<ResourceBindParser>("bind", "urn:ietf:params:xml:ns:xmpp-bind") {} + }; +} + +#endif diff --git a/Swiften/Parser/PayloadParsers/RosterParser.cpp b/Swiften/Parser/PayloadParsers/RosterParser.cpp new file mode 100644 index 0000000..0c4e99b --- /dev/null +++ b/Swiften/Parser/PayloadParsers/RosterParser.cpp @@ -0,0 +1,66 @@ +#include "Swiften/Parser/PayloadParsers/RosterParser.h" + +namespace Swift { + +RosterParser::RosterParser() : level_(TopLevel) { +} + +void RosterParser::handleStartElement(const String& element, const String&, const AttributeMap& attributes) { + if (level_ == PayloadLevel) { + if (element == "item") { + inItem_ = true; + currentItem_ = RosterItemPayload(); + + currentItem_.setJID(JID(attributes.getAttribute("jid"))); + currentItem_.setName(attributes.getAttribute("name")); + + String subscription = attributes.getAttribute("subscription"); + if (subscription == "both") { + currentItem_.setSubscription(RosterItemPayload::Both); + } + else if (subscription == "to") { + currentItem_.setSubscription(RosterItemPayload::To); + } + else if (subscription == "from") { + currentItem_.setSubscription(RosterItemPayload::From); + } + else if (subscription == "remove") { + currentItem_.setSubscription(RosterItemPayload::Remove); + } + else { + currentItem_.setSubscription(RosterItemPayload::None); + } + + if (attributes.getAttribute("ask") == "subscribe") { + currentItem_.setSubscriptionRequested(); + } + } + } + else if (level_ == ItemLevel) { + if (element == "group") { + currentText_ = ""; + } + } + ++level_; +} + +void RosterParser::handleEndElement(const String& element, const String&) { + --level_; + if (level_ == PayloadLevel) { + if (inItem_) { + getPayloadInternal()->addItem(currentItem_); + inItem_ = false; + } + } + else if (level_ == ItemLevel) { + if (element == "group") { + currentItem_.addGroup(currentText_); + } + } +} + +void RosterParser::handleCharacterData(const String& data) { + currentText_ += data; +} + +} diff --git a/Swiften/Parser/PayloadParsers/RosterParser.h b/Swiften/Parser/PayloadParsers/RosterParser.h new file mode 100644 index 0000000..bd8186a --- /dev/null +++ b/Swiften/Parser/PayloadParsers/RosterParser.h @@ -0,0 +1,29 @@ +#ifndef SWIFTEN_RosterParser_H +#define SWIFTEN_RosterParser_H + +#include "Swiften/Elements/RosterPayload.h" +#include "Swiften/Parser/GenericPayloadParser.h" + +namespace Swift { + class RosterParser : public GenericPayloadParser<RosterPayload> { + public: + RosterParser(); + + virtual void handleStartElement(const String& element, const String&, const AttributeMap& attributes); + virtual void handleEndElement(const String& element, const String&); + virtual void handleCharacterData(const String& data); + + private: + enum Level { + TopLevel = 0, + PayloadLevel = 1, + ItemLevel = 2 + }; + int level_; + bool inItem_; + RosterItemPayload currentItem_; + String currentText_; + }; +} + +#endif diff --git a/Swiften/Parser/PayloadParsers/RosterParserFactory.h b/Swiften/Parser/PayloadParsers/RosterParserFactory.h new file mode 100644 index 0000000..f51b3ab --- /dev/null +++ b/Swiften/Parser/PayloadParsers/RosterParserFactory.h @@ -0,0 +1,14 @@ +#ifndef SWIFTEN_RosterParserFactory_H +#define SWIFTEN_RosterParserFactory_H + +#include "Swiften/Parser/GenericPayloadParserFactory.h" +#include "Swiften/Parser/PayloadParsers/RosterParser.h" + +namespace Swift { + class RosterParserFactory : public GenericPayloadParserFactory<RosterParser> { + public: + RosterParserFactory() : GenericPayloadParserFactory<RosterParser>("query", "jabber:iq:roster") {} + }; +} + +#endif diff --git a/Swiften/Parser/PayloadParsers/SecurityLabelParser.cpp b/Swiften/Parser/PayloadParsers/SecurityLabelParser.cpp new file mode 100644 index 0000000..7e65575 --- /dev/null +++ b/Swiften/Parser/PayloadParsers/SecurityLabelParser.cpp @@ -0,0 +1,59 @@ +#include "Swiften/Parser/PayloadParsers/SecurityLabelParser.h" +#include "Swiften/Parser/SerializingParser.h" + +namespace Swift { + +SecurityLabelParser::SecurityLabelParser() : level_(TopLevel), labelParser_(0) { +} + +void SecurityLabelParser::handleStartElement(const String& element, const String& ns, const AttributeMap& attributes) { + ++level_; + if (level_ == DisplayMarkingOrLabelLevel) { + if (element == "displaymarking") { + currentText_ = ""; + getPayloadInternal()->setBackgroundColor(attributes.getAttribute("bgcolor")); + getPayloadInternal()->setForegroundColor(attributes.getAttribute("fgcolor")); + } + else if (element == "label" || element == "equivalentlabel") { + assert(!labelParser_); + labelParser_ = new SerializingParser(); + } + } + else if (level_ >= SecurityLabelLevel && labelParser_) { + labelParser_->handleStartElement(element, ns, attributes); + } +} + +void SecurityLabelParser::handleEndElement(const String& element, const String& ns) { + if (level_ == DisplayMarkingOrLabelLevel) { + if (element == "displaymarking") { + getPayloadInternal()->setDisplayMarking(currentText_); + } + else if (labelParser_) { + if (element == "label") { + getPayloadInternal()->setLabel(labelParser_->getResult()); + } + else { + getPayloadInternal()->addEquivalentLabel(labelParser_->getResult()); + } + delete labelParser_; + labelParser_ = 0; + } + } + else if (labelParser_ && level_ >= SecurityLabelLevel) { + labelParser_->handleEndElement(element, ns); + } + --level_; + +} + +void SecurityLabelParser::handleCharacterData(const String& data) { + if (labelParser_) { + labelParser_->handleCharacterData(data); + } + else { + currentText_ += data; + } +} + +} diff --git a/Swiften/Parser/PayloadParsers/SecurityLabelParser.h b/Swiften/Parser/PayloadParsers/SecurityLabelParser.h new file mode 100644 index 0000000..70040d9 --- /dev/null +++ b/Swiften/Parser/PayloadParsers/SecurityLabelParser.h @@ -0,0 +1,31 @@ +#ifndef SWIFTEN_SecurityLabelParser_H +#define SWIFTEN_SecurityLabelParser_H + +#include "Swiften/Elements/SecurityLabel.h" +#include "Swiften/Parser/GenericPayloadParser.h" + +namespace Swift { + class SerializingParser; + + class SecurityLabelParser : public GenericPayloadParser<SecurityLabel> { + public: + SecurityLabelParser(); + + virtual void handleStartElement(const String& element, const String&, const AttributeMap& attributes); + virtual void handleEndElement(const String& element, const String&); + virtual void handleCharacterData(const String& data); + + private: + enum Level { + TopLevel = 0, + PayloadLevel = 1, + DisplayMarkingOrLabelLevel = 2, + SecurityLabelLevel = 3 + }; + int level_; + SerializingParser* labelParser_; + String currentText_; + }; +} + +#endif diff --git a/Swiften/Parser/PayloadParsers/SecurityLabelParserFactory.h b/Swiften/Parser/PayloadParsers/SecurityLabelParserFactory.h new file mode 100644 index 0000000..0341fbb --- /dev/null +++ b/Swiften/Parser/PayloadParsers/SecurityLabelParserFactory.h @@ -0,0 +1,14 @@ +#ifndef SWIFTEN_SecurityLabelParserFactory_H +#define SWIFTEN_SecurityLabelParserFactory_H + +#include "Swiften/Parser/GenericPayloadParserFactory.h" +#include "Swiften/Parser/PayloadParsers/SecurityLabelParser.h" + +namespace Swift { + class SecurityLabelParserFactory : public GenericPayloadParserFactory<SecurityLabelParser> { + public: + SecurityLabelParserFactory() : GenericPayloadParserFactory<SecurityLabelParser>("securitylabel", "urn:xmpp:sec-label:0") {} + }; +} + +#endif diff --git a/Swiften/Parser/PayloadParsers/SecurityLabelsCatalogParser.cpp b/Swiften/Parser/PayloadParsers/SecurityLabelsCatalogParser.cpp new file mode 100644 index 0000000..e4da756 --- /dev/null +++ b/Swiften/Parser/PayloadParsers/SecurityLabelsCatalogParser.cpp @@ -0,0 +1,55 @@ +#include "Swiften/Parser/PayloadParsers/SecurityLabelsCatalogParser.h" +#include "Swiften/Parser/PayloadParsers/SecurityLabelParserFactory.h" +#include "Swiften/Parser/PayloadParsers/SecurityLabelParser.h" + +namespace Swift { + +SecurityLabelsCatalogParser::SecurityLabelsCatalogParser() : level_(TopLevel), labelParser_(0) { + labelParserFactory_ = new SecurityLabelParserFactory(); +} + +SecurityLabelsCatalogParser::~SecurityLabelsCatalogParser() { + delete labelParserFactory_; +} + +void SecurityLabelsCatalogParser::handleStartElement(const String& element, const String& ns, const AttributeMap& attributes) { + ++level_; + if (level_ == PayloadLevel) { + getPayloadInternal()->setTo(JID(attributes.getAttribute("to"))); + getPayloadInternal()->setName(attributes.getAttribute("name")); + getPayloadInternal()->setDescription(attributes.getAttribute("desc")); + } + else if (level_ == LabelLevel) { + assert(!labelParser_); + if (labelParserFactory_->canParse(element, ns, attributes)) { + labelParser_ = dynamic_cast<SecurityLabelParser*>(labelParserFactory_->createPayloadParser()); + assert(labelParser_); + } + } + + if (labelParser_) { + labelParser_->handleStartElement(element, ns, attributes); + } +} + +void SecurityLabelsCatalogParser::handleEndElement(const String& element, const String& ns) { + if (labelParser_) { + labelParser_->handleEndElement(element, ns); + } + if (level_ == LabelLevel && labelParser_) { + SecurityLabel* label = dynamic_cast<SecurityLabel*>(labelParser_->getPayload().get()); + assert(label); + getPayloadInternal()->addLabel(SecurityLabel(*label)); + delete labelParser_; + labelParser_ = 0; + } + --level_; +} + +void SecurityLabelsCatalogParser::handleCharacterData(const String& data) { + if (labelParser_) { + labelParser_->handleCharacterData(data); + } +} + +} diff --git a/Swiften/Parser/PayloadParsers/SecurityLabelsCatalogParser.h b/Swiften/Parser/PayloadParsers/SecurityLabelsCatalogParser.h new file mode 100644 index 0000000..799e43a --- /dev/null +++ b/Swiften/Parser/PayloadParsers/SecurityLabelsCatalogParser.h @@ -0,0 +1,32 @@ +#ifndef SWIFTEN_SecurityLabelsCatalogParser_H +#define SWIFTEN_SecurityLabelsCatalogParser_H + +#include "Swiften/Elements/SecurityLabelsCatalog.h" +#include "Swiften/Parser/GenericPayloadParser.h" + +namespace Swift { + class SecurityLabelParserFactory; + class SecurityLabelParser; + + class SecurityLabelsCatalogParser : public GenericPayloadParser<SecurityLabelsCatalog> { + public: + SecurityLabelsCatalogParser(); + ~SecurityLabelsCatalogParser(); + + virtual void handleStartElement(const String& element, const String&, const AttributeMap& attributes); + virtual void handleEndElement(const String& element, const String&); + virtual void handleCharacterData(const String& data); + + private: + enum Level { + TopLevel = 0, + PayloadLevel = 1, + LabelLevel = 2 + }; + int level_; + SecurityLabelParserFactory* labelParserFactory_; + SecurityLabelParser* labelParser_; + }; +} + +#endif diff --git a/Swiften/Parser/PayloadParsers/SecurityLabelsCatalogParserFactory.h b/Swiften/Parser/PayloadParsers/SecurityLabelsCatalogParserFactory.h new file mode 100644 index 0000000..99a310b --- /dev/null +++ b/Swiften/Parser/PayloadParsers/SecurityLabelsCatalogParserFactory.h @@ -0,0 +1,14 @@ +#ifndef SWIFTEN_SecurityLabelsCatalogParserFactory_H +#define SWIFTEN_SecurityLabelsCatalogParserFactory_H + +#include "Swiften/Parser/GenericPayloadParserFactory.h" +#include "Swiften/Parser/PayloadParsers/SecurityLabelsCatalogParser.h" + +namespace Swift { + class SecurityLabelsCatalogParserFactory : public GenericPayloadParserFactory<SecurityLabelsCatalogParser> { + public: + SecurityLabelsCatalogParserFactory() : GenericPayloadParserFactory<SecurityLabelsCatalogParser>("catalog", "urn:xmpp:sec-label:catalog:0") {} + }; +} + +#endif diff --git a/Swiften/Parser/PayloadParsers/SoftwareVersionParser.cpp b/Swiften/Parser/PayloadParsers/SoftwareVersionParser.cpp new file mode 100644 index 0000000..dae9f94 --- /dev/null +++ b/Swiften/Parser/PayloadParsers/SoftwareVersionParser.cpp @@ -0,0 +1,32 @@ +#include "Swiften/Parser/PayloadParsers/SoftwareVersionParser.h" + +namespace Swift { + +SoftwareVersionParser::SoftwareVersionParser() : level_(TopLevel) { +} + +void SoftwareVersionParser::handleStartElement(const String&, const String&, const AttributeMap&) { + ++level_; +} + +void SoftwareVersionParser::handleEndElement(const String& element, const String&) { + --level_; + if (level_ == PayloadLevel) { + if (element == "name") { + getPayloadInternal()->setName(currentText_); + } + else if (element == "version") { + getPayloadInternal()->setVersion(currentText_); + } + else if (element == "os") { + getPayloadInternal()->setOS(currentText_); + } + currentText_ = ""; + } +} + +void SoftwareVersionParser::handleCharacterData(const String& data) { + currentText_ += data; +} + +} diff --git a/Swiften/Parser/PayloadParsers/SoftwareVersionParser.h b/Swiften/Parser/PayloadParsers/SoftwareVersionParser.h new file mode 100644 index 0000000..bfffc90 --- /dev/null +++ b/Swiften/Parser/PayloadParsers/SoftwareVersionParser.h @@ -0,0 +1,26 @@ +#ifndef SWIFTEN_SoftwareVersionParser_H +#define SWIFTEN_SoftwareVersionParser_H + +#include "Swiften/Elements/SoftwareVersion.h" +#include "Swiften/Parser/GenericPayloadParser.h" + +namespace Swift { + class SoftwareVersionParser : public GenericPayloadParser<SoftwareVersion> { + public: + SoftwareVersionParser(); + + virtual void handleStartElement(const String& element, const String&, const AttributeMap& attributes); + virtual void handleEndElement(const String& element, const String&); + virtual void handleCharacterData(const String& data); + + private: + enum Level { + TopLevel = 0, + PayloadLevel = 1 + }; + int level_; + String currentText_; + }; +} + +#endif diff --git a/Swiften/Parser/PayloadParsers/SoftwareVersionParserFactory.h b/Swiften/Parser/PayloadParsers/SoftwareVersionParserFactory.h new file mode 100644 index 0000000..cb33e0b --- /dev/null +++ b/Swiften/Parser/PayloadParsers/SoftwareVersionParserFactory.h @@ -0,0 +1,14 @@ +#ifndef SWIFTEN_SoftwareVersionParserFactory_H +#define SWIFTEN_SoftwareVersionParserFactory_H + +#include "Swiften/Parser/GenericPayloadParserFactory.h" +#include "Swiften/Parser/PayloadParsers/SoftwareVersionParser.h" + +namespace Swift { + class SoftwareVersionParserFactory : public GenericPayloadParserFactory<SoftwareVersionParser> { + public: + SoftwareVersionParserFactory() : GenericPayloadParserFactory<SoftwareVersionParser>("query", "jabber:iq:version") {} + }; +} + +#endif diff --git a/Swiften/Parser/PayloadParsers/StartSessionParser.h b/Swiften/Parser/PayloadParsers/StartSessionParser.h new file mode 100644 index 0000000..059d036 --- /dev/null +++ b/Swiften/Parser/PayloadParsers/StartSessionParser.h @@ -0,0 +1,18 @@ +#ifndef SWIFTEN_StartSessionParser_H +#define SWIFTEN_StartSessionParser_H + +#include "Swiften/Elements/StartSession.h" +#include "Swiften/Parser/GenericPayloadParser.h" + +namespace Swift { + class StartSessionParser : public GenericPayloadParser<StartSession> { + public: + StartSessionParser() {} + + virtual void handleStartElement(const String&, const String&, const AttributeMap&) {} + virtual void handleEndElement(const String&, const String&) {} + virtual void handleCharacterData(const String&) {} + }; +} + +#endif diff --git a/Swiften/Parser/PayloadParsers/StartSessionParserFactory.h b/Swiften/Parser/PayloadParsers/StartSessionParserFactory.h new file mode 100644 index 0000000..5eed749 --- /dev/null +++ b/Swiften/Parser/PayloadParsers/StartSessionParserFactory.h @@ -0,0 +1,14 @@ +#ifndef SWIFTEN_StartSessionParserFactory_H +#define SWIFTEN_StartSessionParserFactory_H + +#include "Swiften/Parser/GenericPayloadParserFactory.h" +#include "Swiften/Parser/PayloadParsers/StartSessionParser.h" + +namespace Swift { + class StartSessionParserFactory : public GenericPayloadParserFactory<StartSessionParser> { + public: + StartSessionParserFactory() : GenericPayloadParserFactory<StartSessionParser>("session", "urn:ietf:params:xml:ns:xmpp-session") {} + }; +} + +#endif diff --git a/Swiften/Parser/PayloadParsers/StatusParser.cpp b/Swiften/Parser/PayloadParsers/StatusParser.cpp new file mode 100644 index 0000000..c7771b9 --- /dev/null +++ b/Swiften/Parser/PayloadParsers/StatusParser.cpp @@ -0,0 +1,23 @@ +#include "Swiften/Parser/PayloadParsers/StatusParser.h" + +namespace Swift { + +StatusParser::StatusParser() : level_(0) { +} + +void StatusParser::handleStartElement(const String&, const String&, const AttributeMap&) { + ++level_; +} + +void StatusParser::handleEndElement(const String&, const String&) { + --level_; + if (level_ == 0) { + getPayloadInternal()->setText(text_); + } +} + +void StatusParser::handleCharacterData(const String& data) { + text_ += data; +} + +} diff --git a/Swiften/Parser/PayloadParsers/StatusParser.h b/Swiften/Parser/PayloadParsers/StatusParser.h new file mode 100644 index 0000000..36ae094 --- /dev/null +++ b/Swiften/Parser/PayloadParsers/StatusParser.h @@ -0,0 +1,22 @@ +#ifndef SWIFTEN_StatusParser_H +#define SWIFTEN_StatusParser_H + +#include "Swiften/Elements/Status.h" +#include "Swiften/Parser/GenericPayloadParser.h" + +namespace Swift { + class StatusParser : public GenericPayloadParser<Status> { + public: + StatusParser(); + + virtual void handleStartElement(const String& element, const String&, const AttributeMap& attributes); + virtual void handleEndElement(const String& element, const String&); + virtual void handleCharacterData(const String& data); + + private: + int level_; + String text_; + }; +} + +#endif diff --git a/Swiften/Parser/PayloadParsers/StatusParserFactory.h b/Swiften/Parser/PayloadParsers/StatusParserFactory.h new file mode 100644 index 0000000..72af5a9 --- /dev/null +++ b/Swiften/Parser/PayloadParsers/StatusParserFactory.h @@ -0,0 +1,14 @@ +#ifndef SWIFTEN_StatusParserFactory_H +#define SWIFTEN_StatusParserFactory_H + +#include "Swiften/Parser/GenericPayloadParserFactory.h" +#include "Swiften/Parser/PayloadParsers/StatusParser.h" + +namespace Swift { + class StatusParserFactory : public GenericPayloadParserFactory<StatusParser> { + public: + StatusParserFactory() : GenericPayloadParserFactory<StatusParser>("status") {} + }; +} + +#endif diff --git a/Swiften/Parser/PayloadParsers/StatusShowParser.cpp b/Swiften/Parser/PayloadParsers/StatusShowParser.cpp new file mode 100644 index 0000000..c3719af --- /dev/null +++ b/Swiften/Parser/PayloadParsers/StatusShowParser.cpp @@ -0,0 +1,37 @@ +#include "Swiften/Parser/PayloadParsers/StatusShowParser.h" + +namespace Swift { + +StatusShowParser::StatusShowParser() : level_(0) { +} + +void StatusShowParser::handleStartElement(const String&, const String&, const AttributeMap&) { + ++level_; +} + +void StatusShowParser::handleEndElement(const String&, const String&) { + --level_; + if (level_ == 0) { + if (text_ == "away") { + getPayloadInternal()->setType(StatusShow::Away); + } + else if (text_ == "chat") { + getPayloadInternal()->setType(StatusShow::FFC); + } + else if (text_ == "xa") { + getPayloadInternal()->setType(StatusShow::XA); + } + else if (text_ == "dnd") { + getPayloadInternal()->setType(StatusShow::DND); + } + else { + getPayloadInternal()->setType(StatusShow::Online); + } + } +} + +void StatusShowParser::handleCharacterData(const String& data) { + text_ += data; +} + +} diff --git a/Swiften/Parser/PayloadParsers/StatusShowParser.h b/Swiften/Parser/PayloadParsers/StatusShowParser.h new file mode 100644 index 0000000..a8ddb09 --- /dev/null +++ b/Swiften/Parser/PayloadParsers/StatusShowParser.h @@ -0,0 +1,22 @@ +#ifndef SWIFTEN_StatusShowParser_H +#define SWIFTEN_StatusShowParser_H + +#include "Swiften/Elements/StatusShow.h" +#include "Swiften/Parser/GenericPayloadParser.h" + +namespace Swift { + class StatusShowParser : public GenericPayloadParser<StatusShow> { + public: + StatusShowParser(); + + virtual void handleStartElement(const String& element, const String&, const AttributeMap& attributes); + virtual void handleEndElement(const String& element, const String&); + virtual void handleCharacterData(const String& data); + + private: + int level_; + String text_; + }; +} + +#endif diff --git a/Swiften/Parser/PayloadParsers/StatusShowParserFactory.h b/Swiften/Parser/PayloadParsers/StatusShowParserFactory.h new file mode 100644 index 0000000..2ddc190 --- /dev/null +++ b/Swiften/Parser/PayloadParsers/StatusShowParserFactory.h @@ -0,0 +1,14 @@ +#ifndef SWIFTEN_StatusShowParserFactory_H +#define SWIFTEN_StatusShowParserFactory_H + +#include "Swiften/Parser/GenericPayloadParserFactory.h" +#include "Swiften/Parser/PayloadParsers/StatusShowParser.h" + +namespace Swift { + class StatusShowParserFactory : public GenericPayloadParserFactory<StatusShowParser> { + public: + StatusShowParserFactory() : GenericPayloadParserFactory<StatusShowParser>("show") {} + }; +} + +#endif diff --git a/Swiften/Parser/PayloadParsers/StorageParser.cpp b/Swiften/Parser/PayloadParsers/StorageParser.cpp new file mode 100644 index 0000000..3eab15e --- /dev/null +++ b/Swiften/Parser/PayloadParsers/StorageParser.cpp @@ -0,0 +1,49 @@ +#include "Swiften/Parser/PayloadParsers/StorageParser.h" + +#include <cassert> + +namespace Swift { + +StorageParser::StorageParser() : level(TopLevel) { +} + +void StorageParser::handleStartElement(const String& element, const String&, const AttributeMap& attributes) { + if (level == BookmarkLevel) { + if (element == "conference") { + assert(!conference); + conference = Storage::Conference(); + conference->autoJoin = attributes.getBoolAttribute("autojoin", false); + conference->jid = JID(attributes.getAttribute("jid")); + conference->name = attributes.getAttribute("name"); + } + } + else if (level == DetailLevel) { + currentText = ""; + } + ++level; +} + +void StorageParser::handleEndElement(const String& element, const String&) { + --level; + if (level == BookmarkLevel) { + if (element == "conference") { + assert(conference); + getPayloadInternal()->addConference(*conference); + conference.reset(); + } + } + else if (level == DetailLevel && conference) { + if (element == "nick") { + conference->nick = currentText; + } + else if (element == "password") { + conference->password = currentText; + } + } +} + +void StorageParser::handleCharacterData(const String& data) { + currentText += data; +} + +} diff --git a/Swiften/Parser/PayloadParsers/StorageParser.h b/Swiften/Parser/PayloadParsers/StorageParser.h new file mode 100644 index 0000000..ad0ab4d --- /dev/null +++ b/Swiften/Parser/PayloadParsers/StorageParser.h @@ -0,0 +1,27 @@ +#pragma once + +#include <boost/optional.hpp> + +#include "Swiften/Elements/Storage.h" +#include "Swiften/Parser/GenericPayloadParser.h" + +namespace Swift { + class StorageParser : public GenericPayloadParser<Storage> { + public: + StorageParser(); + + virtual void handleStartElement(const String& element, const String&, const AttributeMap& attributes); + virtual void handleEndElement(const String& element, const String&); + virtual void handleCharacterData(const String& data); + + private: + enum Level { + TopLevel = 0, + BookmarkLevel = 1, + DetailLevel = 2 + }; + int level; + String currentText; + boost::optional<Storage::Conference> conference; + }; +} diff --git a/Swiften/Parser/PayloadParsers/StorageParserFactory.h b/Swiften/Parser/PayloadParsers/StorageParserFactory.h new file mode 100644 index 0000000..147d178 --- /dev/null +++ b/Swiften/Parser/PayloadParsers/StorageParserFactory.h @@ -0,0 +1,11 @@ +#pragma once + +#include "Swiften/Parser/GenericPayloadParserFactory.h" +#include "Swiften/Parser/PayloadParsers/StorageParser.h" + +namespace Swift { + class StorageParserFactory : public GenericPayloadParserFactory<StorageParser> { + public: + StorageParserFactory() : GenericPayloadParserFactory<StorageParser>("storage", "storage:bookmarks") {} + }; +} diff --git a/Swiften/Parser/PayloadParsers/UnitTest/BodyParserTest.cpp b/Swiften/Parser/PayloadParsers/UnitTest/BodyParserTest.cpp new file mode 100644 index 0000000..2fc6180 --- /dev/null +++ b/Swiften/Parser/PayloadParsers/UnitTest/BodyParserTest.cpp @@ -0,0 +1,28 @@ +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> + +#include "Swiften/Parser/PayloadParsers/BodyParser.h" +#include "Swiften/Parser/PayloadParsers/UnitTest/PayloadsParserTester.h" + +using namespace Swift; + +class BodyParserTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(BodyParserTest); + CPPUNIT_TEST(testParse); + CPPUNIT_TEST_SUITE_END(); + + public: + BodyParserTest() {} + + void testParse() { + PayloadsParserTester parser; + + CPPUNIT_ASSERT(parser.parse("<body>foo<baz>bar</baz>fum</body>")); + + Body* payload = dynamic_cast<Body*>(parser.getPayload().get()); + CPPUNIT_ASSERT_EQUAL(String("foobarfum"), payload->getText()); + } +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(BodyParserTest); diff --git a/Swiften/Parser/PayloadParsers/UnitTest/DiscoInfoParserTest.cpp b/Swiften/Parser/PayloadParsers/UnitTest/DiscoInfoParserTest.cpp new file mode 100644 index 0000000..49b706e --- /dev/null +++ b/Swiften/Parser/PayloadParsers/UnitTest/DiscoInfoParserTest.cpp @@ -0,0 +1,47 @@ +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> + +#include "Swiften/Parser/PayloadParsers/DiscoInfoParser.h" +#include "Swiften/Parser/PayloadParsers/UnitTest/PayloadsParserTester.h" + +using namespace Swift; + +class DiscoInfoParserTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(DiscoInfoParserTest); + CPPUNIT_TEST(testParse); + CPPUNIT_TEST_SUITE_END(); + + public: + DiscoInfoParserTest() {} + + void testParse() { + PayloadsParserTester parser; + + CPPUNIT_ASSERT(parser.parse( + "<query xmlns=\"http://jabber.org/protocol/disco#info\">" + "<identity name=\"Swift\" category=\"client\" type=\"pc\" xml:lang=\"en\"/>" + "<identity name=\"Vlug\" category=\"client\" type=\"pc\" xml:lang=\"nl\"/>" + "<feature var=\"foo-feature\"/>" + "<feature var=\"bar-feature\"/>" + "<feature var=\"baz-feature\"/>" + "</query>")); + + DiscoInfo* payload = dynamic_cast<DiscoInfo*>(parser.getPayload().get()); + CPPUNIT_ASSERT_EQUAL(2, static_cast<int>(payload->getIdentities().size())); + CPPUNIT_ASSERT_EQUAL(String("Swift"), payload->getIdentities()[0].getName()); + CPPUNIT_ASSERT_EQUAL(String("pc"), payload->getIdentities()[0].getType()); + CPPUNIT_ASSERT_EQUAL(String("client"), payload->getIdentities()[0].getCategory()); + CPPUNIT_ASSERT_EQUAL(String("en"), payload->getIdentities()[0].getLanguage()); + CPPUNIT_ASSERT_EQUAL(String("Vlug"), payload->getIdentities()[1].getName()); + CPPUNIT_ASSERT_EQUAL(String("pc"), payload->getIdentities()[1].getType()); + CPPUNIT_ASSERT_EQUAL(String("client"), payload->getIdentities()[1].getCategory()); + CPPUNIT_ASSERT_EQUAL(String("nl"), payload->getIdentities()[1].getLanguage()); + CPPUNIT_ASSERT_EQUAL(3, static_cast<int>(payload->getFeatures().size())); + CPPUNIT_ASSERT_EQUAL(String("foo-feature"), payload->getFeatures()[0]); + CPPUNIT_ASSERT_EQUAL(String("bar-feature"), payload->getFeatures()[1]); + CPPUNIT_ASSERT_EQUAL(String("baz-feature"), payload->getFeatures()[2]); + } +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(DiscoInfoParserTest); diff --git a/Swiften/Parser/PayloadParsers/UnitTest/ErrorParserTest.cpp b/Swiften/Parser/PayloadParsers/UnitTest/ErrorParserTest.cpp new file mode 100644 index 0000000..dcd3172 --- /dev/null +++ b/Swiften/Parser/PayloadParsers/UnitTest/ErrorParserTest.cpp @@ -0,0 +1,34 @@ +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> + +#include "Swiften/Parser/PayloadParsers/ErrorParser.h" +#include "Swiften/Parser/PayloadParsers/UnitTest/PayloadsParserTester.h" + +using namespace Swift; + +class ErrorParserTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(ErrorParserTest); + CPPUNIT_TEST(testParse); + CPPUNIT_TEST_SUITE_END(); + + public: + ErrorParserTest() {} + + void testParse() { + PayloadsParserTester parser; + + CPPUNIT_ASSERT(parser.parse( + "<error type=\"modify\">" + "<bad-request xmlns=\"urn:ietf:params:xml:ns:xmpp-stanzas\"/>" + "<text xmlns=\"urn:ietf:params:xml:ns:xmpp-stanzas\">boo</text>" + "</error>")); + + ErrorPayload* payload = dynamic_cast<ErrorPayload*>(parser.getPayload().get()); + CPPUNIT_ASSERT_EQUAL(ErrorPayload::BadRequest, payload->getCondition()); + CPPUNIT_ASSERT_EQUAL(ErrorPayload::Modify, payload->getType()); + CPPUNIT_ASSERT_EQUAL(String("boo"), payload->getText()); + } +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(ErrorParserTest); diff --git a/Swiften/Parser/PayloadParsers/UnitTest/PayloadParserTester.h b/Swiften/Parser/PayloadParsers/UnitTest/PayloadParserTester.h new file mode 100644 index 0000000..20a5feb --- /dev/null +++ b/Swiften/Parser/PayloadParsers/UnitTest/PayloadParserTester.h @@ -0,0 +1,8 @@ +#pragma once + +#include "Swiften/Parser/UnitTest/ParserTester.h" +#include "Swiften/Parser/PayloadParser.h" + +namespace Swift { + typedef ParserTester<PayloadParser> PayloadParserTester; +} diff --git a/Swiften/Parser/PayloadParsers/UnitTest/PayloadsParserTester.h b/Swiften/Parser/PayloadParsers/UnitTest/PayloadsParserTester.h new file mode 100644 index 0000000..ac167cf --- /dev/null +++ b/Swiften/Parser/PayloadParsers/UnitTest/PayloadsParserTester.h @@ -0,0 +1,55 @@ +#pragma once + +#include <cppunit/extensions/HelperMacros.h> + +#include "Swiften/Parser/PayloadParsers/FullPayloadParserFactoryCollection.h" +#include "Swiften/Parser/XMLParser.h" +#include "Swiften/Parser/XMLParserClient.h" +#include "Swiften/Parser/PlatformXMLParserFactory.h" + +namespace Swift { + class PayloadsParserTester : public XMLParserClient { + public: + PayloadsParserTester() : level(0) { + xmlParser = PlatformXMLParserFactory().createXMLParser(this); + } + + ~PayloadsParserTester() { + delete xmlParser; + } + + bool parse(const String& data) { + return xmlParser->parse(data); + } + + virtual void handleStartElement(const String& element, const String& ns, const AttributeMap& attributes) { + if (level == 0) { + CPPUNIT_ASSERT(!payloadParser.get()); + PayloadParserFactory* payloadParserFactory = factories.getPayloadParserFactory(element, ns, attributes); + CPPUNIT_ASSERT(payloadParserFactory); + payloadParser.reset(payloadParserFactory->createPayloadParser()); + } + payloadParser->handleStartElement(element, ns, attributes); + level++; + } + + virtual void handleEndElement(const String& element, const String& ns) { + level--; + payloadParser->handleEndElement(element, ns); + } + + virtual void handleCharacterData(const String& data) { + payloadParser->handleCharacterData(data); + } + + boost::shared_ptr<Payload> getPayload() const { + return payloadParser->getPayload(); + } + + private: + XMLParser* xmlParser; + FullPayloadParserFactoryCollection factories; + std::auto_ptr<PayloadParser> payloadParser; + int level; + }; +} diff --git a/Swiften/Parser/PayloadParsers/UnitTest/PriorityParserTest.cpp b/Swiften/Parser/PayloadParsers/UnitTest/PriorityParserTest.cpp new file mode 100644 index 0000000..acea437 --- /dev/null +++ b/Swiften/Parser/PayloadParsers/UnitTest/PriorityParserTest.cpp @@ -0,0 +1,28 @@ +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> + +#include "Swiften/Parser/PayloadParsers/PriorityParser.h" +#include "Swiften/Parser/PayloadParsers/UnitTest/PayloadsParserTester.h" + +using namespace Swift; + +class PriorityParserTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(PriorityParserTest); + CPPUNIT_TEST(testParse); + CPPUNIT_TEST_SUITE_END(); + + public: + PriorityParserTest() {} + + void testParse() { + PayloadsParserTester parser; + + CPPUNIT_ASSERT(parser.parse("<priority>-120</priority>")); + + Priority* payload = dynamic_cast<Priority*>(parser.getPayload().get()); + CPPUNIT_ASSERT_EQUAL(-120, payload->getPriority()); + } +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(PriorityParserTest); diff --git a/Swiften/Parser/PayloadParsers/UnitTest/PrivateStorageParserTest.cpp b/Swiften/Parser/PayloadParsers/UnitTest/PrivateStorageParserTest.cpp new file mode 100644 index 0000000..e820083 --- /dev/null +++ b/Swiften/Parser/PayloadParsers/UnitTest/PrivateStorageParserTest.cpp @@ -0,0 +1,89 @@ +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> + +#include "Swiften/Elements/Storage.h" +#include "Swiften/Parser/PayloadParsers/PrivateStorageParser.h" +#include "Swiften/Parser/PayloadParsers/UnitTest/PayloadsParserTester.h" +#include "Swiften/Parser/PayloadParsers/UnitTest/PayloadParserTester.h" + +using namespace Swift; + +class PrivateStorageParserTest : public CppUnit::TestFixture { + CPPUNIT_TEST_SUITE(PrivateStorageParserTest); + CPPUNIT_TEST(testParse); + CPPUNIT_TEST(testParse_NoPayload); + CPPUNIT_TEST(testParse_MultiplePayloads); + CPPUNIT_TEST_SUITE_END(); + + public: + PrivateStorageParserTest() {} + + void testParse() { + PayloadsParserTester parser; + + CPPUNIT_ASSERT(parser.parse( + "<query xmlns='jabber:iq:private'>" + "<storage xmlns='storage:bookmarks'>" + "<conference name='Swift' jid='swift@rooms.swift.im'>" + "<nick>Alice</nick>" + "</conference>" + "</storage>" + "</query>")); + + boost::shared_ptr<PrivateStorage> payload = boost::dynamic_pointer_cast<PrivateStorage>(parser.getPayload()); + CPPUNIT_ASSERT(payload); + boost::shared_ptr<Storage> storage = boost::dynamic_pointer_cast<Storage>(payload->getPayload()); + CPPUNIT_ASSERT(storage); + CPPUNIT_ASSERT_EQUAL(String("Alice"), storage->getConferences()[0].nick); + CPPUNIT_ASSERT_EQUAL(JID("swift@rooms.swift.im"), storage->getConferences()[0].jid); + } + + void testParse_NoPayload() { + PayloadsParserTester parser; + + CPPUNIT_ASSERT(parser.parse("<query xmlns='jabber:iq:private'/>")); + + boost::shared_ptr<PrivateStorage> payload = boost::dynamic_pointer_cast<PrivateStorage>(parser.getPayload()); + CPPUNIT_ASSERT(payload); + CPPUNIT_ASSERT(!payload->getPayload()); + } + + void testParse_MultiplePayloads() { + PayloadsParserTester parser; + + CPPUNIT_ASSERT(parser.parse( + "<query xmlns='jabber:iq:private'>" + "<storage xmlns='storage:bookmarks'>" + "<conference name='Swift' jid='swift@rooms.swift.im'>" + "<nick>Alice</nick>" + "</conference>" + "</storage>" + "<storage xmlns='storage:bookmarks'>" + "<conference name='Swift' jid='swift@rooms.swift.im'>" + "<nick>Rabbit</nick>" + "</conference>" + "</storage>" + "</query>")); + + boost::shared_ptr<PrivateStorage> payload = boost::dynamic_pointer_cast<PrivateStorage>(parser.getPayload()); + CPPUNIT_ASSERT(payload); + boost::shared_ptr<Storage> storage = boost::dynamic_pointer_cast<Storage>(payload->getPayload()); + CPPUNIT_ASSERT(storage); + CPPUNIT_ASSERT_EQUAL(String("Rabbit"), storage->getConferences()[0].nick); + } + + void testParse_UnsupportedPayload() { + PayloadParserFactoryCollection factories; + PrivateStorageParser testling(&factories); + PayloadParserTester parser(&testling); + + CPPUNIT_ASSERT(parser.parse( + "<query xmlns='jabber:iq:private'>" + "<foo>Bar</foo>" + "</query>")); + + CPPUNIT_ASSERT(!boost::dynamic_pointer_cast<PrivateStorage>(testling.getPayload())->getPayload()); + } +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(PrivateStorageParserTest); diff --git a/Swiften/Parser/PayloadParsers/UnitTest/RawXMLPayloadParserTest.cpp b/Swiften/Parser/PayloadParsers/UnitTest/RawXMLPayloadParserTest.cpp new file mode 100644 index 0000000..81cc207 --- /dev/null +++ b/Swiften/Parser/PayloadParsers/UnitTest/RawXMLPayloadParserTest.cpp @@ -0,0 +1,34 @@ +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> + +#include "Swiften/Parser/PayloadParsers/RawXMLPayloadParser.h" +#include "Swiften/Parser/PayloadParsers/UnitTest/PayloadParserTester.h" + +using namespace Swift; + +class RawXMLPayloadParserTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(RawXMLPayloadParserTest); + CPPUNIT_TEST(testParse); + CPPUNIT_TEST_SUITE_END(); + + public: + RawXMLPayloadParserTest() {} + + void testParse() { + RawXMLPayloadParser testling; + PayloadParserTester parser(&testling); + + String xml = + "<foo foo-attr=\"foo-val\" xmlns=\"ns:foo\">" + "<bar bar-attr=\"bar-val\" xmlns=\"ns:bar\"/>" + "<baz baz-attr=\"baz-val\" xmlns=\"ns:baz\"/>" + "</foo>"; + CPPUNIT_ASSERT(parser.parse(xml)); + + RawXMLPayload* payload = dynamic_cast<RawXMLPayload*>(testling.getPayload().get()); + CPPUNIT_ASSERT_EQUAL(xml, payload->getRawXML()); + } +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(RawXMLPayloadParserTest); diff --git a/Swiften/Parser/PayloadParsers/UnitTest/ResourceBindParserTest.cpp b/Swiften/Parser/PayloadParsers/UnitTest/ResourceBindParserTest.cpp new file mode 100644 index 0000000..b1b61ef --- /dev/null +++ b/Swiften/Parser/PayloadParsers/UnitTest/ResourceBindParserTest.cpp @@ -0,0 +1,38 @@ +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> + +#include "Swiften/Parser/PayloadParsers/ResourceBindParser.h" +#include "Swiften/Parser/PayloadParsers/UnitTest/PayloadsParserTester.h" + +using namespace Swift; + +class ResourceBindParserTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(ResourceBindParserTest); + CPPUNIT_TEST(testParse_JID); + CPPUNIT_TEST(testParse_Resource); + CPPUNIT_TEST_SUITE_END(); + + public: + ResourceBindParserTest() {} + + void testParse_JID() { + PayloadsParserTester parser; + + CPPUNIT_ASSERT(parser.parse("<bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'><jid>somenode@example.com/someresource</jid></bind>")); + + ResourceBind* payload = dynamic_cast<ResourceBind*>(parser.getPayload().get()); + CPPUNIT_ASSERT_EQUAL(JID("somenode@example.com/someresource"), payload->getJID()); + } + + void testParse_Resource() { + PayloadsParserTester parser; + + CPPUNIT_ASSERT(parser.parse("<bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'><resource>someresource</resource></bind>")); + + ResourceBind* payload = dynamic_cast<ResourceBind*>(parser.getPayload().get()); + CPPUNIT_ASSERT_EQUAL(String("someresource"), payload->getResource()); + } +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(ResourceBindParserTest); diff --git a/Swiften/Parser/PayloadParsers/UnitTest/RosterParserTest.cpp b/Swiften/Parser/PayloadParsers/UnitTest/RosterParserTest.cpp new file mode 100644 index 0000000..4c8db07 --- /dev/null +++ b/Swiften/Parser/PayloadParsers/UnitTest/RosterParserTest.cpp @@ -0,0 +1,50 @@ +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> + +#include "Swiften/Parser/PayloadParsers/RosterParser.h" +#include "Swiften/Parser/PayloadParsers/UnitTest/PayloadsParserTester.h" + +using namespace Swift; + +class RosterParserTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(RosterParserTest); + CPPUNIT_TEST(testParse); + CPPUNIT_TEST_SUITE_END(); + + public: + RosterParserTest() {} + + void testParse() { + PayloadsParserTester parser; + parser.parse( + "<query xmlns='jabber:iq:roster'>" + " <item jid='foo@bar.com' name='Foo @ Bar' subscription='from' ask='subscribe'>" + " <group>Group 1</group>" + " <group>Group 2</group>" + " </item>" + " <item jid='baz@blo.com' name='Baz'/>" + "</query>"); + + RosterPayload* payload = dynamic_cast<RosterPayload*>(parser.getPayload().get()); + const RosterPayload::RosterItemPayloads& items = payload->getItems(); + + CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(2), items.size()); + + CPPUNIT_ASSERT_EQUAL(JID("foo@bar.com"), items[0].getJID()); + CPPUNIT_ASSERT_EQUAL(String("Foo @ Bar"), items[0].getName()); + CPPUNIT_ASSERT_EQUAL(RosterItemPayload::From, items[0].getSubscription()); + CPPUNIT_ASSERT(items[0].getSubscriptionRequested()); + CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(2), items[0].getGroups().size()); + CPPUNIT_ASSERT_EQUAL(String("Group 1"), items[0].getGroups()[0]); + CPPUNIT_ASSERT_EQUAL(String("Group 2"), items[0].getGroups()[1]); + + CPPUNIT_ASSERT_EQUAL(JID("baz@blo.com"), items[1].getJID()); + CPPUNIT_ASSERT_EQUAL(String("Baz"), items[1].getName()); + CPPUNIT_ASSERT_EQUAL(RosterItemPayload::None, items[1].getSubscription()); + CPPUNIT_ASSERT(!items[1].getSubscriptionRequested()); + CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(0), items[1].getGroups().size()); + } +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(RosterParserTest); diff --git a/Swiften/Parser/PayloadParsers/UnitTest/SecurityLabelParserTest.cpp b/Swiften/Parser/PayloadParsers/UnitTest/SecurityLabelParserTest.cpp new file mode 100644 index 0000000..8272bee --- /dev/null +++ b/Swiften/Parser/PayloadParsers/UnitTest/SecurityLabelParserTest.cpp @@ -0,0 +1,45 @@ +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> + +#include "Swiften/Parser/PayloadParsers/SecurityLabelParser.h" +#include "Swiften/Parser/PayloadParsers/UnitTest/PayloadsParserTester.h" + +using namespace Swift; + +class SecurityLabelParserTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(SecurityLabelParserTest); + CPPUNIT_TEST(testParse); + CPPUNIT_TEST_SUITE_END(); + + public: + SecurityLabelParserTest() {} + + void testParse() { + PayloadsParserTester parser; + + CPPUNIT_ASSERT(parser.parse( + "<securitylabel xmlns=\"urn:xmpp:sec-label:0\">" + "<displaymarking fgcolor=\"black\" bgcolor=\"red\">SECRET</displaymarking>" + "<label>" + "<esssecuritylabel xmlns=\"urn:xmpp:sec-label:ess:0\">MQYCAQQGASk=</esssecuritylabel>" + "</label>" + "<equivalentlabel>" + "<icismlabel xmlns=\"http://example.gov/IC-ISM/0\" classification=\"S\" ownerProducer=\"USA\" disseminationControls=\"FOUO\"/>" + "</equivalentlabel>" + "<equivalentlabel>" + "<esssecuritylabel xmlns=\"urn:xmpp:sec-label:ess:0\">MRUCAgD9DA9BcXVhIChvYnNvbGV0ZSk=</esssecuritylabel>" + "</equivalentlabel>" + "</securitylabel>")); + + SecurityLabel* payload = dynamic_cast<SecurityLabel*>(parser.getPayload().get()); + CPPUNIT_ASSERT_EQUAL(String("SECRET"), payload->getDisplayMarking()); + CPPUNIT_ASSERT_EQUAL(String("black"), payload->getForegroundColor()); + CPPUNIT_ASSERT_EQUAL(String("red"), payload->getBackgroundColor()); + CPPUNIT_ASSERT_EQUAL(String("<esssecuritylabel xmlns=\"urn:xmpp:sec-label:ess:0\">MQYCAQQGASk=</esssecuritylabel>"), payload->getLabel()); + CPPUNIT_ASSERT_EQUAL(String("<icismlabel classification=\"S\" disseminationControls=\"FOUO\" ownerProducer=\"USA\" xmlns=\"http://example.gov/IC-ISM/0\"/>"), payload->getEquivalentLabels()[0]); + CPPUNIT_ASSERT_EQUAL(String("<esssecuritylabel xmlns=\"urn:xmpp:sec-label:ess:0\">MRUCAgD9DA9BcXVhIChvYnNvbGV0ZSk=</esssecuritylabel>"), payload->getEquivalentLabels()[1]); + } +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(SecurityLabelParserTest); diff --git a/Swiften/Parser/PayloadParsers/UnitTest/SecurityLabelsCatalogParserTest.cpp b/Swiften/Parser/PayloadParsers/UnitTest/SecurityLabelsCatalogParserTest.cpp new file mode 100644 index 0000000..b2378b6 --- /dev/null +++ b/Swiften/Parser/PayloadParsers/UnitTest/SecurityLabelsCatalogParserTest.cpp @@ -0,0 +1,45 @@ +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> + +#include "Swiften/Parser/PayloadParsers/SecurityLabelsCatalogParser.h" +#include "Swiften/Parser/PayloadParsers/UnitTest/PayloadsParserTester.h" + +using namespace Swift; + +class SecurityLabelsCatalogParserTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(SecurityLabelsCatalogParserTest); + CPPUNIT_TEST(testParse); + CPPUNIT_TEST_SUITE_END(); + + public: + SecurityLabelsCatalogParserTest() {} + + void testParse() { + PayloadsParserTester parser; + + CPPUNIT_ASSERT(parser.parse( + "<catalog desc=\"an example set of labels\" name=\"Default\" to=\"example.com\" xmlns=\"urn:xmpp:sec-label:catalog:0\">" + "<securitylabel xmlns=\"urn:xmpp:sec-label:0\">" + "<displaymarking bgcolor=\"red\" fgcolor=\"black\">SECRET</displaymarking>" + "<label><esssecuritylabel xmlns=\"urn:xmpp:sec-label:ess:0\">MQYCAQQGASk=</esssecuritylabel></label>" + "</securitylabel>" + "<securitylabel xmlns=\"urn:xmpp:sec-label:0\">" + "<displaymarking bgcolor=\"navy\" fgcolor=\"black\">CONFIDENTIAL</displaymarking>" + "<label><esssecuritylabel xmlns=\"urn:xmpp:sec-label:ess:0\">MQMGASk=</esssecuritylabel></label>" + "</securitylabel>" + "</catalog>")); + + SecurityLabelsCatalog* payload = dynamic_cast<SecurityLabelsCatalog*>(parser.getPayload().get()); + CPPUNIT_ASSERT_EQUAL(String("Default"), payload->getName()); + CPPUNIT_ASSERT_EQUAL(String("an example set of labels"), payload->getDescription()); + CPPUNIT_ASSERT_EQUAL(JID("example.com"), payload->getTo()); + CPPUNIT_ASSERT_EQUAL(2, static_cast<int>(payload->getLabels().size())); + CPPUNIT_ASSERT_EQUAL(String("SECRET"), payload->getLabels()[0].getDisplayMarking()); + CPPUNIT_ASSERT_EQUAL(String("<esssecuritylabel xmlns=\"urn:xmpp:sec-label:ess:0\">MQYCAQQGASk=</esssecuritylabel>"), payload->getLabels()[0].getLabel()); + CPPUNIT_ASSERT_EQUAL(String("CONFIDENTIAL"), payload->getLabels()[1].getDisplayMarking()); + CPPUNIT_ASSERT_EQUAL(String("<esssecuritylabel xmlns=\"urn:xmpp:sec-label:ess:0\">MQMGASk=</esssecuritylabel>"), payload->getLabels()[1].getLabel()); + } +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(SecurityLabelsCatalogParserTest); diff --git a/Swiften/Parser/PayloadParsers/UnitTest/SoftwareVersionParserTest.cpp b/Swiften/Parser/PayloadParsers/UnitTest/SoftwareVersionParserTest.cpp new file mode 100644 index 0000000..eeaa8d9 --- /dev/null +++ b/Swiften/Parser/PayloadParsers/UnitTest/SoftwareVersionParserTest.cpp @@ -0,0 +1,35 @@ +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> + +#include "Swiften/Parser/PayloadParsers/SoftwareVersionParser.h" +#include "Swiften/Parser/PayloadParsers/UnitTest/PayloadsParserTester.h" + +using namespace Swift; + +class SoftwareVersionParserTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(SoftwareVersionParserTest); + CPPUNIT_TEST(testParse); + CPPUNIT_TEST_SUITE_END(); + + public: + SoftwareVersionParserTest() {} + + void testParse() { + PayloadsParserTester parser; + + CPPUNIT_ASSERT(parser.parse( + "<query xmlns=\"jabber:iq:version\">" + "<name>myclient</name>" + "<version>1.0</version>" + "<os>Mac OS X</os>" + "</query>")); + + SoftwareVersion* payload = dynamic_cast<SoftwareVersion*>(parser.getPayload().get()); + CPPUNIT_ASSERT_EQUAL(String("myclient"), payload->getName()); + CPPUNIT_ASSERT_EQUAL(String("1.0"), payload->getVersion()); + CPPUNIT_ASSERT_EQUAL(String("Mac OS X"), payload->getOS()); + } +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(SoftwareVersionParserTest); diff --git a/Swiften/Parser/PayloadParsers/UnitTest/StatusParserTest.cpp b/Swiften/Parser/PayloadParsers/UnitTest/StatusParserTest.cpp new file mode 100644 index 0000000..71d5b0b --- /dev/null +++ b/Swiften/Parser/PayloadParsers/UnitTest/StatusParserTest.cpp @@ -0,0 +1,28 @@ +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> + +#include "Swiften/Parser/PayloadParsers/StatusParser.h" +#include "Swiften/Parser/PayloadParsers/UnitTest/PayloadsParserTester.h" + +using namespace Swift; + +class StatusParserTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(StatusParserTest); + CPPUNIT_TEST(testParse); + CPPUNIT_TEST_SUITE_END(); + + public: + StatusParserTest() {} + + void testParse() { + PayloadsParserTester parser; + + CPPUNIT_ASSERT(parser.parse("<status>foo<baz>bar</baz>fum</status>")); + + Status* payload = dynamic_cast<Status*>(parser.getPayload().get()); + CPPUNIT_ASSERT_EQUAL(String("foobarfum"), payload->getText()); + } +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(StatusParserTest); diff --git a/Swiften/Parser/PayloadParsers/UnitTest/StatusShowParserTest.cpp b/Swiften/Parser/PayloadParsers/UnitTest/StatusShowParserTest.cpp new file mode 100644 index 0000000..d45e98b --- /dev/null +++ b/Swiften/Parser/PayloadParsers/UnitTest/StatusShowParserTest.cpp @@ -0,0 +1,68 @@ +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> + +#include "Swiften/Parser/PayloadParsers/StatusShowParser.h" +#include "Swiften/Parser/PayloadParsers/UnitTest/PayloadsParserTester.h" + +using namespace Swift; + +class StatusShowParserTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(StatusShowParserTest); + CPPUNIT_TEST(testParse_Invalid); + CPPUNIT_TEST(testParse_Away); + CPPUNIT_TEST(testParse_FFC); + CPPUNIT_TEST(testParse_XA); + CPPUNIT_TEST(testParse_DND); + CPPUNIT_TEST_SUITE_END(); + + public: + StatusShowParserTest() {} + + void testParse_Invalid() { + PayloadsParserTester parser; + + CPPUNIT_ASSERT(parser.parse("<show>invalid</show>")); + + StatusShow* payload = dynamic_cast<StatusShow*>(parser.getPayload().get()); + CPPUNIT_ASSERT(StatusShow::Online == payload->getType()); + } + + void testParse_Away() { + PayloadsParserTester parser; + + CPPUNIT_ASSERT(parser.parse("<show>away</show>")); + + StatusShow* payload = dynamic_cast<StatusShow*>(parser.getPayload().get()); + CPPUNIT_ASSERT(StatusShow::Away == payload->getType()); + } + + void testParse_FFC() { + PayloadsParserTester parser; + + CPPUNIT_ASSERT(parser.parse("<show>chat</show>")); + + StatusShow* payload = dynamic_cast<StatusShow*>(parser.getPayload().get()); + CPPUNIT_ASSERT(StatusShow::FFC == payload->getType()); + } + + void testParse_XA() { + PayloadsParserTester parser; + + CPPUNIT_ASSERT(parser.parse("<show>xa</show>")); + + StatusShow* payload = dynamic_cast<StatusShow*>(parser.getPayload().get()); + CPPUNIT_ASSERT(StatusShow::XA == payload->getType()); + } + + void testParse_DND() { + PayloadsParserTester parser; + + CPPUNIT_ASSERT(parser.parse("<show>dnd</show>")); + + StatusShow* payload = dynamic_cast<StatusShow*>(parser.getPayload().get()); + CPPUNIT_ASSERT(StatusShow::DND == payload->getType()); + } +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(StatusShowParserTest); diff --git a/Swiften/Parser/PayloadParsers/UnitTest/StorageParserTest.cpp b/Swiften/Parser/PayloadParsers/UnitTest/StorageParserTest.cpp new file mode 100644 index 0000000..3fbf27a --- /dev/null +++ b/Swiften/Parser/PayloadParsers/UnitTest/StorageParserTest.cpp @@ -0,0 +1,64 @@ +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> + +#include "Swiften/Parser/PayloadParsers/StorageParser.h" +#include "Swiften/Parser/PayloadParsers/UnitTest/PayloadsParserTester.h" + +using namespace Swift; + +class StorageParserTest : public CppUnit::TestFixture { + CPPUNIT_TEST_SUITE(StorageParserTest); + CPPUNIT_TEST(testParse_Conference); + CPPUNIT_TEST(testParse_MultipleConferences); + CPPUNIT_TEST_SUITE_END(); + + public: + StorageParserTest() {} + + void testParse_Conference() { + PayloadsParserTester parser; + + CPPUNIT_ASSERT(parser.parse( + "<storage xmlns='storage:bookmarks'>" + "<conference " + "name='Council of Oberon' " + "autojoin='true' jid='council@conference.underhill.org'>" + "<nick>Puck</nick>" + "<password>MyPass</password>" + "</conference>" + "</storage>")); + + Storage* payload = dynamic_cast<Storage*>(parser.getPayload().get()); + std::vector<Storage::Conference> conferences = payload->getConferences(); + CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(conferences.size())); + CPPUNIT_ASSERT_EQUAL(String("Council of Oberon"), conferences[0].name); + CPPUNIT_ASSERT_EQUAL(JID("council@conference.underhill.org"), conferences[0].jid); + CPPUNIT_ASSERT(conferences[0].autoJoin); + CPPUNIT_ASSERT_EQUAL(String("Puck"), conferences[0].nick); + CPPUNIT_ASSERT_EQUAL(String("MyPass"), conferences[0].password); + } + + void testParse_MultipleConferences() { + PayloadsParserTester parser; + + CPPUNIT_ASSERT(parser.parse( + "<storage xmlns='storage:bookmarks'>" + "<conference " + "name='Council of Oberon' " + "jid='council@conference.underhill.org' />" + "<conference " + "name='Tea party' " + "jid='teaparty@wonderland.lit' />" + "</storage>")); + + Storage* payload = dynamic_cast<Storage*>(parser.getPayload().get()); + std::vector<Storage::Conference> conferences = payload->getConferences(); + CPPUNIT_ASSERT_EQUAL(2, static_cast<int>(conferences.size())); + CPPUNIT_ASSERT_EQUAL(String("Council of Oberon"), conferences[0].name); + CPPUNIT_ASSERT_EQUAL(JID("council@conference.underhill.org"), conferences[0].jid); + CPPUNIT_ASSERT_EQUAL(String("Tea party"), conferences[1].name); + CPPUNIT_ASSERT_EQUAL(JID("teaparty@wonderland.lit"), conferences[1].jid); + } +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(StorageParserTest); diff --git a/Swiften/Parser/PayloadParsers/UnitTest/VCardParserTest.cpp b/Swiften/Parser/PayloadParsers/UnitTest/VCardParserTest.cpp new file mode 100644 index 0000000..d1ddba3 --- /dev/null +++ b/Swiften/Parser/PayloadParsers/UnitTest/VCardParserTest.cpp @@ -0,0 +1,51 @@ +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> + +#include "Swiften/Parser/PayloadParsers/VCardParser.h" +#include "Swiften/Parser/PayloadParsers/UnitTest/PayloadsParserTester.h" + +using namespace Swift; + +class VCardParserTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(VCardParserTest); + CPPUNIT_TEST(testParse_Photo); + CPPUNIT_TEST(testParse_Nickname); + CPPUNIT_TEST_SUITE_END(); + + public: + VCardParserTest() {} + + void testParse_Photo() { + PayloadsParserTester parser; + + CPPUNIT_ASSERT(parser.parse( + "<vCard xmlns='vcard-temp'>" + "<PHOTO>" + "<TYPE>image/jpeg</TYPE>" + "<BINVAL>" + "QUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVphYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5ej" + "EyMzQ1Njc4OTA=" + "</BINVAL>" + "</PHOTO>" + "</vCard>")); + + VCard* payload = dynamic_cast<VCard*>(parser.getPayload().get()); + CPPUNIT_ASSERT_EQUAL(String("image/jpeg"), payload->getPhotoType()); + CPPUNIT_ASSERT_EQUAL(ByteArray("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890"), payload->getPhoto()); + } + + void testParse_Nickname() { + PayloadsParserTester parser; + + CPPUNIT_ASSERT(parser.parse( + "<vCard xmlns='vcard-temp'>" + "<NICKNAME>mynick</NICKNAME>" + "</vCard>")); + + VCard* payload = dynamic_cast<VCard*>(parser.getPayload().get()); + CPPUNIT_ASSERT_EQUAL(String("mynick"), payload->getNickname()); + } +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(VCardParserTest); diff --git a/Swiften/Parser/PayloadParsers/UnitTest/VCardUpdateParserTest.cpp b/Swiften/Parser/PayloadParsers/UnitTest/VCardUpdateParserTest.cpp new file mode 100644 index 0000000..ef6c78e --- /dev/null +++ b/Swiften/Parser/PayloadParsers/UnitTest/VCardUpdateParserTest.cpp @@ -0,0 +1,31 @@ +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> + +#include "Swiften/Parser/PayloadParsers/VCardUpdateParser.h" +#include "Swiften/Parser/PayloadParsers/UnitTest/PayloadsParserTester.h" + +using namespace Swift; + +class VCardUpdateParserTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(VCardUpdateParserTest); + CPPUNIT_TEST(testParse); + CPPUNIT_TEST_SUITE_END(); + + public: + VCardUpdateParserTest() {} + + void testParse() { + PayloadsParserTester parser; + + CPPUNIT_ASSERT(parser.parse( + "<x xmlns='vcard-temp:x:update'>" + "<photo>sha1-hash-of-image</photo>" + "</x>")); + + VCardUpdate* payload = dynamic_cast<VCardUpdate*>(parser.getPayload().get()); + CPPUNIT_ASSERT_EQUAL(String("sha1-hash-of-image"), payload->getPhotoHash()); + } +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(VCardUpdateParserTest); diff --git a/Swiften/Parser/PayloadParsers/VCardParser.cpp b/Swiften/Parser/PayloadParsers/VCardParser.cpp new file mode 100644 index 0000000..87416ab --- /dev/null +++ b/Swiften/Parser/PayloadParsers/VCardParser.cpp @@ -0,0 +1,53 @@ +#include "Swiften/Parser/PayloadParsers/VCardParser.h" +#include "Swiften/Base/foreach.h" +#include "Swiften/StringCodecs/Base64.h" + +namespace Swift { + +VCardParser::VCardParser() { +} + +void VCardParser::handleStartElement(const String& element, const String&, const AttributeMap&) { + elementStack_.push_back(element); + currentText_ = ""; +} + +void VCardParser::handleEndElement(const String&, const String&) { + String elementHierarchy = getElementHierarchy(); + if (elementHierarchy == "/vCard/PHOTO/TYPE") { + getPayloadInternal()->setPhotoType(currentText_); + } + else if (elementHierarchy == "/vCard/PHOTO/BINVAL") { + getPayloadInternal()->setPhoto(Base64::decode(currentText_)); + } + else if (elementHierarchy == "/vCard/NICKNAME") { + getPayloadInternal()->setNickname(currentText_); + } + else if (elementHierarchy == "/vCard/FN") { + getPayloadInternal()->setFullName(currentText_); + } + else if (elementHierarchy == "/vCard/N/FAMILY") { + getPayloadInternal()->setFamilyName(currentText_); + } + else if (elementHierarchy == "/vCard/N/GIVEN") { + getPayloadInternal()->setGivenName(currentText_); + } + else if (elementHierarchy == "/vCard/EMAIL/USERID") { + getPayloadInternal()->setEMail(currentText_); + } + elementStack_.pop_back(); +} + +void VCardParser::handleCharacterData(const String& text) { + currentText_ += text; +} + +String VCardParser::getElementHierarchy() const { + String result; + foreach(const String& element, elementStack_) { + result += "/" + element; + } + return result; +} + +} diff --git a/Swiften/Parser/PayloadParsers/VCardParser.h b/Swiften/Parser/PayloadParsers/VCardParser.h new file mode 100644 index 0000000..c1ed46b --- /dev/null +++ b/Swiften/Parser/PayloadParsers/VCardParser.h @@ -0,0 +1,24 @@ +#pragma once + +#include "Swiften/Elements/VCard.h" +#include "Swiften/Parser/GenericPayloadParser.h" + +namespace Swift { + class SerializingParser; + + class VCardParser : public GenericPayloadParser<VCard> { + public: + VCardParser(); + + virtual void handleStartElement(const String& element, const String&, const AttributeMap& attributes); + virtual void handleEndElement(const String& element, const String&); + virtual void handleCharacterData(const String& data); + + private: + String getElementHierarchy() const; + + private: + std::vector<String> elementStack_; + String currentText_; + }; +} diff --git a/Swiften/Parser/PayloadParsers/VCardParserFactory.h b/Swiften/Parser/PayloadParsers/VCardParserFactory.h new file mode 100644 index 0000000..2d5302b --- /dev/null +++ b/Swiften/Parser/PayloadParsers/VCardParserFactory.h @@ -0,0 +1,11 @@ +#pragma once + +#include "Swiften/Parser/GenericPayloadParserFactory.h" +#include "Swiften/Parser/PayloadParsers/VCardParser.h" + +namespace Swift { + class VCardParserFactory : public GenericPayloadParserFactory<VCardParser> { + public: + VCardParserFactory() : GenericPayloadParserFactory<VCardParser>("vCard", "vcard-temp") {} + }; +} diff --git a/Swiften/Parser/PayloadParsers/VCardUpdateParser.cpp b/Swiften/Parser/PayloadParsers/VCardUpdateParser.cpp new file mode 100644 index 0000000..e195eb7 --- /dev/null +++ b/Swiften/Parser/PayloadParsers/VCardUpdateParser.cpp @@ -0,0 +1,26 @@ +#include "Swiften/Parser/PayloadParsers/VCardUpdateParser.h" + +namespace Swift { + +VCardUpdateParser::VCardUpdateParser() : level_(TopLevel) { +} + +void VCardUpdateParser::handleStartElement(const String&, const String&, const AttributeMap&) { + if (level_ == PayloadLevel) { + currentText_ = ""; + } + ++level_; +} + +void VCardUpdateParser::handleEndElement(const String& element, const String&) { + --level_; + if (level_ == PayloadLevel && element == "photo") { + getPayloadInternal()->setPhotoHash(currentText_); + } +} + +void VCardUpdateParser::handleCharacterData(const String& text) { + currentText_ += text; +} + +} diff --git a/Swiften/Parser/PayloadParsers/VCardUpdateParser.h b/Swiften/Parser/PayloadParsers/VCardUpdateParser.h new file mode 100644 index 0000000..3682ccf --- /dev/null +++ b/Swiften/Parser/PayloadParsers/VCardUpdateParser.h @@ -0,0 +1,25 @@ +#pragma once + +#include "Swiften/Elements/VCardUpdate.h" +#include "Swiften/Parser/GenericPayloadParser.h" + +namespace Swift { + class SerializingParser; + + class VCardUpdateParser : public GenericPayloadParser<VCardUpdate> { + public: + VCardUpdateParser(); + + virtual void handleStartElement(const String& element, const String&, const AttributeMap& attributes); + virtual void handleEndElement(const String& element, const String&); + virtual void handleCharacterData(const String& data); + + private: + enum Level { + TopLevel = 0, + PayloadLevel = 1 + }; + int level_; + String currentText_; + }; +} diff --git a/Swiften/Parser/PayloadParsers/VCardUpdateParserFactory.h b/Swiften/Parser/PayloadParsers/VCardUpdateParserFactory.h new file mode 100644 index 0000000..0a8dddf --- /dev/null +++ b/Swiften/Parser/PayloadParsers/VCardUpdateParserFactory.h @@ -0,0 +1,11 @@ +#pragma once + +#include "Swiften/Parser/GenericPayloadParserFactory.h" +#include "Swiften/Parser/PayloadParsers/VCardUpdateParser.h" + +namespace Swift { + class VCardUpdateParserFactory : public GenericPayloadParserFactory<VCardUpdateParser> { + public: + VCardUpdateParserFactory() : GenericPayloadParserFactory<VCardUpdateParser>("x", "vcard-temp:x:update") {} + }; +} diff --git a/Swiften/Parser/PlatformXMLParserFactory.cpp b/Swiften/Parser/PlatformXMLParserFactory.cpp new file mode 100644 index 0000000..0624a2d --- /dev/null +++ b/Swiften/Parser/PlatformXMLParserFactory.cpp @@ -0,0 +1,25 @@ +#include "Swiften/Parser/PlatformXMLParserFactory.h" + +#include <cassert> + +#ifdef HAVE_LIBXML +#include "Swiften/Parser/LibXMLParser.h" +#else +#include "Swiften/Parser/ExpatParser.h" +#endif + + +namespace Swift { + +PlatformXMLParserFactory::PlatformXMLParserFactory() { +} + +XMLParser* PlatformXMLParserFactory::createXMLParser(XMLParserClient* client) { +#ifdef HAVE_LIBXML + return new LibXMLParser(client); +#else + return new ExpatParser(client); +#endif +} + +} diff --git a/Swiften/Parser/PlatformXMLParserFactory.h b/Swiften/Parser/PlatformXMLParserFactory.h new file mode 100644 index 0000000..28b1657 --- /dev/null +++ b/Swiften/Parser/PlatformXMLParserFactory.h @@ -0,0 +1,15 @@ +#ifndef SWIFTEN_PlatformXMLParserFactory_H +#define SWIFTEN_PlatformXMLParserFactory_H + +#include "Swiften/Parser/XMLParserFactory.h" + +namespace Swift { + class PlatformXMLParserFactory : public XMLParserFactory { + public: + PlatformXMLParserFactory(); + + virtual XMLParser* createXMLParser(XMLParserClient*); + }; +} + +#endif diff --git a/Swiften/Parser/PresenceParser.cpp b/Swiften/Parser/PresenceParser.cpp new file mode 100644 index 0000000..72cdd4c --- /dev/null +++ b/Swiften/Parser/PresenceParser.cpp @@ -0,0 +1,45 @@ +#include <iostream> + +#include "Swiften/Parser/PresenceParser.h" + +namespace Swift { + +PresenceParser::PresenceParser(PayloadParserFactoryCollection* factories) : + GenericStanzaParser<Presence>(factories) { +} + +void PresenceParser::handleStanzaAttributes(const AttributeMap& attributes) { + AttributeMap::const_iterator type = attributes.find("type"); + if (type != attributes.end()) { + if (type->second == "unavailable") { + getStanzaGeneric()->setType(Presence::Unavailable); + } + else if (type->second == "probe") { + getStanzaGeneric()->setType(Presence::Probe); + } + else if (type->second == "subscribe") { + getStanzaGeneric()->setType(Presence::Subscribe); + } + else if (type->second == "subscribed") { + getStanzaGeneric()->setType(Presence::Subscribed); + } + else if (type->second == "unsubscribe") { + getStanzaGeneric()->setType(Presence::Unsubscribe); + } + else if (type->second == "unsubscribed") { + getStanzaGeneric()->setType(Presence::Unsubscribed); + } + else if (type->second == "error") { + getStanzaGeneric()->setType(Presence::Error); + } + else { + std::cerr << "Unknown Presence type: " << type->second << std::endl; + getStanzaGeneric()->setType(Presence::Available); + } + } + else { + getStanzaGeneric()->setType(Presence::Available); + } +} + +} diff --git a/Swiften/Parser/PresenceParser.h b/Swiften/Parser/PresenceParser.h new file mode 100644 index 0000000..b3ba445 --- /dev/null +++ b/Swiften/Parser/PresenceParser.h @@ -0,0 +1,17 @@ +#ifndef SWIFTEN_PresenceParser_H +#define SWIFTEN_PresenceParser_H + +#include "Swiften/Parser/GenericStanzaParser.h" +#include "Swiften/Elements/Presence.h" + +namespace Swift { + class PresenceParser : public GenericStanzaParser<Presence> { + public: + PresenceParser(PayloadParserFactoryCollection* factories); + + private: + virtual void handleStanzaAttributes(const AttributeMap&); + }; +} + +#endif diff --git a/Swiften/Parser/SConscript b/Swiften/Parser/SConscript new file mode 100644 index 0000000..7d93d8b --- /dev/null +++ b/Swiften/Parser/SConscript @@ -0,0 +1,58 @@ +Import("swiften_env") + +myenv = swiften_env.Clone() +myenv.MergeFlags(swiften_env["BOOST_FLAGS"]) +myenv.MergeFlags(swiften_env.get("LIBXML_FLAGS", "")) +myenv.MergeFlags(swiften_env.get("EXPAT_FLAGS", "")) + +sources = [ + "AuthRequestParser.cpp", + "AuthChallengeParser.cpp", + "AuthSuccessParser.cpp", + "AuthResponseParser.cpp", + "CompressParser.cpp", + "ElementParser.cpp", + "IQParser.cpp", + "MessageParser.cpp", + "PayloadParser.cpp", + "PayloadParserFactory.cpp", + "PayloadParserFactoryCollection.cpp", + "PayloadParsers/BodyParser.cpp", + "PayloadParsers/ChatStateParser.cpp", + "PayloadParsers/DiscoInfoParser.cpp", + "PayloadParsers/ErrorParser.cpp", + "PayloadParsers/FullPayloadParserFactoryCollection.cpp", + "PayloadParsers/PriorityParser.cpp", + "PayloadParsers/PrivateStorageParser.cpp", + "PayloadParsers/RawXMLPayloadParser.cpp", + "PayloadParsers/ResourceBindParser.cpp", + "PayloadParsers/RosterParser.cpp", + "PayloadParsers/SecurityLabelParser.cpp", + "PayloadParsers/SecurityLabelsCatalogParser.cpp", + "PayloadParsers/SoftwareVersionParser.cpp", + "PayloadParsers/StorageParser.cpp", + "PayloadParsers/StatusParser.cpp", + "PayloadParsers/StatusShowParser.cpp", + "PayloadParsers/VCardParser.cpp", + "PayloadParsers/VCardUpdateParser.cpp", + "PlatformXMLParserFactory.cpp", + "PresenceParser.cpp", + "SerializingParser.cpp", + "StanzaParser.cpp", + "StreamFeaturesParser.cpp", + "XMLParser.cpp", + "XMLParserClient.cpp", + "XMLParserFactory.cpp", + "XMPPParser.cpp", + "XMPPParserClient.cpp", + ] + +if myenv.get("HAVE_EXPAT", 0) : + myenv.Append(CPPDEFINES = "HAVE_EXPAT") + sources += ["ExpatParser.cpp"] +if myenv.get("HAVE_LIBXML", 0) : + myenv.Append(CPPDEFINES = "HAVE_LIBXML") + sources += ["LibXMLParser.cpp"] + +objects = myenv.StaticObject(sources) +swiften_env.Append(SWIFTEN_OBJECTS = [objects]) diff --git a/Swiften/Parser/SerializingParser.cpp b/Swiften/Parser/SerializingParser.cpp new file mode 100644 index 0000000..f69e732 --- /dev/null +++ b/Swiften/Parser/SerializingParser.cpp @@ -0,0 +1,41 @@ +#include "Swiften/Parser/SerializingParser.h" +#include "Swiften/Serializer/XML/XMLTextNode.h" +#include "Swiften/Base/foreach.h" +#include <iostream> + +namespace Swift { + +SerializingParser::SerializingParser() { +} + +void SerializingParser::handleStartElement(const String& tag, const String& ns, const AttributeMap& attributes) { + boost::shared_ptr<XMLElement> element(new XMLElement(tag, ns)); + for (AttributeMap::const_iterator i = attributes.begin(); i != attributes.end(); ++i) { + element->setAttribute((*i).first, (*i).second); + } + + if (elementStack_.empty()) { + rootElement_ = element; + } + else { + (*(elementStack_.end() - 1))->addNode(element); + } + elementStack_.push_back(element); +} + +void SerializingParser::handleEndElement(const String&, const String&) { + assert(!elementStack_.empty()); + elementStack_.pop_back(); +} + +void SerializingParser::handleCharacterData(const String& data) { + if (!elementStack_.empty()) { + (*(elementStack_.end()-1))->addNode(boost::shared_ptr<XMLTextNode>(new XMLTextNode(data))); + } +} + +String SerializingParser::getResult() const { + return (rootElement_ ? rootElement_->serialize() : ""); +} + +} diff --git a/Swiften/Parser/SerializingParser.h b/Swiften/Parser/SerializingParser.h new file mode 100644 index 0000000..b1d4575 --- /dev/null +++ b/Swiften/Parser/SerializingParser.h @@ -0,0 +1,25 @@ +#ifndef SWIFTEN_SerializingParser_H +#define SWIFTEN_SerializingParser_H + +#include "Swiften/Base/String.h" +#include "Swiften/Parser/AttributeMap.h" +#include "Swiften/Serializer/XML/XMLElement.h" + +namespace Swift { + class SerializingParser { + public: + SerializingParser(); + + void handleStartElement(const String& element, const String& ns, const AttributeMap& attributes); + void handleEndElement(const String& element, const String& ns); + void handleCharacterData(const String& data); + + String getResult() const; + + private: + std::vector< boost::shared_ptr<XMLElement> > elementStack_; + boost::shared_ptr<XMLElement> rootElement_; + }; +} + +#endif diff --git a/Swiften/Parser/StanzaParser.cpp b/Swiften/Parser/StanzaParser.cpp new file mode 100644 index 0000000..952265c --- /dev/null +++ b/Swiften/Parser/StanzaParser.cpp @@ -0,0 +1,78 @@ +#include "Swiften/Parser/StanzaParser.h" + +#include <iostream> +#include <cassert> + +#include "Swiften/Parser/PayloadParser.h" +#include "Swiften/Parser/PayloadParserFactory.h" +#include "Swiften/Parser/PayloadParserFactoryCollection.h" +#include "Swiften/Parser/UnknownPayloadParser.h" + +namespace Swift { + +StanzaParser::StanzaParser(PayloadParserFactoryCollection* factories) : + currentDepth_(0), factories_(factories) { +} + +StanzaParser::~StanzaParser() { +} + +void StanzaParser::handleStartElement(const String& element, const String& ns, const AttributeMap& attributes) { + if (inStanza()) { + if (!inPayload()) { + assert(!currentPayloadParser_.get()); + PayloadParserFactory* payloadParserFactory = factories_->getPayloadParserFactory(element, ns, attributes); + if (payloadParserFactory) { + currentPayloadParser_.reset(payloadParserFactory->createPayloadParser()); + } + else { + currentPayloadParser_.reset(new UnknownPayloadParser()); + } + } + assert(currentPayloadParser_.get()); + currentPayloadParser_->handleStartElement(element, ns, attributes); + } + else { + AttributeMap::const_iterator from = attributes.find("from"); + if (from != attributes.end()) { + getStanza()->setFrom(JID(from->second)); + } + AttributeMap::const_iterator to = attributes.find("to"); + if (to != attributes.end()) { + getStanza()->setTo(JID(to->second)); + } + AttributeMap::const_iterator id = attributes.find("id"); + if (id != attributes.end()) { + getStanza()->setID(id->second); + } + handleStanzaAttributes(attributes); + } + ++currentDepth_; +} + +void StanzaParser::handleEndElement(const String& element, const String& ns) { + assert(inStanza()); + if (inPayload()) { + assert(currentPayloadParser_.get()); + currentPayloadParser_->handleEndElement(element, ns); + --currentDepth_; + if (!inPayload()) { + boost::shared_ptr<Payload> payload(currentPayloadParser_->getPayload()); + if (payload) { + getStanza()->addPayload(payload); + } + currentPayloadParser_.reset(); + } + } + else { + --currentDepth_; + } +} + +void StanzaParser::handleCharacterData(const String& data) { + if (currentPayloadParser_.get()) { + currentPayloadParser_->handleCharacterData(data); + } +} + +} diff --git a/Swiften/Parser/StanzaParser.h b/Swiften/Parser/StanzaParser.h new file mode 100644 index 0000000..c6cf753 --- /dev/null +++ b/Swiften/Parser/StanzaParser.h @@ -0,0 +1,48 @@ +#ifndef SWIFTEN_StanzaParser_H +#define SWIFTEN_StanzaParser_H + +#include <boost/noncopyable.hpp> + +#include "Swiften/Base/String.h" +#include "Swiften/Elements/Stanza.h" +#include "Swiften/Parser/ElementParser.h" +#include "Swiften/Parser/AttributeMap.h" + +namespace Swift { + class PayloadParser; + class PayloadParserFactoryCollection; + + class StanzaParser : public ElementParser, public boost::noncopyable { + public: + StanzaParser(PayloadParserFactoryCollection* factories); + ~StanzaParser(); + + void handleStartElement(const String& element, const String& ns, const AttributeMap& attributes); + void handleEndElement(const String& element, const String& ns); + void handleCharacterData(const String& data); + + virtual boost::shared_ptr<Element> getElement() const = 0; + virtual void handleStanzaAttributes(const AttributeMap&) {} + + virtual boost::shared_ptr<Stanza> getStanza() const { + return boost::dynamic_pointer_cast<Stanza>(getElement()); + } + + private: + bool inPayload() const { + return currentDepth_ > 1; + } + + bool inStanza() const { + return currentDepth_ > 0; + } + + + private: + int currentDepth_; + PayloadParserFactoryCollection* factories_; + std::auto_ptr<PayloadParser> currentPayloadParser_; + }; +} + +#endif diff --git a/Swiften/Parser/StartTLSFailureParser.h b/Swiften/Parser/StartTLSFailureParser.h new file mode 100644 index 0000000..c955ca0 --- /dev/null +++ b/Swiften/Parser/StartTLSFailureParser.h @@ -0,0 +1,14 @@ +#ifndef SWIFTEN_StartTLSFailureParser_H +#define SWIFTEN_StartTLSFailureParser_H + +#include "Swiften/Parser/GenericElementParser.h" +#include "Swiften/Elements/StartTLSFailure.h" + +namespace Swift { + class StartTLSFailureParser : public GenericElementParser<StartTLSFailure> { + public: + StartTLSFailureParser() : GenericElementParser<StartTLSFailure>() {} + }; +} + +#endif diff --git a/Swiften/Parser/StartTLSParser.h b/Swiften/Parser/StartTLSParser.h new file mode 100644 index 0000000..afacec2 --- /dev/null +++ b/Swiften/Parser/StartTLSParser.h @@ -0,0 +1,14 @@ +#ifndef SWIFTEN_StartTLSParser_H +#define SWIFTEN_StartTLSParser_H + +#include "Swiften/Parser/GenericElementParser.h" +#include "Swiften/Elements/StartTLSRequest.h" + +namespace Swift { + class StartTLSParser : public GenericElementParser<StartTLSRequest> { + public: + StartTLSParser() : GenericElementParser<StartTLSRequest>() {} + }; +} + +#endif diff --git a/Swiften/Parser/StreamFeaturesParser.cpp b/Swiften/Parser/StreamFeaturesParser.cpp new file mode 100644 index 0000000..5072e7c --- /dev/null +++ b/Swiften/Parser/StreamFeaturesParser.cpp @@ -0,0 +1,61 @@ +#include "Swiften/Parser/StreamFeaturesParser.h" + +namespace Swift { + +StreamFeaturesParser::StreamFeaturesParser() : GenericElementParser<StreamFeatures>(), currentDepth_(0), inMechanisms_(false), inMechanism_(false), inCompression_(false), inCompressionMethod_(false) { +} + +void StreamFeaturesParser::handleStartElement(const String& element, const String& ns, const AttributeMap&) { + if (currentDepth_ == 1) { + if (element == "starttls" && ns == "urn:ietf:params:xml:ns:xmpp-tls") { + getElementGeneric()->setHasStartTLS(); + } + else if (element == "session" && ns == "urn:ietf:params:xml:ns:xmpp-session") { + getElementGeneric()->setHasSession(); + } + else if (element == "bind" && ns == "urn:ietf:params:xml:ns:xmpp-bind") { + getElementGeneric()->setHasResourceBind(); + } + else if (element == "mechanisms" && ns == "urn:ietf:params:xml:ns:xmpp-sasl") { + inMechanisms_ = true; + } + else if (element == "compression" && ns == "http://jabber.org/features/compress") { + inCompression_ = true; + } + } + else if (currentDepth_ == 2) { + if (inCompression_ && element == "method") { + inCompressionMethod_ = true; + currentText_ = ""; + } + else if (inMechanisms_ && element == "mechanism") { + inMechanism_ = true; + currentText_ = ""; + } + } + ++currentDepth_; +} + +void StreamFeaturesParser::handleEndElement(const String&, const String&) { + --currentDepth_; + if (currentDepth_ == 1) { + inCompression_ = false; + inMechanisms_ = false; + } + else if (currentDepth_ == 2) { + if (inCompressionMethod_) { + getElementGeneric()->addCompressionMethod(currentText_); + inCompressionMethod_ = false; + } + else if (inMechanism_) { + getElementGeneric()->addAuthenticationMechanism(currentText_); + inMechanism_ = false; + } + } +} + +void StreamFeaturesParser::handleCharacterData(const String& data) { + currentText_ += data; +} + +} diff --git a/Swiften/Parser/StreamFeaturesParser.h b/Swiften/Parser/StreamFeaturesParser.h new file mode 100644 index 0000000..184a7e6 --- /dev/null +++ b/Swiften/Parser/StreamFeaturesParser.h @@ -0,0 +1,28 @@ +#ifndef SWIFTEN_STREAMFEATURESPARSER_H +#define SWIFTEN_STREAMFEATURESPARSER_H + +#include "Swiften/Base/String.h" +#include "Swiften/Parser/GenericElementParser.h" +#include "Swiften/Elements/StreamFeatures.h" + +namespace Swift { + class StreamFeaturesParser : public GenericElementParser<StreamFeatures> { + public: + StreamFeaturesParser(); + + private: + void handleStartElement(const String& element, const String& ns, const AttributeMap& attributes); + void handleEndElement(const String& element, const String& ns); + void handleCharacterData(const String& data); + + private: + int currentDepth_; + String currentText_; + bool inMechanisms_; + bool inMechanism_; + bool inCompression_; + bool inCompressionMethod_; + }; +} + +#endif diff --git a/Swiften/Parser/TLSProceedParser.h b/Swiften/Parser/TLSProceedParser.h new file mode 100644 index 0000000..2ad5438 --- /dev/null +++ b/Swiften/Parser/TLSProceedParser.h @@ -0,0 +1,14 @@ +#ifndef SWIFTEN_TLSProceedParser_H +#define SWIFTEN_TLSProceedParser_H + +#include "Swiften/Parser/GenericElementParser.h" +#include "Swiften/Elements/TLSProceed.h" + +namespace Swift { + class TLSProceedParser : public GenericElementParser<TLSProceed> { + public: + TLSProceedParser() : GenericElementParser<TLSProceed>() {} + }; +} + +#endif diff --git a/Swiften/Parser/UnitTest/AttributeMapTest.cpp b/Swiften/Parser/UnitTest/AttributeMapTest.cpp new file mode 100644 index 0000000..17bda95 --- /dev/null +++ b/Swiften/Parser/UnitTest/AttributeMapTest.cpp @@ -0,0 +1,71 @@ +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> + +#include "Swiften/Parser/AttributeMap.h" + +using namespace Swift; + +class AttributeMapTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(AttributeMapTest); + CPPUNIT_TEST(testGetBoolAttribute_True); + CPPUNIT_TEST(testGetBoolAttribute_1); + CPPUNIT_TEST(testGetBoolAttribute_False); + CPPUNIT_TEST(testGetBoolAttribute_0); + CPPUNIT_TEST(testGetBoolAttribute_Invalid); + CPPUNIT_TEST(testGetBoolAttribute_UnknownWithDefaultTrue); + CPPUNIT_TEST(testGetBoolAttribute_UnknownWithDefaultFalse); + CPPUNIT_TEST_SUITE_END(); + + public: + AttributeMapTest() {} + + void testGetBoolAttribute_True() { + AttributeMap testling; + testling["foo"] = "true"; + + CPPUNIT_ASSERT(testling.getBoolAttribute("foo")); + } + + void testGetBoolAttribute_1() { + AttributeMap testling; + testling["foo"] = "1"; + + CPPUNIT_ASSERT(testling.getBoolAttribute("foo")); + } + + void testGetBoolAttribute_False() { + AttributeMap testling; + testling["foo"] = "false"; + + CPPUNIT_ASSERT(!testling.getBoolAttribute("foo", true)); + } + + void testGetBoolAttribute_0() { + AttributeMap testling; + testling["foo"] = "0"; + + CPPUNIT_ASSERT(!testling.getBoolAttribute("foo", true)); + } + + void testGetBoolAttribute_Invalid() { + AttributeMap testling; + testling["foo"] = "bla"; + + CPPUNIT_ASSERT(!testling.getBoolAttribute("foo", true)); + } + + void testGetBoolAttribute_UnknownWithDefaultTrue() { + AttributeMap testling; + + CPPUNIT_ASSERT(testling.getBoolAttribute("foo", true)); + } + + void testGetBoolAttribute_UnknownWithDefaultFalse() { + AttributeMap testling; + + CPPUNIT_ASSERT(!testling.getBoolAttribute("foo", false)); + } +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(AttributeMapTest); diff --git a/Swiften/Parser/UnitTest/ElementParserTester.h b/Swiften/Parser/UnitTest/ElementParserTester.h new file mode 100644 index 0000000..4d84c44 --- /dev/null +++ b/Swiften/Parser/UnitTest/ElementParserTester.h @@ -0,0 +1,10 @@ +#ifndef SWIFTEN_ElementParserTester_H +#define SWIFTEN_ElementParserTester_H + +#include "Swiften/Parser/UnitTest/ParserTester.h" + +namespace Swift { + typedef ParserTester<ElementParser> ElementParserTester; +} + +#endif diff --git a/Swiften/Parser/UnitTest/IQParserTest.cpp b/Swiften/Parser/UnitTest/IQParserTest.cpp new file mode 100644 index 0000000..22b0adc --- /dev/null +++ b/Swiften/Parser/UnitTest/IQParserTest.cpp @@ -0,0 +1,70 @@ +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> + +#include "Swiften/Parser/IQParser.h" +#include "Swiften/Parser/PayloadParserFactoryCollection.h" +#include "Swiften/Parser/UnitTest/StanzaParserTester.h" + +using namespace Swift; + +class IQParserTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(IQParserTest); + CPPUNIT_TEST(testParse_Set); + CPPUNIT_TEST(testParse_Get); + CPPUNIT_TEST(testParse_Result); + CPPUNIT_TEST(testParse_Error); + CPPUNIT_TEST_SUITE_END(); + + public: + IQParserTest() {} + + void setUp() { + factoryCollection_ = new PayloadParserFactoryCollection(); + } + + void tearDown() { + delete factoryCollection_; + } + + void testParse_Set() { + IQParser testling(factoryCollection_); + StanzaParserTester parser(&testling); + + CPPUNIT_ASSERT(parser.parse("<iq type=\"set\"/>")); + + CPPUNIT_ASSERT_EQUAL(IQ::Set, testling.getStanzaGeneric()->getType()); + } + + void testParse_Get() { + IQParser testling(factoryCollection_); + StanzaParserTester parser(&testling); + + CPPUNIT_ASSERT(parser.parse("<iq type=\"get\"/>")); + + CPPUNIT_ASSERT_EQUAL(IQ::Get, testling.getStanzaGeneric()->getType()); + } + + void testParse_Result() { + IQParser testling(factoryCollection_); + StanzaParserTester parser(&testling); + + CPPUNIT_ASSERT(parser.parse("<iq type=\"result\"/>")); + + CPPUNIT_ASSERT_EQUAL(IQ::Result, testling.getStanzaGeneric()->getType()); + } + + void testParse_Error() { + IQParser testling(factoryCollection_); + StanzaParserTester parser(&testling); + + CPPUNIT_ASSERT(parser.parse("<iq type=\"error\"/>")); + + CPPUNIT_ASSERT_EQUAL(IQ::Error, testling.getStanzaGeneric()->getType()); + } + + private: + PayloadParserFactoryCollection* factoryCollection_; +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(IQParserTest); diff --git a/Swiften/Parser/UnitTest/MessageParserTest.cpp b/Swiften/Parser/UnitTest/MessageParserTest.cpp new file mode 100644 index 0000000..61a8d20 --- /dev/null +++ b/Swiften/Parser/UnitTest/MessageParserTest.cpp @@ -0,0 +1,80 @@ +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> + +#include "Swiften/Parser/MessageParser.h" +#include "Swiften/Parser/PayloadParserFactoryCollection.h" +#include "Swiften/Parser/UnitTest/StanzaParserTester.h" + +using namespace Swift; + +class MessageParserTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(MessageParserTest); + CPPUNIT_TEST(testParse_Normal); + CPPUNIT_TEST(testParse_Chat); + CPPUNIT_TEST(testParse_Error); + CPPUNIT_TEST(testParse_Groupchat); + CPPUNIT_TEST(testParse_Headline); + CPPUNIT_TEST_SUITE_END(); + + public: + MessageParserTest() {} + + void setUp() { + factoryCollection_ = new PayloadParserFactoryCollection(); + } + + void tearDown() { + delete factoryCollection_; + } + + void testParse_Chat() { + MessageParser testling(factoryCollection_); + StanzaParserTester parser(&testling); + + CPPUNIT_ASSERT(parser.parse("<message type=\"chat\"/>")); + + CPPUNIT_ASSERT_EQUAL(Message::Chat, testling.getStanzaGeneric()->getType()); + } + + void testParse_Groupchat() { + MessageParser testling(factoryCollection_); + StanzaParserTester parser(&testling); + + CPPUNIT_ASSERT(parser.parse("<message type=\"groupchat\"/>")); + + CPPUNIT_ASSERT_EQUAL(Message::Groupchat, testling.getStanzaGeneric()->getType()); + } + + void testParse_Error() { + MessageParser testling(factoryCollection_); + StanzaParserTester parser(&testling); + + CPPUNIT_ASSERT(parser.parse("<message type=\"error\"/>")); + + CPPUNIT_ASSERT_EQUAL(Message::Error, testling.getStanzaGeneric()->getType()); + } + + void testParse_Headline() { + MessageParser testling(factoryCollection_); + StanzaParserTester parser(&testling); + + CPPUNIT_ASSERT(parser.parse("<message type=\"headline\"/>")); + + CPPUNIT_ASSERT_EQUAL(Message::Headline, testling.getStanzaGeneric()->getType()); + } + + void testParse_Normal() { + MessageParser testling(factoryCollection_); + StanzaParserTester parser(&testling); + + CPPUNIT_ASSERT(parser.parse("<message/>")); + + CPPUNIT_ASSERT_EQUAL(Message::Normal, testling.getStanzaGeneric()->getType()); + } + + private: + PayloadParserFactoryCollection* factoryCollection_; +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(MessageParserTest); diff --git a/Swiften/Parser/UnitTest/ParserTester.h b/Swiften/Parser/UnitTest/ParserTester.h new file mode 100644 index 0000000..7aacc8e --- /dev/null +++ b/Swiften/Parser/UnitTest/ParserTester.h @@ -0,0 +1,44 @@ +#ifndef SWIFTEN_ParserTester_H +#define SWIFTEN_ParserTester_H + +#include "Swiften/Parser/XMLParserClient.h" +#include "Swiften/Parser/PlatformXMLParserFactory.h" +#include "Swiften/Parser/XMLParser.h" + +namespace Swift { + class XMLParser; + + template<typename ParserType> + class ParserTester : public XMLParserClient { + public: + ParserTester(ParserType* parser) : parser_(parser) { + xmlParser_ = PlatformXMLParserFactory().createXMLParser(this); + } + + ~ParserTester() { + delete xmlParser_; + } + + bool parse(const String& data) { + return xmlParser_->parse(data); + } + + virtual void handleStartElement(const String& element, const String& ns, const AttributeMap& attributes) { + parser_->handleStartElement(element, ns, attributes); + } + + virtual void handleEndElement(const String& element, const String& ns) { + parser_->handleEndElement(element, ns); + } + + virtual void handleCharacterData(const String& data) { + parser_->handleCharacterData(data); + } + + private: + XMLParser* xmlParser_; + ParserType* parser_; + }; +} + +#endif diff --git a/Swiften/Parser/UnitTest/PayloadParserFactoryCollectionTest.cpp b/Swiften/Parser/UnitTest/PayloadParserFactoryCollectionTest.cpp new file mode 100644 index 0000000..bfd2c2c --- /dev/null +++ b/Swiften/Parser/UnitTest/PayloadParserFactoryCollectionTest.cpp @@ -0,0 +1,97 @@ +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> + +#include "Swiften/Parser/PayloadParserFactoryCollection.h" +#include "Swiften/Parser/PayloadParserFactory.h" + +using namespace Swift; + +class PayloadParserFactoryCollectionTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(PayloadParserFactoryCollectionTest); + CPPUNIT_TEST(testGetPayloadParserFactory); + CPPUNIT_TEST(testGetPayloadParserFactory_NoMatchingFactory); + CPPUNIT_TEST(testGetPayloadParserFactory_TwoMatchingFactories); + CPPUNIT_TEST(testGetPayloadParserFactory_MatchWithDefaultFactory); + CPPUNIT_TEST(testGetPayloadParserFactory_NoMatchWithDefaultFactory); + CPPUNIT_TEST_SUITE_END(); + + public: + PayloadParserFactoryCollectionTest() {} + + void setUp() { + } + + void tearDown() { + } + + void testGetPayloadParserFactory() { + PayloadParserFactoryCollection testling; + DummyFactory factory1("foo"); + testling.addFactory(&factory1); + DummyFactory factory2("bar"); + testling.addFactory(&factory2); + DummyFactory factory3("baz"); + testling.addFactory(&factory3); + + PayloadParserFactory* factory = testling.getPayloadParserFactory("bar", "", AttributeMap()); + + CPPUNIT_ASSERT(factory == &factory2); + } + + void testGetPayloadParserFactory_NoMatchingFactory() { + PayloadParserFactoryCollection testling; + DummyFactory factory("foo"); + + CPPUNIT_ASSERT(!testling.getPayloadParserFactory("bar", "", AttributeMap())); + } + + void testGetPayloadParserFactory_TwoMatchingFactories() { + PayloadParserFactoryCollection testling; + DummyFactory factory1("foo"); + testling.addFactory(&factory1); + DummyFactory factory2("foo"); + testling.addFactory(&factory2); + + PayloadParserFactory* factory = testling.getPayloadParserFactory("foo", "", AttributeMap()); + + CPPUNIT_ASSERT(factory == &factory2); + } + + void testGetPayloadParserFactory_MatchWithDefaultFactory() { + PayloadParserFactoryCollection testling; + DummyFactory factory1("foo"); + testling.addFactory(&factory1); + DummyFactory factory2; + testling.setDefaultFactory(&factory2); + + PayloadParserFactory* factory = testling.getPayloadParserFactory("foo", "", AttributeMap()); + + CPPUNIT_ASSERT(factory == &factory1); + } + + void testGetPayloadParserFactory_NoMatchWithDefaultFactory() { + PayloadParserFactoryCollection testling; + DummyFactory factory1("foo"); + testling.addFactory(&factory1); + DummyFactory factory2; + testling.setDefaultFactory(&factory2); + + PayloadParserFactory* factory = testling.getPayloadParserFactory("baz", "", AttributeMap()); + + CPPUNIT_ASSERT(factory == &factory2); + } + + + private: + struct DummyFactory : public PayloadParserFactory { + DummyFactory(const String& element = "") : element(element) {} + virtual bool canParse(const String& e, const String&, const AttributeMap&) const { + return element.isEmpty() ? true : element == e; + } + virtual PayloadParser* createPayloadParser() { return NULL; } + String element; + }; +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(PayloadParserFactoryCollectionTest); diff --git a/Swiften/Parser/UnitTest/PresenceParserTest.cpp b/Swiften/Parser/UnitTest/PresenceParserTest.cpp new file mode 100644 index 0000000..5305161 --- /dev/null +++ b/Swiften/Parser/UnitTest/PresenceParserTest.cpp @@ -0,0 +1,110 @@ +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> + +#include "Swiften/Parser/PresenceParser.h" +#include "Swiften/Parser/PayloadParserFactoryCollection.h" +#include "Swiften/Parser/UnitTest/StanzaParserTester.h" + +using namespace Swift; + +class PresenceParserTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(PresenceParserTest); + CPPUNIT_TEST(testParse_Available); + CPPUNIT_TEST(testParse_Unavailable); + CPPUNIT_TEST(testParse_Subscribe); + CPPUNIT_TEST(testParse_Subscribed); + CPPUNIT_TEST(testParse_Unsubscribe); + CPPUNIT_TEST(testParse_Unsubscribed); + CPPUNIT_TEST(testParse_Probe); + CPPUNIT_TEST(testParse_Error); + CPPUNIT_TEST_SUITE_END(); + + public: + PresenceParserTest() {} + + void setUp() { + factoryCollection_ = new PayloadParserFactoryCollection(); + } + + void tearDown() { + delete factoryCollection_; + } + + void testParse_Available() { + PresenceParser testling(factoryCollection_); + StanzaParserTester parser(&testling); + + CPPUNIT_ASSERT(parser.parse("<presence/>")); + + CPPUNIT_ASSERT_EQUAL(Presence::Available, testling.getStanzaGeneric()->getType()); + } + + void testParse_Unavailable() { + PresenceParser testling(factoryCollection_); + StanzaParserTester parser(&testling); + + CPPUNIT_ASSERT(parser.parse("<presence type=\"unavailable\"/>")); + + CPPUNIT_ASSERT_EQUAL(Presence::Unavailable, testling.getStanzaGeneric()->getType()); + } + + void testParse_Probe() { + PresenceParser testling(factoryCollection_); + StanzaParserTester parser(&testling); + + CPPUNIT_ASSERT(parser.parse("<presence type=\"probe\"/>")); + + CPPUNIT_ASSERT_EQUAL(Presence::Probe, testling.getStanzaGeneric()->getType()); + } + + void testParse_Subscribe() { + PresenceParser testling(factoryCollection_); + StanzaParserTester parser(&testling); + + CPPUNIT_ASSERT(parser.parse("<presence type=\"subscribe\"/>")); + + CPPUNIT_ASSERT_EQUAL(Presence::Subscribe, testling.getStanzaGeneric()->getType()); + } + + void testParse_Subscribed() { + PresenceParser testling(factoryCollection_); + StanzaParserTester parser(&testling); + + CPPUNIT_ASSERT(parser.parse("<presence type=\"subscribed\"/>")); + + CPPUNIT_ASSERT_EQUAL(Presence::Subscribed, testling.getStanzaGeneric()->getType()); + } + + void testParse_Unsubscribe() { + PresenceParser testling(factoryCollection_); + StanzaParserTester parser(&testling); + + CPPUNIT_ASSERT(parser.parse("<presence type=\"unsubscribe\"/>")); + + CPPUNIT_ASSERT_EQUAL(Presence::Unsubscribe, testling.getStanzaGeneric()->getType()); + } + + void testParse_Unsubscribed() { + PresenceParser testling(factoryCollection_); + StanzaParserTester parser(&testling); + + CPPUNIT_ASSERT(parser.parse("<presence type=\"unsubscribed\"/>")); + + CPPUNIT_ASSERT_EQUAL(Presence::Unsubscribed, testling.getStanzaGeneric()->getType()); + } + + void testParse_Error() { + PresenceParser testling(factoryCollection_); + StanzaParserTester parser(&testling); + + CPPUNIT_ASSERT(parser.parse("<presence type=\"error\"/>")); + + CPPUNIT_ASSERT_EQUAL(Presence::Error, testling.getStanzaGeneric()->getType()); + } + + private: + PayloadParserFactoryCollection* factoryCollection_; +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(PresenceParserTest); diff --git a/Swiften/Parser/UnitTest/SerializingParserTest.cpp b/Swiften/Parser/UnitTest/SerializingParserTest.cpp new file mode 100644 index 0000000..e08a3d0 --- /dev/null +++ b/Swiften/Parser/UnitTest/SerializingParserTest.cpp @@ -0,0 +1,58 @@ +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> + +#include "Swiften/Parser/SerializingParser.h" +#include "Swiften/Parser/UnitTest/StanzaParserTester.h" + +using namespace Swift; + +class SerializingParserTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(SerializingParserTest); + CPPUNIT_TEST(testParse); + CPPUNIT_TEST(testParse_Empty); + CPPUNIT_TEST(testParse_ToplevelCharacterData); + CPPUNIT_TEST_SUITE_END(); + + public: + SerializingParserTest() {} + + void testParse() { + SerializingParser testling; + ParserTester<SerializingParser> parser(&testling); + + CPPUNIT_ASSERT(parser.parse( + "<message type=\"chat\" to=\"me@foo.com\">" + "<body>Hello<&World</body>" + "<html xmlns=\"http://www.w3.org/1999/xhtml\">" + "foo<b>bar</b>baz" + "</html>" + "</message>")); + + CPPUNIT_ASSERT_EQUAL(String( + "<message to=\"me@foo.com\" type=\"chat\">" + "<body>Hello<&World</body>" + "<html xmlns=\"http://www.w3.org/1999/xhtml\">foo<b xmlns=\"http://www.w3.org/1999/xhtml\">bar</b>baz</html>" + "</message>"), testling.getResult()); + } + + void testParse_Empty() { + SerializingParser testling; + + CPPUNIT_ASSERT_EQUAL(String(""), testling.getResult()); + } + + void testParse_ToplevelCharacterData() { + SerializingParser testling; + + AttributeMap attributes; + testling.handleCharacterData("foo"); + testling.handleStartElement("message", "", attributes); + testling.handleEndElement("message", ""); + testling.handleCharacterData("bar"); + + CPPUNIT_ASSERT_EQUAL(String("<message/>"), testling.getResult()); + } +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(SerializingParserTest); diff --git a/Swiften/Parser/UnitTest/StanzaParserTest.cpp b/Swiften/Parser/UnitTest/StanzaParserTest.cpp new file mode 100644 index 0000000..3cb1879 --- /dev/null +++ b/Swiften/Parser/UnitTest/StanzaParserTest.cpp @@ -0,0 +1,209 @@ +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> + +#include "Swiften/Parser/StanzaParser.h" +#include "Swiften/Parser/GenericPayloadParser.h" +#include "Swiften/Parser/PayloadParserFactory.h" +#include "Swiften/Parser/PayloadParserFactoryCollection.h" +#include "Swiften/Elements/Stanza.h" +#include "Swiften/Elements/Payload.h" + +using namespace Swift; + +class StanzaParserTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(StanzaParserTest); + CPPUNIT_TEST(testHandleEndElement_OnePayload); + CPPUNIT_TEST(testHandleEndElement_MultiplePayloads); + CPPUNIT_TEST(testHandleEndElement_StrayCharacterData); + CPPUNIT_TEST(testHandleEndElement_UnknownPayload); + CPPUNIT_TEST(testHandleParse_BasicAttributes); + CPPUNIT_TEST_SUITE_END(); + + public: + StanzaParserTest() {} + + void setUp() { + factoryCollection_ = new PayloadParserFactoryCollection(); + factoryCollection_->addFactory(&factory1_); + factoryCollection_->addFactory(&factory2_); + } + + void tearDown() { + delete factoryCollection_; + } + + void testHandleEndElement_OnePayload() { + MyStanzaParser testling(factoryCollection_); + + AttributeMap attributes; + attributes["foo"] = "fum"; + attributes["bar"] = "baz"; + testling.handleStartElement("mystanza", "", attributes); + testling.handleStartElement("mypayload1", "", attributes); + testling.handleStartElement("child", "", attributes); + testling.handleEndElement("child", ""); + testling.handleEndElement("mypayload1", ""); + testling.handleEndElement("mystanza", ""); + + CPPUNIT_ASSERT(testling.getStanza()->getPayload<MyPayload1>()); + CPPUNIT_ASSERT(testling.getStanza()->getPayload<MyPayload1>()->hasChild); + } + + void testHandleEndElement_MultiplePayloads() { + MyStanzaParser testling(factoryCollection_); + + AttributeMap attributes; + testling.handleStartElement("mystanza", "", attributes); + testling.handleStartElement("mypayload1", "", attributes); + testling.handleEndElement("mypayload1", ""); + testling.handleStartElement("mypayload2", "", attributes); + testling.handleEndElement("mypayload2", ""); + testling.handleEndElement("mystanza", ""); + + CPPUNIT_ASSERT(testling.getStanza()->getPayload<MyPayload1>()); + CPPUNIT_ASSERT(testling.getStanza()->getPayload<MyPayload2>()); + } + + void testHandleEndElement_StrayCharacterData() { + MyStanzaParser testling(factoryCollection_); + + AttributeMap attributes; + testling.handleStartElement("mystanza", "", attributes); + testling.handleStartElement("mypayload1", "", attributes); + testling.handleEndElement("mypayload1", ""); + testling.handleCharacterData("bla"); + testling.handleStartElement("mypayload2", "", attributes); + testling.handleEndElement("mypayload2", ""); + testling.handleEndElement("mystanza", ""); + + CPPUNIT_ASSERT(testling.getStanza()->getPayload<MyPayload1>()); + CPPUNIT_ASSERT(testling.getStanza()->getPayload<MyPayload2>()); + } + + void testHandleEndElement_UnknownPayload() { + MyStanzaParser testling(factoryCollection_); + + AttributeMap attributes; + testling.handleStartElement("mystanza", "", attributes); + testling.handleStartElement("mypayload1", "", attributes); + testling.handleEndElement("mypayload1", ""); + testling.handleStartElement("unknown-payload", "", attributes); + testling.handleStartElement("unknown-payload-child", "", attributes); + testling.handleEndElement("unknown-payload-child", ""); + testling.handleEndElement("unknown-payload", ""); + testling.handleStartElement("mypayload2", "", attributes); + testling.handleEndElement("mypayload2", ""); + testling.handleEndElement("mystanza", ""); + + CPPUNIT_ASSERT(testling.getStanza()->getPayload<MyPayload1>()); + CPPUNIT_ASSERT(testling.getStanza()->getPayload<MyPayload2>()); + } + + void testHandleParse_BasicAttributes() { + MyStanzaParser testling(factoryCollection_); + + AttributeMap attributes; + attributes["to"] = "foo@example.com/blo"; + attributes["from"] = "bar@example.com/baz"; + attributes["id"] = "id-123"; + testling.handleStartElement("mystanza", "", attributes); + testling.handleEndElement("mypayload1", ""); + + CPPUNIT_ASSERT_EQUAL(JID("foo@example.com/blo"), testling.getStanza()->getTo()); + CPPUNIT_ASSERT_EQUAL(JID("bar@example.com/baz"), testling.getStanza()->getFrom()); + CPPUNIT_ASSERT_EQUAL(String("id-123"), testling.getStanza()->getID()); + } + + private: + class MyPayload1 : public Payload + { + public: + MyPayload1() : hasChild(false) {} + + bool hasChild; + }; + + class MyPayload1Parser : public GenericPayloadParser<MyPayload1> + { + public: + MyPayload1Parser() {} + + virtual void handleStartElement(const String& element, const String&, const AttributeMap&) { + if (element != "mypayload1") { + getPayloadInternal()->hasChild = true; + } + } + + virtual void handleEndElement(const String&, const String&) {} + virtual void handleCharacterData(const String&) {} + }; + + class MyPayload1ParserFactory : public PayloadParserFactory + { + public: + MyPayload1ParserFactory() {} + + PayloadParser* createPayloadParser() { return new MyPayload1Parser(); } + + bool canParse(const String& element, const String&, const AttributeMap&) const { + return element == "mypayload1"; + } + }; + + class MyPayload2 : public Payload + { + public: + MyPayload2() {} + }; + + class MyPayload2Parser : public GenericPayloadParser<MyPayload2> + { + public: + MyPayload2Parser() {} + + virtual void handleStartElement(const String&, const String&, const AttributeMap&) {} + virtual void handleEndElement(const String&, const String&) {} + virtual void handleCharacterData(const String&) {} + }; + + + class MyPayload2ParserFactory : public PayloadParserFactory + { + public: + MyPayload2ParserFactory() {} + + PayloadParser* createPayloadParser() { return new MyPayload2Parser(); } + bool canParse(const String& element, const String&, const AttributeMap&) const { + return element == "mypayload2"; + } + }; + + class MyStanza : public Stanza + { + public: + MyStanza() {} + }; + + class MyStanzaParser : public StanzaParser + { + public: + MyStanzaParser(PayloadParserFactoryCollection* collection) : StanzaParser(collection) + { + stanza_ = boost::shared_ptr<MyStanza>(new MyStanza()); + } + + virtual boost::shared_ptr<Element> getElement() const { + return stanza_; + } + + private: + boost::shared_ptr<MyStanza> stanza_; + }; + + MyPayload1ParserFactory factory1_; + MyPayload2ParserFactory factory2_; + PayloadParserFactoryCollection* factoryCollection_; +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(StanzaParserTest); diff --git a/Swiften/Parser/UnitTest/StanzaParserTester.h b/Swiften/Parser/UnitTest/StanzaParserTester.h new file mode 100644 index 0000000..cbd484f --- /dev/null +++ b/Swiften/Parser/UnitTest/StanzaParserTester.h @@ -0,0 +1,11 @@ +#ifndef SWIFTEN_StanzaParserTester_H +#define SWIFTEN_StanzaParserTester_H + +#include "Swiften/Parser/StanzaParser.h" +#include "Swiften/Parser/UnitTest/ParserTester.h" + +namespace Swift { + typedef ParserTester<StanzaParser> StanzaParserTester; +} + +#endif diff --git a/Swiften/Parser/UnitTest/StreamFeaturesParserTest.cpp b/Swiften/Parser/UnitTest/StreamFeaturesParserTest.cpp new file mode 100644 index 0000000..7fd0512 --- /dev/null +++ b/Swiften/Parser/UnitTest/StreamFeaturesParserTest.cpp @@ -0,0 +1,63 @@ +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> + +#include "Swiften/Parser/StreamFeaturesParser.h" +#include "Swiften/Parser/UnitTest/ElementParserTester.h" + +using namespace Swift; + +class StreamFeaturesParserTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(StreamFeaturesParserTest); + CPPUNIT_TEST(testParse); + CPPUNIT_TEST(testParse_Empty); + CPPUNIT_TEST_SUITE_END(); + + public: + StreamFeaturesParserTest() {} + + void testParse() { + StreamFeaturesParser testling; + ElementParserTester parser(&testling); + + CPPUNIT_ASSERT(parser.parse( + "<stream:features xmlns:stream='http://etherx.jabber.org/streams'>" + "<starttls xmlns=\"urn:ietf:params:xml:ns:xmpp-tls\"/>" + "<compression xmlns=\"http://jabber.org/features/compress\">" + "<method>zlib</method>" + "<method>lzw</method>" + "</compression>" + "<mechanisms xmlns=\"urn:ietf:params:xml:ns:xmpp-sasl\">" + "<mechanism>DIGEST-MD5</mechanism>" + "<mechanism>PLAIN</mechanism>" + "</mechanisms>" + "<bind xmlns=\"urn:ietf:params:xml:ns:xmpp-bind\"/>" + "<session xmlns=\"urn:ietf:params:xml:ns:xmpp-session\"/>" + "</stream:features>")); + + StreamFeatures* element = dynamic_cast<StreamFeatures*>(testling.getElement().get()); + CPPUNIT_ASSERT(element->hasStartTLS()); + CPPUNIT_ASSERT(element->hasSession()); + CPPUNIT_ASSERT(element->hasResourceBind()); + CPPUNIT_ASSERT(element->hasCompressionMethod("zlib")); + CPPUNIT_ASSERT(element->hasCompressionMethod("lzw")); + CPPUNIT_ASSERT(element->hasAuthenticationMechanisms()); + CPPUNIT_ASSERT(element->hasAuthenticationMechanism("DIGEST-MD5")); + CPPUNIT_ASSERT(element->hasAuthenticationMechanism("PLAIN")); + } + + void testParse_Empty() { + StreamFeaturesParser testling; + ElementParserTester parser(&testling); + + parser.parse("<stream:features xmlns:stream='http://etherx.jabber.org/streams'/>"); + + StreamFeatures* element = dynamic_cast<StreamFeatures*>(testling.getElement().get()); + CPPUNIT_ASSERT(!element->hasStartTLS()); + CPPUNIT_ASSERT(!element->hasSession()); + CPPUNIT_ASSERT(!element->hasResourceBind()); + CPPUNIT_ASSERT(!element->hasAuthenticationMechanisms()); + } +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(StreamFeaturesParserTest); diff --git a/Swiften/Parser/UnitTest/XMLParserTest.cpp b/Swiften/Parser/UnitTest/XMLParserTest.cpp new file mode 100644 index 0000000..6ef1899 --- /dev/null +++ b/Swiften/Parser/UnitTest/XMLParserTest.cpp @@ -0,0 +1,229 @@ +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> +#include <vector> + +#include "Swiften/Base/String.h" +#include "Swiften/Parser/XMLParserClient.h" +#ifdef HAVE_EXPAT +#include "Swiften/Parser/ExpatParser.h" +#endif +#ifdef HAVE_LIBXML +#include "Swiften/Parser/LibXMLParser.h" +#endif + +using namespace Swift; + +template <typename ParserType> +class XMLParserTest : public CppUnit::TestFixture { + CPPUNIT_TEST_SUITE(XMLParserTest); + CPPUNIT_TEST(testParse_NestedElements); + CPPUNIT_TEST(testParse_ElementInNamespacedElement); + CPPUNIT_TEST(testParse_CharacterData); + CPPUNIT_TEST(testParse_NamespacePrefix); + CPPUNIT_TEST(testParse_UnhandledXML); + CPPUNIT_TEST(testParse_InvalidXML); + CPPUNIT_TEST(testParse_InErrorState); + CPPUNIT_TEST(testParse_Incremental); + CPPUNIT_TEST_SUITE_END(); + + public: + void testParse_NestedElements() { + ParserType testling(&client_); + + CPPUNIT_ASSERT(testling.parse( + "<iq type=\"get\">" + "<query xmlns='jabber:iq:version'/>" + "</iq>")); + + CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(4), client_.events.size()); + + CPPUNIT_ASSERT_EQUAL(Client::StartElement, client_.events[0].type); + CPPUNIT_ASSERT_EQUAL(String("iq"), client_.events[0].data); + CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), client_.events[0].attributes.size()); + CPPUNIT_ASSERT_EQUAL(String("get"), client_.events[0].attributes["type"]); + CPPUNIT_ASSERT_EQUAL(String(), client_.events[0].ns); + + CPPUNIT_ASSERT_EQUAL(Client::StartElement, client_.events[1].type); + CPPUNIT_ASSERT_EQUAL(String("query"), client_.events[1].data); + CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(0), client_.events[1].attributes.size()); + CPPUNIT_ASSERT_EQUAL(String("jabber:iq:version"), client_.events[1].ns); + + CPPUNIT_ASSERT_EQUAL(Client::EndElement, client_.events[2].type); + CPPUNIT_ASSERT_EQUAL(String("query"), client_.events[2].data); + CPPUNIT_ASSERT_EQUAL(String("jabber:iq:version"), client_.events[2].ns); + + CPPUNIT_ASSERT_EQUAL(Client::EndElement, client_.events[3].type); + CPPUNIT_ASSERT_EQUAL(String("iq"), client_.events[3].data); + CPPUNIT_ASSERT_EQUAL(String(), client_.events[3].ns); + } + + void testParse_ElementInNamespacedElement() { + ParserType testling(&client_); + + CPPUNIT_ASSERT(testling.parse( + "<query xmlns='jabber:iq:version'>" + "<name>Swift</name>" + "</query>")); + + CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(5), client_.events.size()); + + CPPUNIT_ASSERT_EQUAL(Client::StartElement, client_.events[0].type); + CPPUNIT_ASSERT_EQUAL(String("query"), client_.events[0].data); + CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(0), client_.events[0].attributes.size()); + CPPUNIT_ASSERT_EQUAL(String("jabber:iq:version"), client_.events[0].ns); + + CPPUNIT_ASSERT_EQUAL(Client::StartElement, client_.events[1].type); + CPPUNIT_ASSERT_EQUAL(String("name"), client_.events[1].data); + CPPUNIT_ASSERT_EQUAL(String("jabber:iq:version"), client_.events[1].ns); + + CPPUNIT_ASSERT_EQUAL(Client::CharacterData, client_.events[2].type); + CPPUNIT_ASSERT_EQUAL(String("Swift"), client_.events[2].data); + + CPPUNIT_ASSERT_EQUAL(Client::EndElement, client_.events[3].type); + CPPUNIT_ASSERT_EQUAL(String("name"), client_.events[3].data); + CPPUNIT_ASSERT_EQUAL(String("jabber:iq:version"), client_.events[3].ns); + + CPPUNIT_ASSERT_EQUAL(Client::EndElement, client_.events[4].type); + CPPUNIT_ASSERT_EQUAL(String("query"), client_.events[4].data); + CPPUNIT_ASSERT_EQUAL(String("jabber:iq:version"), client_.events[4].ns); + } + + void testParse_CharacterData() { + ParserType testling(&client_); + + CPPUNIT_ASSERT(testling.parse("<html>bla<i>bli</i>blo</html>")); + + CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(7), client_.events.size()); + + CPPUNIT_ASSERT_EQUAL(Client::StartElement, client_.events[0].type); + CPPUNIT_ASSERT_EQUAL(String("html"), client_.events[0].data); + + CPPUNIT_ASSERT_EQUAL(Client::CharacterData, client_.events[1].type); + CPPUNIT_ASSERT_EQUAL(String("bla"), client_.events[1].data); + + CPPUNIT_ASSERT_EQUAL(Client::StartElement, client_.events[2].type); + CPPUNIT_ASSERT_EQUAL(String("i"), client_.events[2].data); + + CPPUNIT_ASSERT_EQUAL(Client::CharacterData, client_.events[3].type); + CPPUNIT_ASSERT_EQUAL(String("bli"), client_.events[3].data); + + CPPUNIT_ASSERT_EQUAL(Client::EndElement, client_.events[4].type); + CPPUNIT_ASSERT_EQUAL(String("i"), client_.events[4].data); + + CPPUNIT_ASSERT_EQUAL(Client::CharacterData, client_.events[5].type); + CPPUNIT_ASSERT_EQUAL(String("blo"), client_.events[5].data); + + CPPUNIT_ASSERT_EQUAL(Client::EndElement, client_.events[6].type); + CPPUNIT_ASSERT_EQUAL(String("html"), client_.events[6].data); + } + + void testParse_NamespacePrefix() { + ParserType testling(&client_); + + CPPUNIT_ASSERT(testling.parse("<p:x xmlns:p='bla'><p:y/></p:x>")); + + CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(4), client_.events.size()); + + CPPUNIT_ASSERT_EQUAL(Client::StartElement, client_.events[0].type); + CPPUNIT_ASSERT_EQUAL(String("x"), client_.events[0].data); + CPPUNIT_ASSERT_EQUAL(String("bla"), client_.events[0].ns); + + CPPUNIT_ASSERT_EQUAL(Client::StartElement, client_.events[1].type); + CPPUNIT_ASSERT_EQUAL(String("y"), client_.events[1].data); + CPPUNIT_ASSERT_EQUAL(String("bla"), client_.events[1].ns); + + CPPUNIT_ASSERT_EQUAL(Client::EndElement, client_.events[2].type); + CPPUNIT_ASSERT_EQUAL(String("y"), client_.events[2].data); + CPPUNIT_ASSERT_EQUAL(String("bla"), client_.events[2].ns); + + CPPUNIT_ASSERT_EQUAL(Client::EndElement, client_.events[3].type); + CPPUNIT_ASSERT_EQUAL(String("x"), client_.events[3].data); + CPPUNIT_ASSERT_EQUAL(String("bla"), client_.events[3].ns); + } + + void testParse_UnhandledXML() { + ParserType testling(&client_); + + CPPUNIT_ASSERT(testling.parse("<iq><!-- Testing --></iq>")); + + CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(2), client_.events.size()); + + CPPUNIT_ASSERT_EQUAL(Client::StartElement, client_.events[0].type); + CPPUNIT_ASSERT_EQUAL(String("iq"), client_.events[0].data); + + CPPUNIT_ASSERT_EQUAL(Client::EndElement, client_.events[1].type); + CPPUNIT_ASSERT_EQUAL(String("iq"), client_.events[1].data); + } + + void testParse_InvalidXML() { + ParserType testling(&client_); + + CPPUNIT_ASSERT(!testling.parse("<iq><bla></iq>")); + } + + void testParse_InErrorState() { + ParserType testling(&client_); + + CPPUNIT_ASSERT(!testling.parse("<iq><bla></iq>")); + CPPUNIT_ASSERT(!testling.parse("<iq/>")); + } + + void testParse_Incremental() { + ParserType testling(&client_); + + CPPUNIT_ASSERT(testling.parse("<iq")); + CPPUNIT_ASSERT(testling.parse("></iq>")); + + CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(2), client_.events.size()); + + CPPUNIT_ASSERT_EQUAL(Client::StartElement, client_.events[0].type); + CPPUNIT_ASSERT_EQUAL(String("iq"), client_.events[0].data); + + CPPUNIT_ASSERT_EQUAL(Client::EndElement, client_.events[1].type); + CPPUNIT_ASSERT_EQUAL(String("iq"), client_.events[1].data); + } + + private: + class Client : public XMLParserClient { + public: + enum Type { StartElement, EndElement, CharacterData }; + struct Event { + Event( + Type type, + const String& data, + const String& ns, + const AttributeMap& attributes) + : type(type), data(data), ns(ns), attributes(attributes) {} + Event(Type type, const String& data, const String& ns = String()) + : type(type), data(data), ns(ns) {} + + Type type; + String data; + String ns; + AttributeMap attributes; + }; + + Client() {} + + virtual void handleStartElement(const String& element, const String& ns, const AttributeMap& attributes) { + events.push_back(Event(StartElement, element, ns, attributes)); + } + + virtual void handleEndElement(const String& element, const String& ns) { + events.push_back(Event(EndElement, element, ns)); + } + + virtual void handleCharacterData(const String& data) { + events.push_back(Event(CharacterData, data)); + } + + std::vector<Event> events; + } client_; +}; + +#ifdef HAVE_EXPAT +CPPUNIT_TEST_SUITE_REGISTRATION(XMLParserTest<ExpatParser>); +#endif +#ifdef HAVE_LIBXML +CPPUNIT_TEST_SUITE_REGISTRATION(XMLParserTest<LibXMLParser>); +#endif diff --git a/Swiften/Parser/UnitTest/XMPPParserTest.cpp b/Swiften/Parser/UnitTest/XMPPParserTest.cpp new file mode 100644 index 0000000..ecdd565 --- /dev/null +++ b/Swiften/Parser/UnitTest/XMPPParserTest.cpp @@ -0,0 +1,186 @@ +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> +#include <vector> + +#include "Swiften/Elements/ProtocolHeader.h" +#include "Swiften/Base/String.h" +#include "Swiften/Parser/XMPPParser.h" +#include "Swiften/Parser/ElementParser.h" +#include "Swiften/Parser/XMPPParserClient.h" +#include "Swiften/Parser/PayloadParserFactoryCollection.h" +#include "Swiften/Elements/Presence.h" +#include "Swiften/Elements/IQ.h" +#include "Swiften/Elements/Message.h" +#include "Swiften/Elements/StreamFeatures.h" +#include "Swiften/Elements/UnknownElement.h" + +using namespace Swift; + +class XMPPParserTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(XMPPParserTest); + CPPUNIT_TEST(testParse_SimpleSession); + CPPUNIT_TEST(testParse_SimpleClientFromServerSession); + CPPUNIT_TEST(testParse_Presence); + CPPUNIT_TEST(testParse_IQ); + CPPUNIT_TEST(testParse_Message); + CPPUNIT_TEST(testParse_StreamFeatures); + CPPUNIT_TEST(testParse_UnknownElement); + CPPUNIT_TEST(testParse_StrayCharacterData); + CPPUNIT_TEST(testParse_InvalidStreamStart); + CPPUNIT_TEST_SUITE_END(); + + public: + XMPPParserTest() {} + + void testParse_SimpleSession() { + XMPPParser testling(&client_, &factories_); + + CPPUNIT_ASSERT(testling.parse("<?xml version='1.0'?>")); + CPPUNIT_ASSERT(testling.parse("<stream:stream to='example.com' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' >")); + CPPUNIT_ASSERT(testling.parse("<presence/>")); + CPPUNIT_ASSERT(testling.parse("<presence/>")); + CPPUNIT_ASSERT(testling.parse("<iq/>")); + CPPUNIT_ASSERT(testling.parse("</stream:stream>")); + + CPPUNIT_ASSERT_EQUAL(5, static_cast<int>(client_.events.size())); + CPPUNIT_ASSERT_EQUAL(Client::StreamStart, client_.events[0].type); + CPPUNIT_ASSERT_EQUAL(String("example.com"), client_.events[0].to); + CPPUNIT_ASSERT_EQUAL(Client::ElementEvent, client_.events[1].type); + CPPUNIT_ASSERT_EQUAL(Client::ElementEvent, client_.events[2].type); + CPPUNIT_ASSERT_EQUAL(Client::ElementEvent, client_.events[3].type); + CPPUNIT_ASSERT_EQUAL(Client::StreamEnd, client_.events[4].type); + } + + void testParse_SimpleClientFromServerSession() { + XMPPParser testling(&client_, &factories_); + + CPPUNIT_ASSERT(testling.parse("<?xml version='1.0'?>")); + CPPUNIT_ASSERT(testling.parse("<stream:stream from='example.com' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' id='aeab'>")); + + CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(client_.events.size())); + CPPUNIT_ASSERT_EQUAL(Client::StreamStart, client_.events[0].type); + CPPUNIT_ASSERT_EQUAL(String("example.com"), client_.events[0].from); + CPPUNIT_ASSERT_EQUAL(String("aeab"), client_.events[0].id); + } + + + void testParse_Presence() { + XMPPParser testling(&client_, &factories_); + + CPPUNIT_ASSERT(testling.parse("<stream:stream xmlns:stream='http://etherx.jabber.org/streams'>")); + CPPUNIT_ASSERT(testling.parse("<presence/>")); + + CPPUNIT_ASSERT_EQUAL(2, static_cast<int>(client_.events.size())); + CPPUNIT_ASSERT_EQUAL(Client::ElementEvent, client_.events[1].type); + CPPUNIT_ASSERT(dynamic_cast<Presence*>(client_.events[1].element.get())); + } + + void testParse_IQ() { + XMPPParser testling(&client_, &factories_); + + CPPUNIT_ASSERT(testling.parse("<stream:stream xmlns:stream='http://etherx.jabber.org/streams'>")); + CPPUNIT_ASSERT(testling.parse("<iq/>")); + + CPPUNIT_ASSERT_EQUAL(2, static_cast<int>(client_.events.size())); + CPPUNIT_ASSERT_EQUAL(Client::ElementEvent, client_.events[1].type); + CPPUNIT_ASSERT(dynamic_cast<IQ*>(client_.events[1].element.get())); + } + + void testParse_Message() { + XMPPParser testling(&client_, &factories_); + + CPPUNIT_ASSERT(testling.parse("<stream:stream xmlns:stream='http://etherx.jabber.org/streams'>")); + CPPUNIT_ASSERT(testling.parse("<message/>")); + + CPPUNIT_ASSERT_EQUAL(2, static_cast<int>(client_.events.size())); + CPPUNIT_ASSERT_EQUAL(Client::ElementEvent, client_.events[1].type); + CPPUNIT_ASSERT(dynamic_cast<Message*>(client_.events[1].element.get())); + } + + void testParse_StreamFeatures() { + XMPPParser testling(&client_, &factories_); + + CPPUNIT_ASSERT(testling.parse("<stream:stream xmlns:stream='http://etherx.jabber.org/streams'>")); + CPPUNIT_ASSERT(testling.parse("<stream:features/>")); + + CPPUNIT_ASSERT_EQUAL(2, static_cast<int>(client_.events.size())); + CPPUNIT_ASSERT_EQUAL(Client::ElementEvent, client_.events[1].type); + CPPUNIT_ASSERT(dynamic_cast<StreamFeatures*>(client_.events[1].element.get())); + } + + void testParse_UnknownElement() { + XMPPParser testling(&client_, &factories_); + + CPPUNIT_ASSERT(testling.parse("<stream:stream xmlns:stream='http://etherx.jabber.org/streams'>")); + CPPUNIT_ASSERT(testling.parse("<presence/>")); + CPPUNIT_ASSERT(testling.parse("<foo/>")); + CPPUNIT_ASSERT(testling.parse("<bar/>")); + CPPUNIT_ASSERT(testling.parse("<presence/>")); + + CPPUNIT_ASSERT_EQUAL(5, static_cast<int>(client_.events.size())); + CPPUNIT_ASSERT_EQUAL(Client::ElementEvent, client_.events[2].type); + CPPUNIT_ASSERT(dynamic_cast<UnknownElement*>(client_.events[2].element.get())); + CPPUNIT_ASSERT_EQUAL(Client::ElementEvent, client_.events[3].type); + CPPUNIT_ASSERT(dynamic_cast<UnknownElement*>(client_.events[3].element.get())); + CPPUNIT_ASSERT_EQUAL(Client::ElementEvent, client_.events[4].type); + CPPUNIT_ASSERT(dynamic_cast<Presence*>(client_.events[4].element.get())); + } + + void testParse_StrayCharacterData() { + XMPPParser testling(&client_, &factories_); + + CPPUNIT_ASSERT(testling.parse("<stream:stream xmlns:stream='http://etherx.jabber.org/streams'>")); + CPPUNIT_ASSERT(testling.parse("<presence/>")); + CPPUNIT_ASSERT(testling.parse("bla")); + CPPUNIT_ASSERT(testling.parse("<iq/>")); + + CPPUNIT_ASSERT_EQUAL(3, static_cast<int>(client_.events.size())); + CPPUNIT_ASSERT_EQUAL(Client::ElementEvent, client_.events[2].type); + CPPUNIT_ASSERT(dynamic_cast<IQ*>(client_.events[2].element.get())); + } + + void testParse_InvalidStreamStart() { + XMPPParser testling(&client_, &factories_); + + CPPUNIT_ASSERT(!testling.parse("<tream>")); + } + + private: + class Client : public XMPPParserClient { + public: + enum Type { StreamStart, ElementEvent, StreamEnd }; + struct Event { + Event(Type type, boost::shared_ptr<Element> element) + : type(type), element(element) {} + Event(Type type, const String& from, const String& to, const String& id) : type(type), from(from), to(to), id(id) {} + + Event(Type type) : type(type) {} + + Type type; + String from; + String to; + String id; + boost::shared_ptr<Element> element; + }; + + Client() {} + + void handleStreamStart(const ProtocolHeader& header) { + events.push_back(Event(StreamStart, header.getFrom(), header.getTo(), header.getID())); + } + + void handleElement(boost::shared_ptr<Element> element) { + events.push_back(Event(ElementEvent, element)); + } + + void handleStreamEnd() { + events.push_back(Event(StreamEnd)); + } + + std::vector<Event> events; + } client_; + PayloadParserFactoryCollection factories_; +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(XMPPParserTest); diff --git a/Swiften/Parser/UnknownElementParser.h b/Swiften/Parser/UnknownElementParser.h new file mode 100644 index 0000000..c016664 --- /dev/null +++ b/Swiften/Parser/UnknownElementParser.h @@ -0,0 +1,14 @@ +#ifndef SWIFTEN_UnknownElementParser_H +#define SWIFTEN_UnknownElementParser_H + +#include "Swiften/Parser/GenericElementParser.h" +#include "Swiften/Elements/UnknownElement.h" + +namespace Swift { + class UnknownElementParser : public GenericElementParser<UnknownElement> { + public: + UnknownElementParser() : GenericElementParser<UnknownElement>() {} + }; +} + +#endif diff --git a/Swiften/Parser/UnknownPayloadParser.h b/Swiften/Parser/UnknownPayloadParser.h new file mode 100644 index 0000000..ad56885 --- /dev/null +++ b/Swiften/Parser/UnknownPayloadParser.h @@ -0,0 +1,25 @@ +#ifndef SWIFTEN_UNKNOWNPAYLOADPARSER_H +#define SWIFTEN_UNKNOWNPAYLOADPARSER_H + +#include <boost/shared_ptr.hpp> + +#include "Swiften/Parser/PayloadParser.h" + +namespace Swift { + class String; + + class UnknownPayloadParser : public PayloadParser { + public: + UnknownPayloadParser() {} + + virtual void handleStartElement(const String&, const String&, const AttributeMap&) {} + virtual void handleEndElement(const String&, const String&) {} + virtual void handleCharacterData(const String&) {} + + virtual boost::shared_ptr<Payload> getPayload() const { + return boost::shared_ptr<Payload>(); + } + }; +} + +#endif diff --git a/Swiften/Parser/XMLParser.cpp b/Swiften/Parser/XMLParser.cpp new file mode 100644 index 0000000..a827e99 --- /dev/null +++ b/Swiften/Parser/XMLParser.cpp @@ -0,0 +1,11 @@ +#include "Swiften/Parser/XMLParser.h" + +namespace Swift { + +XMLParser::XMLParser(XMLParserClient* client) : client_(client) { +} + +XMLParser::~XMLParser() { +} + +} diff --git a/Swiften/Parser/XMLParser.h b/Swiften/Parser/XMLParser.h new file mode 100644 index 0000000..7ed90db --- /dev/null +++ b/Swiften/Parser/XMLParser.h @@ -0,0 +1,25 @@ +#ifndef SWIFTEN_XMLParser_H +#define SWIFTEN_XMLParser_H + +namespace Swift { + class String; + class XMLParserClient; + + class XMLParser { + public: + XMLParser(XMLParserClient* client); + virtual ~XMLParser(); + + virtual bool parse(const String& data) = 0; + + protected: + XMLParserClient* getClient() const { + return client_; + } + + private: + XMLParserClient* client_; + }; +} + +#endif diff --git a/Swiften/Parser/XMLParserClient.cpp b/Swiften/Parser/XMLParserClient.cpp new file mode 100644 index 0000000..53a103f --- /dev/null +++ b/Swiften/Parser/XMLParserClient.cpp @@ -0,0 +1,9 @@ +#include "Swiften/Parser/XMLParserClient.h" + +namespace Swift { + +XMLParserClient::~XMLParserClient() { +} + +} + diff --git a/Swiften/Parser/XMLParserClient.h b/Swiften/Parser/XMLParserClient.h new file mode 100644 index 0000000..7179ac6 --- /dev/null +++ b/Swiften/Parser/XMLParserClient.h @@ -0,0 +1,19 @@ +#ifndef XMLPARSERCLIENT_H +#define XMLPARSERCLIENT_H + +#include "Swiften/Parser/AttributeMap.h" + +namespace Swift { + class String; + + class XMLParserClient { + public: + virtual ~XMLParserClient(); + + virtual void handleStartElement(const String& element, const String& ns, const AttributeMap& attributes) = 0; + virtual void handleEndElement(const String& element, const String& ns) = 0; + virtual void handleCharacterData(const String& data) = 0; + }; +} + +#endif diff --git a/Swiften/Parser/XMLParserFactory.cpp b/Swiften/Parser/XMLParserFactory.cpp new file mode 100644 index 0000000..e21bb5c --- /dev/null +++ b/Swiften/Parser/XMLParserFactory.cpp @@ -0,0 +1,8 @@ +#include "Swiften/Parser/XMLParserFactory.h" + +namespace Swift { + +XMLParserFactory::~XMLParserFactory() { +} + +} diff --git a/Swiften/Parser/XMLParserFactory.h b/Swiften/Parser/XMLParserFactory.h new file mode 100644 index 0000000..8d67b17 --- /dev/null +++ b/Swiften/Parser/XMLParserFactory.h @@ -0,0 +1,16 @@ +#ifndef SWIFTEN_XMLParserFactory_H +#define SWIFTEN_XMLParserFactory_H + +namespace Swift { + class XMLParser; + class XMLParserClient; + + class XMLParserFactory { + public: + virtual ~XMLParserFactory(); + + virtual XMLParser* createXMLParser(XMLParserClient*) = 0; + }; +} + +#endif diff --git a/Swiften/Parser/XMPPParser.cpp b/Swiften/Parser/XMPPParser.cpp new file mode 100644 index 0000000..83de263 --- /dev/null +++ b/Swiften/Parser/XMPPParser.cpp @@ -0,0 +1,159 @@ +#include "Swiften/Parser/XMPPParser.h" + +#include <iostream> +#include <cassert> + +#include "Swiften/Elements/ProtocolHeader.h" +#include "Swiften/Base/String.h" +#include "Swiften/Parser/XMLParser.h" +#include "Swiften/Parser/PlatformXMLParserFactory.h" +#include "Swiften/Parser/XMPPParserClient.h" +#include "Swiften/Parser/XMPPParser.h" +#include "Swiften/Parser/ElementParser.h" +#include "Swiften/Parser/PresenceParser.h" +#include "Swiften/Parser/IQParser.h" +#include "Swiften/Parser/MessageParser.h" +#include "Swiften/Parser/StreamFeaturesParser.h" +#include "Swiften/Parser/AuthRequestParser.h" +#include "Swiften/Parser/AuthSuccessParser.h" +#include "Swiften/Parser/AuthFailureParser.h" +#include "Swiften/Parser/AuthChallengeParser.h" +#include "Swiften/Parser/AuthResponseParser.h" +#include "Swiften/Parser/StartTLSParser.h" +#include "Swiften/Parser/StartTLSFailureParser.h" +#include "Swiften/Parser/CompressParser.h" +#include "Swiften/Parser/CompressFailureParser.h" +#include "Swiften/Parser/CompressedParser.h" +#include "Swiften/Parser/UnknownElementParser.h" +#include "Swiften/Parser/TLSProceedParser.h" + +// TODO: Whenever an error occurs in the handlers, stop the parser by returing +// a bool value, and stopping the XML parser + +namespace Swift { + +XMPPParser::XMPPParser( + XMPPParserClient* client, + PayloadParserFactoryCollection* payloadParserFactories) : + xmlParser_(0), + client_(client), + payloadParserFactories_(payloadParserFactories), + currentDepth_(0), + currentElementParser_(0), + parseErrorOccurred_(false) { + xmlParser_ = PlatformXMLParserFactory().createXMLParser(this); +} + +XMPPParser::~XMPPParser() { + delete currentElementParser_; + delete xmlParser_; +} + +bool XMPPParser::parse(const String& data) { + bool xmlParseResult = xmlParser_->parse(data); + return xmlParseResult && !parseErrorOccurred_; +} + +void XMPPParser::handleStartElement(const String& element, const String& ns, const AttributeMap& attributes) { + if (!inStream()) { + if (element == "stream" && ns == "http://etherx.jabber.org/streams") { + ProtocolHeader header; + header.setFrom(attributes.getAttribute("from")); + header.setTo(attributes.getAttribute("to")); + header.setID(attributes.getAttribute("id")); + header.setVersion(attributes.getAttribute("version")); + client_->handleStreamStart(header); + } + else { + parseErrorOccurred_ = true; + } + } + else { + if (!inElement()) { + assert(!currentElementParser_); + delete currentElementParser_; + currentElementParser_ = createElementParser(element, ns); + } + currentElementParser_->handleStartElement(element, ns, attributes); + } + ++currentDepth_; +} + +void XMPPParser::handleEndElement(const String& element, const String& ns) { + assert(inStream()); + if (inElement()) { + assert(currentElementParser_); + currentElementParser_->handleEndElement(element, ns); + --currentDepth_; + if (!inElement()) { + client_->handleElement(currentElementParser_->getElement()); + delete currentElementParser_; + currentElementParser_ = 0; + } + } + else { + assert(element == "stream"); + --currentDepth_; + client_->handleStreamEnd(); + } +} + +void XMPPParser::handleCharacterData(const String& data) { + if (currentElementParser_) { + currentElementParser_->handleCharacterData(data); + } + //else { + // std::cerr << "XMPPParser: Ignoring stray character data: " << data << std::endl; + //} +} + +ElementParser* XMPPParser::createElementParser(const String& element, const String& ns) { + if (element == "presence") { + return new PresenceParser(payloadParserFactories_); + } + else if (element == "iq") { + return new IQParser(payloadParserFactories_); + } + else if (element == "message") { + return new MessageParser(payloadParserFactories_); + } + else if (element == "features" && ns == "http://etherx.jabber.org/streams") { + return new StreamFeaturesParser(); + } + else if (element == "auth") { + return new AuthRequestParser(); + } + else if (element == "success") { + return new AuthSuccessParser(); + } + else if (element == "failure" && ns == "urn:ietf:params:xml:ns:xmpp-sasl") { + return new AuthFailureParser(); + } + else if (element == "challenge" && ns == "urn:ietf:params:xml:ns:xmpp-sasl") { + return new AuthChallengeParser(); + } + else if (element == "response" && ns == "urn:ietf:params:xml:ns:xmpp-sasl") { + return new AuthResponseParser(); + } + else if (element == "starttls") { + return new StartTLSParser(); + } + else if (element == "failure" && ns == "urn:ietf:params:xml:ns:xmpp-tls") { + return new StartTLSFailureParser(); + } + else if (element == "compress") { + return new CompressParser(); + } + else if (element == "compressed") { + return new CompressedParser(); + } + else if (element == "failure" && ns == "http://jabber.org/protocol/compress") { + return new CompressFailureParser(); + } + else if (element == "proceed") { + return new TLSProceedParser(); + } + return new UnknownElementParser(); +} + +} diff --git a/Swiften/Parser/XMPPParser.h b/Swiften/Parser/XMPPParser.h new file mode 100644 index 0000000..9e1109d --- /dev/null +++ b/Swiften/Parser/XMPPParser.h @@ -0,0 +1,54 @@ +#ifndef SWIFTEN_XMPPPARSER_H +#define SWIFTEN_XMPPPARSER_H + +#include <boost/shared_ptr.hpp> +#include <boost/noncopyable.hpp> + +#include "Swiften/Parser/XMLParserClient.h" +#include "Swiften/Parser/AttributeMap.h" + +namespace Swift { + class XMLParser; + class XMPPParserClient; + class String; + class ElementParser; + class PayloadParserFactoryCollection; + + class XMPPParser : public XMLParserClient, boost::noncopyable { + public: + XMPPParser( + XMPPParserClient* parserClient, + PayloadParserFactoryCollection* payloadParserFactories); + ~XMPPParser(); + + bool parse(const String&); + + private: + virtual void handleStartElement( + const String& element, + const String& ns, + const AttributeMap& attributes); + virtual void handleEndElement(const String& element, const String& ns); + virtual void handleCharacterData(const String& data); + + bool inStream() const { + return currentDepth_ > 0; + } + + bool inElement() const { + return currentDepth_ > 1; + } + + ElementParser* createElementParser(const String& element, const String& xmlns); + + private: + XMLParser* xmlParser_; + XMPPParserClient* client_; + PayloadParserFactoryCollection* payloadParserFactories_; + int currentDepth_; + ElementParser* currentElementParser_; + bool parseErrorOccurred_; + }; +} + +#endif diff --git a/Swiften/Parser/XMPPParserClient.cpp b/Swiften/Parser/XMPPParserClient.cpp new file mode 100644 index 0000000..66ea917 --- /dev/null +++ b/Swiften/Parser/XMPPParserClient.cpp @@ -0,0 +1,8 @@ +#include "Swiften/Parser/XMPPParserClient.h" + +namespace Swift { + +XMPPParserClient::~XMPPParserClient() { +} + +} diff --git a/Swiften/Parser/XMPPParserClient.h b/Swiften/Parser/XMPPParserClient.h new file mode 100644 index 0000000..b8f8e46 --- /dev/null +++ b/Swiften/Parser/XMPPParserClient.h @@ -0,0 +1,19 @@ +#pragma once + +#include <boost/shared_ptr.hpp> + +#include "Swiften/Elements/Element.h" + +namespace Swift { + class String; + class ProtocolHeader; + + class XMPPParserClient { + public: + virtual ~XMPPParserClient(); + + virtual void handleStreamStart(const ProtocolHeader&) = 0; + virtual void handleElement(boost::shared_ptr<Element>) = 0; + virtual void handleStreamEnd() = 0; + }; +} diff --git a/Swiften/Presence/PresenceOracle.cpp b/Swiften/Presence/PresenceOracle.cpp new file mode 100644 index 0000000..988fc10 --- /dev/null +++ b/Swiften/Presence/PresenceOracle.cpp @@ -0,0 +1,58 @@ +#include "PresenceOracle.h" + +#include <boost/bind.hpp> +#include "Swiften/Client/StanzaChannel.h" +namespace Swift { + +typedef std::pair<JID, std::map<JID, boost::shared_ptr<Presence> > > JIDMapPair; +typedef std::pair<JID, boost::shared_ptr<Presence> > JIDPresencePair; + +PresenceOracle::PresenceOracle(StanzaChannel* stanzaChannel) { + stanzaChannel_ = stanzaChannel; + stanzaChannel_->onPresenceReceived.connect(boost::bind(&PresenceOracle::handleIncomingPresence, this, _1)); +} + +void PresenceOracle::cancelSubscription(const JID& jid) { + boost::shared_ptr<Presence> stanza(new Presence()); + stanza->setType(Presence::Unsubscribed); + stanza->setTo(jid); + stanzaChannel_->sendPresence(stanza); +} + +void PresenceOracle::confirmSubscription(const JID& jid) { + boost::shared_ptr<Presence> stanza(new Presence()); + stanza->setType(Presence::Subscribed); + stanza->setTo(jid); + stanzaChannel_->sendPresence(stanza); +} + + +void PresenceOracle::requestSubscription(const JID& jid) { + boost::shared_ptr<Presence> stanza(new Presence()); + stanza->setType(Presence::Subscribe); + stanza->setTo(jid); + stanzaChannel_->sendPresence(stanza); +} + + +void PresenceOracle::handleIncomingPresence(boost::shared_ptr<Presence> presence) { + JID bareJID = JID(presence->getFrom().toBare()); + + if (presence->getType() == Presence::Subscribe) { + onPresenceSubscriptionRequest(bareJID, presence->getStatus()); + } else { + std::map<JID, boost::shared_ptr<Presence> > jidMap = entries_[bareJID]; + boost::shared_ptr<Presence> last; + foreach(JIDPresencePair pair, jidMap) { + if (pair.first == presence->getFrom()) { + last = pair.second; + break; + } + } + jidMap[presence->getFrom()] = presence; + entries_[bareJID] = jidMap; + onPresenceChange(presence, last); + } +} + +} diff --git a/Swiften/Presence/PresenceOracle.h b/Swiften/Presence/PresenceOracle.h new file mode 100644 index 0000000..dc6fe5d --- /dev/null +++ b/Swiften/Presence/PresenceOracle.h @@ -0,0 +1,30 @@ +#pragma once + +#include "Swiften/Base/String.h" +#include "Swiften/Elements/Presence.h" + +#include <map> +#include <boost/signal.hpp> + +namespace Swift { +class StanzaChannel; + +class PresenceOracle { + public: + PresenceOracle(StanzaChannel* stanzaChannel); + ~PresenceOracle() {}; + + void cancelSubscription(const JID& jid); + void confirmSubscription(const JID& jid); + void requestSubscription(const JID& jid); + + boost::signal<void (boost::shared_ptr<Presence>, boost::shared_ptr<Presence>)> onPresenceChange; + boost::signal<void (const JID&, const String&)> onPresenceSubscriptionRequest; + + private: + void handleIncomingPresence(boost::shared_ptr<Presence> presence); + std::map<JID, std::map<JID, boost::shared_ptr<Presence> > > entries_; + StanzaChannel* stanzaChannel_; +}; +} + diff --git a/Swiften/Presence/PresenceSender.cpp b/Swiften/Presence/PresenceSender.cpp new file mode 100644 index 0000000..8e7ef68 --- /dev/null +++ b/Swiften/Presence/PresenceSender.cpp @@ -0,0 +1,49 @@ +#include "Swiften/Presence/PresenceSender.h" +#include "Swiften/Base/foreach.h" +#include "Swiften/Client/StanzaChannel.h" + +namespace Swift { + +PresenceSender::PresenceSender(StanzaChannel* channel) : channel(channel) { +} + +void PresenceSender::sendPresence(boost::shared_ptr<Presence> presence) { + if (!channel->isAvailable()) { + return; + } + + channel->sendPresence(presence); + + if (!presence->getTo().isValid()) { + boost::shared_ptr<Presence> presenceCopy(new Presence(*presence)); + foreach(const JID& jid, directedPresenceReceivers) { + presenceCopy->setTo(jid); + channel->sendPresence(presenceCopy); + } + + lastSentUndirectedPresence = presence; + } +} + +void PresenceSender::addDirectedPresenceReceiver(const JID& jid) { + directedPresenceReceivers.insert(jid); + if (channel->isAvailable()) { + if (lastSentUndirectedPresence && lastSentUndirectedPresence->getType() == Presence::Available) { + boost::shared_ptr<Presence> presenceCopy(new Presence(*lastSentUndirectedPresence)); + presenceCopy->setTo(jid); + channel->sendPresence(presenceCopy); + } + } +} + +void PresenceSender::removeDirectedPresenceReceiver(const JID& jid) { + directedPresenceReceivers.erase(jid); + if (channel->isAvailable()) { + boost::shared_ptr<Presence> presence(new Presence()); + presence->setType(Presence::Unavailable); + presence->setTo(jid); + channel->sendPresence(presence); + } +} + +} diff --git a/Swiften/Presence/PresenceSender.h b/Swiften/Presence/PresenceSender.h new file mode 100644 index 0000000..ef69447 --- /dev/null +++ b/Swiften/Presence/PresenceSender.h @@ -0,0 +1,24 @@ +#pragma once + +#include <set> + +#include "Swiften/Elements/Presence.h" + +namespace Swift { + class StanzaChannel; + + class PresenceSender { + public: + PresenceSender(StanzaChannel*); + + void addDirectedPresenceReceiver(const JID&); + void removeDirectedPresenceReceiver(const JID&); + + void sendPresence(boost::shared_ptr<Presence>); + + private: + boost::shared_ptr<Presence> lastSentUndirectedPresence; + StanzaChannel* channel; + std::set<JID> directedPresenceReceivers; + }; +} diff --git a/Swiften/Presence/UnitTest/PresenceOracleTest.cpp b/Swiften/Presence/UnitTest/PresenceOracleTest.cpp new file mode 100644 index 0000000..2c9c526 --- /dev/null +++ b/Swiften/Presence/UnitTest/PresenceOracleTest.cpp @@ -0,0 +1,126 @@ +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> +#include <boost/bind.hpp> +#include <boost/shared_ptr.hpp> +#include <boost/optional.hpp> + +#include "Swiften/Presence/PresenceOracle.h" +#include "Swiften/Client/DummyStanzaChannel.h" + +using namespace Swift; + +class PresencePointerPair { + public: + boost::shared_ptr<Presence> one; + boost::shared_ptr<Presence> two; +}; + +class SubscriptionRequestInfo { + public: + boost::optional<JID> jid; + boost::optional<String> reason; +}; + +class PresenceOracleTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(PresenceOracleTest); + CPPUNIT_TEST(testFirstPresence); + CPPUNIT_TEST(testSecondPresence); + CPPUNIT_TEST(testSubscriptionRequest); + CPPUNIT_TEST_SUITE_END(); + + private: + PresenceOracle* oracle_; + DummyStanzaChannel* stanzaChannel_; + + public: + + void handlePresenceChange(boost::shared_ptr<Presence> newPresence, boost::shared_ptr<Presence> lastPresence, PresencePointerPair* out) { + CPPUNIT_ASSERT(out->one.get() == NULL); + CPPUNIT_ASSERT(out->two.get() == NULL); + out->one = newPresence; + out->two = lastPresence; + CPPUNIT_ASSERT(newPresence.get()); + CPPUNIT_ASSERT_EQUAL(newPresence, out->one); + } + + void handlePresenceSubscriptionRequest(const JID& jid, const String& reason, SubscriptionRequestInfo* info) { + CPPUNIT_ASSERT(!info->jid); + CPPUNIT_ASSERT(!info->reason); + info->jid = jid; + info->reason = reason; + } + + void setUp() { + stanzaChannel_ = new DummyStanzaChannel(); + oracle_ = new PresenceOracle(stanzaChannel_); + } + + void tearDown() { + delete oracle_; + delete stanzaChannel_; + } + + void testFirstPresence() { + PresencePointerPair out; + oracle_->onPresenceChange.connect(boost::bind(&PresenceOracleTest::handlePresenceChange, this, _1, _2, &out)); + + SubscriptionRequestInfo info; + oracle_->onPresenceSubscriptionRequest.connect(boost::bind(&PresenceOracleTest::handlePresenceSubscriptionRequest, this, _1, _2, &info)); + + boost::shared_ptr<Presence> sentPresence(new Presence("blarb")); + stanzaChannel_->onPresenceReceived(sentPresence); + + CPPUNIT_ASSERT(!info.jid); + CPPUNIT_ASSERT(!info.reason); + CPPUNIT_ASSERT(out.two.get() == NULL); + CPPUNIT_ASSERT_EQUAL(sentPresence, out.one); + } + + void testSecondPresence() { + PresencePointerPair out; + oracle_->onPresenceChange.connect(boost::bind(&PresenceOracleTest::handlePresenceChange, this, _1, _2, &out)); + + boost::shared_ptr<Presence> sentPresence1(new Presence("blarb")); + stanzaChannel_->onPresenceReceived(sentPresence1); + CPPUNIT_ASSERT_EQUAL(sentPresence1, out.one); + out.one = boost::shared_ptr<Presence>(); + + SubscriptionRequestInfo info; + oracle_->onPresenceSubscriptionRequest.connect(boost::bind(&PresenceOracleTest::handlePresenceSubscriptionRequest, this, _1, _2, &info)); + + boost::shared_ptr<Presence> sentPresence2(new Presence("test2")); + stanzaChannel_->onPresenceReceived(sentPresence2); + + CPPUNIT_ASSERT(!info.jid); + CPPUNIT_ASSERT(!info.reason); + CPPUNIT_ASSERT_EQUAL(sentPresence1, out.two); + CPPUNIT_ASSERT_EQUAL(sentPresence2, out.one); + } + + void testSubscriptionRequest() { + PresencePointerPair out; + oracle_->onPresenceChange.connect(boost::bind(&PresenceOracleTest::handlePresenceChange, this, _1, _2, &out)); + SubscriptionRequestInfo info; + oracle_->onPresenceSubscriptionRequest.connect(boost::bind(&PresenceOracleTest::handlePresenceSubscriptionRequest, this, _1, _2, &info)); + + String reasonText = "Because I want to"; + JID sentJID = JID("me@example.com"); + + boost::shared_ptr<Presence> sentPresence(new Presence()); + sentPresence->setType(Presence::Subscribe); + sentPresence->setFrom(sentJID); + sentPresence->setStatus(reasonText); + stanzaChannel_->onPresenceReceived(sentPresence); + + CPPUNIT_ASSERT(info.jid); + CPPUNIT_ASSERT(info.reason); + CPPUNIT_ASSERT_EQUAL(sentJID, info.jid.get()); + CPPUNIT_ASSERT_EQUAL(reasonText, info.reason.get()); + + CPPUNIT_ASSERT(!out.two); + CPPUNIT_ASSERT(!out.one); + } +}; +CPPUNIT_TEST_SUITE_REGISTRATION(PresenceOracleTest); + diff --git a/Swiften/Presence/UnitTest/PresenceSenderTest.cpp b/Swiften/Presence/UnitTest/PresenceSenderTest.cpp new file mode 100644 index 0000000..1df0269 --- /dev/null +++ b/Swiften/Presence/UnitTest/PresenceSenderTest.cpp @@ -0,0 +1,120 @@ +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> + +#include "Swiften/Client/DummyStanzaChannel.h" +#include "Swiften/Presence/PresenceSender.h" + +using namespace Swift; + +class PresenceSenderTest : public CppUnit::TestFixture { + CPPUNIT_TEST_SUITE(PresenceSenderTest); + CPPUNIT_TEST(testSendPresence); + CPPUNIT_TEST(testSendPresence_UndirectedPresenceWithDirectedPresenceReceivers); + CPPUNIT_TEST(testSendPresence_DirectedPresenceWithDirectedPresenceReceivers); + CPPUNIT_TEST(testAddDirectedPresenceReceiver); + CPPUNIT_TEST(testAddDirectedPresenceReceiver_AfterSendingDirectedPresence); + CPPUNIT_TEST(testRemoveDirectedPresenceReceiver); + CPPUNIT_TEST_SUITE_END(); + + public: + void setUp() { + channel = new DummyStanzaChannel(); + testPresence = boost::shared_ptr<Presence>(new Presence()); + testPresence->setStatus("Foo"); + secondTestPresence = boost::shared_ptr<Presence>(new Presence()); + secondTestPresence->setStatus("Bar"); + } + + void tearDown() { + delete channel; + } + + void testSendPresence() { + std::auto_ptr<PresenceSender> testling(createPresenceSender()); + testling->sendPresence(testPresence); + + CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(channel->sentStanzas.size())); + boost::shared_ptr<Presence> presence = boost::dynamic_pointer_cast<Presence>(channel->sentStanzas[0]); + CPPUNIT_ASSERT(testPresence == presence); + } + + void testSendPresence_UndirectedPresenceWithDirectedPresenceReceivers() { + std::auto_ptr<PresenceSender> testling(createPresenceSender()); + testling->addDirectedPresenceReceiver(JID("alice@wonderland.lit/teaparty")); + + testling->sendPresence(testPresence); + + CPPUNIT_ASSERT_EQUAL(2, static_cast<int>(channel->sentStanzas.size())); + boost::shared_ptr<Presence> presence = boost::dynamic_pointer_cast<Presence>(channel->sentStanzas[0]); + CPPUNIT_ASSERT(testPresence == presence); + presence = boost::dynamic_pointer_cast<Presence>(channel->sentStanzas[1]); + CPPUNIT_ASSERT_EQUAL(testPresence->getStatus(), presence->getStatus()); + CPPUNIT_ASSERT_EQUAL(JID("alice@wonderland.lit/teaparty"), presence->getTo()); + } + + void testSendPresence_DirectedPresenceWithDirectedPresenceReceivers() { + std::auto_ptr<PresenceSender> testling(createPresenceSender()); + testling->addDirectedPresenceReceiver(JID("alice@wonderland.lit/teaparty")); + channel->sentStanzas.clear(); + + testPresence->setTo(JID("foo@bar.com")); + testling->sendPresence(testPresence); + + CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(channel->sentStanzas.size())); + boost::shared_ptr<Presence> presence = boost::dynamic_pointer_cast<Presence>(channel->sentStanzas[0]); + CPPUNIT_ASSERT(testPresence == presence); + } + + void testAddDirectedPresenceReceiver() { + std::auto_ptr<PresenceSender> testling(createPresenceSender()); + testling->sendPresence(testPresence); + channel->sentStanzas.clear(); + + testling->addDirectedPresenceReceiver(JID("alice@wonderland.lit/teaparty")); + + CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(channel->sentStanzas.size())); + boost::shared_ptr<Presence> presence = boost::dynamic_pointer_cast<Presence>(channel->sentStanzas[0]); + CPPUNIT_ASSERT_EQUAL(testPresence->getStatus(), presence->getStatus()); + CPPUNIT_ASSERT_EQUAL(JID("alice@wonderland.lit/teaparty"), presence->getTo()); + } + + void testAddDirectedPresenceReceiver_AfterSendingDirectedPresence() { + std::auto_ptr<PresenceSender> testling(createPresenceSender()); + testling->sendPresence(testPresence); + secondTestPresence->setTo(JID("foo@bar.com")); + testling->sendPresence(secondTestPresence); + channel->sentStanzas.clear(); + + testling->addDirectedPresenceReceiver(JID("alice@wonderland.lit/teaparty")); + + CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(channel->sentStanzas.size())); + boost::shared_ptr<Presence> presence = boost::dynamic_pointer_cast<Presence>(channel->sentStanzas[0]); + CPPUNIT_ASSERT_EQUAL(testPresence->getStatus(), presence->getStatus()); + CPPUNIT_ASSERT_EQUAL(JID("alice@wonderland.lit/teaparty"), presence->getTo()); + } + + void testRemoveDirectedPresenceReceiver() { + std::auto_ptr<PresenceSender> testling(createPresenceSender()); + testling->addDirectedPresenceReceiver(JID("alice@wonderland.lit/teaparty")); + channel->sentStanzas.clear(); + + testling->removeDirectedPresenceReceiver(JID("alice@wonderland.lit/teaparty")); + testling->sendPresence(testPresence); + + CPPUNIT_ASSERT_EQUAL(2, static_cast<int>(channel->sentStanzas.size())); + CPPUNIT_ASSERT_EQUAL(boost::dynamic_pointer_cast<Presence>(channel->sentStanzas[0])->getType(), Presence::Unavailable); + CPPUNIT_ASSERT(channel->sentStanzas[1] == testPresence); + } + + private: + PresenceSender* createPresenceSender() { + return new PresenceSender(channel); + } + + private: + DummyStanzaChannel* channel; + boost::shared_ptr<Presence> testPresence; + boost::shared_ptr<Presence> secondTestPresence; +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(PresenceSenderTest); diff --git a/Swiften/QA/ClientTest/.gitignore b/Swiften/QA/ClientTest/.gitignore new file mode 100644 index 0000000..9fb3e67 --- /dev/null +++ b/Swiften/QA/ClientTest/.gitignore @@ -0,0 +1 @@ +ClientTest diff --git a/Swiften/QA/ClientTest/ClientTest.cpp b/Swiften/QA/ClientTest/ClientTest.cpp new file mode 100644 index 0000000..74ec908 --- /dev/null +++ b/Swiften/QA/ClientTest/ClientTest.cpp @@ -0,0 +1,68 @@ +#include <boost/bind.hpp> +#include <boost/thread.hpp> + +#include "Swiften/Client/Client.h" +#include "Swiften/Network/BoostTimer.h" +#include "Swiften/EventLoop/MainEventLoop.h" +#include "Swiften/EventLoop/SimpleEventLoop.h" +#include "Swiften/Queries/Requests/GetRosterRequest.h" +#include "Swiften/Client/ClientXMLTracer.h" +#include "Swiften/Network/BoostIOServiceThread.h" +#include "Swiften/Network/MainBoostIOServiceThread.h" + +using namespace Swift; + +SimpleEventLoop eventLoop; + +Client* client = 0; +bool reconnected = false; +bool rosterReceived = false; + +void handleRosterReceived(boost::shared_ptr<Payload>) { + if (reconnected) { + rosterReceived = true; + client->disconnect(); + eventLoop.stop(); + } + else { + reconnected = true; + client->disconnect(); + client->connect(); + } +} + +void handleConnected() { + boost::shared_ptr<GetRosterRequest> rosterRequest(new GetRosterRequest(client)); + rosterRequest->onResponse.connect(boost::bind(&handleRosterReceived, _1)); + rosterRequest->send(); +} + +int main(int, char**) { + char* jid = getenv("SWIFT_CLIENTTEST_JID"); + if (!jid) { + std::cerr << "Please set the SWIFT_CLIENTTEST_JID environment variable" << std::endl; + return -1; + } + char* pass = getenv("SWIFT_CLIENTTEST_PASS"); + if (!pass) { + std::cerr << "Please set the SWIFT_CLIENTTEST_PASS environment variable" << std::endl; + return -1; + } + + client = new Swift::Client(JID(jid), String(pass)); + ClientXMLTracer* tracer = new ClientXMLTracer(client); + client->onConnected.connect(&handleConnected); + client->connect(); + + { + boost::shared_ptr<BoostTimer> timer(new BoostTimer(30000, &MainBoostIOServiceThread::getInstance().getIOService())); + timer->onTick.connect(boost::bind(&SimpleEventLoop::stop, &eventLoop)); + timer->start(); + + eventLoop.run(); + } + + delete tracer; + delete client; + return !rosterReceived; +} diff --git a/Swiften/QA/ClientTest/SConscript b/Swiften/QA/ClientTest/SConscript new file mode 100644 index 0000000..bc9cf95 --- /dev/null +++ b/Swiften/QA/ClientTest/SConscript @@ -0,0 +1,22 @@ +import os + +Import("env") + +if env["TEST"] : + myenv = env.Clone() + myenv.MergeFlags(myenv["SWIFTEN_FLAGS"]) + myenv.MergeFlags(myenv["CPPUNIT_FLAGS"]) + myenv.MergeFlags(myenv["LIBIDN_FLAGS"]) + myenv.MergeFlags(myenv["BOOST_FLAGS"]) + myenv.MergeFlags(myenv.get("SQLITE_FLAGS", "")) + myenv.MergeFlags(myenv["ZLIB_FLAGS"]) + myenv.MergeFlags(myenv["OPENSSL_FLAGS"]) + myenv.MergeFlags(myenv.get("LIBXML_FLAGS", "")) + myenv.MergeFlags(myenv.get("EXPAT_FLAGS", "")) + + for i in ["SWIFT_CLIENTTEST_JID", "SWIFT_CLIENTTEST_PASS"]: + if os.environ.get(i, "") : + myenv["ENV"][i] = os.environ[i] + + tester = myenv.Program("ClientTest", ["ClientTest.cpp"]) + myenv.Test(tester, "system") diff --git a/Swiften/QA/NetworkTest/.gitignore b/Swiften/QA/NetworkTest/.gitignore new file mode 100644 index 0000000..5a3caca --- /dev/null +++ b/Swiften/QA/NetworkTest/.gitignore @@ -0,0 +1 @@ +NetworkTest diff --git a/Swiften/QA/NetworkTest/BoostConnectionServerTest.cpp b/Swiften/QA/NetworkTest/BoostConnectionServerTest.cpp new file mode 100644 index 0000000..926c9ae --- /dev/null +++ b/Swiften/QA/NetworkTest/BoostConnectionServerTest.cpp @@ -0,0 +1,75 @@ +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> +#include <boost/shared_ptr.hpp> + +#include "Swiften/Base/String.h" +#include "Swiften/Network/BoostConnectionServer.h" +#include "Swiften/Network/BoostIOServiceThread.h" +#include "Swiften/EventLoop/DummyEventLoop.h" + +using namespace Swift; + +class BoostConnectionServerTest : public CppUnit::TestFixture { + CPPUNIT_TEST_SUITE(BoostConnectionServerTest); + CPPUNIT_TEST(testConstructor_TwoServersOnSamePort); + CPPUNIT_TEST(testStart_Conflict); + CPPUNIT_TEST(testStop); + CPPUNIT_TEST_SUITE_END(); + + public: + void setUp() { + boostIOServiceThread_ = new BoostIOServiceThread(); + eventLoop_ = new DummyEventLoop(); + stopped = false; + stoppedError.reset(); + } + + void tearDown() { + while (eventLoop_->hasEvents()) { + eventLoop_->processEvents(); + } + delete eventLoop_; + delete boostIOServiceThread_; + } + + void testConstructor_TwoServersOnSamePort() { + boost::shared_ptr<BoostConnectionServer> testling(new BoostConnectionServer(9999, &boostIOServiceThread_->getIOService())); + boost::shared_ptr<BoostConnectionServer> testling2(new BoostConnectionServer(9999, &boostIOServiceThread_->getIOService())); + } + + void testStart_Conflict() { + boost::shared_ptr<BoostConnectionServer> testling(new BoostConnectionServer(9999, &boostIOServiceThread_->getIOService())); + testling->start(); + + boost::shared_ptr<BoostConnectionServer> testling2(new BoostConnectionServer(9999, &boostIOServiceThread_->getIOService())); + testling2->onStopped.connect( + boost::bind(&BoostConnectionServerTest::handleStopped, this, _1)); + + testling->stop(); + } + + void testStop() { + boost::shared_ptr<BoostConnectionServer> testling(new BoostConnectionServer(9999, &boostIOServiceThread_->getIOService())); + testling->start(); + + testling->stop(); + + boost::shared_ptr<BoostConnectionServer> testling2(new BoostConnectionServer(9999, &boostIOServiceThread_->getIOService())); + testling2->start(); + + testling2->stop(); + } + + void handleStopped(boost::optional<BoostConnectionServer::Error> e) { + stopped = true; + stoppedError = e; + } + + private: + BoostIOServiceThread* boostIOServiceThread_; + DummyEventLoop* eventLoop_; + bool stopped; + boost::optional<BoostConnectionServer::Error> stoppedError; +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(BoostConnectionServerTest); diff --git a/Swiften/QA/NetworkTest/BoostConnectionTest.cpp b/Swiften/QA/NetworkTest/BoostConnectionTest.cpp new file mode 100644 index 0000000..af4e003 --- /dev/null +++ b/Swiften/QA/NetworkTest/BoostConnectionTest.cpp @@ -0,0 +1,88 @@ +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> +#include <boost/shared_ptr.hpp> + +#include "Swiften/Base/String.h" +#include "Swiften/Base/sleep.h" +#include "Swiften/Network/BoostConnection.h" +#include "Swiften/Network/HostAddress.h" +#include "Swiften/Network/HostAddressPort.h" +#include "Swiften/Network/BoostIOServiceThread.h" +#include "Swiften/EventLoop/DummyEventLoop.h" + +const unsigned char* address = reinterpret_cast<const unsigned char*>("\x41\x63\xde\x89"); + +using namespace Swift; + +class BoostConnectionTest : public CppUnit::TestFixture { + CPPUNIT_TEST_SUITE(BoostConnectionTest); + CPPUNIT_TEST(testDestructor); + CPPUNIT_TEST(testDestructor_PendingEvents); + CPPUNIT_TEST(testWrite); + CPPUNIT_TEST_SUITE_END(); + + public: + BoostConnectionTest() {} + + void setUp() { + boostIOServiceThread_ = new BoostIOServiceThread(); + eventLoop_ = new DummyEventLoop(); + disconnected = false; + } + + void tearDown() { + delete eventLoop_; + delete boostIOServiceThread_; + } + + void testDestructor() { + { + boost::shared_ptr<BoostConnection> testling(new BoostConnection(&boostIOServiceThread_->getIOService())); + testling->connect(HostAddressPort(HostAddress(address, 4), 5222)); + } + } + + void testDestructor_PendingEvents() { + { + boost::shared_ptr<BoostConnection> testling(new BoostConnection(&boostIOServiceThread_->getIOService())); + testling->connect(HostAddressPort(HostAddress(address, 4), 5222)); + while (!eventLoop_->hasEvents()) { + Swift::sleep(10); + } + } + eventLoop_->processEvents(); + } + + void testWrite() { + boost::shared_ptr<BoostConnection> testling(new BoostConnection(&boostIOServiceThread_->getIOService())); + testling->onConnectFinished.connect(boost::bind(&BoostConnectionTest::doWrite, this, testling.get())); + testling->onDataRead.connect(boost::bind(&BoostConnectionTest::handleDataRead, this, _1)); + testling->onDisconnected.connect(boost::bind(&BoostConnectionTest::handleDisconnected, this)); + testling->connect(HostAddressPort(HostAddress("65.99.222.137"), 5222)); + while (receivedData.isEmpty()) { + Swift::sleep(10); + eventLoop_->processEvents(); + } + testling->disconnect(); + } + + void doWrite(BoostConnection* connection) { + connection->write(ByteArray("<stream:stream>")); + } + + void handleDataRead(const ByteArray& data) { + receivedData += data; + } + + void handleDisconnected() { + disconnected = true; + } + + private: + BoostIOServiceThread* boostIOServiceThread_; + DummyEventLoop* eventLoop_; + ByteArray receivedData; + bool disconnected; +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(BoostConnectionTest); diff --git a/Swiften/QA/NetworkTest/DomainNameResolverTest.cpp b/Swiften/QA/NetworkTest/DomainNameResolverTest.cpp new file mode 100644 index 0000000..09837d6 --- /dev/null +++ b/Swiften/QA/NetworkTest/DomainNameResolverTest.cpp @@ -0,0 +1,168 @@ +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> +#include <boost/bind.hpp> + +#include "Swiften/Base/sleep.h" +#include "Swiften/Base/String.h" +#include "Swiften/Base/ByteArray.h" +#include "Swiften/Network/PlatformDomainNameResolver.h" +#include "Swiften/Network/DomainNameAddressQuery.h" +#include "Swiften/Network/DomainNameServiceQuery.h" +#include "Swiften/EventLoop/DummyEventLoop.h" + +using namespace Swift; + +class DomainNameResolverTest : public CppUnit::TestFixture { + CPPUNIT_TEST_SUITE(DomainNameResolverTest); + CPPUNIT_TEST(testResolveAddress); + CPPUNIT_TEST(testResolveAddress_Error); + //CPPUNIT_TEST(testResolveAddress_IPv6); + CPPUNIT_TEST(testResolveAddress_International); + CPPUNIT_TEST(testResolveAddress_Localhost); + CPPUNIT_TEST(testResolveService); + CPPUNIT_TEST(testResolveService_Error); + CPPUNIT_TEST_SUITE_END(); + + public: + DomainNameResolverTest() {} + + void setUp() { + eventLoop = new DummyEventLoop(); + resolver = new PlatformDomainNameResolver(); + resultsAvailable = false; + } + + void tearDown() { + delete resolver; + delete eventLoop; + } + + void testResolveAddress() { + boost::shared_ptr<DomainNameAddressQuery> query(createAddressQuery("xmpp.test.swift.im")); + + query->run(); + waitForResults(); + + CPPUNIT_ASSERT(!addressQueryError); + CPPUNIT_ASSERT_EQUAL(std::string("10.0.0.0"), addressQueryResult.toString()); + } + + void testResolveAddress_Error() { + boost::shared_ptr<DomainNameAddressQuery> query(createAddressQuery("invalid.test.swift.im")); + + query->run(); + waitForResults(); + + CPPUNIT_ASSERT(addressQueryError); + } + + void testResolveAddress_IPv6() { + boost::shared_ptr<DomainNameAddressQuery> query(createAddressQuery("xmpp-ipv6.test.swift.im")); + + query->run(); + waitForResults(); + + CPPUNIT_ASSERT(!addressQueryError); + CPPUNIT_ASSERT_EQUAL(std::string("0000:0000:0000:0000:0000:ffff:0a00:0104"), addressQueryResult.toString()); + } + + void testResolveAddress_International() { + boost::shared_ptr<DomainNameAddressQuery> query(createAddressQuery("tron\xc3\xa7on.test.swift.im")); + + query->run(); + waitForResults(); + + CPPUNIT_ASSERT(!addressQueryError); + CPPUNIT_ASSERT_EQUAL(std::string("10.0.0.3"), addressQueryResult.toString()); + } + + void testResolveAddress_Localhost() { + boost::shared_ptr<DomainNameAddressQuery> query(createAddressQuery("localhost")); + + query->run(); + waitForResults(); + + CPPUNIT_ASSERT(!addressQueryError); + CPPUNIT_ASSERT_EQUAL(std::string("127.0.0.1"), addressQueryResult.toString()); + } + + + void testResolveService() { + boost::shared_ptr<DomainNameServiceQuery> query(createServiceQuery("_xmpp-client._tcp.xmpp-srv.test.swift.im")); + + query->run(); + waitForResults(); + + CPPUNIT_ASSERT_EQUAL(4, static_cast<int>(serviceQueryResult.size())); + CPPUNIT_ASSERT_EQUAL(String("xmpp1.test.swift.im"), serviceQueryResult[0].hostname); + CPPUNIT_ASSERT_EQUAL(5000, serviceQueryResult[0].port); + CPPUNIT_ASSERT_EQUAL(0, serviceQueryResult[0].priority); + CPPUNIT_ASSERT_EQUAL(1, serviceQueryResult[0].weight); + CPPUNIT_ASSERT_EQUAL(String("xmpp-invalid.test.swift.im"), serviceQueryResult[1].hostname); + CPPUNIT_ASSERT_EQUAL(5000, serviceQueryResult[1].port); + CPPUNIT_ASSERT_EQUAL(1, serviceQueryResult[1].priority); + CPPUNIT_ASSERT_EQUAL(100, serviceQueryResult[1].weight); + CPPUNIT_ASSERT_EQUAL(String("xmpp3.test.swift.im"), serviceQueryResult[2].hostname); + CPPUNIT_ASSERT_EQUAL(5000, serviceQueryResult[2].port); + CPPUNIT_ASSERT_EQUAL(3, serviceQueryResult[2].priority); + CPPUNIT_ASSERT_EQUAL(100, serviceQueryResult[2].weight); + CPPUNIT_ASSERT_EQUAL(String("xmpp2.test.swift.im"), serviceQueryResult[3].hostname); + CPPUNIT_ASSERT_EQUAL(5000, serviceQueryResult[3].port); + CPPUNIT_ASSERT_EQUAL(5, serviceQueryResult[3].priority); + CPPUNIT_ASSERT_EQUAL(100, serviceQueryResult[3].weight); + } + + void testResolveService_Error() { + } + +/* + } + */ + + private: + boost::shared_ptr<DomainNameAddressQuery> createAddressQuery(const String& domain) { + boost::shared_ptr<DomainNameAddressQuery> result = resolver->createAddressQuery(domain); + result->onResult.connect(boost::bind(&DomainNameResolverTest::handleAddressQueryResult, this, _1, _2)); + return result; + } + + void handleAddressQueryResult(const HostAddress& address, boost::optional<DomainNameResolveError> error) { + addressQueryResult = address; + addressQueryError = error; + resultsAvailable = true; + } + + boost::shared_ptr<DomainNameServiceQuery> createServiceQuery(const String& domain) { + boost::shared_ptr<DomainNameServiceQuery> result = resolver->createServiceQuery(domain); + result->onResult.connect(boost::bind(&DomainNameResolverTest::handleServiceQueryResult, this, _1)); + return result; + } + + void handleServiceQueryResult(const std::vector<DomainNameServiceQuery::Result>& result) { + serviceQueryResult = result; + resultsAvailable = true; + } + + void waitForResults() { + eventLoop->processEvents(); + int ticks = 0; + while (!resultsAvailable) { + ticks++; + if (ticks > 1000) { + CPPUNIT_ASSERT(false); + } + Swift::sleep(10); + eventLoop->processEvents(); + } + } + + private: + DummyEventLoop* eventLoop; + bool resultsAvailable; + HostAddress addressQueryResult; + boost::optional<DomainNameResolveError> addressQueryError; + std::vector<DomainNameServiceQuery::Result> serviceQueryResult; + PlatformDomainNameResolver* resolver; +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(DomainNameResolverTest); diff --git a/Swiften/QA/NetworkTest/SConscript b/Swiften/QA/NetworkTest/SConscript new file mode 100644 index 0000000..871df39 --- /dev/null +++ b/Swiften/QA/NetworkTest/SConscript @@ -0,0 +1,18 @@ +import os + +Import("env") + +if env["TEST"] : + myenv = env.Clone() + myenv.MergeFlags(myenv["CHECKER_FLAGS"]) + myenv.MergeFlags(myenv["SWIFTEN_FLAGS"]) + myenv.MergeFlags(myenv["CPPUNIT_FLAGS"]) + myenv.MergeFlags(myenv["BOOST_FLAGS"]) + myenv.MergeFlags(myenv["LIBIDN_FLAGS"]) + + tester = myenv.Program("NetworkTest", [ + "BoostConnectionServerTest.cpp", + "BoostConnectionTest.cpp", + "DomainNameResolverTest.cpp", + ]) + myenv.Test(tester, "system") diff --git a/Swiften/QA/SConscript b/Swiften/QA/SConscript new file mode 100644 index 0000000..ede7b39 --- /dev/null +++ b/Swiften/QA/SConscript @@ -0,0 +1,4 @@ +SConscript([ + "NetworkTest/SConscript", + "ClientTest/SConscript", + ]) diff --git a/Swiften/Queries/DummyIQChannel.h b/Swiften/Queries/DummyIQChannel.h new file mode 100644 index 0000000..c0a4a25 --- /dev/null +++ b/Swiften/Queries/DummyIQChannel.h @@ -0,0 +1,29 @@ +#ifndef SWIFTEN_DummyIQChannel_H +#define SWIFTEN_DummyIQChannel_H + +#include <vector> + +#include "Swiften/Queries/IQChannel.h" + +namespace Swift { + class DummyIQChannel : public IQChannel { + public: + DummyIQChannel() {} + + virtual void sendIQ(boost::shared_ptr<IQ> iq) { + iqs_.push_back(iq); + } + + virtual String getNewIQID() { + return "test-id"; + } + + virtual bool isAvailable() { + return true; + } + + std::vector<boost::shared_ptr<IQ> > iqs_; + }; +} + +#endif diff --git a/Swiften/Queries/GenericRequest.h b/Swiften/Queries/GenericRequest.h new file mode 100644 index 0000000..77dae52 --- /dev/null +++ b/Swiften/Queries/GenericRequest.h @@ -0,0 +1,26 @@ +#pragma once + +#include <boost/signal.hpp> + +#include "Swiften/Queries/Request.h" + +namespace Swift { + template<typename PAYLOAD_TYPE> + class GenericRequest : public Request { + public: + GenericRequest( + IQ::Type type, + const JID& receiver, + boost::shared_ptr<Payload> payload, + IQRouter* router) : + Request(type, receiver, payload, router) { + } + + virtual void handleResponse(boost::shared_ptr<Payload> payload, boost::optional<ErrorPayload> error) { + onResponse(boost::dynamic_pointer_cast<PAYLOAD_TYPE>(payload), error); + } + + public: + boost::signal<void (boost::shared_ptr<PAYLOAD_TYPE>, const boost::optional<ErrorPayload>&)> onResponse; + }; +} diff --git a/Swiften/Queries/GetResponder.h b/Swiften/Queries/GetResponder.h new file mode 100644 index 0000000..2bd2fe2 --- /dev/null +++ b/Swiften/Queries/GetResponder.h @@ -0,0 +1,14 @@ +#pragma once + +#include "Swiften/Queries/Responder.h" + +namespace Swift { + template<typename T> + class GetResponder : public Responder<T> { + public: + GetResponder(IQRouter* router) : Responder<T>(router) {} + + private: + virtual bool handleSetRequest(const JID&, const String&, boost::shared_ptr<T>) { return false; } + }; +} diff --git a/Swiften/Queries/IQChannel.cpp b/Swiften/Queries/IQChannel.cpp new file mode 100644 index 0000000..539dea0 --- /dev/null +++ b/Swiften/Queries/IQChannel.cpp @@ -0,0 +1,8 @@ +#include "Swiften/Queries/IQChannel.h" + +namespace Swift { + +IQChannel::~IQChannel() { +} + +} diff --git a/Swiften/Queries/IQChannel.h b/Swiften/Queries/IQChannel.h new file mode 100644 index 0000000..1692afe --- /dev/null +++ b/Swiften/Queries/IQChannel.h @@ -0,0 +1,24 @@ +#ifndef SWIFTEN_IQChannel_H +#define SWIFTEN_IQChannel_H + +#include <boost/signal.hpp> +#include <boost/shared_ptr.hpp> + +#include "Swiften/Base/String.h" +#include "Swiften/Elements/IQ.h" + +namespace Swift { + class IQChannel { + public: + virtual ~IQChannel(); + + virtual void sendIQ(boost::shared_ptr<IQ>) = 0; + virtual String getNewIQID() = 0; + + virtual bool isAvailable() = 0; + + boost::signal<void (boost::shared_ptr<IQ>)> onIQReceived; + }; +} + +#endif diff --git a/Swiften/Queries/IQHandler.cpp b/Swiften/Queries/IQHandler.cpp new file mode 100644 index 0000000..81bd773 --- /dev/null +++ b/Swiften/Queries/IQHandler.cpp @@ -0,0 +1,9 @@ +#include "Swiften/Queries/IQHandler.h" +#include "Swiften/Queries/IQRouter.h" + +namespace Swift { + +IQHandler::~IQHandler() { +} + +} diff --git a/Swiften/Queries/IQHandler.h b/Swiften/Queries/IQHandler.h new file mode 100644 index 0000000..7389b3a --- /dev/null +++ b/Swiften/Queries/IQHandler.h @@ -0,0 +1,19 @@ +#ifndef SWIFTEN_IQHandler_H +#define SWIFTEN_IQHandler_H + +#include <boost/shared_ptr.hpp> + +#include "Swiften/Elements/IQ.h" + +namespace Swift { + class IQRouter; + + class IQHandler { + public: + virtual ~IQHandler(); + + virtual bool handleIQ(boost::shared_ptr<IQ>) = 0; + }; +} + +#endif diff --git a/Swiften/Queries/IQRouter.cpp b/Swiften/Queries/IQRouter.cpp new file mode 100644 index 0000000..fdfa00b --- /dev/null +++ b/Swiften/Queries/IQRouter.cpp @@ -0,0 +1,81 @@ +#include "Swiften/Queries/IQRouter.h" + +#include <algorithm> +#include <boost/bind.hpp> + +#include "Swiften/Base/foreach.h" +#include "Swiften/Queries/IQHandler.h" +#include "Swiften/Queries/IQChannel.h" +#include "Swiften/Elements/ErrorPayload.h" + +namespace Swift { + +static void noop(IQHandler*) {} + +IQRouter::IQRouter(IQChannel* channel) : channel_(channel), queueRemoves_(false) { + channel->onIQReceived.connect(boost::bind(&IQRouter::handleIQ, this, _1)); +} + +IQRouter::~IQRouter() { +} + +bool IQRouter::isAvailable() { + return channel_->isAvailable(); +} + +void IQRouter::handleIQ(boost::shared_ptr<IQ> iq) { + queueRemoves_ = true; + + bool handled = false; + foreach(boost::shared_ptr<IQHandler> handler, handlers_) { + handled |= handler->handleIQ(iq); + if (handled) { + break; + } + } + if (!handled && (iq->getType() == IQ::Get || iq->getType() == IQ::Set) ) { + channel_->sendIQ(IQ::createError(iq->getFrom(), iq->getID(), ErrorPayload::FeatureNotImplemented, ErrorPayload::Cancel)); + } + + processPendingRemoves(); + + queueRemoves_ = false; +} + +void IQRouter::processPendingRemoves() { + foreach(boost::shared_ptr<IQHandler> handler, queuedRemoves_) { + handlers_.erase(std::remove(handlers_.begin(), handlers_.end(), handler), handlers_.end()); + } + queuedRemoves_.clear(); +} + +void IQRouter::addHandler(IQHandler* handler) { + addHandler(boost::shared_ptr<IQHandler>(handler, noop)); +} + +void IQRouter::removeHandler(IQHandler* handler) { + removeHandler(boost::shared_ptr<IQHandler>(handler, noop)); +} + +void IQRouter::addHandler(boost::shared_ptr<IQHandler> handler) { + handlers_.push_back(handler); +} + +void IQRouter::removeHandler(boost::shared_ptr<IQHandler> handler) { + if (queueRemoves_) { + queuedRemoves_.push_back(handler); + } + else { + handlers_.erase(std::remove(handlers_.begin(), handlers_.end(), handler), handlers_.end()); + } +} + +void IQRouter::sendIQ(boost::shared_ptr<IQ> iq) { + channel_->sendIQ(iq); +} + +String IQRouter::getNewIQID() { + return channel_->getNewIQID(); +} + +} diff --git a/Swiften/Queries/IQRouter.h b/Swiften/Queries/IQRouter.h new file mode 100644 index 0000000..717a845 --- /dev/null +++ b/Swiften/Queries/IQRouter.h @@ -0,0 +1,41 @@ +#ifndef SWIFTEN_IQRouter_H +#define SWIFTEN_IQRouter_H + +#include <boost/shared_ptr.hpp> +#include <vector> + +#include "Swiften/Base/String.h" +#include "Swiften/Elements/IQ.h" + +namespace Swift { + class IQChannel; + class IQHandler; + + class IQRouter { + public: + IQRouter(IQChannel* channel); + ~IQRouter(); + + void addHandler(IQHandler* handler); + void removeHandler(IQHandler* handler); + void addHandler(boost::shared_ptr<IQHandler> handler); + void removeHandler(boost::shared_ptr<IQHandler> handler); + + void sendIQ(boost::shared_ptr<IQ> iq); + String getNewIQID(); + + bool isAvailable(); + + private: + void handleIQ(boost::shared_ptr<IQ> iq); + void processPendingRemoves(); + + private: + IQChannel* channel_; + std::vector< boost::shared_ptr<IQHandler> > handlers_; + std::vector< boost::shared_ptr<IQHandler> > queuedRemoves_; + bool queueRemoves_; + }; +} + +#endif diff --git a/Swiften/Queries/Request.cpp b/Swiften/Queries/Request.cpp new file mode 100644 index 0000000..ce9f763 --- /dev/null +++ b/Swiften/Queries/Request.cpp @@ -0,0 +1,55 @@ +#include "Swiften/Queries/Request.h" +#include "Swiften/Queries/IQRouter.h" +#include "Swiften/EventLoop/MainEventLoop.h" + +namespace Swift { + +Request::Request(IQ::Type type, const JID& receiver, boost::shared_ptr<Payload> payload, IQRouter* router) : router_(router), type_(type), receiver_(receiver), payload_(payload), sent_(false) { +} + +Request::Request(IQ::Type type, const JID& receiver, IQRouter* router) : router_(router), type_(type), receiver_(receiver), sent_(false) { +} + +void Request::send() { + assert(payload_); + assert(!sent_); + sent_ = true; + + boost::shared_ptr<IQ> iq(new IQ(type_)); + iq->setTo(receiver_); + iq->addPayload(payload_); + id_ = router_->getNewIQID(); + iq->setID(id_); + + try { + router_->addHandler(shared_from_this()); + } + catch (const std::exception&) { + router_->addHandler(this); + } + + router_->sendIQ(iq); +} + +bool Request::handleIQ(boost::shared_ptr<IQ> iq) { + bool handled = false; + if (sent_ && iq->getID() == id_) { + if (iq->getType() == IQ::Result) { + handleResponse(iq->getPayloadOfSameType(payload_), boost::optional<ErrorPayload>()); + } + else { + boost::shared_ptr<ErrorPayload> errorPayload = iq->getPayload<ErrorPayload>(); + if (errorPayload) { + handleResponse(boost::shared_ptr<Payload>(), boost::optional<ErrorPayload>(*errorPayload)); + } + else { + handleResponse(boost::shared_ptr<Payload>(), boost::optional<ErrorPayload>(ErrorPayload::UndefinedCondition)); + } + } + router_->removeHandler(this); + handled = true; + } + return handled; +} + +} diff --git a/Swiften/Queries/Request.h b/Swiften/Queries/Request.h new file mode 100644 index 0000000..cc4a58e --- /dev/null +++ b/Swiften/Queries/Request.h @@ -0,0 +1,50 @@ +#ifndef SWIFTEN_Request_H +#define SWIFTEN_Request_H + +#include <boost/shared_ptr.hpp> +#include <boost/optional.hpp> +#include <boost/enable_shared_from_this.hpp> + +#include "Swiften/Base/String.h" +#include "Swiften/Queries/IQHandler.h" +#include "Swiften/Elements/IQ.h" +#include "Swiften/Elements/Payload.h" +#include "Swiften/Elements/ErrorPayload.h" +#include "Swiften/JID/JID.h" + +namespace Swift { + class Request : public IQHandler, public boost::enable_shared_from_this<Request> { + public: + Request( + IQ::Type type, + const JID& receiver, + boost::shared_ptr<Payload> payload, + IQRouter* router); + Request( + IQ::Type type, + const JID& receiver, + IQRouter* router); + + void send(); + + protected: + virtual void setPayload(boost::shared_ptr<Payload> p) { + payload_ = p; + } + + virtual void handleResponse(boost::shared_ptr<Payload>, boost::optional<ErrorPayload>) = 0; + + private: + bool handleIQ(boost::shared_ptr<IQ>); + + private: + IQRouter* router_; + IQ::Type type_; + JID receiver_; + boost::shared_ptr<Payload> payload_; + String id_; + bool sent_; + }; +} + +#endif diff --git a/Swiften/Queries/Requests/GetDiscoInfoRequest.h b/Swiften/Queries/Requests/GetDiscoInfoRequest.h new file mode 100644 index 0000000..70f09ca --- /dev/null +++ b/Swiften/Queries/Requests/GetDiscoInfoRequest.h @@ -0,0 +1,13 @@ +#pragma once + +#include "Swiften/Queries/GenericRequest.h" +#include "Swiften/Elements/DiscoInfo.h" + +namespace Swift { + class GetDiscoInfoRequest : public GenericRequest<DiscoInfo> { + public: + GetDiscoInfoRequest(const JID& jid, IQRouter* router) : + GenericRequest<DiscoInfo>(IQ::Get, jid, boost::shared_ptr<DiscoInfo>(new DiscoInfo()), router) { + } + }; +} diff --git a/Swiften/Queries/Requests/GetPrivateStorageRequest.h b/Swiften/Queries/Requests/GetPrivateStorageRequest.h new file mode 100644 index 0000000..5d6440e --- /dev/null +++ b/Swiften/Queries/Requests/GetPrivateStorageRequest.h @@ -0,0 +1,30 @@ +#pragma once + +#include <boost/signals.hpp> +#include <boost/shared_ptr.hpp> + +#include "Swiften/Queries/Request.h" +#include "Swiften/Elements/PrivateStorage.h" +#include "Swiften/Elements/ErrorPayload.h" + +namespace Swift { + template<typename PAYLOAD_TYPE> + class GetPrivateStorageRequest : public Request { + public: + GetPrivateStorageRequest(IQRouter* router) : Request(IQ::Get, JID(), boost::shared_ptr<PrivateStorage>(new PrivateStorage(boost::shared_ptr<Payload>(new PAYLOAD_TYPE()))), router) { + } + + virtual void handleResponse(boost::shared_ptr<Payload> payload, boost::optional<ErrorPayload> error) { + boost::shared_ptr<PrivateStorage> storage = boost::dynamic_pointer_cast<PrivateStorage>(payload); + if (storage) { + onResponse(boost::dynamic_pointer_cast<PAYLOAD_TYPE>(storage->getPayload()), error); + } + else { + onResponse(boost::shared_ptr<PAYLOAD_TYPE>(), error); + } + } + + public: + boost::signal<void (boost::shared_ptr<PAYLOAD_TYPE>, const boost::optional<ErrorPayload>&)> onResponse; + }; +} diff --git a/Swiften/Queries/Requests/GetRosterRequest.h b/Swiften/Queries/Requests/GetRosterRequest.h new file mode 100644 index 0000000..cac1c12 --- /dev/null +++ b/Swiften/Queries/Requests/GetRosterRequest.h @@ -0,0 +1,13 @@ +#pragma once + +#include "Swiften/Queries/GenericRequest.h" +#include "Swiften/Elements/RosterPayload.h" + +namespace Swift { + class GetRosterRequest : public GenericRequest<RosterPayload> { + public: + GetRosterRequest(IQRouter* router) : + GenericRequest<RosterPayload>(IQ::Get, JID(), boost::shared_ptr<Payload>(new RosterPayload()), router) { + } + }; +} diff --git a/Swiften/Queries/Requests/GetSecurityLabelsCatalogRequest.h b/Swiften/Queries/Requests/GetSecurityLabelsCatalogRequest.h new file mode 100644 index 0000000..87b51ce --- /dev/null +++ b/Swiften/Queries/Requests/GetSecurityLabelsCatalogRequest.h @@ -0,0 +1,16 @@ +#pragma once + +#include "Swiften/Queries/GenericRequest.h" +#include "Swiften/Elements/SecurityLabelsCatalog.h" + +namespace Swift { + class GetSecurityLabelsCatalogRequest : public GenericRequest<SecurityLabelsCatalog> { + public: + GetSecurityLabelsCatalogRequest( + const JID& recipient, + IQRouter* router) : + GenericRequest<SecurityLabelsCatalog>( + IQ::Get, JID(), boost::shared_ptr<SecurityLabelsCatalog>(new SecurityLabelsCatalog(recipient)), router) { + } + }; +} diff --git a/Swiften/Queries/Requests/GetVCardRequest.h b/Swiften/Queries/Requests/GetVCardRequest.h new file mode 100644 index 0000000..8fc6e17 --- /dev/null +++ b/Swiften/Queries/Requests/GetVCardRequest.h @@ -0,0 +1,12 @@ +#pragma once + +#include "Swiften/Queries/GenericRequest.h" +#include "Swiften/Elements/VCard.h" + +namespace Swift { + class GetVCardRequest : public GenericRequest<VCard> { + public: + GetVCardRequest(const JID& jid, IQRouter* router) : GenericRequest<VCard>(IQ::Get, jid, boost::shared_ptr<Payload>(new VCard()), router) { + } + }; +} diff --git a/Swiften/Queries/Requests/SetPrivateStorageRequest.h b/Swiften/Queries/Requests/SetPrivateStorageRequest.h new file mode 100644 index 0000000..834ddd8 --- /dev/null +++ b/Swiften/Queries/Requests/SetPrivateStorageRequest.h @@ -0,0 +1,24 @@ +#pragma once + +#include <boost/signals.hpp> +#include <boost/shared_ptr.hpp> + +#include "Swiften/Queries/Request.h" +#include "Swiften/Elements/PrivateStorage.h" +#include "Swiften/Elements/ErrorPayload.h" + +namespace Swift { + template<typename PAYLOAD_TYPE> + class SetPrivateStorageRequest : public Request { + public: + SetPrivateStorageRequest(boost::shared_ptr<PAYLOAD_TYPE> payload, IQRouter* router) : Request(IQ::Set, JID(), boost::shared_ptr<PrivateStorage>(new PrivateStorage(payload)), router) { + } + + virtual void handleResponse(boost::shared_ptr<Payload> payload, boost::optional<ErrorPayload> error) { + onResponse(error); + } + + public: + boost::signal<void (const boost::optional<ErrorPayload>&)> onResponse; + }; +} diff --git a/Swiften/Queries/Requests/UnitTest/GetPrivateStorageRequestTest.cpp b/Swiften/Queries/Requests/UnitTest/GetPrivateStorageRequestTest.cpp new file mode 100644 index 0000000..a86a111 --- /dev/null +++ b/Swiften/Queries/Requests/UnitTest/GetPrivateStorageRequestTest.cpp @@ -0,0 +1,106 @@ +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> +#include <boost/shared_ptr.hpp> +#include <boost/bind.hpp> + +#include "Swiften/Queries/Requests/GetPrivateStorageRequest.h" +#include "Swiften/Queries/IQRouter.h" +#include "Swiften/Queries/DummyIQChannel.h" +#include "Swiften/Elements/Payload.h" + +using namespace Swift; + +class GetPrivateStorageRequestTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(GetPrivateStorageRequestTest); + CPPUNIT_TEST(testSend); + CPPUNIT_TEST(testHandleResponse); + CPPUNIT_TEST(testHandleResponse_Error); + CPPUNIT_TEST_SUITE_END(); + + public: + class MyPayload : public Payload { + public: + MyPayload(const String& text = "") : text(text) {} + String text; + }; + + public: + GetPrivateStorageRequestTest() {} + + void setUp() { + channel = new DummyIQChannel(); + router = new IQRouter(channel); + } + + void tearDown() { + delete router; + delete channel; + } + + void testSend() { + GetPrivateStorageRequest<MyPayload> request(router); + request.send(); + + CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(channel->iqs_.size())); + CPPUNIT_ASSERT_EQUAL(JID(), channel->iqs_[0]->getTo()); + CPPUNIT_ASSERT_EQUAL(IQ::Get, channel->iqs_[0]->getType()); + boost::shared_ptr<PrivateStorage> storage = channel->iqs_[0]->getPayload<PrivateStorage>(); + CPPUNIT_ASSERT(storage); + boost::shared_ptr<MyPayload> payload = boost::dynamic_pointer_cast<MyPayload>(storage->getPayload()); + CPPUNIT_ASSERT(payload); + } + + void testHandleResponse() { + GetPrivateStorageRequest<MyPayload> testling(router); + testling.onResponse.connect(boost::bind(&GetPrivateStorageRequestTest::handleResponse, this, _1, _2)); + testling.send(); + channel->onIQReceived(createResponse("test-id", "foo")); + + CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(responses.size())); + CPPUNIT_ASSERT_EQUAL(String("foo"), boost::dynamic_pointer_cast<MyPayload>(responses[0])->text); + } + + void testHandleResponse_Error() { + GetPrivateStorageRequest<MyPayload> testling(router); + testling.onResponse.connect(boost::bind(&GetPrivateStorageRequestTest::handleResponse, this, _1, _2)); + testling.send(); + channel->onIQReceived(createError("test-id")); + + CPPUNIT_ASSERT_EQUAL(0, static_cast<int>(responses.size())); + CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(errors.size())); + } + + private: + void handleResponse(boost::shared_ptr<Payload> p, const boost::optional<ErrorPayload>& e) { + if (e) { + errors.push_back(*e); + } + else { + responses.push_back(p); + } + } + + boost::shared_ptr<IQ> createResponse(const String& id, const String& text) { + boost::shared_ptr<IQ> iq(new IQ(IQ::Result)); + boost::shared_ptr<PrivateStorage> storage(new PrivateStorage()); + storage->setPayload(boost::shared_ptr<Payload>(new MyPayload(text))); + iq->addPayload(storage); + iq->setID(id); + return iq; + } + + boost::shared_ptr<IQ> createError(const String& id) { + boost::shared_ptr<IQ> iq(new IQ(IQ::Error)); + iq->setID(id); + return iq; + } + + private: + IQRouter* router; + DummyIQChannel* channel; + std::vector< ErrorPayload > errors; + std::vector< boost::shared_ptr<Payload> > responses; +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(GetPrivateStorageRequestTest); diff --git a/Swiften/Queries/Responder.h b/Swiften/Queries/Responder.h new file mode 100644 index 0000000..9c025eb --- /dev/null +++ b/Swiften/Queries/Responder.h @@ -0,0 +1,58 @@ +#ifndef SWIFTEN_Responder_H +#define SWIFTEN_Responder_H + +#include "Swiften/Queries/IQHandler.h" +#include "Swiften/Queries/IQRouter.h" +#include "Swiften/Elements/ErrorPayload.h" + +namespace Swift { + template<typename PAYLOAD_TYPE> + class Responder : public IQHandler { + public: + Responder(IQRouter* router) : router_(router) { + router_->addHandler(this); + } + + ~Responder() { + router_->removeHandler(this); + } + + protected: + virtual bool handleGetRequest(const JID& from, const String& id, boost::shared_ptr<PAYLOAD_TYPE> payload) = 0; + virtual bool handleSetRequest(const JID& from, const String& id, boost::shared_ptr<PAYLOAD_TYPE> payload) = 0; + + void sendResponse(const JID& to, const String& id, boost::shared_ptr<Payload> payload) { + router_->sendIQ(IQ::createResult(to, id, payload)); + } + + void sendError(const JID& to, const String& id, ErrorPayload::Condition condition, ErrorPayload::Type type) { + router_->sendIQ(IQ::createError(to, id, condition, type)); + } + + private: + virtual bool handleIQ(boost::shared_ptr<IQ> iq) { + if (iq->getType() == IQ::Set || iq->getType() == IQ::Get) { + boost::shared_ptr<PAYLOAD_TYPE> payload(iq->getPayload<PAYLOAD_TYPE>()); + if (payload) { + bool result; + if (iq->getType() == IQ::Set) { + result = handleSetRequest(iq->getFrom(), iq->getID(), payload); + } + else { + result = handleGetRequest(iq->getFrom(), iq->getID(), payload); + } + if (!result) { + router_->sendIQ(IQ::createError(iq->getFrom(), iq->getID(), ErrorPayload::NotAllowed, ErrorPayload::Cancel)); + } + return true; + } + } + return false; + } + + private: + IQRouter* router_; + }; +} + +#endif diff --git a/Swiften/Queries/Responders/DiscoInfoResponder.cpp b/Swiften/Queries/Responders/DiscoInfoResponder.cpp new file mode 100644 index 0000000..572f83f --- /dev/null +++ b/Swiften/Queries/Responders/DiscoInfoResponder.cpp @@ -0,0 +1,36 @@ +#include "Swiften/Queries/Responders/DiscoInfoResponder.h" +#include "Swiften/Queries/IQRouter.h" +#include "Swiften/Elements/DiscoInfo.h" + +namespace Swift { + +DiscoInfoResponder::DiscoInfoResponder(IQRouter* router) : GetResponder<DiscoInfo>(router) { +} + +void DiscoInfoResponder::setDiscoInfo(const DiscoInfo& info) { + info_ = info; +} + +void DiscoInfoResponder::setDiscoInfo(const String& node, const DiscoInfo& info) { + DiscoInfo newInfo(info); + newInfo.setNode(node); + nodeInfo_[node] = newInfo; +} + +bool DiscoInfoResponder::handleGetRequest(const JID& from, const String& id, boost::shared_ptr<DiscoInfo> info) { + if (info->getNode().isEmpty()) { + sendResponse(from, id, boost::shared_ptr<DiscoInfo>(new DiscoInfo(info_))); + } + else { + std::map<String,DiscoInfo>::const_iterator i = nodeInfo_.find(info->getNode()); + if (i != nodeInfo_.end()) { + sendResponse(from, id, boost::shared_ptr<DiscoInfo>(new DiscoInfo((*i).second))); + } + else { + sendError(from, id, ErrorPayload::ItemNotFound, ErrorPayload::Cancel); + } + } + return true; +} + +} diff --git a/Swiften/Queries/Responders/DiscoInfoResponder.h b/Swiften/Queries/Responders/DiscoInfoResponder.h new file mode 100644 index 0000000..3270d5d --- /dev/null +++ b/Swiften/Queries/Responders/DiscoInfoResponder.h @@ -0,0 +1,28 @@ +#ifndef SWIFTEN_DiscoInfoResponder_H +#define SWIFTEN_DiscoInfoResponder_H + +#include <map> + +#include "Swiften/Queries/GetResponder.h" +#include "Swiften/Elements/DiscoInfo.h" + +namespace Swift { + class IQRouter; + + class DiscoInfoResponder : public GetResponder<DiscoInfo> { + public: + DiscoInfoResponder(IQRouter* router); + + void setDiscoInfo(const DiscoInfo& info); + void setDiscoInfo(const String& node, const DiscoInfo& info); + + private: + virtual bool handleGetRequest(const JID& from, const String& id, boost::shared_ptr<DiscoInfo> payload); + + private: + DiscoInfo info_; + std::map<String, DiscoInfo> nodeInfo_; + }; +} + +#endif diff --git a/Swiften/Queries/Responders/RosterPushResponder.h b/Swiften/Queries/Responders/RosterPushResponder.h new file mode 100644 index 0000000..69185c8 --- /dev/null +++ b/Swiften/Queries/Responders/RosterPushResponder.h @@ -0,0 +1,23 @@ +#pragma once + +#include <boost/signal.hpp> + +#include "Swiften/Queries/SetResponder.h" +#include "Swiften/Elements/RosterPayload.h" + +namespace Swift { + class RosterPushResponder : public SetResponder<RosterPayload> { + public: + RosterPushResponder(IQRouter* router) : SetResponder<RosterPayload>(router) {} + + public: + boost::signal<void (boost::shared_ptr<RosterPayload>)> onRosterReceived; + + private: + virtual bool handleSetRequest(const JID& from, const String& id, boost::shared_ptr<RosterPayload> payload) { + onRosterReceived(payload); + sendResponse(from, id, boost::shared_ptr<Payload>()); + return true; + } + }; +} diff --git a/Swiften/Queries/Responders/SoftwareVersionResponder.cpp b/Swiften/Queries/Responders/SoftwareVersionResponder.cpp new file mode 100644 index 0000000..e608f24 --- /dev/null +++ b/Swiften/Queries/Responders/SoftwareVersionResponder.cpp @@ -0,0 +1,16 @@ +#include "Swiften/Queries/Responders/SoftwareVersionResponder.h" +#include "Swiften/Queries/IQRouter.h" + +namespace Swift { + +SoftwareVersionResponder::SoftwareVersionResponder( + const String& client, const String& version, IQRouter* router) : + GetResponder<SoftwareVersion>(router), client_(client), version_(version) { +} + +bool SoftwareVersionResponder::handleGetRequest(const JID& from, const String& id, boost::shared_ptr<SoftwareVersion>) { + sendResponse(from, id, boost::shared_ptr<SoftwareVersion>(new SoftwareVersion(client_, version_))); + return true; +} + +} diff --git a/Swiften/Queries/Responders/SoftwareVersionResponder.h b/Swiften/Queries/Responders/SoftwareVersionResponder.h new file mode 100644 index 0000000..85d1089 --- /dev/null +++ b/Swiften/Queries/Responders/SoftwareVersionResponder.h @@ -0,0 +1,23 @@ +#ifndef SWIFTEN_SoftwareVersionResponder_H +#define SWIFTEN_SoftwareVersionResponder_H + +#include "Swiften/Queries/GetResponder.h" +#include "Swiften/Elements/SoftwareVersion.h" + +namespace Swift { + class IQRouter; + + class SoftwareVersionResponder : public GetResponder<SoftwareVersion> { + public: + SoftwareVersionResponder(const String& client, const String& version, IQRouter* router); + + private: + virtual bool handleGetRequest(const JID& from, const String& id, boost::shared_ptr<SoftwareVersion> payload); + + private: + String client_; + String version_; + }; +} + +#endif diff --git a/Swiften/Queries/Responders/UnitTest/DiscoInfoResponderTest.cpp b/Swiften/Queries/Responders/UnitTest/DiscoInfoResponderTest.cpp new file mode 100644 index 0000000..5993d0c --- /dev/null +++ b/Swiften/Queries/Responders/UnitTest/DiscoInfoResponderTest.cpp @@ -0,0 +1,84 @@ +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> +#include <typeinfo> + +#include "Swiften/Queries/Responders/DiscoInfoResponder.h" +#include "Swiften/Queries/IQRouter.h" +#include "Swiften/Queries/DummyIQChannel.h" + +using namespace Swift; + +class DiscoInfoResponderTest : public CppUnit::TestFixture { + CPPUNIT_TEST_SUITE(DiscoInfoResponderTest); + CPPUNIT_TEST(testHandleRequest_GetToplevelInfo); + CPPUNIT_TEST(testHandleRequest_GetNodeInfo); + CPPUNIT_TEST(testHandleRequest_GetInvalidNodeInfo); + CPPUNIT_TEST_SUITE_END(); + + public: + DiscoInfoResponderTest() {} + + void setUp() { + channel_ = new DummyIQChannel(); + router_ = new IQRouter(channel_); + } + + void tearDown() { + delete router_; + delete channel_; + } + + void testHandleRequest_GetToplevelInfo() { + DiscoInfoResponder testling(router_); + DiscoInfo discoInfo; + discoInfo.addFeature("foo"); + testling.setDiscoInfo(discoInfo); + + boost::shared_ptr<DiscoInfo> query(new DiscoInfo()); + channel_->onIQReceived(IQ::createRequest(IQ::Get, JID("foo@bar.com"), "id-1", query)); + + CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(channel_->iqs_.size())); + boost::shared_ptr<DiscoInfo> payload(channel_->iqs_[0]->getPayload<DiscoInfo>()); + CPPUNIT_ASSERT(payload); + CPPUNIT_ASSERT_EQUAL(String(""), payload->getNode()); + CPPUNIT_ASSERT(payload->hasFeature("foo")); + } + + void testHandleRequest_GetNodeInfo() { + DiscoInfoResponder testling(router_); + DiscoInfo discoInfo; + discoInfo.addFeature("foo"); + testling.setDiscoInfo(discoInfo); + DiscoInfo discoInfoBar; + discoInfoBar.addFeature("bar"); + testling.setDiscoInfo("bar-node", discoInfoBar); + + boost::shared_ptr<DiscoInfo> query(new DiscoInfo()); + query->setNode("bar-node"); + channel_->onIQReceived(IQ::createRequest(IQ::Get, JID("foo@bar.com"), "id-1", query)); + + CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(channel_->iqs_.size())); + boost::shared_ptr<DiscoInfo> payload(channel_->iqs_[0]->getPayload<DiscoInfo>()); + CPPUNIT_ASSERT(payload); + CPPUNIT_ASSERT_EQUAL(String("bar-node"), payload->getNode()); + CPPUNIT_ASSERT(payload->hasFeature("bar")); + } + + void testHandleRequest_GetInvalidNodeInfo() { + DiscoInfoResponder testling(router_); + + boost::shared_ptr<DiscoInfo> query(new DiscoInfo()); + query->setNode("bar-node"); + channel_->onIQReceived(IQ::createRequest(IQ::Get, JID("foo@bar.com"), "id-1", query)); + + CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(channel_->iqs_.size())); + boost::shared_ptr<ErrorPayload> payload(channel_->iqs_[0]->getPayload<ErrorPayload>()); + CPPUNIT_ASSERT(payload); + } + + private: + IQRouter* router_; + DummyIQChannel* channel_; +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(DiscoInfoResponderTest); diff --git a/Swiften/Queries/SetResponder.h b/Swiften/Queries/SetResponder.h new file mode 100644 index 0000000..51fe39a --- /dev/null +++ b/Swiften/Queries/SetResponder.h @@ -0,0 +1,14 @@ +#pragma once + +#include "Swiften/Queries/Responder.h" + +namespace Swift { + template<typename T> + class SetResponder : public Responder<T> { + public: + SetResponder(IQRouter* router) : Responder<T>(router) {} + + private: + virtual bool handleGetRequest(const JID&, const String&, boost::shared_ptr<T>) { return false; } + }; +} diff --git a/Swiften/Queries/UnitTest/IQRouterTest.cpp b/Swiften/Queries/UnitTest/IQRouterTest.cpp new file mode 100644 index 0000000..5760b09 --- /dev/null +++ b/Swiften/Queries/UnitTest/IQRouterTest.cpp @@ -0,0 +1,143 @@ +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> +#include <boost/shared_ptr.hpp> +#include <boost/bind.hpp> + +#include "Swiften/Queries/IQHandler.h" +#include "Swiften/Queries/IQRouter.h" +#include "Swiften/Queries/DummyIQChannel.h" + +using namespace Swift; + +class IQRouterTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(IQRouterTest); + CPPUNIT_TEST(testRemoveHandler); + CPPUNIT_TEST(testRemoveHandler_AfterHandleIQ); + CPPUNIT_TEST(testHandleIQ_SuccesfulHandlerFirst); + CPPUNIT_TEST(testHandleIQ_SuccesfulHandlerLast); + CPPUNIT_TEST(testHandleIQ_NoSuccesfulHandler); + CPPUNIT_TEST(testHandleIQ_HandlerRemovedDuringHandle); + CPPUNIT_TEST_SUITE_END(); + + public: + IQRouterTest() {} + + void setUp() { + channel_ = new DummyIQChannel(); + } + + void tearDown() { + delete channel_; + } + + void testRemoveHandler() { + IQRouter testling(channel_); + DummyIQHandler handler1(true, &testling); + DummyIQHandler handler2(true, &testling); + testling.removeHandler(&handler1); + + channel_->onIQReceived(boost::shared_ptr<IQ>(new IQ())); + + CPPUNIT_ASSERT_EQUAL(0, handler1.called); + CPPUNIT_ASSERT_EQUAL(1, handler2.called); + } + + void testRemoveHandler_AfterHandleIQ() { + IQRouter testling(channel_); + DummyIQHandler handler1(true, &testling); + DummyIQHandler handler2(true, &testling); + + channel_->onIQReceived(boost::shared_ptr<IQ>(new IQ())); + testling.removeHandler(&handler1); + channel_->onIQReceived(boost::shared_ptr<IQ>(new IQ())); + + CPPUNIT_ASSERT_EQUAL(1, handler1.called); + CPPUNIT_ASSERT_EQUAL(1, handler2.called); + } + + void testHandleIQ_SuccesfulHandlerFirst() { + IQRouter testling(channel_); + DummyIQHandler handler1(true, &testling); + DummyIQHandler handler2(false, &testling); + + channel_->onIQReceived(boost::shared_ptr<IQ>(new IQ())); + + CPPUNIT_ASSERT_EQUAL(1, handler1.called); + CPPUNIT_ASSERT_EQUAL(0, handler2.called); + CPPUNIT_ASSERT_EQUAL(0, static_cast<int>(channel_->iqs_.size())); + } + + void testHandleIQ_SuccesfulHandlerLast() { + IQRouter testling(channel_); + DummyIQHandler handler1(false, &testling); + DummyIQHandler handler2(true, &testling); + + channel_->onIQReceived(boost::shared_ptr<IQ>(new IQ())); + + CPPUNIT_ASSERT_EQUAL(1, handler1.called); + CPPUNIT_ASSERT_EQUAL(1, handler2.called); + CPPUNIT_ASSERT_EQUAL(0, static_cast<int>(channel_->iqs_.size())); + } + + void testHandleIQ_NoSuccesfulHandler() { + IQRouter testling(channel_); + DummyIQHandler handler(false, &testling); + + channel_->onIQReceived(boost::shared_ptr<IQ>(new IQ())); + + CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(channel_->iqs_.size())); + CPPUNIT_ASSERT(channel_->iqs_[0]->getPayload<ErrorPayload>()); + } + + + void testHandleIQ_HandlerRemovedDuringHandle() { + IQRouter testling(channel_); + RemovingIQHandler handler1(&testling); + DummyIQHandler handler2(true, &testling); + + channel_->onIQReceived(boost::shared_ptr<IQ>(new IQ())); + channel_->onIQReceived(boost::shared_ptr<IQ>(new IQ())); + + CPPUNIT_ASSERT_EQUAL(1, handler1.called); + CPPUNIT_ASSERT_EQUAL(2, handler2.called); + } + + private: + struct DummyIQHandler : public IQHandler { + DummyIQHandler(bool handle, IQRouter* router) : handle(handle), router(router), called(0) { + router->addHandler(this); + } + + ~DummyIQHandler() { + router->removeHandler(this); + } + + virtual bool handleIQ(boost::shared_ptr<IQ>) { + called++; + return handle; + } + bool handle; + IQRouter* router; + int called; + }; + + struct RemovingIQHandler : public IQHandler { + RemovingIQHandler(IQRouter* router) : router(router), called(0) { + router->addHandler(this); + } + + virtual bool handleIQ(boost::shared_ptr<IQ>) { + called++; + router->removeHandler(this); + return false; + } + IQRouter* router; + int called; + }; + + + DummyIQChannel* channel_; +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(IQRouterTest); diff --git a/Swiften/Queries/UnitTest/RequestTest.cpp b/Swiften/Queries/UnitTest/RequestTest.cpp new file mode 100644 index 0000000..c569bb5 --- /dev/null +++ b/Swiften/Queries/UnitTest/RequestTest.cpp @@ -0,0 +1,167 @@ +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> +#include <boost/shared_ptr.hpp> +#include <boost/bind.hpp> + +#include "Swiften/Queries/GenericRequest.h" +#include "Swiften/Queries/IQRouter.h" +#include "Swiften/Queries/DummyIQChannel.h" +#include "Swiften/Elements/Payload.h" + +using namespace Swift; + +class RequestTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(RequestTest); + CPPUNIT_TEST(testSendGet); + CPPUNIT_TEST(testSendSet); + CPPUNIT_TEST(testHandleIQ); + CPPUNIT_TEST(testHandleIQ_InvalidID); + CPPUNIT_TEST(testHandleIQ_Error); + CPPUNIT_TEST(testHandleIQ_ErrorWithoutPayload); + CPPUNIT_TEST(testHandleIQ_BeforeSend); + CPPUNIT_TEST_SUITE_END(); + + public: + class MyPayload : public Payload { + public: + MyPayload(const String& s = "") : text_(s) {} + String text_; + }; + + typedef GenericRequest<MyPayload> MyRequest; + + public: + RequestTest() {} + + void setUp() { + channel_ = new DummyIQChannel(); + router_ = new IQRouter(channel_); + payload_ = boost::shared_ptr<Payload>(new MyPayload("foo")); + responsePayload_ = boost::shared_ptr<Payload>(new MyPayload("bar")); + responsesReceived_ = 0; + } + + void tearDown() { + delete router_; + delete channel_; + } + + void testSendSet() { + MyRequest testling(IQ::Set, JID("foo@bar.com/baz"), payload_, router_); + testling.send(); + + CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(channel_->iqs_.size())); + CPPUNIT_ASSERT_EQUAL(JID("foo@bar.com/baz"), channel_->iqs_[0]->getTo()); + CPPUNIT_ASSERT_EQUAL(IQ::Set, channel_->iqs_[0]->getType()); + CPPUNIT_ASSERT_EQUAL(String("test-id"), channel_->iqs_[0]->getID()); + } + + void testSendGet() { + MyRequest testling(IQ::Get, JID("foo@bar.com/baz"), payload_, router_); + testling.send(); + + CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(channel_->iqs_.size())); + CPPUNIT_ASSERT_EQUAL(IQ::Get, channel_->iqs_[0]->getType()); + } + + void testHandleIQ() { + MyRequest testling(IQ::Get, JID("foo@bar.com/baz"), payload_, router_); + testling.onResponse.connect(boost::bind(&RequestTest::handleResponse, this, _1, _2)); + testling.send(); + + channel_->onIQReceived(createResponse("test-id")); + + CPPUNIT_ASSERT_EQUAL(1, responsesReceived_); + CPPUNIT_ASSERT_EQUAL(0, static_cast<int>(receivedErrors.size())); + CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(channel_->iqs_.size())); + } + + // FIXME: Doesn't test that it didn't handle the payload + void testHandleIQ_InvalidID() { + MyRequest testling(IQ::Get, JID("foo@bar.com/baz"), payload_, router_); + testling.onResponse.connect(boost::bind(&RequestTest::handleResponse, this, _1, _2)); + testling.send(); + + channel_->onIQReceived(createResponse("different-id")); + + CPPUNIT_ASSERT_EQUAL(0, responsesReceived_); + CPPUNIT_ASSERT_EQUAL(0, static_cast<int>(receivedErrors.size())); + CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(channel_->iqs_.size())); + } + + void testHandleIQ_Error() { + MyRequest testling(IQ::Get, JID("foo@bar.com/baz"), payload_, router_); + testling.onResponse.connect(boost::bind(&RequestTest::handleResponse, this, _1, _2)); + testling.send(); + + boost::shared_ptr<IQ> error = createError("test-id"); + boost::shared_ptr<Payload> errorPayload = boost::shared_ptr<ErrorPayload>(new ErrorPayload(ErrorPayload::FeatureNotImplemented)); + error->addPayload(errorPayload); + channel_->onIQReceived(error); + + CPPUNIT_ASSERT_EQUAL(0, responsesReceived_); + CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(receivedErrors.size())); + CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(channel_->iqs_.size())); + CPPUNIT_ASSERT_EQUAL(ErrorPayload::FeatureNotImplemented, receivedErrors[0].getCondition()); + } + + void testHandleIQ_ErrorWithoutPayload() { + MyRequest testling(IQ::Get, JID("foo@bar.com/baz"), payload_, router_); + testling.onResponse.connect(boost::bind(&RequestTest::handleResponse, this, _1, _2)); + testling.send(); + + channel_->onIQReceived(createError("test-id")); + + CPPUNIT_ASSERT_EQUAL(0, responsesReceived_); + CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(receivedErrors.size())); + CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(channel_->iqs_.size())); + CPPUNIT_ASSERT_EQUAL(ErrorPayload::UndefinedCondition, receivedErrors[0].getCondition()); + } + + void testHandleIQ_BeforeSend() { + MyRequest testling(IQ::Get, JID("foo@bar.com/baz"), payload_, router_); + testling.onResponse.connect(boost::bind(&RequestTest::handleResponse, this, _1, _2)); + channel_->onIQReceived(createResponse("test-id")); + + CPPUNIT_ASSERT_EQUAL(0, responsesReceived_); + CPPUNIT_ASSERT_EQUAL(0, static_cast<int>(receivedErrors.size())); + CPPUNIT_ASSERT_EQUAL(0, static_cast<int>(channel_->iqs_.size())); + } + + private: + void handleResponse(boost::shared_ptr<Payload> p, const boost::optional<ErrorPayload>& e) { + if (e) { + receivedErrors.push_back(*e); + } + else { + boost::shared_ptr<MyPayload> payload(boost::dynamic_pointer_cast<MyPayload>(p)); + CPPUNIT_ASSERT(payload); + CPPUNIT_ASSERT_EQUAL(String("bar"), payload->text_); + ++responsesReceived_; + } + } + + boost::shared_ptr<IQ> createResponse(const String& id) { + boost::shared_ptr<IQ> iq(new IQ(IQ::Result)); + iq->addPayload(responsePayload_); + iq->setID(id); + return iq; + } + + boost::shared_ptr<IQ> createError(const String& id) { + boost::shared_ptr<IQ> iq(new IQ(IQ::Error)); + iq->setID(id); + return iq; + } + + private: + IQRouter* router_; + DummyIQChannel* channel_; + boost::shared_ptr<Payload> payload_; + boost::shared_ptr<Payload> responsePayload_; + int responsesReceived_; + std::vector<ErrorPayload> receivedErrors; +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(RequestTest); diff --git a/Swiften/Queries/UnitTest/ResponderTest.cpp b/Swiften/Queries/UnitTest/ResponderTest.cpp new file mode 100644 index 0000000..5c758e4 --- /dev/null +++ b/Swiften/Queries/UnitTest/ResponderTest.cpp @@ -0,0 +1,132 @@ +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> +#include <boost/shared_ptr.hpp> +#include <boost/bind.hpp> + +#include "Swiften/Queries/Responder.h" +#include "Swiften/Queries/IQRouter.h" +#include "Swiften/Queries/DummyIQChannel.h" +#include "Swiften/Elements/SoftwareVersion.h" + +using namespace Swift; + +class ResponderTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(ResponderTest); + CPPUNIT_TEST(testConstructor); + CPPUNIT_TEST(testHandleIQ_Set); + CPPUNIT_TEST(testHandleIQ_Get); + CPPUNIT_TEST(testHandleIQ_Error); + CPPUNIT_TEST(testHandleIQ_Result); + CPPUNIT_TEST(testHandleIQ_NoPayload); + CPPUNIT_TEST_SUITE_END(); + + public: + ResponderTest() {} + + void setUp() { + channel_ = new DummyIQChannel(); + router_ = new IQRouter(channel_); + payload_ = boost::shared_ptr<SoftwareVersion>(new SoftwareVersion("foo")); + } + + void tearDown() { + delete router_; + delete channel_; + } + + void testConstructor() { + MyResponder testling(router_); + + channel_->onIQReceived(createRequest(IQ::Set)); + + CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(testling.setPayloads_.size())); + } + + void testHandleIQ_Set() { + MyResponder testling(router_); + + CPPUNIT_ASSERT(dynamic_cast<IQHandler*>(&testling)->handleIQ(createRequest(IQ::Set))); + + CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(testling.setPayloads_.size())); + CPPUNIT_ASSERT(payload_ == testling.setPayloads_[0]); + CPPUNIT_ASSERT_EQUAL(0, static_cast<int>(testling.getPayloads_.size())); + } + + void testHandleIQ_Get() { + MyResponder testling(router_); + + CPPUNIT_ASSERT(dynamic_cast<IQHandler*>(&testling)->handleIQ(createRequest(IQ::Get))); + + CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(testling.getPayloads_.size())); + CPPUNIT_ASSERT_EQUAL(0, static_cast<int>(testling.setPayloads_.size())); + CPPUNIT_ASSERT(payload_ == testling.getPayloads_[0]); + } + + void testHandleIQ_Error() { + MyResponder testling(router_); + + CPPUNIT_ASSERT(!dynamic_cast<IQHandler*>(&testling)->handleIQ(createRequest(IQ::Error))); + + CPPUNIT_ASSERT_EQUAL(0, static_cast<int>(testling.getPayloads_.size())); + CPPUNIT_ASSERT_EQUAL(0, static_cast<int>(testling.setPayloads_.size())); + } + + void testHandleIQ_Result() { + MyResponder testling(router_); + + CPPUNIT_ASSERT(!dynamic_cast<IQHandler*>(&testling)->handleIQ(createRequest(IQ::Result))); + + CPPUNIT_ASSERT_EQUAL(0, static_cast<int>(testling.getPayloads_.size())); + CPPUNIT_ASSERT_EQUAL(0, static_cast<int>(testling.setPayloads_.size())); + } + + void testHandleIQ_NoPayload() { + MyResponder testling(router_); + + CPPUNIT_ASSERT(!dynamic_cast<IQHandler*>(&testling)->handleIQ(boost::shared_ptr<IQ>(new IQ(IQ::Get)))); + + CPPUNIT_ASSERT_EQUAL(0, static_cast<int>(testling.getPayloads_.size())); + CPPUNIT_ASSERT_EQUAL(0, static_cast<int>(testling.setPayloads_.size())); + } + + private: + boost::shared_ptr<IQ> createRequest(IQ::Type type) { + boost::shared_ptr<IQ> iq(new IQ(type)); + iq->addPayload(payload_); + iq->setID("myid"); + iq->setFrom(JID("foo@bar.com/baz")); + return iq; + } + + private: + class MyResponder : public Responder<SoftwareVersion> { + public: + MyResponder(IQRouter* router) : Responder<SoftwareVersion>(router), getRequestResponse_(true), setRequestResponse_(true) {} + + virtual bool handleGetRequest(const JID& from, const String& id, boost::shared_ptr<SoftwareVersion> payload) { + CPPUNIT_ASSERT_EQUAL(JID("foo@bar.com/baz"), from); + CPPUNIT_ASSERT_EQUAL(String("myid"), id); + getPayloads_.push_back(payload); + return getRequestResponse_; + } + virtual bool handleSetRequest(const JID& from, const String& id, boost::shared_ptr<SoftwareVersion> payload) { + CPPUNIT_ASSERT_EQUAL(JID("foo@bar.com/baz"), from); + CPPUNIT_ASSERT_EQUAL(String("myid"), id); + setPayloads_.push_back(payload); + return setRequestResponse_; + } + + bool getRequestResponse_; + bool setRequestResponse_; + std::vector<boost::shared_ptr<SoftwareVersion> > getPayloads_; + std::vector<boost::shared_ptr<SoftwareVersion> > setPayloads_; + }; + + private: + IQRouter* router_; + DummyIQChannel* channel_; + boost::shared_ptr<SoftwareVersion> payload_; +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(ResponderTest); diff --git a/Swiften/Roster/AppearOffline.h b/Swiften/Roster/AppearOffline.h new file mode 100644 index 0000000..673e018 --- /dev/null +++ b/Swiften/Roster/AppearOffline.h @@ -0,0 +1,26 @@ +#pragma once + +#include "Swiften/Roster/RosterItemOperation.h" +#include "Swiften/Roster/ContactRosterItem.h" + +namespace Swift { + +class RosterItem; + +class AppearOffline : public RosterItemOperation { + public: + AppearOffline() { + } + + virtual void operator() (RosterItem* item) const { + ContactRosterItem* contact = dynamic_cast<ContactRosterItem*>(item); + if (contact) { + contact->setStatusShow(StatusShow::None); + } + } + +}; + +} + + diff --git a/Swiften/Roster/ContactRosterItem.cpp b/Swiften/Roster/ContactRosterItem.cpp new file mode 100644 index 0000000..968f7f1 --- /dev/null +++ b/Swiften/Roster/ContactRosterItem.cpp @@ -0,0 +1,50 @@ +#include "Swiften/Roster/ContactRosterItem.h" +#include "Swiften/Roster/GroupRosterItem.h" + +namespace Swift { + + +ContactRosterItem::ContactRosterItem(const JID& jid, const String& name, GroupRosterItem* parent, TreeWidgetFactory* factory) : jid_(jid), name_(name) { + parent->addChild(this); + widget_ = factory->createTreeWidgetItem(parent->getWidget()); + widget_->setText(name.isEmpty() ? jid.toString() : name); + widget_->onUserAction.connect(boost::bind(&ContactRosterItem::handleUserAction, this, _1)); + setStatusShow(StatusShow::None); +} + +ContactRosterItem::~ContactRosterItem() { + delete widget_; +} + +StatusShow::Type ContactRosterItem::getStatusShow() { + return statusShow_; +} + +void ContactRosterItem::setStatusShow(StatusShow::Type show) { + statusShow_ = show; + widget_->setStatusShow(show); +} + +void ContactRosterItem::setStatusText(const String& status) { + widget_->setStatusText(status); +} + +void ContactRosterItem::setAvatarPath(const String& path) { + widget_->setAvatarPath(path); +} + +const JID& ContactRosterItem::getJID() const { + return jid_; +} + +void ContactRosterItem::show() { + widget_->show(); +} + +void ContactRosterItem::hide() { + widget_->hide(); +} + +} + + diff --git a/Swiften/Roster/ContactRosterItem.h b/Swiften/Roster/ContactRosterItem.h new file mode 100644 index 0000000..f1810aa --- /dev/null +++ b/Swiften/Roster/ContactRosterItem.h @@ -0,0 +1,41 @@ +#ifndef SWIFTEN_ContactRosterItem_H +#define SWIFTEN_ContactRosterItem_H + +#include "Swiften/Base/String.h" +#include "Swiften/JID/JID.h" +#include "Swiften/Roster/TreeWidgetFactory.h" +#include "Swiften/Roster/RosterItem.h" +#include "Swiften/Roster/UserRosterAction.h" +#include "Swiften/Elements/StatusShow.h" + +#include <boost/bind.hpp> +#include <boost/signal.hpp> +#include <boost/shared_ptr.hpp> + +namespace Swift { + +class TreeWidgetItem; +class GroupRosterItem; +class ContactRosterItem : public RosterItem { + public: + ContactRosterItem(const JID& jid, const String& name, GroupRosterItem* parent, TreeWidgetFactory* factory); + ~ContactRosterItem(); + + StatusShow::Type getStatusShow(); + void setStatusShow(StatusShow::Type show); + void setStatusText(const String& status); + void setAvatarPath(const String& path); + const JID& getJID() const; + void show(); + void hide(); + + private: + JID jid_; + String name_; + TreeWidgetItem *widget_; + StatusShow::Type statusShow_; +}; + +} +#endif + diff --git a/Swiften/Roster/GroupRosterItem.h b/Swiften/Roster/GroupRosterItem.h new file mode 100644 index 0000000..2ab59ea --- /dev/null +++ b/Swiften/Roster/GroupRosterItem.h @@ -0,0 +1,68 @@ +#ifndef SWIFTEN_GroupRosterItem_H +#define SWIFTEN_GroupRosterItem_H + +#include "Swiften/Roster/RosterItem.h" +#include "Swiften/Base/String.h" +#include "Swiften/Roster/TreeWidget.h" +#include "Swiften/Roster/TreeWidgetFactory.h" +#include "Swiften/Roster/TreeWidgetItem.h" +#include "Swiften/Roster/ContactRosterItem.h" + +#include <list> + +namespace Swift { + +class GroupRosterItem : public RosterItem { + public: + GroupRosterItem(const String& name, TreeWidget* tree, TreeWidgetFactory* factory) : name_(name) { + widget_ = factory->createTreeWidgetItem(tree); + widget_->setExpanded(true); + widget_->setText(name); + } + + ~GroupRosterItem() { + delete widget_; + } + + const String& getName() const { + return name_; + } + + TreeWidgetItem* getWidget() const { + return widget_; + } + + const std::list<RosterItem*>& getChildren() const { + return children_; + } + + void addChild(RosterItem* item) { + children_.push_back(item); + } + + void removeChild(const JID& jid) { + std::list<RosterItem*>::iterator it = children_.begin(); + while (it != children_.end()) { + ContactRosterItem* contact = dynamic_cast<ContactRosterItem*>(*it); + if (contact && contact->getJID() == jid) { + delete contact; + it = children_.erase(it); + continue; + } + GroupRosterItem* group = dynamic_cast<GroupRosterItem*>(*it); + if (group) { + group->removeChild(jid); + } + it++; + } + } + + private: + String name_; + TreeWidgetItem* widget_; + std::list<RosterItem*> children_; +}; + +} +#endif + diff --git a/Swiften/Roster/OfflineRosterFilter.h b/Swiften/Roster/OfflineRosterFilter.h new file mode 100644 index 0000000..512d074 --- /dev/null +++ b/Swiften/Roster/OfflineRosterFilter.h @@ -0,0 +1,24 @@ +#ifndef SWIFTEN_OfflineRosterFilter_H +#define SWIFTEN_OfflineRosterFilter_H + +#include "Swiften/Roster/ContactRosterItem.h" +#include "Swiften/Roster/RosterItem.h" +#include "Swiften/Roster/RosterFilter.h" +#include "Swiften/Elements/StatusShow.h" + +namespace Swift { + +class OfflineRosterFilter : public RosterFilter { + public: + virtual ~OfflineRosterFilter() {} + virtual bool operator() (RosterItem *item) const { + ContactRosterItem *contactItem = dynamic_cast<ContactRosterItem*>(item); + return contactItem && contactItem->getStatusShow() == StatusShow::None; + } +}; + +} +#endif + + + diff --git a/Swiften/Roster/OpenChatRosterAction.h b/Swiften/Roster/OpenChatRosterAction.h new file mode 100644 index 0000000..03715a5 --- /dev/null +++ b/Swiften/Roster/OpenChatRosterAction.h @@ -0,0 +1,20 @@ +#ifndef SWIFTEN_OpenChatRosterAction_H +#define SWIFTEN_OpenChatRosterAction_H + +#include "Swiften/Roster/UserRosterAction.h" + +namespace Swift { +class RosterItem; +class TreeWidgetItem; + +class OpenChatRosterAction : public UserRosterAction { + public: + virtual ~OpenChatRosterAction() {}; + +}; + +} +#endif + + + diff --git a/Swiften/Roster/Roster.cpp b/Swiften/Roster/Roster.cpp new file mode 100644 index 0000000..28245af --- /dev/null +++ b/Swiften/Roster/Roster.cpp @@ -0,0 +1,139 @@ +#include "Swiften/Roster/Roster.h" + +#include "Swiften/Base/foreach.h" +#include "Swiften/Base/String.h" +#include "Swiften/JID/JID.h" +#include "Swiften/Roster/ContactRosterItem.h" +#include "Swiften/Roster/RosterItem.h" +#include "Swiften/Roster/GroupRosterItem.h" +#include "Swiften/Roster/RosterItemOperation.h" +#include "Swiften/Roster/TreeWidget.h" +#include "Swiften/Roster/TreeWidgetFactory.h" + +#include <boost/bind.hpp> + +#include <deque> + +namespace Swift { + +Roster::Roster(TreeWidget *treeWidget, TreeWidgetFactory *widgetFactory) : treeWidget_(treeWidget), widgetFactory_(widgetFactory) { +} + +Roster::~Roster() { + foreach (RosterItem* item, items_) { + delete item; + } + delete treeWidget_; +} + +TreeWidget* Roster::getWidget() { + return treeWidget_; +} + +GroupRosterItem* Roster::getGroup(const String& groupName) { + foreach (RosterItem *item, children_) { + GroupRosterItem *group = dynamic_cast<GroupRosterItem*>(item); + if (group && group->getName() == groupName) { + return group; + } + } + GroupRosterItem* group = new GroupRosterItem(groupName, treeWidget_, widgetFactory_); + children_.push_back(group); + items_.push_back(group); + return group; +} + +void Roster::handleUserAction(boost::shared_ptr<UserRosterAction> action) { + onUserAction(action); +} + +void Roster::addContact(const JID& jid, const String& name, const String& group) { + ContactRosterItem *item = new ContactRosterItem(jid, name, getGroup(group), widgetFactory_); + items_.push_back(item); + item->onUserAction.connect(boost::bind(&Roster::handleUserAction, this, _1)); + filterItem(item); + +} + +void Roster::removeContact(const JID& jid) { + std::vector<RosterItem*>::iterator it = children_.begin(); + while (it != children_.end()) { + ContactRosterItem* contact = dynamic_cast<ContactRosterItem*>(*it); + if (contact && contact->getJID() == jid) { + delete contact; + it = children_.erase(it); + continue; + } + GroupRosterItem* group = dynamic_cast<GroupRosterItem*>(*it); + if (group) { + group->removeChild(jid); + } + it++; + } +} + +void Roster::removeContactFromGroup(const JID& jid, const String& groupName) { + std::vector<RosterItem*>::iterator it = children_.begin(); + while (it != children_.end()) { + GroupRosterItem* group = dynamic_cast<GroupRosterItem*>(*it); + if (group && group->getName() == groupName) { + group->removeChild(jid); + } + it++; + } +} + + +void Roster::applyOnItems(const RosterItemOperation& operation) { + std::deque<RosterItem*> queue(children_.begin(), children_.end()); + while (!queue.empty()) { + RosterItem* item = *queue.begin(); + queue.pop_front(); + operation(item); + GroupRosterItem* group = dynamic_cast<GroupRosterItem*>(item); + if (group) { + queue.insert(queue.begin(), group->getChildren().begin(), group->getChildren().end()); + } + } + filterAll(); +} + +void Roster::removeFilter(RosterFilter *filter) { + for (unsigned int i = 0; i < filters_.size(); i++) { + if (filters_[i] == filter) { + filters_.erase(filters_.begin() + i); + break; + } + } + filterAll(); +} + + +void Roster::filterItem(RosterItem* rosterItem) { + ContactRosterItem *item = dynamic_cast<ContactRosterItem*>(rosterItem); + if (!item) { + return; + } + bool hide = true; + foreach (RosterFilter *filter, filters_) { + hide &= (*filter)(item); + } + filters_.size() > 0 && hide ? item->hide() : item->show(); +} + +void Roster::filterAll() { + std::deque<RosterItem*> queue(children_.begin(), children_.end()); + while (!queue.empty()) { + RosterItem *item = *queue.begin(); + queue.pop_front(); + GroupRosterItem* group = dynamic_cast<GroupRosterItem*>(item); + if (group) { + queue.insert(queue.begin(), group->getChildren().begin(), group->getChildren().end()); + } else { + filterItem(item); + } + } +} + +} + diff --git a/Swiften/Roster/Roster.h b/Swiften/Roster/Roster.h new file mode 100644 index 0000000..6010832 --- /dev/null +++ b/Swiften/Roster/Roster.h @@ -0,0 +1,49 @@ +#ifndef SWIFTEN_Roster_H +#define SWIFTEN_Roster_H + +#include "Swiften/Base/String.h" +#include "Swiften/JID/JID.h" +#include "Swiften/Roster/RosterItemOperation.h" +#include "Swiften/Roster/UserRosterAction.h" +#include "Swiften/Roster/RosterFilter.h" + +#include <vector> +#include <boost/signal.hpp> +#include <boost/shared_ptr.hpp> + +namespace Swift { + +class TreeWidgetFactory; +class TreeWidget; +class RosterItem; +class GroupRosterItem; + +class Roster { + public: + Roster(TreeWidget *treeWidget, TreeWidgetFactory *widgetFactory); + ~Roster(); + + TreeWidget* getWidget(); + GroupRosterItem* getGroup(const String& groupName); + void addContact(const JID& jid, const String& name, const String& group); + void removeContact(const JID& jid); + void removeContactFromGroup(const JID& jid, const String& group); + void applyOnItems(const RosterItemOperation& operation); + boost::signal<void (boost::shared_ptr<UserRosterAction>)> onUserAction; + void addFilter(RosterFilter *filter) {filters_.push_back(filter);filterAll();} + void removeFilter(RosterFilter *filter); + std::vector<RosterFilter*> getFilters() {return filters_;} + + private: + void filterItem(RosterItem* item); + void filterAll(); + void handleUserAction(boost::shared_ptr<UserRosterAction> action); + TreeWidget *treeWidget_; + TreeWidgetFactory *widgetFactory_; + std::vector<RosterItem*> children_; + std::vector<RosterItem*> items_; + std::vector<RosterFilter*> filters_; +}; +} + +#endif diff --git a/Swiften/Roster/RosterFilter.h b/Swiften/Roster/RosterFilter.h new file mode 100644 index 0000000..a824304 --- /dev/null +++ b/Swiften/Roster/RosterFilter.h @@ -0,0 +1,17 @@ +#ifndef SWIFTEN_RosterFilter_H +#define SWIFTEN_RosterFilter_H + +#include "Swiften/Roster/RosterItem.h" + +namespace Swift { + +class RosterFilter { + public: + virtual ~RosterFilter() {} + virtual bool operator() (RosterItem* item) const = 0; +}; + +} +#endif + + diff --git a/Swiften/Roster/RosterItem.h b/Swiften/Roster/RosterItem.h new file mode 100644 index 0000000..2707920 --- /dev/null +++ b/Swiften/Roster/RosterItem.h @@ -0,0 +1,24 @@ +#ifndef SWIFTEN_RosterItem_H +#define SWIFTEN_RosterItem_H + +#include "Swiften/Roster/UserRosterAction.h" + +#include <boost/signal.hpp> +#include <boost/shared_ptr.hpp> + +namespace Swift { + +class RosterItem { + public: + virtual ~RosterItem() {}; + boost::signal<void (boost::shared_ptr<UserRosterAction>)> onUserAction; + protected: + void handleUserAction(boost::shared_ptr<UserRosterAction> action) { + action->setRosterItem(this); + onUserAction(action); + } +}; + +} +#endif + diff --git a/Swiften/Roster/RosterItemOperation.h b/Swiften/Roster/RosterItemOperation.h new file mode 100644 index 0000000..ea8e723 --- /dev/null +++ b/Swiften/Roster/RosterItemOperation.h @@ -0,0 +1,16 @@ +#ifndef SWIFTEN_RosterItemOperation_H +#define SWIFTEN_RosterItemOperation_H + +#include "Swiften/Roster/RosterItem.h" + +namespace Swift { + +class RosterItemOperation { + public: + virtual ~RosterItemOperation() {} + virtual void operator() (RosterItem*) const = 0; +}; + +} +#endif + diff --git a/Swiften/Roster/SetAvatar.h b/Swiften/Roster/SetAvatar.h new file mode 100644 index 0000000..7bc9c37 --- /dev/null +++ b/Swiften/Roster/SetAvatar.h @@ -0,0 +1,33 @@ +#ifndef SWIFTEN_SetAvatar_H +#define SWIFTEN_SetAvatar_H + +#include "Swiften/Elements/Presence.h" +#include "Swiften/JID/JID.h" +#include "Swiften/Roster/RosterItemOperation.h" +#include "Swiften/Roster/ContactRosterItem.h" + +namespace Swift { + +class RosterItem; + +class SetAvatar : public RosterItemOperation { + public: + SetAvatar(const JID& jid, const String& path, JID::CompareType compareType = JID::WithoutResource) : jid_(jid), path_(path), compareType_(compareType) { + } + + virtual void operator() (RosterItem* item) const { + ContactRosterItem* contact = dynamic_cast<ContactRosterItem*>(item); + if (contact && contact->getJID().equals(jid_, compareType_)) { + contact->setAvatarPath(path_); + } + } + + private: + JID jid_; + String path_; + JID::CompareType compareType_; +}; + +} +#endif + diff --git a/Swiften/Roster/SetPresence.h b/Swiften/Roster/SetPresence.h new file mode 100644 index 0000000..a18ae6d --- /dev/null +++ b/Swiften/Roster/SetPresence.h @@ -0,0 +1,38 @@ +#ifndef SWIFTEN_SetPresence_H +#define SWIFTEN_SetPresence_H + +#include "Swiften/Elements/Presence.h" +#include "Swiften/JID/JID.h" +#include "Swiften/Roster/RosterItemOperation.h" +#include "Swiften/Roster/ContactRosterItem.h" + +namespace Swift { + +class RosterItem; + +class SetPresence : public RosterItemOperation { + public: + SetPresence(boost::shared_ptr<Presence> presence, JID::CompareType compareType = JID::WithoutResource) : presence_(presence), compareType_(compareType) { + } + + virtual void operator() (RosterItem* item) const { + ContactRosterItem* contact = dynamic_cast<ContactRosterItem*>(item); + if (contact && contact->getJID().equals(presence_->getFrom(), compareType_)) { + if (presence_->getType() != Presence::Available) { + contact->setStatusShow(StatusShow::None); + contact->setStatusText(presence_->getStatus()); + } else { + contact->setStatusShow(presence_->getShow()); + contact->setStatusText(presence_->getStatus()); + } + } + } + + private: + boost::shared_ptr<Presence> presence_; + JID::CompareType compareType_; +}; + +} +#endif + diff --git a/Swiften/Roster/TreeWidget.h b/Swiften/Roster/TreeWidget.h new file mode 100644 index 0000000..a26003e --- /dev/null +++ b/Swiften/Roster/TreeWidget.h @@ -0,0 +1,13 @@ +#ifndef SWIFTEN_TreeWidget_H +#define SWIFTEN_TreeWidget_H + +namespace Swift { + +class TreeWidget { + public: + virtual ~TreeWidget() {} +}; + +} +#endif + diff --git a/Swiften/Roster/TreeWidgetFactory.h b/Swiften/Roster/TreeWidgetFactory.h new file mode 100644 index 0000000..f4ba68d --- /dev/null +++ b/Swiften/Roster/TreeWidgetFactory.h @@ -0,0 +1,20 @@ +#ifndef SWIFTEN_TreeWidgetFactory_H +#define SWIFTEN_TreeWidgetFactory_H + +namespace Swift { + +class TreeWidgetItem; +class TreeWidget; + +class TreeWidgetFactory { + public: + virtual ~TreeWidgetFactory() {} + virtual TreeWidget* createTreeWidget() = 0; + virtual TreeWidgetItem* createTreeWidgetItem(TreeWidgetItem* item) = 0; + virtual TreeWidgetItem* createTreeWidgetItem(TreeWidget* item) = 0; +}; + +} + +#endif + diff --git a/Swiften/Roster/TreeWidgetItem.h b/Swiften/Roster/TreeWidgetItem.h new file mode 100644 index 0000000..4124546 --- /dev/null +++ b/Swiften/Roster/TreeWidgetItem.h @@ -0,0 +1,34 @@ +#ifndef SWIFTEN_TreeWidgetItem_H +#define SWIFTEN_TreeWidgetItem_H + +#include "Swiften/Base/String.h" +#include "Swiften/Roster/UserRosterAction.h" +#include "Swiften/Elements/StatusShow.h" + +#include <boost/signal.hpp> +#include <boost/shared_ptr.hpp> + +namespace Swift { + +class TreeWidgetItem { + public: + virtual ~TreeWidgetItem() {} + virtual void setText(const String& text) = 0; + virtual void setStatusText(const String& text) = 0; + virtual void setAvatarPath(const String& path) = 0; + virtual void setExpanded(bool b) = 0; + //virtual void setTextColor(unsigned long color) = 0; + virtual void setStatusShow(StatusShow::Type show) = 0; + //virtual void setBackgroundColor(unsigned long color) = 0; + boost::signal<void (boost::shared_ptr<UserRosterAction>)> onUserAction; + virtual void show() = 0; + virtual void hide() = 0; + void performUserAction(boost::shared_ptr<UserRosterAction> action) { + action->setTreeWidgetItem(this); + onUserAction(action); + } +}; + +} +#endif + diff --git a/Swiften/Roster/UnitTest/MockTreeWidget.h b/Swiften/Roster/UnitTest/MockTreeWidget.h new file mode 100644 index 0000000..e6f6def --- /dev/null +++ b/Swiften/Roster/UnitTest/MockTreeWidget.h @@ -0,0 +1,14 @@ +#ifndef SWIFTEN_MockTreeWidget_H +#define SWIFTEN_MockTreeWidget_H + +#include "Swiften/Roster/TreeWidget.h" + +namespace Swift { + +class MockTreeWidget : public TreeWidget { + public: + virtual ~MockTreeWidget() {} +}; + +} +#endif diff --git a/Swiften/Roster/UnitTest/MockTreeWidgetFactory.h b/Swiften/Roster/UnitTest/MockTreeWidgetFactory.h new file mode 100644 index 0000000..b2b4f10 --- /dev/null +++ b/Swiften/Roster/UnitTest/MockTreeWidgetFactory.h @@ -0,0 +1,50 @@ +#ifndef SWIFTEN_MockTreeWidgetFactory_H +#define SWIFTEN_MockTreeWidgetFactory_H + +#include "Swiften/Roster/TreeWidgetFactory.h" + +#include <vector> +#include "Swiften/Base/foreach.h" +#include "Swiften/Roster/UnitTest/MockTreeWidget.h" +#include "Swiften/Roster/UnitTest/MockTreeWidgetItem.h" + +namespace Swift { + +class MockTreeWidgetItem; +class MockTreeWidget; + +class MockTreeWidgetFactory : public TreeWidgetFactory { + public: + virtual ~MockTreeWidgetFactory() {} + virtual TreeWidget* createTreeWidget() { + root_ = new MockTreeWidget(); + return root_; + }; + virtual TreeWidgetItem* createTreeWidgetItem(TreeWidgetItem* group) { + MockTreeWidgetItem* entry = new MockTreeWidgetItem(); + groupMembers_[group].push_back(entry); + return entry; + }; + virtual TreeWidgetItem* createTreeWidgetItem(TreeWidget*) { + MockTreeWidgetItem* group = new MockTreeWidgetItem(); + groups_.push_back(group); + return group; + }; + virtual std::vector<String> getGroups() { + std::vector<String> groupNames; + foreach (MockTreeWidgetItem* group, groups_) { + groupNames.push_back(group->getText()); + } + return groupNames; + }; + private: + std::vector<MockTreeWidgetItem*> groups_; + std::map<TreeWidgetItem*, std::vector<MockTreeWidgetItem*> > groupMembers_; + MockTreeWidget* root_; +}; + +} + +#endif + + diff --git a/Swiften/Roster/UnitTest/MockTreeWidgetItem.h b/Swiften/Roster/UnitTest/MockTreeWidgetItem.h new file mode 100644 index 0000000..a40aca7 --- /dev/null +++ b/Swiften/Roster/UnitTest/MockTreeWidgetItem.h @@ -0,0 +1,30 @@ +#ifndef SWIFTEN_MockTreeWidgetItem_H +#define SWIFTEN_MockTreeWidgetItem_H + +#include "Swiften/Base/String.h" +#include "Swiften/Roster/TreeWidgetItem.h" + +#include <boost/signal.hpp> +#include <boost/shared_ptr.hpp> + +namespace Swift { + +class MockTreeWidgetItem : public TreeWidgetItem { + public: + virtual ~MockTreeWidgetItem() {}; + virtual void setText(const String& text) {text_ = text;}; + String getText() {return text_;}; + virtual void setStatusText(const String&) {}; + virtual void setAvatarPath(const String&) {}; + virtual void setExpanded(bool) {}; + virtual void setStatusShow(StatusShow::Type /*show*/) {}; + virtual void show() {}; + virtual void hide() {}; + private: + String text_; +}; + +} +#endif + + diff --git a/Swiften/Roster/UnitTest/OfflineRosterFilterTest.cpp b/Swiften/Roster/UnitTest/OfflineRosterFilterTest.cpp new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/Swiften/Roster/UnitTest/OfflineRosterFilterTest.cpp diff --git a/Swiften/Roster/UnitTest/RosterTest.cpp b/Swiften/Roster/UnitTest/RosterTest.cpp new file mode 100644 index 0000000..b43a41c --- /dev/null +++ b/Swiften/Roster/UnitTest/RosterTest.cpp @@ -0,0 +1,57 @@ +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> +#include <boost/shared_ptr.hpp> + +#include "Swiften/Roster/Roster.h" +#include "Swiften/Roster/UnitTest/MockTreeWidget.h" +#include "Swiften/Roster/UnitTest/MockTreeWidgetFactory.h" +#include "Swiften/Roster/UnitTest/MockTreeWidgetItem.h" + +using namespace Swift; + +class RosterTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(RosterTest); + CPPUNIT_TEST(testGetGroup); + CPPUNIT_TEST_SUITE_END(); + + private: + Roster *roster_; + TreeWidget *widget_; + TreeWidgetFactory *factory_; + JID jid1_; + JID jid2_; + JID jid3_; + + public: + + RosterTest() : jid1_(JID("a@b.c")), jid2_(JID("b@c.d")), jid3_(JID("c@d.e")) {} + + void setUp() { + factory_ = new MockTreeWidgetFactory(); + widget_ = factory_->createTreeWidget(); + roster_ = new Roster(widget_, factory_); + } + + void tearDown() { + delete roster_; + //delete widget_; + delete factory_; + } + + void testGetGroup() { + roster_->addContact(jid1_, "Bert", "group1"); + roster_->addContact(jid2_, "Ernie", "group2"); + roster_->addContact(jid3_, "Cookie", "group1"); + + CPPUNIT_ASSERT_EQUAL(roster_->getGroup("group1"), roster_->getGroup("group1")); + CPPUNIT_ASSERT_EQUAL(roster_->getGroup("group2"), roster_->getGroup("group2")); + CPPUNIT_ASSERT_EQUAL(roster_->getGroup("group3"), roster_->getGroup("group3")); + CPPUNIT_ASSERT(roster_->getGroup("group1") != roster_->getGroup("group2")); + CPPUNIT_ASSERT(roster_->getGroup("group2") != roster_->getGroup("group3")); + CPPUNIT_ASSERT(roster_->getGroup("group3") != roster_->getGroup("group1")); + } + +}; +CPPUNIT_TEST_SUITE_REGISTRATION(RosterTest); + diff --git a/Swiften/Roster/UnitTest/XMPPRosterTest.cpp b/Swiften/Roster/UnitTest/XMPPRosterTest.cpp new file mode 100644 index 0000000..d03953e --- /dev/null +++ b/Swiften/Roster/UnitTest/XMPPRosterTest.cpp @@ -0,0 +1,164 @@ +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> +#include <boost/shared_ptr.hpp> +#include <boost/bind.hpp> + +#include <vector> + +#include "Swiften/Roster/XMPPRoster.h" + + +using namespace Swift; + +enum XMPPRosterEvents {None, Add, Remove, Update}; + +class XMPPRosterSignalHandler { +public: + XMPPRosterSignalHandler(XMPPRoster* roster) { + lastEvent_ = None; + roster->onJIDAdded.connect(boost::bind(&XMPPRosterSignalHandler::handleJIDAdded, this, _1)); + roster->onJIDRemoved.connect(boost::bind(&XMPPRosterSignalHandler::handleJIDRemoved, this, _1)); + roster->onJIDUpdated.connect(boost::bind(&XMPPRosterSignalHandler::handleJIDUpdated, this, _1, _2, _3)); + } + + XMPPRosterEvents getLastEvent() { + return lastEvent_; + } + + JID getLastJID() { + return lastJID_; + } + + String getLastOldName() { + return lastOldName_; + } + + std::vector<String> getLastOldGroups() { + return lastOldGroups_; + } + + void reset() { + lastEvent_ = None; + } + +private: + void handleJIDAdded(const JID& jid) { + lastJID_ = jid; + lastEvent_ = Add; + } + + void handleJIDRemoved(const JID& jid) { + lastJID_ = jid; + lastEvent_ = Remove; + } + + void handleJIDUpdated(const JID& jid, const String& oldName, const std::vector<String>& oldGroups) { + CPPUNIT_ASSERT_EQUAL(None, lastEvent_); + lastJID_ = jid; + lastOldName_ = oldName; + lastOldGroups_ = oldGroups; + lastEvent_ = Update; + } + + XMPPRosterEvents lastEvent_; + JID lastJID_; + String lastOldName_; + std::vector<String> lastOldGroups_; + +}; + +class XMPPRosterTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(XMPPRosterTest); + CPPUNIT_TEST(testJIDAdded); + CPPUNIT_TEST(testJIDRemoved); + CPPUNIT_TEST(testJIDUpdated); + CPPUNIT_TEST_SUITE_END(); + + private: + XMPPRoster* roster_; + XMPPRosterSignalHandler* handler_; + JID jid1_; + JID jid2_; + JID jid3_; + std::vector<String> groups1_; + std::vector<String> groups2_; + + + public: + + XMPPRosterTest() : jid1_(JID("a@b.c")), jid2_(JID("b@c.d")), jid3_(JID("c@d.e")) {} + + void setUp() { + roster_ = new XMPPRoster(); + handler_ = new XMPPRosterSignalHandler(roster_); + groups1_.push_back("bobs"); + groups1_.push_back("berts"); + groups2_.push_back("ernies"); + } + + void tearDown() { + delete roster_; + } + + void testJIDAdded() { + roster_->addContact(jid1_, "NewName", groups1_, RosterItemPayload::Both); + CPPUNIT_ASSERT_EQUAL(Add, handler_->getLastEvent()); + CPPUNIT_ASSERT_EQUAL(jid1_, handler_->getLastJID()); + CPPUNIT_ASSERT_EQUAL(String("NewName"), roster_->getNameForJID(jid1_)); + CPPUNIT_ASSERT(groups1_ == roster_->getGroupsForJID(jid1_)); + handler_->reset(); + roster_->addContact(jid2_, "NameTwo", groups1_, RosterItemPayload::Both); + CPPUNIT_ASSERT_EQUAL(Add, handler_->getLastEvent()); + CPPUNIT_ASSERT_EQUAL(jid2_, handler_->getLastJID()); + CPPUNIT_ASSERT_EQUAL(String("NameTwo"), roster_->getNameForJID(jid2_)); + CPPUNIT_ASSERT_EQUAL(String("NewName"), roster_->getNameForJID(jid1_)); + CPPUNIT_ASSERT(groups1_ == roster_->getGroupsForJID(jid2_)); + CPPUNIT_ASSERT(groups1_ == roster_->getGroupsForJID(jid1_)); + handler_->reset(); + roster_->addContact(jid3_, "NewName", groups2_, RosterItemPayload::Both); + CPPUNIT_ASSERT_EQUAL(Add, handler_->getLastEvent()); + CPPUNIT_ASSERT_EQUAL(jid3_, handler_->getLastJID()); + CPPUNIT_ASSERT_EQUAL(String("NewName"), roster_->getNameForJID(jid3_)); + CPPUNIT_ASSERT(groups2_ == roster_->getGroupsForJID(jid3_)); + } + + void testJIDRemoved() { + roster_->addContact(jid1_, "NewName", groups1_, RosterItemPayload::Both); + handler_->reset(); + roster_->removeContact(jid1_); + CPPUNIT_ASSERT_EQUAL(Remove, handler_->getLastEvent()); + CPPUNIT_ASSERT_EQUAL(jid1_, handler_->getLastJID()); + handler_->reset(); + roster_->addContact(jid1_, "NewName2", groups1_, RosterItemPayload::Both); + CPPUNIT_ASSERT_EQUAL(Add, handler_->getLastEvent()); + CPPUNIT_ASSERT_EQUAL(jid1_, handler_->getLastJID()); + CPPUNIT_ASSERT_EQUAL(String("NewName2"), roster_->getNameForJID(jid1_)); + roster_->addContact(jid2_, "NewName3", groups1_, RosterItemPayload::Both); + handler_->reset(); + roster_->removeContact(jid2_); + CPPUNIT_ASSERT_EQUAL(Remove, handler_->getLastEvent()); + CPPUNIT_ASSERT_EQUAL(jid2_, handler_->getLastJID()); + handler_->reset(); + roster_->removeContact(jid1_); + CPPUNIT_ASSERT_EQUAL(Remove, handler_->getLastEvent()); + CPPUNIT_ASSERT_EQUAL(jid1_, handler_->getLastJID()); + } + + void testJIDUpdated() { + roster_->addContact(jid1_, "NewName", groups1_, RosterItemPayload::Both); + CPPUNIT_ASSERT_EQUAL(Add, handler_->getLastEvent()); + CPPUNIT_ASSERT_EQUAL(jid1_, handler_->getLastJID()); + CPPUNIT_ASSERT_EQUAL(String("NewName"), roster_->getNameForJID(jid1_)); + CPPUNIT_ASSERT(groups1_ == roster_->getGroupsForJID(jid1_)); + handler_->reset(); + roster_->addContact(jid1_, "NameTwo", groups2_, RosterItemPayload::Both); + CPPUNIT_ASSERT_EQUAL(Update, handler_->getLastEvent()); + CPPUNIT_ASSERT_EQUAL(jid1_, handler_->getLastJID()); + CPPUNIT_ASSERT_EQUAL(String("NameTwo"), roster_->getNameForJID(jid1_)); + CPPUNIT_ASSERT(groups2_ == roster_->getGroupsForJID(jid1_)); + } + +}; +CPPUNIT_TEST_SUITE_REGISTRATION(XMPPRosterTest); + diff --git a/Swiften/Roster/UserRosterAction.h b/Swiften/Roster/UserRosterAction.h new file mode 100644 index 0000000..80ace68 --- /dev/null +++ b/Swiften/Roster/UserRosterAction.h @@ -0,0 +1,32 @@ +#ifndef SWIFTEN_UserRosterAction_H +#define SWIFTEN_UserRosterAction_H + +namespace Swift { +class RosterItem; +class TreeWidgetItem; + +class UserRosterAction { + public: + virtual ~UserRosterAction() {}; + void setRosterItem(RosterItem *item) { + rosterItem_ = item; + }; + void setTreeWidgetItem(TreeWidgetItem *item) { + treeWidgetItem_ = item; + } + RosterItem* getRosterItem() { + return rosterItem_; + } + TreeWidgetItem* getTreeWidgetItem() { + return treeWidgetItem_; + } + + private: + RosterItem *rosterItem_; + TreeWidgetItem *treeWidgetItem_; +}; + +} +#endif + + diff --git a/Swiften/Roster/XMPPRoster.cpp b/Swiften/Roster/XMPPRoster.cpp new file mode 100644 index 0000000..62edc45 --- /dev/null +++ b/Swiften/Roster/XMPPRoster.cpp @@ -0,0 +1,48 @@ +#include "Swiften/Roster/XMPPRoster.h" + +namespace Swift { + +void XMPPRoster::addContact(const JID& jid, const String& name, const std::vector<String>& groups, RosterItemPayload::Subscription subscription) { + JID bareJID(jid.toBare()); + bool exists = containsJID(bareJID); + String oldName = getNameForJID(bareJID); + std::vector<String> oldGroups = entries_[bareJID].groups; + if (exists) { + entries_.erase(bareJID); + } + XMPPRosterItem item; + item.groups = groups; + item.name = name; + item.jid = jid; + item.subscription = subscription; + entries_[bareJID] = item; + if (exists) { + onJIDUpdated(bareJID, oldName, oldGroups); + } else { + onJIDAdded(bareJID); + } +} + +void XMPPRoster::removeContact(const JID& jid) { + entries_.erase(JID(jid.toBare())); + onJIDRemoved(jid); +} + +bool XMPPRoster::containsJID(const JID& jid) { + return entries_.find(JID(jid.toBare())) != entries_.end(); +} + +const String& XMPPRoster::getNameForJID(const JID& jid) { + return entries_[JID(jid.toBare())].name; +} + +const std::vector<String>& XMPPRoster::getGroupsForJID(const JID& jid) { + return entries_[JID(jid.toBare())].groups; +} + +RosterItemPayload::Subscription XMPPRoster::getSubscriptionStateForJID(const JID& jid) { + return entries_[JID(jid.toBare())].subscription; +} + +} + diff --git a/Swiften/Roster/XMPPRoster.h b/Swiften/Roster/XMPPRoster.h new file mode 100644 index 0000000..47326c3 --- /dev/null +++ b/Swiften/Roster/XMPPRoster.h @@ -0,0 +1,44 @@ +#ifndef SWIFTEN_XMPPRoster_H +#define SWIFTEN_XMPPRoster_H + +#include "Swiften/Base/String.h" +#include "Swiften/JID/JID.h" +#include "Swiften/Elements/RosterItemPayload.h" + +#include <map> +#include <vector> +#include <boost/signal.hpp> + +namespace Swift { + +struct XMPPRosterItem { + JID jid; + String name; + std::vector<String> groups; + RosterItemPayload::Subscription subscription; +}; + +class XMPPRoster { + public: + XMPPRoster() {}; + ~XMPPRoster() {}; + + void addContact(const JID& jid, const String& name, const std::vector<String>& groups, const RosterItemPayload::Subscription subscription); + bool containsJID(const JID& jid); + void removeContact(const JID& jid); + RosterItemPayload::Subscription getSubscriptionStateForJID(const JID& jid); + const String& getNameForJID(const JID& jid); + const std::vector<String>& getGroupsForJID(const JID& jid); + + boost::signal<void (const JID&)> onJIDAdded; + boost::signal<void (const JID&)> onJIDRemoved; + boost::signal<void (const JID&, const String&, const std::vector<String>&)> onJIDUpdated; + + private: + //std::map<JID, std::pair<String, std::vector<String> > > entries_; + std::map<JID, XMPPRosterItem> entries_; +}; +} + +#endif + diff --git a/Swiften/SASL/ClientAuthenticator.cpp b/Swiften/SASL/ClientAuthenticator.cpp new file mode 100644 index 0000000..5fc9e85 --- /dev/null +++ b/Swiften/SASL/ClientAuthenticator.cpp @@ -0,0 +1,11 @@ +#include "Swiften/SASL/ClientAuthenticator.h" + +namespace Swift { + +ClientAuthenticator::ClientAuthenticator(const String& name) : name(name) { +} + +ClientAuthenticator::~ClientAuthenticator() { +} + +} diff --git a/Swiften/SASL/ClientAuthenticator.h b/Swiften/SASL/ClientAuthenticator.h new file mode 100644 index 0000000..f42a51e --- /dev/null +++ b/Swiften/SASL/ClientAuthenticator.h @@ -0,0 +1,43 @@ +#pragma once + +#include "Swiften/Base/String.h" +#include "Swiften/Base/ByteArray.h" + +namespace Swift { + class ClientAuthenticator { + public: + ClientAuthenticator(const String& name); + virtual ~ClientAuthenticator(); + + const String& getName() const { + return name; + } + + void setCredentials(const String& authcid, const String& password, const String& authzid = String()) { + this->authcid = authcid; + this->password = password; + this->authzid = authzid; + } + + virtual ByteArray getResponse() const = 0; + virtual bool setChallenge(const ByteArray&) = 0; + + const String& getAuthenticationID() const { + return authcid; + } + + const String& getAuthorizationID() const { + return authzid; + } + + const String& getPassword() const { + return password; + } + + private: + String name; + String authcid; + String password; + String authzid; + }; +} diff --git a/Swiften/SASL/PLAINClientAuthenticator.cpp b/Swiften/SASL/PLAINClientAuthenticator.cpp new file mode 100644 index 0000000..8f88c3c --- /dev/null +++ b/Swiften/SASL/PLAINClientAuthenticator.cpp @@ -0,0 +1,16 @@ +#include "Swiften/SASL/PLAINClientAuthenticator.h" + +namespace Swift { + +PLAINClientAuthenticator::PLAINClientAuthenticator() : ClientAuthenticator("PLAIN") { +} + +ByteArray PLAINClientAuthenticator::getResponse() const { + return ByteArray(getAuthorizationID()) + '\0' + ByteArray(getAuthenticationID()) + '\0' + ByteArray(getPassword()); +} + +bool PLAINClientAuthenticator::setChallenge(const ByteArray&) { + return true; +} + +} diff --git a/Swiften/SASL/PLAINClientAuthenticator.h b/Swiften/SASL/PLAINClientAuthenticator.h new file mode 100644 index 0000000..854eb30 --- /dev/null +++ b/Swiften/SASL/PLAINClientAuthenticator.h @@ -0,0 +1,13 @@ +#pragma once + +#include "Swiften/SASL/ClientAuthenticator.h" + +namespace Swift { + class PLAINClientAuthenticator : public ClientAuthenticator { + public: + PLAINClientAuthenticator(); + + virtual ByteArray getResponse() const; + virtual bool setChallenge(const ByteArray&); + }; +} diff --git a/Swiften/SASL/PLAINMessage.cpp b/Swiften/SASL/PLAINMessage.cpp new file mode 100644 index 0000000..66f8cd0 --- /dev/null +++ b/Swiften/SASL/PLAINMessage.cpp @@ -0,0 +1,38 @@ +#include "Swiften/SASL/PLAINMessage.h" + +namespace Swift { + +PLAINMessage::PLAINMessage(const String& authcid, const String& password, const String& authzid) : authcid(authcid), authzid(authzid), password(password) { +} + +PLAINMessage::PLAINMessage(const ByteArray& value) { + size_t i = 0; + while (i < value.getSize() && value[i] != '\0') { + authzid += value[i]; + ++i; + } + if (i == value.getSize()) { + return; + } + ++i; + while (i < value.getSize() && value[i] != '\0') { + authcid += value[i]; + ++i; + } + if (i == value.getSize()) { + authcid = ""; + return; + } + ++i; + while (i < value.getSize()) { + password += value[i]; + ++i; + } +} + +ByteArray PLAINMessage::getValue() const { + String s = authzid + '\0' + authcid + '\0' + password; + return ByteArray(s.getUTF8Data(), s.getUTF8Size()); +} + +} diff --git a/Swiften/SASL/PLAINMessage.h b/Swiften/SASL/PLAINMessage.h new file mode 100644 index 0000000..dd5e2ee --- /dev/null +++ b/Swiften/SASL/PLAINMessage.h @@ -0,0 +1,33 @@ +// TODO: Get rid of this +// +#pragma once + +#include "Swiften/Base/String.h" +#include "Swiften/Base/ByteArray.h" + +namespace Swift { + class PLAINMessage { + public: + PLAINMessage(const String& authcid, const String& password, const String& authzid = ""); + PLAINMessage(const ByteArray& value); + + ByteArray getValue() const; + + const String& getAuthenticationID() const { + return authcid; + } + + const String& getPassword() const { + return password; + } + + const String& getAuthorizationID() const { + return authzid; + } + + private: + String authcid; + String authzid; + String password; + }; +} diff --git a/Swiften/SASL/SCRAMSHA1ClientAuthenticator.cpp b/Swiften/SASL/SCRAMSHA1ClientAuthenticator.cpp new file mode 100644 index 0000000..5dc924e --- /dev/null +++ b/Swiften/SASL/SCRAMSHA1ClientAuthenticator.cpp @@ -0,0 +1,143 @@ +#include "Swiften/SASL/SCRAMSHA1ClientAuthenticator.h" + +#include <cassert> +#include <map> +#include <boost/lexical_cast.hpp> + +#include "Swiften/StringCodecs/SHA1.h" +#include "Swiften/StringCodecs/Base64.h" +#include "Swiften/StringCodecs/HMACSHA1.h" +#include "Swiften/StringCodecs/PBKDF2.h" +#include "Swiften/StringPrep/StringPrep.h" + +namespace Swift { + +static String escape(const String& s) { + String result; + for (size_t i = 0; i < s.getUTF8Size(); ++i) { + if (s[i] == ',') { + result += "=2C"; + } + else if (s[i] == '=') { + result += "=3D"; + } + else { + result += s[i]; + } + } + return result; +} + + +SCRAMSHA1ClientAuthenticator::SCRAMSHA1ClientAuthenticator(const String& nonce) : ClientAuthenticator("SCRAM-SHA-1"), step(Initial), clientnonce(nonce) { +} + +ByteArray SCRAMSHA1ClientAuthenticator::getResponse() const { + if (step == Initial) { + return getGS2Header() + getInitialBareClientMessage(); + } + else if (step == Proof) { + ByteArray clientKey = HMACSHA1::getResult(saltedPassword, "Client Key"); + ByteArray storedKey = SHA1::getHash(clientKey); + ByteArray clientSignature = HMACSHA1::getResult(storedKey, authMessage); + ByteArray clientProof = clientKey; + for (unsigned int i = 0; i < clientProof.getSize(); ++i) { + clientProof[i] ^= clientSignature[i]; + } + ByteArray result = ByteArray("c=") + Base64::encode(getGS2Header()) + ",r=" + clientnonce + serverNonce + ",p=" + Base64::encode(clientProof); + return result; + } + else { + return ByteArray(); + } +} + +bool SCRAMSHA1ClientAuthenticator::setChallenge(const ByteArray& challenge) { + if (step == Initial) { + initialServerMessage = challenge; + + std::map<char, String> keys = parseMap(String(initialServerMessage.getData(), initialServerMessage.getSize())); + + // Extract the salt + ByteArray salt = Base64::decode(keys['s']); + + // Extract the server nonce + String clientServerNonce = keys['r']; + if (clientServerNonce.getUTF8Size() <= clientnonce.getUTF8Size()) { + return false; + } + String receivedClientNonce = clientServerNonce.getSubstring(0, clientnonce.getUTF8Size()); + if (receivedClientNonce != clientnonce) { + return false; + } + serverNonce = clientServerNonce.getSubstring(clientnonce.getUTF8Size(), clientServerNonce.npos()); + + // Extract the number of iterations + int iterations = 0; + try { + iterations = boost::lexical_cast<int>(keys['i'].getUTF8String()); + } + catch (const boost::bad_lexical_cast&) { + return false; + } + if (iterations <= 0) { + return false; + } + + // Compute all the values needed for the server signature + saltedPassword = PBKDF2::encode(StringPrep::getPrepared(getPassword(), StringPrep::SASLPrep), salt, iterations); + authMessage = getInitialBareClientMessage() + "," + initialServerMessage + "," + "c=" + Base64::encode(getGS2Header()) + ",r=" + clientnonce + serverNonce; + ByteArray serverKey = HMACSHA1::getResult(saltedPassword, "Server Key"); + serverSignature = HMACSHA1::getResult(serverKey, authMessage); + + step = Proof; + return true; + } + else if (step == Proof) { + ByteArray result = ByteArray("v=") + ByteArray(Base64::encode(serverSignature)); + step = Final; + return challenge == result; + } + else { + return true; + } +} + +std::map<char, String> SCRAMSHA1ClientAuthenticator::parseMap(const String& s) { + std::map<char, String> result; + if (s.getUTF8Size() > 0) { + char key; + String value; + size_t i = 0; + bool expectKey = true; + while (i < s.getUTF8Size()) { + if (expectKey) { + key = s[i]; + expectKey = false; + i++; + } + else if (s[i] == ',') { + result[key] = value; + value = ""; + expectKey = true; + } + else { + value += s[i]; + } + i++; + } + result[key] = value; + } + return result; +} + +ByteArray SCRAMSHA1ClientAuthenticator::getInitialBareClientMessage() const { + String authenticationID = StringPrep::getPrepared(getAuthenticationID(), StringPrep::SASLPrep); + return ByteArray(String("n=" + escape(authenticationID) + ",r=" + clientnonce)); +} + +ByteArray SCRAMSHA1ClientAuthenticator::getGS2Header() const { + return ByteArray("n,") + (getAuthorizationID().isEmpty() ? "" : "a=" + escape(getAuthorizationID())) + ","; +} + +} diff --git a/Swiften/SASL/SCRAMSHA1ClientAuthenticator.h b/Swiften/SASL/SCRAMSHA1ClientAuthenticator.h new file mode 100644 index 0000000..3d28014 --- /dev/null +++ b/Swiften/SASL/SCRAMSHA1ClientAuthenticator.h @@ -0,0 +1,36 @@ +#pragma once + +#include <map> + +#include "Swiften/Base/String.h" +#include "Swiften/Base/ByteArray.h" +#include "Swiften/SASL/ClientAuthenticator.h" + +namespace Swift { + class SCRAMSHA1ClientAuthenticator : public ClientAuthenticator { + public: + SCRAMSHA1ClientAuthenticator(const String& nonce); + + virtual ByteArray getResponse() const; + virtual bool setChallenge(const ByteArray&); + + private: + ByteArray getInitialBareClientMessage() const; + ByteArray getGS2Header() const; + + static std::map<char, String> parseMap(const String&); + + private: + enum Step { + Initial, + Proof, + Final + } step; + String clientnonce; + ByteArray initialServerMessage; + ByteArray serverNonce; + ByteArray authMessage; + ByteArray saltedPassword; + ByteArray serverSignature; + }; +} diff --git a/Swiften/SASL/SConscript b/Swiften/SASL/SConscript new file mode 100644 index 0000000..22b242e --- /dev/null +++ b/Swiften/SASL/SConscript @@ -0,0 +1,12 @@ +Import("swiften_env") + +myenv = swiften_env.Clone() +myenv.MergeFlags(swiften_env["LIBIDN_FLAGS"]) + +objects = myenv.StaticObject([ + "ClientAuthenticator.cpp", + "PLAINClientAuthenticator.cpp", + "PLAINMessage.cpp", + "SCRAMSHA1ClientAuthenticator.cpp", + ]) +swiften_env.Append(SWIFTEN_OBJECTS = [objects]) diff --git a/Swiften/SASL/UnitTest/PLAINClientAuthenticatorTest.cpp b/Swiften/SASL/UnitTest/PLAINClientAuthenticatorTest.cpp new file mode 100644 index 0000000..e92cd34 --- /dev/null +++ b/Swiften/SASL/UnitTest/PLAINClientAuthenticatorTest.cpp @@ -0,0 +1,32 @@ +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> + +#include "Swiften/SASL/PLAINClientAuthenticator.h" + +using namespace Swift; + +class PLAINClientAuthenticatorTest : public CppUnit::TestFixture { + CPPUNIT_TEST_SUITE(PLAINClientAuthenticatorTest); + CPPUNIT_TEST(testGetResponse_WithoutAuthzID); + CPPUNIT_TEST(testGetResponse_WithAuthzID); + CPPUNIT_TEST_SUITE_END(); + + public: + void testGetResponse_WithoutAuthzID() { + PLAINClientAuthenticator testling; + + testling.setCredentials("user", "pass"); + + CPPUNIT_ASSERT_EQUAL(testling.getResponse(), ByteArray("\0user\0pass", 10)); + } + + void testGetResponse_WithAuthzID() { + PLAINClientAuthenticator testling; + + testling.setCredentials("user", "pass", "authz"); + + CPPUNIT_ASSERT_EQUAL(testling.getResponse(), ByteArray("authz\0user\0pass", 15)); + } +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(PLAINClientAuthenticatorTest); diff --git a/Swiften/SASL/UnitTest/PLAINMessageTest.cpp b/Swiften/SASL/UnitTest/PLAINMessageTest.cpp new file mode 100644 index 0000000..6493bd5 --- /dev/null +++ b/Swiften/SASL/UnitTest/PLAINMessageTest.cpp @@ -0,0 +1,61 @@ +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> + +#include "Swiften/SASL/PLAINMessage.h" + +using namespace Swift; + +class PLAINMessageTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(PLAINMessageTest); + CPPUNIT_TEST(testGetValue_WithoutAuthzID); + CPPUNIT_TEST(testGetValue_WithAuthzID); + CPPUNIT_TEST(testConstructor_WithoutAuthzID); + CPPUNIT_TEST(testConstructor_WithAuthzID); + CPPUNIT_TEST(testConstructor_NoAuthcid); + CPPUNIT_TEST(testConstructor_NoPassword); + CPPUNIT_TEST_SUITE_END(); + + public: + PLAINMessageTest() {} + + void testGetValue_WithoutAuthzID() { + PLAINMessage message("user", "pass"); + CPPUNIT_ASSERT_EQUAL(message.getValue(), ByteArray("\0user\0pass", 10)); + } + + void testGetValue_WithAuthzID() { + PLAINMessage message("user", "pass", "authz"); + CPPUNIT_ASSERT_EQUAL(message.getValue(), ByteArray("authz\0user\0pass", 15)); + } + + void testConstructor_WithoutAuthzID() { + PLAINMessage message(ByteArray("\0user\0pass", 10)); + + CPPUNIT_ASSERT_EQUAL(String(""), message.getAuthorizationID()); + CPPUNIT_ASSERT_EQUAL(String("user"), message.getAuthenticationID()); + CPPUNIT_ASSERT_EQUAL(String("pass"), message.getPassword()); + } + + void testConstructor_WithAuthzID() { + PLAINMessage message(ByteArray("authz\0user\0pass", 15)); + + CPPUNIT_ASSERT_EQUAL(String("authz"), message.getAuthorizationID()); + CPPUNIT_ASSERT_EQUAL(String("user"), message.getAuthenticationID()); + CPPUNIT_ASSERT_EQUAL(String("pass"), message.getPassword()); + } + + void testConstructor_NoAuthcid() { + PLAINMessage message(ByteArray("authzid", 7)); + + CPPUNIT_ASSERT_EQUAL(String(""), message.getAuthenticationID()); + } + + void testConstructor_NoPassword() { + PLAINMessage message(ByteArray("authzid\0authcid", 15)); + + CPPUNIT_ASSERT_EQUAL(String(""), message.getAuthenticationID()); + } +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(PLAINMessageTest); diff --git a/Swiften/SASL/UnitTest/SCRAMSHA1ClientAuthenticatorTest.cpp b/Swiften/SASL/UnitTest/SCRAMSHA1ClientAuthenticatorTest.cpp new file mode 100644 index 0000000..db69d13 --- /dev/null +++ b/Swiften/SASL/UnitTest/SCRAMSHA1ClientAuthenticatorTest.cpp @@ -0,0 +1,172 @@ +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> + +#include "Swiften/SASL/SCRAMSHA1ClientAuthenticator.h" +#include "Swiften/Base/ByteArray.h" + +using namespace Swift; + +class SCRAMSHA1ClientAuthenticatorTest : public CppUnit::TestFixture { + CPPUNIT_TEST_SUITE(SCRAMSHA1ClientAuthenticatorTest); + CPPUNIT_TEST(testGetInitialResponse); + CPPUNIT_TEST(testGetInitialResponse_UsernameHasSpecialChars); + CPPUNIT_TEST(testGetInitialResponse_WithAuthorizationID); + CPPUNIT_TEST(testGetInitialResponse_WithAuthorizationIDWithSpecialChars); + CPPUNIT_TEST(testGetFinalResponse); + CPPUNIT_TEST(testSetChallenge); + CPPUNIT_TEST(testSetChallenge_InvalidClientNonce); + CPPUNIT_TEST(testSetChallenge_OnlyClientNonce); + CPPUNIT_TEST(testSetChallenge_InvalidIterations); + CPPUNIT_TEST(testSetChallenge_ZeroIterations); + CPPUNIT_TEST(testSetChallenge_NegativeIterations); + CPPUNIT_TEST(testSetChallenge_MissingIterations); + CPPUNIT_TEST(testSetFinalChallenge); + CPPUNIT_TEST(testSetFinalChallenge_InvalidChallenge); + CPPUNIT_TEST(testGetResponseAfterFinalChallenge); + CPPUNIT_TEST_SUITE_END(); + + public: + void setUp() { + } + + void testGetInitialResponse() { + SCRAMSHA1ClientAuthenticator testling("abcdefghABCDEFGH"); + testling.setCredentials("user", "pass", ""); + + ByteArray response = testling.getResponse(); + + CPPUNIT_ASSERT_EQUAL(String("n,,n=user,r=abcdefghABCDEFGH"), testling.getResponse().toString()); + } + + void testGetInitialResponse_UsernameHasSpecialChars() { + SCRAMSHA1ClientAuthenticator testling("abcdefghABCDEFGH"); + testling.setCredentials(",us=,er=", "pass", ""); + + ByteArray response = testling.getResponse(); + + CPPUNIT_ASSERT_EQUAL(String("n,,n==2Cus=3D=2Cer=3D,r=abcdefghABCDEFGH"), testling.getResponse().toString()); + } + + void testGetInitialResponse_WithAuthorizationID() { + SCRAMSHA1ClientAuthenticator testling("abcdefghABCDEFGH"); + testling.setCredentials("user", "pass", "auth"); + + ByteArray response = testling.getResponse(); + + CPPUNIT_ASSERT_EQUAL(String("n,a=auth,n=user,r=abcdefghABCDEFGH"), testling.getResponse().toString()); + } + + void testGetInitialResponse_WithAuthorizationIDWithSpecialChars() { + SCRAMSHA1ClientAuthenticator testling("abcdefghABCDEFGH"); + testling.setCredentials("user", "pass", "a=u,th"); + + ByteArray response = testling.getResponse(); + + CPPUNIT_ASSERT_EQUAL(String("n,a=a=3Du=2Cth,n=user,r=abcdefghABCDEFGH"), testling.getResponse().toString()); + } + + void testGetFinalResponse() { + SCRAMSHA1ClientAuthenticator testling("abcdefgh"); + testling.setCredentials("user", "pass", ""); + testling.setChallenge(ByteArray("r=abcdefghABCDEFGH,s=MTIzNDU2NzgK,i=4096")); + + ByteArray response = testling.getResponse(); + + CPPUNIT_ASSERT_EQUAL(String("c=biws,r=abcdefghABCDEFGH,p=CZbjGDpIteIJwQNBgO0P8pKkMGY="), testling.getResponse().toString()); + } + + void testSetFinalChallenge() { + SCRAMSHA1ClientAuthenticator testling("abcdefgh"); + testling.setCredentials("user", "pass", ""); + testling.setChallenge(ByteArray("r=abcdefghABCDEFGH,s=MTIzNDU2NzgK,i=4096")); + + bool result = testling.setChallenge(ByteArray("v=Dd+Q20knZs9jeeK0pi1Mx1Se+yo=")); + + CPPUNIT_ASSERT(result); + } + + void testSetChallenge() { + SCRAMSHA1ClientAuthenticator testling("abcdefgh"); + testling.setCredentials("user", "pass", ""); + + bool result = testling.setChallenge(ByteArray("r=abcdefghABCDEFGH,s=MTIzNDU2NzgK,i=4096")); + + CPPUNIT_ASSERT(result); + } + + void testSetChallenge_InvalidClientNonce() { + SCRAMSHA1ClientAuthenticator testling("abcdefgh"); + testling.setCredentials("user", "pass", ""); + + bool result = testling.setChallenge(ByteArray("r=abcdefgiABCDEFGH,s=MTIzNDU2NzgK,i=4096")); + + CPPUNIT_ASSERT(!result); + } + + void testSetChallenge_OnlyClientNonce() { + SCRAMSHA1ClientAuthenticator testling("abcdefgh"); + testling.setCredentials("user", "pass", ""); + + bool result = testling.setChallenge(ByteArray("r=abcdefgh,s=MTIzNDU2NzgK,i=4096")); + + CPPUNIT_ASSERT(!result); + } + + void testSetChallenge_InvalidIterations() { + SCRAMSHA1ClientAuthenticator testling("abcdefgh"); + testling.setCredentials("user", "pass", ""); + + bool result = testling.setChallenge(ByteArray("r=abcdefghABCDEFGH,s=MTIzNDU2NzgK,i=bla")); + + CPPUNIT_ASSERT(!result); + } + + void testSetChallenge_MissingIterations() { + SCRAMSHA1ClientAuthenticator testling("abcdefgh"); + testling.setCredentials("user", "pass", ""); + + bool result = testling.setChallenge(ByteArray("r=abcdefghABCDEFGH,s=MTIzNDU2NzgK")); + + CPPUNIT_ASSERT(!result); + } + + void testSetChallenge_ZeroIterations() { + SCRAMSHA1ClientAuthenticator testling("abcdefgh"); + testling.setCredentials("user", "pass", ""); + + bool result = testling.setChallenge(ByteArray("r=abcdefghABCDEFGH,s=MTIzNDU2NzgK,i=0")); + + CPPUNIT_ASSERT(!result); + } + + void testSetChallenge_NegativeIterations() { + SCRAMSHA1ClientAuthenticator testling("abcdefgh"); + testling.setCredentials("user", "pass", ""); + + bool result = testling.setChallenge(ByteArray("r=abcdefghABCDEFGH,s=MTIzNDU2NzgK,i=-1")); + + CPPUNIT_ASSERT(!result); + } + + void testSetFinalChallenge_InvalidChallenge() { + SCRAMSHA1ClientAuthenticator testling("abcdefgh"); + testling.setCredentials("user", "pass", ""); + testling.setChallenge(ByteArray("r=abcdefghABCDEFGH,s=MTIzNDU2NzgK,i=4096")); + bool result = testling.setChallenge(ByteArray("v=e26kI69ICb6zosapLLxrER/631A=")); + + CPPUNIT_ASSERT(!result); + } + + void testGetResponseAfterFinalChallenge() { + SCRAMSHA1ClientAuthenticator testling("abcdefgh"); + testling.setCredentials("user", "pass", ""); + testling.setChallenge(ByteArray("r=abcdefghABCDEFGH,s=MTIzNDU2NzgK,i=4096")); + testling.setChallenge(ByteArray("v=Dd+Q20knZs9jeeK0pi1Mx1Se+yo=")); + + ByteArray result = testling.getResponse(); + + CPPUNIT_ASSERT_EQUAL(ByteArray(), result); + } +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(SCRAMSHA1ClientAuthenticatorTest); diff --git a/Swiften/SConscript b/Swiften/SConscript new file mode 100644 index 0000000..82c75d5 --- /dev/null +++ b/Swiften/SConscript @@ -0,0 +1,221 @@ +Import("env") + +################################################################################ +# Flags +################################################################################ + +if env["SCONS_STAGE"] == "flags" : + env["SWIFTEN_FLAGS"] = { + "LIBPATH": [Dir(".")], + "LIBS": ["Swiften"] + } + +################################################################################ +# Build +################################################################################ + +if env["SCONS_STAGE"] == "build" : + swiften_env = env.Clone() + swiften_env.MergeFlags(swiften_env["BOOST_FLAGS"]) + Export("swiften_env") + +# TODO: Move all this to a submodule SConscript + myenv = swiften_env.Clone() + myenv.MergeFlags(myenv["ZLIB_FLAGS"]) + myenv.MergeFlags(myenv["OPENSSL_FLAGS"]) + sources = [ + "Avatars/AvatarFileStorage.cpp", + "Avatars/AvatarManager.cpp", + "Avatars/AvatarStorage.cpp", + "Chat/ChatStateTracker.cpp", + "Chat/ChatStateNotifier.cpp", + "Chat/ChatStateMessageSender.cpp", + "Client/Client.cpp", + "Client/ClientSession.cpp", + "Compress/ZLibCodecompressor.cpp", + "Disco/CapsInfoGenerator.cpp", + "Elements/DiscoInfo.cpp", + "Elements/Element.cpp", + "Elements/IQ.cpp", + "Elements/Payload.cpp", + "Elements/RosterPayload.cpp", + "Elements/Stanza.cpp", + "MUC/MUC.cpp", + "MUC/MUCOccupant.cpp", + "MUC/MUCRegistry.cpp", + "MUC/MUCBookmarkManager.cpp", + "Notifier/Notifier.cpp", + "Presence/PresenceOracle.cpp", + "Presence/PresenceSender.cpp", + "Queries/IQChannel.cpp", + "Queries/IQHandler.cpp", + "Queries/IQRouter.cpp", + "Queries/Request.cpp", + "Queries/Responders/DiscoInfoResponder.cpp", + "Queries/Responders/SoftwareVersionResponder.cpp", + "Roster/ContactRosterItem.cpp", + "Roster/Roster.cpp", + "Roster/XMPPRoster.cpp", + "Serializer/AuthRequestSerializer.cpp", + "Serializer/AuthSuccessSerializer.cpp", + "Serializer/AuthChallengeSerializer.cpp", + "Serializer/AuthResponseSerializer.cpp", + "Serializer/CompressRequestSerializer.cpp", + "Serializer/ElementSerializer.cpp", + "Serializer/MessageSerializer.cpp", + "Serializer/PayloadSerializer.cpp", + "Serializer/PayloadSerializerCollection.cpp", + "Serializer/PayloadSerializers/CapsInfoSerializer.cpp", + "Serializer/PayloadSerializers/ChatStateSerializer.cpp", + "Serializer/PayloadSerializers/DiscoInfoSerializer.cpp", + "Serializer/PayloadSerializers/ErrorSerializer.cpp", + "Serializer/PayloadSerializers/FullPayloadSerializerCollection.cpp", + "Serializer/PayloadSerializers/MUCPayloadSerializer.cpp", + "Serializer/PayloadSerializers/ResourceBindSerializer.cpp", + "Serializer/PayloadSerializers/RosterSerializer.cpp", + "Serializer/PayloadSerializers/SecurityLabelSerializer.cpp", + "Serializer/PayloadSerializers/SecurityLabelsCatalogSerializer.cpp", + "Serializer/PayloadSerializers/SoftwareVersionSerializer.cpp", + "Serializer/PayloadSerializers/VCardSerializer.cpp", + "Serializer/PayloadSerializers/VCardUpdateSerializer.cpp", + "Serializer/PayloadSerializers/StorageSerializer.cpp", + "Serializer/PayloadSerializers/PrivateStorageSerializer.cpp", + "Serializer/PresenceSerializer.cpp", + "Serializer/StanzaSerializer.cpp", + "Serializer/StreamFeaturesSerializer.cpp", + "Serializer/XML/XMLElement.cpp", + "Serializer/XML/XMLNode.cpp", + "Serializer/XMPPSerializer.cpp", + "Server/ServerFromClientSession.cpp", + "Server/ServerSession.cpp", + "Server/ServerStanzaRouter.cpp", + "Server/SimpleUserRegistry.cpp", + "Server/UserRegistry.cpp", + "Session/Session.cpp", + "Session/SessionStream.cpp", + "Session/BasicSessionStream.cpp", + "StringCodecs/Base64.cpp", + "StringCodecs/SHA1.cpp", + "StringCodecs/HMACSHA1.cpp", + "StringCodecs/MD5.cpp", + "StringCodecs/PBKDF2.cpp", + "StringCodecs/Hexify.cpp", + ] +# "Notifier/GrowlNotifier.cpp", + + if myenv.get("HAVE_OPENSSL", 0) : + sources += ["TLS/OpenSSL/OpenSSLContext.cpp"] + + SConscript(dirs = [ + "Base", + "StringPrep", + "SASL", + "Application", + "EventLoop", + "Parser", + "JID", + "Network", + "History", + "StreamStack", + "LinkLocal", + ]) + SConscript(test_only = True, dirs = [ + "QA", + ]) + SConscript(dirs = [ + "Examples" + ]) + + myenv.StaticLibrary("Swiften", sources + swiften_env["SWIFTEN_OBJECTS"]) + + env.Append(UNITTEST_SOURCES = [ + File("Application/UnitTest/ApplicationTest.cpp"), + File("Avatars/UnitTest/MockAvatarManager.cpp"), + File("Base/UnitTest/IDGeneratorTest.cpp"), + File("Base/UnitTest/StringTest.cpp"), + File("Base/UnitTest/ByteArrayTest.cpp"), + File("Chat/UnitTest/ChatStateNotifierTest.cpp"), +# File("Chat/UnitTest/ChatStateTrackerTest.cpp"), + File("Client/UnitTest/ClientSessionTest.cpp"), + File("Compress/UnitTest/ZLibCompressorTest.cpp"), + File("Compress/UnitTest/ZLibDecompressorTest.cpp"), + File("Disco/UnitTest/CapsInfoGeneratorTest.cpp"), + File("Elements/UnitTest/IQTest.cpp"), + File("Elements/UnitTest/StanzaTest.cpp"), + File("Elements/UnitTest/StanzasTest.cpp"), + File("EventLoop/UnitTest/EventLoopTest.cpp"), + File("EventLoop/UnitTest/SimpleEventLoopTest.cpp"), + File("History/UnitTest/SQLiteHistoryManagerTest.cpp"), + File("JID/UnitTest/JIDTest.cpp"), + File("LinkLocal/UnitTest/LinkLocalConnectorTest.cpp"), + File("LinkLocal/UnitTest/LinkLocalServiceBrowserTest.cpp"), + File("LinkLocal/UnitTest/LinkLocalServiceInfoTest.cpp"), + File("LinkLocal/UnitTest/LinkLocalServiceTest.cpp"), + File("Network/UnitTest/HostAddressTest.cpp"), + File("Network/UnitTest/ConnectorTest.cpp"), + File("Parser/PayloadParsers/UnitTest/BodyParserTest.cpp"), + File("Parser/PayloadParsers/UnitTest/DiscoInfoParserTest.cpp"), + File("Parser/PayloadParsers/UnitTest/ErrorParserTest.cpp"), + File("Parser/PayloadParsers/UnitTest/PriorityParserTest.cpp"), + File("Parser/PayloadParsers/UnitTest/RawXMLPayloadParserTest.cpp"), + File("Parser/PayloadParsers/UnitTest/ResourceBindParserTest.cpp"), + File("Parser/PayloadParsers/UnitTest/RosterParserTest.cpp"), + File("Parser/PayloadParsers/UnitTest/SecurityLabelParserTest.cpp"), + File("Parser/PayloadParsers/UnitTest/SecurityLabelsCatalogParserTest.cpp"), + File("Parser/PayloadParsers/UnitTest/SoftwareVersionParserTest.cpp"), + File("Parser/PayloadParsers/UnitTest/StatusParserTest.cpp"), + File("Parser/PayloadParsers/UnitTest/StatusShowParserTest.cpp"), + File("Parser/PayloadParsers/UnitTest/VCardParserTest.cpp"), + File("Parser/PayloadParsers/UnitTest/StorageParserTest.cpp"), + File("Parser/PayloadParsers/UnitTest/PrivateStorageParserTest.cpp"), + File("Parser/PayloadParsers/UnitTest/VCardUpdateParserTest.cpp"), + File("Parser/UnitTest/AttributeMapTest.cpp"), + File("Parser/UnitTest/IQParserTest.cpp"), + File("Parser/UnitTest/MessageParserTest.cpp"), + File("Parser/UnitTest/PayloadParserFactoryCollectionTest.cpp"), + File("Parser/UnitTest/PresenceParserTest.cpp"), + File("Parser/UnitTest/SerializingParserTest.cpp"), + File("Parser/UnitTest/StanzaParserTest.cpp"), + File("Parser/UnitTest/StreamFeaturesParserTest.cpp"), + File("Parser/UnitTest/XMLParserTest.cpp"), + File("Parser/UnitTest/XMPPParserTest.cpp"), + File("Presence/UnitTest/PresenceOracleTest.cpp"), + File("Presence/UnitTest/PresenceSenderTest.cpp"), + File("Queries/Requests/UnitTest/GetPrivateStorageRequestTest.cpp"), + File("Queries/Responders/UnitTest/DiscoInfoResponderTest.cpp"), + File("Queries/UnitTest/IQRouterTest.cpp"), + File("Queries/UnitTest/RequestTest.cpp"), + File("Queries/UnitTest/ResponderTest.cpp"), + File("Roster/UnitTest/OfflineRosterFilterTest.cpp"), + File("Roster/UnitTest/RosterTest.cpp"), + File("Roster/UnitTest/XMPPRosterTest.cpp"), + File("SASL/UnitTest/PLAINMessageTest.cpp"), + File("SASL/UnitTest/PLAINClientAuthenticatorTest.cpp"), + File("SASL/UnitTest/SCRAMSHA1ClientAuthenticatorTest.cpp"), + File("Serializer/PayloadSerializers/UnitTest/PayloadsSerializer.cpp"), + File("Serializer/PayloadSerializers/UnitTest/CapsInfoSerializerTest.cpp"), + File("Serializer/PayloadSerializers/UnitTest/DiscoInfoSerializerTest.cpp"), + File("Serializer/PayloadSerializers/UnitTest/ErrorSerializerTest.cpp"), + File("Serializer/PayloadSerializers/UnitTest/PrioritySerializerTest.cpp"), + File("Serializer/PayloadSerializers/UnitTest/ResourceBindSerializerTest.cpp"), + File("Serializer/PayloadSerializers/UnitTest/RosterSerializerTest.cpp"), + File("Serializer/PayloadSerializers/UnitTest/SecurityLabelSerializerTest.cpp"), + File("Serializer/PayloadSerializers/UnitTest/SecurityLabelsCatalogSerializerTest.cpp"), + File("Serializer/PayloadSerializers/UnitTest/SoftwareVersionSerializerTest.cpp"), + File("Serializer/PayloadSerializers/UnitTest/StatusSerializerTest.cpp"), + File("Serializer/PayloadSerializers/UnitTest/StatusShowSerializerTest.cpp"), + File("Serializer/PayloadSerializers/UnitTest/VCardUpdateSerializerTest.cpp"), + File("Serializer/PayloadSerializers/UnitTest/StorageSerializerTest.cpp"), + File("Serializer/PayloadSerializers/UnitTest/PrivateStorageSerializerTest.cpp"), + File("Serializer/UnitTest/StreamFeaturesSerializerTest.cpp"), + File("Serializer/XML/UnitTest/XMLElementTest.cpp"), + File("Server/UnitTest/ServerStanzaRouterTest.cpp"), + File("StreamStack/UnitTest/StreamStackTest.cpp"), + File("StreamStack/UnitTest/XMPPLayerTest.cpp"), + File("StringCodecs/UnitTest/Base64Test.cpp"), + File("StringCodecs/UnitTest/SHA1Test.cpp"), + File("StringCodecs/UnitTest/MD5Test.cpp"), + File("StringCodecs/UnitTest/HexifyTest.cpp"), + File("StringCodecs/UnitTest/HMACSHA1Test.cpp"), + File("StringCodecs/UnitTest/PBKDF2Test.cpp"), + ]) diff --git a/Swiften/Serializer/AuthChallengeSerializer.cpp b/Swiften/Serializer/AuthChallengeSerializer.cpp new file mode 100644 index 0000000..152607d --- /dev/null +++ b/Swiften/Serializer/AuthChallengeSerializer.cpp @@ -0,0 +1,17 @@ +#include "Swiften/Serializer/AuthChallengeSerializer.h" + +#include "Swiften/Elements/AuthChallenge.h" +#include "Swiften/StringCodecs/Base64.h" + +namespace Swift { + +AuthChallengeSerializer::AuthChallengeSerializer() { +} + +String AuthChallengeSerializer::serialize(boost::shared_ptr<Element> element) const { + boost::shared_ptr<AuthChallenge> authRequest(boost::dynamic_pointer_cast<AuthChallenge>(element)); + String value = (authRequest->getValue().isEmpty() ? "=" : Base64::encode(authRequest->getValue())); + return "<challenge xmlns=\"urn:ietf:params:xml:ns:xmpp-sasl\">" + value + "</challenge>"; +} + +} diff --git a/Swiften/Serializer/AuthChallengeSerializer.h b/Swiften/Serializer/AuthChallengeSerializer.h new file mode 100644 index 0000000..010d9a9 --- /dev/null +++ b/Swiften/Serializer/AuthChallengeSerializer.h @@ -0,0 +1,15 @@ +#pragma once + +#include <boost/shared_ptr.hpp> + +#include "Swiften/Elements/AuthChallenge.h" +#include "Swiften/Serializer/GenericElementSerializer.h" + +namespace Swift { + class AuthChallengeSerializer : public GenericElementSerializer<AuthChallenge> { + public: + AuthChallengeSerializer(); + + virtual String serialize(boost::shared_ptr<Element> element) const; + }; +} diff --git a/Swiften/Serializer/AuthFailureSerializer.h b/Swiften/Serializer/AuthFailureSerializer.h new file mode 100644 index 0000000..e7e2ced --- /dev/null +++ b/Swiften/Serializer/AuthFailureSerializer.h @@ -0,0 +1,22 @@ +#ifndef SWIFTEN_AuthFailureSerializer_H +#define SWIFTEN_AuthFailureSerializer_H + +#include <boost/shared_ptr.hpp> + +#include "Swiften/Elements/AuthFailure.h" +#include "Swiften/Serializer/GenericElementSerializer.h" +#include "Swiften/Serializer/XML/XMLElement.h" + +namespace Swift { + class AuthFailureSerializer : public GenericElementSerializer<AuthFailure> { + public: + AuthFailureSerializer() : GenericElementSerializer<AuthFailure>() { + } + + virtual String serialize(boost::shared_ptr<Element>) const { + return XMLElement("failure", "urn:ietf:params:xml:ns:xmpp-sasl").serialize(); + } + }; +} + +#endif diff --git a/Swiften/Serializer/AuthRequestSerializer.cpp b/Swiften/Serializer/AuthRequestSerializer.cpp new file mode 100644 index 0000000..7122485 --- /dev/null +++ b/Swiften/Serializer/AuthRequestSerializer.cpp @@ -0,0 +1,17 @@ +#include "Swiften/Serializer/AuthRequestSerializer.h" + +#include "Swiften/Elements/AuthRequest.h" +#include "Swiften/StringCodecs/Base64.h" + +namespace Swift { + +AuthRequestSerializer::AuthRequestSerializer() { +} + +String AuthRequestSerializer::serialize(boost::shared_ptr<Element> element) const { + boost::shared_ptr<AuthRequest> authRequest(boost::dynamic_pointer_cast<AuthRequest>(element)); + String value = (authRequest->getMessage().isEmpty() ? "=" : Base64::encode(authRequest->getMessage())); + return "<auth xmlns=\"urn:ietf:params:xml:ns:xmpp-sasl\" mechanism=\"" + authRequest->getMechanism() + "\">" + value + "</auth>"; +} + +} diff --git a/Swiften/Serializer/AuthRequestSerializer.h b/Swiften/Serializer/AuthRequestSerializer.h new file mode 100644 index 0000000..b5131a3 --- /dev/null +++ b/Swiften/Serializer/AuthRequestSerializer.h @@ -0,0 +1,18 @@ +#ifndef SWIFTEN_AuthRequestSerializer_H +#define SWIFTEN_AuthRequestSerializer_H + +#include <boost/shared_ptr.hpp> + +#include "Swiften/Elements/AuthRequest.h" +#include "Swiften/Serializer/GenericElementSerializer.h" + +namespace Swift { + class AuthRequestSerializer : public GenericElementSerializer<AuthRequest> { + public: + AuthRequestSerializer(); + + virtual String serialize(boost::shared_ptr<Element> element) const; + }; +} + +#endif diff --git a/Swiften/Serializer/AuthResponseSerializer.cpp b/Swiften/Serializer/AuthResponseSerializer.cpp new file mode 100644 index 0000000..2348a3d --- /dev/null +++ b/Swiften/Serializer/AuthResponseSerializer.cpp @@ -0,0 +1,17 @@ +#include "Swiften/Serializer/AuthResponseSerializer.h" + +#include "Swiften/Elements/AuthResponse.h" +#include "Swiften/StringCodecs/Base64.h" + +namespace Swift { + +AuthResponseSerializer::AuthResponseSerializer() { +} + +String AuthResponseSerializer::serialize(boost::shared_ptr<Element> element) const { + boost::shared_ptr<AuthResponse> authRequest(boost::dynamic_pointer_cast<AuthResponse>(element)); + String value = (authRequest->getValue().isEmpty() ? "=" : Base64::encode(authRequest->getValue())); + return "<response xmlns=\"urn:ietf:params:xml:ns:xmpp-sasl\">" + value + "</response>"; +} + +} diff --git a/Swiften/Serializer/AuthResponseSerializer.h b/Swiften/Serializer/AuthResponseSerializer.h new file mode 100644 index 0000000..8d47291 --- /dev/null +++ b/Swiften/Serializer/AuthResponseSerializer.h @@ -0,0 +1,15 @@ +#pragma once + +#include <boost/shared_ptr.hpp> + +#include "Swiften/Elements/AuthResponse.h" +#include "Swiften/Serializer/GenericElementSerializer.h" + +namespace Swift { + class AuthResponseSerializer : public GenericElementSerializer<AuthResponse> { + public: + AuthResponseSerializer(); + + virtual String serialize(boost::shared_ptr<Element> element) const; + }; +} diff --git a/Swiften/Serializer/AuthSuccessSerializer.cpp b/Swiften/Serializer/AuthSuccessSerializer.cpp new file mode 100644 index 0000000..6d7f195 --- /dev/null +++ b/Swiften/Serializer/AuthSuccessSerializer.cpp @@ -0,0 +1,17 @@ +#include "Swiften/Serializer/AuthSuccessSerializer.h" + +#include "Swiften/Elements/AuthSuccess.h" +#include "Swiften/StringCodecs/Base64.h" + +namespace Swift { + +AuthSuccessSerializer::AuthSuccessSerializer() { +} + +String AuthSuccessSerializer::serialize(boost::shared_ptr<Element> element) const { + boost::shared_ptr<AuthSuccess> authRequest(boost::dynamic_pointer_cast<AuthSuccess>(element)); + String value = (authRequest->getValue().isEmpty() ? "=" : Base64::encode(authRequest->getValue())); + return "<success xmlns=\"urn:ietf:params:xml:ns:xmpp-sasl\">" + value + "</challenge>"; +} + +} diff --git a/Swiften/Serializer/AuthSuccessSerializer.h b/Swiften/Serializer/AuthSuccessSerializer.h new file mode 100644 index 0000000..6ced772 --- /dev/null +++ b/Swiften/Serializer/AuthSuccessSerializer.h @@ -0,0 +1,15 @@ +#pragma once + +#include <boost/shared_ptr.hpp> + +#include "Swiften/Elements/AuthSuccess.h" +#include "Swiften/Serializer/GenericElementSerializer.h" + +namespace Swift { + class AuthSuccessSerializer : public GenericElementSerializer<AuthSuccess> { + public: + AuthSuccessSerializer(); + + virtual String serialize(boost::shared_ptr<Element> element) const; + }; +} diff --git a/Swiften/Serializer/CompressFailureSerializer.h b/Swiften/Serializer/CompressFailureSerializer.h new file mode 100644 index 0000000..c3e7953 --- /dev/null +++ b/Swiften/Serializer/CompressFailureSerializer.h @@ -0,0 +1,22 @@ +#ifndef SWIFTEN_CompressFailureSerializer_H +#define SWIFTEN_CompressFailureSerializer_H + +#include <boost/shared_ptr.hpp> + +#include "Swiften/Elements/CompressFailure.h" +#include "Swiften/Serializer/GenericElementSerializer.h" +#include "Swiften/Serializer/XML/XMLElement.h" + +namespace Swift { + class CompressFailureSerializer : public GenericElementSerializer<CompressFailure> { + public: + CompressFailureSerializer() : GenericElementSerializer<CompressFailure>() { + } + + virtual String serialize(boost::shared_ptr<Element>) const { + return XMLElement("failure", "http://jabber.org/protocol/compress").serialize(); + } + }; +} + +#endif diff --git a/Swiften/Serializer/CompressRequestSerializer.cpp b/Swiften/Serializer/CompressRequestSerializer.cpp new file mode 100644 index 0000000..4d3310b --- /dev/null +++ b/Swiften/Serializer/CompressRequestSerializer.cpp @@ -0,0 +1,19 @@ +#include "Swiften/Serializer/CompressRequestSerializer.h" + +#include "Swiften/Elements/CompressRequest.h" + +namespace Swift { + +CompressRequestSerializer::CompressRequestSerializer() { +} + +String CompressRequestSerializer::serialize(boost::shared_ptr<Element> element) const { + boost::shared_ptr<CompressRequest> compressRequest(boost::dynamic_pointer_cast<CompressRequest>(element)); + return "<compress xmlns='http://jabber.org/protocol/compress'><method>" + compressRequest->getMethod() + "</method></compress>"; +} + +bool CompressRequestSerializer::canSerialize(boost::shared_ptr<Element> element) const { + return dynamic_cast<CompressRequest*>(element.get()) != 0; +} + +} diff --git a/Swiften/Serializer/CompressRequestSerializer.h b/Swiften/Serializer/CompressRequestSerializer.h new file mode 100644 index 0000000..b6a493c --- /dev/null +++ b/Swiften/Serializer/CompressRequestSerializer.h @@ -0,0 +1,18 @@ +#ifndef SWIFTEN_COMPRESSREQUESTSERIALIZER_H +#define SWIFTEN_COMPRESSREQUESTSERIALIZER_H + +#include <boost/shared_ptr.hpp> + +#include "Swiften/Serializer/ElementSerializer.h" + +namespace Swift { + class CompressRequestSerializer : public ElementSerializer { + public: + CompressRequestSerializer(); + + virtual String serialize(boost::shared_ptr<Element> element) const; + virtual bool canSerialize(boost::shared_ptr<Element> element) const; + }; +} + +#endif diff --git a/Swiften/Serializer/ElementSerializer.cpp b/Swiften/Serializer/ElementSerializer.cpp new file mode 100644 index 0000000..22c64a6 --- /dev/null +++ b/Swiften/Serializer/ElementSerializer.cpp @@ -0,0 +1,8 @@ +#include "Swiften/Serializer/ElementSerializer.h" + +namespace Swift { + +ElementSerializer::~ElementSerializer() { +} + +} diff --git a/Swiften/Serializer/ElementSerializer.h b/Swiften/Serializer/ElementSerializer.h new file mode 100644 index 0000000..d2f5611 --- /dev/null +++ b/Swiften/Serializer/ElementSerializer.h @@ -0,0 +1,19 @@ +#ifndef SWIFTEN_ELEMENTSERIALIZER_H +#define SWIFTEN_ELEMENTSERIALIZER_H + +#include <boost/shared_ptr.hpp> + +#include "Swiften/Base/String.h" +#include "Swiften/Elements/Element.h" + +namespace Swift { + class ElementSerializer { + public: + virtual ~ElementSerializer(); + + virtual String serialize(boost::shared_ptr<Element> element) const = 0; + virtual bool canSerialize(boost::shared_ptr<Element> element) const = 0; + }; +} + +#endif diff --git a/Swiften/Serializer/GenericElementSerializer.h b/Swiften/Serializer/GenericElementSerializer.h new file mode 100644 index 0000000..7ccecb2 --- /dev/null +++ b/Swiften/Serializer/GenericElementSerializer.h @@ -0,0 +1,18 @@ +#ifndef SWIFTEN_GenericElementSerializer_H +#define SWIFTEN_GenericElementSerializer_H + +#include "Swiften/Serializer/ElementSerializer.h" + +namespace Swift { + template<typename T> + class GenericElementSerializer : public ElementSerializer { + public: + virtual String serialize(boost::shared_ptr<Element> element) const = 0; + + virtual bool canSerialize(boost::shared_ptr<Element> element) const { + return dynamic_cast<T*>(element.get()) != 0; + } + }; +} + +#endif diff --git a/Swiften/Serializer/GenericPayloadSerializer.h b/Swiften/Serializer/GenericPayloadSerializer.h new file mode 100644 index 0000000..c4a45c3 --- /dev/null +++ b/Swiften/Serializer/GenericPayloadSerializer.h @@ -0,0 +1,25 @@ +#ifndef SWIFTEN_GenericPayloadSerializer_H +#define SWIFTEN_GenericPayloadSerializer_H + +#include <boost/shared_ptr.hpp> + +#include "Swiften/Serializer/PayloadSerializer.h" +#include "Swiften/Elements/Body.h" + +namespace Swift { + template<typename PAYLOAD_TYPE> + class GenericPayloadSerializer : public PayloadSerializer { + public: + virtual String serialize(boost::shared_ptr<Payload> element) const { + return serializePayload(boost::dynamic_pointer_cast<PAYLOAD_TYPE>(element)); + } + + virtual bool canSerialize(boost::shared_ptr<Payload> element) const { + return dynamic_cast<PAYLOAD_TYPE*>(element.get()); + } + + virtual String serializePayload(boost::shared_ptr<PAYLOAD_TYPE>) const = 0; + }; +} + +#endif diff --git a/Swiften/Serializer/GenericStanzaSerializer.h b/Swiften/Serializer/GenericStanzaSerializer.h new file mode 100644 index 0000000..c59f735 --- /dev/null +++ b/Swiften/Serializer/GenericStanzaSerializer.h @@ -0,0 +1,29 @@ +#ifndef SWIFTEN_GENERICSTANZASERIALIZER_H +#define SWIFTEN_GENERICSTANZASERIALIZER_H + +#include "Swiften/Serializer/StanzaSerializer.h" + +namespace Swift { + template<typename STANZA_TYPE> + class GenericStanzaSerializer : public StanzaSerializer { + public: + GenericStanzaSerializer(const String& tag, PayloadSerializerCollection* payloadSerializers) : StanzaSerializer(tag, payloadSerializers) {} + + virtual bool canSerialize(boost::shared_ptr<Element> element) const { + return dynamic_cast<STANZA_TYPE*>(element.get()) != 0; + } + + virtual void setStanzaSpecificAttributes( + boost::shared_ptr<Element> stanza, + XMLElement& element) const { + setStanzaSpecificAttributesGeneric( + boost::dynamic_pointer_cast<STANZA_TYPE>(stanza), element); + } + + virtual void setStanzaSpecificAttributesGeneric( + boost::shared_ptr<STANZA_TYPE>, + XMLElement&) const = 0; + }; +} + +#endif diff --git a/Swiften/Serializer/IQSerializer.h b/Swiften/Serializer/IQSerializer.h new file mode 100644 index 0000000..3623f24 --- /dev/null +++ b/Swiften/Serializer/IQSerializer.h @@ -0,0 +1,30 @@ +#ifndef SWIFTEN_IQSerializer_H +#define SWIFTEN_IQSerializer_H + +#include <cassert> + +#include "Swiften/Serializer/GenericStanzaSerializer.h" +#include "Swiften/Elements/IQ.h" +#include "Swiften/Serializer/XML/XMLElement.h" + +namespace Swift { + class IQSerializer : public GenericStanzaSerializer<IQ> { + public: + IQSerializer(PayloadSerializerCollection* payloadSerializers) : + GenericStanzaSerializer<IQ>("iq", payloadSerializers) {} + + private: + virtual void setStanzaSpecificAttributesGeneric( + boost::shared_ptr<IQ> iq, + XMLElement& element) const { + switch (iq->getType()) { + case IQ::Get: element.setAttribute("type","get"); break; + case IQ::Set: element.setAttribute("type","set"); break; + case IQ::Result: element.setAttribute("type","result"); break; + case IQ::Error: element.setAttribute("type","error"); break; + } + } + }; +} + +#endif diff --git a/Swiften/Serializer/MessageSerializer.cpp b/Swiften/Serializer/MessageSerializer.cpp new file mode 100644 index 0000000..a3cf2ad --- /dev/null +++ b/Swiften/Serializer/MessageSerializer.cpp @@ -0,0 +1,27 @@ +#include "Swiften/Serializer/MessageSerializer.h" +#include "Swiften/Serializer/XML/XMLElement.h" + +namespace Swift { + +MessageSerializer::MessageSerializer(PayloadSerializerCollection* payloadSerializers) : + GenericStanzaSerializer<Message>("message", payloadSerializers) { +} + +void MessageSerializer::setStanzaSpecificAttributesGeneric( + boost::shared_ptr<Message> message, + XMLElement& element) const { + if (message->getType() == Message::Chat) { + element.setAttribute("type", "chat"); + } + else if (message->getType() == Message::Groupchat) { + element.setAttribute("type", "groupchat"); + } + else if (message->getType() == Message::Headline) { + element.setAttribute("type", "headline"); + } + else if (message->getType() == Message::Error) { + element.setAttribute("type", "error"); + } +} + +} diff --git a/Swiften/Serializer/MessageSerializer.h b/Swiften/Serializer/MessageSerializer.h new file mode 100644 index 0000000..8cb32c7 --- /dev/null +++ b/Swiften/Serializer/MessageSerializer.h @@ -0,0 +1,21 @@ +#ifndef SWIFTEN_MessageSerializer_H +#define SWIFTEN_MessageSerializer_H + +#include "Swiften/Serializer/GenericStanzaSerializer.h" +#include "Swiften/Elements/Message.h" + +namespace Swift { + class XMLElement; + + class MessageSerializer : public GenericStanzaSerializer<Message> { + public: + MessageSerializer(PayloadSerializerCollection* payloadSerializers); + + private: + void setStanzaSpecificAttributesGeneric( + boost::shared_ptr<Message> message, + XMLElement& element) const; + }; +} + +#endif diff --git a/Swiften/Serializer/PayloadSerializer.cpp b/Swiften/Serializer/PayloadSerializer.cpp new file mode 100644 index 0000000..47295ac --- /dev/null +++ b/Swiften/Serializer/PayloadSerializer.cpp @@ -0,0 +1,8 @@ +#include "Swiften/Serializer/PayloadSerializer.h" + +namespace Swift { + +PayloadSerializer::~PayloadSerializer() { +} + +} diff --git a/Swiften/Serializer/PayloadSerializer.h b/Swiften/Serializer/PayloadSerializer.h new file mode 100644 index 0000000..935ff4b --- /dev/null +++ b/Swiften/Serializer/PayloadSerializer.h @@ -0,0 +1,19 @@ +#ifndef SWIFTEN_PAYLOADSERIALIZER_H +#define SWIFTEN_PAYLOADSERIALIZER_H + +#include <boost/shared_ptr.hpp> + +#include "Swiften/Base/String.h" +#include "Swiften/Elements/Payload.h" + +namespace Swift { + class PayloadSerializer { + public: + virtual ~PayloadSerializer(); + + virtual bool canSerialize(boost::shared_ptr<Payload>) const = 0; + virtual String serialize(boost::shared_ptr<Payload>) const = 0; + }; +} + +#endif diff --git a/Swiften/Serializer/PayloadSerializerCollection.cpp b/Swiften/Serializer/PayloadSerializerCollection.cpp new file mode 100644 index 0000000..db86eea --- /dev/null +++ b/Swiften/Serializer/PayloadSerializerCollection.cpp @@ -0,0 +1,27 @@ +#include <boost/bind.hpp> +#include <algorithm> + +#include "Swiften/Serializer/PayloadSerializerCollection.h" +#include "Swiften/Serializer/PayloadSerializer.h" + +namespace Swift { + +PayloadSerializerCollection::PayloadSerializerCollection() { +} + +void PayloadSerializerCollection::addSerializer(PayloadSerializer* serializer) { + serializers_.push_back(serializer); +} + +void PayloadSerializerCollection::removeSerializer(PayloadSerializer* serializer) { + serializers_.erase(remove(serializers_.begin(), serializers_.end(), serializer), serializers_.end()); +} + +PayloadSerializer* PayloadSerializerCollection::getPayloadSerializer(boost::shared_ptr<Payload> payload) const { + std::vector<PayloadSerializer*>::const_iterator i = std::find_if( + serializers_.begin(), serializers_.end(), + boost::bind(&PayloadSerializer::canSerialize, _1, payload)); + return (i != serializers_.end() ? *i : NULL); +} + +} diff --git a/Swiften/Serializer/PayloadSerializerCollection.h b/Swiften/Serializer/PayloadSerializerCollection.h new file mode 100644 index 0000000..63ccabe --- /dev/null +++ b/Swiften/Serializer/PayloadSerializerCollection.h @@ -0,0 +1,23 @@ +#pragma once + +#include <vector> +#include <boost/shared_ptr.hpp> + +#include "Swiften/Elements/Payload.h" + +namespace Swift { + class PayloadSerializer; + class String; + + class PayloadSerializerCollection { + public: + PayloadSerializerCollection(); + + void addSerializer(PayloadSerializer* factory); + void removeSerializer(PayloadSerializer* factory); + PayloadSerializer* getPayloadSerializer(boost::shared_ptr<Payload>) const; + + private: + std::vector<PayloadSerializer*> serializers_; + }; +} diff --git a/Swiften/Serializer/PayloadSerializers/BodySerializer.h b/Swiften/Serializer/PayloadSerializers/BodySerializer.h new file mode 100644 index 0000000..7fba561 --- /dev/null +++ b/Swiften/Serializer/PayloadSerializers/BodySerializer.h @@ -0,0 +1,20 @@ +#ifndef SWIFTEN_BodySerializer_H +#define SWIFTEN_BodySerializer_H + +#include "Swiften/Serializer/GenericPayloadSerializer.h" +#include "Swiften/Serializer/XML/XMLTextNode.h" +#include "Swiften/Elements/Body.h" + +namespace Swift { + class BodySerializer : public GenericPayloadSerializer<Body> { + public: + BodySerializer() : GenericPayloadSerializer<Body>() {} + + virtual String serializePayload(boost::shared_ptr<Body> body) const { + XMLTextNode textNode(body->getText()); + return "<body>" + textNode.serialize() + "</body>"; + } + }; +} + +#endif diff --git a/Swiften/Serializer/PayloadSerializers/CapsInfoSerializer.cpp b/Swiften/Serializer/PayloadSerializers/CapsInfoSerializer.cpp new file mode 100644 index 0000000..fbb72af --- /dev/null +++ b/Swiften/Serializer/PayloadSerializers/CapsInfoSerializer.cpp @@ -0,0 +1,20 @@ +#include "Swiften/Serializer/PayloadSerializers/CapsInfoSerializer.h" + +#include <boost/shared_ptr.hpp> + +#include "Swiften/Serializer/XML/XMLElement.h" + +namespace Swift { + +CapsInfoSerializer::CapsInfoSerializer() : GenericPayloadSerializer<CapsInfo>() { +} + +String CapsInfoSerializer::serializePayload(boost::shared_ptr<CapsInfo> capsInfo) const { + XMLElement capsElement("c", "http://jabber.org/protocol/caps"); + capsElement.setAttribute("node", capsInfo->getNode()); + capsElement.setAttribute("hash", capsInfo->getHash()); + capsElement.setAttribute("ver", capsInfo->getVersion()); + return capsElement.serialize(); +} + +} diff --git a/Swiften/Serializer/PayloadSerializers/CapsInfoSerializer.h b/Swiften/Serializer/PayloadSerializers/CapsInfoSerializer.h new file mode 100644 index 0000000..cb4fa33 --- /dev/null +++ b/Swiften/Serializer/PayloadSerializers/CapsInfoSerializer.h @@ -0,0 +1,16 @@ +#ifndef SWIFTEN_CapsInfoSerializer_H +#define SWIFTEN_CapsInfoSerializer_H + +#include "Swiften/Serializer/GenericPayloadSerializer.h" +#include "Swiften/Elements/CapsInfo.h" + +namespace Swift { + class CapsInfoSerializer : public GenericPayloadSerializer<CapsInfo> { + public: + CapsInfoSerializer(); + + virtual String serializePayload(boost::shared_ptr<CapsInfo>) const; + }; +} + +#endif diff --git a/Swiften/Serializer/PayloadSerializers/ChatStateSerializer.cpp b/Swiften/Serializer/PayloadSerializers/ChatStateSerializer.cpp new file mode 100644 index 0000000..0507092 --- /dev/null +++ b/Swiften/Serializer/PayloadSerializers/ChatStateSerializer.cpp @@ -0,0 +1,22 @@ +#include "Swiften/Serializer/PayloadSerializers/ChatStateSerializer.h" + +namespace Swift { + +ChatStateSerializer::ChatStateSerializer() : GenericPayloadSerializer<ChatState>() { +} + +String ChatStateSerializer::serializePayload(boost::shared_ptr<ChatState> chatState) const { + String result("<"); + switch (chatState->getChatState()) { + case ChatState::Active: result += "active"; break; + case ChatState::Composing: result += "composing"; break; + case ChatState::Paused: result += "paused"; break; + case ChatState::Inactive: result += "inactive"; break; + case ChatState::Gone: result += "gone"; break; + default: result += "gone"; break; + } + result += " xmlns=\"http://jabber.org/protocol/chatstates\"/>"; + return result; +} + +} diff --git a/Swiften/Serializer/PayloadSerializers/ChatStateSerializer.h b/Swiften/Serializer/PayloadSerializers/ChatStateSerializer.h new file mode 100644 index 0000000..e99b8b6 --- /dev/null +++ b/Swiften/Serializer/PayloadSerializers/ChatStateSerializer.h @@ -0,0 +1,13 @@ +#pragma once + +#include "Swiften/Serializer/GenericPayloadSerializer.h" +#include "Swiften/Elements/ChatState.h" + +namespace Swift { + class ChatStateSerializer : public GenericPayloadSerializer<ChatState> { + public: + ChatStateSerializer(); + + virtual String serializePayload(boost::shared_ptr<ChatState> error) const; + }; +} diff --git a/Swiften/Serializer/PayloadSerializers/DiscoInfoSerializer.cpp b/Swiften/Serializer/PayloadSerializers/DiscoInfoSerializer.cpp new file mode 100644 index 0000000..4c39574 --- /dev/null +++ b/Swiften/Serializer/PayloadSerializers/DiscoInfoSerializer.cpp @@ -0,0 +1,36 @@ +#include "Swiften/Serializer/PayloadSerializers/DiscoInfoSerializer.h" + +#include <boost/shared_ptr.hpp> + +#include "Swiften/Base/foreach.h" +#include "Swiften/Serializer/XML/XMLElement.h" + +namespace Swift { + +DiscoInfoSerializer::DiscoInfoSerializer() : GenericPayloadSerializer<DiscoInfo>() { +} + +String DiscoInfoSerializer::serializePayload(boost::shared_ptr<DiscoInfo> discoInfo) const { + XMLElement queryElement("query", "http://jabber.org/protocol/disco#info"); + if (!discoInfo->getNode().isEmpty()) { + queryElement.setAttribute("node", discoInfo->getNode()); + } + foreach(const DiscoInfo::Identity& identity, discoInfo->getIdentities()) { + boost::shared_ptr<XMLElement> identityElement(new XMLElement("identity")); + if (!identity.getLanguage().isEmpty()) { + identityElement->setAttribute("xml:lang", identity.getLanguage()); + } + identityElement->setAttribute("category", identity.getCategory()); + identityElement->setAttribute("name", identity.getName()); + identityElement->setAttribute("type", identity.getType()); + queryElement.addNode(identityElement); + } + foreach(const String& feature, discoInfo->getFeatures()) { + boost::shared_ptr<XMLElement> featureElement(new XMLElement("feature")); + featureElement->setAttribute("var", feature); + queryElement.addNode(featureElement); + } + return queryElement.serialize(); +} + +} diff --git a/Swiften/Serializer/PayloadSerializers/DiscoInfoSerializer.h b/Swiften/Serializer/PayloadSerializers/DiscoInfoSerializer.h new file mode 100644 index 0000000..929a455 --- /dev/null +++ b/Swiften/Serializer/PayloadSerializers/DiscoInfoSerializer.h @@ -0,0 +1,16 @@ +#ifndef SWIFTEN_DiscoInfoSerializer_H +#define SWIFTEN_DiscoInfoSerializer_H + +#include "Swiften/Serializer/GenericPayloadSerializer.h" +#include "Swiften/Elements/DiscoInfo.h" + +namespace Swift { + class DiscoInfoSerializer : public GenericPayloadSerializer<DiscoInfo> { + public: + DiscoInfoSerializer(); + + virtual String serializePayload(boost::shared_ptr<DiscoInfo>) const; + }; +} + +#endif diff --git a/Swiften/Serializer/PayloadSerializers/ErrorSerializer.cpp b/Swiften/Serializer/PayloadSerializers/ErrorSerializer.cpp new file mode 100644 index 0000000..f5ce478 --- /dev/null +++ b/Swiften/Serializer/PayloadSerializers/ErrorSerializer.cpp @@ -0,0 +1,56 @@ +#include "Swiften/Serializer/PayloadSerializers/ErrorSerializer.h" +#include "Swiften/Serializer/XML/XMLTextNode.h" + +namespace Swift { + +ErrorSerializer::ErrorSerializer() : GenericPayloadSerializer<ErrorPayload>() { +} + +String ErrorSerializer::serializePayload(boost::shared_ptr<ErrorPayload> error) const { + String result("<error type=\""); + switch (error->getType()) { + case ErrorPayload::Continue: result += "continue"; break; + case ErrorPayload::Modify: result += "modify"; break; + case ErrorPayload::Auth: result += "auth"; break; + case ErrorPayload::Wait: result += "wait"; break; + default: result += "cancel"; break; + } + result += "\">"; + + String conditionElement; + switch (error->getCondition()) { + case ErrorPayload::BadRequest: conditionElement = "bad-request"; break; + case ErrorPayload::Conflict: conditionElement = "conflict"; break; + case ErrorPayload::FeatureNotImplemented: conditionElement = "feature-not-implemented"; break; + case ErrorPayload::Forbidden: conditionElement = "forbidden"; break; + case ErrorPayload::Gone: conditionElement = "gone"; break; + case ErrorPayload::InternalServerError: conditionElement = "internal-server-error"; break; + case ErrorPayload::ItemNotFound: conditionElement = "item-not-found"; break; + case ErrorPayload::JIDMalformed: conditionElement = "jid-malformed"; break; + case ErrorPayload::NotAcceptable: conditionElement = "not-acceptable"; break; + case ErrorPayload::NotAllowed: conditionElement = "not-allowed"; break; + case ErrorPayload::NotAuthorized: conditionElement = "not-authorized"; break; + case ErrorPayload::PaymentRequired: conditionElement = "payment-required"; break; + case ErrorPayload::RecipientUnavailable: conditionElement = "recipient-unavailable"; break; + case ErrorPayload::Redirect: conditionElement = "redirect"; break; + case ErrorPayload::RegistrationRequired: conditionElement = "registration-required"; break; + case ErrorPayload::RemoteServerNotFound: conditionElement = "remote-server-not-found"; break; + case ErrorPayload::RemoteServerTimeout: conditionElement = "remote-server-timeout"; break; + case ErrorPayload::ResourceConstraint: conditionElement = "resource-constraint"; break; + case ErrorPayload::ServiceUnavailable: conditionElement = "service-unavailable"; break; + case ErrorPayload::SubscriptionRequired: conditionElement = "subscription-required"; break; + case ErrorPayload::UnexpectedRequest: conditionElement = "unexpected-request"; break; + default: conditionElement = "undefined-condition"; break; + } + result += "<" + conditionElement + " xmlns=\"urn:ietf:params:xml:ns:xmpp-stanzas\"/>"; + + if (!error->getText().isEmpty()) { + XMLTextNode textNode(error->getText()); + result += "<text xmlns=\"urn:ietf:params:xml:ns:xmpp-stanzas\">" + textNode.serialize() + "</text>"; + } + + result += "</error>"; + return result; +} + +} diff --git a/Swiften/Serializer/PayloadSerializers/ErrorSerializer.h b/Swiften/Serializer/PayloadSerializers/ErrorSerializer.h new file mode 100644 index 0000000..931596f --- /dev/null +++ b/Swiften/Serializer/PayloadSerializers/ErrorSerializer.h @@ -0,0 +1,16 @@ +#ifndef SWIFTEN_ErrorSerializer_H +#define SWIFTEN_ErrorSerializer_H + +#include "Swiften/Serializer/GenericPayloadSerializer.h" +#include "Swiften/Elements/ErrorPayload.h" + +namespace Swift { + class ErrorSerializer : public GenericPayloadSerializer<ErrorPayload> { + public: + ErrorSerializer(); + + virtual String serializePayload(boost::shared_ptr<ErrorPayload> error) const; + }; +} + +#endif diff --git a/Swiften/Serializer/PayloadSerializers/FullPayloadSerializerCollection.cpp b/Swiften/Serializer/PayloadSerializers/FullPayloadSerializerCollection.cpp new file mode 100644 index 0000000..accf6c6 --- /dev/null +++ b/Swiften/Serializer/PayloadSerializers/FullPayloadSerializerCollection.cpp @@ -0,0 +1,61 @@ +#include "Swiften/Serializer/PayloadSerializers/FullPayloadSerializerCollection.h" +#include "Swiften/Base/foreach.h" +#include "Swiften/Serializer/PayloadSerializer.h" +#include "Swiften/Serializer/PayloadSerializers/BodySerializer.h" +#include "Swiften/Serializer/PayloadSerializers/ChatStateSerializer.h" +#include "Swiften/Serializer/PayloadSerializers/PrioritySerializer.h" +#include "Swiften/Serializer/PayloadSerializers/ErrorSerializer.h" +#include "Swiften/Serializer/PayloadSerializers/RosterSerializer.h" +#include "Swiften/Serializer/PayloadSerializers/MUCPayloadSerializer.h" +#include "Swiften/Serializer/PayloadSerializers/SoftwareVersionSerializer.h" +#include "Swiften/Serializer/PayloadSerializers/StatusSerializer.h" +#include "Swiften/Serializer/PayloadSerializers/StatusShowSerializer.h" +#include "Swiften/Serializer/PayloadSerializers/DiscoInfoSerializer.h" +#include "Swiften/Serializer/PayloadSerializers/CapsInfoSerializer.h" +#include "Swiften/Serializer/PayloadSerializers/ResourceBindSerializer.h" +#include "Swiften/Serializer/PayloadSerializers/StartSessionSerializer.h" +#include "Swiften/Serializer/PayloadSerializers/SecurityLabelSerializer.h" +#include "Swiften/Serializer/PayloadSerializers/SecurityLabelsCatalogSerializer.h" +#include "Swiften/Serializer/PayloadSerializers/VCardSerializer.h" +#include "Swiften/Serializer/PayloadSerializers/VCardUpdateSerializer.h" +#include "Swiften/Serializer/PayloadSerializers/RawXMLPayloadSerializer.h" +#include "Swiften/Serializer/PayloadSerializers/StorageSerializer.h" +#include "Swiften/Serializer/PayloadSerializers/PrivateStorageSerializer.h" + +namespace Swift { + +FullPayloadSerializerCollection::FullPayloadSerializerCollection() { + serializers_.push_back(new BodySerializer()); + serializers_.push_back(new ChatStateSerializer()); + serializers_.push_back(new PrioritySerializer()); + serializers_.push_back(new ErrorSerializer()); + serializers_.push_back(new RosterSerializer()); + serializers_.push_back(new MUCPayloadSerializer()); + serializers_.push_back(new SoftwareVersionSerializer()); + serializers_.push_back(new StatusSerializer()); + serializers_.push_back(new StatusShowSerializer()); + serializers_.push_back(new DiscoInfoSerializer()); + serializers_.push_back(new CapsInfoSerializer()); + serializers_.push_back(new ResourceBindSerializer()); + serializers_.push_back(new StartSessionSerializer()); + serializers_.push_back(new SecurityLabelSerializer()); + serializers_.push_back(new SecurityLabelsCatalogSerializer()); + serializers_.push_back(new VCardSerializer()); + serializers_.push_back(new VCardUpdateSerializer()); + serializers_.push_back(new RawXMLPayloadSerializer()); + serializers_.push_back(new StorageSerializer()); + serializers_.push_back(new PrivateStorageSerializer(this)); + foreach(PayloadSerializer* serializer, serializers_) { + addSerializer(serializer); + } +} + +FullPayloadSerializerCollection::~FullPayloadSerializerCollection() { + foreach(PayloadSerializer* serializer, serializers_) { + removeSerializer(serializer); + delete serializer; + } + serializers_.clear(); +} + +} diff --git a/Swiften/Serializer/PayloadSerializers/FullPayloadSerializerCollection.h b/Swiften/Serializer/PayloadSerializers/FullPayloadSerializerCollection.h new file mode 100644 index 0000000..e1655b5 --- /dev/null +++ b/Swiften/Serializer/PayloadSerializers/FullPayloadSerializerCollection.h @@ -0,0 +1,19 @@ +#ifndef SWIFTEN_FULLPAYLOADSERIALIZERCOLLECTION_H +#define SWIFTEN_FULLPAYLOADSERIALIZERCOLLECTION_H + +#include <vector> + +#include "Swiften/Serializer/PayloadSerializerCollection.h" + +namespace Swift { + class FullPayloadSerializerCollection : public PayloadSerializerCollection { + public: + FullPayloadSerializerCollection(); + ~FullPayloadSerializerCollection(); + + private: + std::vector<PayloadSerializer*> serializers_; + }; +} + +#endif diff --git a/Swiften/Serializer/PayloadSerializers/MUCPayloadSerializer.cpp b/Swiften/Serializer/PayloadSerializers/MUCPayloadSerializer.cpp new file mode 100644 index 0000000..9c701cc --- /dev/null +++ b/Swiften/Serializer/PayloadSerializers/MUCPayloadSerializer.cpp @@ -0,0 +1,13 @@ +#include "Swiften/Serializer/PayloadSerializers/MUCPayloadSerializer.h" + +namespace Swift { + +MUCPayloadSerializer::MUCPayloadSerializer() : GenericPayloadSerializer<MUCPayload>() { +} + +String MUCPayloadSerializer::serializePayload(boost::shared_ptr<MUCPayload>) const { + String result("<x xmlns='http://jabber.org/protocol/muc'/>"); + return result; +} + +} diff --git a/Swiften/Serializer/PayloadSerializers/MUCPayloadSerializer.h b/Swiften/Serializer/PayloadSerializers/MUCPayloadSerializer.h new file mode 100644 index 0000000..504c969 --- /dev/null +++ b/Swiften/Serializer/PayloadSerializers/MUCPayloadSerializer.h @@ -0,0 +1,16 @@ +#ifndef SWIFTEN_MUCPayloadSerializer_H +#define SWIFTEN_MUCPayloadSerializer_H + +#include "Swiften/Serializer/GenericPayloadSerializer.h" +#include "Swiften/Elements/MUCPayload.h" + +namespace Swift { + class MUCPayloadSerializer : public GenericPayloadSerializer<MUCPayload> { + public: + MUCPayloadSerializer(); + + virtual String serializePayload(boost::shared_ptr<MUCPayload> version) const; + }; +} + +#endif diff --git a/Swiften/Serializer/PayloadSerializers/PrioritySerializer.h b/Swiften/Serializer/PayloadSerializers/PrioritySerializer.h new file mode 100644 index 0000000..f132b6e --- /dev/null +++ b/Swiften/Serializer/PayloadSerializers/PrioritySerializer.h @@ -0,0 +1,20 @@ +#ifndef SWIFTEN_PrioritySerializer_H +#define SWIFTEN_PrioritySerializer_H + +#include <boost/lexical_cast.hpp> + +#include "Swiften/Serializer/GenericPayloadSerializer.h" +#include "Swiften/Elements/Priority.h" + +namespace Swift { + class PrioritySerializer : public GenericPayloadSerializer<Priority> { + public: + PrioritySerializer() : GenericPayloadSerializer<Priority>() {} + + virtual String serializePayload(boost::shared_ptr<Priority> priority) const { + return "<priority>" + boost::lexical_cast<std::string>(priority->getPriority()) + "</priority>"; + } + }; +} + +#endif diff --git a/Swiften/Serializer/PayloadSerializers/PrivateStorageSerializer.cpp b/Swiften/Serializer/PayloadSerializers/PrivateStorageSerializer.cpp new file mode 100644 index 0000000..999252c --- /dev/null +++ b/Swiften/Serializer/PayloadSerializers/PrivateStorageSerializer.cpp @@ -0,0 +1,28 @@ +#include "Swiften/Serializer/PayloadSerializers/PrivateStorageSerializer.h" + +#include <boost/shared_ptr.hpp> + +#include "Swiften/Base/foreach.h" +#include "Swiften/Serializer/XML/XMLElement.h" +#include "Swiften/Serializer/XML/XMLTextNode.h" +#include "Swiften/Serializer/XML/XMLRawTextNode.h" +#include "Swiften/Serializer/PayloadSerializerCollection.h" + +namespace Swift { + +PrivateStorageSerializer::PrivateStorageSerializer(PayloadSerializerCollection* serializers) : serializers(serializers) { +} + +String PrivateStorageSerializer::serializePayload(boost::shared_ptr<PrivateStorage> storage) const { + XMLElement storageElement("query", "jabber:iq:private"); + boost::shared_ptr<Payload> payload = storage->getPayload(); + if (payload) { + PayloadSerializer* serializer = serializers->getPayloadSerializer(payload); + if (serializer) { + storageElement.addNode(boost::shared_ptr<XMLRawTextNode>(new XMLRawTextNode(serializer->serialize(payload)))); + } + } + return storageElement.serialize(); +} + +} diff --git a/Swiften/Serializer/PayloadSerializers/PrivateStorageSerializer.h b/Swiften/Serializer/PayloadSerializers/PrivateStorageSerializer.h new file mode 100644 index 0000000..c655634 --- /dev/null +++ b/Swiften/Serializer/PayloadSerializers/PrivateStorageSerializer.h @@ -0,0 +1,18 @@ +#pragma once + +#include "Swiften/Serializer/GenericPayloadSerializer.h" +#include "Swiften/Elements/PrivateStorage.h" + +namespace Swift { + class PayloadSerializerCollection; + + class PrivateStorageSerializer : public GenericPayloadSerializer<PrivateStorage> { + public: + PrivateStorageSerializer(PayloadSerializerCollection* serializers); + + virtual String serializePayload(boost::shared_ptr<PrivateStorage>) const; + + private: + PayloadSerializerCollection* serializers; + }; +} diff --git a/Swiften/Serializer/PayloadSerializers/RawXMLPayloadSerializer.h b/Swiften/Serializer/PayloadSerializers/RawXMLPayloadSerializer.h new file mode 100644 index 0000000..f980174 --- /dev/null +++ b/Swiften/Serializer/PayloadSerializers/RawXMLPayloadSerializer.h @@ -0,0 +1,15 @@ +#pragma once + +#include "Swiften/Serializer/GenericPayloadSerializer.h" +#include "Swiften/Elements/RawXMLPayload.h" + +namespace Swift { + class RawXMLPayloadSerializer : public GenericPayloadSerializer<RawXMLPayload> { + public: + RawXMLPayloadSerializer() : GenericPayloadSerializer<RawXMLPayload>() {} + + virtual String serializePayload(boost::shared_ptr<RawXMLPayload> p) const { + return p->getRawXML(); + } + }; +} diff --git a/Swiften/Serializer/PayloadSerializers/ResourceBindSerializer.cpp b/Swiften/Serializer/PayloadSerializers/ResourceBindSerializer.cpp new file mode 100644 index 0000000..93ab136 --- /dev/null +++ b/Swiften/Serializer/PayloadSerializers/ResourceBindSerializer.cpp @@ -0,0 +1,28 @@ +#include "Swiften/Serializer/PayloadSerializers/ResourceBindSerializer.h" + +#include <boost/shared_ptr.hpp> + +#include "Swiften/Serializer/XML/XMLElement.h" +#include "Swiften/Serializer/XML/XMLTextNode.h" + +namespace Swift { + +ResourceBindSerializer::ResourceBindSerializer() : GenericPayloadSerializer<ResourceBind>() { +} + +String ResourceBindSerializer::serializePayload(boost::shared_ptr<ResourceBind> resourceBind) const { + XMLElement bindElement("bind", "urn:ietf:params:xml:ns:xmpp-bind"); + if (resourceBind->getJID().isValid()) { + boost::shared_ptr<XMLElement> jidNode(new XMLElement("jid")); + jidNode->addNode(boost::shared_ptr<XMLTextNode>(new XMLTextNode(resourceBind->getJID().toString()))); + bindElement.addNode(jidNode); + } + else if (!resourceBind->getResource().isEmpty()) { + boost::shared_ptr<XMLElement> resourceNode(new XMLElement("resource")); + resourceNode->addNode(boost::shared_ptr<XMLTextNode>(new XMLTextNode(resourceBind->getResource()))); + bindElement.addNode(resourceNode); + } + return bindElement.serialize(); +} + +} diff --git a/Swiften/Serializer/PayloadSerializers/ResourceBindSerializer.h b/Swiften/Serializer/PayloadSerializers/ResourceBindSerializer.h new file mode 100644 index 0000000..61cf539 --- /dev/null +++ b/Swiften/Serializer/PayloadSerializers/ResourceBindSerializer.h @@ -0,0 +1,16 @@ +#ifndef SWIFTEN_ResourceBindSerializer_H +#define SWIFTEN_ResourceBindSerializer_H + +#include "Swiften/Serializer/GenericPayloadSerializer.h" +#include "Swiften/Elements/ResourceBind.h" + +namespace Swift { + class ResourceBindSerializer : public GenericPayloadSerializer<ResourceBind> { + public: + ResourceBindSerializer(); + + virtual String serializePayload(boost::shared_ptr<ResourceBind>) const; + }; +} + +#endif diff --git a/Swiften/Serializer/PayloadSerializers/RosterSerializer.cpp b/Swiften/Serializer/PayloadSerializers/RosterSerializer.cpp new file mode 100644 index 0000000..6329f86 --- /dev/null +++ b/Swiften/Serializer/PayloadSerializers/RosterSerializer.cpp @@ -0,0 +1,45 @@ +#include "Swiften/Serializer/PayloadSerializers/RosterSerializer.h" + +#include <boost/shared_ptr.hpp> + +#include "Swiften/Base/foreach.h" +#include "Swiften/Serializer/XML/XMLTextNode.h" +#include "Swiften/Serializer/XML/XMLElement.h" + +namespace Swift { + +RosterSerializer::RosterSerializer() : GenericPayloadSerializer<RosterPayload>() { +} + +String RosterSerializer::serializePayload(boost::shared_ptr<RosterPayload> roster) const { + XMLElement queryElement("query", "jabber:iq:roster"); + foreach(const RosterItemPayload& item, roster->getItems()) { + boost::shared_ptr<XMLElement> itemElement(new XMLElement("item")); + itemElement->setAttribute("jid", item.getJID()); + itemElement->setAttribute("name", item.getName()); + + switch (item.getSubscription()) { + case RosterItemPayload::To: itemElement->setAttribute("subscription", "to"); break; + case RosterItemPayload::From: itemElement->setAttribute("subscription", "from"); break; + case RosterItemPayload::Both: itemElement->setAttribute("subscription", "both"); break; + case RosterItemPayload::Remove: itemElement->setAttribute("subscription", "remove"); break; + case RosterItemPayload::None: itemElement->setAttribute("subscription", "none"); break; + } + + if (item.getSubscriptionRequested()) { + itemElement->setAttribute("ask", "subscribe"); + } + + foreach(const String& group, item.getGroups()) { + boost::shared_ptr<XMLElement> groupElement(new XMLElement("group")); + groupElement->addNode(boost::shared_ptr<XMLTextNode>(new XMLTextNode(group))); + itemElement->addNode(groupElement); + } + + queryElement.addNode(itemElement); + } + + return queryElement.serialize(); +} + +} diff --git a/Swiften/Serializer/PayloadSerializers/RosterSerializer.h b/Swiften/Serializer/PayloadSerializers/RosterSerializer.h new file mode 100644 index 0000000..fb6a713 --- /dev/null +++ b/Swiften/Serializer/PayloadSerializers/RosterSerializer.h @@ -0,0 +1,16 @@ +#ifndef SWIFTEN_RosterSerializer_H +#define SWIFTEN_RosterSerializer_H + +#include "Swiften/Serializer/GenericPayloadSerializer.h" +#include "Swiften/Elements/RosterPayload.h" + +namespace Swift { + class RosterSerializer : public GenericPayloadSerializer<RosterPayload> { + public: + RosterSerializer(); + + virtual String serializePayload(boost::shared_ptr<RosterPayload>) const; + }; +} + +#endif diff --git a/Swiften/Serializer/PayloadSerializers/SecurityLabelSerializer.cpp b/Swiften/Serializer/PayloadSerializers/SecurityLabelSerializer.cpp new file mode 100644 index 0000000..dcce4dc --- /dev/null +++ b/Swiften/Serializer/PayloadSerializers/SecurityLabelSerializer.cpp @@ -0,0 +1,38 @@ +#include "Swiften/Serializer/PayloadSerializers/SecurityLabelSerializer.h" +#include "Swiften/Base/foreach.h" +#include "Swiften/Serializer/XML/XMLRawTextNode.h" +#include "Swiften/Serializer/XML/XMLTextNode.h" +#include "Swiften/Serializer/XML/XMLElement.h" + +namespace Swift { + +SecurityLabelSerializer::SecurityLabelSerializer() : GenericPayloadSerializer<SecurityLabel>() { +} + +String SecurityLabelSerializer::serializePayload(boost::shared_ptr<SecurityLabel> label) const { + XMLElement element("securitylabel", "urn:xmpp:sec-label:0"); + if (!label->getDisplayMarking().isEmpty()) { + boost::shared_ptr<XMLElement> displayMarking(new XMLElement("displaymarking")); + if (!label->getForegroundColor().isEmpty()) { + displayMarking->setAttribute("fgcolor", label->getForegroundColor()); + } + if (!label->getBackgroundColor().isEmpty()) { + displayMarking->setAttribute("bgcolor", label->getBackgroundColor()); + } + displayMarking->addNode(boost::shared_ptr<XMLTextNode>(new XMLTextNode(label->getDisplayMarking()))); + element.addNode(displayMarking); + } + + boost::shared_ptr<XMLElement> labelElement(new XMLElement("label")); + labelElement->addNode(boost::shared_ptr<XMLRawTextNode>(new XMLRawTextNode(label->getLabel()))); + element.addNode(labelElement); + + foreach(const String& equivalentLabel, label->getEquivalentLabels()) { + boost::shared_ptr<XMLElement> equivalentLabelElement(new XMLElement("equivalentlabel")); + equivalentLabelElement->addNode(boost::shared_ptr<XMLRawTextNode>(new XMLRawTextNode(equivalentLabel))); + element.addNode(equivalentLabelElement); + } + return element.serialize(); +} + +} diff --git a/Swiften/Serializer/PayloadSerializers/SecurityLabelSerializer.h b/Swiften/Serializer/PayloadSerializers/SecurityLabelSerializer.h new file mode 100644 index 0000000..b0b0804 --- /dev/null +++ b/Swiften/Serializer/PayloadSerializers/SecurityLabelSerializer.h @@ -0,0 +1,16 @@ +#ifndef SWIFTEN_SecurityLabelSerializer_H +#define SWIFTEN_SecurityLabelSerializer_H + +#include "Swiften/Serializer/GenericPayloadSerializer.h" +#include "Swiften/Elements/SecurityLabel.h" + +namespace Swift { + class SecurityLabelSerializer : public GenericPayloadSerializer<SecurityLabel> { + public: + SecurityLabelSerializer(); + + virtual String serializePayload(boost::shared_ptr<SecurityLabel> version) const; + }; +} + +#endif diff --git a/Swiften/Serializer/PayloadSerializers/SecurityLabelsCatalogSerializer.cpp b/Swiften/Serializer/PayloadSerializers/SecurityLabelsCatalogSerializer.cpp new file mode 100644 index 0000000..bc6a41f --- /dev/null +++ b/Swiften/Serializer/PayloadSerializers/SecurityLabelsCatalogSerializer.cpp @@ -0,0 +1,30 @@ +#include "Swiften/Serializer/PayloadSerializers/SecurityLabelsCatalogSerializer.h" +#include "Swiften/Base/foreach.h" +#include "Swiften/Serializer/XML/XMLElement.h" +#include "Swiften/Serializer/XML/XMLRawTextNode.h" +#include "Swiften/Serializer/PayloadSerializers/SecurityLabelSerializer.h" + +namespace Swift { + +SecurityLabelsCatalogSerializer::SecurityLabelsCatalogSerializer() : GenericPayloadSerializer<SecurityLabelsCatalog>() { +} + +String SecurityLabelsCatalogSerializer::serializePayload(boost::shared_ptr<SecurityLabelsCatalog> catalog) const { + XMLElement element("catalog", "urn:xmpp:sec-label:catalog:0"); + if (!catalog->getName().isEmpty()) { + element.setAttribute("name", catalog->getName()); + } + if (catalog->getTo().isValid()) { + element.setAttribute("to", catalog->getTo()); + } + if (!catalog->getDescription().isEmpty()) { + element.setAttribute("desc", catalog->getDescription()); + } + foreach (const SecurityLabel& label, catalog->getLabels()) { + String serializedLabel = SecurityLabelSerializer().serialize(boost::shared_ptr<SecurityLabel>(new SecurityLabel(label))); + element.addNode(boost::shared_ptr<XMLRawTextNode>(new XMLRawTextNode(serializedLabel))); + } + return element.serialize(); +} + +} diff --git a/Swiften/Serializer/PayloadSerializers/SecurityLabelsCatalogSerializer.h b/Swiften/Serializer/PayloadSerializers/SecurityLabelsCatalogSerializer.h new file mode 100644 index 0000000..d086c79 --- /dev/null +++ b/Swiften/Serializer/PayloadSerializers/SecurityLabelsCatalogSerializer.h @@ -0,0 +1,16 @@ +#ifndef SWIFTEN_SecurityLabelsCatalogSerializer_H +#define SWIFTEN_SecurityLabelsCatalogSerializer_H + +#include "Swiften/Serializer/GenericPayloadSerializer.h" +#include "Swiften/Elements/SecurityLabelsCatalog.h" + +namespace Swift { + class SecurityLabelsCatalogSerializer : public GenericPayloadSerializer<SecurityLabelsCatalog> { + public: + SecurityLabelsCatalogSerializer(); + + virtual String serializePayload(boost::shared_ptr<SecurityLabelsCatalog> version) const; + }; +} + +#endif diff --git a/Swiften/Serializer/PayloadSerializers/SoftwareVersionSerializer.cpp b/Swiften/Serializer/PayloadSerializers/SoftwareVersionSerializer.cpp new file mode 100644 index 0000000..71ad9a2 --- /dev/null +++ b/Swiften/Serializer/PayloadSerializers/SoftwareVersionSerializer.cpp @@ -0,0 +1,23 @@ +#include "Swiften/Serializer/PayloadSerializers/SoftwareVersionSerializer.h" + +namespace Swift { + +SoftwareVersionSerializer::SoftwareVersionSerializer() : GenericPayloadSerializer<SoftwareVersion>() { +} + +String SoftwareVersionSerializer::serializePayload(boost::shared_ptr<SoftwareVersion> version) const { + String result("<query xmlns=\"jabber:iq:version\">"); + if (!version->getName().isEmpty()) { + result += "<name>" + version->getName() + "</name>"; + } + if (!version->getVersion().isEmpty()) { + result += "<version>" + version->getVersion() + "</version>"; + } + if (!version->getOS().isEmpty()) { + result += "<os>" + version->getOS() + "</os>"; + } + result += "</query>"; + return result; +} + +} diff --git a/Swiften/Serializer/PayloadSerializers/SoftwareVersionSerializer.h b/Swiften/Serializer/PayloadSerializers/SoftwareVersionSerializer.h new file mode 100644 index 0000000..50242f2 --- /dev/null +++ b/Swiften/Serializer/PayloadSerializers/SoftwareVersionSerializer.h @@ -0,0 +1,16 @@ +#ifndef SWIFTEN_SoftwareVersionSerializer_H +#define SWIFTEN_SoftwareVersionSerializer_H + +#include "Swiften/Serializer/GenericPayloadSerializer.h" +#include "Swiften/Elements/SoftwareVersion.h" + +namespace Swift { + class SoftwareVersionSerializer : public GenericPayloadSerializer<SoftwareVersion> { + public: + SoftwareVersionSerializer(); + + virtual String serializePayload(boost::shared_ptr<SoftwareVersion> version) const; + }; +} + +#endif diff --git a/Swiften/Serializer/PayloadSerializers/StartSessionSerializer.h b/Swiften/Serializer/PayloadSerializers/StartSessionSerializer.h new file mode 100644 index 0000000..df35054 --- /dev/null +++ b/Swiften/Serializer/PayloadSerializers/StartSessionSerializer.h @@ -0,0 +1,20 @@ +#ifndef SWIFTEN_StartSessionSerializer_H +#define SWIFTEN_StartSessionSerializer_H + +#include <boost/lexical_cast.hpp> + +#include "Swiften/Serializer/GenericPayloadSerializer.h" +#include "Swiften/Elements/StartSession.h" + +namespace Swift { + class StartSessionSerializer : public GenericPayloadSerializer<StartSession> { + public: + StartSessionSerializer() : GenericPayloadSerializer<StartSession>() {} + + virtual String serializePayload(boost::shared_ptr<StartSession>) const { + return XMLElement("session", "urn:ietf:params:xml:ns:xmpp-session").serialize(); + } + }; +} + +#endif diff --git a/Swiften/Serializer/PayloadSerializers/StatusSerializer.h b/Swiften/Serializer/PayloadSerializers/StatusSerializer.h new file mode 100644 index 0000000..dc5f6d1 --- /dev/null +++ b/Swiften/Serializer/PayloadSerializers/StatusSerializer.h @@ -0,0 +1,22 @@ +#ifndef SWIFTEN_StatusSerializer_H +#define SWIFTEN_StatusSerializer_H + +#include "Swiften/Serializer/GenericPayloadSerializer.h" +#include "Swiften/Serializer/XML/XMLElement.h" +#include "Swiften/Serializer/XML/XMLTextNode.h" +#include "Swiften/Elements/Status.h" + +namespace Swift { + class StatusSerializer : public GenericPayloadSerializer<Status> { + public: + StatusSerializer() : GenericPayloadSerializer<Status>() {} + + virtual String serializePayload(boost::shared_ptr<Status> status) const { + XMLElement element("status"); + element.addNode(boost::shared_ptr<XMLTextNode>(new XMLTextNode(status->getText()))); + return element.serialize(); + } + }; +} + +#endif diff --git a/Swiften/Serializer/PayloadSerializers/StatusShowSerializer.h b/Swiften/Serializer/PayloadSerializers/StatusShowSerializer.h new file mode 100644 index 0000000..f66c09c --- /dev/null +++ b/Swiften/Serializer/PayloadSerializers/StatusShowSerializer.h @@ -0,0 +1,33 @@ +#ifndef SWIFTEN_StatusShowSerializer_H +#define SWIFTEN_StatusShowSerializer_H + +#include "Swiften/Serializer/GenericPayloadSerializer.h" +#include "Swiften/Elements/StatusShow.h" + +namespace Swift { + class StatusShowSerializer : public GenericPayloadSerializer<StatusShow> { + public: + StatusShowSerializer() : GenericPayloadSerializer<StatusShow>() {} + + virtual String serializePayload(boost::shared_ptr<StatusShow> statusShow) const { + if (statusShow->getType () == StatusShow::Online || statusShow->getType() == StatusShow::None) { + return ""; + } + else { + String result("<show>"); + switch (statusShow->getType()) { + case StatusShow::Away: result += "away"; break; + case StatusShow::XA: result += "xa"; break; + case StatusShow::FFC: result += "chat"; break; + case StatusShow::DND: result += "dnd"; break; + case StatusShow::Online: assert(false); break; + case StatusShow::None: assert(false); break; + } + result += "</show>"; + return result; + } + } + }; +} + +#endif diff --git a/Swiften/Serializer/PayloadSerializers/StorageSerializer.cpp b/Swiften/Serializer/PayloadSerializers/StorageSerializer.cpp new file mode 100644 index 0000000..4268381 --- /dev/null +++ b/Swiften/Serializer/PayloadSerializers/StorageSerializer.cpp @@ -0,0 +1,36 @@ +#include "Swiften/Serializer/PayloadSerializers/StorageSerializer.h" + +#include <boost/shared_ptr.hpp> + +#include "Swiften/Base/foreach.h" +#include "Swiften/Serializer/XML/XMLElement.h" +#include "Swiften/Serializer/XML/XMLTextNode.h" + +namespace Swift { + +StorageSerializer::StorageSerializer() : GenericPayloadSerializer<Storage>() { +} + +String StorageSerializer::serializePayload(boost::shared_ptr<Storage> storage) const { + XMLElement storageElement("storage", "storage:bookmarks"); + foreach(const Storage::Conference& conference, storage->getConferences()) { + boost::shared_ptr<XMLElement> conferenceElement(new XMLElement("conference")); + conferenceElement->setAttribute("name", conference.name); + conferenceElement->setAttribute("jid", conference.jid); + conferenceElement->setAttribute("autojoin", conference.autoJoin ? "1" : "0"); + if (!conference.nick.isEmpty()) { + boost::shared_ptr<XMLElement> nickElement(new XMLElement("nick")); + nickElement->addNode(boost::shared_ptr<XMLTextNode>(new XMLTextNode(conference.nick))); + conferenceElement->addNode(nickElement); + } + if (!conference.password.isEmpty()) { + boost::shared_ptr<XMLElement> passwordElement(new XMLElement("password")); + passwordElement->addNode(boost::shared_ptr<XMLTextNode>(new XMLTextNode(conference.password))); + conferenceElement->addNode(passwordElement); + } + storageElement.addNode(conferenceElement); + } + return storageElement.serialize(); +} + +} diff --git a/Swiften/Serializer/PayloadSerializers/StorageSerializer.h b/Swiften/Serializer/PayloadSerializers/StorageSerializer.h new file mode 100644 index 0000000..e657376 --- /dev/null +++ b/Swiften/Serializer/PayloadSerializers/StorageSerializer.h @@ -0,0 +1,13 @@ +#pragma once + +#include "Swiften/Serializer/GenericPayloadSerializer.h" +#include "Swiften/Elements/Storage.h" + +namespace Swift { + class StorageSerializer : public GenericPayloadSerializer<Storage> { + public: + StorageSerializer(); + + virtual String serializePayload(boost::shared_ptr<Storage>) const; + }; +} diff --git a/Swiften/Serializer/PayloadSerializers/UnitTest/CapsInfoSerializerTest.cpp b/Swiften/Serializer/PayloadSerializers/UnitTest/CapsInfoSerializerTest.cpp new file mode 100644 index 0000000..650a7ee --- /dev/null +++ b/Swiften/Serializer/PayloadSerializers/UnitTest/CapsInfoSerializerTest.cpp @@ -0,0 +1,25 @@ +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> + +#include "Swiften/Serializer/PayloadSerializers/CapsInfoSerializer.h" + +using namespace Swift; + +class CapsInfoSerializerTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(CapsInfoSerializerTest); + CPPUNIT_TEST(testSerialize); + CPPUNIT_TEST_SUITE_END(); + + public: + CapsInfoSerializerTest() {} + + void testSerialize() { + CapsInfoSerializer testling; + boost::shared_ptr<CapsInfo> priority(new CapsInfo("http://swift.im", "myversion", "sha-1")); + + CPPUNIT_ASSERT_EQUAL(String("<c hash=\"sha-1\" node=\"http://swift.im\" ver=\"myversion\" xmlns=\"http://jabber.org/protocol/caps\"/>"), testling.serialize(priority)); + } +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(CapsInfoSerializerTest); diff --git a/Swiften/Serializer/PayloadSerializers/UnitTest/DiscoInfoSerializerTest.cpp b/Swiften/Serializer/PayloadSerializers/UnitTest/DiscoInfoSerializerTest.cpp new file mode 100644 index 0000000..d3e247f --- /dev/null +++ b/Swiften/Serializer/PayloadSerializers/UnitTest/DiscoInfoSerializerTest.cpp @@ -0,0 +1,38 @@ +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> + +#include "Swiften/Serializer/PayloadSerializers/DiscoInfoSerializer.h" + +using namespace Swift; + +class DiscoInfoSerializerTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(DiscoInfoSerializerTest); + CPPUNIT_TEST(testSerialize); + CPPUNIT_TEST_SUITE_END(); + + public: + DiscoInfoSerializerTest() {} + + void testSerialize() { + DiscoInfoSerializer testling; + boost::shared_ptr<DiscoInfo> discoInfo(new DiscoInfo()); + discoInfo->addIdentity(DiscoInfo::Identity("Swift", "client", "pc")); + discoInfo->addIdentity(DiscoInfo::Identity("Vlug", "client", "pc", "nl")); + discoInfo->addFeature("http://jabber.org/protocol/caps"); + discoInfo->addFeature("http://jabber.org/protocol/disco#info"); + discoInfo->setNode("http://swift.im#bla"); + + String expectedResult = + "<query node=\"http://swift.im#bla\" xmlns=\"http://jabber.org/protocol/disco#info\">" + "<identity category=\"client\" name=\"Swift\" type=\"pc\"/>" + "<identity category=\"client\" name=\"Vlug\" type=\"pc\" xml:lang=\"nl\"/>" + "<feature var=\"http://jabber.org/protocol/caps\"/>" + "<feature var=\"http://jabber.org/protocol/disco#info\"/>" + "</query>"; + + CPPUNIT_ASSERT_EQUAL(expectedResult, testling.serialize(discoInfo)); + } +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(DiscoInfoSerializerTest); diff --git a/Swiften/Serializer/PayloadSerializers/UnitTest/ErrorSerializerTest.cpp b/Swiften/Serializer/PayloadSerializers/UnitTest/ErrorSerializerTest.cpp new file mode 100644 index 0000000..ecd904a --- /dev/null +++ b/Swiften/Serializer/PayloadSerializers/UnitTest/ErrorSerializerTest.cpp @@ -0,0 +1,25 @@ +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> + +#include "Swiften/Serializer/PayloadSerializers/ErrorSerializer.h" + +using namespace Swift; + +class ErrorSerializerTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(ErrorSerializerTest); + CPPUNIT_TEST(testSerialize); + CPPUNIT_TEST_SUITE_END(); + + public: + ErrorSerializerTest() {} + + void testSerialize() { + ErrorSerializer testling; + boost::shared_ptr<ErrorPayload> error(new ErrorPayload(ErrorPayload::BadRequest, ErrorPayload::Cancel, "My Error")); + + CPPUNIT_ASSERT_EQUAL(String("<error type=\"cancel\"><bad-request xmlns=\"urn:ietf:params:xml:ns:xmpp-stanzas\"/><text xmlns=\"urn:ietf:params:xml:ns:xmpp-stanzas\">My Error</text></error>"), testling.serialize(error)); + } +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(ErrorSerializerTest); diff --git a/Swiften/Serializer/PayloadSerializers/UnitTest/PayloadsSerializer.cpp b/Swiften/Serializer/PayloadSerializers/UnitTest/PayloadsSerializer.cpp new file mode 100644 index 0000000..23ff4a0 --- /dev/null +++ b/Swiften/Serializer/PayloadSerializers/UnitTest/PayloadsSerializer.cpp @@ -0,0 +1,21 @@ +#include "Swiften/Serializer/PayloadSerializers/UnitTest/PayloadsSerializer.h" + +#include <cppunit/extensions/HelperMacros.h> + +#include "Swiften/Serializer/PayloadSerializer.h" + +namespace Swift { + +String PayloadsSerializer::serialize(boost::shared_ptr<Payload> payload) { + PayloadSerializer* serializer = serializers.getPayloadSerializer(payload); + if (serializer) { + return serializer->serialize(payload); + } + else { + CPPUNIT_ASSERT(false); + return ""; + } +} + + +} diff --git a/Swiften/Serializer/PayloadSerializers/UnitTest/PayloadsSerializer.h b/Swiften/Serializer/PayloadSerializers/UnitTest/PayloadsSerializer.h new file mode 100644 index 0000000..9fca334 --- /dev/null +++ b/Swiften/Serializer/PayloadSerializers/UnitTest/PayloadsSerializer.h @@ -0,0 +1,17 @@ +#pragma once + +#include <boost/shared_ptr.hpp> + +#include "Swiften/Elements/Payload.h" +#include "Swiften/Base/String.h" +#include "Swiften/Serializer/PayloadSerializers/FullPayloadSerializerCollection.h" + +namespace Swift { + class PayloadsSerializer { + public: + String serialize(boost::shared_ptr<Payload> payload); + + private: + FullPayloadSerializerCollection serializers; + }; +} diff --git a/Swiften/Serializer/PayloadSerializers/UnitTest/PrioritySerializerTest.cpp b/Swiften/Serializer/PayloadSerializers/UnitTest/PrioritySerializerTest.cpp new file mode 100644 index 0000000..5f6432b --- /dev/null +++ b/Swiften/Serializer/PayloadSerializers/UnitTest/PrioritySerializerTest.cpp @@ -0,0 +1,25 @@ +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> + +#include "Swiften/Serializer/PayloadSerializers/PrioritySerializer.h" + +using namespace Swift; + +class PrioritySerializerTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(PrioritySerializerTest); + CPPUNIT_TEST(testSerialize); + CPPUNIT_TEST_SUITE_END(); + + public: + PrioritySerializerTest() {} + + void testSerialize() { + PrioritySerializer testling; + boost::shared_ptr<Priority> priority(new Priority(-113)); + + CPPUNIT_ASSERT_EQUAL(String("<priority>-113</priority>"), testling.serialize(priority)); + } +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(PrioritySerializerTest); diff --git a/Swiften/Serializer/PayloadSerializers/UnitTest/PrivateStorageSerializerTest.cpp b/Swiften/Serializer/PayloadSerializers/UnitTest/PrivateStorageSerializerTest.cpp new file mode 100644 index 0000000..7f0d5c9 --- /dev/null +++ b/Swiften/Serializer/PayloadSerializers/UnitTest/PrivateStorageSerializerTest.cpp @@ -0,0 +1,44 @@ +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> + +#include "Swiften/Serializer/PayloadSerializers/UnitTest/PayloadsSerializer.h" +#include "Swiften/Elements/PrivateStorage.h" +#include "Swiften/Elements/Storage.h" + +using namespace Swift; + +class PrivateStorageSerializerTest : public CppUnit::TestFixture { + CPPUNIT_TEST_SUITE(PrivateStorageSerializerTest); + CPPUNIT_TEST(testSerialize); + CPPUNIT_TEST_SUITE_END(); + + public: + PrivateStorageSerializerTest() {} + + void testSerialize() { + PayloadsSerializer serializer; + + boost::shared_ptr<PrivateStorage> privateStorage(new PrivateStorage()); + boost::shared_ptr<Storage> storage(new Storage()); + Storage::Conference conference; + conference.name = "Swift"; + conference.jid = JID("swift@rooms.swift.im"); + conference.nick = "Alice"; + storage->addConference(conference); + privateStorage->setPayload(storage); + + CPPUNIT_ASSERT_EQUAL(String( + "<query xmlns=\"jabber:iq:private\">" + "<storage xmlns=\"storage:bookmarks\">" + "<conference " + "autojoin=\"0\" " + "jid=\"swift@rooms.swift.im\" " + "name=\"Swift\">" + "<nick>Alice</nick>" + "</conference>" + "</storage>" + "</query>"), serializer.serialize(privateStorage)); + } +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(PrivateStorageSerializerTest); diff --git a/Swiften/Serializer/PayloadSerializers/UnitTest/ResourceBindSerializerTest.cpp b/Swiften/Serializer/PayloadSerializers/UnitTest/ResourceBindSerializerTest.cpp new file mode 100644 index 0000000..ff09966 --- /dev/null +++ b/Swiften/Serializer/PayloadSerializers/UnitTest/ResourceBindSerializerTest.cpp @@ -0,0 +1,49 @@ +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> + +#include "Swiften/Serializer/PayloadSerializers/ResourceBindSerializer.h" + +using namespace Swift; + +class ResourceBindSerializerTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(ResourceBindSerializerTest); + CPPUNIT_TEST(testSerialize_JID); + CPPUNIT_TEST(testSerialize_Resource); + CPPUNIT_TEST(testSerialize_Empty); + CPPUNIT_TEST_SUITE_END(); + + public: + ResourceBindSerializerTest() {} + + void testSerialize_JID() { + ResourceBindSerializer testling; + boost::shared_ptr<ResourceBind> resourceBind(new ResourceBind()); + resourceBind->setJID(JID("somenode@example.com/someresource")); + + CPPUNIT_ASSERT_EQUAL(String( + "<bind xmlns=\"urn:ietf:params:xml:ns:xmpp-bind\">" + "<jid>somenode@example.com/someresource</jid>" + "</bind>"), testling.serialize(resourceBind)); + } + + void testSerialize_Resource() { + ResourceBindSerializer testling; + boost::shared_ptr<ResourceBind> resourceBind(new ResourceBind()); + resourceBind->setResource("someresource"); + + CPPUNIT_ASSERT_EQUAL(String( + "<bind xmlns=\"urn:ietf:params:xml:ns:xmpp-bind\">" + "<resource>someresource</resource>" + "</bind>"), testling.serialize(resourceBind)); + } + + void testSerialize_Empty() { + ResourceBindSerializer testling; + boost::shared_ptr<ResourceBind> resourceBind(new ResourceBind()); + + CPPUNIT_ASSERT_EQUAL(String("<bind xmlns=\"urn:ietf:params:xml:ns:xmpp-bind\"/>"), testling.serialize(resourceBind)); + } +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(ResourceBindSerializerTest); diff --git a/Swiften/Serializer/PayloadSerializers/UnitTest/RosterSerializerTest.cpp b/Swiften/Serializer/PayloadSerializers/UnitTest/RosterSerializerTest.cpp new file mode 100644 index 0000000..81fdc09 --- /dev/null +++ b/Swiften/Serializer/PayloadSerializers/UnitTest/RosterSerializerTest.cpp @@ -0,0 +1,48 @@ +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> + +#include "Swiften/Serializer/PayloadSerializers/RosterSerializer.h" + +using namespace Swift; + +class RosterSerializerTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(RosterSerializerTest); + CPPUNIT_TEST(testSerialize); + CPPUNIT_TEST_SUITE_END(); + + public: + RosterSerializerTest() {} + + void testSerialize() { + RosterSerializer testling; + boost::shared_ptr<RosterPayload> roster(new RosterPayload()); + + RosterItemPayload item1; + item1.setJID(JID("foo@bar.com")); + item1.setName("Foo @ Bar"); + item1.setSubscription(RosterItemPayload::From); + item1.addGroup("Group 1"); + item1.addGroup("Group 2"); + item1.setSubscriptionRequested(); + roster->addItem(item1); + + RosterItemPayload item2; + item2.setJID(JID("baz@blo.com")); + item2.setName("Baz"); + roster->addItem(item2); + + String expectedResult = + "<query xmlns=\"jabber:iq:roster\">" + "<item ask=\"subscribe\" jid=\"foo@bar.com\" name=\"Foo @ Bar\" subscription=\"from\">" + "<group>Group 1</group>" + "<group>Group 2</group>" + "</item>" + "<item jid=\"baz@blo.com\" name=\"Baz\" subscription=\"none\"/>" + "</query>"; + + CPPUNIT_ASSERT_EQUAL(expectedResult, testling.serialize(roster)); + } +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(RosterSerializerTest); diff --git a/Swiften/Serializer/PayloadSerializers/UnitTest/SecurityLabelSerializerTest.cpp b/Swiften/Serializer/PayloadSerializers/UnitTest/SecurityLabelSerializerTest.cpp new file mode 100644 index 0000000..e75695f --- /dev/null +++ b/Swiften/Serializer/PayloadSerializers/UnitTest/SecurityLabelSerializerTest.cpp @@ -0,0 +1,54 @@ +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> + +#include "Swiften/Serializer/PayloadSerializers/SecurityLabelSerializer.h" + +using namespace Swift; + +class SecurityLabelSerializerTest : public CppUnit::TestFixture { + CPPUNIT_TEST_SUITE(SecurityLabelSerializerTest); + CPPUNIT_TEST(testSerialize); + CPPUNIT_TEST(testSerialize_EmptyLabel); + CPPUNIT_TEST_SUITE_END(); + + public: + void testSerialize() { + SecurityLabelSerializer testling; + boost::shared_ptr<SecurityLabel> securityLabel(new SecurityLabel()); + securityLabel->setDisplayMarking("SECRET"); + securityLabel->setForegroundColor("black"); + securityLabel->setBackgroundColor("red"); + securityLabel->setLabel("<esssecuritylabel xmlns=\"urn:xmpp:sec-label:ess:0\">MQYCAQQGASk=</esssecuritylabel>"); + securityLabel->addEquivalentLabel("<icismlabel xmlns=\"http://example.gov/IC-ISM/0\" classification=\"S\" ownerProducer=\"USA\" disseminationControls=\"FOUO\"/>"); + securityLabel->addEquivalentLabel("<esssecuritylabel xmlns=\"urn:xmpp:sec-label:ess:0\">MRUCAgD9DA9BcXVhIChvYnNvbGV0ZSk=</esssecuritylabel>"); + + CPPUNIT_ASSERT_EQUAL(String( + "<securitylabel xmlns=\"urn:xmpp:sec-label:0\">" + "<displaymarking bgcolor=\"red\" fgcolor=\"black\">SECRET</displaymarking>" + "<label>" + "<esssecuritylabel xmlns=\"urn:xmpp:sec-label:ess:0\">MQYCAQQGASk=</esssecuritylabel>" + "</label>" + "<equivalentlabel>" + "<icismlabel xmlns=\"http://example.gov/IC-ISM/0\" classification=\"S\" ownerProducer=\"USA\" disseminationControls=\"FOUO\"/>" + "</equivalentlabel>" + "<equivalentlabel>" + "<esssecuritylabel xmlns=\"urn:xmpp:sec-label:ess:0\">MRUCAgD9DA9BcXVhIChvYnNvbGV0ZSk=</esssecuritylabel>" + "</equivalentlabel>" + "</securitylabel>"), testling.serialize(securityLabel)); + } + + void testSerialize_EmptyLabel() { + SecurityLabelSerializer testling; + boost::shared_ptr<SecurityLabel> securityLabel(new SecurityLabel()); + securityLabel->setDisplayMarking("SECRET"); + securityLabel->setLabel(""); + + CPPUNIT_ASSERT_EQUAL(String( + "<securitylabel xmlns=\"urn:xmpp:sec-label:0\">" + "<displaymarking>SECRET</displaymarking>" + "<label></label>" + "</securitylabel>"), testling.serialize(securityLabel)); + } +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(SecurityLabelSerializerTest); diff --git a/Swiften/Serializer/PayloadSerializers/UnitTest/SecurityLabelsCatalogSerializerTest.cpp b/Swiften/Serializer/PayloadSerializers/UnitTest/SecurityLabelsCatalogSerializerTest.cpp new file mode 100644 index 0000000..3055aca --- /dev/null +++ b/Swiften/Serializer/PayloadSerializers/UnitTest/SecurityLabelsCatalogSerializerTest.cpp @@ -0,0 +1,52 @@ +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> + +#include "Swiften/Serializer/PayloadSerializers/SecurityLabelsCatalogSerializer.h" + +using namespace Swift; + +class SecurityLabelsCatalogSerializerTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(SecurityLabelsCatalogSerializerTest); + CPPUNIT_TEST(testSerialize); + CPPUNIT_TEST_SUITE_END(); + + public: + SecurityLabelsCatalogSerializerTest() {} + + void testSerialize() { + SecurityLabelsCatalogSerializer testling; + boost::shared_ptr<SecurityLabelsCatalog> catalog(new SecurityLabelsCatalog()); + catalog->setTo(JID("example.com")); + catalog->setName("Default"); + catalog->setDescription("an example set of labels"); + + SecurityLabel securityLabel1; + securityLabel1.setDisplayMarking("SECRET"); + securityLabel1.setForegroundColor("black"); + securityLabel1.setBackgroundColor("red"); + securityLabel1.setLabel("<esssecuritylabel xmlns=\"urn:xmpp:sec-label:ess:0\">MQYCAQQGASk=</esssecuritylabel>"); + catalog->addLabel(securityLabel1); + + SecurityLabel securityLabel2; + securityLabel2.setDisplayMarking("CONFIDENTIAL"); + securityLabel2.setForegroundColor("black"); + securityLabel2.setBackgroundColor("navy"); + securityLabel2.setLabel("<esssecuritylabel xmlns=\"urn:xmpp:sec-label:ess:0\">MQMGASk=</esssecuritylabel>"); + catalog->addLabel(securityLabel2); + + CPPUNIT_ASSERT_EQUAL(String( + "<catalog desc=\"an example set of labels\" name=\"Default\" to=\"example.com\" xmlns=\"urn:xmpp:sec-label:catalog:0\">" + "<securitylabel xmlns=\"urn:xmpp:sec-label:0\">" + "<displaymarking bgcolor=\"red\" fgcolor=\"black\">SECRET</displaymarking>" + "<label><esssecuritylabel xmlns=\"urn:xmpp:sec-label:ess:0\">MQYCAQQGASk=</esssecuritylabel></label>" + "</securitylabel>" + "<securitylabel xmlns=\"urn:xmpp:sec-label:0\">" + "<displaymarking bgcolor=\"navy\" fgcolor=\"black\">CONFIDENTIAL</displaymarking>" + "<label><esssecuritylabel xmlns=\"urn:xmpp:sec-label:ess:0\">MQMGASk=</esssecuritylabel></label>" + "</securitylabel>" + "</catalog>"), testling.serialize(catalog)); + } +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(SecurityLabelsCatalogSerializerTest); diff --git a/Swiften/Serializer/PayloadSerializers/UnitTest/SoftwareVersionSerializerTest.cpp b/Swiften/Serializer/PayloadSerializers/UnitTest/SoftwareVersionSerializerTest.cpp new file mode 100644 index 0000000..fd5dba5 --- /dev/null +++ b/Swiften/Serializer/PayloadSerializers/UnitTest/SoftwareVersionSerializerTest.cpp @@ -0,0 +1,25 @@ +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> + +#include "Swiften/Serializer/PayloadSerializers/SoftwareVersionSerializer.h" + +using namespace Swift; + +class SoftwareVersionSerializerTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(SoftwareVersionSerializerTest); + CPPUNIT_TEST(testSerialize); + CPPUNIT_TEST_SUITE_END(); + + public: + SoftwareVersionSerializerTest() {} + + void testSerialize() { + SoftwareVersionSerializer testling; + boost::shared_ptr<SoftwareVersion> softwareVersion(new SoftwareVersion("Swift", "0.1", "Mac OS X")); + + CPPUNIT_ASSERT_EQUAL(String("<query xmlns=\"jabber:iq:version\"><name>Swift</name><version>0.1</version><os>Mac OS X</os></query>"), testling.serialize(softwareVersion)); + } +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(SoftwareVersionSerializerTest); diff --git a/Swiften/Serializer/PayloadSerializers/UnitTest/StatusSerializerTest.cpp b/Swiften/Serializer/PayloadSerializers/UnitTest/StatusSerializerTest.cpp new file mode 100644 index 0000000..6dedacd --- /dev/null +++ b/Swiften/Serializer/PayloadSerializers/UnitTest/StatusSerializerTest.cpp @@ -0,0 +1,25 @@ +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> + +#include "Swiften/Serializer/PayloadSerializers/StatusSerializer.h" + +using namespace Swift; + +class StatusSerializerTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(StatusSerializerTest); + CPPUNIT_TEST(testSerialize); + CPPUNIT_TEST_SUITE_END(); + + public: + StatusSerializerTest() {} + + void testSerialize() { + StatusSerializer testling; + boost::shared_ptr<Status> status(new Status("I am away")); + + CPPUNIT_ASSERT_EQUAL(String("<status>I am away</status>"), testling.serialize(status)); + } +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(StatusSerializerTest); diff --git a/Swiften/Serializer/PayloadSerializers/UnitTest/StatusShowSerializerTest.cpp b/Swiften/Serializer/PayloadSerializers/UnitTest/StatusShowSerializerTest.cpp new file mode 100644 index 0000000..42e1c7c --- /dev/null +++ b/Swiften/Serializer/PayloadSerializers/UnitTest/StatusShowSerializerTest.cpp @@ -0,0 +1,58 @@ +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> + +#include "Swiften/Serializer/PayloadSerializers/StatusShowSerializer.h" + +using namespace Swift; + +class StatusShowSerializerTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(StatusShowSerializerTest); + CPPUNIT_TEST(testSerialize_Online); + CPPUNIT_TEST(testSerialize_Away); + CPPUNIT_TEST(testSerialize_FFC); + CPPUNIT_TEST(testSerialize_XA); + CPPUNIT_TEST(testSerialize_DND); + CPPUNIT_TEST_SUITE_END(); + + public: + StatusShowSerializerTest() {} + + void testSerialize_Online() { + StatusShowSerializer testling; + boost::shared_ptr<StatusShow> statusShow(new StatusShow(StatusShow::Online)); + CPPUNIT_ASSERT_EQUAL(String(""), testling.serialize(statusShow)); + } + + void testSerialize_Away() { + StatusShowSerializer testling; + boost::shared_ptr<StatusShow> statusShow(new StatusShow(StatusShow::Away)); + CPPUNIT_ASSERT_EQUAL(String("<show>away</show>"), testling.serialize(statusShow)); + } + + void testSerialize_FFC() { + StatusShowSerializer testling; + boost::shared_ptr<StatusShow> statusShow(new StatusShow(StatusShow::FFC)); + CPPUNIT_ASSERT_EQUAL(String("<show>chat</show>"), testling.serialize(statusShow)); + } + + void testSerialize_XA() { + StatusShowSerializer testling; + boost::shared_ptr<StatusShow> statusShow(new StatusShow(StatusShow::XA)); + CPPUNIT_ASSERT_EQUAL(String("<show>xa</show>"), testling.serialize(statusShow)); + } + + void testSerialize_DND() { + StatusShowSerializer testling; + boost::shared_ptr<StatusShow> statusShow(new StatusShow(StatusShow::DND)); + CPPUNIT_ASSERT_EQUAL(String("<show>dnd</show>"), testling.serialize(statusShow)); + } + + void testSerialize_None() { + StatusShowSerializer testling; + boost::shared_ptr<StatusShow> statusShow(new StatusShow(StatusShow::None)); + CPPUNIT_ASSERT_EQUAL(String(""), testling.serialize(statusShow)); + } +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(StatusShowSerializerTest); diff --git a/Swiften/Serializer/PayloadSerializers/UnitTest/StorageSerializerTest.cpp b/Swiften/Serializer/PayloadSerializers/UnitTest/StorageSerializerTest.cpp new file mode 100644 index 0000000..daf43c5 --- /dev/null +++ b/Swiften/Serializer/PayloadSerializers/UnitTest/StorageSerializerTest.cpp @@ -0,0 +1,60 @@ +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> + +#include "Swiften/Serializer/PayloadSerializers/UnitTest/PayloadsSerializer.h" +#include "Swiften/Elements/Storage.h" + +using namespace Swift; + +class StorageSerializerTest : public CppUnit::TestFixture { + CPPUNIT_TEST_SUITE(StorageSerializerTest); + CPPUNIT_TEST(testSerialize); + CPPUNIT_TEST(testSerialize_NoNickOrPassword); + CPPUNIT_TEST_SUITE_END(); + + public: + StorageSerializerTest() {} + + void testSerialize() { + PayloadsSerializer serializer; + boost::shared_ptr<Storage> storage(new Storage()); + Storage::Conference conference; + conference.name = "Council of Oberon"; + conference.autoJoin = true; + conference.jid = JID("council@conference.underhill.org"); + conference.nick = "Puck"; + conference.password = "MyPass"; + storage->addConference(conference); + + CPPUNIT_ASSERT_EQUAL(String( + "<storage xmlns=\"storage:bookmarks\">" + "<conference " + "autojoin=\"1\" " + "jid=\"council@conference.underhill.org\" " + "name=\"Council of Oberon\">" + "<nick>Puck</nick>" + "<password>MyPass</password>" + "</conference>" + "</storage>"), serializer.serialize(storage)); + } + + void testSerialize_NoNickOrPassword() { + PayloadsSerializer serializer; + boost::shared_ptr<Storage> storage(new Storage()); + Storage::Conference conference; + conference.name = "Council of Oberon"; + conference.autoJoin = true; + conference.jid = JID("council@conference.underhill.org"); + storage->addConference(conference); + + CPPUNIT_ASSERT_EQUAL(String( + "<storage xmlns=\"storage:bookmarks\">" + "<conference " + "autojoin=\"1\" " + "jid=\"council@conference.underhill.org\" " + "name=\"Council of Oberon\"/>" + "</storage>"), serializer.serialize(storage)); + } +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(StorageSerializerTest); diff --git a/Swiften/Serializer/PayloadSerializers/UnitTest/VCardUpdateSerializerTest.cpp b/Swiften/Serializer/PayloadSerializers/UnitTest/VCardUpdateSerializerTest.cpp new file mode 100644 index 0000000..23988d7 --- /dev/null +++ b/Swiften/Serializer/PayloadSerializers/UnitTest/VCardUpdateSerializerTest.cpp @@ -0,0 +1,31 @@ +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> + +#include "Swiften/Serializer/PayloadSerializers/VCardUpdateSerializer.h" + +using namespace Swift; + +class VCardUpdateSerializerTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(VCardUpdateSerializerTest); + CPPUNIT_TEST(testSerialize); + CPPUNIT_TEST_SUITE_END(); + + public: + VCardUpdateSerializerTest() {} + + void testSerialize() { + VCardUpdateSerializer testling; + boost::shared_ptr<VCardUpdate> update(new VCardUpdate()); + update->setPhotoHash("sha1-hash-of-image"); + + String expectedResult = + "<x xmlns=\"vcard-temp:x:update\">" + "<photo>sha1-hash-of-image</photo>" + "</x>"; + + CPPUNIT_ASSERT_EQUAL(expectedResult, testling.serialize(update)); + } +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(VCardUpdateSerializerTest); diff --git a/Swiften/Serializer/PayloadSerializers/VCardSerializer.cpp b/Swiften/Serializer/PayloadSerializers/VCardSerializer.cpp new file mode 100644 index 0000000..fbb0274 --- /dev/null +++ b/Swiften/Serializer/PayloadSerializers/VCardSerializer.cpp @@ -0,0 +1,50 @@ +#include "Swiften/Serializer/PayloadSerializers/VCardSerializer.h" + +#include <boost/shared_ptr.hpp> + +#include "Swiften/Serializer/XML/XMLElement.h" +#include "Swiften/Serializer/XML/XMLTextNode.h" + +namespace Swift { + +VCardSerializer::VCardSerializer() : GenericPayloadSerializer<VCard>() { +} + +String VCardSerializer::serializePayload(boost::shared_ptr<VCard> vcard) const { + XMLElement queryElement("vCard", "vcard-temp"); + if (!vcard->getFullName().isEmpty()) { + boost::shared_ptr<XMLElement> fullNameElement(new XMLElement("FN")); + fullNameElement->addNode(boost::shared_ptr<XMLTextNode>(new XMLTextNode(vcard->getFullName()))); + queryElement.addNode(fullNameElement); + } + if (!vcard->getGivenName().isEmpty() || !vcard->getFamilyName().isEmpty()) { + boost::shared_ptr<XMLElement> nameElement(new XMLElement("N")); + if (!vcard->getFamilyName().isEmpty()) { + boost::shared_ptr<XMLElement> familyNameElement(new XMLElement("FAMILY")); + familyNameElement->addNode(boost::shared_ptr<XMLTextNode>(new XMLTextNode(vcard->getFamilyName()))); + nameElement->addNode(familyNameElement); + } + if (!vcard->getGivenName().isEmpty()) { + boost::shared_ptr<XMLElement> givenNameElement(new XMLElement("GIVEN")); + givenNameElement->addNode(boost::shared_ptr<XMLTextNode>(new XMLTextNode(vcard->getGivenName()))); + nameElement->addNode(givenNameElement); + } + queryElement.addNode(nameElement); + } + if (!vcard->getEMail().isEmpty()) { + boost::shared_ptr<XMLElement> emailElement(new XMLElement("EMAIL")); + boost::shared_ptr<XMLElement> userIDElement(new XMLElement("USERID")); + userIDElement->addNode(boost::shared_ptr<XMLTextNode>(new XMLTextNode(vcard->getEMail()))); + emailElement->addNode(userIDElement); + queryElement.addNode(emailElement); + } + if (!vcard->getNickname().isEmpty()) { + boost::shared_ptr<XMLElement> nickElement(new XMLElement("NICKNAME")); + nickElement->addNode(boost::shared_ptr<XMLTextNode>(new XMLTextNode(vcard->getNickname()))); + queryElement.addNode(nickElement); + } + // TODO + return queryElement.serialize(); +} + +} diff --git a/Swiften/Serializer/PayloadSerializers/VCardSerializer.h b/Swiften/Serializer/PayloadSerializers/VCardSerializer.h new file mode 100644 index 0000000..baf5947 --- /dev/null +++ b/Swiften/Serializer/PayloadSerializers/VCardSerializer.h @@ -0,0 +1,13 @@ +#pragma once + +#include "Swiften/Serializer/GenericPayloadSerializer.h" +#include "Swiften/Elements/VCard.h" + +namespace Swift { + class VCardSerializer : public GenericPayloadSerializer<VCard> { + public: + VCardSerializer(); + + virtual String serializePayload(boost::shared_ptr<VCard>) const; + }; +} diff --git a/Swiften/Serializer/PayloadSerializers/VCardUpdateSerializer.cpp b/Swiften/Serializer/PayloadSerializers/VCardUpdateSerializer.cpp new file mode 100644 index 0000000..540b48d --- /dev/null +++ b/Swiften/Serializer/PayloadSerializers/VCardUpdateSerializer.cpp @@ -0,0 +1,21 @@ +#include "Swiften/Serializer/PayloadSerializers/VCardUpdateSerializer.h" + +#include <boost/shared_ptr.hpp> + +#include "Swiften/Serializer/XML/XMLElement.h" +#include "Swiften/Serializer/XML/XMLTextNode.h" + +namespace Swift { + +VCardUpdateSerializer::VCardUpdateSerializer() : GenericPayloadSerializer<VCardUpdate>() { +} + +String VCardUpdateSerializer::serializePayload(boost::shared_ptr<VCardUpdate> vcardUpdate) const { + XMLElement updateElement("x", "vcard-temp:x:update"); + boost::shared_ptr<XMLElement> photoElement(new XMLElement("photo")); + photoElement->addNode(boost::shared_ptr<XMLTextNode>(new XMLTextNode(vcardUpdate->getPhotoHash()))); + updateElement.addNode(photoElement); + return updateElement.serialize(); +} + +} diff --git a/Swiften/Serializer/PayloadSerializers/VCardUpdateSerializer.h b/Swiften/Serializer/PayloadSerializers/VCardUpdateSerializer.h new file mode 100644 index 0000000..e1373a9 --- /dev/null +++ b/Swiften/Serializer/PayloadSerializers/VCardUpdateSerializer.h @@ -0,0 +1,13 @@ +#pragma once + +#include "Swiften/Serializer/GenericPayloadSerializer.h" +#include "Swiften/Elements/VCardUpdate.h" + +namespace Swift { + class VCardUpdateSerializer : public GenericPayloadSerializer<VCardUpdate> { + public: + VCardUpdateSerializer(); + + virtual String serializePayload(boost::shared_ptr<VCardUpdate>) const; + }; +} diff --git a/Swiften/Serializer/PresenceSerializer.cpp b/Swiften/Serializer/PresenceSerializer.cpp new file mode 100644 index 0000000..f7585d5 --- /dev/null +++ b/Swiften/Serializer/PresenceSerializer.cpp @@ -0,0 +1,27 @@ +#include "Swiften/Serializer/PresenceSerializer.h" +#include "Swiften/Serializer/XML/XMLElement.h" + +#include "boost/shared_ptr.hpp" + +namespace Swift { + +PresenceSerializer::PresenceSerializer(PayloadSerializerCollection* payloadSerializers) : + GenericStanzaSerializer<Presence>("presence", payloadSerializers) { +} + +void PresenceSerializer::setStanzaSpecificAttributesGeneric( + boost::shared_ptr<Presence> presence, + XMLElement& element) const { + switch (presence->getType()) { + case Presence::Unavailable: element.setAttribute("type","unavailable"); break; + case Presence::Probe: element.setAttribute("type","probe"); break; + case Presence::Subscribe: element.setAttribute("type","subscribe"); break; + case Presence::Subscribed: element.setAttribute("type","subscribed"); break; + case Presence::Unsubscribe: element.setAttribute("type","unsubscribe"); break; + case Presence::Unsubscribed: element.setAttribute("type","unsubscribed"); break; + case Presence::Error: element.setAttribute("type","error"); break; + case Presence::Available: break; + } +} + +} diff --git a/Swiften/Serializer/PresenceSerializer.h b/Swiften/Serializer/PresenceSerializer.h new file mode 100644 index 0000000..158d4f2 --- /dev/null +++ b/Swiften/Serializer/PresenceSerializer.h @@ -0,0 +1,21 @@ +#ifndef SWIFTEN_PresenceSerializer_H +#define SWIFTEN_PresenceSerializer_H + +#include <cassert> + +#include "Swiften/Serializer/GenericStanzaSerializer.h" +#include "Swiften/Elements/Presence.h" + +namespace Swift { + class PresenceSerializer : public GenericStanzaSerializer<Presence> { + public: + PresenceSerializer(PayloadSerializerCollection* payloadSerializers); + + private: + virtual void setStanzaSpecificAttributesGeneric( + boost::shared_ptr<Presence> presence, + XMLElement& element) const; + }; +} + +#endif diff --git a/Swiften/Serializer/StanzaSerializer.cpp b/Swiften/Serializer/StanzaSerializer.cpp new file mode 100644 index 0000000..d940634 --- /dev/null +++ b/Swiften/Serializer/StanzaSerializer.cpp @@ -0,0 +1,50 @@ +#include "Swiften/Serializer/StanzaSerializer.h" + +#include <sstream> +#include <typeinfo> +#include <iostream> + +#include "Swiften/Serializer/XML/XMLElement.h" +#include "Swiften/Serializer/XML/XMLRawTextNode.h" +#include "Swiften/Serializer/PayloadSerializer.h" +#include "Swiften/Serializer/PayloadSerializerCollection.h" +#include "Swiften/Elements/Stanza.h" + +namespace Swift { + +StanzaSerializer::StanzaSerializer(const String& tag, PayloadSerializerCollection* payloadSerializers) : tag_(tag), payloadSerializers_(payloadSerializers) { +} + +String StanzaSerializer::serialize(boost::shared_ptr<Element> element) const { + boost::shared_ptr<Stanza> stanza(boost::dynamic_pointer_cast<Stanza>(element)); + + XMLElement stanzaElement(tag_); + if (stanza->getFrom().isValid()) { + stanzaElement.setAttribute("from", stanza->getFrom()); + } + if (stanza->getTo().isValid()) { + stanzaElement.setAttribute("to", stanza->getTo()); + } + if (!stanza->getID().isEmpty()) { + stanzaElement.setAttribute("id", stanza->getID()); + } + setStanzaSpecificAttributes(stanza, stanzaElement); + + String serializedPayloads; + foreach (const boost::shared_ptr<Payload>& payload, stanza->getPayloads()) { + PayloadSerializer* serializer = payloadSerializers_->getPayloadSerializer(payload); + if (serializer) { + serializedPayloads += serializer->serialize(payload); + } + else { + std::cerr << "Could not find serializer for " << typeid(*(payload.get())).name() << std::endl; + } + } + if (!serializedPayloads.isEmpty()) { + stanzaElement.addNode(boost::shared_ptr<XMLNode>(new XMLRawTextNode(serializedPayloads))); + } + + return stanzaElement.serialize(); +} + +} diff --git a/Swiften/Serializer/StanzaSerializer.h b/Swiften/Serializer/StanzaSerializer.h new file mode 100644 index 0000000..0f7abaf --- /dev/null +++ b/Swiften/Serializer/StanzaSerializer.h @@ -0,0 +1,25 @@ +#ifndef SWIFTEN_STANZASERIALIZER_H +#define SWIFTEN_STANZASERIALIZER_H + +#include "Swiften/Elements/Stanza.h" +#include "Swiften/Serializer/ElementSerializer.h" +#include "Swiften/Base/String.h" + +namespace Swift { + class PayloadSerializerCollection; + class XMLElement; + + class StanzaSerializer : public ElementSerializer { + public: + StanzaSerializer(const String& tag, PayloadSerializerCollection* payloadSerializers); + + virtual String serialize(boost::shared_ptr<Element>) const; + virtual void setStanzaSpecificAttributes(boost::shared_ptr<Element>, XMLElement&) const = 0; + + private: + String tag_; + PayloadSerializerCollection* payloadSerializers_; + }; +} + +#endif diff --git a/Swiften/Serializer/StartTLSFailureSerializer.h b/Swiften/Serializer/StartTLSFailureSerializer.h new file mode 100644 index 0000000..472fea0 --- /dev/null +++ b/Swiften/Serializer/StartTLSFailureSerializer.h @@ -0,0 +1,22 @@ +#ifndef SWIFTEN_StartTLSFailureSerializer_H +#define SWIFTEN_StartTLSFailureSerializer_H + +#include <boost/shared_ptr.hpp> + +#include "Swiften/Elements/StartTLSFailure.h" +#include "Swiften/Serializer/GenericElementSerializer.h" +#include "Swiften/Serializer/XML/XMLElement.h" + +namespace Swift { + class StartTLSFailureSerializer : public GenericElementSerializer<StartTLSFailure> { + public: + StartTLSFailureSerializer() : GenericElementSerializer<StartTLSFailure>() { + } + + virtual String serialize(boost::shared_ptr<Element>) const { + return XMLElement("failure", "urn:ietf:params:xml:ns:xmpp-tls").serialize(); + } + }; +} + +#endif diff --git a/Swiften/Serializer/StartTLSRequestSerializer.h b/Swiften/Serializer/StartTLSRequestSerializer.h new file mode 100644 index 0000000..fa85fe2 --- /dev/null +++ b/Swiften/Serializer/StartTLSRequestSerializer.h @@ -0,0 +1,22 @@ +#ifndef SWIFTEN_StartTLSRequestSerializer_H +#define SWIFTEN_StartTLSRequestSerializer_H + +#include <boost/shared_ptr.hpp> + +#include "Swiften/Elements/StartTLSRequest.h" +#include "Swiften/Serializer/GenericElementSerializer.h" +#include "Swiften/Serializer/XML/XMLElement.h" + +namespace Swift { + class StartTLSRequestSerializer : public GenericElementSerializer<StartTLSRequest> { + public: + StartTLSRequestSerializer() : GenericElementSerializer<StartTLSRequest>() { + } + + virtual String serialize(boost::shared_ptr<Element>) const { + return XMLElement("starttls", "urn:ietf:params:xml:ns:xmpp-tls").serialize(); + } + }; +} + +#endif diff --git a/Swiften/Serializer/StreamFeaturesSerializer.cpp b/Swiften/Serializer/StreamFeaturesSerializer.cpp new file mode 100644 index 0000000..49e62c4 --- /dev/null +++ b/Swiften/Serializer/StreamFeaturesSerializer.cpp @@ -0,0 +1,46 @@ +#include "Swiften/Serializer/StreamFeaturesSerializer.h" + +#include "Swiften/Serializer/XML/XMLElement.h" +#include "Swiften/Serializer/XML/XMLTextNode.h" +#include "Swiften/Base/foreach.h" + +namespace Swift { + +StreamFeaturesSerializer::StreamFeaturesSerializer() { +} + +String StreamFeaturesSerializer::serialize(boost::shared_ptr<Element> element) const { + boost::shared_ptr<StreamFeatures> streamFeatures(boost::dynamic_pointer_cast<StreamFeatures>(element)); + + XMLElement streamFeaturesElement("stream:features"); + if (streamFeatures->hasStartTLS()) { + streamFeaturesElement.addNode(boost::shared_ptr<XMLElement>(new XMLElement("starttls", "urn:ietf:params:xml:ns:xmpp-tls"))); + } + if (!streamFeatures->getCompressionMethods().empty()) { + boost::shared_ptr<XMLElement> compressionElement(new XMLElement("compression", "http://jabber.org/features/compress")); + foreach(const String& method, streamFeatures->getCompressionMethods()) { + boost::shared_ptr<XMLElement> methodElement(new XMLElement("method")); + methodElement->addNode(boost::shared_ptr<XMLTextNode>(new XMLTextNode(method))); + compressionElement->addNode(methodElement); + } + streamFeaturesElement.addNode(compressionElement); + } + if (!streamFeatures->getAuthenticationMechanisms().empty()) { + boost::shared_ptr<XMLElement> mechanismsElement(new XMLElement("mechanisms", "urn:ietf:params:xml:ns:xmpp-sasl")); + foreach(const String& mechanism, streamFeatures->getAuthenticationMechanisms()) { + boost::shared_ptr<XMLElement> mechanismElement(new XMLElement("mechanism")); + mechanismElement->addNode(boost::shared_ptr<XMLTextNode>(new XMLTextNode(mechanism))); + mechanismsElement->addNode(mechanismElement); + } + streamFeaturesElement.addNode(mechanismsElement); + } + if (streamFeatures->hasResourceBind()) { + streamFeaturesElement.addNode(boost::shared_ptr<XMLElement>(new XMLElement("bind", "urn:ietf:params:xml:ns:xmpp-bind"))); + } + if (streamFeatures->hasSession()) { + streamFeaturesElement.addNode(boost::shared_ptr<XMLElement>(new XMLElement("session", "urn:ietf:params:xml:ns:xmpp-session"))); + } + return streamFeaturesElement.serialize(); +} + +} diff --git a/Swiften/Serializer/StreamFeaturesSerializer.h b/Swiften/Serializer/StreamFeaturesSerializer.h new file mode 100644 index 0000000..f2da1bf --- /dev/null +++ b/Swiften/Serializer/StreamFeaturesSerializer.h @@ -0,0 +1,18 @@ +#ifndef SWIFTEN_StreamFeaturesSerializer_H +#define SWIFTEN_StreamFeaturesSerializer_H + +#include <boost/shared_ptr.hpp> + +#include "Swiften/Elements/StreamFeatures.h" +#include "Swiften/Serializer/GenericElementSerializer.h" + +namespace Swift { + class StreamFeaturesSerializer : public GenericElementSerializer<StreamFeatures> { + public: + StreamFeaturesSerializer(); + + virtual String serialize(boost::shared_ptr<Element> element) const; + }; +} + +#endif diff --git a/Swiften/Serializer/TLSProceedSerializer.h b/Swiften/Serializer/TLSProceedSerializer.h new file mode 100644 index 0000000..d3829ac --- /dev/null +++ b/Swiften/Serializer/TLSProceedSerializer.h @@ -0,0 +1,22 @@ +#ifndef SWIFTEN_TLSProceedSerializer_H +#define SWIFTEN_TLSProceedSerializer_H + +#include <boost/shared_ptr.hpp> + +#include "Swiften/Elements/TLSProceed.h" +#include "Swiften/Serializer/GenericElementSerializer.h" +#include "Swiften/Serializer/XML/XMLElement.h" + +namespace Swift { + class TLSProceedSerializer : public GenericElementSerializer<TLSProceed> { + public: + TLSProceedSerializer() : GenericElementSerializer<TLSProceed>() { + } + + virtual String serialize(boost::shared_ptr<Element>) const { + return XMLElement("proceed", "urn:ietf:params:xml:ns:xmpp-tls").serialize(); + } + }; +} + +#endif diff --git a/Swiften/Serializer/UnitTest/StreamFeaturesSerializerTest.cpp b/Swiften/Serializer/UnitTest/StreamFeaturesSerializerTest.cpp new file mode 100644 index 0000000..187fe64 --- /dev/null +++ b/Swiften/Serializer/UnitTest/StreamFeaturesSerializerTest.cpp @@ -0,0 +1,46 @@ +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> + +#include "Swiften/Serializer/StreamFeaturesSerializer.h" +#include "Swiften/Elements/StreamFeatures.h" + +using namespace Swift; + +class StreamFeaturesSerializerTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(StreamFeaturesSerializerTest); + CPPUNIT_TEST(testSerialize); + CPPUNIT_TEST_SUITE_END(); + + public: + StreamFeaturesSerializerTest() {} + + void testSerialize() { + StreamFeaturesSerializer testling; + boost::shared_ptr<StreamFeatures> streamFeatures(new StreamFeatures()); + streamFeatures->setHasStartTLS(); + streamFeatures->addCompressionMethod("zlib"); + streamFeatures->addCompressionMethod("lzw"); + streamFeatures->addAuthenticationMechanism("DIGEST-MD5"); + streamFeatures->addAuthenticationMechanism("PLAIN"); + streamFeatures->setHasResourceBind(); + streamFeatures->setHasSession(); + + CPPUNIT_ASSERT_EQUAL(String( + "<stream:features>" + "<starttls xmlns=\"urn:ietf:params:xml:ns:xmpp-tls\"/>" + "<compression xmlns=\"http://jabber.org/features/compress\">" + "<method>zlib</method>" + "<method>lzw</method>" + "</compression>" + "<mechanisms xmlns=\"urn:ietf:params:xml:ns:xmpp-sasl\">" + "<mechanism>DIGEST-MD5</mechanism>" + "<mechanism>PLAIN</mechanism>" + "</mechanisms>" + "<bind xmlns=\"urn:ietf:params:xml:ns:xmpp-bind\"/>" + "<session xmlns=\"urn:ietf:params:xml:ns:xmpp-session\"/>" + "</stream:features>"), testling.serialize(streamFeatures)); + } +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(StreamFeaturesSerializerTest); diff --git a/Swiften/Serializer/XML/UnitTest/XMLElementTest.cpp b/Swiften/Serializer/XML/UnitTest/XMLElementTest.cpp new file mode 100644 index 0000000..49eb109 --- /dev/null +++ b/Swiften/Serializer/XML/UnitTest/XMLElementTest.cpp @@ -0,0 +1,62 @@ +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> + +#include "Swiften/Serializer/XML/XMLElement.h" +#include "Swiften/Serializer/XML/XMLTextNode.h" + +using namespace Swift; + +class XMLElementTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(XMLElementTest); + CPPUNIT_TEST(testSerialize); + CPPUNIT_TEST(testSerialize_NoChildren); + CPPUNIT_TEST(testSerialize_SpecialAttributeCharacters); + CPPUNIT_TEST(testSerialize_EmptyAttributeValue); + CPPUNIT_TEST_SUITE_END(); + + public: + XMLElementTest() {} + + void testSerialize() { + XMLElement testling("foo", "http://example.com"); + testling.setAttribute("myatt", "myval"); + boost::shared_ptr<XMLElement> barElement(new XMLElement("bar")); + barElement->addNode(boost::shared_ptr<XMLTextNode>(new XMLTextNode("Blo"))); + testling.addNode(barElement); + boost::shared_ptr<XMLElement> bazElement(new XMLElement("baz")); + bazElement->addNode(boost::shared_ptr<XMLTextNode>(new XMLTextNode("Bli"))); + testling.addNode(bazElement); + + String result = testling.serialize(); + String expectedResult = + "<foo myatt=\"myval\" xmlns=\"http://example.com\">" + "<bar>Blo</bar>" + "<baz>Bli</baz>" + "</foo>"; + + CPPUNIT_ASSERT_EQUAL(expectedResult, result); + } + + void testSerialize_NoChildren() { + XMLElement testling("foo", "http://example.com"); + + CPPUNIT_ASSERT_EQUAL(String("<foo xmlns=\"http://example.com\"/>"), testling.serialize()); + } + + void testSerialize_SpecialAttributeCharacters() { + XMLElement testling("foo"); + testling.setAttribute("myatt", "<\"'&>"); + + CPPUNIT_ASSERT_EQUAL(String("<foo myatt=\"<"'&>\"/>"), testling.serialize()); + } + + void testSerialize_EmptyAttributeValue() { + XMLElement testling("foo"); + testling.setAttribute("myatt", ""); + + CPPUNIT_ASSERT_EQUAL(String("<foo myatt=\"\"/>"), testling.serialize()); + } +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(XMLElementTest); diff --git a/Swiften/Serializer/XML/XMLElement.cpp b/Swiften/Serializer/XML/XMLElement.cpp new file mode 100644 index 0000000..71f4d0c --- /dev/null +++ b/Swiften/Serializer/XML/XMLElement.cpp @@ -0,0 +1,49 @@ +#include "Swiften/Serializer/XML/XMLElement.h" + +#include "Swiften/Base/foreach.h" + +namespace Swift { + +XMLElement::XMLElement(const String& tag, const String& xmlns) : + tag_(tag) { + if (!xmlns.isEmpty()) { + setAttribute("xmlns", xmlns); + } +} + +String XMLElement::serialize() { + String result; + result += "<" + tag_; + typedef std::pair<String,String> Pair; + foreach(const Pair& p, attributes_) { + result += " " + p.first + "=\"" + p.second + "\""; + } + + if (childNodes_.size() > 0) { + result += ">"; + foreach (boost::shared_ptr<XMLNode> node, childNodes_) { + result += node->serialize(); + } + result += "</" + tag_ + ">"; + } + else { + result += "/>"; + } + return result; +} + +void XMLElement::setAttribute(const String& attribute, const String& value) { + String escapedValue(value); + escapedValue.replaceAll('&', "&"); + escapedValue.replaceAll('<', "<"); + escapedValue.replaceAll('>', ">"); + escapedValue.replaceAll('\'', "'"); + escapedValue.replaceAll('"', """); + attributes_[attribute] = escapedValue; +} + +void XMLElement::addNode(boost::shared_ptr<XMLNode> node) { + childNodes_.push_back(node); +} + +} diff --git a/Swiften/Serializer/XML/XMLElement.h b/Swiften/Serializer/XML/XMLElement.h new file mode 100644 index 0000000..f2eb8bf --- /dev/null +++ b/Swiften/Serializer/XML/XMLElement.h @@ -0,0 +1,27 @@ +#ifndef SWIFTEN_XMLElement_H +#define SWIFTEN_XMLElement_H + +#include <boost/shared_ptr.hpp> +#include <vector> +#include <map> + +#include "Swiften/Base/String.h" +#include "Swiften/Serializer/XML/XMLNode.h" + +namespace Swift { + class XMLElement : public XMLNode { + public: + XMLElement(const String& tag, const String& xmlns = ""); + + void setAttribute(const String& attribute, const String& value); + void addNode(boost::shared_ptr<XMLNode> node); + + virtual String serialize(); + + private: + String tag_; + std::map<String, String> attributes_; + std::vector< boost::shared_ptr<XMLNode> > childNodes_; + }; +} +#endif diff --git a/Swiften/Serializer/XML/XMLNode.cpp b/Swiften/Serializer/XML/XMLNode.cpp new file mode 100644 index 0000000..1bef64a --- /dev/null +++ b/Swiften/Serializer/XML/XMLNode.cpp @@ -0,0 +1,8 @@ +#include "Swiften/Serializer/XML/XMLNode.h" + +namespace Swift { + +XMLNode::~XMLNode() { +} + +} diff --git a/Swiften/Serializer/XML/XMLNode.h b/Swiften/Serializer/XML/XMLNode.h new file mode 100644 index 0000000..b31c0d6 --- /dev/null +++ b/Swiften/Serializer/XML/XMLNode.h @@ -0,0 +1,15 @@ +#ifndef SWIFTEN_XMLNode_H +#define SWIFTEN_XMLNode_H + +#include "Swiften/Base/String.h" + +namespace Swift { + class XMLNode { + public: + virtual ~XMLNode(); + + virtual String serialize() = 0; + }; +} + +#endif diff --git a/Swiften/Serializer/XML/XMLRawTextNode.h b/Swiften/Serializer/XML/XMLRawTextNode.h new file mode 100644 index 0000000..e5800c3 --- /dev/null +++ b/Swiften/Serializer/XML/XMLRawTextNode.h @@ -0,0 +1,21 @@ +#ifndef SWIFTEN_XMLRawTextNode_H +#define SWIFTEN_XMLRawTextNode_H + +#include "Swiften/Serializer/XML/XMLNode.h" + +namespace Swift { + class XMLRawTextNode : public XMLNode { + public: + XMLRawTextNode(const String& text) : text_(text) { + } + + String serialize() { + return text_; + } + + private: + String text_; + }; +} + +#endif diff --git a/Swiften/Serializer/XML/XMLTextNode.h b/Swiften/Serializer/XML/XMLTextNode.h new file mode 100644 index 0000000..87dda53 --- /dev/null +++ b/Swiften/Serializer/XML/XMLTextNode.h @@ -0,0 +1,21 @@ +#pragma once + +#include "Swiften/Serializer/XML/XMLNode.h" + +namespace Swift { + class XMLTextNode : public XMLNode { + public: + XMLTextNode(const String& text) : text_(text) { + text_.replaceAll('&', "&"); // Should come first + text_.replaceAll('<', "<"); + text_.replaceAll('>', ">"); + } + + String serialize() { + return text_; + } + + private: + String text_; + }; +} diff --git a/Swiften/Serializer/XMPPSerializer.cpp b/Swiften/Serializer/XMPPSerializer.cpp new file mode 100644 index 0000000..082cdf3 --- /dev/null +++ b/Swiften/Serializer/XMPPSerializer.cpp @@ -0,0 +1,77 @@ +#include "Swiften/Serializer/XMPPSerializer.h" + +#include <boost/bind.hpp> +#include <iostream> + +#include "Swiften/Elements/ProtocolHeader.h" +#include "Swiften/Base/foreach.h" +#include "Swiften/Serializer/CompressRequestSerializer.h" +#include "Swiften/Serializer/CompressFailureSerializer.h" +#include "Swiften/Serializer/StreamFeaturesSerializer.h" +#include "Swiften/Serializer/AuthRequestSerializer.h" +#include "Swiften/Serializer/AuthFailureSerializer.h" +#include "Swiften/Serializer/AuthSuccessSerializer.h" +#include "Swiften/Serializer/AuthChallengeSerializer.h" +#include "Swiften/Serializer/AuthResponseSerializer.h" +#include "Swiften/Serializer/StartTLSRequestSerializer.h" +#include "Swiften/Serializer/StartTLSFailureSerializer.h" +#include "Swiften/Serializer/TLSProceedSerializer.h" +#include "Swiften/Serializer/MessageSerializer.h" +#include "Swiften/Serializer/PresenceSerializer.h" +#include "Swiften/Serializer/IQSerializer.h" + +namespace Swift { + +XMPPSerializer::XMPPSerializer(PayloadSerializerCollection* payloadSerializers) { + serializers_.push_back(boost::shared_ptr<ElementSerializer>(new PresenceSerializer(payloadSerializers))); + serializers_.push_back(boost::shared_ptr<ElementSerializer>(new IQSerializer(payloadSerializers))); + serializers_.push_back(boost::shared_ptr<ElementSerializer>(new MessageSerializer(payloadSerializers))); + serializers_.push_back(boost::shared_ptr<ElementSerializer>(new CompressRequestSerializer())); + serializers_.push_back(boost::shared_ptr<ElementSerializer>(new CompressFailureSerializer())); + serializers_.push_back(boost::shared_ptr<ElementSerializer>(new AuthRequestSerializer())); + serializers_.push_back(boost::shared_ptr<ElementSerializer>(new AuthFailureSerializer())); + serializers_.push_back(boost::shared_ptr<ElementSerializer>(new AuthSuccessSerializer())); + serializers_.push_back(boost::shared_ptr<ElementSerializer>(new AuthChallengeSerializer())); + serializers_.push_back(boost::shared_ptr<ElementSerializer>(new AuthResponseSerializer())); + serializers_.push_back(boost::shared_ptr<ElementSerializer>(new StartTLSRequestSerializer())); + serializers_.push_back(boost::shared_ptr<ElementSerializer>(new StartTLSFailureSerializer())); + serializers_.push_back(boost::shared_ptr<ElementSerializer>(new TLSProceedSerializer())); + serializers_.push_back(boost::shared_ptr<ElementSerializer>(new StreamFeaturesSerializer())); +} + +String XMPPSerializer::serializeHeader(const ProtocolHeader& header) const { + String result = "<?xml version=\"1.0\"?><stream:stream xmlns=\"jabber:client\" xmlns:stream=\"http://etherx.jabber.org/streams\""; + if (!header.getFrom().isEmpty()) { + result += " from=\"" + header.getFrom() + "\""; + } + if (!header.getTo().isEmpty()) { + result += " to=\"" + header.getTo() + "\""; + } + if (!header.getID().isEmpty()) { + result += " id=\"" + header.getID() + "\""; + } + if (!header.getVersion().isEmpty()) { + result += " version=\"" + header.getVersion() + "\""; + } + result += ">"; + return result; +} + +String XMPPSerializer::serializeElement(boost::shared_ptr<Element> element) const { + std::vector< boost::shared_ptr<ElementSerializer> >::const_iterator i = std::find_if( + serializers_.begin(), serializers_.end(), + boost::bind(&ElementSerializer::canSerialize, _1, element)); + if (i != serializers_.end()) { + return (*i)->serialize(element); + } + else { + std::cerr << "Could not find serializer for " << typeid(*(element.get())).name() << std::endl; + return ""; + } +} + +String XMPPSerializer::serializeFooter() const { + return "</stream:stream>"; +} + +} diff --git a/Swiften/Serializer/XMPPSerializer.h b/Swiften/Serializer/XMPPSerializer.h new file mode 100644 index 0000000..38ba3ff --- /dev/null +++ b/Swiften/Serializer/XMPPSerializer.h @@ -0,0 +1,26 @@ +#pragma once + +#include <boost/shared_ptr.hpp> +#include <vector> + +#include "Swiften/Elements/Element.h" +#include "Swiften/Base/String.h" +#include "Swiften/Serializer/ElementSerializer.h" + +namespace Swift { + class PayloadSerializerCollection; + class CompressRequestSerializer; + class ProtocolHeader; + + class XMPPSerializer { + public: + XMPPSerializer(PayloadSerializerCollection*); + + String serializeHeader(const ProtocolHeader&) const; + String serializeElement(boost::shared_ptr<Element> stanza) const; + String serializeFooter() const; + + private: + std::vector< boost::shared_ptr<ElementSerializer> > serializers_; + }; +} diff --git a/Swiften/Server/ServerFromClientSession.cpp b/Swiften/Server/ServerFromClientSession.cpp new file mode 100644 index 0000000..c974e18 --- /dev/null +++ b/Swiften/Server/ServerFromClientSession.cpp @@ -0,0 +1,97 @@ +#include "Swiften/Server/ServerFromClientSession.h" + +#include <boost/bind.hpp> + +#include "Swiften/Elements/ProtocolHeader.h" +#include "Swiften/Server/UserRegistry.h" +#include "Swiften/Network/Connection.h" +#include "Swiften/StreamStack/XMPPLayer.h" +#include "Swiften/Elements/StreamFeatures.h" +#include "Swiften/Elements/ResourceBind.h" +#include "Swiften/Elements/StartSession.h" +#include "Swiften/Elements/IQ.h" +#include "Swiften/Elements/AuthSuccess.h" +#include "Swiften/Elements/AuthFailure.h" +#include "Swiften/Elements/AuthRequest.h" +#include "Swiften/SASL/PLAINMessage.h" + +namespace Swift { + +ServerFromClientSession::ServerFromClientSession( + const String& id, + boost::shared_ptr<Connection> connection, + PayloadParserFactoryCollection* payloadParserFactories, + PayloadSerializerCollection* payloadSerializers, + UserRegistry* userRegistry) : + Session(connection, payloadParserFactories, payloadSerializers), + id_(id), + userRegistry_(userRegistry), + authenticated_(false), + initialized(false) { +} + + +void ServerFromClientSession::handleElement(boost::shared_ptr<Element> element) { + if (isInitialized()) { + onElementReceived(element); + } + else { + if (AuthRequest* authRequest = dynamic_cast<AuthRequest*>(element.get())) { + if (authRequest->getMechanism() != "PLAIN") { + getXMPPLayer()->writeElement(boost::shared_ptr<AuthFailure>(new AuthFailure)); + finishSession(NoSupportedAuthMechanismsError); + } + else { + PLAINMessage plainMessage(authRequest->getMessage()); + if (userRegistry_->isValidUserPassword(JID(plainMessage.getAuthenticationID(), getLocalJID().getDomain()), plainMessage.getPassword())) { + getXMPPLayer()->writeElement(boost::shared_ptr<AuthSuccess>(new AuthSuccess())); + user_ = plainMessage.getAuthenticationID(); + authenticated_ = true; + getXMPPLayer()->resetParser(); + } + else { + getXMPPLayer()->writeElement(boost::shared_ptr<AuthFailure>(new AuthFailure)); + finishSession(AuthenticationFailedError); + } + } + } + else if (IQ* iq = dynamic_cast<IQ*>(element.get())) { + if (boost::shared_ptr<ResourceBind> resourceBind = iq->getPayload<ResourceBind>()) { + setRemoteJID(JID(user_, getLocalJID().getDomain(), resourceBind->getResource())); + boost::shared_ptr<ResourceBind> resultResourceBind(new ResourceBind()); + resultResourceBind->setJID(getRemoteJID()); + getXMPPLayer()->writeElement(IQ::createResult(JID(), iq->getID(), resultResourceBind)); + } + else if (iq->getPayload<StartSession>()) { + getXMPPLayer()->writeElement(IQ::createResult(getRemoteJID(), iq->getID())); + setInitialized(); + } + } + } +} + +void ServerFromClientSession::handleStreamStart(const ProtocolHeader& incomingHeader) { + setLocalJID(JID("", incomingHeader.getTo())); + ProtocolHeader header; + header.setFrom(incomingHeader.getTo()); + header.setID(id_); + getXMPPLayer()->writeHeader(header); + + boost::shared_ptr<StreamFeatures> features(new StreamFeatures()); + if (!authenticated_) { + features->addAuthenticationMechanism("PLAIN"); + } + else { + features->setHasResourceBind(); + features->setHasSession(); + } + getXMPPLayer()->writeElement(features); +} + +void ServerFromClientSession::setInitialized() { + initialized = true; + onSessionStarted(); +} + + +} diff --git a/Swiften/Server/ServerFromClientSession.h b/Swiften/Server/ServerFromClientSession.h new file mode 100644 index 0000000..33826a4 --- /dev/null +++ b/Swiften/Server/ServerFromClientSession.h @@ -0,0 +1,52 @@ +#pragma once + +#include <boost/shared_ptr.hpp> +#include <boost/signal.hpp> +#include <boost/enable_shared_from_this.hpp> + +#include "Swiften/Base/String.h" +#include "Swiften/Session/Session.h" +#include "Swiften/JID/JID.h" +#include "Swiften/Network/Connection.h" + +namespace Swift { + class ProtocolHeader; + class Element; + class Stanza; + class PayloadParserFactoryCollection; + class PayloadSerializerCollection; + class StreamStack; + class UserRegistry; + class XMPPLayer; + class ConnectionLayer; + class Connection; + class ByteArray; + + class ServerFromClientSession : public Session { + public: + ServerFromClientSession( + const String& id, + boost::shared_ptr<Connection> connection, + PayloadParserFactoryCollection* payloadParserFactories, + PayloadSerializerCollection* payloadSerializers, + UserRegistry* userRegistry); + + boost::signal<void ()> onSessionStarted; + + private: + void handleElement(boost::shared_ptr<Element>); + void handleStreamStart(const ProtocolHeader& header); + + void setInitialized(); + bool isInitialized() const { + return initialized; + } + + private: + String id_; + UserRegistry* userRegistry_; + bool authenticated_; + bool initialized; + String user_; + }; +} diff --git a/Swiften/Server/ServerSession.cpp b/Swiften/Server/ServerSession.cpp new file mode 100644 index 0000000..e62957c --- /dev/null +++ b/Swiften/Server/ServerSession.cpp @@ -0,0 +1,8 @@ +#include "Swiften/Server/ServerSession.h" + +namespace Swift { + +ServerSession::~ServerSession() { +} + +} diff --git a/Swiften/Server/ServerSession.h b/Swiften/Server/ServerSession.h new file mode 100644 index 0000000..1ebf68e --- /dev/null +++ b/Swiften/Server/ServerSession.h @@ -0,0 +1,17 @@ +#pragma once + +#include <boost/shared_ptr.hpp> + +#include "Swiften/Elements/Stanza.h" + +namespace Swift { + class ServerSession { + public: + virtual ~ServerSession(); + + virtual const JID& getJID() const = 0; + virtual int getPriority() const = 0; + + virtual void sendStanza(boost::shared_ptr<Stanza>) = 0; + }; +} diff --git a/Swiften/Server/ServerStanzaRouter.cpp b/Swiften/Server/ServerStanzaRouter.cpp new file mode 100644 index 0000000..5661de5 --- /dev/null +++ b/Swiften/Server/ServerStanzaRouter.cpp @@ -0,0 +1,67 @@ +#include "Swiften/Server/ServerStanzaRouter.h" +#include "Swiften/Server/ServerSession.h" + +#include <cassert> +#include <algorithm> + +namespace Swift { + +namespace { + struct PriorityLessThan { + bool operator()(const ServerSession* s1, const ServerSession* s2) const { + return s1->getPriority() < s2->getPriority(); + } + }; + + struct HasJID { + HasJID(const JID& jid) : jid(jid) {} + bool operator()(const ServerSession* session) const { + return session->getJID().equals(jid, JID::WithResource); + } + JID jid; + }; +} + +ServerStanzaRouter::ServerStanzaRouter() { +} + +bool ServerStanzaRouter::routeStanza(boost::shared_ptr<Stanza> stanza) { + JID to = stanza->getTo(); + assert(to.isValid()); + + // For a full JID, first try to route to a session with the full JID + if (!to.isBare()) { + std::vector<ServerSession*>::const_iterator i = std::find_if(clientSessions_.begin(), clientSessions_.end(), HasJID(to)); + if (i != clientSessions_.end()) { + (*i)->sendStanza(stanza); + return true; + } + } + + // Look for candidate sessions + to = to.toBare(); + std::vector<ServerSession*> candidateSessions; + for (std::vector<ServerSession*>::const_iterator i = clientSessions_.begin(); i != clientSessions_.end(); ++i) { + if ((*i)->getJID().equals(to, JID::WithoutResource) && (*i)->getPriority() >= 0) { + candidateSessions.push_back(*i); + } + } + if (candidateSessions.empty()) { + return false; + } + + // Find the session with the highest priority + std::vector<ServerSession*>::const_iterator i = std::max_element(clientSessions_.begin(), clientSessions_.end(), PriorityLessThan()); + (*i)->sendStanza(stanza); + return true; +} + +void ServerStanzaRouter::addClientSession(ServerSession* clientSession) { + clientSessions_.push_back(clientSession); +} + +void ServerStanzaRouter::removeClientSession(ServerSession* clientSession) { + clientSessions_.erase(std::remove(clientSessions_.begin(), clientSessions_.end(), clientSession), clientSessions_.end()); +} + +} diff --git a/Swiften/Server/ServerStanzaRouter.h b/Swiften/Server/ServerStanzaRouter.h new file mode 100644 index 0000000..057a2ea --- /dev/null +++ b/Swiften/Server/ServerStanzaRouter.h @@ -0,0 +1,24 @@ +#pragma once + +#include <boost/shared_ptr.hpp> +#include <map> + +#include "Swiften/JID/JID.h" +#include "Swiften/Elements/Stanza.h" + +namespace Swift { + class ServerSession; + + class ServerStanzaRouter { + public: + ServerStanzaRouter(); + + bool routeStanza(boost::shared_ptr<Stanza>); + + void addClientSession(ServerSession*); + void removeClientSession(ServerSession*); + + private: + std::vector<ServerSession*> clientSessions_; + }; +} diff --git a/Swiften/Server/SimpleUserRegistry.cpp b/Swiften/Server/SimpleUserRegistry.cpp new file mode 100644 index 0000000..1a6743a --- /dev/null +++ b/Swiften/Server/SimpleUserRegistry.cpp @@ -0,0 +1,17 @@ +#include "Swiften/Server/SimpleUserRegistry.h" + +namespace Swift { + +SimpleUserRegistry::SimpleUserRegistry() { +} + +bool SimpleUserRegistry::isValidUserPassword(const JID& user, const String& password) const { + std::map<JID,String>::const_iterator i = users.find(user); + return i != users.end() ? i->second == password : false; +} + +void SimpleUserRegistry::addUser(const JID& user, const String& password) { + users.insert(std::make_pair(user, password)); +} + +} diff --git a/Swiften/Server/SimpleUserRegistry.h b/Swiften/Server/SimpleUserRegistry.h new file mode 100644 index 0000000..253025d --- /dev/null +++ b/Swiften/Server/SimpleUserRegistry.h @@ -0,0 +1,22 @@ +#pragma once + +#include <map> + +#include "Swiften/JID/JID.h" +#include "Swiften/Base/String.h" +#include "Swiften/Server/UserRegistry.h" + +namespace Swift { + class String; + + class SimpleUserRegistry : public UserRegistry { + public: + SimpleUserRegistry(); + + virtual bool isValidUserPassword(const JID& user, const String& password) const; + void addUser(const JID& user, const String& password); + + private: + std::map<JID, String> users; + }; +} diff --git a/Swiften/Server/UnitTest/ServerStanzaRouterTest.cpp b/Swiften/Server/UnitTest/ServerStanzaRouterTest.cpp new file mode 100644 index 0000000..03a607a --- /dev/null +++ b/Swiften/Server/UnitTest/ServerStanzaRouterTest.cpp @@ -0,0 +1,144 @@ +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> + +#include "Swiften/Elements/Message.h" +#include "Swiften/Server/ServerStanzaRouter.h" +#include "Swiften/Server/ServerSession.h" + +using namespace Swift; + +class ServerStanzaRouterTest : public CppUnit::TestFixture { + CPPUNIT_TEST_SUITE(ServerStanzaRouterTest); + CPPUNIT_TEST(testRouteStanza_FullJID); + CPPUNIT_TEST(testRouteStanza_FullJIDWithNegativePriority); + CPPUNIT_TEST(testRouteStanza_FullJIDWithOnlyBareJIDMatchingSession); + CPPUNIT_TEST(testRouteStanza_BareJIDWithoutMatchingSession); + CPPUNIT_TEST(testRouteStanza_BareJIDWithMultipleSessions); + CPPUNIT_TEST(testRouteStanza_BareJIDWithOnlyNegativePriorities); + CPPUNIT_TEST(testRouteStanza_BareJIDWithChangingPresence); + CPPUNIT_TEST_SUITE_END(); + + public: + ServerStanzaRouterTest() {} + + void setUp() { + } + + void tearDown() { + } + + void testRouteStanza_FullJID() { + ServerStanzaRouter testling; + MockServerSession session1(JID("foo@bar.com/Bla"), 0); + testling.addClientSession(&session1); + MockServerSession session2(JID("foo@bar.com/Baz"), 0); + testling.addClientSession(&session2); + + bool result = testling.routeStanza(createMessageTo("foo@bar.com/Baz")); + + CPPUNIT_ASSERT(result); + CPPUNIT_ASSERT_EQUAL(0, static_cast<int>(session1.sentStanzas.size())); + CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(session2.sentStanzas.size())); + } + + void testRouteStanza_FullJIDWithNegativePriority() { + ServerStanzaRouter testling; + MockServerSession session1(JID("foo@bar.com/Bla"), -1); + testling.addClientSession(&session1); + MockServerSession session2(JID("foo@bar.com/Baz"), 0); + testling.addClientSession(&session2); + + bool result = testling.routeStanza(createMessageTo("foo@bar.com/Bla")); + + CPPUNIT_ASSERT(result); + CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(session1.sentStanzas.size())); + CPPUNIT_ASSERT_EQUAL(0, static_cast<int>(session2.sentStanzas.size())); + } + + void testRouteStanza_FullJIDWithOnlyBareJIDMatchingSession() { + ServerStanzaRouter testling; + MockServerSession session(JID("foo@bar.com/Bla"), 0); + testling.addClientSession(&session); + + bool result = testling.routeStanza(createMessageTo("foo@bar.com/Baz")); + + CPPUNIT_ASSERT(result); + CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(session.sentStanzas.size())); + } + + void testRouteStanza_BareJIDWithoutMatchingSession() { + ServerStanzaRouter testling; + + bool result = testling.routeStanza(createMessageTo("foo@bar.com")); + + CPPUNIT_ASSERT(!result); + } + + void testRouteStanza_BareJIDWithMultipleSessions() { + ServerStanzaRouter testling; + MockServerSession session1(JID("foo@bar.com/Bla"), 1); + testling.addClientSession(&session1); + MockServerSession session2(JID("foo@bar.com/Baz"), 8); + testling.addClientSession(&session2); + MockServerSession session3(JID("foo@bar.com/Bar"), 5); + testling.addClientSession(&session3); + + bool result = testling.routeStanza(createMessageTo("foo@bar.com")); + + CPPUNIT_ASSERT(result); + CPPUNIT_ASSERT_EQUAL(0, static_cast<int>(session1.sentStanzas.size())); + CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(session2.sentStanzas.size())); + CPPUNIT_ASSERT_EQUAL(0, static_cast<int>(session3.sentStanzas.size())); + } + + void testRouteStanza_BareJIDWithOnlyNegativePriorities() { + ServerStanzaRouter testling; + MockServerSession session(JID("foo@bar.com/Bla"), -1); + testling.addClientSession(&session); + + bool result = testling.routeStanza(createMessageTo("foo@bar.com")); + + CPPUNIT_ASSERT(!result); + } + + void testRouteStanza_BareJIDWithChangingPresence() { + ServerStanzaRouter testling; + MockServerSession session1(JID("foo@bar.com/Baz"), 8); + testling.addClientSession(&session1); + MockServerSession session2(JID("foo@bar.com/Bar"), 5); + testling.addClientSession(&session2); + + session1.priority = 3; + session2.priority = 4; + bool result = testling.routeStanza(createMessageTo("foo@bar.com")); + + CPPUNIT_ASSERT(result); + CPPUNIT_ASSERT_EQUAL(0, static_cast<int>(session1.sentStanzas.size())); + CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(session2.sentStanzas.size())); + } + + private: + boost::shared_ptr<Message> createMessageTo(const String& recipient) { + boost::shared_ptr<Message> message(new Message()); + message->setTo(JID(recipient)); + return message; + } + + class MockServerSession : public ServerSession { + public: + MockServerSession(const JID& jid, int priority) : jid(jid), priority(priority) {} + + virtual const JID& getJID() const { return jid; } + virtual int getPriority() const { return priority; } + + virtual void sendStanza(boost::shared_ptr<Stanza> stanza) { + sentStanzas.push_back(stanza); + } + + JID jid; + int priority; + std::vector< boost::shared_ptr<Stanza> > sentStanzas; + }; +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(ServerStanzaRouterTest); diff --git a/Swiften/Server/UserRegistry.cpp b/Swiften/Server/UserRegistry.cpp new file mode 100644 index 0000000..d1a509f --- /dev/null +++ b/Swiften/Server/UserRegistry.cpp @@ -0,0 +1,8 @@ +#include "Swiften/Server/UserRegistry.h" + +namespace Swift { + +UserRegistry::~UserRegistry() { +} + +} diff --git a/Swiften/Server/UserRegistry.h b/Swiften/Server/UserRegistry.h new file mode 100644 index 0000000..5ced019 --- /dev/null +++ b/Swiften/Server/UserRegistry.h @@ -0,0 +1,13 @@ +#pragma once + +namespace Swift { + class String; + class JID; + + class UserRegistry { + public: + virtual ~UserRegistry(); + + virtual bool isValidUserPassword(const JID& user, const String& password) const = 0; + }; +} diff --git a/Swiften/Session/BasicSessionStream.cpp b/Swiften/Session/BasicSessionStream.cpp new file mode 100644 index 0000000..ed7f1eb --- /dev/null +++ b/Swiften/Session/BasicSessionStream.cpp @@ -0,0 +1,140 @@ +#include "Swiften/Session/BasicSessionStream.h" + +#include <boost/bind.hpp> + +#include "Swiften/StreamStack/XMPPLayer.h" +#include "Swiften/StreamStack/StreamStack.h" +#include "Swiften/StreamStack/ConnectionLayer.h" +#include "Swiften/StreamStack/WhitespacePingLayer.h" +#include "Swiften/StreamStack/CompressionLayer.h" +#include "Swiften/StreamStack/TLSLayer.h" +#include "Swiften/StreamStack/TLSLayerFactory.h" + +namespace Swift { + +BasicSessionStream::BasicSessionStream(boost::shared_ptr<Connection> connection, PayloadParserFactoryCollection* payloadParserFactories, PayloadSerializerCollection* payloadSerializers, TLSLayerFactory* tlsLayerFactory, TimerFactory* timerFactory) : available(false), connection(connection), payloadParserFactories(payloadParserFactories), payloadSerializers(payloadSerializers), tlsLayerFactory(tlsLayerFactory), timerFactory(timerFactory) { +} + +void BasicSessionStream::initialize() { + xmppLayer = boost::shared_ptr<XMPPLayer>( + new XMPPLayer(payloadParserFactories, payloadSerializers)); + xmppLayer->onStreamStart.connect(boost::bind(&BasicSessionStream::handleStreamStartReceived, shared_from_this(), _1)); + xmppLayer->onElement.connect(boost::bind(&BasicSessionStream::handleElementReceived, shared_from_this(), _1)); + xmppLayer->onError.connect(boost::bind( + &BasicSessionStream::handleXMPPError, shared_from_this())); + xmppLayer->onDataRead.connect(boost::bind(&BasicSessionStream::handleDataRead, shared_from_this(), _1)); + xmppLayer->onWriteData.connect(boost::bind(&BasicSessionStream::handleDataWritten, shared_from_this(), _1)); + + connection->onDisconnected.connect(boost::bind(&BasicSessionStream::handleConnectionError, shared_from_this(), _1)); + connectionLayer = boost::shared_ptr<ConnectionLayer>( + new ConnectionLayer(connection)); + + streamStack = new StreamStack(xmppLayer, connectionLayer); + + available = true; +} + +BasicSessionStream::~BasicSessionStream() { + delete streamStack; +} + +void BasicSessionStream::writeHeader(const ProtocolHeader& header) { + assert(available); + xmppLayer->writeHeader(header); +} + +void BasicSessionStream::writeElement(boost::shared_ptr<Element> element) { + assert(available); + xmppLayer->writeElement(element); +} + +void BasicSessionStream::writeFooter() { + assert(available); + xmppLayer->writeFooter(); +} + +bool BasicSessionStream::isAvailable() { + return available; +} + +bool BasicSessionStream::supportsTLSEncryption() { + return tlsLayerFactory && tlsLayerFactory->canCreate(); +} + +void BasicSessionStream::addTLSEncryption() { + assert(available); + tlsLayer = tlsLayerFactory->createTLSLayer(); + if (hasTLSCertificate() && !tlsLayer->setClientCertificate(getTLSCertificate())) { + onError(boost::shared_ptr<Error>(new Error(Error::InvalidTLSCertificateError))); + } + else { + streamStack->addLayer(tlsLayer); + tlsLayer->onError.connect(boost::bind(&BasicSessionStream::handleTLSError, shared_from_this())); + tlsLayer->onConnected.connect(boost::bind(&BasicSessionStream::handleTLSConnected, shared_from_this())); + tlsLayer->connect(); + } +} + +void BasicSessionStream::addZLibCompression() { + boost::shared_ptr<CompressionLayer> compressionLayer(new CompressionLayer()); + streamStack->addLayer(compressionLayer); +} + +void BasicSessionStream::setWhitespacePingEnabled(bool enabled) { + if (enabled) { + if (!whitespacePingLayer) { + whitespacePingLayer = boost::shared_ptr<WhitespacePingLayer>(new WhitespacePingLayer(timerFactory)); + streamStack->addLayer(whitespacePingLayer); + } + whitespacePingLayer->setActive(); + } + else if (whitespacePingLayer) { + whitespacePingLayer->setInactive(); + } +} + +void BasicSessionStream::resetXMPPParser() { + xmppLayer->resetParser(); +} + +void BasicSessionStream::handleStreamStartReceived(const ProtocolHeader& header) { + onStreamStartReceived(header); +} + +void BasicSessionStream::handleElementReceived(boost::shared_ptr<Element> element) { + onElementReceived(element); +} + +void BasicSessionStream::handleXMPPError() { + available = false; + onError(boost::shared_ptr<Error>(new Error(Error::ParseError))); +} + +void BasicSessionStream::handleTLSConnected() { + onTLSEncrypted(); +} + +void BasicSessionStream::handleTLSError() { + available = false; + onError(boost::shared_ptr<Error>(new Error(Error::TLSError))); +} + +void BasicSessionStream::handleConnectionError(const boost::optional<Connection::Error>& error) { + available = false; + if (error == Connection::ReadError) { + onError(boost::shared_ptr<Error>(new Error(Error::ConnectionReadError))); + } + else { + onError(boost::shared_ptr<Error>(new Error(Error::ConnectionWriteError))); + } +} + +void BasicSessionStream::handleDataRead(const ByteArray& data) { + onDataRead(String(data.getData(), data.getSize())); +} + +void BasicSessionStream::handleDataWritten(const ByteArray& data) { + onDataWritten(String(data.getData(), data.getSize())); +} + +}; diff --git a/Swiften/Session/BasicSessionStream.h b/Swiften/Session/BasicSessionStream.h new file mode 100644 index 0000000..8618458 --- /dev/null +++ b/Swiften/Session/BasicSessionStream.h @@ -0,0 +1,75 @@ +#pragma once + +#include <boost/shared_ptr.hpp> +#include <boost/enable_shared_from_this.hpp> + +#include "Swiften/Network/Connection.h" +#include "Swiften/Session/SessionStream.h" + +namespace Swift { + class TLSLayerFactory; + class TLSLayer; + class TimerFactory; + class WhitespacePingLayer; + class PayloadParserFactoryCollection; + class PayloadSerializerCollection; + class StreamStack; + class XMPPLayer; + class ConnectionLayer; + class CompressionLayer; + + class BasicSessionStream : + public SessionStream, + public boost::enable_shared_from_this<BasicSessionStream> { + public: + BasicSessionStream( + boost::shared_ptr<Connection> connection, + PayloadParserFactoryCollection* payloadParserFactories, + PayloadSerializerCollection* payloadSerializers, + TLSLayerFactory* tlsLayerFactory, + TimerFactory* whitespacePingLayerFactory + ); + ~BasicSessionStream(); + + void initialize(); + + virtual bool isAvailable(); + + virtual void writeHeader(const ProtocolHeader& header); + virtual void writeElement(boost::shared_ptr<Element>); + virtual void writeFooter(); + + virtual void addZLibCompression(); + + virtual bool supportsTLSEncryption(); + virtual void addTLSEncryption(); + + virtual void setWhitespacePingEnabled(bool); + + virtual void resetXMPPParser(); + + private: + void handleConnectionError(const boost::optional<Connection::Error>& error); + void handleXMPPError(); + void handleTLSConnected(); + void handleTLSError(); + void handleStreamStartReceived(const ProtocolHeader&); + void handleElementReceived(boost::shared_ptr<Element>); + void handleDataRead(const ByteArray& data); + void handleDataWritten(const ByteArray& data); + + private: + bool available; + boost::shared_ptr<Connection> connection; + PayloadParserFactoryCollection* payloadParserFactories; + PayloadSerializerCollection* payloadSerializers; + TLSLayerFactory* tlsLayerFactory; + TimerFactory* timerFactory; + boost::shared_ptr<XMPPLayer> xmppLayer; + boost::shared_ptr<ConnectionLayer> connectionLayer; + StreamStack* streamStack; + boost::shared_ptr<CompressionLayer> compressionLayer; + boost::shared_ptr<TLSLayer> tlsLayer; + boost::shared_ptr<WhitespacePingLayer> whitespacePingLayer; + }; +} diff --git a/Swiften/Session/Session.cpp b/Swiften/Session/Session.cpp new file mode 100644 index 0000000..64456db --- /dev/null +++ b/Swiften/Session/Session.cpp @@ -0,0 +1,82 @@ +#include "Swiften/Session/Session.h" + +#include <boost/bind.hpp> + +#include "Swiften/StreamStack/XMPPLayer.h" +#include "Swiften/StreamStack/StreamStack.h" + +namespace Swift { + +Session::Session( + boost::shared_ptr<Connection> connection, + PayloadParserFactoryCollection* payloadParserFactories, + PayloadSerializerCollection* payloadSerializers) : + connection(connection), + payloadParserFactories(payloadParserFactories), + payloadSerializers(payloadSerializers), + streamStack(0), + finishing(false) { +} + +Session::~Session() { + delete streamStack; +} + +void Session::startSession() { + initializeStreamStack(); + handleSessionStarted(); +} + +void Session::finishSession() { + finishing = true; + connection->disconnect(); + handleSessionFinished(boost::optional<SessionError>()); + finishing = false; + onSessionFinished(boost::optional<SessionError>()); +} + +void Session::finishSession(const SessionError& error) { + finishing = true; + connection->disconnect(); + handleSessionFinished(boost::optional<SessionError>(error)); + finishing = false; + onSessionFinished(boost::optional<SessionError>(error)); +} + +void Session::initializeStreamStack() { + xmppLayer = boost::shared_ptr<XMPPLayer>( + new XMPPLayer(payloadParserFactories, payloadSerializers)); + xmppLayer->onStreamStart.connect( + boost::bind(&Session::handleStreamStart, shared_from_this(), _1)); + xmppLayer->onElement.connect(boost::bind(&Session::handleElement, shared_from_this(), _1)); + xmppLayer->onError.connect( + boost::bind(&Session::finishSession, shared_from_this(), XMLError)); + xmppLayer->onDataRead.connect(boost::bind(boost::ref(onDataRead), _1)); + xmppLayer->onWriteData.connect(boost::bind(boost::ref(onDataWritten), _1)); + connection->onDisconnected.connect( + boost::bind(&Session::handleDisconnected, shared_from_this(), _1)); + connectionLayer = boost::shared_ptr<ConnectionLayer>(new ConnectionLayer(connection)); + streamStack = new StreamStack(xmppLayer, connectionLayer); +} + +void Session::sendElement(boost::shared_ptr<Element> stanza) { + xmppLayer->writeElement(stanza); +} + +void Session::handleDisconnected(const boost::optional<Connection::Error>& connectionError) { + if (connectionError) { + switch (*connectionError) { + case Connection::ReadError: + finishSession(ConnectionReadError); + break; + case Connection::WriteError: + finishSession(ConnectionWriteError); + break; + } + } + else { + finishSession(); + } +} + +} diff --git a/Swiften/Session/Session.h b/Swiften/Session/Session.h new file mode 100644 index 0000000..d63254a --- /dev/null +++ b/Swiften/Session/Session.h @@ -0,0 +1,105 @@ +#pragma once + +#include <boost/shared_ptr.hpp> +#include <boost/signal.hpp> +#include <boost/optional.hpp> +#include <boost/enable_shared_from_this.hpp> + +#include "Swiften/JID/JID.h" +#include "Swiften/Elements/Element.h" +#include "Swiften/Network/Connection.h" +#include "Swiften/StreamStack/ConnectionLayer.h" + +namespace Swift { + class ProtocolHeader; + class StreamStack; + class JID; + class Element; + class ByteArray; + class PayloadParserFactoryCollection; + class PayloadSerializerCollection; + class XMPPLayer; + + class Session : public boost::enable_shared_from_this<Session> { + public: + enum SessionError { + ConnectionReadError, + ConnectionWriteError, + XMLError, + AuthenticationFailedError, + NoSupportedAuthMechanismsError, + UnexpectedElementError, + ResourceBindError, + SessionStartError, + TLSError, + ClientCertificateLoadError, + ClientCertificateError + }; + + Session( + boost::shared_ptr<Connection> connection, + PayloadParserFactoryCollection* payloadParserFactories, + PayloadSerializerCollection* payloadSerializers); + virtual ~Session(); + + void startSession(); + void finishSession(); + + void sendElement(boost::shared_ptr<Element>); + + const JID& getLocalJID() const { + return localJID; + } + + const JID& getRemoteJID() const { + return remoteJID; + } + + boost::signal<void (boost::shared_ptr<Element>)> onElementReceived; + boost::signal<void (const boost::optional<SessionError>&)> onSessionFinished; + boost::signal<void (const ByteArray&)> onDataWritten; + boost::signal<void (const ByteArray&)> onDataRead; + + protected: + void setRemoteJID(const JID& j) { + remoteJID = j; + } + + void setLocalJID(const JID& j) { + localJID = j; + } + + void finishSession(const SessionError&); + + virtual void handleSessionStarted() {} + virtual void handleSessionFinished(const boost::optional<SessionError>&) {} + virtual void handleElement(boost::shared_ptr<Element>) = 0; + virtual void handleStreamStart(const ProtocolHeader&) = 0; + + void initializeStreamStack(); + + boost::shared_ptr<XMPPLayer> getXMPPLayer() const { + return xmppLayer; + } + + StreamStack* getStreamStack() const { + return streamStack; + } + + void setFinished(); + + private: + void handleDisconnected(const boost::optional<Connection::Error>& error); + + private: + JID localJID; + JID remoteJID; + boost::shared_ptr<Connection> connection; + PayloadParserFactoryCollection* payloadParserFactories; + PayloadSerializerCollection* payloadSerializers; + boost::shared_ptr<XMPPLayer> xmppLayer; + boost::shared_ptr<ConnectionLayer> connectionLayer; + StreamStack* streamStack; + bool finishing; + }; +} diff --git a/Swiften/Session/SessionStream.cpp b/Swiften/Session/SessionStream.cpp new file mode 100644 index 0000000..1d73d0f --- /dev/null +++ b/Swiften/Session/SessionStream.cpp @@ -0,0 +1,8 @@ +#include "Swiften/Session/SessionStream.h" + +namespace Swift { + +SessionStream::~SessionStream() { +} + +}; diff --git a/Swiften/Session/SessionStream.h b/Swiften/Session/SessionStream.h new file mode 100644 index 0000000..8c64ccf --- /dev/null +++ b/Swiften/Session/SessionStream.h @@ -0,0 +1,69 @@ +#pragma once + +#include <boost/signal.hpp> +#include <boost/shared_ptr.hpp> + +#include "Swiften/Elements/ProtocolHeader.h" +#include "Swiften/Elements/Element.h" +#include "Swiften/Base/Error.h" +#include "Swiften/TLS/PKCS12Certificate.h" + +namespace Swift { + class SessionStream { + public: + class Error : public Swift::Error { + public: + enum Type { + ParseError, + TLSError, + InvalidTLSCertificateError, + ConnectionReadError, + ConnectionWriteError + }; + + Error(Type type) : type(type) {} + + Type type; + }; + + virtual ~SessionStream(); + + virtual bool isAvailable() = 0; + + virtual void writeHeader(const ProtocolHeader& header) = 0; + virtual void writeFooter() = 0; + virtual void writeElement(boost::shared_ptr<Element>) = 0; + + virtual void addZLibCompression() = 0; + + virtual bool supportsTLSEncryption() = 0; + virtual void addTLSEncryption() = 0; + virtual void setWhitespacePingEnabled(bool enabled) = 0; + + virtual void resetXMPPParser() = 0; + + void setTLSCertificate(const PKCS12Certificate& cert) { + certificate = cert; + } + + virtual bool hasTLSCertificate() { + return !certificate.isNull(); + } + + + boost::signal<void (const ProtocolHeader&)> onStreamStartReceived; + boost::signal<void (boost::shared_ptr<Element>)> onElementReceived; + boost::signal<void (boost::shared_ptr<Error>)> onError; + boost::signal<void ()> onTLSEncrypted; + boost::signal<void (const String&)> onDataRead; + boost::signal<void (const String&)> onDataWritten; + + protected: + const PKCS12Certificate& getTLSCertificate() const { + return certificate; + } + + private: + PKCS12Certificate certificate; + }; +} diff --git a/Swiften/Session/SessionTracer.h b/Swiften/Session/SessionTracer.h new file mode 100644 index 0000000..29a07e0 --- /dev/null +++ b/Swiften/Session/SessionTracer.h @@ -0,0 +1,29 @@ +#pragma once + +#include <iostream> + +#include "Swiften/Session/Session.h" +#include "Swiften/Base/String.h" +#include "Swiften/Base/ByteArray.h" + +namespace Swift { + class SessionTracer { + public: + SessionTracer(boost::shared_ptr<Session> session) : session(session) { + session->onDataRead.connect(boost::bind(&SessionTracer::printData, this, '<', _1)); + session->onDataWritten.connect(boost::bind(&SessionTracer::printData, this, '>', _1)); + } + + private: + void printData(char direction, const ByteArray& data) { + std::cerr << direction << direction << " " << session->getLocalJID() << " "; + for (unsigned int i = 0; i < 72 - session->getLocalJID().toString().getLength() - session->getRemoteJID().toString().getLength(); ++i) { + std::cerr << direction; + } + std::cerr << " " << session->getRemoteJID()<< " " << direction << direction << std::endl; + std::cerr << String(data.getData(), data.getSize()) << std::endl; + } + + boost::shared_ptr<Session> session; + }; +} diff --git a/Swiften/Settings/SettingsProvider.h b/Swiften/Settings/SettingsProvider.h new file mode 100644 index 0000000..d07e790 --- /dev/null +++ b/Swiften/Settings/SettingsProvider.h @@ -0,0 +1,24 @@ +#ifndef SWIFTEN_SettingsProvider_H +#define SWIFTEN_SettingsProvider_H + +#include "Swiften/Base/String.h" + +#include <vector> + +namespace Swift { + +class SettingsProvider { + public: + virtual ~SettingsProvider() {} + virtual String getStringSetting(const String &settingPath) = 0; + virtual void storeString(const String &settingPath, const String &settingValue) = 0; + virtual bool getBoolSetting(const String &settingPath, bool defaultValue) = 0; + virtual void storeBool(const String &settingPath, bool settingValue) = 0; + virtual std::vector<String> getAvailableProfiles() = 0; + virtual void createProfile(const String& profile) = 0; +}; + +} +#endif + + diff --git a/Swiften/StreamStack/CompressionLayer.h b/Swiften/StreamStack/CompressionLayer.h new file mode 100644 index 0000000..6a8f2b3 --- /dev/null +++ b/Swiften/StreamStack/CompressionLayer.h @@ -0,0 +1,48 @@ +#ifndef SWIFTEN_COMPRESSIONLAYER_H +#define SWIFTEN_COMPRESSIONLAYER_H + +#include <boost/noncopyable.hpp> +#include <boost/signal.hpp> + +#include "Swiften/Base/ByteArray.h" +#include "Swiften/StreamStack/StreamLayer.h" +#include "Swiften/Compress/ZLibException.h" +#include "Swiften/Compress/ZLibCompressor.h" +#include "Swiften/Compress/ZLibDecompressor.h" + +namespace Swift { + class ZLibCompressor; + class ZLibDecompressor; + + class CompressionLayer : public StreamLayer, boost::noncopyable { + public: + CompressionLayer() {} + + virtual void writeData(const ByteArray& data) { + try { + onWriteData(compressor_.process(data)); + } + catch (const ZLibException& e) { + onError(); + } + } + + virtual void handleDataRead(const ByteArray& data) { + try { + onDataRead(decompressor_.process(data)); + } + catch (const ZLibException& e) { + onError(); + } + } + + public: + boost::signal<void ()> onError; + + private: + ZLibCompressor compressor_; + ZLibDecompressor decompressor_; + }; +} + +#endif diff --git a/Swiften/StreamStack/ConnectionLayer.h b/Swiften/StreamStack/ConnectionLayer.h new file mode 100644 index 0000000..7688f78 --- /dev/null +++ b/Swiften/StreamStack/ConnectionLayer.h @@ -0,0 +1,23 @@ +#pragma once + +#include <boost/signal.hpp> +#include <boost/shared_ptr.hpp> + +#include "Swiften/StreamStack/LowLayer.h" +#include "Swiften/Network/Connection.h" + +namespace Swift { + class ConnectionLayer : public LowLayer { + public: + ConnectionLayer(boost::shared_ptr<Connection> connection) : connection(connection) { + connection->onDataRead.connect(onDataRead); + } + + void writeData(const ByteArray& data) { + connection->write(data); + } + + private: + boost::shared_ptr<Connection> connection; + }; +} diff --git a/Swiften/StreamStack/HighLayer.cpp b/Swiften/StreamStack/HighLayer.cpp new file mode 100644 index 0000000..2f5e1df --- /dev/null +++ b/Swiften/StreamStack/HighLayer.cpp @@ -0,0 +1,8 @@ +#include "Swiften/StreamStack/HighLayer.h" + +namespace Swift { + +HighLayer::~HighLayer() { +} + +} diff --git a/Swiften/StreamStack/HighLayer.h b/Swiften/StreamStack/HighLayer.h new file mode 100644 index 0000000..bd6c6e6 --- /dev/null +++ b/Swiften/StreamStack/HighLayer.h @@ -0,0 +1,19 @@ +#ifndef SWIFTEN_HIGHLAYER_H +#define SWIFTEN_HIGHLAYER_H + +#include <boost/signal.hpp> + +#include "Swiften/Base/ByteArray.h" + +namespace Swift { + class HighLayer { + public: + virtual ~HighLayer(); + + virtual void handleDataRead(const ByteArray& data) = 0; + + boost::signal<void (const ByteArray&)> onWriteData; + }; +} + +#endif diff --git a/Swiften/StreamStack/LowLayer.cpp b/Swiften/StreamStack/LowLayer.cpp new file mode 100644 index 0000000..24aa7a2 --- /dev/null +++ b/Swiften/StreamStack/LowLayer.cpp @@ -0,0 +1,8 @@ +#include "Swiften/StreamStack/LowLayer.h" + +namespace Swift { + +LowLayer::~LowLayer() { +} + +} diff --git a/Swiften/StreamStack/LowLayer.h b/Swiften/StreamStack/LowLayer.h new file mode 100644 index 0000000..763cfc4 --- /dev/null +++ b/Swiften/StreamStack/LowLayer.h @@ -0,0 +1,19 @@ +#ifndef SWIFTEN_LOWLAYER_H +#define SWIFTEN_LOWLAYER_H + +#include <boost/signal.hpp> + +#include "Swiften/Base/ByteArray.h" + +namespace Swift { + class LowLayer { + public: + virtual ~LowLayer(); + + virtual void writeData(const ByteArray& data) = 0; + + boost::signal<void (const ByteArray&)> onDataRead; + }; +} + +#endif diff --git a/Swiften/StreamStack/OpenSSLLayer.cpp b/Swiften/StreamStack/OpenSSLLayer.cpp new file mode 100644 index 0000000..da93e4e --- /dev/null +++ b/Swiften/StreamStack/OpenSSLLayer.cpp @@ -0,0 +1,28 @@ +#include "Swiften/StreamStack/OpenSSLLayer.h" + +namespace Swift { + +OpenSSLLayer::OpenSSLLayer() { + context_.onDataForNetwork.connect(onWriteData); + context_.onDataForApplication.connect(onDataRead); + context_.onConnected.connect(onConnected); + context_.onError.connect(onError); +} + +void OpenSSLLayer::connect() { + context_.connect(); +} + +void OpenSSLLayer::writeData(const ByteArray& data) { + context_.handleDataFromApplication(data); +} + +void OpenSSLLayer::handleDataRead(const ByteArray& data) { + context_.handleDataFromNetwork(data); +} + +bool OpenSSLLayer::setClientCertificate(const PKCS12Certificate& certificate) { + return context_.setClientCertificate(certificate); +} + +} diff --git a/Swiften/StreamStack/OpenSSLLayer.h b/Swiften/StreamStack/OpenSSLLayer.h new file mode 100644 index 0000000..615c75e --- /dev/null +++ b/Swiften/StreamStack/OpenSSLLayer.h @@ -0,0 +1,27 @@ +#ifndef SWIFTEN_OpenSSLLayer_H +#define SWIFTEN_OpenSSLLayer_H + +#include <boost/noncopyable.hpp> +#include <boost/signal.hpp> + +#include "Swiften/Base/ByteArray.h" +#include "Swiften/StreamStack/TLSLayer.h" +#include "Swiften/TLS/OpenSSL/OpenSSLContext.h" + +namespace Swift { + class OpenSSLLayer : public TLSLayer, boost::noncopyable { + public: + OpenSSLLayer(); + + virtual void connect(); + virtual bool setClientCertificate(const PKCS12Certificate&); + + virtual void writeData(const ByteArray& data); + virtual void handleDataRead(const ByteArray& data); + + private: + OpenSSLContext context_; + }; +} + +#endif diff --git a/Swiften/StreamStack/PlatformTLSLayerFactory.cpp b/Swiften/StreamStack/PlatformTLSLayerFactory.cpp new file mode 100644 index 0000000..cdcb8a7 --- /dev/null +++ b/Swiften/StreamStack/PlatformTLSLayerFactory.cpp @@ -0,0 +1,31 @@ +#include "Swiften/StreamStack/PlatformTLSLayerFactory.h" + +#include <cassert> + +#ifdef HAVE_OPENSSL +#include "Swiften/StreamStack/OpenSSLLayer.h" +#endif + +namespace Swift { + +PlatformTLSLayerFactory::PlatformTLSLayerFactory() { +} + +bool PlatformTLSLayerFactory::canCreate() const { +#ifdef HAVE_OPENSSL + return true; +#else + return false; +#endif +} + +boost::shared_ptr<TLSLayer> PlatformTLSLayerFactory::createTLSLayer() { +#ifdef HAVE_OPENSSL + return boost::shared_ptr<TLSLayer>(new OpenSSLLayer()); +#else + assert(false); + return boost::shared_ptr<TLSLayer>(); +#endif +} + +} diff --git a/Swiften/StreamStack/PlatformTLSLayerFactory.h b/Swiften/StreamStack/PlatformTLSLayerFactory.h new file mode 100644 index 0000000..6c343b0 --- /dev/null +++ b/Swiften/StreamStack/PlatformTLSLayerFactory.h @@ -0,0 +1,13 @@ +#pragma once + +#include "Swiften/StreamStack/TLSLayerFactory.h" + +namespace Swift { + class PlatformTLSLayerFactory : public TLSLayerFactory { + public: + PlatformTLSLayerFactory(); + + bool canCreate() const; + virtual boost::shared_ptr<TLSLayer> createTLSLayer(); + }; +} diff --git a/Swiften/StreamStack/SConscript b/Swiften/StreamStack/SConscript new file mode 100644 index 0000000..449a39b --- /dev/null +++ b/Swiften/StreamStack/SConscript @@ -0,0 +1,21 @@ +Import("swiften_env") + +myenv = swiften_env.Clone() +myenv.MergeFlags(swiften_env["OPENSSL_FLAGS"]) + +sources = [ + "HighLayer.cpp", + "LowLayer.cpp", + "PlatformTLSLayerFactory.cpp", + "StreamStack.cpp", + "TLSLayerFactory.cpp", + "WhitespacePingLayer.cpp", + "XMPPLayer.cpp", + ] + +if myenv.get("HAVE_OPENSSL", 0) : + myenv.Append(CPPDEFINES = "HAVE_OPENSSL") + sources += ["OpenSSLLayer.cpp"] + +objects = myenv.StaticObject(sources) +swiften_env.Append(SWIFTEN_OBJECTS = [objects]) diff --git a/Swiften/StreamStack/StreamLayer.h b/Swiften/StreamStack/StreamLayer.h new file mode 100644 index 0000000..6ea2051 --- /dev/null +++ b/Swiften/StreamStack/StreamLayer.h @@ -0,0 +1,16 @@ +#ifndef SWIFTEN_STREAMLAYER_H +#define SWIFTEN_STREAMLAYER_H + +#include <boost/signal.hpp> + +#include "Swiften/StreamStack/LowLayer.h" +#include "Swiften/StreamStack/HighLayer.h" + +namespace Swift { + class StreamLayer : public LowLayer, public HighLayer { + public: + StreamLayer() {} + }; +} + +#endif diff --git a/Swiften/StreamStack/StreamStack.cpp b/Swiften/StreamStack/StreamStack.cpp new file mode 100644 index 0000000..9eb9b4e --- /dev/null +++ b/Swiften/StreamStack/StreamStack.cpp @@ -0,0 +1,40 @@ +#include "Swiften/StreamStack/StreamStack.h" + +#include <boost/bind.hpp> + +#include "Swiften/Base/foreach.h" +#include "Swiften/StreamStack/XMPPLayer.h" +#include "Swiften/StreamStack/LowLayer.h" +#include "Swiften/StreamStack/StreamLayer.h" + +namespace Swift { + +StreamStack::StreamStack(boost::shared_ptr<XMPPLayer> xmppLayer, boost::shared_ptr<LowLayer> physicalLayer) : xmppLayer_(xmppLayer), physicalLayer_(physicalLayer) { + xmppReadSlotConnection_ = physicalLayer_->onDataRead.connect(boost::bind(&XMPPLayer::parseData, xmppLayer_, _1)); + xmppWriteSignalConnection_ = xmppLayer_->onWriteData.connect(boost::bind(&LowLayer::writeData, physicalLayer_, _1)); +} + +StreamStack::~StreamStack() { + // Disconnect the write signal connections to break cyclic signal + // dependencies. The read signal connections have + // to remain, since these can be reached from the main event loop. + xmppWriteSignalConnection_.disconnect(); + foreach(const boost::bsignals::connection& connection, writeSignalConnections_) { + connection.disconnect(); + } +} + +void StreamStack::addLayer(boost::shared_ptr<StreamLayer> newLayer) { + xmppReadSlotConnection_.disconnect(); + xmppWriteSignalConnection_.disconnect(); + + boost::shared_ptr<LowLayer> lowLayer = (layers_.empty() ? physicalLayer_ : *layers_.rbegin()); + + lowLayer->onDataRead.connect(boost::bind(&HighLayer::handleDataRead, newLayer, _1), boost::bsignals::at_front); + writeSignalConnections_.push_back(newLayer->onWriteData.connect(boost::bind(&LowLayer::writeData, lowLayer, _1), boost::bsignals::at_front)); + xmppWriteSignalConnection_ = xmppLayer_->onWriteData.connect(boost::bind(&LowLayer::writeData, newLayer, _1), boost::bsignals::at_front); + xmppReadSlotConnection_ = newLayer->onDataRead.connect(boost::bind(&XMPPLayer::parseData, xmppLayer_, _1), boost::bsignals::at_front); + layers_.push_back(newLayer); +} + +} diff --git a/Swiften/StreamStack/StreamStack.h b/Swiften/StreamStack/StreamStack.h new file mode 100644 index 0000000..87c522d --- /dev/null +++ b/Swiften/StreamStack/StreamStack.h @@ -0,0 +1,44 @@ +#pragma once + +#include <boost/shared_ptr.hpp> +#include <boost/signal.hpp> +#include <vector> + +#include "Swiften/Elements/Stanza.h" +#include "Swiften/Base/foreach.h" + +namespace Swift { + class XMPPLayer; + class LowLayer; + class StreamLayer; + + class StreamStack { + public: + StreamStack(boost::shared_ptr<XMPPLayer> xmppLayer, boost::shared_ptr<LowLayer> physicalLayer); + ~StreamStack(); + + void addLayer(boost::shared_ptr<StreamLayer>); + + boost::shared_ptr<XMPPLayer> getXMPPLayer() const { + return xmppLayer_; + } + + template<typename T> boost::shared_ptr<T> getLayer() { + foreach(const boost::shared_ptr<StreamLayer>& streamLayer, layers_) { + boost::shared_ptr<T> layer = boost::dynamic_pointer_cast<T>(streamLayer); + if (layer) { + return layer; + } + } + return boost::shared_ptr<T>(); + } + + private: + boost::shared_ptr<XMPPLayer> xmppLayer_; + boost::shared_ptr<LowLayer> physicalLayer_; + std::vector< boost::shared_ptr<StreamLayer> > layers_; + boost::bsignals::connection xmppReadSlotConnection_; + boost::bsignals::connection xmppWriteSignalConnection_; + std::vector< boost::bsignals::connection > writeSignalConnections_; + }; +} diff --git a/Swiften/StreamStack/TLSLayer.h b/Swiften/StreamStack/TLSLayer.h new file mode 100644 index 0000000..490e16c --- /dev/null +++ b/Swiften/StreamStack/TLSLayer.h @@ -0,0 +1,19 @@ +#ifndef SWIFTEN_TLSLayer_H +#define SWIFTEN_TLSLayer_H + +#include "Swiften/StreamStack/StreamLayer.h" +#include "Swiften/TLS/PKCS12Certificate.h" + +namespace Swift { + class TLSLayer : public StreamLayer { + public: + virtual void connect() = 0; + virtual bool setClientCertificate(const PKCS12Certificate&) = 0; + + public: + boost::signal<void ()> onError; + boost::signal<void ()> onConnected; + }; +} + +#endif diff --git a/Swiften/StreamStack/TLSLayerFactory.cpp b/Swiften/StreamStack/TLSLayerFactory.cpp new file mode 100644 index 0000000..15de455 --- /dev/null +++ b/Swiften/StreamStack/TLSLayerFactory.cpp @@ -0,0 +1,8 @@ +#include "Swiften/StreamStack/TLSLayerFactory.h" + +namespace Swift { + +TLSLayerFactory::~TLSLayerFactory() { +} + +} diff --git a/Swiften/StreamStack/TLSLayerFactory.h b/Swiften/StreamStack/TLSLayerFactory.h new file mode 100644 index 0000000..1b0bfc1 --- /dev/null +++ b/Swiften/StreamStack/TLSLayerFactory.h @@ -0,0 +1,15 @@ +#pragma once + +#include <boost/shared_ptr.hpp> + +namespace Swift { + class TLSLayer; + + class TLSLayerFactory { + public: + virtual ~TLSLayerFactory(); + virtual bool canCreate() const = 0; + + virtual boost::shared_ptr<TLSLayer> createTLSLayer() = 0; + }; +} diff --git a/Swiften/StreamStack/UnitTest/StreamStackTest.cpp b/Swiften/StreamStack/UnitTest/StreamStackTest.cpp new file mode 100644 index 0000000..6b97c0d --- /dev/null +++ b/Swiften/StreamStack/UnitTest/StreamStackTest.cpp @@ -0,0 +1,165 @@ +#include <vector> +#include <boost/bind.hpp> +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> + +#include "Swiften/Base/ByteArray.h" +#include "Swiften/StreamStack/StreamStack.h" +#include "Swiften/StreamStack/LowLayer.h" +#include "Swiften/StreamStack/XMPPLayer.h" +#include "Swiften/StreamStack/StreamLayer.h" +#include "Swiften/Parser/PayloadParsers/FullPayloadParserFactoryCollection.h" +#include "Swiften/Serializer/PayloadSerializers/FullPayloadSerializerCollection.h" + +using namespace Swift; + +class StreamStackTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(StreamStackTest); + CPPUNIT_TEST(testWriteData_NoIntermediateStreamStack); + CPPUNIT_TEST(testWriteData_OneIntermediateStream); + CPPUNIT_TEST(testWriteData_TwoIntermediateStreamStack); + CPPUNIT_TEST(testReadData_NoIntermediateStreamStack); + CPPUNIT_TEST(testReadData_OneIntermediateStream); + CPPUNIT_TEST(testReadData_TwoIntermediateStreamStack); + CPPUNIT_TEST(testAddLayer_ExistingOnWriteDataSlot); + CPPUNIT_TEST_SUITE_END(); + + public: + StreamStackTest() {} + + void setUp() { + physicalStream_ = boost::shared_ptr<TestLowLayer>(new TestLowLayer()); + xmppStream_ = boost::shared_ptr<XMPPLayer>(new XMPPLayer(&parserFactories_, &serializers_)); + elementsReceived_ = 0; + dataWriteReceived_ = 0; + } + + void tearDown() { + } + + void testWriteData_NoIntermediateStreamStack() { + StreamStack testling(xmppStream_, physicalStream_); + + xmppStream_->writeData("foo"); + + CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), physicalStream_->data_.size()); + CPPUNIT_ASSERT_EQUAL(ByteArray("foo"), physicalStream_->data_[0]); + } + + void testWriteData_OneIntermediateStream() { + StreamStack testling(xmppStream_, physicalStream_); + boost::shared_ptr<MyStreamLayer> xStream(new MyStreamLayer("X")); + testling.addLayer(xStream); + + xmppStream_->writeData("foo"); + + CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), physicalStream_->data_.size()); + CPPUNIT_ASSERT_EQUAL(ByteArray("Xfoo"), physicalStream_->data_[0]); + } + + void testWriteData_TwoIntermediateStreamStack() { + StreamStack testling(xmppStream_, physicalStream_); + boost::shared_ptr<MyStreamLayer> xStream(new MyStreamLayer("X")); + boost::shared_ptr<MyStreamLayer> yStream(new MyStreamLayer("Y")); + testling.addLayer(xStream); + testling.addLayer(yStream); + + xmppStream_->writeData("foo"); + + CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), physicalStream_->data_.size()); + CPPUNIT_ASSERT_EQUAL(ByteArray("XYfoo"), physicalStream_->data_[0]); + } + + void testReadData_NoIntermediateStreamStack() { + StreamStack testling(xmppStream_, physicalStream_); + xmppStream_->onElement.connect(boost::bind(&StreamStackTest::handleElement, this, _1)); + + physicalStream_->onDataRead(ByteArray("<stream:stream xmlns:stream='http://etherx.jabber.org/streams'><presence/>")); + + CPPUNIT_ASSERT_EQUAL(1, elementsReceived_); + } + + void testReadData_OneIntermediateStream() { + StreamStack testling(xmppStream_, physicalStream_); + xmppStream_->onElement.connect(boost::bind(&StreamStackTest::handleElement, this, _1)); + boost::shared_ptr<MyStreamLayer> xStream(new MyStreamLayer("<")); + testling.addLayer(xStream); + + physicalStream_->onDataRead(ByteArray("stream:stream xmlns:stream='http://etherx.jabber.org/streams'><presence/>")); + + CPPUNIT_ASSERT_EQUAL(1, elementsReceived_); + } + + void testReadData_TwoIntermediateStreamStack() { + StreamStack testling(xmppStream_, physicalStream_); + xmppStream_->onElement.connect(boost::bind(&StreamStackTest::handleElement, this, _1)); + boost::shared_ptr<MyStreamLayer> xStream(new MyStreamLayer("s")); + boost::shared_ptr<MyStreamLayer> yStream(new MyStreamLayer("<")); + testling.addLayer(xStream); + testling.addLayer(yStream); + + physicalStream_->onDataRead(ByteArray("tream:stream xmlns:stream='http://etherx.jabber.org/streams'><presence/>")); + + CPPUNIT_ASSERT_EQUAL(1, elementsReceived_); + } + + void testAddLayer_ExistingOnWriteDataSlot() { + StreamStack testling(xmppStream_, physicalStream_); + xmppStream_->onWriteData.connect(boost::bind(&StreamStackTest::handleWriteData, this, _1)); + boost::shared_ptr<MyStreamLayer> xStream(new MyStreamLayer("X")); + testling.addLayer(xStream); + + xmppStream_->writeData("foo"); + + CPPUNIT_ASSERT_EQUAL(1, dataWriteReceived_); + } + + void handleElement(boost::shared_ptr<Element>) { + ++elementsReceived_; + } + + void handleWriteData(ByteArray) { + ++dataWriteReceived_; + } + + private: + class MyStreamLayer : public StreamLayer { + public: + MyStreamLayer(const String& prepend) : prepend_(prepend) { + } + + virtual void writeData(const ByteArray& data) { + onWriteData(ByteArray(prepend_) + data); + } + + virtual void handleDataRead(const ByteArray& data) { + onDataRead(ByteArray(prepend_) + data); + } + + private: + String prepend_; + }; + + class TestLowLayer : public LowLayer { + public: + TestLowLayer() { + } + + virtual void writeData(const ByteArray& data) { + data_.push_back(data); + } + + std::vector<ByteArray> data_; + }; + + private: + FullPayloadParserFactoryCollection parserFactories_; + FullPayloadSerializerCollection serializers_; + boost::shared_ptr<TestLowLayer> physicalStream_; + boost::shared_ptr<XMPPLayer> xmppStream_; + int elementsReceived_; + int dataWriteReceived_; +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(StreamStackTest); diff --git a/Swiften/StreamStack/UnitTest/XMPPLayerTest.cpp b/Swiften/StreamStack/UnitTest/XMPPLayerTest.cpp new file mode 100644 index 0000000..e284ba9 --- /dev/null +++ b/Swiften/StreamStack/UnitTest/XMPPLayerTest.cpp @@ -0,0 +1,119 @@ +#include <vector> +#include <boost/bind.hpp> +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> + +#include "Swiften/Elements/ProtocolHeader.h" +#include "Swiften/Elements/Presence.h" +#include "Swiften/Base/ByteArray.h" +#include "Swiften/StreamStack/XMPPLayer.h" +#include "Swiften/Parser/PayloadParsers/FullPayloadParserFactoryCollection.h" +#include "Swiften/Serializer/PayloadSerializers/FullPayloadSerializerCollection.h" + +using namespace Swift; + +class XMPPLayerTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(XMPPLayerTest); + CPPUNIT_TEST(testParseData_Error); + CPPUNIT_TEST(testResetParser); + CPPUNIT_TEST(testResetParser_FromSlot); + CPPUNIT_TEST(testWriteHeader); + CPPUNIT_TEST(testWriteElement); + CPPUNIT_TEST(testWriteFooter); + CPPUNIT_TEST_SUITE_END(); + + public: + XMPPLayerTest() {} + + void setUp() { + testling_ = new XMPPLayer(&parserFactories_, &serializers_); + elementsReceived_ = 0; + dataReceived_ = ""; + errorReceived_ = 0; + } + + void tearDown() { + delete testling_; + } + + void testParseData_Error() { + testling_->onError.connect(boost::bind(&XMPPLayerTest::handleError, this)); + + testling_->parseData("<iq>"); + + CPPUNIT_ASSERT_EQUAL(1, errorReceived_); + } + + void testResetParser() { + testling_->onElement.connect(boost::bind(&XMPPLayerTest::handleElement, this, _1)); + testling_->onError.connect(boost::bind(&XMPPLayerTest::handleError, this)); + + testling_->parseData("<stream:stream to=\"example.com\" xmlns=\"jabber:client\" xmlns:stream=\"http://etherx.jabber.org/streams\" >"); + testling_->resetParser(); + testling_->parseData("<stream:stream to=\"example.com\" xmlns=\"jabber:client\" xmlns:stream=\"http://etherx.jabber.org/streams\" >"); + testling_->parseData("<presence/>"); + + CPPUNIT_ASSERT_EQUAL(1, elementsReceived_); + CPPUNIT_ASSERT_EQUAL(0, errorReceived_); + } + + void testResetParser_FromSlot() { + testling_->onElement.connect(boost::bind(&XMPPLayerTest::handleElementAndReset, this, _1)); + testling_->parseData("<stream:stream to=\"example.com\" xmlns=\"jabber:client\" xmlns:stream=\"http://etherx.jabber.org/streams\" ><presence/>"); + testling_->parseData("<stream:stream to=\"example.com\" xmlns=\"jabber:client\" xmlns:stream=\"http://etherx.jabber.org/streams\" ><presence/>"); + + CPPUNIT_ASSERT_EQUAL(2, elementsReceived_); + CPPUNIT_ASSERT_EQUAL(0, errorReceived_); + } + + void testWriteHeader() { + testling_->onWriteData.connect(boost::bind(&XMPPLayerTest::handleWriteData, this, _1)); + ProtocolHeader header; + header.setTo("example.com"); + testling_->writeHeader(header); + + CPPUNIT_ASSERT_EQUAL(String("<?xml version=\"1.0\"?><stream:stream xmlns=\"jabber:client\" xmlns:stream=\"http://etherx.jabber.org/streams\" to=\"example.com\" version=\"1.0\">"), dataReceived_); + } + + void testWriteElement() { + testling_->onWriteData.connect(boost::bind(&XMPPLayerTest::handleWriteData, this, _1)); + testling_->writeElement(boost::shared_ptr<Presence>(new Presence())); + + CPPUNIT_ASSERT_EQUAL(String("<presence/>"), dataReceived_); + } + + void testWriteFooter() { + testling_->onWriteData.connect(boost::bind(&XMPPLayerTest::handleWriteData, this, _1)); + testling_->writeFooter(); + + CPPUNIT_ASSERT_EQUAL(String("</stream:stream>"), dataReceived_); + } + + void handleElement(boost::shared_ptr<Element>) { + ++elementsReceived_; + } + + void handleElementAndReset(boost::shared_ptr<Element>) { + ++elementsReceived_; + testling_->resetParser(); + } + + void handleWriteData(ByteArray ba) { + dataReceived_ += std::string(ba.getData(), ba.getSize()); + } + + void handleError() { + ++errorReceived_; + } + + private: + FullPayloadParserFactoryCollection parserFactories_; + FullPayloadSerializerCollection serializers_; + XMPPLayer* testling_; + int elementsReceived_; + String dataReceived_; + int errorReceived_; +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(XMPPLayerTest); diff --git a/Swiften/StreamStack/WhitespacePingLayer.cpp b/Swiften/StreamStack/WhitespacePingLayer.cpp new file mode 100644 index 0000000..5c1eb00 --- /dev/null +++ b/Swiften/StreamStack/WhitespacePingLayer.cpp @@ -0,0 +1,39 @@ +#include "Swiften/StreamStack/WhitespacePingLayer.h" + +#include <boost/bind.hpp> + +#include "Swiften/Network/TimerFactory.h" +#include "Swiften/Network/Timer.h" + +namespace Swift { + +static const int TIMEOUT_MILLISECONDS = 60000; + +WhitespacePingLayer::WhitespacePingLayer(TimerFactory* timerFactory) : isActive(false) { + timer = timerFactory->createTimer(TIMEOUT_MILLISECONDS); + timer->onTick.connect(boost::bind(&WhitespacePingLayer::handleTimerTick, this)); +} + +void WhitespacePingLayer::writeData(const ByteArray& data) { + onWriteData(data); +} + +void WhitespacePingLayer::handleDataRead(const ByteArray& data) { + onDataRead(data); +} + +void WhitespacePingLayer::handleTimerTick() { + onWriteData(" "); +} + +void WhitespacePingLayer::setActive() { + isActive = true; + timer->start(); +} + +void WhitespacePingLayer::setInactive() { + timer->stop(); + isActive = false; +} + +} diff --git a/Swiften/StreamStack/WhitespacePingLayer.h b/Swiften/StreamStack/WhitespacePingLayer.h new file mode 100644 index 0000000..18ea39a --- /dev/null +++ b/Swiften/StreamStack/WhitespacePingLayer.h @@ -0,0 +1,32 @@ +#pragma once + +#include <boost/noncopyable.hpp> +#include <boost/shared_ptr.hpp> + +#include "Swiften/StreamStack/StreamLayer.h" + +namespace Swift { + class Timer; + class TimerFactory; + + class WhitespacePingLayer : public StreamLayer, boost::noncopyable { + public: + WhitespacePingLayer(TimerFactory* timerFactory); + + void setActive(); + void setInactive(); + void writeData(const ByteArray& data); + void handleDataRead(const ByteArray& data); + + bool getIsActive() const { + return isActive; + } + + private: + void handleTimerTick(); + + private: + bool isActive; + boost::shared_ptr<Timer> timer; + }; +} diff --git a/Swiften/StreamStack/XMPPLayer.cpp b/Swiften/StreamStack/XMPPLayer.cpp new file mode 100644 index 0000000..b87cb4a --- /dev/null +++ b/Swiften/StreamStack/XMPPLayer.cpp @@ -0,0 +1,80 @@ +#include "Swiften/StreamStack/XMPPLayer.h" +#include "Swiften/Parser/XMPPParser.h" +#include "Swiften/Serializer/XMPPSerializer.h" +#include "Swiften/Elements/ProtocolHeader.h" + +namespace Swift { + +XMPPLayer::XMPPLayer( + PayloadParserFactoryCollection* payloadParserFactories, + PayloadSerializerCollection* payloadSerializers) : + payloadParserFactories_(payloadParserFactories), + payloadSerializers_(payloadSerializers), + resetParserAfterParse_(false), + inParser_(false) { + xmppParser_ = new XMPPParser(this, payloadParserFactories_); + xmppSerializer_ = new XMPPSerializer(payloadSerializers_); +} + +XMPPLayer::~XMPPLayer() { + delete xmppSerializer_; + delete xmppParser_; +} + +void XMPPLayer::writeHeader(const ProtocolHeader& header) { + onWriteData(ByteArray(xmppSerializer_->serializeHeader(header))); +} + +void XMPPLayer::writeFooter() { + onWriteData(ByteArray(xmppSerializer_->serializeFooter())); +} + +void XMPPLayer::writeElement(boost::shared_ptr<Element> element) { + onWriteData(ByteArray(xmppSerializer_->serializeElement(element))); +} + +void XMPPLayer::writeData(const String& data) { + onWriteData(ByteArray(data)); +} + +void XMPPLayer::parseData(ByteArray data) { + onDataRead(data); + inParser_ = true; + if (!xmppParser_->parse(String(data.getData(), data.getSize()))) { + inParser_ = false; + onError(); + return; + } + inParser_ = false; + if (resetParserAfterParse_) { + doResetParser(); + } +} + +void XMPPLayer::doResetParser() { + delete xmppParser_; + xmppParser_ = new XMPPParser(this, payloadParserFactories_); + resetParserAfterParse_ = false; +} + +void XMPPLayer::handleStreamStart(const ProtocolHeader& header) { + onStreamStart(header); +} + +void XMPPLayer::handleElement(boost::shared_ptr<Element> stanza) { + onElement(stanza); +} + +void XMPPLayer::handleStreamEnd() { +} + +void XMPPLayer::resetParser() { + if (inParser_) { + resetParserAfterParse_ = true; + } + else { + doResetParser(); + } +} + +} diff --git a/Swiften/StreamStack/XMPPLayer.h b/Swiften/StreamStack/XMPPLayer.h new file mode 100644 index 0000000..7974811 --- /dev/null +++ b/Swiften/StreamStack/XMPPLayer.h @@ -0,0 +1,55 @@ +#pragma once + +#include <boost/shared_ptr.hpp> +#include <boost/signal.hpp> +#include <boost/noncopyable.hpp> + +#include "Swiften/Base/ByteArray.h" +#include "Swiften/Elements/Element.h" +#include "Swiften/Parser/XMPPParserClient.h" + +namespace Swift { + class ProtocolHeader; + class XMPPParser; + class PayloadParserFactoryCollection; + class XMPPSerializer; + class PayloadSerializerCollection; + + class XMPPLayer : public XMPPParserClient, boost::noncopyable { + public: + XMPPLayer( + PayloadParserFactoryCollection* payloadParserFactories, + PayloadSerializerCollection* payloadSerializers); + ~XMPPLayer(); + + void writeHeader(const ProtocolHeader& header); + void writeFooter(); + void writeElement(boost::shared_ptr<Element>); + void writeData(const String& data); + + void parseData(ByteArray data); + void resetParser(); + + public: + boost::signal<void (const ProtocolHeader&)> onStreamStart; + boost::signal<void (boost::shared_ptr<Element>)> onElement; + boost::signal<void (const ByteArray&)> onWriteData; + boost::signal<void (const ByteArray&)> onDataRead; + boost::signal<void ()> onError; + + private: + void handleStreamStart(const ProtocolHeader&); + void handleElement(boost::shared_ptr<Element>); + void handleStreamEnd(); + + void doResetParser(); + + private: + PayloadParserFactoryCollection* payloadParserFactories_; + XMPPParser* xmppParser_; + PayloadSerializerCollection* payloadSerializers_; + XMPPSerializer* xmppSerializer_; + bool resetParserAfterParse_; + bool inParser_; + }; +} diff --git a/Swiften/StringCodecs/Base64.cpp b/Swiften/StringCodecs/Base64.cpp new file mode 100644 index 0000000..03b0896 --- /dev/null +++ b/Swiften/StringCodecs/Base64.cpp @@ -0,0 +1,109 @@ +#include <boost/numeric/conversion/cast.hpp> + +#include "Swiften/StringCodecs/Base64.h" + +namespace Swift { + +#pragma GCC diagnostic ignored "-Wold-style-cast" + +String Base64::encode(const ByteArray &s) { + int i; + int len = s.getSize(); + char tbl[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; + int a, b, c; + + std::string p; + p.resize((len+2)/3*4); + int at = 0; + for( i = 0; i < len; i += 3 ) { + a = ((unsigned char) (s[i]) & 3) << 4; + if(i + 1 < len) { + a += (unsigned char) (s[i + 1]) >> 4; + b = ((unsigned char) (s[i + 1]) & 0xF) << 2; + if(i + 2 < len) { + b += (unsigned char) (s[i + 2]) >> 6; + c = (unsigned char) (s[i + 2]) & 0x3F; + } + else + c = 64; + } + else { + b = c = 64; + } + + p[at++] = tbl[(unsigned char) (s[i]) >> 2]; + p[at++] = tbl[a]; + p[at++] = tbl[b]; + p[at++] = tbl[c]; + } + return p; +} + +ByteArray Base64::decode(const String& input) { + String inputWithoutNewlines(input); + inputWithoutNewlines.removeAll('\n'); + + const std::string& s = inputWithoutNewlines.getUTF8String(); + ByteArray p; + + // -1 specifies invalid + // 64 specifies eof + // everything else specifies data + + char tbl[] = { + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,62,-1,-1,-1,63, + 52,53,54,55,56,57,58,59,60,61,-1,-1,-1,64,-1,-1, + -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14, + 15,16,17,18,19,20,21,22,23,24,25,-1,-1,-1,-1,-1, + -1,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40, + 41,42,43,44,45,46,47,48,49,50,51,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + }; + + // this should be a multiple of 4 + int len = s.size(); + + if(len % 4) { + return p; + } + + p.resize(len / 4 * 3); + + int i; + int at = 0; + + int a, b, c, d; + c = d = 0; + + for( i = 0; i < len; i += 4 ) { + a = tbl[boost::numeric_cast<int>(s[i])]; + b = tbl[boost::numeric_cast<int>(s[i + 1])]; + c = tbl[boost::numeric_cast<int>(s[i + 2])]; + d = tbl[boost::numeric_cast<int>(s[i + 3])]; + if((a == 64 || b == 64) || (a < 0 || b < 0 || c < 0 || d < 0)) { + p.resize(0); + return p; + } + p[at++] = ((a & 0x3F) << 2) | ((b >> 4) & 0x03); + p[at++] = ((b & 0x0F) << 4) | ((c >> 2) & 0x0F); + p[at++] = ((c & 0x03) << 6) | ((d >> 0) & 0x3F); + } + + if(c & 64) + p.resize(at - 2); + else if(d & 64) + p.resize(at - 1); + + return p; +} + +} diff --git a/Swiften/StringCodecs/Base64.h b/Swiften/StringCodecs/Base64.h new file mode 100644 index 0000000..1ea378c --- /dev/null +++ b/Swiften/StringCodecs/Base64.h @@ -0,0 +1,14 @@ +#pragma once + +#include <vector> + +#include "Swiften/Base/String.h" +#include "Swiften/Base/ByteArray.h" + +namespace Swift { + class Base64 { + public: + static String encode(const ByteArray& s); + static ByteArray decode(const String &s); + }; +} diff --git a/Swiften/StringCodecs/HMACSHA1.cpp b/Swiften/StringCodecs/HMACSHA1.cpp new file mode 100644 index 0000000..f4066cb --- /dev/null +++ b/Swiften/StringCodecs/HMACSHA1.cpp @@ -0,0 +1,39 @@ +#include "Swiften/StringCodecs/HMACSHA1.h" + +#include <cassert> + +#include "Swiften/StringCodecs/SHA1.h" +#include "Swiften/Base/ByteArray.h" + +namespace Swift { + +static const unsigned int B = 64; + +ByteArray HMACSHA1::getResult(const ByteArray& key, const ByteArray& data) { + assert(key.getSize() <= B); + + // Create the padded key + ByteArray paddedKey(key); + paddedKey.resize(B); + for (unsigned int i = key.getSize(); i < paddedKey.getSize(); ++i) { + paddedKey[i] = 0x0; + } + + // Create the first value + ByteArray x(paddedKey); + for (unsigned int i = 0; i < x.getSize(); ++i) { + x[i] ^= 0x36; + } + x += data; + + // Create the second value + ByteArray y(paddedKey); + for (unsigned int i = 0; i < y.getSize(); ++i) { + y[i] ^= 0x5c; + } + y += SHA1::getHash(x); + + return SHA1::getHash(y); +} + +} diff --git a/Swiften/StringCodecs/HMACSHA1.h b/Swiften/StringCodecs/HMACSHA1.h new file mode 100644 index 0000000..698e4bc --- /dev/null +++ b/Swiften/StringCodecs/HMACSHA1.h @@ -0,0 +1,10 @@ +#pragma once + +namespace Swift { + class ByteArray; + + class HMACSHA1 { + public: + static ByteArray getResult(const ByteArray& key, const ByteArray& data); + }; +} diff --git a/Swiften/StringCodecs/Hexify.cpp b/Swiften/StringCodecs/Hexify.cpp new file mode 100644 index 0000000..504e314 --- /dev/null +++ b/Swiften/StringCodecs/Hexify.cpp @@ -0,0 +1,22 @@ +#include "Swiften/StringCodecs/Hexify.h" + +#include <sstream> +#include <iomanip> +#include <boost/numeric/conversion/cast.hpp> + +#include "Swiften/Base/String.h" +#include "Swiften/Base/ByteArray.h" + +namespace Swift { + +String Hexify::hexify(const ByteArray& data) { + std::ostringstream result; + result << std::hex; + + for (unsigned int i = 0; i < data.getSize(); ++i) { + result << std::setw(2) << std::setfill('0') << boost::numeric_cast<unsigned int>(static_cast<unsigned char>(data[i])); + } + return String(result.str()); +} + +} diff --git a/Swiften/StringCodecs/Hexify.h b/Swiften/StringCodecs/Hexify.h new file mode 100644 index 0000000..7bd5bee --- /dev/null +++ b/Swiften/StringCodecs/Hexify.h @@ -0,0 +1,11 @@ +#pragma once + +namespace Swift { + class String; + class ByteArray; + + class Hexify { + public: + static String hexify(const ByteArray& data); + }; +} diff --git a/Swiften/StringCodecs/MD5.cpp b/Swiften/StringCodecs/MD5.cpp new file mode 100644 index 0000000..937d128 --- /dev/null +++ b/Swiften/StringCodecs/MD5.cpp @@ -0,0 +1,359 @@ +/* + * This implementation is shamelessly copied from L. Peter Deutsch's + * implementation, and altered to use our own defines and datastructures. + * Original license below. + */ + +/* + Copyright (C) 1999, 2002 Aladdin Enterprises. All rights reserved. + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + L. Peter Deutsch + ghost@aladdin.com + */ + +#include "Swiften/StringCodecs/MD5.h" + +#include <cassert> + +#include "Swiften/Base/ByteArray.h" +#include "Swiften/Base/Platform.h" + +namespace Swift { + +typedef unsigned char md5_byte_t; /* 8-bit byte */ +typedef unsigned int md5_word_t; /* 32-bit word */ + +/* Define the state of the MD5 Algorithm. */ +typedef struct md5_state_s { + md5_word_t count[2]; /* message length in bits, lsw first */ + md5_word_t abcd[4]; /* digest buffer */ + md5_byte_t buf[64]; /* accumulate block */ +} md5_state_t; + +#define T_MASK ((md5_word_t)~0) +#define T1 /* 0xd76aa478 */ (T_MASK ^ 0x28955b87) +#define T2 /* 0xe8c7b756 */ (T_MASK ^ 0x173848a9) +#define T3 0x242070db +#define T4 /* 0xc1bdceee */ (T_MASK ^ 0x3e423111) +#define T5 /* 0xf57c0faf */ (T_MASK ^ 0x0a83f050) +#define T6 0x4787c62a +#define T7 /* 0xa8304613 */ (T_MASK ^ 0x57cfb9ec) +#define T8 /* 0xfd469501 */ (T_MASK ^ 0x02b96afe) +#define T9 0x698098d8 +#define T10 /* 0x8b44f7af */ (T_MASK ^ 0x74bb0850) +#define T11 /* 0xffff5bb1 */ (T_MASK ^ 0x0000a44e) +#define T12 /* 0x895cd7be */ (T_MASK ^ 0x76a32841) +#define T13 0x6b901122 +#define T14 /* 0xfd987193 */ (T_MASK ^ 0x02678e6c) +#define T15 /* 0xa679438e */ (T_MASK ^ 0x5986bc71) +#define T16 0x49b40821 +#define T17 /* 0xf61e2562 */ (T_MASK ^ 0x09e1da9d) +#define T18 /* 0xc040b340 */ (T_MASK ^ 0x3fbf4cbf) +#define T19 0x265e5a51 +#define T20 /* 0xe9b6c7aa */ (T_MASK ^ 0x16493855) +#define T21 /* 0xd62f105d */ (T_MASK ^ 0x29d0efa2) +#define T22 0x02441453 +#define T23 /* 0xd8a1e681 */ (T_MASK ^ 0x275e197e) +#define T24 /* 0xe7d3fbc8 */ (T_MASK ^ 0x182c0437) +#define T25 0x21e1cde6 +#define T26 /* 0xc33707d6 */ (T_MASK ^ 0x3cc8f829) +#define T27 /* 0xf4d50d87 */ (T_MASK ^ 0x0b2af278) +#define T28 0x455a14ed +#define T29 /* 0xa9e3e905 */ (T_MASK ^ 0x561c16fa) +#define T30 /* 0xfcefa3f8 */ (T_MASK ^ 0x03105c07) +#define T31 0x676f02d9 +#define T32 /* 0x8d2a4c8a */ (T_MASK ^ 0x72d5b375) +#define T33 /* 0xfffa3942 */ (T_MASK ^ 0x0005c6bd) +#define T34 /* 0x8771f681 */ (T_MASK ^ 0x788e097e) +#define T35 0x6d9d6122 +#define T36 /* 0xfde5380c */ (T_MASK ^ 0x021ac7f3) +#define T37 /* 0xa4beea44 */ (T_MASK ^ 0x5b4115bb) +#define T38 0x4bdecfa9 +#define T39 /* 0xf6bb4b60 */ (T_MASK ^ 0x0944b49f) +#define T40 /* 0xbebfbc70 */ (T_MASK ^ 0x4140438f) +#define T41 0x289b7ec6 +#define T42 /* 0xeaa127fa */ (T_MASK ^ 0x155ed805) +#define T43 /* 0xd4ef3085 */ (T_MASK ^ 0x2b10cf7a) +#define T44 0x04881d05 +#define T45 /* 0xd9d4d039 */ (T_MASK ^ 0x262b2fc6) +#define T46 /* 0xe6db99e5 */ (T_MASK ^ 0x1924661a) +#define T47 0x1fa27cf8 +#define T48 /* 0xc4ac5665 */ (T_MASK ^ 0x3b53a99a) +#define T49 /* 0xf4292244 */ (T_MASK ^ 0x0bd6ddbb) +#define T50 0x432aff97 +#define T51 /* 0xab9423a7 */ (T_MASK ^ 0x546bdc58) +#define T52 /* 0xfc93a039 */ (T_MASK ^ 0x036c5fc6) +#define T53 0x655b59c3 +#define T54 /* 0x8f0ccc92 */ (T_MASK ^ 0x70f3336d) +#define T55 /* 0xffeff47d */ (T_MASK ^ 0x00100b82) +#define T56 /* 0x85845dd1 */ (T_MASK ^ 0x7a7ba22e) +#define T57 0x6fa87e4f +#define T58 /* 0xfe2ce6e0 */ (T_MASK ^ 0x01d3191f) +#define T59 /* 0xa3014314 */ (T_MASK ^ 0x5cfebceb) +#define T60 0x4e0811a1 +#define T61 /* 0xf7537e82 */ (T_MASK ^ 0x08ac817d) +#define T62 /* 0xbd3af235 */ (T_MASK ^ 0x42c50dca) +#define T63 0x2ad7d2bb +#define T64 /* 0xeb86d391 */ (T_MASK ^ 0x14792c6e) + + +static void md5_process(md5_state_t *pms, const md5_byte_t *data /*[64]*/) { + md5_word_t + a = pms->abcd[0], b = pms->abcd[1], + c = pms->abcd[2], d = pms->abcd[3]; + md5_word_t t; +#ifdef SWIFTEN_BIG_ENDIAN + /* Define storage only for big-endian CPUs. */ + md5_word_t X[16]; +#else + /* Define storage for little-endian or both types of CPUs. */ + md5_word_t xbuf[16]; + const md5_word_t *X; +#endif + + { +#ifdef SWIFTEN_LITTLE_ENDIAN + { + /* + * On little-endian machines, we can process properly aligned + * data without copying it. + */ + if (!((data - (const md5_byte_t *)0) & 3)) { + /* data are properly aligned */ + X = (const md5_word_t *)data; + } else { + /* not aligned */ + memcpy(xbuf, data, 64); + X = xbuf; + } + } +#else + { + /* + * On big-endian machines, we must arrange the bytes in the + * right order. + */ + const md5_byte_t *xp = data; + int i; + + for (i = 0; i < 16; ++i, xp += 4) + X[i] = xp[0] + (xp[1] << 8) + (xp[2] << 16) + (xp[3] << 24); + } +#endif + } + +#define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32 - (n)))) + + /* Round 1. */ + /* Let [abcd k s i] denote the operation + a = b + ((a + F(b,c,d) + X[k] + T[i]) <<< s). */ +#define F(x, y, z) (((x) & (y)) | (~(x) & (z))) +#define SET(a, b, c, d, k, s, Ti)\ + t = a + F(b,c,d) + X[k] + Ti;\ + a = ROTATE_LEFT(t, s) + b + /* Do the following 16 operations. */ + SET(a, b, c, d, 0, 7, T1); + SET(d, a, b, c, 1, 12, T2); + SET(c, d, a, b, 2, 17, T3); + SET(b, c, d, a, 3, 22, T4); + SET(a, b, c, d, 4, 7, T5); + SET(d, a, b, c, 5, 12, T6); + SET(c, d, a, b, 6, 17, T7); + SET(b, c, d, a, 7, 22, T8); + SET(a, b, c, d, 8, 7, T9); + SET(d, a, b, c, 9, 12, T10); + SET(c, d, a, b, 10, 17, T11); + SET(b, c, d, a, 11, 22, T12); + SET(a, b, c, d, 12, 7, T13); + SET(d, a, b, c, 13, 12, T14); + SET(c, d, a, b, 14, 17, T15); + SET(b, c, d, a, 15, 22, T16); +#undef SET + + /* Round 2. */ + /* Let [abcd k s i] denote the operation + a = b + ((a + G(b,c,d) + X[k] + T[i]) <<< s). */ +#define G(x, y, z) (((x) & (z)) | ((y) & ~(z))) +#define SET(a, b, c, d, k, s, Ti)\ + t = a + G(b,c,d) + X[k] + Ti;\ + a = ROTATE_LEFT(t, s) + b + /* Do the following 16 operations. */ + SET(a, b, c, d, 1, 5, T17); + SET(d, a, b, c, 6, 9, T18); + SET(c, d, a, b, 11, 14, T19); + SET(b, c, d, a, 0, 20, T20); + SET(a, b, c, d, 5, 5, T21); + SET(d, a, b, c, 10, 9, T22); + SET(c, d, a, b, 15, 14, T23); + SET(b, c, d, a, 4, 20, T24); + SET(a, b, c, d, 9, 5, T25); + SET(d, a, b, c, 14, 9, T26); + SET(c, d, a, b, 3, 14, T27); + SET(b, c, d, a, 8, 20, T28); + SET(a, b, c, d, 13, 5, T29); + SET(d, a, b, c, 2, 9, T30); + SET(c, d, a, b, 7, 14, T31); + SET(b, c, d, a, 12, 20, T32); +#undef SET + + /* Round 3. */ + /* Let [abcd k s t] denote the operation + a = b + ((a + H(b,c,d) + X[k] + T[i]) <<< s). */ +#define H(x, y, z) ((x) ^ (y) ^ (z)) +#define SET(a, b, c, d, k, s, Ti)\ + t = a + H(b,c,d) + X[k] + Ti;\ + a = ROTATE_LEFT(t, s) + b + /* Do the following 16 operations. */ + SET(a, b, c, d, 5, 4, T33); + SET(d, a, b, c, 8, 11, T34); + SET(c, d, a, b, 11, 16, T35); + SET(b, c, d, a, 14, 23, T36); + SET(a, b, c, d, 1, 4, T37); + SET(d, a, b, c, 4, 11, T38); + SET(c, d, a, b, 7, 16, T39); + SET(b, c, d, a, 10, 23, T40); + SET(a, b, c, d, 13, 4, T41); + SET(d, a, b, c, 0, 11, T42); + SET(c, d, a, b, 3, 16, T43); + SET(b, c, d, a, 6, 23, T44); + SET(a, b, c, d, 9, 4, T45); + SET(d, a, b, c, 12, 11, T46); + SET(c, d, a, b, 15, 16, T47); + SET(b, c, d, a, 2, 23, T48); +#undef SET + + /* Round 4. */ + /* Let [abcd k s t] denote the operation + a = b + ((a + I(b,c,d) + X[k] + T[i]) <<< s). */ +#define I(x, y, z) ((y) ^ ((x) | ~(z))) +#define SET(a, b, c, d, k, s, Ti)\ + t = a + I(b,c,d) + X[k] + Ti;\ + a = ROTATE_LEFT(t, s) + b + /* Do the following 16 operations. */ + SET(a, b, c, d, 0, 6, T49); + SET(d, a, b, c, 7, 10, T50); + SET(c, d, a, b, 14, 15, T51); + SET(b, c, d, a, 5, 21, T52); + SET(a, b, c, d, 12, 6, T53); + SET(d, a, b, c, 3, 10, T54); + SET(c, d, a, b, 10, 15, T55); + SET(b, c, d, a, 1, 21, T56); + SET(a, b, c, d, 8, 6, T57); + SET(d, a, b, c, 15, 10, T58); + SET(c, d, a, b, 6, 15, T59); + SET(b, c, d, a, 13, 21, T60); + SET(a, b, c, d, 4, 6, T61); + SET(d, a, b, c, 11, 10, T62); + SET(c, d, a, b, 2, 15, T63); + SET(b, c, d, a, 9, 21, T64); +#undef SET + + /* Then perform the following additions. (That is increment each + of the four registers by the value it had before this block + was started.) */ + pms->abcd[0] += a; + pms->abcd[1] += b; + pms->abcd[2] += c; + pms->abcd[3] += d; +} + +void +md5_init(md5_state_t *pms) +{ + pms->count[0] = pms->count[1] = 0; + pms->abcd[0] = 0x67452301; + pms->abcd[1] = /*0xefcdab89*/ T_MASK ^ 0x10325476; + pms->abcd[2] = /*0x98badcfe*/ T_MASK ^ 0x67452301; + pms->abcd[3] = 0x10325476; +} + +void +md5_append(md5_state_t *pms, const md5_byte_t *data, int nbytes) +{ + const md5_byte_t *p = data; + int left = nbytes; + int offset = (pms->count[0] >> 3) & 63; + md5_word_t nbits = (md5_word_t)(nbytes << 3); + + if (nbytes <= 0) + return; + + /* Update the message length. */ + pms->count[1] += nbytes >> 29; + pms->count[0] += nbits; + if (pms->count[0] < nbits) + pms->count[1]++; + + /* Process an initial partial block. */ + if (offset) { + int copy = (offset + nbytes > 64 ? 64 - offset : nbytes); + + memcpy(pms->buf + offset, p, copy); + if (offset + copy < 64) + return; + p += copy; + left -= copy; + md5_process(pms, pms->buf); + } + + /* Process full blocks. */ + for (; left >= 64; p += 64, left -= 64) + md5_process(pms, p); + + /* Process a final partial block. */ + if (left) + memcpy(pms->buf, p, left); +} + +void +md5_finish(md5_state_t *pms, md5_byte_t digest[16]) +{ + static const md5_byte_t pad[64] = { + 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }; + md5_byte_t data[8]; + int i; + + /* Save the length before padding. */ + for (i = 0; i < 8; ++i) + data[i] = (md5_byte_t)(pms->count[i >> 2] >> ((i & 3) << 3)); + /* Pad to 56 bytes mod 64. */ + md5_append(pms, pad, ((55 - (pms->count[0] >> 3)) & 63) + 1); + /* Append the length. */ + md5_append(pms, data, 8); + for (i = 0; i < 16; ++i) + digest[i] = (md5_byte_t)(pms->abcd[i >> 2] >> ((i & 3) << 3)); +} + +ByteArray MD5::getHash(const ByteArray& data) { + ByteArray digest; + digest.resize(16); + + md5_state_t state; + md5_init(&state); + md5_append(&state, reinterpret_cast<const md5_byte_t*>(data.getData()), data.getSize()); + md5_finish(&state, reinterpret_cast<md5_byte_t*>(digest.getData())); + + return digest; +} + +} diff --git a/Swiften/StringCodecs/MD5.h b/Swiften/StringCodecs/MD5.h new file mode 100644 index 0000000..8773409 --- /dev/null +++ b/Swiften/StringCodecs/MD5.h @@ -0,0 +1,10 @@ +#pragma once + +namespace Swift { + class ByteArray; + + class MD5 { + public: + static ByteArray getHash(const ByteArray& data); + }; +} diff --git a/Swiften/StringCodecs/PBKDF2.cpp b/Swiften/StringCodecs/PBKDF2.cpp new file mode 100644 index 0000000..c09c10e --- /dev/null +++ b/Swiften/StringCodecs/PBKDF2.cpp @@ -0,0 +1,20 @@ +#include "Swiften/StringCodecs/PBKDF2.h" +#include "Swiften/StringCodecs/HMACSHA1.h" + +namespace Swift { + +ByteArray PBKDF2::encode(const ByteArray& password, const ByteArray& salt, int iterations) { + ByteArray u = HMACSHA1::getResult(password, salt + ByteArray("\0\0\0\1", 4)); + ByteArray result = u; + int i = 1; + while (i < iterations) { + u = HMACSHA1::getResult(password, u); + for (unsigned int j = 0; j < u.getSize(); ++j) { + result[j] ^= u[j]; + } + ++i; + } + return result; +} + +} diff --git a/Swiften/StringCodecs/PBKDF2.h b/Swiften/StringCodecs/PBKDF2.h new file mode 100644 index 0000000..e87380d --- /dev/null +++ b/Swiften/StringCodecs/PBKDF2.h @@ -0,0 +1,10 @@ +#pragma once + +#include "Swiften/Base/ByteArray.h" + +namespace Swift { + class PBKDF2 { + public: + static ByteArray encode(const ByteArray& password, const ByteArray& salt, int iterations); + }; +} diff --git a/Swiften/StringCodecs/SHA1.cpp b/Swiften/StringCodecs/SHA1.cpp new file mode 100644 index 0000000..ef99d9a --- /dev/null +++ b/Swiften/StringCodecs/SHA1.cpp @@ -0,0 +1,197 @@ +#include "Swiften/Base/Platform.h" + +#pragma GCC diagnostic ignored "-Wold-style-cast" + +/* +SHA-1 in C +By Steve Reid <steve@edmweb.com> +100% Public Domain + +Test Vectors (from FIPS PUB 180-1) +"abc" + A9993E36 4706816A BA3E2571 7850C26C 9CD0D89D +"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq" + 84983E44 1C3BD26E BAAE4AA1 F95129E5 E54670F1 +A million repetitions of "a" + 34AA973C D4C4DAA4 F61EEB2B DBAD2731 6534016F +*/ + +/* #define LITTLE_ENDIAN * This should be #define'd if true. */ +/* #define SHA1HANDSOFF * Copies data before messing with it. */ + +#include <boost/cstdint.hpp> +#include <stdio.h> +#include <string.h> + +typedef struct { + boost::uint32_t state[5]; + boost::uint32_t count[2]; + boost::uint8_t buffer[64]; +} SHA1_CTX; + +void SHA1Transform(boost::uint32_t state[5], boost::uint8_t buffer[64]); +void SHA1Init(SHA1_CTX* context); +void SHA1Update(SHA1_CTX* context, boost::uint8_t* data, unsigned int len); +void SHA1Final(boost::uint8_t digest[20], SHA1_CTX* context); + +#define rol(value, bits) (((value) << (bits)) | ((value) >> (32 - (bits)))) + +/* blk0() and blk() perform the initial expand. */ +/* I got the idea of expanding during the round function from SSLeay */ +#ifdef SWIFTEN_LITTLE_ENDIAN +#define blk0(i) (block->l[i] = (rol(block->l[i],24)&0xFF00FF00) \ + |(rol(block->l[i],8)&0x00FF00FF)) +#else +#define blk0(i) block->l[i] +#endif +#define blk(i) (block->l[i&15] = rol(block->l[(i+13)&15]^block->l[(i+8)&15] \ + ^block->l[(i+2)&15]^block->l[i&15],1)) + +/* (R0+R1), R2, R3, R4 are the different operations used in SHA1 */ +#define R0(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk0(i)+0x5A827999+rol(v,5);w=rol(w,30); +#define R1(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk(i)+0x5A827999+rol(v,5);w=rol(w,30); +#define R2(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0x6ED9EBA1+rol(v,5);w=rol(w,30); +#define R3(v,w,x,y,z,i) z+=(((w|x)&y)|(w&x))+blk(i)+0x8F1BBCDC+rol(v,5);w=rol(w,30); +#define R4(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0xCA62C1D6+rol(v,5);w=rol(w,30); + + +/* Hash a single 512-bit block. This is the core of the algorithm. */ + +void SHA1Transform(boost::uint32_t state[5], boost::uint8_t buffer[64]) +{ +boost::uint32_t a, b, c, d, e; +typedef union { + boost::uint8_t c[64]; + boost::uint32_t l[16]; +} CHAR64LONG16; +CHAR64LONG16* block; +#ifdef SHA1HANDSOFF +static boost::uint8_t workspace[64]; + block = (CHAR64LONG16*)workspace; + memcpy(block, buffer, 64); +#else + block = reinterpret_cast<CHAR64LONG16*>(buffer); +#endif + /* Copy context->state[] to working vars */ + a = state[0]; + b = state[1]; + c = state[2]; + d = state[3]; + e = state[4]; + /* 4 rounds of 20 operations each. Loop unrolled. */ + R0(a,b,c,d,e, 0); R0(e,a,b,c,d, 1); R0(d,e,a,b,c, 2); R0(c,d,e,a,b, 3); + R0(b,c,d,e,a, 4); R0(a,b,c,d,e, 5); R0(e,a,b,c,d, 6); R0(d,e,a,b,c, 7); + R0(c,d,e,a,b, 8); R0(b,c,d,e,a, 9); R0(a,b,c,d,e,10); R0(e,a,b,c,d,11); + R0(d,e,a,b,c,12); R0(c,d,e,a,b,13); R0(b,c,d,e,a,14); R0(a,b,c,d,e,15); + R1(e,a,b,c,d,16); R1(d,e,a,b,c,17); R1(c,d,e,a,b,18); R1(b,c,d,e,a,19); + R2(a,b,c,d,e,20); R2(e,a,b,c,d,21); R2(d,e,a,b,c,22); R2(c,d,e,a,b,23); + R2(b,c,d,e,a,24); R2(a,b,c,d,e,25); R2(e,a,b,c,d,26); R2(d,e,a,b,c,27); + R2(c,d,e,a,b,28); R2(b,c,d,e,a,29); R2(a,b,c,d,e,30); R2(e,a,b,c,d,31); + R2(d,e,a,b,c,32); R2(c,d,e,a,b,33); R2(b,c,d,e,a,34); R2(a,b,c,d,e,35); + R2(e,a,b,c,d,36); R2(d,e,a,b,c,37); R2(c,d,e,a,b,38); R2(b,c,d,e,a,39); + R3(a,b,c,d,e,40); R3(e,a,b,c,d,41); R3(d,e,a,b,c,42); R3(c,d,e,a,b,43); + R3(b,c,d,e,a,44); R3(a,b,c,d,e,45); R3(e,a,b,c,d,46); R3(d,e,a,b,c,47); + R3(c,d,e,a,b,48); R3(b,c,d,e,a,49); R3(a,b,c,d,e,50); R3(e,a,b,c,d,51); + R3(d,e,a,b,c,52); R3(c,d,e,a,b,53); R3(b,c,d,e,a,54); R3(a,b,c,d,e,55); + R3(e,a,b,c,d,56); R3(d,e,a,b,c,57); R3(c,d,e,a,b,58); R3(b,c,d,e,a,59); + R4(a,b,c,d,e,60); R4(e,a,b,c,d,61); R4(d,e,a,b,c,62); R4(c,d,e,a,b,63); + R4(b,c,d,e,a,64); R4(a,b,c,d,e,65); R4(e,a,b,c,d,66); R4(d,e,a,b,c,67); + R4(c,d,e,a,b,68); R4(b,c,d,e,a,69); R4(a,b,c,d,e,70); R4(e,a,b,c,d,71); + R4(d,e,a,b,c,72); R4(c,d,e,a,b,73); R4(b,c,d,e,a,74); R4(a,b,c,d,e,75); + R4(e,a,b,c,d,76); R4(d,e,a,b,c,77); R4(c,d,e,a,b,78); R4(b,c,d,e,a,79); + /* Add the working vars back into context.state[] */ + state[0] += a; + state[1] += b; + state[2] += c; + state[3] += d; + state[4] += e; + /* Wipe variables */ + a = b = c = d = e = 0; +} + + +/* SHA1Init - Initialize new context */ + +void SHA1Init(SHA1_CTX* context) +{ + /* SHA1 initialization constants */ + context->state[0] = 0x67452301; + context->state[1] = 0xEFCDAB89; + context->state[2] = 0x98BADCFE; + context->state[3] = 0x10325476; + context->state[4] = 0xC3D2E1F0; + context->count[0] = context->count[1] = 0; +} + + +/* Run your data through this. */ + +void SHA1Update(SHA1_CTX* context, boost::uint8_t* data, unsigned int len) +{ +unsigned int i, j; + + j = (context->count[0] >> 3) & 63; + if ((context->count[0] += len << 3) < (len << 3)) context->count[1]++; + context->count[1] += (len >> 29); + if ((j + len) > 63) { + memcpy(&context->buffer[j], data, (i = 64-j)); + SHA1Transform(context->state, context->buffer); + for ( ; i + 63 < len; i += 64) { + SHA1Transform(context->state, &data[i]); + } + j = 0; + } + else i = 0; + memcpy(&context->buffer[j], &data[i], len - i); +} + + +/* Add padding and return the message digest. */ + +void SHA1Final(boost::uint8_t digest[20], SHA1_CTX* context) +{ +boost::uint32_t i, j; +boost::uint8_t finalcount[8]; + + for (i = 0; i < 8; i++) { + finalcount[i] = (boost::uint8_t) ((context->count[(i >= 4 ? 0 : 1)] + >> ((3-(i & 3)) * 8) ) & 255); /* Endian independent */ + } + SHA1Update(context, (boost::uint8_t *)("\200"), 1); + while ((context->count[0] & 504) != 448) { + SHA1Update(context, (boost::uint8_t *)("\0"), 1); + } + SHA1Update(context, finalcount, 8); /* Should cause a SHA1Transform() */ + for (i = 0; i < 20; i++) { + digest[i] = (boost::uint8_t) + ((context->state[i>>2] >> ((3-(i & 3)) * 8) ) & 255); + } + /* Wipe variables */ + i = j = 0; + memset(context->buffer, 0, 64); + memset(context->state, 0, 20); + memset(context->count, 0, 8); + memset(&finalcount, 0, 8); +#ifdef SHA1HANDSOFF /* make SHA1Transform overwrite it's own static vars */ + SHA1Transform(context->state, context->buffer); +#endif +} + +// ----------------------------------------------------------------------------- + +#include "Swiften/StringCodecs/SHA1.h" + +namespace Swift { + +ByteArray SHA1::getHash(const ByteArray& input) { + ByteArray inputCopy(input); + ByteArray digest; + digest.resize(20); + SHA1_CTX context; + SHA1Init(&context); + SHA1Update(&context, (boost::uint8_t*) inputCopy.getData(), inputCopy.getSize()); + SHA1Final((boost::uint8_t*) digest.getData(), &context); + return digest; +} + +} diff --git a/Swiften/StringCodecs/SHA1.h b/Swiften/StringCodecs/SHA1.h new file mode 100644 index 0000000..9f54e8e --- /dev/null +++ b/Swiften/StringCodecs/SHA1.h @@ -0,0 +1,10 @@ +#pragma once + +#include "Swiften/Base/ByteArray.h" + +namespace Swift { + class SHA1 { + public: + static ByteArray getHash(const ByteArray& data); + }; +} diff --git a/Swiften/StringCodecs/UnitTest/Base64Test.cpp b/Swiften/StringCodecs/UnitTest/Base64Test.cpp new file mode 100644 index 0000000..a28a9ab --- /dev/null +++ b/Swiften/StringCodecs/UnitTest/Base64Test.cpp @@ -0,0 +1,44 @@ +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> + +#include "Swiften/StringCodecs/Base64.h" + +using namespace Swift; + +class Base64Test : public CppUnit::TestFixture { + CPPUNIT_TEST_SUITE(Base64Test); + CPPUNIT_TEST(testEncode); + CPPUNIT_TEST(testEncode_NonAscii); + CPPUNIT_TEST(testEncode_NoData); + CPPUNIT_TEST(testDecode); + CPPUNIT_TEST(testDecode_NoData); + CPPUNIT_TEST_SUITE_END(); + + public: + void testEncode() { + String result(Base64::encode("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890")); + CPPUNIT_ASSERT_EQUAL(String("QUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVphYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5ejEyMzQ1Njc4OTA="), result); + } + + void testEncode_NonAscii() { + String result(Base64::encode(ByteArray("\x42\x06\xb2\x3c\xa6\xb0\xa6\x43\xd2\x0d\x89\xb0\x4f\xf5\x8c\xf7\x8b\x80\x96\xed"))); + CPPUNIT_ASSERT_EQUAL(String("QgayPKawpkPSDYmwT/WM94uAlu0="), result); + } + + void testEncode_NoData() { + String result(Base64::encode(ByteArray())); + CPPUNIT_ASSERT_EQUAL(String(""), result); + } + + void testDecode() { + ByteArray result(Base64::decode("QUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVphYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5ejEyMzQ1Njc4OTA=")); + CPPUNIT_ASSERT_EQUAL(ByteArray("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890"), result); + } + + void testDecode_NoData() { + ByteArray result(Base64::decode("")); + CPPUNIT_ASSERT_EQUAL(ByteArray(), result); + } +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(Base64Test); diff --git a/Swiften/StringCodecs/UnitTest/HMACSHA1Test.cpp b/Swiften/StringCodecs/UnitTest/HMACSHA1Test.cpp new file mode 100644 index 0000000..edfae10 --- /dev/null +++ b/Swiften/StringCodecs/UnitTest/HMACSHA1Test.cpp @@ -0,0 +1,21 @@ +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> + +#include "Swiften/Base/ByteArray.h" +#include "Swiften/StringCodecs/HMACSHA1.h" + +using namespace Swift; + +class HMACSHA1Test : public CppUnit::TestFixture { + CPPUNIT_TEST_SUITE(HMACSHA1Test); + CPPUNIT_TEST(testGetResult); + CPPUNIT_TEST_SUITE_END(); + + public: + void testGetResult() { + ByteArray result(HMACSHA1::getResult("foo", "foobar")); + CPPUNIT_ASSERT_EQUAL(ByteArray("\xa4\xee\xba\x8e\x63\x3d\x77\x88\x69\xf5\x68\xd0\x5a\x1b\x3d\xc7\x2b\xfd\x4\xdd"), result); + } +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(HMACSHA1Test); diff --git a/Swiften/StringCodecs/UnitTest/HexifyTest.cpp b/Swiften/StringCodecs/UnitTest/HexifyTest.cpp new file mode 100644 index 0000000..bf032a3 --- /dev/null +++ b/Swiften/StringCodecs/UnitTest/HexifyTest.cpp @@ -0,0 +1,21 @@ +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> + +#include "Swiften/StringCodecs/Hexify.h" +#include "Swiften/Base/String.h" +#include "Swiften/Base/ByteArray.h" + +using namespace Swift; + +class HexifyTest : public CppUnit::TestFixture { + CPPUNIT_TEST_SUITE(HexifyTest); + CPPUNIT_TEST(testHexify); + CPPUNIT_TEST_SUITE_END(); + + public: + void testHexify() { + CPPUNIT_ASSERT_EQUAL(String("4206b23ca6b0a643d20d89b04ff58cf78b8096ed"), Hexify::hexify(ByteArray("\x42\x06\xb2\x3c\xa6\xb0\xa6\x43\xd2\x0d\x89\xb0\x4f\xf5\x8c\xf7\x8b\x80\x96\xed"))); + } +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(HexifyTest); diff --git a/Swiften/StringCodecs/UnitTest/MD5Test.cpp b/Swiften/StringCodecs/UnitTest/MD5Test.cpp new file mode 100644 index 0000000..cad8754 --- /dev/null +++ b/Swiften/StringCodecs/UnitTest/MD5Test.cpp @@ -0,0 +1,29 @@ +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> + +#include "Swiften/StringCodecs/MD5.h" +#include "Swiften/Base/ByteArray.h" + +using namespace Swift; + +class MD5Test : public CppUnit::TestFixture { + CPPUNIT_TEST_SUITE(MD5Test); + CPPUNIT_TEST(testGetHash_Empty); + CPPUNIT_TEST(testGetHash_Alphabet); + CPPUNIT_TEST_SUITE_END(); + + public: + void testGetHash_Empty() { + ByteArray result(MD5::getHash("")); + + CPPUNIT_ASSERT_EQUAL(ByteArray("\xd4\x1d\x8c\xd9\x8f\x00\xb2\x04\xe9\x80\x09\x98\xec\xf8\x42\x7e", 16), result); + } + + void testGetHash_Alphabet() { + ByteArray result(MD5::getHash("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789")); + + CPPUNIT_ASSERT_EQUAL(ByteArray("\xd1\x74\xab\x98\xd2\x77\xd9\xf5\xa5\x61\x1c\x2c\x9f\x41\x9d\x9f", 16), result); + } +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(MD5Test); diff --git a/Swiften/StringCodecs/UnitTest/PBKDF2Test.cpp b/Swiften/StringCodecs/UnitTest/PBKDF2Test.cpp new file mode 100644 index 0000000..92f4fe9 --- /dev/null +++ b/Swiften/StringCodecs/UnitTest/PBKDF2Test.cpp @@ -0,0 +1,36 @@ +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> + +#include "Swiften/Base/ByteArray.h" +#include "Swiften/StringCodecs/PBKDF2.h" + +using namespace Swift; + +class PBKDF2Test : public CppUnit::TestFixture { + CPPUNIT_TEST_SUITE(PBKDF2Test); + CPPUNIT_TEST(testGetResult_I1); + CPPUNIT_TEST(testGetResult_I2); + CPPUNIT_TEST(testGetResult_I4096); + CPPUNIT_TEST_SUITE_END(); + + public: + void testGetResult_I1() { + ByteArray result(PBKDF2::encode("password", "salt", 1)); + + CPPUNIT_ASSERT_EQUAL(ByteArray("\x0c\x60\xc8\x0f\x96\x1f\x0e\x71\xf3\xa9\xb5\x24\xaf\x60\x12\x06\x2f\xe0\x37\xa6"), result); + } + + void testGetResult_I2() { + ByteArray result(PBKDF2::encode("password", "salt", 2)); + + CPPUNIT_ASSERT_EQUAL(ByteArray("\xea\x6c\x1\x4d\xc7\x2d\x6f\x8c\xcd\x1e\xd9\x2a\xce\x1d\x41\xf0\xd8\xde\x89\x57"), result); + } + + void testGetResult_I4096() { + ByteArray result(PBKDF2::encode("password", "salt", 4096)); + + CPPUNIT_ASSERT_EQUAL(ByteArray("\x4b\x00\x79\x1\xb7\x65\x48\x9a\xbe\xad\x49\xd9\x26\xf7\x21\xd0\x65\xa4\x29\xc1", 20), result); + } +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(PBKDF2Test); diff --git a/Swiften/StringCodecs/UnitTest/SHA1Test.cpp b/Swiften/StringCodecs/UnitTest/SHA1Test.cpp new file mode 100644 index 0000000..3dbf341 --- /dev/null +++ b/Swiften/StringCodecs/UnitTest/SHA1Test.cpp @@ -0,0 +1,37 @@ +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> + +#include "Swiften/StringCodecs/SHA1.h" + +using namespace Swift; + +class SHA1Test : public CppUnit::TestFixture { + CPPUNIT_TEST_SUITE(SHA1Test); + CPPUNIT_TEST(testGetHash); + CPPUNIT_TEST(testGetHash_Twice); + CPPUNIT_TEST(testGetHash_NoData); + CPPUNIT_TEST_SUITE_END(); + + public: + void testGetHash() { + ByteArray result(SHA1::getHash("client/pc//Exodus 0.9.1<http://jabber.org/protocol/caps<http://jabber.org/protocol/disco#info<http://jabber.org/protocol/disco#items<http://jabber.org/protocol/muc<")); + CPPUNIT_ASSERT_EQUAL(ByteArray("\x42\x06\xb2\x3c\xa6\xb0\xa6\x43\xd2\x0d\x89\xb0\x4f\xf5\x8c\xf7\x8b\x80\x96\xed"), result); + } + + + void testGetHash_Twice() { + ByteArray input("client/pc//Exodus 0.9.1<http://jabber.org/protocol/caps<http://jabber.org/protocol/disco#info<http://jabber.org/protocol/disco#items<http://jabber.org/protocol/muc<"); + SHA1::getHash(input); + ByteArray result(SHA1::getHash(input)); + + CPPUNIT_ASSERT_EQUAL(ByteArray("\x42\x06\xb2\x3c\xa6\xb0\xa6\x43\xd2\x0d\x89\xb0\x4f\xf5\x8c\xf7\x8b\x80\x96\xed"), result); + } + + void testGetHash_NoData() { + ByteArray result(SHA1::getHash(ByteArray())); + + CPPUNIT_ASSERT_EQUAL(ByteArray("\xda\x39\xa3\xee\x5e\x6b\x4b\x0d\x32\x55\xbf\xef\x95\x60\x18\x90\xaf\xd8\x07\x09"), result); + } +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(SHA1Test); diff --git a/Swiften/StringPrep/SConscript b/Swiften/StringPrep/SConscript new file mode 100644 index 0000000..480d81a --- /dev/null +++ b/Swiften/StringPrep/SConscript @@ -0,0 +1,9 @@ +Import("swiften_env") + +myenv = swiften_env.Clone() +myenv.MergeFlags(swiften_env["LIBIDN_FLAGS"]) + +objects = myenv.StaticObject([ + "StringPrep.cpp" + ]) +swiften_env.Append(SWIFTEN_OBJECTS = [objects]) diff --git a/Swiften/StringPrep/StringPrep.cpp b/Swiften/StringPrep/StringPrep.cpp new file mode 100644 index 0000000..a4af208 --- /dev/null +++ b/Swiften/StringPrep/StringPrep.cpp @@ -0,0 +1,33 @@ +#include "Swiften/StringPrep/StringPrep.h" + +#include <stringprep.h> +#include <vector> + +namespace Swift { + +static const int MAX_STRINGPREP_SIZE = 1024; + +const Stringprep_profile* getLibIDNProfile(StringPrep::Profile profile) { + switch(profile) { + case StringPrep::NamePrep: return stringprep_nameprep; break; + case StringPrep::XMPPNodePrep: return stringprep_xmpp_nodeprep; break; + case StringPrep::XMPPResourcePrep: return stringprep_xmpp_resourceprep; break; + case StringPrep::SASLPrep: return stringprep_saslprep; break; + } + assert(false); + return 0; +} + +String StringPrep::getPrepared(const String& s, Profile profile) { + + std::vector<char> input(s.getUTF8String().begin(), s.getUTF8String().end()); + input.resize(MAX_STRINGPREP_SIZE); + if (stringprep(&input[0], MAX_STRINGPREP_SIZE, static_cast<Stringprep_profile_flags>(0), getLibIDNProfile(profile)) == 0) { + return String(&input[0]); + } + else { + return ""; + } +} + +} diff --git a/Swiften/StringPrep/StringPrep.h b/Swiften/StringPrep/StringPrep.h new file mode 100644 index 0000000..7dbb03a --- /dev/null +++ b/Swiften/StringPrep/StringPrep.h @@ -0,0 +1,17 @@ +#pragma once + +#include "Swiften/Base/String.h" + +namespace Swift { + class StringPrep { + public: + enum Profile { + NamePrep, + XMPPNodePrep, + XMPPResourcePrep, + SASLPrep, + }; + + static String getPrepared(const String& s, Profile profile); + }; +} diff --git a/Swiften/TLS/OpenSSL/OpenSSLContext.cpp b/Swiften/TLS/OpenSSL/OpenSSLContext.cpp new file mode 100644 index 0000000..3ce8d54 --- /dev/null +++ b/Swiften/TLS/OpenSSL/OpenSSLContext.cpp @@ -0,0 +1,159 @@ +#include <vector> +#include <openssl/err.h> +#include <openssl/pkcs12.h> + +#include "Swiften/TLS/OpenSSL/OpenSSLContext.h" +#include "Swiften/TLS/PKCS12Certificate.h" + +#pragma GCC diagnostic ignored "-Wold-style-cast" + +namespace Swift { + +static const int SSL_READ_BUFFERSIZE = 8192; + +void freeX509Stack(STACK_OF(X509)* stack) { + sk_X509_free(stack); +} + +OpenSSLContext::OpenSSLContext() : state_(Start), context_(0), handle_(0), readBIO_(0), writeBIO_(0) { + ensureLibraryInitialized(); + context_ = SSL_CTX_new(TLSv1_client_method()); +} + +OpenSSLContext::~OpenSSLContext() { + SSL_free(handle_); + SSL_CTX_free(context_); +} + +void OpenSSLContext::ensureLibraryInitialized() { + static bool isLibraryInitialized = false; + if (!isLibraryInitialized) { + SSL_load_error_strings(); + SSL_library_init(); + OpenSSL_add_all_algorithms(); + isLibraryInitialized = true; + } +} + +void OpenSSLContext::connect() { + handle_ = SSL_new(context_); + // Ownership of BIOs is ransferred + readBIO_ = BIO_new(BIO_s_mem()); + writeBIO_ = BIO_new(BIO_s_mem()); + SSL_set_bio(handle_, readBIO_, writeBIO_); + + state_ = Connecting; + doConnect(); +} + +void OpenSSLContext::doConnect() { + int connectResult = SSL_connect(handle_); + int error = SSL_get_error(handle_, connectResult); + switch (error) { + case SSL_ERROR_NONE: + state_ = Connected; + onConnected(); + //X509* x = SSL_get_peer_certificate(handle_); + //std::cout << x->name << std::endl; + //const char* comp = SSL_get_current_compression(handle_); + //std::cout << "Compression: " << SSL_COMP_get_name(comp) << std::endl; + break; + case SSL_ERROR_WANT_READ: + sendPendingDataToNetwork(); + break; + default: + state_ = Error; + onError(); + } +} + +void OpenSSLContext::sendPendingDataToNetwork() { + int size = BIO_pending(writeBIO_); + if (size > 0) { + ByteArray data; + data.resize(size); + BIO_read(writeBIO_, data.getData(), size); + onDataForNetwork(data); + } +} + +void OpenSSLContext::handleDataFromNetwork(const ByteArray& data) { + BIO_write(readBIO_, data.getData(), data.getSize()); + switch (state_) { + case Connecting: + doConnect(); + break; + case Connected: + sendPendingDataToApplication(); + break; + case Start: assert(false); break; + case Error: assert(false); break; + } +} + +void OpenSSLContext::handleDataFromApplication(const ByteArray& data) { + if (SSL_write(handle_, data.getData(), data.getSize()) >= 0) { + sendPendingDataToNetwork(); + } + else { + state_ = Error; + onError(); + } +} + +void OpenSSLContext::sendPendingDataToApplication() { + ByteArray data; + data.resize(SSL_READ_BUFFERSIZE); + int ret = SSL_read(handle_, data.getData(), data.getSize()); + while (ret > 0) { + data.resize(ret); + onDataForApplication(data); + data.resize(SSL_READ_BUFFERSIZE); + ret = SSL_read(handle_, data.getData(), data.getSize()); + } + if (ret < 0 && SSL_get_error(handle_, ret) != SSL_ERROR_WANT_READ) { + state_ = Error; + onError(); + } +} + +bool OpenSSLContext::setClientCertificate(const PKCS12Certificate& certificate) { + if (certificate.isNull()) { + return false; + } + + // Create a PKCS12 structure + BIO* bio = BIO_new(BIO_s_mem()); + BIO_write(bio, certificate.getData().getData(), certificate.getData().getSize()); + boost::shared_ptr<PKCS12> pkcs12(d2i_PKCS12_bio(bio, NULL), PKCS12_free); + BIO_free(bio); + if (!pkcs12) { + return false; + } + + // Parse PKCS12 + X509 *certPtr = 0; + EVP_PKEY* privateKeyPtr = 0; + STACK_OF(X509)* caCertsPtr = 0; + int result = PKCS12_parse(pkcs12.get(), certificate.getPassword().getUTF8Data(), &privateKeyPtr, &certPtr, &caCertsPtr); + if (result != 1) { + return false; + } + boost::shared_ptr<X509> cert(certPtr, X509_free); + boost::shared_ptr<EVP_PKEY> privateKey(privateKeyPtr, EVP_PKEY_free); + boost::shared_ptr<STACK_OF(X509)> caCerts(caCertsPtr, freeX509Stack); + + // Use the key & certificates + if (SSL_CTX_use_certificate(context_, cert.get()) != 1) { + return false; + } + if (SSL_CTX_use_PrivateKey(context_, privateKey.get()) != 1) { + return false; + } + for (int i = 0; i < sk_X509_num(caCerts.get()); ++i) { + SSL_CTX_add_extra_chain_cert(context_, sk_X509_value(caCerts.get(), i)); + } + return true; +} + +} diff --git a/Swiften/TLS/OpenSSL/OpenSSLContext.h b/Swiften/TLS/OpenSSL/OpenSSLContext.h new file mode 100644 index 0000000..3e735df --- /dev/null +++ b/Swiften/TLS/OpenSSL/OpenSSLContext.h @@ -0,0 +1,43 @@ +#include <openssl/ssl.h> +#include <boost/signal.hpp> +#include <boost/noncopyable.hpp> + +#include "Swiften/Base/ByteArray.h" + +namespace Swift { + class PKCS12Certificate; + + class OpenSSLContext : boost::noncopyable { + public: + OpenSSLContext(); + ~OpenSSLContext(); + + void connect(); + bool setClientCertificate(const PKCS12Certificate& cert); + + void handleDataFromNetwork(const ByteArray&); + void handleDataFromApplication(const ByteArray&); + + public: + boost::signal<void (const ByteArray&)> onDataForNetwork; + boost::signal<void (const ByteArray&)> onDataForApplication; + boost::signal<void ()> onError; + boost::signal<void ()> onConnected; + + private: + static void ensureLibraryInitialized(); + + void doConnect(); + void sendPendingDataToNetwork(); + void sendPendingDataToApplication(); + + private: + enum State { Start, Connecting, Connected, Error }; + + State state_; + SSL_CTX* context_; + SSL* handle_; + BIO* readBIO_; + BIO* writeBIO_; + }; +} diff --git a/Swiften/TLS/PKCS12Certificate.h b/Swiften/TLS/PKCS12Certificate.h new file mode 100644 index 0000000..4b0e708 --- /dev/null +++ b/Swiften/TLS/PKCS12Certificate.h @@ -0,0 +1,37 @@ +#ifndef SWIFTEN_PKCS12Certificate_H +#define SWIFTEN_PKCS12Certificate_H + +#include "Swiften/Base/ByteArray.h" + +namespace Swift { + class PKCS12Certificate { + public: + PKCS12Certificate() {} + + PKCS12Certificate(const String& filename, const String& password) : password_(password) { + data_.readFromFile(filename); + } + + bool isNull() const { + return data_.isEmpty(); + } + + const ByteArray& getData() const { + return data_; + } + + void setData(const ByteArray& data) { + data_ = data; + } + + const String& getPassword() const { + return password_; + } + + private: + ByteArray data_; + String password_; + }; +} + +#endif |