From 69f6c80767ae1b36d8761188e02983ed4b20c371 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Remko=20Tron=C3=A7on?= <git@el-tramo.be>
Date: Sat, 11 Jul 2009 21:10:20 +0200
Subject: Implemented basic server stanza routing.


diff --git a/Swiften/Makefile.inc b/Swiften/Makefile.inc
index 0ed7bab..6e47259 100644
--- a/Swiften/Makefile.inc
+++ b/Swiften/Makefile.inc
@@ -21,6 +21,7 @@ include Swiften/Presence/Makefile.inc
 include Swiften/Notifier/Makefile.inc
 include Swiften/History/Makefile.inc
 include Swiften/Avatars/Makefile.inc
+include Swiften/Server/Makefile.inc
 
 CPPFLAGS += $(SQLITE_CPPFLAGS)
 
diff --git a/Swiften/Server/Makefile.inc b/Swiften/Server/Makefile.inc
new file mode 100644
index 0000000..8dd6051
--- /dev/null
+++ b/Swiften/Server/Makefile.inc
@@ -0,0 +1,5 @@
+SWIFTEN_SOURCES += \
+	Swiften/Server/ServerSession.cpp \
+	Swiften/Server/ServerStanzaRouter.cpp
+
+include Swiften/Server/UnitTest/Makefile.inc
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/UnitTest/Makefile.inc b/Swiften/Server/UnitTest/Makefile.inc
new file mode 100644
index 0000000..a61ec06
--- /dev/null
+++ b/Swiften/Server/UnitTest/Makefile.inc
@@ -0,0 +1,2 @@
+UNITTEST_SOURCES += \
+	Swiften/Server/UnitTest/ServerStanzaRouterTest.cpp
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);
-- 
cgit v0.10.2-6-g49f6