From 01385d84f07466550fff702079555c65963463a8 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Remko=20Tron=C3=A7on?= <git@el-tramo.be>
Date: Fri, 8 Apr 2011 23:58:03 +0200
Subject: Added XEP-0237 Roster Versioning support.

Resolves: #803
Release-Notes: Added support for XEP-0237 Roster Versioning

diff --git a/Swiften/Client/Client.cpp b/Swiften/Client/Client.cpp
index 7918c46..c6ada7c 100644
--- a/Swiften/Client/Client.cpp
+++ b/Swiften/Client/Client.cpp
@@ -25,6 +25,7 @@
 #include "Swiften/Presence/SubscriptionManager.h"
 #include "Swiften/TLS/BlindCertificateTrustChecker.h"
 #include <Swiften/Client/NickManagerImpl.h>
+#include <Swiften/Client/ClientSession.h>
 
 namespace Swift {
 
@@ -35,7 +36,7 @@ Client::Client(const JID& jid, const std::string& password, NetworkFactories* ne
 	softwareVersionResponder->start();
 
 	roster = new XMPPRosterImpl();
-	rosterController = new XMPPRosterController(getIQRouter(), roster);
+	rosterController = new XMPPRosterController(getIQRouter(), roster, storages->getRosterStorage());
 
 	subscriptionManager = new SubscriptionManager(getStanzaChannel());
 
@@ -98,6 +99,11 @@ void Client::setSoftwareVersion(const std::string& name, const std::string& vers
 }
 
 void Client::requestRoster() {
+	// FIXME: We should set this once when the session is finished, but there
+	// is currently no callback for this
+	if (getSession()) {
+		rosterController->setUseVersioning(getSession()->getRosterVersioningSupported());
+	}
 	rosterController->requestRoster();
 }
 
diff --git a/Swiften/Client/ClientSession.cpp b/Swiften/Client/ClientSession.cpp
index e1c1d8e..cadc9a4 100644
--- a/Swiften/Client/ClientSession.cpp
+++ b/Swiften/Client/ClientSession.cpp
@@ -54,6 +54,7 @@ ClientSession::ClientSession(
 			needSessionStart(false),
 			needResourceBind(false),
 			needAcking(false),
+			rosterVersioningSupported(false),
 			authenticator(NULL),
 			certificateTrustChecker(NULL) {
 }
@@ -223,6 +224,7 @@ void ClientSession::handleElement(boost::shared_ptr<Element> element) {
 		}
 		else {
 			// Start the session
+			rosterVersioningSupported = streamFeatures->hasRosterVersioning();
 			stream->setWhitespacePingEnabled(true);
 			needSessionStart = streamFeatures->hasSession();
 			needResourceBind = streamFeatures->hasResourceBind();
diff --git a/Swiften/Client/ClientSession.h b/Swiften/Client/ClientSession.h
index 25ee694..4447f57 100644
--- a/Swiften/Client/ClientSession.h
+++ b/Swiften/Client/ClientSession.h
@@ -89,6 +89,10 @@ namespace Swift {
 				return stanzaAckRequester_;
 			}
 
+			bool getRosterVersioningSupported() const {
+				return rosterVersioningSupported;
+			}
+
 			const JID& getLocalJID() const {
 				return localJID;
 			}
@@ -153,6 +157,7 @@ namespace Swift {
 			bool needSessionStart;
 			bool needResourceBind;
 			bool needAcking;
+			bool rosterVersioningSupported;
 			ClientAuthenticator* authenticator;
 			boost::shared_ptr<StanzaAckRequester> stanzaAckRequester_;
 			boost::shared_ptr<StanzaAckResponder> stanzaAckResponder_;
diff --git a/Swiften/Client/CoreClient.h b/Swiften/Client/CoreClient.h
index 060c699..ceb0b0a 100644
--- a/Swiften/Client/CoreClient.h
+++ b/Swiften/Client/CoreClient.h
@@ -196,6 +196,11 @@ namespace Swift {
 			 */
 			boost::signal<void (boost::shared_ptr<Stanza>)> onStanzaAcked;
 
+		protected:
+			boost::shared_ptr<ClientSession> getSession() const {
+				return session_;
+			}
+
 		private:
 			void handleConnectorFinished(boost::shared_ptr<Connection>);
 			void handleStanzaChannelAvailableChanged(bool available);
diff --git a/Swiften/Client/FileStorages.cpp b/Swiften/Client/FileStorages.cpp
index 3c76c46..e6a93fd 100644
--- a/Swiften/Client/FileStorages.cpp
+++ b/Swiften/Client/FileStorages.cpp
@@ -8,6 +8,7 @@
 #include "Swiften/VCards/VCardFileStorage.h"
 #include "Swiften/Avatars/AvatarFileStorage.h"
 #include "Swiften/Disco/CapsFileStorage.h"
+#include "Swiften/Roster/RosterFileStorage.h"
 
 namespace Swift {
 
@@ -16,9 +17,11 @@ FileStorages::FileStorages(const boost::filesystem::path& baseDir, const JID& ji
 	vcardStorage = new VCardFileStorage(baseDir / profile / "vcards");
 	capsStorage = new CapsFileStorage(baseDir / "caps");
 	avatarStorage = new AvatarFileStorage(baseDir / "avatars", baseDir / profile / "avatars");
+	rosterStorage = new RosterFileStorage(baseDir / profile / "roster.xml");
 }
 
 FileStorages::~FileStorages() {
+	delete rosterStorage;
 	delete avatarStorage;
 	delete capsStorage;
 	delete vcardStorage;
@@ -36,4 +39,8 @@ AvatarStorage* FileStorages::getAvatarStorage() const {
 	return avatarStorage;
 }
 
+RosterStorage* FileStorages::getRosterStorage() const {
+	return rosterStorage;
+}
+
 }
diff --git a/Swiften/Client/FileStorages.h b/Swiften/Client/FileStorages.h
index fc05c86..28df314 100644
--- a/Swiften/Client/FileStorages.h
+++ b/Swiften/Client/FileStorages.h
@@ -14,6 +14,7 @@ namespace Swift {
 	class VCardFileStorage;
 	class AvatarFileStorage;
 	class CapsFileStorage;
+	class RosterFileStorage;
 	class JID;
 
 	/**
@@ -41,10 +42,12 @@ namespace Swift {
 			virtual VCardStorage* getVCardStorage() const;
 			virtual AvatarStorage* getAvatarStorage() const;
 			virtual CapsStorage* getCapsStorage() const;
+			virtual RosterStorage* getRosterStorage() const;
 
 		private:
 			VCardFileStorage* vcardStorage;
 			AvatarFileStorage* avatarStorage;
 			CapsFileStorage* capsStorage;
+			RosterFileStorage* rosterStorage;
 	};
 }
diff --git a/Swiften/Client/MemoryStorages.cpp b/Swiften/Client/MemoryStorages.cpp
index 5f6371b..6941add 100644
--- a/Swiften/Client/MemoryStorages.cpp
+++ b/Swiften/Client/MemoryStorages.cpp
@@ -8,6 +8,7 @@
 #include "Swiften/VCards/VCardMemoryStorage.h"
 #include "Swiften/Avatars/AvatarMemoryStorage.h"
 #include "Swiften/Disco/CapsMemoryStorage.h"
+#include "Swiften/Roster/RosterMemoryStorage.h"
 
 namespace Swift {
 
@@ -15,9 +16,11 @@ MemoryStorages::MemoryStorages() {
 	vcardStorage = new VCardMemoryStorage();
 	capsStorage = new CapsMemoryStorage();
 	avatarStorage = new AvatarMemoryStorage();
+	rosterStorage = new RosterMemoryStorage();
 }
 
 MemoryStorages::~MemoryStorages() {
+	delete rosterStorage;
 	delete avatarStorage;
 	delete capsStorage;
 	delete vcardStorage;
@@ -35,4 +38,9 @@ AvatarStorage* MemoryStorages::getAvatarStorage() const {
 	return avatarStorage;
 }
 
+RosterStorage* MemoryStorages::getRosterStorage() const {
+	return rosterStorage;
+}
+
+
 }
diff --git a/Swiften/Client/MemoryStorages.h b/Swiften/Client/MemoryStorages.h
index 67025cd..1e1a596 100644
--- a/Swiften/Client/MemoryStorages.h
+++ b/Swiften/Client/MemoryStorages.h
@@ -23,10 +23,12 @@ namespace Swift {
 			virtual VCardStorage* getVCardStorage() const;
 			virtual AvatarStorage* getAvatarStorage() const;
 			virtual CapsStorage* getCapsStorage() const;
+			virtual RosterStorage* getRosterStorage() const;
 
 		private:
 			VCardMemoryStorage* vcardStorage;
 			AvatarStorage* avatarStorage;
 			CapsStorage* capsStorage;
+			RosterStorage* rosterStorage;
 	};
 }
diff --git a/Swiften/Client/Storages.cpp b/Swiften/Client/Storages.cpp
new file mode 100644
index 0000000..3c2dbc5
--- /dev/null
+++ b/Swiften/Client/Storages.cpp
@@ -0,0 +1,12 @@
+/*
+ * Copyright (c) 2010 Remko Tronçon
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#include <Swiften/Client/Storages.h>
+
+using namespace Swift;
+
+Storages::~Storages() {
+}
diff --git a/Swiften/Client/Storages.h b/Swiften/Client/Storages.h
index e62f0a9..1c5bbe9 100644
--- a/Swiften/Client/Storages.h
+++ b/Swiften/Client/Storages.h
@@ -10,6 +10,7 @@ namespace Swift {
 	class VCardStorage;
 	class AvatarStorage;
 	class CapsStorage;
+	class RosterStorage;
 
 	/**
 	 * An interface to hold storage classes for different
@@ -17,10 +18,11 @@ namespace Swift {
 	 */
 	class Storages {
 		public:
-			virtual ~Storages() {}
+			virtual ~Storages();
 
 			virtual VCardStorage* getVCardStorage() const = 0;
 			virtual AvatarStorage* getAvatarStorage() const = 0;
 			virtual CapsStorage* getCapsStorage() const = 0;
+			virtual RosterStorage* getRosterStorage() const = 0;
 	};
 }
diff --git a/Swiften/Elements/RosterItemPayload.h b/Swiften/Elements/RosterItemPayload.h
index b8a1b10..915ae31 100644
--- a/Swiften/Elements/RosterItemPayload.h
+++ b/Swiften/Elements/RosterItemPayload.h
@@ -4,22 +4,20 @@
  * See Documentation/Licenses/GPLv3.txt for more information.
  */
 
-#ifndef SWIFTEN_RosterItemPayloadPayload_H
-#define SWIFTEN_RosterItemPayloadPayload_H
+#pragma once
 
 #include <vector>
-
-#include "Swiften/JID/JID.h"
 #include <string>
 
+#include <Swiften/JID/JID.h>
+
 namespace Swift {
-	class RosterItemPayload
-	{
+	class RosterItemPayload {
 		public:
 			enum Subscription { None, To, From, Both, Remove };
 
 			RosterItemPayload() : subscription_(None), ask_(false) {}
-			RosterItemPayload(const JID& jid, const std::string& name, Subscription subscription) : jid_(jid), name_(name), subscription_(subscription), ask_(false) { }
+			RosterItemPayload(const JID& jid, const std::string& name, Subscription subscription, const std::vector<std::string>& groups = std::vector<std::string>()) : jid_(jid), name_(name), subscription_(subscription), groups_(groups), ask_(false) { }
 			
 			void setJID(const JID& jid) { jid_ = jid; }
 			const JID& getJID() const { return jid_; }
@@ -51,5 +49,3 @@ namespace Swift {
 			std::string unknownContent_;
 	};
 }
-
-#endif
diff --git a/Swiften/Elements/RosterPayload.h b/Swiften/Elements/RosterPayload.h
index b46b384..3102f0e 100644
--- a/Swiften/Elements/RosterPayload.h
+++ b/Swiften/Elements/RosterPayload.h
@@ -33,7 +33,16 @@ namespace Swift {
 				return items_;
 			}
 
+			const boost::optional<std::string>& getVersion() const {
+				return version_;
+			}
+
+			void setVersion(const std::string& version) {
+				version_ = version;
+			}
+
 		private:
 			RosterItemPayloads items_;
+			boost::optional<std::string> version_;
 	};
 }
diff --git a/Swiften/Elements/StreamFeatures.h b/Swiften/Elements/StreamFeatures.h
index 4bb21f0..5dc5a26 100644
--- a/Swiften/Elements/StreamFeatures.h
+++ b/Swiften/Elements/StreamFeatures.h
@@ -17,7 +17,7 @@ namespace Swift {
 		public:
 			typedef boost::shared_ptr<StreamFeatures> ref;
 
-			StreamFeatures() : hasStartTLS_(false), hasResourceBind_(false), hasSession_(false), hasStreamManagement_(false) {}
+			StreamFeatures() : hasStartTLS_(false), hasResourceBind_(false), hasSession_(false), hasStreamManagement_(false), hasRosterVersioning_(false) {}
 
 			void setHasStartTLS() {
 				hasStartTLS_ = true;
@@ -75,6 +75,14 @@ namespace Swift {
 				hasStreamManagement_ = true;
 			}
 
+			bool hasRosterVersioning() const {
+				return hasRosterVersioning_;
+			}
+
+			void setHasRosterVersioning() {
+				hasRosterVersioning_ = true;
+			}
+
 		private:
 			bool hasStartTLS_;
 			std::vector<std::string> compressionMethods_;
@@ -82,5 +90,6 @@ namespace Swift {
 			bool hasResourceBind_;
 			bool hasSession_;
 			bool hasStreamManagement_;
+			bool hasRosterVersioning_;
 	};
 }
diff --git a/Swiften/Parser/PayloadParsers/RosterParser.cpp b/Swiften/Parser/PayloadParsers/RosterParser.cpp
index ba19fbf..0da9f48 100644
--- a/Swiften/Parser/PayloadParsers/RosterParser.cpp
+++ b/Swiften/Parser/PayloadParsers/RosterParser.cpp
@@ -13,7 +13,13 @@ RosterParser::RosterParser() : level_(TopLevel), inItem_(false), unknownContentP
 }
 
 void RosterParser::handleStartElement(const std::string& element, const std::string& ns, const AttributeMap& attributes) {
-	if (level_ == PayloadLevel) {
+	if (level_ == TopLevel) {
+		AttributeMap::const_iterator i = attributes.find("ver");
+		if (i != attributes.end()) {
+			getPayloadInternal()->setVersion(i->second);
+		}
+	}
+	else if (level_ == PayloadLevel) {
 		if (element == "item") {
 			inItem_ = true;
 			currentItem_ = RosterItemPayload();
diff --git a/Swiften/Parser/PayloadParsers/UnitTest/RosterParserTest.cpp b/Swiften/Parser/PayloadParsers/UnitTest/RosterParserTest.cpp
index 1bcea0e..3102b74 100644
--- a/Swiften/Parser/PayloadParsers/UnitTest/RosterParserTest.cpp
+++ b/Swiften/Parser/PayloadParsers/UnitTest/RosterParserTest.cpp
@@ -17,6 +17,8 @@ class RosterParserTest : public CppUnit::TestFixture
 		CPPUNIT_TEST_SUITE(RosterParserTest);
 		CPPUNIT_TEST(testParse);
 		CPPUNIT_TEST(testParse_ItemWithUnknownContent);
+		CPPUNIT_TEST(testParse_WithVersion);
+		CPPUNIT_TEST(testParse_WithEmptyVersion);
 		CPPUNIT_TEST_SUITE_END();
 
 	public:
@@ -32,6 +34,8 @@ class RosterParserTest : public CppUnit::TestFixture
 				"</query>"));
 
 			RosterPayload* payload = dynamic_cast<RosterPayload*>(parser.getPayload().get());
+
+			CPPUNIT_ASSERT(!payload->getVersion());
 			const RosterPayload::RosterItemPayloads& items = payload->getItems();
 
 			CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(2), items.size());
@@ -74,6 +78,24 @@ class RosterParserTest : public CppUnit::TestFixture
 				"<baz xmlns=\"jabber:iq:roster\"><fum xmlns=\"jabber:iq:roster\">foo</fum></baz>"
 				), items[0].getUnknownContent());
 		}
+
+		void testParse_WithVersion() {
+			PayloadsParserTester parser;
+			CPPUNIT_ASSERT(parser.parse("<query xmlns='jabber:iq:roster' ver='ver10'/>"));
+
+			RosterPayload* payload = dynamic_cast<RosterPayload*>(parser.getPayload().get());
+			CPPUNIT_ASSERT(payload->getVersion());
+			CPPUNIT_ASSERT_EQUAL(std::string("ver10"), *payload->getVersion());
+		}
+
+		void testParse_WithEmptyVersion() {
+			PayloadsParserTester parser;
+			CPPUNIT_ASSERT(parser.parse("<query xmlns='jabber:iq:roster' ver=''/>"));
+
+			RosterPayload* payload = dynamic_cast<RosterPayload*>(parser.getPayload().get());
+			CPPUNIT_ASSERT(payload->getVersion());
+			CPPUNIT_ASSERT_EQUAL(std::string(""), *payload->getVersion());
+		}
 };
 
 CPPUNIT_TEST_SUITE_REGISTRATION(RosterParserTest);
diff --git a/Swiften/Parser/StreamFeaturesParser.cpp b/Swiften/Parser/StreamFeaturesParser.cpp
index 377f215..1b3ad54 100644
--- a/Swiften/Parser/StreamFeaturesParser.cpp
+++ b/Swiften/Parser/StreamFeaturesParser.cpp
@@ -31,6 +31,9 @@ void StreamFeaturesParser::handleStartElement(const std::string& element, const
 		else if (element == "compression" && ns == "http://jabber.org/features/compress") {
 			inCompression_ = true;
 		}
+		else if (element == "ver" && ns == "urn:xmpp:features:rosterver") {
+			getElementGeneric()->setHasRosterVersioning();
+		}
 	}
 	else if (currentDepth_ == 2) {
 		if (inCompression_ && element == "method") {
diff --git a/Swiften/Parser/UnitTest/StreamFeaturesParserTest.cpp b/Swiften/Parser/UnitTest/StreamFeaturesParserTest.cpp
index 1cdaf54..9fdea88 100644
--- a/Swiften/Parser/UnitTest/StreamFeaturesParserTest.cpp
+++ b/Swiften/Parser/UnitTest/StreamFeaturesParserTest.cpp
@@ -37,6 +37,7 @@ class StreamFeaturesParserTest : public CppUnit::TestFixture {
 					"<bind xmlns=\"urn:ietf:params:xml:ns:xmpp-bind\"/>"
 					"<sm xmlns='urn:xmpp:sm:2'/>"
 					"<session xmlns=\"urn:ietf:params:xml:ns:xmpp-session\"/>"
+					"<ver xmlns=\"urn:xmpp:features:rosterver\"/>"
 				"</stream:features>"));
 
 			StreamFeatures::ref element = boost::dynamic_pointer_cast<StreamFeatures>(testling.getElement());
@@ -49,6 +50,7 @@ class StreamFeaturesParserTest : public CppUnit::TestFixture {
 			CPPUNIT_ASSERT(element->hasAuthenticationMechanism("DIGEST-MD5"));
 			CPPUNIT_ASSERT(element->hasAuthenticationMechanism("PLAIN"));
 			CPPUNIT_ASSERT(element->hasStreamManagement());
+			CPPUNIT_ASSERT(element->hasRosterVersioning());
 		}
 
 		void testParse_Empty() {
diff --git a/Swiften/Roster/GetRosterRequest.h b/Swiften/Roster/GetRosterRequest.h
index 00cf77f..a3486f0 100644
--- a/Swiften/Roster/GetRosterRequest.h
+++ b/Swiften/Roster/GetRosterRequest.h
@@ -19,6 +19,12 @@ namespace Swift {
 				return ref(new GetRosterRequest(router));
 			}
 
+			static ref create(IQRouter* router, const std::string& version) {
+				ref result(new GetRosterRequest(router));
+				result->getPayloadGeneric()->setVersion(version);
+				return result;
+			}
+
 		private:
 			GetRosterRequest(IQRouter* router) :
 					GenericRequest<RosterPayload>(IQ::Get, JID(), boost::shared_ptr<Payload>(new RosterPayload()), router) {
diff --git a/Swiften/Roster/RosterFileStorage.cpp b/Swiften/Roster/RosterFileStorage.cpp
new file mode 100644
index 0000000..de7b442
--- /dev/null
+++ b/Swiften/Roster/RosterFileStorage.cpp
@@ -0,0 +1,24 @@
+/*
+ * Copyright (c) 2011 Remko Tronçon
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#include <Swiften/Roster/RosterFileStorage.h>
+
+#include <boost/smart_ptr/make_shared.hpp>
+
+namespace Swift {
+
+RosterFileStorage::RosterFileStorage(const boost::filesystem::path& path) : path(path) {
+}
+
+// FIXME
+void RosterFileStorage::setRoster(boost::shared_ptr<RosterPayload> r) {
+	roster.reset();
+	if (r) {
+		roster = boost::make_shared<RosterPayload>(*r);
+	}
+}
+
+}
diff --git a/Swiften/Roster/RosterFileStorage.h b/Swiften/Roster/RosterFileStorage.h
new file mode 100644
index 0000000..dc91c27
--- /dev/null
+++ b/Swiften/Roster/RosterFileStorage.h
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2011 Remko Tronçon
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#pragma once
+
+#include <boost/filesystem/path.hpp>
+
+#include <Swiften/Roster/RosterStorage.h>
+
+namespace Swift {
+	class RosterFileStorage : public RosterStorage {
+		public:
+			RosterFileStorage(const boost::filesystem::path& path);
+
+			// FIXME
+			virtual boost::shared_ptr<RosterPayload> getRoster() const {
+				return roster;
+			}
+
+			virtual void setRoster(boost::shared_ptr<RosterPayload>);
+
+		private:
+			boost::filesystem::path path;
+			boost::shared_ptr<RosterPayload> roster;
+	};
+}
diff --git a/Swiften/Roster/RosterMemoryStorage.cpp b/Swiften/Roster/RosterMemoryStorage.cpp
new file mode 100644
index 0000000..cbf4563
--- /dev/null
+++ b/Swiften/Roster/RosterMemoryStorage.cpp
@@ -0,0 +1,23 @@
+/*
+ * Copyright (c) 2011 Remko Tronçon
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#include <Swiften/Roster/RosterMemoryStorage.h>
+
+#include <boost/smart_ptr/make_shared.hpp>
+
+namespace Swift {
+
+RosterMemoryStorage::RosterMemoryStorage() {
+}
+
+void RosterMemoryStorage::setRoster(boost::shared_ptr<RosterPayload> r) {
+	roster.reset();
+	if (r) {
+		roster = boost::make_shared<RosterPayload>(*r);
+	}
+}
+
+}
diff --git a/Swiften/Roster/RosterMemoryStorage.h b/Swiften/Roster/RosterMemoryStorage.h
new file mode 100644
index 0000000..b659d77
--- /dev/null
+++ b/Swiften/Roster/RosterMemoryStorage.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright (c) 2011 Remko Tronçon
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#pragma once
+
+#include <Swiften/Roster/RosterStorage.h>
+
+namespace Swift {
+	class RosterMemoryStorage : public RosterStorage {
+		public:
+			RosterMemoryStorage();
+
+			virtual boost::shared_ptr<RosterPayload> getRoster() const {
+				return roster;
+			}
+
+			virtual void setRoster(boost::shared_ptr<RosterPayload>);
+
+		private:
+			boost::shared_ptr<RosterPayload> roster;
+	};
+}
diff --git a/Swiften/Roster/RosterStorage.cpp b/Swiften/Roster/RosterStorage.cpp
new file mode 100644
index 0000000..6bf58de
--- /dev/null
+++ b/Swiften/Roster/RosterStorage.cpp
@@ -0,0 +1,14 @@
+/*
+ * Copyright (c) 2011 Remko Tronçon
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#include <Swiften/Roster/RosterStorage.h>
+
+namespace Swift {
+
+RosterStorage::~RosterStorage() {
+}
+
+}
diff --git a/Swiften/Roster/RosterStorage.h b/Swiften/Roster/RosterStorage.h
new file mode 100644
index 0000000..ba24cb3
--- /dev/null
+++ b/Swiften/Roster/RosterStorage.h
@@ -0,0 +1,21 @@
+/*
+ * Copyright (c) 2011 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/RosterPayload.h>
+
+namespace Swift {
+	class RosterStorage {
+		public:
+			virtual ~RosterStorage();
+
+			virtual boost::shared_ptr<RosterPayload> getRoster() const = 0;
+			virtual void setRoster(boost::shared_ptr<RosterPayload>) = 0;
+	};
+}
diff --git a/Swiften/Roster/UnitTest/XMPPRosterControllerTest.cpp b/Swiften/Roster/UnitTest/XMPPRosterControllerTest.cpp
index 4ef1cc1..4c98673 100644
--- a/Swiften/Roster/UnitTest/XMPPRosterControllerTest.cpp
+++ b/Swiften/Roster/UnitTest/XMPPRosterControllerTest.cpp
@@ -16,15 +16,24 @@
 #include "Swiften/Client/DummyStanzaChannel.h"
 #include "Swiften/Queries/IQRouter.h"
 #include "Swiften/Roster/XMPPRosterImpl.h"
+#include <Swiften/Roster/RosterMemoryStorage.h>
 
 using namespace Swift;
 
 class XMPPRosterControllerTest : public CppUnit::TestFixture {
 		CPPUNIT_TEST_SUITE(XMPPRosterControllerTest);
+		CPPUNIT_TEST(testGet_Response);
 		CPPUNIT_TEST(testGet_EmptyResponse);
+		CPPUNIT_TEST(testGet_NoRosterInStorage);
+		CPPUNIT_TEST(testGet_NoVersionInStorage);
+		CPPUNIT_TEST(testGet_VersionInStorage);
+		CPPUNIT_TEST(testGet_ServerDoesNotSupportVersion);
+		CPPUNIT_TEST(testGet_ResponseWithoutNewVersion);
+		CPPUNIT_TEST(testGet_ResponseWithNewVersion);
 		CPPUNIT_TEST(testAdd);
 		CPPUNIT_TEST(testModify);
 		CPPUNIT_TEST(testRemove);
+		CPPUNIT_TEST(testRemove_RosterStorageUpdated);
 		CPPUNIT_TEST(testMany);
 		CPPUNIT_TEST_SUITE_END();
 
@@ -34,20 +43,36 @@ class XMPPRosterControllerTest : public CppUnit::TestFixture {
 			router_ = new IQRouter(channel_);
 			xmppRoster_ = new XMPPRosterImpl();
 			handler_ = new XMPPRosterSignalHandler(xmppRoster_);
+			rosterStorage_ = new RosterMemoryStorage();
 			jid1_ = JID("foo@bar.com");
 			jid2_ = JID("alice@wonderland.lit");
 			jid3_ = JID("jane@austen.lit");
 		}
 
 		void tearDown() {
+			delete rosterStorage_;
 			delete handler_;
 			delete xmppRoster_;
 			delete router_;
 			delete channel_;
 		}
 
+		void testGet_Response() {
+			std::auto_ptr<XMPPRosterController> testling(createController());
+
+			testling->requestRoster();
+			boost::shared_ptr<RosterPayload> payload = boost::make_shared<RosterPayload>();
+			payload->addItem(RosterItemPayload(jid1_, "Bob", RosterItemPayload::Both));
+			payload->addItem(RosterItemPayload(jid2_, "Alice", RosterItemPayload::Both));
+			channel_->onIQReceived(IQ::createResult("foo@bar.com", channel_->sentStanzas[0]->getID(), payload));
+
+			CPPUNIT_ASSERT_EQUAL(2, handler_->getEventCount());
+			CPPUNIT_ASSERT(xmppRoster_->getItem(jid1_));
+			CPPUNIT_ASSERT(xmppRoster_->getItem(jid2_));
+		}
+
 		void testGet_EmptyResponse() {
-			XMPPRosterController controller(router_, xmppRoster_);
+			XMPPRosterController controller(router_, xmppRoster_, rosterStorage_);
 
 			controller.requestRoster();
 
@@ -55,7 +80,7 @@ class XMPPRosterControllerTest : public CppUnit::TestFixture {
 		}
 
 		void testAdd() {
-			XMPPRosterController controller(router_, xmppRoster_);
+			XMPPRosterController controller(router_, xmppRoster_, rosterStorage_);
 
 			boost::shared_ptr<RosterPayload> payload(new RosterPayload());
 			payload->addItem(RosterItemPayload(jid1_, "Bob", RosterItemPayload::Both));
@@ -68,8 +93,115 @@ class XMPPRosterControllerTest : public CppUnit::TestFixture {
 			CPPUNIT_ASSERT_EQUAL(std::string("Bob"), xmppRoster_->getNameForJID(jid1_));
 		}
 
+		void testGet_NoRosterInStorage() {
+			std::auto_ptr<XMPPRosterController> testling(createController());
+			testling->setUseVersioning(true);
+
+			testling->requestRoster();
+
+			boost::shared_ptr<RosterPayload> roster = channel_->sentStanzas[0]->getPayload<RosterPayload>();
+			CPPUNIT_ASSERT(roster->getVersion());
+			CPPUNIT_ASSERT_EQUAL(std::string(""), *roster->getVersion());
+		}
+
+		void testGet_NoVersionInStorage() {
+			std::auto_ptr<XMPPRosterController> testling(createController());
+			testling->setUseVersioning(true);
+			rosterStorage_->setRoster(boost::make_shared<RosterPayload>());
+
+			testling->requestRoster();
+
+			boost::shared_ptr<RosterPayload> roster = channel_->sentStanzas[0]->getPayload<RosterPayload>();
+			CPPUNIT_ASSERT(roster->getVersion());
+			CPPUNIT_ASSERT_EQUAL(std::string(""), *roster->getVersion());
+		}
+
+		void testGet_VersionInStorage() {
+			std::auto_ptr<XMPPRosterController> testling(createController());
+			testling->setUseVersioning(true);
+			boost::shared_ptr<RosterPayload> payload(new RosterPayload());
+			payload->setVersion("foover");
+			rosterStorage_->setRoster(payload);
+
+			testling->requestRoster();
+
+			boost::shared_ptr<RosterPayload> roster = channel_->sentStanzas[0]->getPayload<RosterPayload>();
+			CPPUNIT_ASSERT(roster->getVersion());
+			CPPUNIT_ASSERT_EQUAL(std::string("foover"), *roster->getVersion());
+		}
+
+		void testGet_ServerDoesNotSupportVersion() {
+			std::auto_ptr<XMPPRosterController> testling(createController());
+			boost::shared_ptr<RosterPayload> payload(new RosterPayload());
+			payload->setVersion("foover");
+			rosterStorage_->setRoster(payload);
+
+			testling->requestRoster();
+
+			boost::shared_ptr<RosterPayload> roster = channel_->sentStanzas[0]->getPayload<RosterPayload>();
+			CPPUNIT_ASSERT(!roster->getVersion());
+		}
+
+		void testGet_ResponseWithoutNewVersion() {
+			std::auto_ptr<XMPPRosterController> testling(createController());
+			testling->setUseVersioning(true);
+			boost::shared_ptr<RosterPayload> storedRoster(new RosterPayload());
+			storedRoster->setVersion("version10");
+			storedRoster->addItem(RosterItemPayload(jid1_, "Bob", RosterItemPayload::Both));
+			storedRoster->addItem(RosterItemPayload(jid2_, "Alice", RosterItemPayload::Both));
+			rosterStorage_->setRoster(storedRoster);
+			testling->requestRoster();
+
+			channel_->onIQReceived(IQ::createResult("foo@bar.com", channel_->sentStanzas[0]->getID(), boost::shared_ptr<RosterPayload>()));
+
+			CPPUNIT_ASSERT_EQUAL(2, handler_->getEventCount());
+			CPPUNIT_ASSERT(xmppRoster_->getItem(jid1_));
+			CPPUNIT_ASSERT(xmppRoster_->getItem(jid2_));
+			CPPUNIT_ASSERT_EQUAL(Add, handler_->getLastEvent());
+			CPPUNIT_ASSERT_EQUAL(jid2_, handler_->getLastJID());
+			CPPUNIT_ASSERT(rosterStorage_->getRoster());
+			CPPUNIT_ASSERT(rosterStorage_->getRoster()->getVersion());
+			CPPUNIT_ASSERT_EQUAL(std::string("version10"), *rosterStorage_->getRoster()->getVersion());
+			CPPUNIT_ASSERT(rosterStorage_->getRoster()->getItem(jid1_));
+			CPPUNIT_ASSERT(rosterStorage_->getRoster()->getItem(jid2_));
+		}
+
+		void testGet_ResponseWithNewVersion() {
+			std::auto_ptr<XMPPRosterController> testling(createController());
+			testling->setUseVersioning(true);
+			boost::shared_ptr<RosterPayload> storedRoster(new RosterPayload());
+			storedRoster->setVersion("version10");
+			storedRoster->addItem(RosterItemPayload(jid1_, "Bob", RosterItemPayload::Both));
+			rosterStorage_->setRoster(storedRoster);
+			testling->requestRoster();
+
+			boost::shared_ptr<RosterPayload> serverRoster(new RosterPayload());
+			serverRoster->setVersion("version12");
+			serverRoster->addItem(RosterItemPayload(jid2_, "Alice", RosterItemPayload::Both));
+			std::vector<std::string> groups;
+			groups.push_back("foo");
+			groups.push_back("bar");
+			serverRoster->addItem(RosterItemPayload(jid3_, "Rabbit", RosterItemPayload::Both, groups));
+			channel_->onIQReceived(IQ::createResult("foo@bar.com", channel_->sentStanzas[0]->getID(), serverRoster));
+
+
+			CPPUNIT_ASSERT_EQUAL(2, handler_->getEventCount());
+			CPPUNIT_ASSERT(!xmppRoster_->getItem(jid1_));
+			CPPUNIT_ASSERT(xmppRoster_->getItem(jid2_));
+			CPPUNIT_ASSERT(xmppRoster_->getItem(jid3_));
+			CPPUNIT_ASSERT_EQUAL(jid3_, handler_->getLastJID());
+			CPPUNIT_ASSERT_EQUAL(Add, handler_->getLastEvent());
+			CPPUNIT_ASSERT(rosterStorage_->getRoster());
+			CPPUNIT_ASSERT(rosterStorage_->getRoster()->getVersion());
+			CPPUNIT_ASSERT_EQUAL(std::string("version12"), *rosterStorage_->getRoster()->getVersion());
+			CPPUNIT_ASSERT(!rosterStorage_->getRoster()->getItem(jid1_));
+			CPPUNIT_ASSERT(rosterStorage_->getRoster()->getItem(jid2_));
+			CPPUNIT_ASSERT(rosterStorage_->getRoster()->getItem(jid3_));
+			CPPUNIT_ASSERT_EQUAL(2, static_cast<int>(rosterStorage_->getRoster()->getItem(jid3_)->getGroups().size()));
+		}
+
 		void testModify() {
-			XMPPRosterController controller(router_, xmppRoster_);
+			XMPPRosterController controller(router_, xmppRoster_, rosterStorage_);
 			boost::shared_ptr<RosterPayload> payload1(new RosterPayload());
 			payload1->addItem(RosterItemPayload(jid1_, "Bob", RosterItemPayload::Both));
 			channel_->onIQReceived(IQ::createRequest(IQ::Set, JID(), "id1", payload1));
@@ -87,9 +219,9 @@ class XMPPRosterControllerTest : public CppUnit::TestFixture {
 
 			CPPUNIT_ASSERT_EQUAL(std::string("Bob2"), xmppRoster_->getNameForJID(jid1_));
 		}
-
+		
 		void testRemove() {
-			XMPPRosterController controller(router_, xmppRoster_);
+			XMPPRosterController controller(router_, xmppRoster_, rosterStorage_);
 			boost::shared_ptr<RosterPayload> payload1(new RosterPayload());
 			payload1->addItem(RosterItemPayload(jid1_, "Bob", RosterItemPayload::Both));
 			channel_->onIQReceived(IQ::createRequest(IQ::Set, JID(), "id1", payload1));
@@ -107,8 +239,31 @@ class XMPPRosterControllerTest : public CppUnit::TestFixture {
 
 		}
 
+		void testRemove_RosterStorageUpdated() {
+			std::auto_ptr<XMPPRosterController> testling(createController());
+			testling->setUseVersioning(true);
+			boost::shared_ptr<RosterPayload> storedRoster(new RosterPayload());
+			storedRoster->setVersion("version10");
+			storedRoster->addItem(RosterItemPayload(jid1_, "Bob", RosterItemPayload::Both));
+			storedRoster->addItem(RosterItemPayload(jid2_, "Alice", RosterItemPayload::Both));
+			rosterStorage_->setRoster(storedRoster);
+			testling->requestRoster();
+			channel_->onIQReceived(IQ::createResult("foo@bar.com", channel_->sentStanzas[0]->getID(), boost::shared_ptr<RosterPayload>()));
+
+			boost::shared_ptr<RosterPayload> payload2(new RosterPayload());
+			payload2->setVersion("version15");
+			payload2->addItem(RosterItemPayload(jid1_, "Bob", RosterItemPayload::Remove));
+			channel_->onIQReceived(IQ::createRequest(IQ::Set, JID(), "id2", payload2));
+
+			CPPUNIT_ASSERT(rosterStorage_->getRoster());
+			CPPUNIT_ASSERT(rosterStorage_->getRoster()->getVersion());
+			CPPUNIT_ASSERT_EQUAL(std::string("version15"), *rosterStorage_->getRoster()->getVersion());
+			CPPUNIT_ASSERT(!rosterStorage_->getRoster()->getItem(jid1_));
+			CPPUNIT_ASSERT(rosterStorage_->getRoster()->getItem(jid2_));
+		}
+
 		void testMany() {
-			XMPPRosterController controller(router_, xmppRoster_);
+			XMPPRosterController controller(router_, xmppRoster_, rosterStorage_);
 			boost::shared_ptr<RosterPayload> payload1(new RosterPayload());
 			payload1->addItem(RosterItemPayload(jid1_, "Bob", RosterItemPayload::Both));
 			channel_->onIQReceived(IQ::createRequest(IQ::Set, JID(), "id1", payload1));
@@ -171,12 +326,18 @@ class XMPPRosterControllerTest : public CppUnit::TestFixture {
 			handler_->reset();
 
 		}
+	
+	private:
+			XMPPRosterController* createController() {
+				return new XMPPRosterController(router_, xmppRoster_, rosterStorage_);
+			}
 
 	private:
 		DummyStanzaChannel* channel_;
 		IQRouter* router_;
 		XMPPRosterImpl* xmppRoster_;
 		XMPPRosterSignalHandler* handler_;
+		RosterMemoryStorage* rosterStorage_;
 		JID jid1_;
 		JID jid2_;
 		JID jid3_;
diff --git a/Swiften/Roster/UnitTest/XMPPRosterSignalHandler.cpp b/Swiften/Roster/UnitTest/XMPPRosterSignalHandler.cpp
index 983bc22..d89644a 100644
--- a/Swiften/Roster/UnitTest/XMPPRosterSignalHandler.cpp
+++ b/Swiften/Roster/UnitTest/XMPPRosterSignalHandler.cpp
@@ -11,7 +11,7 @@
 
 using namespace Swift;
 
-XMPPRosterSignalHandler::XMPPRosterSignalHandler(Swift::XMPPRoster* roster) {
+XMPPRosterSignalHandler::XMPPRosterSignalHandler(Swift::XMPPRoster* roster) : eventCount(0) {
 	lastEvent_ = None;
 	roster->onJIDAdded.connect(boost::bind(&XMPPRosterSignalHandler::handleJIDAdded, this, _1));
 	roster->onJIDRemoved.connect(boost::bind(&XMPPRosterSignalHandler::handleJIDRemoved, this, _1));
@@ -24,4 +24,5 @@ void XMPPRosterSignalHandler::handleJIDUpdated(const Swift::JID& jid, const std:
 	lastOldName_ = oldName;
 	lastOldGroups_ = oldGroups;
 	lastEvent_ = Update;
+	eventCount++;
 }
diff --git a/Swiften/Roster/UnitTest/XMPPRosterSignalHandler.h b/Swiften/Roster/UnitTest/XMPPRosterSignalHandler.h
index c59b4c6..2cf1159 100644
--- a/Swiften/Roster/UnitTest/XMPPRosterSignalHandler.h
+++ b/Swiften/Roster/UnitTest/XMPPRosterSignalHandler.h
@@ -37,15 +37,21 @@ public:
 		lastEvent_ = None;
 	}
 
+	int getEventCount() const {
+		return eventCount;
+	}
+
 private:
 	void handleJIDAdded(const Swift::JID& jid) {
 		lastJID_ = jid;
 		lastEvent_ = Add;
+		eventCount++;
 	}
 
 	void handleJIDRemoved(const Swift::JID& jid) {
 		lastJID_ = jid;
 		lastEvent_ = Remove;
+		eventCount++;
 	}
 
 	void handleJIDUpdated(const Swift::JID& jid, const std::string& oldName, const std::vector<std::string>& oldGroups);
@@ -54,5 +60,5 @@ private:
 	Swift::JID lastJID_;
 	std::string lastOldName_;
 	std::vector<std::string> lastOldGroups_;
-
+	int eventCount;
 };
diff --git a/Swiften/Roster/XMPPRosterController.cpp b/Swiften/Roster/XMPPRosterController.cpp
index a294d35..baa74ff 100644
--- a/Swiften/Roster/XMPPRosterController.cpp
+++ b/Swiften/Roster/XMPPRosterController.cpp
@@ -13,14 +13,15 @@
 #include "Swiften/Queries/IQRouter.h"
 #include "Swiften/Roster/GetRosterRequest.h"
 #include "Swiften/Roster/XMPPRosterImpl.h"
+#include <Swiften/Roster/RosterStorage.h>
 
 namespace Swift {
 	
 /**
  * The controller does not gain ownership of these parameters.
  */
-XMPPRosterController::XMPPRosterController(IQRouter* iqRouter, XMPPRosterImpl* xmppRoster) : iqRouter_(iqRouter), rosterPushResponder_(iqRouter), xmppRoster_(xmppRoster) {
-	rosterPushResponder_.onRosterReceived.connect(boost::bind(&XMPPRosterController::handleRosterReceived, this, _1, false));
+XMPPRosterController::XMPPRosterController(IQRouter* iqRouter, XMPPRosterImpl* xmppRoster, RosterStorage* rosterStorage) : iqRouter_(iqRouter), rosterPushResponder_(iqRouter), xmppRoster_(xmppRoster), rosterStorage_(rosterStorage), useVersioning(false) {
+	rosterPushResponder_.onRosterReceived.connect(boost::bind(&XMPPRosterController::handleRosterReceived, this, _1, false, boost::shared_ptr<RosterPayload>()));
 	rosterPushResponder_.start();
 }
 
@@ -30,12 +31,24 @@ XMPPRosterController::~XMPPRosterController() {
 
 void XMPPRosterController::requestRoster() {
 	xmppRoster_->clear();
-	GetRosterRequest::ref rosterRequest = GetRosterRequest::create(iqRouter_);
-	rosterRequest->onResponse.connect(boost::bind(&XMPPRosterController::handleRosterReceived, this, _1, true));
+
+	boost::shared_ptr<RosterPayload> storedRoster = rosterStorage_->getRoster();
+	GetRosterRequest::ref rosterRequest;
+	if (useVersioning) {
+		std::string version = "";
+		if (storedRoster && storedRoster->getVersion()) {
+			version = *storedRoster->getVersion();
+		}
+		rosterRequest = GetRosterRequest::create(iqRouter_, version);
+	}
+	else {
+		rosterRequest = GetRosterRequest::create(iqRouter_);
+	}
+	rosterRequest->onResponse.connect(boost::bind(&XMPPRosterController::handleRosterReceived, this, _1, true, storedRoster));
 	rosterRequest->send();
 }
 
-void XMPPRosterController::handleRosterReceived(boost::shared_ptr<RosterPayload> rosterPayload, bool initial) {
+void XMPPRosterController::handleRosterReceived(boost::shared_ptr<RosterPayload> rosterPayload, bool initial, boost::shared_ptr<RosterPayload> previousRoster) {
 	if (rosterPayload) {
 		foreach(const RosterItemPayload& item, rosterPayload->getItems()) {
 			//Don't worry about the updated case, the XMPPRoster sorts that out.
@@ -46,9 +59,33 @@ void XMPPRosterController::handleRosterReceived(boost::shared_ptr<RosterPayload>
 			}
 		}
 	}
+	else if (previousRoster) {
+		// The cached version hasn't changed; emit all items
+		foreach(const RosterItemPayload& item, previousRoster->getItems()) {
+			if (item.getSubscription() != RosterItemPayload::Remove) {
+				xmppRoster_->addContact(item.getJID(), item.getName(), item.getGroups(), item.getSubscription());
+			}
+			else {
+				std::cerr << "ERROR: Stored invalid roster item" << std::endl;
+			}
+		}
+	}
 	if (initial) {
 		xmppRoster_->onInitialRosterPopulated();
 	}
+	if (rosterPayload && rosterPayload->getVersion() && useVersioning) {
+		saveRoster(*rosterPayload->getVersion());
+	}
+}
+
+void XMPPRosterController::saveRoster(const std::string& version) {
+	std::vector<XMPPRosterItem> items = xmppRoster_->getItems();
+	boost::shared_ptr<RosterPayload> roster(new RosterPayload());
+	roster->setVersion(version);
+	foreach(const XMPPRosterItem& item, items) {
+		roster->addItem(RosterItemPayload(item.getJID(), item.getName(), item.getSubscription(), item.getGroups()));
+	}
+	rosterStorage_->setRoster(roster);
 }
 
 }
diff --git a/Swiften/Roster/XMPPRosterController.h b/Swiften/Roster/XMPPRosterController.h
index eeb84f6..9313bb6 100644
--- a/Swiften/Roster/XMPPRosterController.h
+++ b/Swiften/Roster/XMPPRosterController.h
@@ -18,21 +18,29 @@
 namespace Swift {
 	class IQRouter;
 	class XMPPRosterImpl;
+	class RosterStorage;
 
 	class XMPPRosterController {
 		public:
-			XMPPRosterController(IQRouter *iqRouter, XMPPRosterImpl* xmppRoster);
+			XMPPRosterController(IQRouter *iqRouter, XMPPRosterImpl* xmppRoster, RosterStorage* storage);
 			~XMPPRosterController();
 
 			void requestRoster();
 
+			void setUseVersioning(bool b) {
+				useVersioning = b;
+			}
+
 		private:
-			void handleRosterReceived(boost::shared_ptr<RosterPayload> rosterPayload, bool initial);
+			void handleRosterReceived(boost::shared_ptr<RosterPayload> rosterPayload, bool initial, boost::shared_ptr<RosterPayload> previousRoster);
+			void saveRoster(const std::string& version);
 
 		private:
 			IQRouter* iqRouter_;
 			RosterPushResponder rosterPushResponder_;
 			XMPPRosterImpl* xmppRoster_;
+			RosterStorage* rosterStorage_;
+			bool useVersioning;
 	};
 }
 
diff --git a/Swiften/SConscript b/Swiften/SConscript
index 166e2ef..7f27ea1 100644
--- a/Swiften/SConscript
+++ b/Swiften/SConscript
@@ -62,6 +62,7 @@ if env["SCONS_STAGE"] == "build" :
 			"Client/NickResolver.cpp",
 			"Client/NickManager.cpp",
 			"Client/NickManagerImpl.cpp",
+			"Client/Storages.cpp",
 			"Compress/ZLibCodecompressor.cpp",
 			"Compress/ZLibDecompressor.cpp",
 			"Compress/ZLibCompressor.cpp",
@@ -88,6 +89,9 @@ if env["SCONS_STAGE"] == "build" :
 			"Queries/Requests/GetInBandRegistrationFormRequest.cpp",
 			"Queries/Requests/SubmitInBandRegistrationFormRequest.cpp",
 			"Queries/Responders/SoftwareVersionResponder.cpp",
+			"Roster/RosterStorage.cpp",
+			"Roster/RosterMemoryStorage.cpp",
+			"Roster/RosterFileStorage.cpp",
 			"Roster/XMPPRoster.cpp",
 			"Roster/XMPPRosterImpl.cpp",
 			"Roster/XMPPRosterController.cpp",
diff --git a/Swiften/Serializer/PayloadSerializers/RosterSerializer.cpp b/Swiften/Serializer/PayloadSerializers/RosterSerializer.cpp
index 40faf73..886676a 100644
--- a/Swiften/Serializer/PayloadSerializers/RosterSerializer.cpp
+++ b/Swiften/Serializer/PayloadSerializers/RosterSerializer.cpp
@@ -20,6 +20,9 @@ RosterSerializer::RosterSerializer() : GenericPayloadSerializer<RosterPayload>()
 
 std::string RosterSerializer::serializePayload(boost::shared_ptr<RosterPayload> roster)  const {
 	XMLElement queryElement("query", "jabber:iq:roster");
+	if (roster->getVersion()) {
+		queryElement.setAttribute("ver", *roster->getVersion());
+	}
 	foreach(const RosterItemPayload& item, roster->getItems()) {
 		boost::shared_ptr<XMLElement> itemElement(new XMLElement("item"));
 		itemElement->setAttribute("jid", item.getJID());
diff --git a/Swiften/Serializer/PayloadSerializers/UnitTest/RosterSerializerTest.cpp b/Swiften/Serializer/PayloadSerializers/UnitTest/RosterSerializerTest.cpp
index b8ceac3..61316df 100644
--- a/Swiften/Serializer/PayloadSerializers/UnitTest/RosterSerializerTest.cpp
+++ b/Swiften/Serializer/PayloadSerializers/UnitTest/RosterSerializerTest.cpp
@@ -11,16 +11,15 @@
 
 using namespace Swift;
 
-class RosterSerializerTest : public CppUnit::TestFixture
-{
+class RosterSerializerTest : public CppUnit::TestFixture {
 		CPPUNIT_TEST_SUITE(RosterSerializerTest);
 		CPPUNIT_TEST(testSerialize);
 		CPPUNIT_TEST(testSerialize_ItemWithUnknownContent);
+		CPPUNIT_TEST(testSerialize_WithVersion);
+		CPPUNIT_TEST(testSerialize_WithEmptyVersion);
 		CPPUNIT_TEST_SUITE_END();
 
 	public:
-		RosterSerializerTest() {}
-
 		void testSerialize() {
 			RosterSerializer testling;
 			boost::shared_ptr<RosterPayload> roster(new RosterPayload());
@@ -77,6 +76,26 @@ class RosterSerializerTest : public CppUnit::TestFixture
 
 			CPPUNIT_ASSERT_EQUAL(expectedResult, testling.serialize(roster));
 		}
+
+		void testSerialize_WithVersion() {
+			RosterSerializer testling;
+			boost::shared_ptr<RosterPayload> roster(new RosterPayload());
+			roster->setVersion("ver20");
+
+			std::string expectedResult = "<query ver=\"ver20\" xmlns=\"jabber:iq:roster\"/>";
+
+			CPPUNIT_ASSERT_EQUAL(expectedResult, testling.serialize(roster));
+		}
+
+		void testSerialize_WithEmptyVersion() {
+			RosterSerializer testling;
+			boost::shared_ptr<RosterPayload> roster(new RosterPayload());
+			roster->setVersion("");
+
+			std::string expectedResult = "<query ver=\"\" xmlns=\"jabber:iq:roster\"/>";
+
+			CPPUNIT_ASSERT_EQUAL(expectedResult, testling.serialize(roster));
+		}
 };
 
 CPPUNIT_TEST_SUITE_REGISTRATION(RosterSerializerTest);
diff --git a/Swiften/Serializer/StreamFeaturesSerializer.cpp b/Swiften/Serializer/StreamFeaturesSerializer.cpp
index 915433c..11744b4 100644
--- a/Swiften/Serializer/StreamFeaturesSerializer.cpp
+++ b/Swiften/Serializer/StreamFeaturesSerializer.cpp
@@ -6,6 +6,8 @@
 
 #include "Swiften/Serializer/StreamFeaturesSerializer.h"
 
+#include <boost/smart_ptr/make_shared.hpp>
+
 #include "Swiften/Serializer/XML/XMLElement.h"
 #include "Swiften/Serializer/XML/XMLTextNode.h"
 #include "Swiften/Base/foreach.h"
@@ -49,6 +51,9 @@ std::string StreamFeaturesSerializer::serialize(boost::shared_ptr<Element> eleme
 	if (streamFeatures->hasStreamManagement()) {
 		streamFeaturesElement.addNode(boost::shared_ptr<XMLElement>(new XMLElement("sm", "urn:xmpp:sm:2")));
 	}
+	if (streamFeatures->hasRosterVersioning()) {
+		streamFeaturesElement.addNode(boost::make_shared<XMLElement>("ver", "urn:xmpp:features:rosterver"));
+	}
 	return streamFeaturesElement.serialize();
 }
 
diff --git a/Swiften/Serializer/UnitTest/StreamFeaturesSerializerTest.cpp b/Swiften/Serializer/UnitTest/StreamFeaturesSerializerTest.cpp
index 65caa81..aa896c2 100644
--- a/Swiften/Serializer/UnitTest/StreamFeaturesSerializerTest.cpp
+++ b/Swiften/Serializer/UnitTest/StreamFeaturesSerializerTest.cpp
@@ -32,6 +32,7 @@ class StreamFeaturesSerializerTest : public CppUnit::TestFixture
 			streamFeatures->setHasResourceBind();
 			streamFeatures->setHasSession();
 			streamFeatures->setHasStreamManagement();
+			streamFeatures->setHasRosterVersioning();
 
 			CPPUNIT_ASSERT_EQUAL(std::string(
 				"<stream:features>"
@@ -47,6 +48,7 @@ class StreamFeaturesSerializerTest : public CppUnit::TestFixture
 					"<bind xmlns=\"urn:ietf:params:xml:ns:xmpp-bind\"/>"
 					"<session xmlns=\"urn:ietf:params:xml:ns:xmpp-session\"/>"
 					"<sm xmlns=\"urn:xmpp:sm:2\"/>"
+					"<ver xmlns=\"urn:xmpp:features:rosterver\"/>"
 				"</stream:features>"), testling.serialize(streamFeatures));
 		}
 };
-- 
cgit v0.10.2-6-g49f6