From 0091fcc571758791442f82ece2a72444b6fe79cf Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Remko=20Tron=C3=A7on?= <git@el-tramo.be>
Date: Sun, 29 Aug 2010 14:51:45 +0200
Subject: Added Stanza Ack Requester & Responder.


diff --git a/QA/UnitTest/SConscript b/QA/UnitTest/SConscript
index cd9cdc8..d47474d 100644
--- a/QA/UnitTest/SConscript
+++ b/QA/UnitTest/SConscript
@@ -5,6 +5,7 @@ Import("env")
 if env["TEST"] :
 	if env["SCONS_STAGE"] == "flags" :
 		env["UNITTEST_SOURCES"] = []
+		env["UNITTEST_OBJECTS"] = []
 	if env["SCONS_STAGE"] == "test" :
 		myenv = env.Clone()
 		myenv.MergeFlags(env.get("CHECKER_FLAGS",""))
@@ -23,7 +24,7 @@ if env["TEST"] :
 			myenv.Append(CPPDEFINES = ["HAVE_LIBXML"])
 		if env.get("HAVE_EXPAT") :
 			myenv.Append(CPPDEFINES = ["HAVE_EXPAT"])
-		checker = myenv.Program("checker", env["UNITTEST_SOURCES"])
+		checker = myenv.Program("checker", env["UNITTEST_SOURCES"] + env["UNITTEST_OBJECTS"])
 		for i in ["HOME", "USERPROFILE", "APPDATA"]:
 			if os.environ.get(i, "") :
 				myenv["ENV"][i] = os.environ[i]
diff --git a/Swiften/Elements/Message.h b/Swiften/Elements/Message.h
index f42aec6..e5e00ab 100644
--- a/Swiften/Elements/Message.h
+++ b/Swiften/Elements/Message.h
@@ -9,6 +9,7 @@
 #include <boost/optional.hpp>
 
 #include "Swiften/Base/String.h"
+#include "Swiften/Base/Shared.h"
 #include "Swiften/Elements/Body.h"
 #include "Swiften/Elements/Subject.h"
 #include "Swiften/Elements/ErrorPayload.h"
@@ -16,8 +17,7 @@
 
 namespace Swift
 {
-	class Message : public Stanza
-	{
+	class Message : public Stanza, public Shared<Message> {
 	  public:
 			enum Type { Normal, Chat, Error, Groupchat, Headline };
 
diff --git a/Swiften/Elements/Stanza.h b/Swiften/Elements/Stanza.h
index 20fb557..5d8fd6c 100644
--- a/Swiften/Elements/Stanza.h
+++ b/Swiften/Elements/Stanza.h
@@ -14,6 +14,7 @@
 #include "Swiften/Elements/Element.h"
 #include "Swiften/Elements/Payload.h"
 #include "Swiften/Base/String.h"
+#include "Swiften/Base/Shared.h"
 #include "Swiften/Base/foreach.h"
 #include "Swiften/JID/JID.h"
 
diff --git a/Swiften/Elements/StanzaAck.h b/Swiften/Elements/StanzaAck.h
index a1a39f8..eaf4e26 100644
--- a/Swiften/Elements/StanzaAck.h
+++ b/Swiften/Elements/StanzaAck.h
@@ -12,21 +12,23 @@
 namespace Swift {
 	class StanzaAck : public Element, public Shared<StanzaAck> {
 		public:
-			StanzaAck() : handledStanzasCount(-1) {}
+			StanzaAck() : valid(false), handledStanzasCount(0) {}
 
-			int getHandledStanzasCount() const {
+			unsigned int getHandledStanzasCount() const {
 				return handledStanzasCount;
 			}
 
 			void setHandledStanzasCount(int i) {
 				handledStanzasCount = i;
+				valid = true;
 			}
 
 			bool isValid() const {
-				return handledStanzasCount != -1;
+				return valid;
 			}
 
 		private:
-			int handledStanzasCount;
+			bool valid;
+			unsigned int handledStanzasCount;
 	};
 }
diff --git a/Swiften/Parser/StanzaAckParser.cpp b/Swiften/Parser/StanzaAckParser.cpp
index 62e912b..d85eb9b 100644
--- a/Swiften/Parser/StanzaAckParser.cpp
+++ b/Swiften/Parser/StanzaAckParser.cpp
@@ -20,7 +20,6 @@ void StanzaAckParser::handleStartElement(const String&, const String&, const Att
 			getElementGeneric()->setHandledStanzasCount(boost::lexical_cast<int>(handledStanzasString.getUTF8String()));
 		}
 		catch (const boost::bad_lexical_cast &) {
-			getElementGeneric()->setHandledStanzasCount(-1);
 		}
 	}
 	++depth;
diff --git a/Swiften/Parser/UnitTest/StanzaAckParserTest.cpp b/Swiften/Parser/UnitTest/StanzaAckParserTest.cpp
index 39e67a7..b90af6e 100644
--- a/Swiften/Parser/UnitTest/StanzaAckParserTest.cpp
+++ b/Swiften/Parser/UnitTest/StanzaAckParserTest.cpp
@@ -28,7 +28,7 @@ class StanzaAckParserTest : public CppUnit::TestFixture {
 			CPPUNIT_ASSERT(parser.parse("<a h=\"12\" xmlns=\"urn:xmpp:sm:2\"/>"));
 
 			CPPUNIT_ASSERT(testling.getElementGeneric()->isValid());
-			CPPUNIT_ASSERT_EQUAL(12, testling.getElementGeneric()->getHandledStanzasCount());
+			CPPUNIT_ASSERT_EQUAL(12U, testling.getElementGeneric()->getHandledStanzasCount());
 		}
 
 		void testParse_Invalid() {
diff --git a/Swiften/SConscript b/Swiften/SConscript
index 3e5c35b..a23efa0 100644
--- a/Swiften/SConscript
+++ b/Swiften/SConscript
@@ -129,6 +129,7 @@ if env["SCONS_STAGE"] == "build" :
 			"History",
 			"StreamStack",
 			"LinkLocal",
+			"StreamManagement",
 		])
 	SConscript(test_only = True, dirs = [
 			"QA",
@@ -225,7 +226,8 @@ if env["SCONS_STAGE"] == "build" :
 			File("Serializer/UnitTest/AuthRequestSerializerTest.cpp"),
 			File("Serializer/UnitTest/AuthResponseSerializerTest.cpp"),
 			File("Serializer/XML/UnitTest/XMLElementTest.cpp"),
-			File("Server/UnitTest/ServerStanzaRouterTest.cpp"),
+			File("StreamManagement/UnitTest/StanzaAckRequesterTest.cpp"),
+			File("StreamManagement/UnitTest/StanzaAckResponderTest.cpp"),
 			File("StreamStack/UnitTest/StreamStackTest.cpp"),
 			File("StreamStack/UnitTest/XMPPLayerTest.cpp"),
 			File("StringCodecs/UnitTest/Base64Test.cpp"),
diff --git a/Swiften/StreamManagement/SConscript b/Swiften/StreamManagement/SConscript
new file mode 100644
index 0000000..d3fab8e
--- /dev/null
+++ b/Swiften/StreamManagement/SConscript
@@ -0,0 +1,8 @@
+Import("swiften_env")
+
+sources = [
+		"StanzaAckRequester.cpp",
+		"StanzaAckResponder.cpp",
+	]
+
+swiften_env.Append(SWIFTEN_OBJECTS = swiften_env.StaticObject(sources))
\ No newline at end of file
diff --git a/Swiften/StreamManagement/StanzaAckRequester.cpp b/Swiften/StreamManagement/StanzaAckRequester.cpp
new file mode 100644
index 0000000..b007675
--- /dev/null
+++ b/Swiften/StreamManagement/StanzaAckRequester.cpp
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2010 Remko Tronçon
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#include "Swiften/StreamManagement/StanzaAckRequester.h"
+
+#include <boost/numeric/conversion/cast.hpp>
+
+namespace Swift {
+
+static const unsigned int MAX_HANDLED_STANZA_COUNT = boost::numeric_cast<unsigned int>((1ULL<<32) - 1);
+
+StanzaAckRequester::StanzaAckRequester() : lastHandledStanzasCount(0) {
+
+}
+
+void StanzaAckRequester::handleStanzaSent(boost::shared_ptr<Stanza> stanza) {
+	unackedStanzas.push_back(stanza);
+	onRequestAck();
+}
+
+void StanzaAckRequester::handleAckReceived(unsigned int handledStanzasCount) {
+	unsigned int i = lastHandledStanzasCount;
+	while (i != handledStanzasCount) {
+		if (unackedStanzas.size() == 0) {
+			std::cerr << "Warning: Server acked more stanzas than we sent" << std::endl;
+			break;
+		}
+		boost::shared_ptr<Stanza> ackedStanza = unackedStanzas.front();
+		unackedStanzas.pop_front();
+		onStanzaAcked(ackedStanza);
+		i = (i == MAX_HANDLED_STANZA_COUNT ? 0 : i + 1);
+	}
+	lastHandledStanzasCount = handledStanzasCount;
+}
+
+}
diff --git a/Swiften/StreamManagement/StanzaAckRequester.h b/Swiften/StreamManagement/StanzaAckRequester.h
new file mode 100644
index 0000000..89f068e
--- /dev/null
+++ b/Swiften/StreamManagement/StanzaAckRequester.h
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2010 Remko Tronçon
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#pragma once
+
+#include <boost/shared_ptr.hpp>
+#include <deque>
+
+#include "Swiften/Elements/Stanza.h"
+#include "Swiften/Base/boost_bsignals.h"
+
+namespace Swift {
+	class StanzaAckRequester {
+		public:
+			StanzaAckRequester();
+
+			void handleStanzaSent(boost::shared_ptr<Stanza> stanza);
+			void handleAckReceived(unsigned int handledStanzasCount);
+
+		public:
+			boost::signal<void ()> onRequestAck;
+			boost::signal<void (boost::shared_ptr<Stanza>)> onStanzaAcked;
+
+		private:
+			friend class StanzaAckRequesterTest;
+			unsigned int lastHandledStanzasCount;
+			std::deque<boost::shared_ptr<Stanza> > unackedStanzas;
+	};
+
+}
diff --git a/Swiften/StreamManagement/StanzaAckResponder.cpp b/Swiften/StreamManagement/StanzaAckResponder.cpp
new file mode 100644
index 0000000..05ab5c4
--- /dev/null
+++ b/Swiften/StreamManagement/StanzaAckResponder.cpp
@@ -0,0 +1,24 @@
+/*
+ * Copyright (c) 2010 Remko Tronçon
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#include "Swiften/StreamManagement/StanzaAckResponder.h"
+
+#include <boost/numeric/conversion/cast.hpp>
+
+namespace Swift {
+
+static const unsigned int MAX_HANDLED_STANZA_COUNT = boost::numeric_cast<unsigned int>((1ULL<<32) - 1);
+
+StanzaAckResponder::StanzaAckResponder() : handledStanzasCount(0) {
+}
+
+void StanzaAckResponder::handleStanzaReceived() {
+	handledStanzasCount = (handledStanzasCount == MAX_HANDLED_STANZA_COUNT ? 0 : handledStanzasCount + 1);
+}
+
+void StanzaAckResponder::handleAckRequestReceived() {
+	onAck(handledStanzasCount);}
+}
diff --git a/Swiften/StreamManagement/StanzaAckResponder.h b/Swiften/StreamManagement/StanzaAckResponder.h
new file mode 100644
index 0000000..bc83aa1
--- /dev/null
+++ b/Swiften/StreamManagement/StanzaAckResponder.h
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2010 Remko Tronçon
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#pragma once
+
+#include <boost/shared_ptr.hpp>
+
+#include "Swiften/Elements/Stanza.h"
+#include "Swiften/Base/boost_bsignals.h"
+
+namespace Swift {
+	class StanzaAckResponder {
+		public:
+			StanzaAckResponder();
+
+			void handleStanzaReceived();
+			void handleAckRequestReceived();
+
+		public:
+			boost::signal<void (unsigned int /* handledStanzaCount */)> onAck;
+
+		private:
+			friend class StanzaAckResponderTest;
+			unsigned int handledStanzasCount;
+	};
+
+}
diff --git a/Swiften/StreamManagement/UnitTest/StanzaAckRequesterTest.cpp b/Swiften/StreamManagement/UnitTest/StanzaAckRequesterTest.cpp
new file mode 100644
index 0000000..70fe6eb
--- /dev/null
+++ b/Swiften/StreamManagement/UnitTest/StanzaAckRequesterTest.cpp
@@ -0,0 +1,120 @@
+/*
+ * Copyright (c) 2010 Remko Tronçon
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#include <cppunit/extensions/HelperMacros.h>
+#include <cppunit/extensions/TestFactoryRegistry.h>
+#include <boost/bind.hpp>
+#include <boost/numeric/conversion/cast.hpp>
+
+#include "Swiften/StreamManagement/StanzaAckRequester.h"
+#include "Swiften/Elements/Message.h"
+
+using namespace Swift;
+
+namespace Swift {
+
+class StanzaAckRequesterTest : public CppUnit::TestFixture {
+		CPPUNIT_TEST_SUITE(StanzaAckRequesterTest);
+		CPPUNIT_TEST(testHandleStanzaSent_RequestsAck);
+		CPPUNIT_TEST(testHandleAckReceived_AcksStanza);
+		CPPUNIT_TEST(testHandleAckReceived_AcksMultipleStanzas);
+		CPPUNIT_TEST(testHandleAckReceived_MultipleAcks);
+		CPPUNIT_TEST(testHandleAckReceived_WrapAround);
+		CPPUNIT_TEST_SUITE_END();
+
+	public:
+		void setUp() {
+			acksRequested = 0;
+		}
+
+		void testHandleStanzaSent_RequestsAck() {
+			std::auto_ptr<StanzaAckRequester> testling(createRequester());
+			testling->handleStanzaSent(createMessage("m1"));
+
+			CPPUNIT_ASSERT_EQUAL(1, acksRequested);
+		}
+
+		void testHandleAckReceived_AcksStanza() {
+			std::auto_ptr<StanzaAckRequester> testling(createRequester());
+			testling->handleStanzaSent(createMessage("m1"));
+
+			testling->handleAckReceived(1);
+
+			CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(ackedStanzas.size()));
+			CPPUNIT_ASSERT_EQUAL(String("m1"), ackedStanzas[0]->getID());
+		}
+
+		void testHandleAckReceived_AcksMultipleStanzas() {
+			std::auto_ptr<StanzaAckRequester> testling(createRequester());
+			testling->handleStanzaSent(createMessage("m1"));
+			testling->handleStanzaSent(createMessage("m2"));
+
+			testling->handleAckReceived(2);
+
+			CPPUNIT_ASSERT_EQUAL(2, static_cast<int>(ackedStanzas.size()));
+			CPPUNIT_ASSERT_EQUAL(String("m1"), ackedStanzas[0]->getID());
+			CPPUNIT_ASSERT_EQUAL(String("m2"), ackedStanzas[1]->getID());
+		}
+
+		void testHandleAckReceived_MultipleAcks() {
+			std::auto_ptr<StanzaAckRequester> testling(createRequester());
+			testling->handleStanzaSent(createMessage("m1"));
+			testling->handleAckReceived(1);
+
+			testling->handleStanzaSent(createMessage("m2"));
+			testling->handleStanzaSent(createMessage("m3"));
+			testling->handleAckReceived(3);
+
+			CPPUNIT_ASSERT_EQUAL(3, static_cast<int>(ackedStanzas.size()));
+			CPPUNIT_ASSERT_EQUAL(String("m1"), ackedStanzas[0]->getID());
+			CPPUNIT_ASSERT_EQUAL(String("m2"), ackedStanzas[1]->getID());
+			CPPUNIT_ASSERT_EQUAL(String("m3"), ackedStanzas[2]->getID());
+		}
+
+		// Handle stanza ack count wrapping, as per the XEP
+		void testHandleAckReceived_WrapAround() {
+			std::auto_ptr<StanzaAckRequester> testling(createRequester());
+			testling->lastHandledStanzasCount = boost::numeric_cast<unsigned int>((1ULL<<32) - 1);
+			testling->handleStanzaSent(createMessage("m1"));
+			testling->handleStanzaSent(createMessage("m2"));
+
+			testling->handleAckReceived(1);
+
+			CPPUNIT_ASSERT_EQUAL(2, static_cast<int>(ackedStanzas.size()));
+			CPPUNIT_ASSERT_EQUAL(String("m1"), ackedStanzas[0]->getID());
+			CPPUNIT_ASSERT_EQUAL(String("m2"), ackedStanzas[1]->getID());
+		}
+
+	private:
+		Message::ref createMessage(const String& id) {
+			Message::ref result(new Message());
+			result->setID(id);
+			return result;
+		}
+
+		StanzaAckRequester* createRequester() {
+			StanzaAckRequester* requester = new StanzaAckRequester();
+			requester->onRequestAck.connect(boost::bind(&StanzaAckRequesterTest::handleRequestAck, this));
+			requester->onStanzaAcked.connect(boost::bind(&StanzaAckRequesterTest::handleStanzaAcked, this, _1));
+			return requester;
+		}
+
+		void handleRequestAck() {
+			acksRequested++;
+		}
+
+		void handleStanzaAcked(boost::shared_ptr<Stanza> stanza) {
+			ackedStanzas.push_back(stanza);
+		}
+
+	private:
+		int acksRequested;
+		std::vector< boost::shared_ptr<Stanza> > ackedStanzas;
+};
+
+}
+
+CPPUNIT_TEST_SUITE_REGISTRATION(Swift::StanzaAckRequesterTest);
diff --git a/Swiften/StreamManagement/UnitTest/StanzaAckResponderTest.cpp b/Swiften/StreamManagement/UnitTest/StanzaAckResponderTest.cpp
new file mode 100644
index 0000000..fa2b782
--- /dev/null
+++ b/Swiften/StreamManagement/UnitTest/StanzaAckResponderTest.cpp
@@ -0,0 +1,98 @@
+/*
+ * Copyright (c) 2010 Remko Tronçon
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#include <cppunit/extensions/HelperMacros.h>
+#include <cppunit/extensions/TestFactoryRegistry.h>
+#include <boost/bind.hpp>
+#include <boost/numeric/conversion/cast.hpp>
+
+#include "Swiften/StreamManagement/StanzaAckResponder.h"
+#include "Swiften/Elements/Message.h"
+
+using namespace Swift;
+
+namespace Swift {
+
+class StanzaAckResponderTest : public CppUnit::TestFixture {
+		CPPUNIT_TEST_SUITE(StanzaAckResponderTest);
+		CPPUNIT_TEST(testHandleAckRequestReceived_AcksStanza);
+		CPPUNIT_TEST(testHandleAckRequestReceived_AcksMultipleStanzas);
+		CPPUNIT_TEST(testHandleAckRequestReceived_MultipleAcks);
+		CPPUNIT_TEST(testHandleAckRequestReceived_WrapAround);
+		CPPUNIT_TEST_SUITE_END();
+
+	public:
+		void testHandleAckRequestReceived_AcksStanza() {
+			std::auto_ptr<StanzaAckResponder> testling(createResponder());
+			testling->handleStanzaReceived();
+
+			testling->handleAckRequestReceived();
+
+			CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(acks.size()));
+			CPPUNIT_ASSERT_EQUAL(1U, acks[0]);
+		}
+
+		void testHandleAckRequestReceived_AcksMultipleStanzas() {
+			std::auto_ptr<StanzaAckResponder> testling(createResponder());
+			testling->handleStanzaReceived();
+			testling->handleStanzaReceived();
+
+			testling->handleAckRequestReceived();
+
+			CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(acks.size()));
+			CPPUNIT_ASSERT_EQUAL(2U, acks[0]);
+		}
+
+		void testHandleAckRequestReceived_MultipleAcks() {
+			std::auto_ptr<StanzaAckResponder> testling(createResponder());
+			testling->handleStanzaReceived();
+			testling->handleAckRequestReceived();
+
+			testling->handleStanzaReceived();
+			testling->handleAckRequestReceived();
+
+			CPPUNIT_ASSERT_EQUAL(2, static_cast<int>(acks.size()));
+			CPPUNIT_ASSERT_EQUAL(1U, acks[0]);
+			CPPUNIT_ASSERT_EQUAL(2U, acks[1]);
+		}
+
+		// Handle stanza ack count wrapping, as per the XEP
+		void testHandleAckRequestReceived_WrapAround() {
+			std::auto_ptr<StanzaAckResponder> testling(createResponder());
+			testling->handledStanzasCount = boost::numeric_cast<unsigned int>((1ULL<<32) - 1);
+			testling->handleStanzaReceived();
+			testling->handleStanzaReceived();
+
+			testling->handleAckRequestReceived();
+
+			CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(acks.size()));
+			CPPUNIT_ASSERT_EQUAL(1U, acks[0]);
+		}
+
+	private:
+		Message::ref createMessage(const String& id) {
+			Message::ref result(new Message());
+			result->setID(id);
+			return result;
+		}
+
+		StanzaAckResponder* createResponder() {
+			StanzaAckResponder* responder = new StanzaAckResponder();
+			responder->onAck.connect(boost::bind(&StanzaAckResponderTest::handleAck, this, _1));
+			return responder;
+		}
+
+		void handleAck(unsigned int h) {
+			acks.push_back(h);
+		}
+
+	private:
+		std::vector<unsigned int> acks;
+};
+
+}
+
+CPPUNIT_TEST_SUITE_REGISTRATION(Swift::StanzaAckResponderTest);
-- 
cgit v0.10.2-6-g49f6