From 46b13c4d25270c6933d20c6aa790619e30e4cfe8 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Remko=20Tron=C3=A7on?= <git@el-tramo.be>
Date: Sun, 29 Aug 2010 17:20:29 +0200
Subject: Added stanza acking support to client.


diff --git a/BuildTools/Eclipse/Swift (Mac OS X).launch b/BuildTools/Eclipse/Swift (Mac OS X).launch
index aeb7576..16126ac 100644
--- a/BuildTools/Eclipse/Swift (Mac OS X).launch	
+++ b/BuildTools/Eclipse/Swift (Mac OS X).launch	
@@ -4,7 +4,7 @@
 <stringAttribute key="org.eclipse.cdt.launch.COREFILE_PATH" value=""/>
 <stringAttribute key="org.eclipse.cdt.launch.PROGRAM_NAME" value="Swift/QtUI/Swift.app/Contents/MacOS/Swift"/>
 <stringAttribute key="org.eclipse.cdt.launch.PROJECT_ATTR" value="swift"/>
-<stringAttribute key="org.eclipse.cdt.launch.PROJECT_BUILD_CONFIG_ID_ATTR" value="0.980756260.1834106966"/>
+<stringAttribute key="org.eclipse.cdt.launch.PROJECT_BUILD_CONFIG_ID_ATTR" value="0.980756260"/>
 <booleanAttribute key="org.eclipse.cdt.launch.use_terminal" value="true"/>
 <listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_PATHS">
 <listEntry value="/swift"/>
diff --git a/Swift/QtUI/QtXMLConsoleWidget.cpp b/Swift/QtUI/QtXMLConsoleWidget.cpp
index 00f9d3b..1e7eff3 100644
--- a/Swift/QtUI/QtXMLConsoleWidget.cpp
+++ b/Swift/QtUI/QtXMLConsoleWidget.cpp
@@ -71,22 +71,29 @@ void QtXMLConsoleWidget::closeEvent(QCloseEvent* event) {
 }
 
 void QtXMLConsoleWidget::handleDataRead(const String& data) {
-	textEdit->setTextColor(QColor(33,98,33));
-	appendTextIfEnabled(data);
+	appendTextIfEnabled(data, QColor(33,98,33));
 }
 
 void QtXMLConsoleWidget::handleDataWritten(const String& data) {
-	textEdit->setTextColor(QColor(155,1,0));
-	appendTextIfEnabled(data);
+	appendTextIfEnabled(data, QColor(155,1,0));
 }
 
-void QtXMLConsoleWidget::appendTextIfEnabled(const String& data) {
+void QtXMLConsoleWidget::appendTextIfEnabled(const String& data, const QColor& color) {
 	if (enabled->isChecked()) {
 		QScrollBar* scrollBar = textEdit->verticalScrollBar();
 		bool scrollToBottom = (!scrollBar || scrollBar->value() == scrollBar->maximum());
-		textEdit->append(P2QSTRING(data));
+
+		QTextCursor cursor(textEdit->document());
+		cursor.beginEditBlock();
+		cursor.movePosition(QTextCursor::End);
+		QTextCharFormat format;
+		format.setForeground(QBrush(color));
+		cursor.mergeCharFormat(format);
+		cursor.insertText(P2QSTRING(data));
+		cursor.endEditBlock();
+
 		if (scrollToBottom) {
-			textEdit->ensureCursorVisible();
+			scrollBar->setValue(scrollBar->maximum());
 		}
 	}
 }
diff --git a/Swift/QtUI/QtXMLConsoleWidget.h b/Swift/QtUI/QtXMLConsoleWidget.h
index 28b15e8..1cfe54f 100644
--- a/Swift/QtUI/QtXMLConsoleWidget.h
+++ b/Swift/QtUI/QtXMLConsoleWidget.h
@@ -11,6 +11,7 @@
 
 class QTextEdit;
 class QCheckBox;
+class QColor;
 
 namespace Swift {
 	class QtXMLConsoleWidget : public QtTabbable, public XMLConsoleWidget {
@@ -29,7 +30,7 @@ namespace Swift {
 			virtual void closeEvent(QCloseEvent* event);
 			virtual void showEvent(QShowEvent* event);
 
-			void appendTextIfEnabled(const String& data);
+			void appendTextIfEnabled(const String& data, const QColor& color);
 
 		private:
 			QTextEdit* textEdit;
diff --git a/Swiften/Client/Client.cpp b/Swiften/Client/Client.cpp
index 763c83e..2406b0f 100644
--- a/Swiften/Client/Client.cpp
+++ b/Swiften/Client/Client.cpp
@@ -81,9 +81,10 @@ void Client::handleConnectorFinished(boost::shared_ptr<Connection> connection, C
 
 		session_ = ClientSession::create(jid_, sessionStream_);
 		session_->onInitialized.connect(boost::bind(boost::ref(onConnected)));
+		session_->onStanzaAcked.connect(boost::bind(&Client::handleStanzaAcked, this, _1));
 		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_->onStanzaReceived.connect(boost::bind(&Client::handleStanza, this, _1));
 		session_->start();
 	}
 }
@@ -115,7 +116,7 @@ void Client::send(boost::shared_ptr<Stanza> stanza) {
 		std::cerr << "Warning: Client: Trying to send a stanza while disconnected." << std::endl;
 		return;
 	}
-	session_->sendElement(stanza);
+	session_->sendStanza(stanza);
 }
 
 void Client::sendIQ(boost::shared_ptr<IQ> iq) {
@@ -134,20 +135,20 @@ 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);
+void Client::handleStanza(boost::shared_ptr<Stanza> stanza) {
+	boost::shared_ptr<Message> message = boost::dynamic_pointer_cast<Message>(stanza);
 	if (message) {
 		onMessageReceived(message);
 		return;
 	}
 
-	boost::shared_ptr<Presence> presence = boost::dynamic_pointer_cast<Presence>(element);
+	boost::shared_ptr<Presence> presence = boost::dynamic_pointer_cast<Presence>(stanza);
 	if (presence) {
 		onPresenceReceived(presence);
 		return;
 	}
 
-	boost::shared_ptr<IQ> iq = boost::dynamic_pointer_cast<IQ>(element);
+	boost::shared_ptr<IQ> iq = boost::dynamic_pointer_cast<IQ>(stanza);
 	if (iq) {
 		onIQReceived(iq);
 		return;
@@ -222,6 +223,13 @@ void Client::handleNeedCredentials() {
 	session_->sendCredentials(password_);
 }
 
+bool Client::getStreamManagementEnabled() const {
+	if (session_) {
+		return session_->getStreamManagementEnabled();
+	}
+	return false;
+}
+
 void Client::handleDataRead(const String& data) {
 	onDataRead(data);
 }
@@ -230,4 +238,8 @@ void Client::handleDataWritten(const String& data) {
 	onDataWritten(data);
 }
 
+void Client::handleStanzaAcked(boost::shared_ptr<Stanza> stanza) {
+	onStanzaAcked(stanza);
+}
+
 }
diff --git a/Swiften/Client/Client.h b/Swiften/Client/Client.h
index 313f856..10e7c38 100644
--- a/Swiften/Client/Client.h
+++ b/Swiften/Client/Client.h
@@ -44,6 +44,8 @@ namespace Swift {
 			
 			bool isAvailable();
 
+			bool getStreamManagementEnabled() const;
+
 			virtual void sendIQ(boost::shared_ptr<IQ>);
 			virtual void sendMessage(boost::shared_ptr<Message>);
 			virtual void sendPresence(boost::shared_ptr<Presence>);
@@ -53,16 +55,18 @@ namespace Swift {
 			boost::signal<void ()> onConnected;
 			boost::signal<void (const String&)> onDataRead;
 			boost::signal<void (const String&)> onDataWritten;
+			boost::signal<void (boost::shared_ptr<Stanza>)> onStanzaAcked;
 
 		private:
 			void handleConnectorFinished(boost::shared_ptr<Connection>, Connector::ref);
 			void send(boost::shared_ptr<Stanza>);
 			virtual String getNewIQID();
-			void handleElement(boost::shared_ptr<Element>);
+			void handleStanza(boost::shared_ptr<Stanza>);
 			void handleSessionFinished(boost::shared_ptr<Error>);
 			void handleNeedCredentials();
 			void handleDataRead(const String&);
 			void handleDataWritten(const String&);
+			void handleStanzaAcked(boost::shared_ptr<Stanza>);
 
 			void closeConnection();
 
diff --git a/Swiften/Client/ClientSession.cpp b/Swiften/Client/ClientSession.cpp
index 720bc6d..551d835 100644
--- a/Swiften/Client/ClientSession.cpp
+++ b/Swiften/Client/ClientSession.cpp
@@ -24,7 +24,12 @@
 #include "Swiften/Elements/Compressed.h"
 #include "Swiften/Elements/CompressFailure.h"
 #include "Swiften/Elements/CompressRequest.h"
+#include "Swiften/Elements/EnableStreamManagement.h"
+#include "Swiften/Elements/StreamManagementEnabled.h"
+#include "Swiften/Elements/StreamManagementFailed.h"
 #include "Swiften/Elements/StartSession.h"
+#include "Swiften/Elements/StanzaAck.h"
+#include "Swiften/Elements/StanzaAckRequest.h"
 #include "Swiften/Elements/IQ.h"
 #include "Swiften/Elements/ResourceBind.h"
 #include "Swiften/SASL/PLAINClientAuthenticator.h"
@@ -42,6 +47,7 @@ ClientSession::ClientSession(
 			stream(stream),
 			allowPLAINOverNonTLS(false),
 			needSessionStart(false),
+			needResourceBind(false),
 			authenticator(NULL) {
 }
 
@@ -65,8 +71,11 @@ void ClientSession::sendStreamHeader() {
 	stream->writeHeader(header);
 }
 
-void ClientSession::sendElement(boost::shared_ptr<Element> element) {
-	stream->writeElement(element);
+void ClientSession::sendStanza(boost::shared_ptr<Stanza> stanza) {
+	stream->writeElement(stanza);
+	if (stanzaAckRequester_) {
+		stanzaAckRequester_->handleStanzaSent(stanza);
+	}
 }
 
 void ClientSession::handleStreamStart(const ProtocolHeader&) {
@@ -75,8 +84,77 @@ void ClientSession::handleStreamStart(const ProtocolHeader&) {
 }
 
 void ClientSession::handleElement(boost::shared_ptr<Element> element) {
-	if (getState() == Initialized) {
-		onElementReceived(element);
+	if (boost::shared_ptr<Stanza> stanza = boost::dynamic_pointer_cast<Stanza>(element)) {
+		if (stanzaAckResponder_) {
+			stanzaAckResponder_->handleStanzaReceived();
+		}
+		if (getState() == Initialized) {
+			onStanzaReceived(stanza);
+		}
+		else if (boost::shared_ptr<IQ> iq = boost::dynamic_pointer_cast<IQ>(element)) {
+			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);
+					}
+					needResourceBind = false;
+					continueSessionInitialization();
+				}
+				else {
+					finishSession(Error::UnexpectedElementError);
+				}
+			}
+			else if (state == StartingSession) {
+				if (iq->getType() == IQ::Result) {
+					needSessionStart = false;
+					continueSessionInitialization();
+				}
+				else if (iq->getType() == IQ::Error) {
+					finishSession(Error::SessionStartError);
+				}
+				else {
+					finishSession(Error::UnexpectedElementError);
+				}
+			}
+			else {
+				finishSession(Error::UnexpectedElementError);
+			}
+		}
+	}
+	else if (boost::dynamic_pointer_cast<StanzaAckRequest>(element)) {
+		if (stanzaAckResponder_) {
+			stanzaAckResponder_->handleAckRequestReceived();
+		}
+	}
+	else if (boost::shared_ptr<StanzaAck> ack = boost::dynamic_pointer_cast<StanzaAck>(element)) {
+		if (stanzaAckRequester_) {
+			if (ack->isValid()) {
+				stanzaAckRequester_->handleAckReceived(ack->getHandledStanzasCount());
+			}
+			else {
+				std::cerr << "Warning: Got invalid ack from server" << std::endl;
+			}
+		}
+		else {
+			std::cerr << "Warning: Ignoring ack" << std::endl;
+		}
+	}
+	else if (getState() == Initialized) {
+		boost::shared_ptr<Stanza> stanza = boost::dynamic_pointer_cast<Stanza>(element);
+		if (stanza) {
+			if (stanzaAckResponder_) {
+				stanzaAckResponder_->handleStanzaReceived();
+			}
+			onStanzaReceived(stanza);
+		}
 	}
 	else if (StreamFeatures* streamFeatures = dynamic_cast<StreamFeatures*>(element.get())) {
 		if (!checkState(Negotiating)) {
@@ -133,25 +211,14 @@ void ClientSession::handleElement(boost::shared_ptr<Element> element) {
 		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();
+			needSessionStart = streamFeatures->hasSession();
+			needResourceBind = streamFeatures->hasResourceBind();
+			if (streamFeatures->hasStreamManagement()) {
+				state = EnablingSessionManagement;
+				stream->writeElement(boost::shared_ptr<EnableStreamManagement>(new EnableStreamManagement()));
 			}
 			else {
-				state = Initialized;
-				onInitialized();
+				continueSessionInitialization();
 			}
 		}
 	}
@@ -165,6 +232,17 @@ void ClientSession::handleElement(boost::shared_ptr<Element> element) {
 	else if (boost::dynamic_pointer_cast<CompressFailure>(element)) {
 		finishSession(Error::CompressionFailedError);
 	}
+	else if (boost::dynamic_pointer_cast<StreamManagementEnabled>(element)) {
+		stanzaAckRequester_ = boost::shared_ptr<StanzaAckRequester>(new StanzaAckRequester());
+		stanzaAckRequester_->onRequestAck.connect(boost::bind(&ClientSession::requestAck, this));
+		stanzaAckRequester_->onStanzaAcked.connect(boost::bind(&ClientSession::handleStanzaAcked, this, _1));
+		stanzaAckResponder_ = boost::shared_ptr<StanzaAckResponder>(new StanzaAckResponder());
+		stanzaAckResponder_->onAck.connect(boost::bind(&ClientSession::ack, this, _1));
+		continueSessionInitialization();
+	}
+	else if (boost::dynamic_pointer_cast<StreamManagementFailed>(element)) {
+		continueSessionInitialization();
+	}
 	else if (AuthChallenge* challenge = dynamic_cast<AuthChallenge*>(element.get())) {
 		checkState(Authenticating);
 		assert(authenticator);
@@ -201,47 +279,6 @@ void ClientSession::handleElement(boost::shared_ptr<Element> element) {
 	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;
@@ -249,9 +286,23 @@ void ClientSession::handleElement(boost::shared_ptr<Element> element) {
 	}
 }
 
-void ClientSession::sendSessionStart() {
-	state = StartingSession;
-	stream->writeElement(IQ::createRequest(IQ::Set, JID(), "session-start", boost::shared_ptr<StartSession>(new StartSession())));
+void ClientSession::continueSessionInitialization() {
+	if (needResourceBind) {
+		state = BindingResource;
+		boost::shared_ptr<ResourceBind> resourceBind(new ResourceBind());
+		if (!localJID.getResource().isEmpty()) {
+			resourceBind->setResource(localJID.getResource());
+		}
+		sendStanza(IQ::createRequest(IQ::Set, JID(), "session-bind", resourceBind));
+	}
+	else if (needSessionStart) {
+		state = StartingSession;
+		sendStanza(IQ::createRequest(IQ::Set, JID(), "session-start", boost::shared_ptr<StartSession>(new StartSession())));
+	}
+	else {
+		state = Initialized;
+		onInitialized();
+	}
 }
 
 bool ClientSession::checkState(State state) {
@@ -298,4 +349,16 @@ void ClientSession::finishSession(boost::shared_ptr<Swift::Error> error) {
 }
 
 
+void ClientSession::requestAck() {
+	stream->writeElement(boost::shared_ptr<StanzaAckRequest>(new StanzaAckRequest()));
+}
+
+void ClientSession::handleStanzaAcked(boost::shared_ptr<Stanza> stanza) {
+	onStanzaAcked(stanza);
+}
+
+void ClientSession::ack(unsigned int handledStanzasCount) {
+	stream->writeElement(boost::shared_ptr<StanzaAck>(new StanzaAck(handledStanzasCount)));
+}
+
 }
diff --git a/Swiften/Client/ClientSession.h b/Swiften/Client/ClientSession.h
index 936fd18..c15508a 100644
--- a/Swiften/Client/ClientSession.h
+++ b/Swiften/Client/ClientSession.h
@@ -16,6 +16,8 @@
 #include "Swiften/Base/String.h"
 #include "Swiften/JID/JID.h"
 #include "Swiften/Elements/Element.h"
+#include "Swiften/StreamManagement/StanzaAckRequester.h"
+#include "Swiften/StreamManagement/StanzaAckResponder.h"
 
 namespace Swift {
 	class ClientAuthenticator;
@@ -31,6 +33,7 @@ namespace Swift {
 				Encrypting,
 				WaitingForCredentials,
 				Authenticating,
+				EnablingSessionManagement,
 				BindingResource,
 				StartingSession,
 				Initialized,
@@ -65,17 +68,22 @@ namespace Swift {
 				allowPLAINOverNonTLS = b;
 			}
 
+			bool getStreamManagementEnabled() const {
+				return stanzaAckRequester_;
+			}
+
 			void start();
 			void finish();
 
 			void sendCredentials(const String& password);
-			void sendElement(boost::shared_ptr<Element> element);
+			void sendStanza(boost::shared_ptr<Stanza>);
 
 		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;
+			boost::signal<void (boost::shared_ptr<Stanza>)> onStanzaReceived;
+			boost::signal<void (boost::shared_ptr<Stanza>)> onStanzaAcked;
 		
 		private:
 			ClientSession(
@@ -90,7 +98,6 @@ namespace Swift {
 			}
 
 			void sendStreamHeader();
-			void sendSessionStart();
 
 			void handleElement(boost::shared_ptr<Element>);
 			void handleStreamStart(const ProtocolHeader&);
@@ -99,6 +106,11 @@ namespace Swift {
 			void handleTLSEncrypted();
 
 			bool checkState(State);
+			void continueSessionInitialization();
+
+			void requestAck();
+			void handleStanzaAcked(boost::shared_ptr<Stanza> stanza);
+			void ack(unsigned int handledStanzasCount);
 
 		private:
 			JID localJID;
@@ -106,6 +118,9 @@ namespace Swift {
 			boost::shared_ptr<SessionStream> stream;
 			bool allowPLAINOverNonTLS;
 			bool needSessionStart;
+			bool needResourceBind;
 			ClientAuthenticator* authenticator;
+			boost::shared_ptr<StanzaAckRequester> stanzaAckRequester_;
+			boost::shared_ptr<StanzaAckResponder> stanzaAckResponder_;
 	};
 }
diff --git a/Swiften/Client/UnitTest/ClientSessionTest.cpp b/Swiften/Client/UnitTest/ClientSessionTest.cpp
index 180eab8..e27f130 100644
--- a/Swiften/Client/UnitTest/ClientSessionTest.cpp
+++ b/Swiften/Client/UnitTest/ClientSessionTest.cpp
@@ -19,6 +19,11 @@
 #include "Swiften/Elements/AuthRequest.h"
 #include "Swiften/Elements/AuthSuccess.h"
 #include "Swiften/Elements/AuthFailure.h"
+#include "Swiften/Elements/StreamManagementEnabled.h"
+#include "Swiften/Elements/StreamManagementFailed.h"
+#include "Swiften/Elements/EnableStreamManagement.h"
+#include "Swiften/Elements/IQ.h"
+#include "Swiften/Elements/ResourceBind.h"
 
 using namespace Swift;
 
@@ -31,6 +36,8 @@ class ClientSessionTest : public CppUnit::TestFixture {
 		CPPUNIT_TEST(testAuthenticate);
 		CPPUNIT_TEST(testAuthenticate_Unauthorized);
 		CPPUNIT_TEST(testAuthenticate_NoValidAuthMechanisms);
+		CPPUNIT_TEST(testStreamManagement);
+		CPPUNIT_TEST(testStreamManagement_Failed);
 		/*
 		CPPUNIT_TEST(testResourceBind);
 		CPPUNIT_TEST(testResourceBind_ChangeResource);
@@ -163,6 +170,46 @@ class ClientSessionTest : public CppUnit::TestFixture {
 			CPPUNIT_ASSERT(sessionFinishedError);
 		}
 
+		void testStreamManagement() {
+			boost::shared_ptr<ClientSession> session(createSession());
+			session->start();
+			server->receiveStreamStart();
+			server->sendStreamStart();
+			server->sendStreamFeaturesWithPLAINAuthentication();
+			session->sendCredentials("mypass");
+			server->receiveAuthRequest("PLAIN");
+			server->sendAuthSuccess();
+			server->receiveStreamStart();
+			server->sendStreamStart();
+			server->sendStreamFeaturesWithBindAndStreamManagement();
+			server->receiveStreamManagementEnable();
+			server->sendStreamManagementEnabled();
+
+			CPPUNIT_ASSERT(session->getStreamManagementEnabled());
+			server->receiveBind();
+			// TODO: Test if the requesters & responders do their work
+		}
+
+		void testStreamManagement_Failed() {
+			boost::shared_ptr<ClientSession> session(createSession());
+			session->start();
+			server->receiveStreamStart();
+			server->sendStreamStart();
+			server->sendStreamFeaturesWithPLAINAuthentication();
+			session->sendCredentials("mypass");
+			server->receiveAuthRequest("PLAIN");
+			server->sendAuthSuccess();
+			server->receiveStreamStart();
+			server->sendStreamStart();
+			server->sendStreamFeaturesWithBindAndStreamManagement();
+			server->receiveStreamManagementEnable();
+			server->sendStreamManagementFailed();
+
+			CPPUNIT_ASSERT(!session->getStreamManagementEnabled());
+			server->receiveBind();
+		}
+
+
 	private:
 		boost::shared_ptr<ClientSession> createSession() {
 			boost::shared_ptr<ClientSession> session = ClientSession::create(JID("me@foo.com"), server);
@@ -277,6 +324,13 @@ class ClientSessionTest : public CppUnit::TestFixture {
 					onElementReceived(streamFeatures);
 				}
 
+				void sendStreamFeaturesWithBindAndStreamManagement() {
+					boost::shared_ptr<StreamFeatures> streamFeatures(new StreamFeatures());
+					streamFeatures->setHasResourceBind();
+					streamFeatures->setHasStreamManagement();
+					onElementReceived(streamFeatures);
+				}
+
 				void sendAuthSuccess() {
 					onElementReceived(boost::shared_ptr<AuthSuccess>(new AuthSuccess()));
 				}
@@ -285,6 +339,14 @@ class ClientSessionTest : public CppUnit::TestFixture {
 					onElementReceived(boost::shared_ptr<AuthFailure>(new AuthFailure()));
 				}
 
+				void sendStreamManagementEnabled() {
+					onElementReceived(boost::shared_ptr<StreamManagementEnabled>(new StreamManagementEnabled()));
+				}
+
+				void sendStreamManagementFailed() {
+					onElementReceived(boost::shared_ptr<StreamManagementFailed>(new StreamManagementFailed()));
+				}
+
 				void receiveStreamStart() {
 					Event event = popEvent();
 					CPPUNIT_ASSERT(event.header);
@@ -304,6 +366,20 @@ class ClientSessionTest : public CppUnit::TestFixture {
 					CPPUNIT_ASSERT_EQUAL(mech, request->getMechanism());
 				}
 
+				void receiveStreamManagementEnable() {
+					Event event = popEvent();
+					CPPUNIT_ASSERT(event.element);
+					CPPUNIT_ASSERT(boost::dynamic_pointer_cast<EnableStreamManagement>(event.element));
+				}
+
+				void receiveBind() {
+					Event event = popEvent();
+					CPPUNIT_ASSERT(event.element);
+					boost::shared_ptr<IQ> iq = boost::dynamic_pointer_cast<IQ>(event.element);
+					CPPUNIT_ASSERT(iq);
+					CPPUNIT_ASSERT(iq->getPayload<ResourceBind>());
+				}
+
 				Event popEvent() {
 					CPPUNIT_ASSERT(receivedEvents.size() > 0);
 					Event event = receivedEvents.front();
diff --git a/Swiften/Elements/StanzaAck.h b/Swiften/Elements/StanzaAck.h
index eaf4e26..53b62b4 100644
--- a/Swiften/Elements/StanzaAck.h
+++ b/Swiften/Elements/StanzaAck.h
@@ -13,6 +13,7 @@ namespace Swift {
 	class StanzaAck : public Element, public Shared<StanzaAck> {
 		public:
 			StanzaAck() : valid(false), handledStanzasCount(0) {}
+			StanzaAck(unsigned int handledStanzasCount) : valid(true), handledStanzasCount(handledStanzasCount) {}
 
 			unsigned int getHandledStanzasCount() const {
 				return handledStanzasCount;
diff --git a/Swiften/StreamManagement/StanzaAckResponder.cpp b/Swiften/StreamManagement/StanzaAckResponder.cpp
index 05ab5c4..5b71f91 100644
--- a/Swiften/StreamManagement/StanzaAckResponder.cpp
+++ b/Swiften/StreamManagement/StanzaAckResponder.cpp
@@ -20,5 +20,7 @@ void StanzaAckResponder::handleStanzaReceived() {
 }
 
 void StanzaAckResponder::handleAckRequestReceived() {
-	onAck(handledStanzasCount);}
+	onAck(handledStanzasCount);
+}
+
 }
-- 
cgit v0.10.2-6-g49f6