From b5fc240f1cf4e340e04177a23cb8cf827b9ca16b Mon Sep 17 00:00:00 2001
From: Tobias Markmann <tm@ayena.de>
Date: Wed, 17 Dec 2014 16:21:30 +0100
Subject: Update Jingle FT protocol to namespace verison
 urn:xmpp:jingle:apps:file-transfer:4.

Test-Information:

Adjusted unit tests and successfully build/run them on OS X 10.9.5.

Change-Id: I63789e3fb351999f719157b54fa9fcf95f40fb07

diff --git a/Swiften/Elements/HashElement.h b/Swiften/Elements/HashElement.h
new file mode 100644
index 0000000..44d2f96
--- /dev/null
+++ b/Swiften/Elements/HashElement.h
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2014 Isode Limited.
+ * All rights reserved.
+ * See the COPYING file for more information.
+ */
+
+#pragma once
+
+#include <Swiften/Base/ByteArray.h>
+
+namespace Swift {
+	/*
+	 * @brief This class represents a XEP-0300 <hash/> element.
+	 */
+	class HashElement {
+		public:
+			HashElement(const std::string& algorithm, const ByteArray& hash) : algorithm_(algorithm), hash_(hash) {
+			}
+
+			void setHashValue(const std::string& algorithm, const ByteArray& hash) {
+				algorithm_ = algorithm;
+				hash_ = hash;
+			}
+
+			const std::string& getAlgorithm() const {
+				return algorithm_;
+			}
+
+			const ByteArray& getHashValue() const {
+				return hash_;
+			}
+
+			bool operator==(const HashElement& rhs) const {
+				return (algorithm_ == rhs.algorithm_) && (hash_ == rhs.hash_);
+			}
+
+
+		private:
+			std::string algorithm_;
+			ByteArray hash_;
+	};
+}
diff --git a/Swiften/Elements/JingleFileTransferDescription.h b/Swiften/Elements/JingleFileTransferDescription.h
index 620f8f5..9308abf 100644
--- a/Swiften/Elements/JingleFileTransferDescription.h
+++ b/Swiften/Elements/JingleFileTransferDescription.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2011 Isode Limited.
+ * Copyright (c) 2011-2014 Isode Limited.
  * All rights reserved.
  * See the COPYING file for more information.
  */
@@ -10,32 +10,22 @@
 #include <vector>
 
 #include <Swiften/Elements/JingleDescription.h>
-#include <Swiften/Elements/StreamInitiationFileInfo.h>
+#include <Swiften/Elements/JingleFileTransferFileInfo.h>
 
 namespace Swift {
 	class JingleFileTransferDescription : public JingleDescription {
 		public:
 			typedef boost::shared_ptr<JingleFileTransferDescription> ref;
 
-			void addOffer(const StreamInitiationFileInfo& offer) {
-				offers.push_back(offer);
+			void setFileInfo(const JingleFileTransferFileInfo& fileInfo) {
+				fileInfo_ = fileInfo;
 			}
-			
 
-			const std::vector<StreamInitiationFileInfo>& getOffers() const {
-				return offers;
-			}
-			
-			void addRequest(const StreamInitiationFileInfo& request) {
-				reqeusts.push_back(request);
-			}
-			
-			const std::vector<StreamInitiationFileInfo>& getRequests() const {
-				return reqeusts;
+			const JingleFileTransferFileInfo& getFileInfo() {
+				return fileInfo_;
 			}
 
 		private:
-			std::vector<StreamInitiationFileInfo> offers;
-			std::vector<StreamInitiationFileInfo> reqeusts;
+			JingleFileTransferFileInfo fileInfo_;
 	};
 }
diff --git a/Swiften/Elements/JingleFileTransferFileInfo.h b/Swiften/Elements/JingleFileTransferFileInfo.h
new file mode 100644
index 0000000..8fec59e
--- /dev/null
+++ b/Swiften/Elements/JingleFileTransferFileInfo.h
@@ -0,0 +1,120 @@
+/*
+ * Copyright (c) 2014 Isode Limited.
+ * All rights reserved.
+ * See the COPYING file for more information.
+ */
+
+#pragma once
+
+#include <string>
+#include <vector>
+#include <map>
+
+#include <boost/optional.hpp>
+#include <boost/shared_ptr.hpp>
+#include <boost/date_time/posix_time/posix_time_types.hpp>
+
+#include <Swiften/Elements/HashElement.h>
+#include <Swiften/Elements/Payload.h>
+
+namespace Swift {
+
+	/**
+	 * @brief This class represents the file info used in XEP-0234.
+	 */
+	class JingleFileTransferFileInfo : public Payload {
+		typedef boost::shared_ptr<JingleFileTransferFileInfo> ref;
+
+		public:
+			JingleFileTransferFileInfo(const std::string& name = "", const std::string& description = "", unsigned long long size = 0, const boost::posix_time::ptime &date = boost::posix_time::ptime()) :
+				name_(name), description_(description), size_(size), date_(date), supportsRangeRequests_(false), rangeOffset_(0) {
+			}
+
+		public:
+			typedef std::map<std::string, ByteArray> HashElementMap;
+
+		public:
+			void setName(const std::string& name) {
+				name_ = name;;
+			}
+	
+			const std::string& getName() const {
+				return name_;
+			}
+
+			void setDescription(const std::string& description) {
+				description_ = description;
+			}
+	
+			const std::string& getDescription() const {
+				return description_;
+			}
+
+			void setMediaType(const std::string& mediaType) {
+				mediaType_ = mediaType;
+			}
+
+			const std::string& getMediaType() const {
+				return mediaType_;
+			}
+	
+			void setSize(const boost::uintmax_t size) {
+				size_ = size;
+			}
+	
+			boost::uintmax_t getSize() const {
+				return size_;
+			}
+
+			void setDate(const boost::posix_time::ptime& date) {
+				date_ = date;
+			}
+	
+			const boost::posix_time::ptime& getDate() const {
+				return date_;
+			}
+
+			void setSupportsRangeRequests(const bool supportsIt) {
+				supportsRangeRequests_ = supportsIt;
+			}
+	
+			bool getSupportsRangeRequests() const {
+				return supportsRangeRequests_;
+			}
+	
+			void setRangeOffset(const boost::uintmax_t offset) {
+				supportsRangeRequests_ = true;
+				rangeOffset_ = offset;
+			}
+	
+			boost::uintmax_t getRangeOffset() const {
+				return rangeOffset_;
+			}
+
+			void addHash(const HashElement& hash) {
+				hashes_[hash.getAlgorithm()] = hash.getHashValue();
+			}
+
+			const std::map<std::string, ByteArray>& getHashes() const {
+				return hashes_;
+			}
+
+			boost::optional<ByteArray> getHash(const std::string& algorithm) const {
+				boost::optional<ByteArray> ret;
+				if (hashes_.find(algorithm) != hashes_.end()) {
+					ret = boost::optional<ByteArray>(hashes_.find(algorithm)->second);
+				}
+				return ret;
+			}
+
+		private:	
+			std::string name_;
+			std::string description_;
+			std::string mediaType_;
+			boost::uintmax_t size_;
+			boost::posix_time::ptime date_;
+			bool supportsRangeRequests_;
+			boost::uintmax_t rangeOffset_;
+			HashElementMap hashes_;
+	};
+}
diff --git a/Swiften/Elements/JingleFileTransferHash.h b/Swiften/Elements/JingleFileTransferHash.h
index 5603531..d34e35d 100644
--- a/Swiften/Elements/JingleFileTransferHash.h
+++ b/Swiften/Elements/JingleFileTransferHash.h
@@ -4,6 +4,12 @@
  * See Documentation/Licenses/BSD-simplified.txt for more information.
  */
 
+/*
+ * Copyright (c) 2014 Isode Limited.
+ * All rights reserved.
+ * See the COPYING file for more information.
+ */
+
 #pragma once
 
 #include <boost/shared_ptr.hpp>
@@ -11,25 +17,24 @@
 #include <string>
 
 #include <Swiften/Elements/JingleDescription.h>
+#include <Swiften/Elements/JingleFileTransferFileInfo.h>
 
 namespace Swift {
 
 class JingleFileTransferHash : public Payload {
 public:
-	typedef std::map<std::string, std::string> HashesMap;
-public:
 	typedef boost::shared_ptr<JingleFileTransferHash> ref;
 
-	void setHash(const std::string& algo, const std::string& hash) {
-		hashes[algo] = hash;
+	void setFileInfo(const JingleFileTransferFileInfo& fileInfo) {
+		fileInfo_ = fileInfo;
 	}
 
-	const HashesMap& getHashes() const {
-		return hashes;
+	JingleFileTransferFileInfo& getFileInfo() {
+		return fileInfo_;
 	}
 
 private:
-	HashesMap hashes;
+	JingleFileTransferFileInfo fileInfo_;
 };
 	
 }
diff --git a/Swiften/FileTransfer/DefaultFileTransferTransporter.cpp b/Swiften/FileTransfer/DefaultFileTransferTransporter.cpp
index 74492b1..1a77685 100644
--- a/Swiften/FileTransfer/DefaultFileTransferTransporter.cpp
+++ b/Swiften/FileTransfer/DefaultFileTransferTransporter.cpp
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2013 Isode Limited.
+ * Copyright (c) 2013-2014 Isode Limited.
  * All rights reserved.
  * See the COPYING file for more information.
  */
@@ -242,8 +242,10 @@ boost::shared_ptr<TransportSession> DefaultFileTransferTransporter::createIBBSen
 
 boost::shared_ptr<TransportSession> DefaultFileTransferTransporter::createIBBReceiveSession(
 		const std::string& sessionID, unsigned long long size, boost::shared_ptr<WriteBytestream> stream) {
-	closeLocalSession();
-	closeRemoteSession();
+	if (s5bServerManager->getServer()) {
+		closeLocalSession();
+		closeRemoteSession();
+	}
 	boost::shared_ptr<IBBReceiveSession> ibbSession = boost::make_shared<IBBReceiveSession>(
 			sessionID, initiator, responder, size, stream, router);
 	return boost::make_shared<IBBReceiveTransportSession>(ibbSession);
diff --git a/Swiften/FileTransfer/FileTransferManagerImpl.cpp b/Swiften/FileTransfer/FileTransferManagerImpl.cpp
index f248b8a..fe8bfd6 100644
--- a/Swiften/FileTransfer/FileTransferManagerImpl.cpp
+++ b/Swiften/FileTransfer/FileTransferManagerImpl.cpp
@@ -5,7 +5,7 @@
  */
 
 /*
- * Copyright (c) 2013 Isode Limited.
+ * Copyright (c) 2013-2014 Isode Limited.
  * All rights reserved.
  * See the COPYING file for more information.
  */
@@ -21,7 +21,7 @@
 #include <Swiften/Base/Path.h>
 #include "Swiften/Disco/EntityCapsProvider.h"
 #include <Swiften/JID/JID.h>
-#include <Swiften/Elements/StreamInitiationFileInfo.h>
+#include <Swiften/Elements/JingleFileTransferFileInfo.h>
 #include <Swiften/FileTransfer/SOCKS5BytestreamServerManager.h>
 #include <Swiften/FileTransfer/OutgoingFileTransferManager.h>
 #include <Swiften/FileTransfer/IncomingFileTransferManager.h>
@@ -146,7 +146,7 @@ OutgoingFileTransfer::ref FileTransferManagerImpl::createOutgoingFileTransfer(
 		const boost::posix_time::ptime& lastModified, 
 		boost::shared_ptr<ReadBytestream> bytestream,
 		const FileTransferOptions& config) {
-	StreamInitiationFileInfo fileInfo;
+	JingleFileTransferFileInfo fileInfo;
 	fileInfo.setDate(lastModified);
 	fileInfo.setSize(sizeInBytes);
 	fileInfo.setName(filename);
diff --git a/Swiften/FileTransfer/FileTransferOptions.h b/Swiften/FileTransfer/FileTransferOptions.h
index 3a0abcb..3d00d2b 100644
--- a/Swiften/FileTransfer/FileTransferOptions.h
+++ b/Swiften/FileTransfer/FileTransferOptions.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2013 Isode Limited.
+ * Copyright (c) 2013-2014 Isode Limited.
  * All rights reserved.
  * See the COPYING file for more information.
  */
@@ -12,7 +12,7 @@
 namespace Swift {
 	class SWIFTEN_API FileTransferOptions {
 		public:
-			FileTransferOptions() : allowInBand(false) {
+			FileTransferOptions() : allowInBand(true) {
 			}
 			SWIFTEN_DEFAULT_COPY_CONSTRUCTOR(FileTransferOptions)
 			~FileTransferOptions();
diff --git a/Swiften/FileTransfer/FileTransferTransporter.h b/Swiften/FileTransfer/FileTransferTransporter.h
index d149722..2116f0d 100644
--- a/Swiften/FileTransfer/FileTransferTransporter.h
+++ b/Swiften/FileTransfer/FileTransferTransporter.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2013 Isode Limited.
+ * Copyright (c) 2013-2014 Isode Limited.
  * All rights reserved.
  * See the COPYING file for more information.
  */
@@ -20,6 +20,11 @@ namespace Swift {
 	class ReadBytestream;
 	class WriteBytestream;
 
+	/**
+	 * @brief The FileTransferTransporter class is an abstract factory definition
+	 * to generate SOCKS5 bytestream transports or IBB bytestreams for use in file
+	 * transfers.
+	 */
 	class SWIFTEN_API FileTransferTransporter {
 		public:
 			virtual ~FileTransferTransporter();
diff --git a/Swiften/FileTransfer/IncomingFileTransfer.h b/Swiften/FileTransfer/IncomingFileTransfer.h
index e0cb4ad..93ecf1a 100644
--- a/Swiften/FileTransfer/IncomingFileTransfer.h
+++ b/Swiften/FileTransfer/IncomingFileTransfer.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2010 Isode Limited.
+ * Copyright (c) 2010-2014 Isode Limited.
  * All rights reserved.
  * See the COPYING file for more information.
  */
@@ -16,6 +16,10 @@ namespace Swift {
 	class WriteBytestream;
 	class JID;
 
+	/**
+	 * @brief The IncomingFileTransfer abstract class is the general interface in Swiften
+	 * for incoming file transfers.
+	 */
 	class IncomingFileTransfer : public FileTransfer {
 		public:
 			typedef boost::shared_ptr<IncomingFileTransfer> ref;
diff --git a/Swiften/FileTransfer/IncomingFileTransferManager.cpp b/Swiften/FileTransfer/IncomingFileTransferManager.cpp
index 05d6259..239c4a8 100644
--- a/Swiften/FileTransfer/IncomingFileTransferManager.cpp
+++ b/Swiften/FileTransfer/IncomingFileTransferManager.cpp
@@ -44,13 +44,13 @@ bool IncomingFileTransferManager::handleIncomingJingleSession(
 	if (JingleContentPayload::ref content = Jingle::getContentWithDescription<JingleFileTransferDescription>(contents)) {
 		if (content->getTransport<JingleS5BTransportPayload>()) {
 			JingleFileTransferDescription::ref description = content->getDescription<JingleFileTransferDescription>();
-			if (description && description->getOffers().size() == 1) {
+			if (description) {
 				IncomingJingleFileTransfer::ref transfer = boost::make_shared<IncomingJingleFileTransfer>(
 						recipient, session, content, transporterFactory, timerFactory, crypto);
 				onIncomingFileTransfer(transfer);
 			} 
 			else {
-				SWIFT_LOG(warning) << "Received a file-transfer request with no description or more than one file.";
+				SWIFT_LOG(warning) << "Received a file-transfer request with no file description.";
 				session->sendTerminate(JinglePayload::Reason::FailedApplication);
 			}
 		}
diff --git a/Swiften/FileTransfer/IncomingJingleFileTransfer.cpp b/Swiften/FileTransfer/IncomingJingleFileTransfer.cpp
index 66b2e53..720eefd 100644
--- a/Swiften/FileTransfer/IncomingJingleFileTransfer.cpp
+++ b/Swiften/FileTransfer/IncomingJingleFileTransfer.cpp
@@ -1,15 +1,18 @@
 /*
- * Copyright (c) 2011-2013 Isode Limited.
+ * Copyright (c) 2011-2014 Isode Limited.
  * All rights reserved.
  * See the COPYING file for more information.
  */
 
 #include <Swiften/FileTransfer/IncomingJingleFileTransfer.h>
 
+#include <set>
+
 #include <boost/bind.hpp>
 #include <boost/smart_ptr/make_shared.hpp>
 
 #include <Swiften/Base/Log.h>
+#include <Swiften/StringCodecs/Base64.h>
 #include <Swiften/Base/foreach.h>
 #include <Swiften/Jingle/JingleSession.h>
 #include <Swiften/Elements/JingleIBBTransportPayload.h>
@@ -43,11 +46,9 @@ IncomingJingleFileTransfer::IncomingJingleFileTransfer(
 			hashCalculator(NULL) {
 	description = initialContent->getDescription<JingleFileTransferDescription>();
 	assert(description);
-	assert(description->getOffers().size() == 1);
-	StreamInitiationFileInfo fileInfo = description->getOffers().front();
+	JingleFileTransferFileInfo fileInfo = description->getFileInfo();
 	setFileInfo(fileInfo.getName(), fileInfo.getSize());
-	hash = fileInfo.getHash();
-	hashAlgorithm = fileInfo.getAlgo();
+	hashes = fileInfo.getHashes();
 
 	waitOnHashTimer = timerFactory->createTimer(5000);
 	waitOnHashTimerTickedConnection = waitOnHashTimer->onTick.connect(
@@ -68,20 +69,33 @@ void IncomingJingleFileTransfer::accept(
 	this->options = options;
 
 	assert(!hashCalculator);
+
 	hashCalculator = new IncrementalBytestreamHashCalculator(
-			hashAlgorithm == "md5" || hash.empty(), hashAlgorithm == "sha-1" || hash.empty(), crypto);
+			hashes.find("md5") != hashes.end(), hashes.find("sha-1") != hashes.end(), crypto);
 
 	writeStreamDataReceivedConnection = stream->onWrite.connect(
 			boost::bind(&IncomingJingleFileTransfer::handleWriteStreamDataReceived, this, _1));
 
 	if (JingleS5BTransportPayload::ref s5bTransport = initialContent->getTransport<JingleS5BTransportPayload>()) {
-		SWIFT_LOG(debug) << "Got S5B transport payload!" << std::endl;
+		SWIFT_LOG(debug) << "Got S5B transport as initial payload." << std::endl;
 		setTransporter(transporterFactory->createResponderTransporter(
 				getInitiator(), getResponder(), s5bTransport->getSessionID()));
 		transporter->addRemoteCandidates(s5bTransport->getCandidates());
 		setState(GeneratingInitialLocalCandidates);
 		transporter->startGeneratingLocalCandidates();
 	}
+	else if (JingleIBBTransportPayload::ref ibbTransport = initialContent->getTransport<JingleIBBTransportPayload>()) {
+		SWIFT_LOG(debug) << "Got IBB transport as initial payload." << std::endl;
+		setTransporter(transporterFactory->createResponderTransporter(
+				getInitiator(), getResponder(), ibbTransport->getSessionID()));
+
+		startTransferring(transporter->createIBBReceiveSession(
+			ibbTransport->getSessionID(),
+			description->getFileInfo().getSize(),
+			stream));
+
+		session->sendAccept(getContentID(), initialContent->getDescriptions()[0], ibbTransport);
+	}
 	else {
 		// Can't happen, because the transfer would have been rejected automatically
 		assert(false);
@@ -121,13 +135,11 @@ void IncomingJingleFileTransfer::handleSessionInfoReceived(JinglePayload::ref ji
 	if (transferHash) {
 		SWIFT_LOG(debug) << "Received hash information." << std::endl;
 		waitOnHashTimer->stop();
-		if (transferHash->getHashes().find("sha-1") != transferHash->getHashes().end()) {
-			hashAlgorithm = "sha-1";
-			hash = transferHash->getHashes().find("sha-1")->second;
+		if (transferHash->getFileInfo().getHashes().find("sha-1") != transferHash->getFileInfo().getHashes().end()) {
+			hashes["sha-1"] = transferHash->getFileInfo().getHash("sha-1").get();
 		}
-		else if (transferHash->getHashes().find("md5") != transferHash->getHashes().end()) {
-			hashAlgorithm = "md5";
-			hash = transferHash->getHashes().find("md5")->second;
+		else if (transferHash->getFileInfo().getHashes().find("md5") != transferHash->getFileInfo().getHashes().end()) {
+			hashes["md5"] = transferHash->getFileInfo().getHash("md5").get();
 		}
 		if (state == WaitingForHash) {
 			checkHashAndTerminate();
@@ -172,7 +184,12 @@ void IncomingJingleFileTransfer::checkHashAndTerminate() {
 void IncomingJingleFileTransfer::checkIfAllDataReceived() {
 	if (receivedBytes == getFileSizeInBytes()) {
 		SWIFT_LOG(debug) << "All data received." << std::endl;
-		if (hash.empty()) {
+		bool hashInfoAvailable = true;
+		foreach(const JingleFileTransferFileInfo::HashElementMap::value_type& hashElement, hashes) {
+			hashInfoAvailable &= !hashElement.second.empty();
+		}
+
+		if (!hashInfoAvailable) {
 			SWIFT_LOG(debug) << "No hash information yet. Waiting a while on hash info." << std::endl;
 			setState(WaitingForHash);
 			waitOnHashTimer->start();
@@ -207,7 +224,7 @@ void IncomingJingleFileTransfer::handleTransportReplaceReceived(
 
 		startTransferring(transporter->createIBBReceiveSession(
 			ibbTransport->getSessionID(), 
-			description->getOffers()[0].getSize(),
+			description->getFileInfo().getSize(),
 			stream));
 		session->sendTransportAccept(content, ibbTransport);
 	} 
@@ -222,17 +239,17 @@ JingleContentID IncomingJingleFileTransfer::getContentID() const {
 }
 
 bool IncomingJingleFileTransfer::verifyData() {
-	if (hashAlgorithm.empty() || hash.empty()) {
+	if (hashes.empty()) {
 		SWIFT_LOG(debug) << "no verification possible, skipping" << std::endl;
 		return true;
 	} 
-	if (hashAlgorithm == "sha-1") {
-		SWIFT_LOG(debug) << "Verify SHA-1 hash: " << (hash == hashCalculator->getSHA1String()) << std::endl;
-		return hash == hashCalculator->getSHA1String();
+	if (hashes.find("sha-1") != hashes.end()) {
+		SWIFT_LOG(debug) << "Verify SHA-1 hash: " << (hashes["sha-1"] == hashCalculator->getSHA1Hash()) << std::endl;
+		return hashes["sha-1"] == hashCalculator->getSHA1Hash();
 	}
-	else if (hashAlgorithm == "md5") {
-		SWIFT_LOG(debug) << "Verify MD5 hash: " << (hash == hashCalculator->getMD5String()) << std::endl;
-		return hash == hashCalculator->getMD5String();
+	else if (hashes.find("md5") != hashes.end()) {
+		SWIFT_LOG(debug) << "Verify MD5 hash: " << (hashes["md5"] == hashCalculator->getMD5Hash()) << std::endl;
+		return hashes["md5"] == hashCalculator->getMD5Hash();
 	}
 	else {
 		SWIFT_LOG(debug) << "Unknown hash, skipping" << std::endl;
diff --git a/Swiften/FileTransfer/IncomingJingleFileTransfer.h b/Swiften/FileTransfer/IncomingJingleFileTransfer.h
index 727d278..7fc22f4 100644
--- a/Swiften/FileTransfer/IncomingJingleFileTransfer.h
+++ b/Swiften/FileTransfer/IncomingJingleFileTransfer.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2010-2013 Isode Limited.
+ * Copyright (c) 2010-2014 Isode Limited.
  * All rights reserved.
  * See the COPYING file for more information.
  */
@@ -17,6 +17,7 @@
 #include <Swiften/FileTransfer/JingleFileTransfer.h>
 #include <Swiften/Elements/JingleS5BTransportPayload.h>
 #include <Swiften/FileTransfer/FileTransferOptions.h>
+#include <Swiften/Base/ByteArray.h>
 
 namespace Swift {
 	class JID;
@@ -29,7 +30,15 @@ namespace Swift {
 	class CryptoProvider;
 	class IncrementalBytestreamHashCalculator;
 	class JingleFileTransferDescription;
+	class HashElement;
 
+	/**
+	 * @brief The IncomingJingleFileTransfer class contains the business logic for managing incoming
+	 *        Jingle file transfers.
+	 *
+	 * Calling IncomingJingleFileTransfer::accept will start to negotiate possible transfer
+	 * methods and after a working method has been decided among peers the trasnfer is started.
+	 */
 	class SWIFTEN_API IncomingJingleFileTransfer : public IncomingFileTransfer, public JingleFileTransfer {
 		public:
 			typedef boost::shared_ptr<IncomingJingleFileTransfer> ref;
@@ -43,7 +52,7 @@ namespace Swift {
 				CryptoProvider*);
 			~IncomingJingleFileTransfer();
 
-			virtual void accept(boost::shared_ptr<WriteBytestream>, const FileTransferOptions&) SWIFTEN_OVERRIDE;
+			virtual void accept(boost::shared_ptr<WriteBytestream>, const FileTransferOptions& = FileTransferOptions()) SWIFTEN_OVERRIDE;
 			virtual void cancel() SWIFTEN_OVERRIDE;
 
 		private:
@@ -108,8 +117,7 @@ namespace Swift {
 			boost::uintmax_t receivedBytes;
 			IncrementalBytestreamHashCalculator* hashCalculator;
 			boost::shared_ptr<Timer> waitOnHashTimer;
-			std::string hashAlgorithm;
-			std::string hash;
+			std::map<std::string, ByteArray> hashes;
 			FileTransferOptions options;
 
 			boost::bsignals::scoped_connection writeStreamDataReceivedConnection;
diff --git a/Swiften/FileTransfer/IncrementalBytestreamHashCalculator.cpp b/Swiften/FileTransfer/IncrementalBytestreamHashCalculator.cpp
index e982fd0..7eb9560 100644
--- a/Swiften/FileTransfer/IncrementalBytestreamHashCalculator.cpp
+++ b/Swiften/FileTransfer/IncrementalBytestreamHashCalculator.cpp
@@ -5,7 +5,7 @@
  */
 
 /*
- * Copyright (c) 2013 Isode Limited.
+ * Copyright (c) 2013-2014 Isode Limited.
  * All rights reserved.
  * See the COPYING file for more information.
  */
@@ -46,20 +46,30 @@ void IncrementalBytestreamHashCalculator::feedData(const SafeByteArray& data) {
 	}
 }*/
 
-std::string IncrementalBytestreamHashCalculator::getSHA1String() {
+ByteArray IncrementalBytestreamHashCalculator::getSHA1Hash() {
 	assert(sha1Hasher);
 	if (!sha1Hash) {
-		sha1Hash = Hexify::hexify(sha1Hasher->getHash());
+		sha1Hash = sha1Hasher->getHash();
 	}
 	return *sha1Hash;
 }
 
-std::string IncrementalBytestreamHashCalculator::getMD5String() {
+ByteArray IncrementalBytestreamHashCalculator::getMD5Hash() {
 	assert(md5Hasher);
 	if (!md5Hash) {
-		md5Hash = Hexify::hexify(md5Hasher->getHash());
+		md5Hash = md5Hasher->getHash();
 	}
 	return *md5Hash;
 }
 
+std::string IncrementalBytestreamHashCalculator::getSHA1String() {
+	assert(sha1Hasher);
+	return Hexify::hexify(getSHA1Hash());;
+}
+
+std::string IncrementalBytestreamHashCalculator::getMD5String() {
+	assert(md5Hasher);
+	return Hexify::hexify(getMD5Hash());;
+}
+
 }
diff --git a/Swiften/FileTransfer/IncrementalBytestreamHashCalculator.h b/Swiften/FileTransfer/IncrementalBytestreamHashCalculator.h
index bb6b441..bc4ebf9 100644
--- a/Swiften/FileTransfer/IncrementalBytestreamHashCalculator.h
+++ b/Swiften/FileTransfer/IncrementalBytestreamHashCalculator.h
@@ -5,7 +5,7 @@
  */
 
 /*
- * Copyright (c) 2013 Isode Limited.
+ * Copyright (c) 2013-2014 Isode Limited.
  * All rights reserved.
  * See the COPYING file for more information.
  */
@@ -30,14 +30,17 @@ namespace Swift {
 		void feedData(const ByteArray& data);
 		//void feedData(const SafeByteArray& data);
 
+		ByteArray getSHA1Hash();
+		ByteArray getMD5Hash();
+
 		std::string getSHA1String();
 		std::string getMD5String();
 
 	private:
 		Hash* md5Hasher;
 		Hash* sha1Hasher;
-		boost::optional<std::string> md5Hash;
-		boost::optional<std::string> sha1Hash;
+		boost::optional<ByteArray> md5Hash;
+		boost::optional<ByteArray> sha1Hash;
 	};
 
 }
diff --git a/Swiften/FileTransfer/OutgoingFileTransferManager.cpp b/Swiften/FileTransfer/OutgoingFileTransferManager.cpp
index 5ed4656..5d0555f 100644
--- a/Swiften/FileTransfer/OutgoingFileTransferManager.cpp
+++ b/Swiften/FileTransfer/OutgoingFileTransferManager.cpp
@@ -5,7 +5,7 @@
  */
 
 /*
- * Copyright (c) 2013 Isode Limited.
+ * Copyright (c) 2013-2014 Isode Limited.
  * All rights reserved.
  * See the COPYING file for more information.
  */
@@ -43,7 +43,7 @@ boost::shared_ptr<OutgoingFileTransfer> OutgoingFileTransferManager::createOutgo
 		const JID& from, 
 		const JID& recipient, 
 		boost::shared_ptr<ReadBytestream> readBytestream, 
-		const StreamInitiationFileInfo& fileInfo,
+		const JingleFileTransferFileInfo& fileInfo,
 		const FileTransferOptions& config) {
 	JingleSessionImpl::ref jingleSession = boost::make_shared<JingleSessionImpl>(
 			from, recipient, idGenerator->generateID(), iqRouter);
diff --git a/Swiften/FileTransfer/OutgoingFileTransferManager.h b/Swiften/FileTransfer/OutgoingFileTransferManager.h
index 8dd4bbc..fd7380b 100644
--- a/Swiften/FileTransfer/OutgoingFileTransferManager.h
+++ b/Swiften/FileTransfer/OutgoingFileTransferManager.h
@@ -5,7 +5,7 @@
  */
 
 /*
- * Copyright (c) 2013 Isode Limited.
+ * Copyright (c) 2013-2014 Isode Limited.
  * All rights reserved.
  * See the COPYING file for more information.
  */
@@ -22,7 +22,7 @@ namespace Swift {
 	class JID;
 	class IDGenerator;
 	class ReadBytestream;
-	class StreamInitiationFileInfo;
+	class JingleFileTransferFileInfo;
 	class CryptoProvider;
 	class FileTransferOptions;
 
@@ -39,7 +39,7 @@ namespace Swift {
 					const JID& from, 
 					const JID& to, 
 					boost::shared_ptr<ReadBytestream>, 
-					const StreamInitiationFileInfo&,
+					const JingleFileTransferFileInfo&,
 					const FileTransferOptions&);
 
 		private:
diff --git a/Swiften/FileTransfer/OutgoingJingleFileTransfer.cpp b/Swiften/FileTransfer/OutgoingJingleFileTransfer.cpp
index 93214eb..369af8f 100644
--- a/Swiften/FileTransfer/OutgoingJingleFileTransfer.cpp
+++ b/Swiften/FileTransfer/OutgoingJingleFileTransfer.cpp
@@ -5,7 +5,7 @@
  */
 
 /*
- * Copyright (C) 2013 Isode Limited.
+ * Copyright (C) 2013-2014 Isode Limited.
  * All rights reserved.
  * See the COPYING file for more information.
  */
@@ -49,7 +49,7 @@ OutgoingJingleFileTransfer::OutgoingJingleFileTransfer(
 		boost::shared_ptr<ReadBytestream> stream,
 		FileTransferTransporterFactory* transporterFactory,
 		IDGenerator* idGenerator,
-		const StreamInitiationFileInfo& fileInfo,
+		const JingleFileTransferFileInfo& fileInfo,
 		const FileTransferOptions& options,
 		CryptoProvider* crypto) :
 			JingleFileTransfer(session, toJID, transporterFactory),
@@ -149,8 +149,8 @@ void OutgoingJingleFileTransfer::sendSessionInfoHash() {
 	SWIFT_LOG(debug) << std::endl;
 
 	JingleFileTransferHash::ref hashElement = boost::make_shared<JingleFileTransferHash>();
-	hashElement->setHash("sha-1", hashCalculator->getSHA1String());
-	hashElement->setHash("md5", hashCalculator->getMD5String());
+	hashElement->getFileInfo().addHash(HashElement("sha-1", hashCalculator->getSHA1Hash()));
+	hashElement->getFileInfo().addHash(HashElement("md5", hashCalculator->getMD5Hash()));
 	session->sendInfo(hashElement);
 }
 
@@ -162,7 +162,9 @@ void OutgoingJingleFileTransfer::handleLocalTransportCandidatesGenerated(
 	fillCandidateMap(localCandidates, candidates);
 
 	JingleFileTransferDescription::ref description = boost::make_shared<JingleFileTransferDescription>();
-	description->addOffer(fileInfo);
+	fileInfo.addHash(HashElement("sha-1", ByteArray()));
+	fileInfo.addHash(HashElement("md5", ByteArray()));
+	description->setFileInfo(fileInfo);
 
 	JingleS5BTransportPayload::ref transport = boost::make_shared<JingleS5BTransportPayload>();
 	transport->setSessionID(s5bSessionID);
diff --git a/Swiften/FileTransfer/OutgoingJingleFileTransfer.h b/Swiften/FileTransfer/OutgoingJingleFileTransfer.h
index c21c50b..f022b9f 100644
--- a/Swiften/FileTransfer/OutgoingJingleFileTransfer.h
+++ b/Swiften/FileTransfer/OutgoingJingleFileTransfer.h
@@ -5,7 +5,7 @@
  */
 
 /*
- * Copyright (c) 2013 Isode Limited.
+ * Copyright (c) 2013-2014 Isode Limited.
  * All rights reserved.
  * See the COPYING file for more information.
  */
@@ -18,7 +18,7 @@
 #include <Swiften/Base/API.h>
 #include <Swiften/Base/Override.h>
 #include <Swiften/Jingle/JingleContentID.h>
-#include <Swiften/Elements/StreamInitiationFileInfo.h>
+#include <Swiften/Elements/JingleFileTransferFileInfo.h>
 #include <Swiften/FileTransfer/OutgoingFileTransfer.h>
 #include <Swiften/FileTransfer/JingleFileTransfer.h>
 #include <Swiften/FileTransfer/FileTransferOptions.h>
@@ -40,7 +40,7 @@ namespace Swift {
 				boost::shared_ptr<ReadBytestream>,
 				FileTransferTransporterFactory*,
 				IDGenerator*,
-				const StreamInitiationFileInfo&,
+				const JingleFileTransferFileInfo&,
 				const FileTransferOptions&,
 				CryptoProvider*);
 			virtual ~OutgoingJingleFileTransfer();
@@ -99,7 +99,7 @@ namespace Swift {
 		private:
 			IDGenerator* idGenerator;
 			boost::shared_ptr<ReadBytestream> stream;
-			StreamInitiationFileInfo fileInfo;
+			JingleFileTransferFileInfo fileInfo;
 			FileTransferOptions options;
 			JingleContentID contentID;
 			IncrementalBytestreamHashCalculator* hashCalculator;
diff --git a/Swiften/FileTransfer/UnitTest/IncomingJingleFileTransferTest.cpp b/Swiften/FileTransfer/UnitTest/IncomingJingleFileTransferTest.cpp
index a296b33..207f590 100644
--- a/Swiften/FileTransfer/UnitTest/IncomingJingleFileTransferTest.cpp
+++ b/Swiften/FileTransfer/UnitTest/IncomingJingleFileTransferTest.cpp
@@ -5,7 +5,7 @@
  */
 
 /*
- * Copyright (c) 2013 Isode Limited.
+ * Copyright (c) 2013-2014 Isode Limited.
  * All rights reserved.
  * See the COPYING file for more information.
  */
@@ -22,14 +22,20 @@
 #include <Swiften/Elements/IBB.h>
 #include <Swiften/Elements/JingleIBBTransportPayload.h>
 #include <Swiften/Elements/JingleS5BTransportPayload.h>
+#include <Swiften/Elements/JingleFileTransferDescription.h>
+#include <Swiften/FileTransfer/DefaultFileTransferTransporterFactory.h>
 #include <Swiften/FileTransfer/ByteArrayWriteBytestream.h>
 #include <Swiften/FileTransfer/IncomingJingleFileTransfer.h>
 #include <Swiften/FileTransfer/SOCKS5BytestreamRegistry.h>
+#include <Swiften/Network/PlatformNetworkEnvironment.h>
 #include <Swiften/FileTransfer/SOCKS5BytestreamProxiesManager.h>
+#include <Swiften/FileTransfer/SOCKS5BytestreamServerManager.h>
 #include <Swiften/Jingle/FakeJingleSession.h>
+#include <Swiften/Network/NATTraverser.h>
 #include <Swiften/Network/DummyTimerFactory.h>
 #include <Swiften/EventLoop/DummyEventLoop.h>
 #include <Swiften/Network/DummyConnectionFactory.h>
+#include <Swiften/Network/DummyConnectionServerFactory.h>
 #include <Swiften/Network/PlatformNATTraversalWorker.h>
 #include <Swiften/Queries/IQRouter.h>
 #include <Swiften/Crypto/CryptoProvider.h>
@@ -42,53 +48,61 @@ using namespace boost;
 
 class IncomingJingleFileTransferTest : public CppUnit::TestFixture {
 		CPPUNIT_TEST_SUITE(IncomingJingleFileTransferTest);
-		//CPPUNIT_TEST(test_AcceptOnyIBBSendsSessionAccept);
-		//CPPUNIT_TEST(test_OnlyIBBTransferReceiveWorks);
+		CPPUNIT_TEST(test_AcceptOnyIBBSendsSessionAccept);
+		CPPUNIT_TEST(test_OnlyIBBTransferReceiveWorks);
 		//CPPUNIT_TEST(test_AcceptFailingS5BFallsBackToIBB);
 		CPPUNIT_TEST_SUITE_END();
 public:
-		// shared_ptr<IncomingJingleFileTransfer> createTestling() {
-		// 	JID ourJID("our@jid.org/full");
-		// 	return boost::shared_ptr<IncomingJingleFileTransfer>(new IncomingJingleFileTransfer(ourJID, shared_ptr<JingleSession>(session), jingleContentPayload, fakeRJTCSF.get(), fakeLJTCF.get(), iqRouter, bytestreamRegistry, bytestreamProxy, timerFactory, crypto.get()));
-		// }
+		shared_ptr<IncomingJingleFileTransfer> createTestling() {
+			JID ourJID("our@jid.org/full");
+			return boost::make_shared<IncomingJingleFileTransfer>(ourJID, shared_ptr<JingleSession>(session), jingleContentPayload, ftTransporterFactory, timerFactory, crypto.get());
+		}
 
-		// IQ::ref createIBBRequest(IBB::ref ibb, const JID& from, const std::string& id) {
-		// 	IQ::ref request = IQ::createRequest(IQ::Set, JID("foo@bar.com/baz"), id, ibb);
-		// 	request->setFrom(from);
-		// 	return request;
-		// }
+		IQ::ref createIBBRequest(IBB::ref ibb, const JID& from, const std::string& id) {
+			IQ::ref request = IQ::createRequest(IQ::Set, JID("foo@bar.com/baz"), id, ibb);
+			request->setFrom(from);
+			return request;
+		}
 
 		void setUp() {
 			crypto = boost::shared_ptr<CryptoProvider>(PlatformCryptoProvider::create());
 			eventLoop = new DummyEventLoop();
 			session = boost::make_shared<FakeJingleSession>("foo@bar.com/baz", "mysession");
-			// jingleContentPayload = make_shared<JingleContentPayload>();
+			jingleContentPayload = make_shared<JingleContentPayload>();
 			// fakeRJTCSF = make_shared<FakeRemoteJingleTransportCandidateSelectorFactory>();
 			// fakeLJTCF = make_shared<FakeLocalJingleTransportCandidateGeneratorFactory>();
-			// stanzaChannel = new DummyStanzaChannel();
-			// iqRouter = new IQRouter(stanzaChannel);
-			// bytestreamRegistry = new SOCKS5BytestreamRegistry();
-			// timerFactory = new DummyTimerFactory();
-			// connectionFactory = new DummyConnectionFactory(eventLoop);
-			// bytestreamProxy = new SOCKS5BytestreamProxiesManager(connectionFactory, timerFactory);
+			stanzaChannel = new DummyStanzaChannel();
+			connectionFactory = new DummyConnectionFactory(eventLoop);
+			serverConnectionFactory = new DummyConnectionServerFactory(eventLoop);
+			iqRouter = new IQRouter(stanzaChannel);
+			bytestreamRegistry = new SOCKS5BytestreamRegistry();
+			networkEnvironment = new PlatformNetworkEnvironment();
+			natTraverser = new PlatformNATTraversalWorker(eventLoop);
+			bytestreamServerManager = new SOCKS5BytestreamServerManager(bytestreamRegistry, serverConnectionFactory, networkEnvironment, natTraverser);
+			idGenerator = new SimpleIDGenerator();
+			timerFactory = new DummyTimerFactory();
+			bytestreamProxy = new SOCKS5BytestreamProxiesManager(connectionFactory, timerFactory);
+			ftTransporterFactory = new DefaultFileTransferTransporterFactory(bytestreamRegistry, bytestreamServerManager, bytestreamProxy, idGenerator, connectionFactory, timerFactory, crypto.get(), iqRouter);
 		}
 
 		void tearDown() {
-			// delete bytestreamProxy;
-			// delete connectionFactory;
-			// delete timerFactory;
-			// delete bytestreamRegistry;
-			// delete iqRouter;
-			// delete stanzaChannel;
+			delete ftTransporterFactory;
+			delete bytestreamServerManager;
+			delete bytestreamProxy;
+			delete connectionFactory;
+			delete timerFactory;
+			delete bytestreamRegistry;
+			delete iqRouter;
+			delete stanzaChannel;
 			delete eventLoop;
+			Log::setLogLevel(Log::error);
 		}
 
 		// Tests whether IncomingJingleFileTransfer would accept a IBB only file transfer.
-#if 0
 		void test_AcceptOnyIBBSendsSessionAccept() {
 			//1. create your test incoming file transfer
 			shared_ptr<JingleFileTransferDescription> desc = make_shared<JingleFileTransferDescription>();
-			desc->addOffer(StreamInitiationFileInfo("foo.txt", "", 10));
+			desc->setFileInfo(JingleFileTransferFileInfo("foo.txt", "", 10));
 			jingleContentPayload->addDescription(desc);
 			JingleIBBTransportPayload::ref tpRef = make_shared<JingleIBBTransportPayload>();
 			tpRef->setSessionID("mysession");
@@ -107,7 +121,7 @@ public:
 		void test_OnlyIBBTransferReceiveWorks() {
 			//1. create your test incoming file transfer
 			shared_ptr<JingleFileTransferDescription> desc = make_shared<JingleFileTransferDescription>();
-			desc->addOffer(StreamInitiationFileInfo("file.txt", "", 10));
+			desc->setFileInfo(JingleFileTransferFileInfo("file.txt", "", 10));
 			jingleContentPayload->addDescription(desc);
 			JingleIBBTransportPayload::ref tpRef = make_shared<JingleIBBTransportPayload>();
 			tpRef->setSessionID("mysession");
@@ -139,6 +153,8 @@ public:
 			shared_ptr<ByteArrayWriteBytestream> byteStream = make_shared<ByteArrayWriteBytestream>();
 			fileTransfer->accept(byteStream);
 
+			// candidates are gathered
+
 			// check whether accept has been called
 			FakeJingleSession::AcceptCall acceptCall = getCall<FakeJingleSession::AcceptCall>(0);
 			CPPUNIT_ASSERT_EQUAL(payLoad->getSessionID(), acceptCall.payload->getSessionID());
@@ -149,7 +165,7 @@ public:
 			CPPUNIT_ASSERT(s5bPayload->hasCandidateError());
 
 			// indicate transport replace (Romeo)
-			session->onTransportReplaceReceived(getContentID(), addJingleIBBPayload());
+			session->handleTransportReplaceReceived(getContentID(), addJingleIBBPayload());
 
 			FakeJingleSession::AcceptTransportCall acceptTransportCall = getCall<FakeJingleSession::AcceptTransportCall>(2);
 
@@ -158,16 +174,16 @@ public:
 			stanzaChannel->onIQReceived(createIBBRequest(IBB::createIBBData("mysession", 0, createByteArray("abc")), "foo@bar.com/baz", "id-a"));
 			CPPUNIT_ASSERT(createByteArray("abc") == byteStream->getData());
 		}
-
+#if 0
 		void test_S5BTransferReceiveTest() {
 			addFileTransferDescription();
 			JingleS5BTransportPayload::ref payLoad = addJingleS5BPayload();
 		}
-
+#endif
 private:
 	void addFileTransferDescription() {
 		shared_ptr<JingleFileTransferDescription> desc = make_shared<JingleFileTransferDescription>();
-		desc->addOffer(StreamInitiationFileInfo("file.txt", "", 10));
+		desc->setFileInfo(JingleFileTransferFileInfo("file.txt", "", 10));
 		jingleContentPayload->addDescription(desc);
 	}
 
@@ -196,23 +212,26 @@ private:
 		CPPUNIT_ASSERT(cmd);
 		return *cmd;
 	}
-#endif
 
 private:
 	EventLoop* eventLoop;
 	boost::shared_ptr<CryptoProvider> crypto;
 	boost::shared_ptr<FakeJingleSession> session;
-#if 0
 	shared_ptr<JingleContentPayload> jingleContentPayload;
-	shared_ptr<FakeRemoteJingleTransportCandidateSelectorFactory> fakeRJTCSF;
-	shared_ptr<FakeLocalJingleTransportCandidateGeneratorFactory> fakeLJTCF;
+//	shared_ptr<FakeRemoteJingleTransportCandidateSelectorFactory> fakeRJTCSF;
+//	shared_ptr<FakeLocalJingleTransportCandidateGeneratorFactory> fakeLJTCF;
+	FileTransferTransporterFactory* ftTransporterFactory;
+	SOCKS5BytestreamServerManager* bytestreamServerManager;
 	DummyStanzaChannel* stanzaChannel;
 	IQRouter* iqRouter;
 	SOCKS5BytestreamRegistry* bytestreamRegistry;
 	DummyConnectionFactory* connectionFactory;
+	DummyConnectionServerFactory* serverConnectionFactory;
 	SOCKS5BytestreamProxiesManager* bytestreamProxy;
 	DummyTimerFactory* timerFactory;
-#endif
+	NetworkEnvironment* networkEnvironment;
+	NATTraverser* natTraverser;
+	IDGenerator* idGenerator;
 };
 
 CPPUNIT_TEST_SUITE_REGISTRATION(IncomingJingleFileTransferTest);
diff --git a/Swiften/Jingle/FakeJingleSession.cpp b/Swiften/Jingle/FakeJingleSession.cpp
index 1df106a..3b94da7 100644
--- a/Swiften/Jingle/FakeJingleSession.cpp
+++ b/Swiften/Jingle/FakeJingleSession.cpp
@@ -4,7 +4,14 @@
  * See Documentation/Licenses/BSD-simplified.txt for more information.
  */
 
+/*
+ * Copyright (c) 2014 Isode Limited.
+ * All rights reserved.
+ * See the COPYING file for more information.
+ */
+
 #include <Swiften/Jingle/FakeJingleSession.h>
+#include <Swiften/Jingle/JingleSessionListener.h>
 
 #include <boost/smart_ptr/make_shared.hpp>
 
@@ -50,4 +57,8 @@ void FakeJingleSession::sendTransportReplace(const JingleContentID& id, JingleTr
 	calledCommands.push_back(ReplaceTransportCall(id, payload));
 }
 
+void FakeJingleSession::handleTransportReplaceReceived(const JingleContentID& contentID, JingleTransportPayload::ref transport) {
+	notifyListeners(&JingleSessionListener::handleTransportReplaceReceived, contentID, transport);
+}
+
 }
diff --git a/Swiften/Jingle/FakeJingleSession.h b/Swiften/Jingle/FakeJingleSession.h
index 24e85d8..19028ad 100644
--- a/Swiften/Jingle/FakeJingleSession.h
+++ b/Swiften/Jingle/FakeJingleSession.h
@@ -5,7 +5,7 @@
  */
 
 /*
- * Copyright (c) 2013 Isode Limited.
+ * Copyright (c) 2013-2014 Isode Limited.
  * All rights reserved.
  * See the COPYING file for more information.
  */
@@ -96,6 +96,8 @@ namespace Swift {
 			virtual void sendTransportReject(const JingleContentID&, JingleTransportPayload::ref) SWIFTEN_OVERRIDE;
 			virtual void sendTransportReplace(const JingleContentID&, JingleTransportPayload::ref) SWIFTEN_OVERRIDE;
 
+			void handleTransportReplaceReceived(const JingleContentID&, JingleTransportPayload::ref);
+
 		public:
 			std::vector<Command> calledCommands;
 			SimpleIDGenerator idGenerator;
diff --git a/Swiften/Network/DummyConnectionServer.h b/Swiften/Network/DummyConnectionServer.h
new file mode 100644
index 0000000..0cbce0e
--- /dev/null
+++ b/Swiften/Network/DummyConnectionServer.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 2010-2014 Isode Limited.
+ * All rights reserved.
+ * See the COPYING file for more information.
+ */
+
+#pragma once
+
+#include <boost/enable_shared_from_this.hpp>
+
+#include <Swiften/Base/API.h>
+#include <Swiften/Network/ConnectionServer.h>
+#include <Swiften/Network/HostAddressPort.h>
+#include <Swiften/EventLoop/EventLoop.h>
+#include <Swiften/EventLoop/EventOwner.h>
+
+namespace Swift {
+	class SWIFTEN_API DummyConnectionServer : public ConnectionServer, public EventOwner, public boost::enable_shared_from_this<DummyConnectionServer> {
+		public:
+			DummyConnectionServer(EventLoop* eventLoop, int port) : eventLoop(eventLoop), localAddressPort(HostAddress(), port) {}
+			DummyConnectionServer(EventLoop* eventLoop, const Swift::HostAddress& hostAddress, int port) : eventLoop(eventLoop), localAddressPort(hostAddress, port) {}
+			virtual ~DummyConnectionServer() {}
+
+			virtual HostAddressPort getAddressPort() const {
+				return localAddressPort;
+			}
+
+			virtual boost::optional<Error> tryStart() {
+				return boost::optional<Error>();
+			}
+
+			virtual void start() {
+
+			}
+
+			virtual void stop() {
+
+			}
+
+		public:
+			EventLoop* eventLoop;
+			HostAddressPort localAddressPort;
+	};
+}
diff --git a/Swiften/Network/DummyConnectionServerFactory.h b/Swiften/Network/DummyConnectionServerFactory.h
new file mode 100644
index 0000000..6369452
--- /dev/null
+++ b/Swiften/Network/DummyConnectionServerFactory.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2014 Isode Limited.
+ * All rights reserved.
+ * See the COPYING file for more information.
+ */
+
+#pragma once
+
+#include <boost/smart_ptr/make_shared.hpp>
+
+#include <Swiften/Network/ConnectionServerFactory.h>
+#include <Swiften/Network/DummyConnectionServer.h>
+
+namespace Swift {
+
+class EventLoop;
+
+class DummyConnectionServerFactory : public ConnectionServerFactory {
+public:
+	DummyConnectionServerFactory(EventLoop* eventLoop) : eventLoop(eventLoop) {}
+	virtual ~DummyConnectionServerFactory() {}
+
+	virtual boost::shared_ptr<ConnectionServer> createConnectionServer(int port) {
+		return boost::make_shared<DummyConnectionServer>(eventLoop, port);
+	}
+
+	virtual boost::shared_ptr<ConnectionServer> createConnectionServer(const Swift::HostAddress& hostAddress, int port) {
+		return boost::make_shared<DummyConnectionServer>(eventLoop, hostAddress, port);
+	}
+
+private:
+	EventLoop* eventLoop;
+};
+
+}
diff --git a/Swiften/Parser/PayloadParsers/FullPayloadParserFactoryCollection.cpp b/Swiften/Parser/PayloadParsers/FullPayloadParserFactoryCollection.cpp
index fe61c72..812e968 100644
--- a/Swiften/Parser/PayloadParsers/FullPayloadParserFactoryCollection.cpp
+++ b/Swiften/Parser/PayloadParsers/FullPayloadParserFactoryCollection.cpp
@@ -78,6 +78,7 @@
 #include <Swiften/Parser/PayloadParsers/StatusShowParser.h>
 #include <Swiften/Parser/PayloadParsers/StorageParser.h>
 #include <Swiften/Parser/PayloadParsers/StreamInitiationFileInfoParser.h>
+#include <Swiften/Parser/PayloadParsers/JingleFileTransferFileInfoParser.h>
 #include <Swiften/Parser/PayloadParsers/StreamInitiationParser.h>
 #include <Swiften/Parser/PayloadParsers/SubjectParser.h>
 #include <Swiften/Parser/PayloadParsers/UserLocationParser.h>
@@ -140,6 +141,7 @@ FullPayloadParserFactoryCollection::FullPayloadParserFactoryCollection() {
 	factories_.push_back(boost::make_shared<GenericPayloadParserFactory<JingleS5BTransportMethodPayloadParser> >("transport", "urn:xmpp:jingle:transports:s5b:1"));
 	factories_.push_back(boost::make_shared<JingleFileTransferDescriptionParserFactory>(this));
 	factories_.push_back(boost::make_shared<GenericPayloadParserFactory<StreamInitiationFileInfoParser> >("file", "http://jabber.org/protocol/si/profile/file-transfer"));
+	factories_.push_back(boost::make_shared<GenericPayloadParserFactory<JingleFileTransferFileInfoParser> >("file", "urn:xmpp:jingle:apps:file-transfer:4"));
 	factories_.push_back(boost::make_shared<GenericPayloadParserFactory<JingleFileTransferReceivedParser> >("received", "urn:xmpp:jingle:apps:file-transfer:3"));
 	factories_.push_back(boost::make_shared<GenericPayloadParserFactory<JingleFileTransferHashParser> >("checksum"));
 	factories_.push_back(boost::make_shared<GenericPayloadParserFactory<S5BProxyRequestParser> >("query", "http://jabber.org/protocol/bytestreams"));
diff --git a/Swiften/Parser/PayloadParsers/JingleFileTransferDescriptionParser.cpp b/Swiften/Parser/PayloadParsers/JingleFileTransferDescriptionParser.cpp
index b394115..fb1836f 100644
--- a/Swiften/Parser/PayloadParsers/JingleFileTransferDescriptionParser.cpp
+++ b/Swiften/Parser/PayloadParsers/JingleFileTransferDescriptionParser.cpp
@@ -4,72 +4,59 @@
  * See Documentation/Licenses/BSD-simplified.txt for more information.
  */
 
-#include "JingleFileTransferDescriptionParser.h"
+/*
+ * Copyright (c) 2014 Isode Limited.
+ * All rights reserved.
+ * See the COPYING file for more information.
+ */
+
+#include <Swiften/Parser/PayloadParsers/JingleFileTransferDescriptionParser.h>
+
+#include <boost/optional.hpp>
+#include <boost/lexical_cast.hpp>
 
 #include <Swiften/Parser/PayloadParserFactoryCollection.h>
 #include <Swiften/Parser/PayloadParserFactory.h>
-#include <Swiften/Base/Log.h>
+#include <Swiften/Base/DateTime.h>
+#include <Swiften/StringCodecs/Base64.h>
 
 namespace Swift {
 
-JingleFileTransferDescriptionParser::JingleFileTransferDescriptionParser(PayloadParserFactoryCollection* factories) :   factories(factories), level(0),
-															currentElement(UnknownElement) {
-	
+JingleFileTransferDescriptionParser::JingleFileTransferDescriptionParser(PayloadParserFactoryCollection* factories) :   factories(factories), level(0) {
 }
 
 void JingleFileTransferDescriptionParser::handleStartElement(const std::string& element, const std::string& ns, const AttributeMap& attributes) {
-	if (level == 0) {
-		
-	}
-	
 	if (level == 1) {
-		if (element == "offer") {
-			currentElement = OfferElement;
-		} else if (element == "request") {
-			currentElement = RequestElement;
-		} else {
-			currentElement = UnknownElement;
-		}
-	}
-
-	if (level == 2) {
 		PayloadParserFactory* payloadParserFactory = factories->getPayloadParserFactory(element, ns, attributes);
 		if (payloadParserFactory) {
 			currentPayloadParser.reset(payloadParserFactory->createPayloadParser());
 		}
 	}
 
-	if (level >= 2 && currentPayloadParser) {
+	if (level >= 1 && currentPayloadParser) {
 		currentPayloadParser->handleStartElement(element, ns, attributes);
 	}
-
 	++level;
 }
 
 void JingleFileTransferDescriptionParser::handleEndElement(const std::string& element, const std::string& ns) {
 	--level;
-	if (currentPayloadParser) { 
-		if (level >= 2) {
-			currentPayloadParser->handleEndElement(element, ns);
-		}
+	if (level >= 1 && currentPayloadParser) {
+		currentPayloadParser->handleEndElement(element, ns);
+	}
 
-		if (level == 2) {
-			boost::shared_ptr<StreamInitiationFileInfo> info = boost::dynamic_pointer_cast<StreamInitiationFileInfo>(currentPayloadParser->getPayload());
-			if (info) {
-				if (currentElement == OfferElement) {
-					getPayloadInternal()->addOffer(*info);
-				} else if (currentElement == RequestElement) {
-					getPayloadInternal()->addRequest(*info);
-				}
-			}
+	if (level == 0) {
+		boost::shared_ptr<JingleFileTransferFileInfo> info = boost::dynamic_pointer_cast<JingleFileTransferFileInfo>(currentPayloadParser->getPayload());
+		if (info) {
+			getPayloadInternal()->setFileInfo(*info);
 		}
 	}
 }
 
 void JingleFileTransferDescriptionParser::handleCharacterData(const std::string& data) {
-	if (level >= 2 && currentPayloadParser) {
+	if (level >= 1 && currentPayloadParser) {
 		currentPayloadParser->handleCharacterData(data);
 	}
 }
-		
+
 }
diff --git a/Swiften/Parser/PayloadParsers/JingleFileTransferDescriptionParser.h b/Swiften/Parser/PayloadParsers/JingleFileTransferDescriptionParser.h
index 7ea22b4..a1215c1 100644
--- a/Swiften/Parser/PayloadParsers/JingleFileTransferDescriptionParser.h
+++ b/Swiften/Parser/PayloadParsers/JingleFileTransferDescriptionParser.h
@@ -4,6 +4,12 @@
  * See Documentation/Licenses/BSD-simplified.txt for more information.
  */
 
+/*
+ * Copyright (c) 2014 Isode Limited.
+ * All rights reserved.
+ * See the COPYING file for more information.
+ */
+
 #pragma once
 
 #include <Swiften/Elements/JingleFileTransferDescription.h>
@@ -20,18 +26,11 @@ class JingleFileTransferDescriptionParser : public GenericPayloadParser<JingleFi
 
 		virtual void handleStartElement(const std::string& element, const std::string&, const AttributeMap& attributes);
 		virtual void handleEndElement(const std::string& element, const std::string&);
-		virtual void handleCharacterData(const std::string& data);	
+		virtual void handleCharacterData(const std::string& data);
 
 	private:
-		enum CurrentParseElement {
-			UnknownElement,
-			RequestElement,
-			OfferElement
-		};
-		
 		PayloadParserFactoryCollection* factories;
 		int level;
-		CurrentParseElement currentElement;
 		boost::shared_ptr<PayloadParser> currentPayloadParser;
 };
 
diff --git a/Swiften/Parser/PayloadParsers/JingleFileTransferDescriptionParserFactory.h b/Swiften/Parser/PayloadParsers/JingleFileTransferDescriptionParserFactory.h
index b997c1d..7bd4b8a 100644
--- a/Swiften/Parser/PayloadParsers/JingleFileTransferDescriptionParserFactory.h
+++ b/Swiften/Parser/PayloadParsers/JingleFileTransferDescriptionParserFactory.h
@@ -4,6 +4,12 @@
  * See Documentation/Licenses/BSD-simplified.txt for more information.
  */
 
+/*
+ * Copyright (c) 2014 Isode Limited.
+ * All rights reserved.
+ * See the COPYING file for more information.
+ */
+
 #pragma once
 
 #include <Swiften/Parser/GenericPayloadParserFactory.h>
@@ -19,7 +25,7 @@ namespace Swift {
 			}
 
 			virtual bool canParse(const std::string& element, const std::string& ns, const AttributeMap&) const {
-				return element == "description" && ns == "urn:xmpp:jingle:apps:file-transfer:3";
+				return element == "description" && ns == "urn:xmpp:jingle:apps:file-transfer:4";
 			}
 
 			virtual PayloadParser* createPayloadParser() {
diff --git a/Swiften/Parser/PayloadParsers/JingleFileTransferFileInfoParser.cpp b/Swiften/Parser/PayloadParsers/JingleFileTransferFileInfoParser.cpp
new file mode 100644
index 0000000..2a62f23
--- /dev/null
+++ b/Swiften/Parser/PayloadParsers/JingleFileTransferFileInfoParser.cpp
@@ -0,0 +1,79 @@
+/*
+ * Copyright (c) 2014 Isode Limited.
+ * All rights reserved.
+ * See the COPYING file for more information.
+ */
+
+#include <Swiften/Parser/PayloadParsers/JingleFileTransferFileInfoParser.h>
+
+#include <boost/optional.hpp>
+#include <boost/lexical_cast.hpp>
+
+#include <Swiften/Base/DateTime.h>
+#include <Swiften/StringCodecs/Base64.h>
+
+namespace Swift {
+
+JingleFileTransferFileInfoParser::JingleFileTransferFileInfoParser() : level(0) {	
+}
+
+template<typename T> boost::optional<T> safeLexicalCast(const std::string& str) {
+	boost::optional<T> ret;
+	try {
+		ret = boost::lexical_cast<T>(str);
+	} catch (boost::bad_lexical_cast &) {
+
+	}
+	return ret;
+}
+
+void JingleFileTransferFileInfoParser::handleStartElement(const std::string& element, const std::string&, const AttributeMap& attributes) {
+	charData.clear();
+	if (element == "hash") {
+		hashAlg = attributes.getAttributeValue("algo").get_value_or("");
+	}
+	else if (element == "range") {
+		rangeOffset = safeLexicalCast<boost::uintmax_t>(attributes.getAttributeValue("offset").get_value_or(""));
+	}
+
+	++level;
+}
+
+void JingleFileTransferFileInfoParser::handleEndElement(const std::string& element, const std::string&) {
+	--level;
+	if (level == 1) {
+		if (element == "date") {
+			getPayloadInternal()->setDate(stringToDateTime(charData));
+		}
+		else if (element == "desc") {
+			getPayloadInternal()->setDescription(charData);
+		}
+		else if (element == "media-type") {
+			getPayloadInternal()->setMediaType(charData);
+		}
+		else if (element == "name") {
+			getPayloadInternal()->setName(charData);
+		}
+		else if (element == "size") {
+			boost::optional<boost::uintmax_t> size = safeLexicalCast<boost::uintmax_t>(charData);
+			if (size) {
+				getPayloadInternal()->setSize(size.get());
+			}
+		}
+		else if (element == "range") {
+			getPayloadInternal()->setSupportsRangeRequests(true);
+			if (rangeOffset) {
+				getPayloadInternal()->setRangeOffset(rangeOffset.get_value_or(0));
+			}
+		}
+		else if (element == "hash") {
+			getPayloadInternal()->addHash(HashElement(hashAlg, Base64::decode(charData)));
+		}
+	}
+}
+
+void JingleFileTransferFileInfoParser::handleCharacterData(const std::string& data) {
+	charData += data;
+}
+
+}
diff --git a/Swiften/Parser/PayloadParsers/JingleFileTransferFileInfoParser.h b/Swiften/Parser/PayloadParsers/JingleFileTransferFileInfoParser.h
new file mode 100644
index 0000000..eb6245c
--- /dev/null
+++ b/Swiften/Parser/PayloadParsers/JingleFileTransferFileInfoParser.h
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2014 Isode Limited.
+ * All rights reserved.
+ * See the COPYING file for more information.
+ */
+
+#pragma once
+
+#include <Swiften/Elements/JingleFileTransferFileInfo.h>
+#include <Swiften/Parser/GenericPayloadParser.h>
+
+namespace Swift {
+
+class JingleFileTransferFileInfoParser : public GenericPayloadParser<JingleFileTransferFileInfo> {
+	public:
+		JingleFileTransferFileInfoParser();
+
+		virtual void handleStartElement(const std::string& element, const std::string&, const AttributeMap& attributes);
+		virtual void handleEndElement(const std::string& element, const std::string&);
+		virtual void handleCharacterData(const std::string& data);
+		
+	private:
+		int level;
+		std::string charData;
+		std::string hashAlg;
+		boost::optional<boost::uintmax_t> rangeOffset;
+};
+
+}
diff --git a/Swiften/Parser/PayloadParsers/JingleFileTransferHashParser.cpp b/Swiften/Parser/PayloadParsers/JingleFileTransferHashParser.cpp
index 87f8317..6a1a031 100644
--- a/Swiften/Parser/PayloadParsers/JingleFileTransferHashParser.cpp
+++ b/Swiften/Parser/PayloadParsers/JingleFileTransferHashParser.cpp
@@ -4,39 +4,54 @@
  * See Documentation/Licenses/BSD-simplified.txt for more information.
  */
 
+/*
+ * Copyright (c) 2014 Isode Limited.
+ * All rights reserved.
+ * See the COPYING file for more information.
+ */
+
 #include "JingleFileTransferHashParser.h"
 
 #include <boost/shared_ptr.hpp>
 #include <boost/algorithm/string.hpp>
 
-#include <Swiften/Parser/PayloadParsers/StreamInitiationFileInfoParser.h>
+#include <Swiften/Parser/PayloadParsers/JingleFileTransferFileInfoParser.h>
 #include <Swiften/Parser/GenericPayloadParserFactory.h>
 #include <Swiften/Parser/PayloadParserFactory.h>
 
 namespace Swift {
 
-JingleFileTransferHashParser::JingleFileTransferHashParser() {
+JingleFileTransferHashParser::JingleFileTransferHashParser() : level(0) {
 }
 	
-void JingleFileTransferHashParser::handleStartElement(const std::string& element, const std::string&, const AttributeMap& attributes) {
-	if (element == "hash") {
-		algo = attributes.getAttribute("algo");
+void JingleFileTransferHashParser::handleStartElement(const std::string& element, const std::string& ns, const AttributeMap& attributes) {
+	if (level == 1 && element == "file") {
+		currentPayloadParser = boost::make_shared<JingleFileTransferFileInfoParser>();
 	}
+
+	if (level >= 1 && currentPayloadParser) {
+		currentPayloadParser->handleStartElement(element, ns, attributes);
+	}
+	++level;
 }
 
-void JingleFileTransferHashParser::handleEndElement(const std::string& element, const std::string& ) {
-	if (element == "hash" && !algo.empty() && !hash.empty()) {
-		getPayloadInternal()->setHash(algo, hash);
-		algo.clear();
-		hash.clear();
+void JingleFileTransferHashParser::handleEndElement(const std::string& element, const std::string& ns) {
+	--level;
+	if (level >= 1 && currentPayloadParser) {
+		currentPayloadParser->handleEndElement(element, ns);
+	}
+
+	if (level == 1) {
+		boost::shared_ptr<JingleFileTransferFileInfo> info = boost::dynamic_pointer_cast<JingleFileTransferFileInfo>(currentPayloadParser->getPayload());
+		if (info) {
+			getPayloadInternal()->setFileInfo(*info);
+		}
 	}
 }
 
 void JingleFileTransferHashParser::handleCharacterData(const std::string& data) {
-	if (!algo.empty()) {
-		std::string new_data(data);
-		boost::trim(new_data);
-		hash += new_data;
+	if (level >= 1 && currentPayloadParser) {
+		currentPayloadParser->handleCharacterData(data);
 	}
 }
 
diff --git a/Swiften/Parser/PayloadParsers/JingleFileTransferHashParser.h b/Swiften/Parser/PayloadParsers/JingleFileTransferHashParser.h
index 35e4a05..5a7c6c5 100644
--- a/Swiften/Parser/PayloadParsers/JingleFileTransferHashParser.h
+++ b/Swiften/Parser/PayloadParsers/JingleFileTransferHashParser.h
@@ -4,6 +4,12 @@
  * See Documentation/Licenses/BSD-simplified.txt for more information.
  */
 
+/*
+ * Copyright (c) 2014 Isode Limited.
+ * All rights reserved.
+ * See the COPYING file for more information.
+ */
+
 #pragma once
 
 #include <Swiften/Elements/JingleFileTransferHash.h>
@@ -20,8 +26,8 @@ public:
 	virtual void handleCharacterData(const std::string& data);
 	
 private:
-	std::string algo;
-	std::string hash;
+	int level;
+	boost::shared_ptr<PayloadParser> currentPayloadParser;
 };
 
 }
diff --git a/Swiften/Parser/PayloadParsers/StreamInitiationFileInfoParser.cpp b/Swiften/Parser/PayloadParsers/StreamInitiationFileInfoParser.cpp
index cc69348..b3888d6 100644
--- a/Swiften/Parser/PayloadParsers/StreamInitiationFileInfoParser.cpp
+++ b/Swiften/Parser/PayloadParsers/StreamInitiationFileInfoParser.cpp
@@ -4,7 +4,13 @@
  * See Documentation/Licenses/BSD-simplified.txt for more information.
  */
 
-#include "StreamInitiationFileInfoParser.h"
+/*
+ * Copyright (c) 2014 Isode Limited.
+ * All rights reserved.
+ * See the COPYING file for more information.
+ */
+
+#include <Swiften/Parser/PayloadParsers/StreamInitiationFileInfoParser.h>
 
 #include <boost/optional.hpp>
 #include <boost/lexical_cast.hpp>
diff --git a/Swiften/Parser/PayloadParsers/UnitTest/JingleParserTest.cpp b/Swiften/Parser/PayloadParsers/UnitTest/JingleParserTest.cpp
index 8c8601a..a6be599 100644
--- a/Swiften/Parser/PayloadParsers/UnitTest/JingleParserTest.cpp
+++ b/Swiften/Parser/PayloadParsers/UnitTest/JingleParserTest.cpp
@@ -16,7 +16,7 @@
 #include <Swiften/Elements/JingleFileTransferReceived.h>
 #include <Swiften/Elements/JingleFileTransferHash.h>
 #include <Swiften/Base/DateTime.h>
-
+#include <Swiften/StringCodecs/Base64.h>
 #include <Swiften/Base/Log.h>
 
 using namespace Swift;
@@ -36,8 +36,6 @@ class JingleParserTest : public CppUnit::TestFixture {
 		CPPUNIT_TEST(testParse_Xep0234_Example5);
 		CPPUNIT_TEST(testParse_Xep0234_Example8);
 		CPPUNIT_TEST(testParse_Xep0234_Example10);
-		CPPUNIT_TEST(testParse_Xep0234_Example11);
-		CPPUNIT_TEST(testParse_Xep0234_Example12);
 		
 		CPPUNIT_TEST(testParse_Xep0260_Example1);
 		CPPUNIT_TEST(testParse_Xep0260_Example3);
@@ -223,40 +221,39 @@ class JingleParserTest : public CppUnit::TestFixture {
 			PayloadsParserTester parser;
 			CPPUNIT_ASSERT(parser.parse(
 				"<jingle xmlns='urn:xmpp:jingle:1'\n"
-				"          action='session-initiate'\n"
-				"          initiator='romeo@montague.lit/orchard'\n"
-				"          sid='851ba2'>\n"
-				"    <content creator='initiator' name='a-file-offer'>\n"
-				"      <description xmlns='urn:xmpp:jingle:apps:file-transfer:3'>\n"
-				"        <offer>\n"
-				"          <file xmlns='http://jabber.org/protocol/si/profile/file-transfer'\n"
-				"                date='1969-07-21T02:56:15Z'\n"
-				"                hash='552da749930852c69ae5d2141d3766b1'\n"
-				"                name='test.txt'\n"
-				"                size='1022'>\n"
-				"            <desc>This is a test. If this were a real file...</desc>\n"
-				"            <range/>\n"
-				"          </file>\n"
-				"        </offer>\n"
-				"      </description>\n"
-				"      <transport xmlns='urn:xmpp:jingle:transports:s5b:1'\n"
-				"                 mode='tcp'\n"
-				"                 sid='vj3hs98y'>\n"
-				"        <candidate cid='hft54dqy'\n"
-				"                   host='192.168.4.1'\n"
-				"                   jid='romeo@montague.lit/orchard'\n"
-				"                   port='5086'\n"
-				"                   priority='8257636'\n"
-				"                   type='direct'/>\n"
-				"        <candidate cid='hutr46fe'\n"
-				"                   host='24.24.24.1'\n"
-				"                   jid='romeo@montague.lit/orchard'\n"
-				"                   port='5087'\n"
-				"                   priority='8258636'\n"
-				"                   type='direct'/>\n"
-				"      </transport>\n"
-				"    </content>\n"
-				"  </jingle>\n"
+					" action='session-initiate'\n"
+					" initiator='romeo@montague.lit/orchard'\n"
+					" sid='851ba2'>\n"
+					"<content creator='initiator' name='a-file-offer'>\n"
+						"<description xmlns='urn:xmpp:jingle:apps:file-transfer:4'>\n"
+							"<file>\n"
+								"<date>1969-07-21T02:56:15Z</date>\n"
+								"<desc>This is a test. If this were a real file...</desc>\n"
+								"<media-type>text/plain</media-type>\n"
+								"<name>test.txt</name>\n"
+								"<range/>\n"
+								"<size>1022</size>\n"
+								"<hash xmlns='urn:xmpp:hashes:1' algo='sha-1'>VS2nSZMIUsaa5dIUHTdmsQ==</hash>\n"
+							"</file>\n"
+						"</description>\n"
+						"<transport xmlns='urn:xmpp:jingle:transports:s5b:1'\n"
+							" mode='tcp'\n"
+							" sid='vj3hs98y'>\n"
+							"<candidate cid='hft54dqy'\n"
+								" host='192.168.4.1'\n"
+								" jid='romeo@montague.lit/orchard'\n"
+								" port='5086'\n"
+								" priority='8257636'\n"
+								" type='direct'/>\n"
+							"<candidate cid='hutr46fe'\n"
+								" host='24.24.24.1'\n"
+								" jid='romeo@montague.lit/orchard'\n"
+								" port='5087'\n"
+								" priority='8258636'\n"
+								" type='direct'/>\n"
+						"</transport>\n"
+					"</content>\n"
+				"</jingle>\n"
 			));
 						
 			JinglePayload::ref jingle = parser.getPayload<JinglePayload>();
@@ -270,16 +267,15 @@ class JingleParserTest : public CppUnit::TestFixture {
 			
 			JingleFileTransferDescription::ref description = contents[0]->getDescription<JingleFileTransferDescription>();
 			
-			
-			std::vector<StreamInitiationFileInfo> offers = description->getOffers();
-			CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), offers.size());
-			CPPUNIT_ASSERT_EQUAL(std::string("test.txt"), offers[0].getName());
-			CPPUNIT_ASSERT_EQUAL(std::string("552da749930852c69ae5d2141d3766b1"), offers[0].getHash());
-			CPPUNIT_ASSERT(1022 == offers[0].getSize());
-			CPPUNIT_ASSERT_EQUAL(std::string("This is a test. If this were a real file..."), offers[0].getDescription());
-			CPPUNIT_ASSERT_EQUAL(true, offers[0].getSupportsRangeRequests());
-			CPPUNIT_ASSERT(stringToDateTime("1969-07-21T02:56:15Z") == offers[0].getDate());
-			CPPUNIT_ASSERT_EQUAL(std::string("md5"), offers[0].getAlgo());	
+			CPPUNIT_ASSERT(description);
+			JingleFileTransferFileInfo fileInfo = description->getFileInfo();
+			CPPUNIT_ASSERT_EQUAL(std::string("test.txt"), fileInfo.getName());
+			CPPUNIT_ASSERT_EQUAL(std::string("sha-1"), fileInfo.getHashes().begin()->first);
+			CPPUNIT_ASSERT_EQUAL(std::string("VS2nSZMIUsaa5dIUHTdmsQ=="), Base64::encode(fileInfo.getHashes().begin()->second));
+			CPPUNIT_ASSERT(1022 == fileInfo.getSize());
+			CPPUNIT_ASSERT_EQUAL(std::string("This is a test. If this were a real file..."), fileInfo.getDescription());
+			CPPUNIT_ASSERT_EQUAL(true, fileInfo.getSupportsRangeRequests());
+			CPPUNIT_ASSERT(stringToDateTime("1969-07-21T02:56:15Z") == fileInfo.getDate());
 		}
 		
 		// http://xmpp.org/extensions/xep-0234.html#example-3
@@ -287,22 +283,21 @@ class JingleParserTest : public CppUnit::TestFixture {
 			PayloadsParserTester parser;
 			CPPUNIT_ASSERT(parser.parse(
 				"<jingle xmlns='urn:xmpp:jingle:1'\n"
-				"        action='session-accept'\n"
-				"        initiator='romeo@montague.lit/orchard'\n"
-				"        sid='851ba2'>\n"
-				"  <content creator='initiator' name='a-file-offer'>\n"
-				"    <description xmlns='urn:xmpp:jingle:apps:file-transfer:3'>\n"
-				"      <offer>\n"
-				"        <file xmlns='http://jabber.org/protocol/si/profile/file-transfer'\n"
-				"              name='test.txt'\n"
-				"              size='1022'\n"
-				"              hash='552da749930852c69ae5d2141d3766b1'\n"
-				"              date='1969-07-21T02:56:15Z'>\n"
-				"          <desc>This is a test. If this were a real file...</desc>\n"
-				"          <range/>\n"
-				"        </file>\n"
-				"      </offer>\n"
-				"    </description>\n"
+					" action='session-accept'\n"
+					" initiator='romeo@montague.lit/orchard'\n"
+					" sid='851ba2'>\n"
+					"<content creator='initiator' name='a-file-offer'>\n"
+						"<description xmlns='urn:xmpp:jingle:apps:file-transfer:4'>\n"
+							"<file>\n"
+								"<date>1969-07-21T02:56:15Z</date>\n"
+								"<desc>This is a test. If this were a real file...</desc>\n"
+								"<media-type>text/plain</media-type>\n"
+								"<name>test.txt</name>\n"
+								"<range/>\n"
+								"<size>1022</size>\n"
+								"<hash xmlns='urn:xmpp:hashes:1' algo='sha-1'>VS2nSZMIUsaa5dIUHTdmsQ==</hash>\n"
+							"</file>\n"
+						"</description>\n"
 				"    <transport xmlns='urn:xmpp:jingle:transports:s5b:1'\n"
 				"               mode='tcp'\n"
 				"               sid='vj3hs98y'>\n"
@@ -340,16 +335,16 @@ class JingleParserTest : public CppUnit::TestFixture {
 			
 			JingleFileTransferDescription::ref description = contents[0]->getDescription<JingleFileTransferDescription>();
 			
+			CPPUNIT_ASSERT(description);
 			
-			std::vector<StreamInitiationFileInfo> offers = description->getOffers();
-			CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), offers.size());
-			CPPUNIT_ASSERT_EQUAL(std::string("test.txt"), offers[0].getName());
-			CPPUNIT_ASSERT_EQUAL(std::string("552da749930852c69ae5d2141d3766b1"), offers[0].getHash());
-			CPPUNIT_ASSERT(1022 == offers[0].getSize());
-			CPPUNIT_ASSERT_EQUAL(std::string("This is a test. If this were a real file..."), offers[0].getDescription());
-			CPPUNIT_ASSERT_EQUAL(true, offers[0].getSupportsRangeRequests());
-			CPPUNIT_ASSERT(stringToDateTime("1969-07-21T02:56:15Z") == offers[0].getDate());
-			CPPUNIT_ASSERT_EQUAL(std::string("md5"), offers[0].getAlgo());
+			JingleFileTransferFileInfo fileInfo = description->getFileInfo();
+			CPPUNIT_ASSERT_EQUAL(std::string("test.txt"), fileInfo.getName());
+			CPPUNIT_ASSERT_EQUAL(std::string("sha-1"), fileInfo.getHashes().begin()->first);
+			CPPUNIT_ASSERT_EQUAL(std::string("VS2nSZMIUsaa5dIUHTdmsQ=="), Base64::encode(fileInfo.getHashes().begin()->second));
+			CPPUNIT_ASSERT(1022 == fileInfo.getSize());
+			CPPUNIT_ASSERT_EQUAL(std::string("This is a test. If this were a real file..."), fileInfo.getDescription());
+			CPPUNIT_ASSERT_EQUAL(true, fileInfo.getSupportsRangeRequests());
+			CPPUNIT_ASSERT(stringToDateTime("1969-07-21T02:56:15Z") == fileInfo.getDate());
 		}
 		
 		// http://xmpp.org/extensions/xep-0234.html#example-5
@@ -393,11 +388,9 @@ class JingleParserTest : public CppUnit::TestFixture {
 				"        action='session-info'\n"
 				"        initiator='romeo@montague.lit/orchard'\n"
 				"        sid='a73sjjvkla37jfea'>\n"
-				"	<checksum xmlns='urn:xmpp:jingle:apps:file-transfer:3'>\n"
+				"	<checksum xmlns='urn:xmpp:jingle:apps:file-transfer:4'>\n"
 				"	  <file>\n"
-				"	    <hashes xmlns='urn:xmpp:hashes:0'>\n"
-				"	      <hash algo='sha-1'>552da749930852c69ae5d2141d3766b1</hash>\n"
-				"	    </hashes>\n"
+				"	      <hash xmlns='urn:xmpp:hashes:0' algo='sha-1'>VS2nSZMIUsaa5dIUHTdmsQ==</hash>\n"
 				"	  </file>\n"
 				"	</checksum>\n"
 				"</jingle>\n"
@@ -410,12 +403,12 @@ class JingleParserTest : public CppUnit::TestFixture {
 			
 			JingleFileTransferHash::ref hash = jingle->getPayload<JingleFileTransferHash>();
 			CPPUNIT_ASSERT(hash);
-			CPPUNIT_ASSERT_EQUAL(std::string("552da749930852c69ae5d2141d3766b1"), hash->getHashes().find("sha-1")->second);
-			
+			CPPUNIT_ASSERT_EQUAL(std::string("VS2nSZMIUsaa5dIUHTdmsQ=="), Base64::encode(hash->getFileInfo().getHash("sha-1").get()));
 		}
 		
 		// http://xmpp.org/extensions/xep-0234.html#example-10
 		void testParse_Xep0234_Example10() {
+			Log::setLogLevel(Log::debug);
 			PayloadsParserTester parser;
 			CPPUNIT_ASSERT(parser.parse(
 				"<jingle xmlns='urn:xmpp:jingle:1'\n"
@@ -423,13 +416,11 @@ class JingleParserTest : public CppUnit::TestFixture {
 				"        initiator='romeo@montague.lit/orchard'\n"
 				"        sid='uj3b2'>\n"
 				"  <content creator='initiator' name='a-file-request'>\n"
-				"    <description xmlns='urn:xmpp:jingle:apps:file-transfer:3'>\n"
-				"      <request>\n"
-				"        <file xmlns='http://jabber.org/protocol/si/profile/file-transfer'\n"
-				"              hash='552da749930852c69ae5d2141d3766b1'>\n"
-				"          <range offset='270336'/>\n"
-				"        </file>\n"
-				"      </request>\n"
+				"    <description xmlns='urn:xmpp:jingle:apps:file-transfer:4'>\n"
+				"      <file>\n"
+				"        <hash xmlns='urn:xmpp:hashes:1' algo='sha-1'>VS2nSZMIUsaa5dIUHTdmsQ==</hash>\n"
+				"        <range offset='270336'/>\n"
+				"      </file>\n"
 				"    </description>\n"
 				"    <transport xmlns='urn:xmpp:jingle:transports:s5b:1'\n"
 				"               mode='tcp'\n"
@@ -466,101 +457,13 @@ class JingleParserTest : public CppUnit::TestFixture {
 			JingleContentPayload::ref content = jingle->getPayload<JingleContentPayload>();
 			CPPUNIT_ASSERT(content);
 			
-			StreamInitiationFileInfo file = content->getDescription<JingleFileTransferDescription>()->getRequests()[0];
-			CPPUNIT_ASSERT_EQUAL(std::string("552da749930852c69ae5d2141d3766b1"), file.getHash());
-			CPPUNIT_ASSERT_EQUAL(static_cast<unsigned long long>(270336), file.getRangeOffset());
+			JingleFileTransferFileInfo file = content->getDescription<JingleFileTransferDescription>()->getFileInfo();
+			CPPUNIT_ASSERT_EQUAL(std::string("sha-1"), file.getHashes().begin()->first);
+			CPPUNIT_ASSERT_EQUAL(std::string("VS2nSZMIUsaa5dIUHTdmsQ=="), Base64::encode(file.getHashes().begin()->second));
+			CPPUNIT_ASSERT_EQUAL(static_cast<boost::uintmax_t>(270336), file.getRangeOffset());
 			CPPUNIT_ASSERT_EQUAL(true, file.getSupportsRangeRequests());
 		}
 		
-		// http://xmpp.org/extensions/xep-0234.html#example-11
-		void testParse_Xep0234_Example11() {
-			PayloadsParserTester parser;
-			CPPUNIT_ASSERT(parser.parse(
-				"<jingle xmlns='urn:xmpp:jingle:1'\n"
-				"        action='session-initiate'\n"
-				"        initiator='romeo@montague.lit/orchard'\n"
-				"        sid='h2va419i'>\n"
-				"  <content creator='initiator' name='a-file-offer'>\n"
-				"    <description xmlns='urn:xmpp:jingle:apps:file-transfer:3'>\n"
-				"      <offer>\n"
-				"        <file xmlns='http://jabber.org/protocol/si/profile/file-transfer'\n"
-				"              date='2011-06-01T15:58:15Z'\n"
-				"              hash='a749930852c69ae5d2141d3766b1552d'\n"
-				"              name='somefile.txt'\n"
-				"              size='1234'/>\n"
-				"        <file xmlns='http://jabber.org/protocol/si/profile/file-transfer'\n"
-				"              date='2011-06-01T15:58:15Z'\n"
-				"              hash='930852c69ae5d2141d3766b1552da749'\n"
-				"              name='anotherfile.txt'\n"
-				"              size='2345'/>\n"
-				"        <file xmlns='http://jabber.org/protocol/si/profile/file-transfer'\n"
-				"              date='2011-06-01T15:58:15Z'\n"
-				"              hash='52c69ae5d2141d3766b1552da7499308'\n"
-				"              name='yetanotherfile.txt'\n"
-				"              size='3456'/>\n"
-				"      </offer>\n"
-				"    </description>\n"
-				"    <transport xmlns='urn:xmpp:jingle:transports:s5b:1'\n"
-				"               mode='tcp'\n"
-				"               sid='vj3hs98y'>\n"
-				"      <candidate cid='hft54dqy'\n"
-				"                 host='192.168.4.1'\n"
-				"                 jid='romeo@montague.lit/orchard'\n"
-				"                 port='5086'\n"
-				"                 priority='8257636'\n"
-				"                 type='direct'/>\n"
-				"      <candidate cid='hutr46fe'\n"
-				"                 host='24.24.24.1'\n"
-				"                 jid='romeo@montague.lit/orchard'\n"
-				"                 port='5087'\n"
-				"                 priority='8258636'\n"
-				"                 type='direct'/>\n"
-				"    </transport>\n"
-				"  </content>\n"
-				"</jingle>\n"			
-			));
-			
-			JinglePayload::ref jingle = parser.getPayload<JinglePayload>();
-			CPPUNIT_ASSERT(jingle);
-			CPPUNIT_ASSERT_EQUAL(JinglePayload::SessionInitiate, jingle->getAction());
-			CPPUNIT_ASSERT_EQUAL(JID("romeo@montague.lit/orchard"), jingle->getInitiator());
-			CPPUNIT_ASSERT_EQUAL(std::string("h2va419i"), jingle->getSessionID());
-			
-			JingleContentPayload::ref content = jingle->getPayload<JingleContentPayload>();
-			CPPUNIT_ASSERT(content);
-			CPPUNIT_ASSERT_EQUAL(JingleContentPayload::InitiatorCreator, content->getCreator());
-			CPPUNIT_ASSERT_EQUAL(std::string("a-file-offer"), content->getName());
-			
-			std::vector<StreamInitiationFileInfo> offers = content->getDescription<JingleFileTransferDescription>()->getOffers();
-			CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(3), offers.size());
-		}
-		
-		// http://xmpp.org/extensions/xep-0234.html#example-12
-		void testParse_Xep0234_Example12() {
-			PayloadsParserTester parser;
-			CPPUNIT_ASSERT(parser.parse(
-				"<jingle xmlns='urn:xmpp:jingle:1'\n"
-				"        action='session-info'\n"
-				"        initiator='romeo@montague.lit/orchard'\n"
-				"        sid='a73sjjvkla37jfea'>\n"
-				"  <received xmlns='urn:xmpp:jingle:apps:file-transfer:3'>\n"
-				"    <file xmlns='http://jabber.org/protocol/si/profile/file-transfer'\n"
-				"          hash='a749930852c69ae5d2141d3766b1552d'/>\n"
-				"  </received>\n"
-				"</jingle>\n"
-			));
-			
-			JinglePayload::ref jingle = parser.getPayload<JinglePayload>();
-			CPPUNIT_ASSERT(jingle);
-			CPPUNIT_ASSERT_EQUAL(JinglePayload::SessionInfo, jingle->getAction());
-			CPPUNIT_ASSERT_EQUAL(JID("romeo@montague.lit/orchard"), jingle->getInitiator());
-			CPPUNIT_ASSERT_EQUAL(std::string("a73sjjvkla37jfea"), jingle->getSessionID());
-			
-			boost::shared_ptr<JingleFileTransferReceived> received = jingle->getPayload<JingleFileTransferReceived>();
-			CPPUNIT_ASSERT(received);
-			CPPUNIT_ASSERT_EQUAL(std::string("a749930852c69ae5d2141d3766b1552d"), received->getFileInfo().getHash());			
-		}
-		
 		// http://xmpp.org/extensions/xep-0260.html#example-1
 		void testParse_Xep0260_Example1() {
 			PayloadsParserTester parser;
diff --git a/Swiften/Parser/SConscript b/Swiften/Parser/SConscript
index 94ea6d3..4bdf66f 100644
--- a/Swiften/Parser/SConscript
+++ b/Swiften/Parser/SConscript
@@ -38,6 +38,7 @@ sources = [
 		"PayloadParsers/JingleFileTransferDescriptionParser.cpp",
 		"PayloadParsers/JingleFileTransferReceivedParser.cpp",
 		"PayloadParsers/JingleFileTransferHashParser.cpp",
+		"PayloadParsers/JingleFileTransferFileInfoParser.cpp",
 		"PayloadParsers/StreamInitiationFileInfoParser.cpp",
 		"PayloadParsers/CommandParser.cpp",
 		"PayloadParsers/InBandRegistrationPayloadParser.cpp",
diff --git a/Swiften/SConscript b/Swiften/SConscript
index 877b084..147f7a6 100644
--- a/Swiften/SConscript
+++ b/Swiften/SConscript
@@ -235,6 +235,7 @@ if env["SCONS_STAGE"] == "build" :
 			"Serializer/PayloadSerializers/MAMResultSerializer.cpp",
 			"Serializer/PayloadSerializers/MAMQuerySerializer.cpp",
 			"Serializer/PayloadSerializers/IsodeIQDelegationSerializer.cpp",
+			"Serializer/PayloadSerializers/JingleFileTransferFileInfoSerializer.cpp",
 			"Serializer/PresenceSerializer.cpp",
 			"Serializer/StanzaSerializer.cpp",
 			"Serializer/StreamErrorSerializer.cpp",
diff --git a/Swiften/Serializer/PayloadSerializers/FullPayloadSerializerCollection.cpp b/Swiften/Serializer/PayloadSerializers/FullPayloadSerializerCollection.cpp
index 4a4d678..a9a1ae3 100644
--- a/Swiften/Serializer/PayloadSerializers/FullPayloadSerializerCollection.cpp
+++ b/Swiften/Serializer/PayloadSerializers/FullPayloadSerializerCollection.cpp
@@ -32,6 +32,7 @@
 #include <Swiften/Serializer/PayloadSerializers/JingleFileTransferDescriptionSerializer.h>
 #include <Swiften/Serializer/PayloadSerializers/JingleFileTransferHashSerializer.h>
 #include <Swiften/Serializer/PayloadSerializers/JingleFileTransferReceivedSerializer.h>
+#include <Swiften/Serializer/PayloadSerializers/JingleFileTransferFileInfoSerializer.h>
 #include <Swiften/Serializer/PayloadSerializers/JingleIBBTransportPayloadSerializer.h>
 #include <Swiften/Serializer/PayloadSerializers/JinglePayloadSerializer.h>
 #include <Swiften/Serializer/PayloadSerializers/JingleS5BTransportPayloadSerializer.h>
@@ -127,6 +128,7 @@ FullPayloadSerializerCollection::FullPayloadSerializerCollection() {
 	serializers_.push_back(new IdleSerializer());
 	
 	serializers_.push_back(new StreamInitiationFileInfoSerializer());
+	serializers_.push_back(new JingleFileTransferFileInfoSerializer());
 	serializers_.push_back(new JingleContentPayloadSerializer());
 	serializers_.push_back(new JingleFileTransferDescriptionSerializer());
 	serializers_.push_back(new JingleFileTransferHashSerializer());
diff --git a/Swiften/Serializer/PayloadSerializers/JingleFileTransferDescriptionSerializer.cpp b/Swiften/Serializer/PayloadSerializers/JingleFileTransferDescriptionSerializer.cpp
index 16337ff..bbe1510 100644
--- a/Swiften/Serializer/PayloadSerializers/JingleFileTransferDescriptionSerializer.cpp
+++ b/Swiften/Serializer/PayloadSerializers/JingleFileTransferDescriptionSerializer.cpp
@@ -4,17 +4,27 @@
  * See Documentation/Licenses/BSD-simplified.txt for more information.
  */
 
+/*
+ * Copyright (c) 2015 Isode Limited.
+ * All rights reserved.
+ * See the COPYING file for more information.
+ */
+
 #include <Swiften/Serializer/PayloadSerializers/JingleFileTransferDescriptionSerializer.h>
 
 #include <boost/shared_ptr.hpp>
 #include <boost/smart_ptr/make_shared.hpp>
+#include <boost/lexical_cast.hpp>
 
 #include <Swiften/Base/foreach.h>
+#include <Swiften/Base/DateTime.h>
+#include <Swiften/StringCodecs/Base64.h>
+
 #include <Swiften/Serializer/XML/XMLNode.h>
 #include <Swiften/Serializer/XML/XMLElement.h>
 #include <Swiften/Serializer/XML/XMLRawTextNode.h>
 
-#include <Swiften/Serializer/PayloadSerializers/StreamInitiationFileInfoSerializer.h>
+#include <Swiften/Serializer/PayloadSerializers/JingleFileTransferFileInfoSerializer.h>
 
 namespace Swift {
 
@@ -22,24 +32,11 @@ JingleFileTransferDescriptionSerializer::JingleFileTransferDescriptionSerializer
 }
 
 std::string JingleFileTransferDescriptionSerializer::serializePayload(boost::shared_ptr<JingleFileTransferDescription> payload) const {
-	XMLElement description("description", "urn:xmpp:jingle:apps:file-transfer:3");
-	StreamInitiationFileInfoSerializer fileInfoSerializer;
-	if (!payload->getOffers().empty()) {
-		boost::shared_ptr<XMLElement> offers = boost::make_shared<XMLElement>("offer");
-		foreach(const StreamInitiationFileInfo &fileInfo, payload->getOffers()) {
-			boost::shared_ptr<XMLRawTextNode> fileInfoXML = boost::make_shared<XMLRawTextNode>(fileInfoSerializer.serialize(boost::make_shared<StreamInitiationFileInfo>(fileInfo)));
-			offers->addNode(fileInfoXML);
-		}
-		description.addNode(offers);
-	}
-	if (!payload->getRequests().empty()) {
-		boost::shared_ptr<XMLElement> requests = boost::make_shared<XMLElement>("request");
-		foreach(const StreamInitiationFileInfo &fileInfo, payload->getRequests()) {
-			boost::shared_ptr<XMLRawTextNode> fileInfoXML = boost::make_shared<XMLRawTextNode>(fileInfoSerializer.serialize(boost::make_shared<StreamInitiationFileInfo>(fileInfo)));
-			requests->addNode(fileInfoXML);
-		}
-		description.addNode(requests);
-	}
+	XMLElement description("description", "urn:xmpp:jingle:apps:file-transfer:4");
+
+	JingleFileTransferFileInfoSerializer fileInfoSerializer;
+	boost::shared_ptr<XMLRawTextNode> fileInfoXML = boost::make_shared<XMLRawTextNode>(fileInfoSerializer.serialize(boost::make_shared<JingleFileTransferFileInfo>(payload->getFileInfo())));
+	description.addNode(fileInfoXML);
 	return description.serialize();
 }
 
diff --git a/Swiften/Serializer/PayloadSerializers/JingleFileTransferFileInfoSerializer.cpp b/Swiften/Serializer/PayloadSerializers/JingleFileTransferFileInfoSerializer.cpp
new file mode 100644
index 0000000..1a675d0
--- /dev/null
+++ b/Swiften/Serializer/PayloadSerializers/JingleFileTransferFileInfoSerializer.cpp
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2014 Isode Limited.
+ * All rights reserved.
+ * See the COPYING file for more information.
+ */
+
+#include <Swiften/Serializer/PayloadSerializers/JingleFileTransferFileInfoSerializer.h>
+
+#include <boost/shared_ptr.hpp>
+#include <boost/smart_ptr/make_shared.hpp>
+#include <boost/lexical_cast.hpp>
+
+#include <Swiften/Base/foreach.h>
+#include <Swiften/Base/DateTime.h>
+#include <Swiften/Serializer/XML/XMLElement.h>
+#include <Swiften/Serializer/XML/XMLRawTextNode.h>
+#include <Swiften/Serializer/XML/XMLTextNode.h>
+#include <Swiften/StringCodecs/Base64.h>
+
+namespace Swift {
+
+JingleFileTransferFileInfoSerializer::JingleFileTransferFileInfoSerializer() {
+}
+
+std::string JingleFileTransferFileInfoSerializer::serializePayload(boost::shared_ptr<JingleFileTransferFileInfo> fileInfo) const {
+
+	XMLElement fileElement("file", "");
+
+	if (fileInfo->getDate() != stringToDateTime("")) {
+		fileElement.addNode(boost::make_shared<XMLElement>("date", "", dateTimeToString(fileInfo->getDate())));
+	}
+
+	if (!fileInfo->getDescription().empty()) {
+		fileElement.addNode(boost::make_shared<XMLElement>("desc", "", fileInfo->getDescription()));
+	}
+
+	if (!fileInfo->getMediaType().empty()) {
+		fileElement.addNode(boost::make_shared<XMLElement>("media-type", "", fileInfo->getMediaType()));
+	}
+
+	if (!fileInfo->getName().empty()) {
+		fileElement.addNode(boost::make_shared<XMLElement>("name", "", fileInfo->getName()));
+	}
+
+	if (fileInfo->getSupportsRangeRequests()) {
+		boost::shared_ptr<XMLElement> range = boost::make_shared<XMLElement>("range");
+		if (fileInfo->getRangeOffset() != 0) {
+			range->setAttribute("offset", boost::lexical_cast<std::string>(fileInfo->getRangeOffset()));
+		}
+		fileElement.addNode(range);
+	}
+
+	if (fileInfo->getSize() > 0) {
+		fileElement.addNode(boost::make_shared<XMLElement>("size", "", boost::lexical_cast<std::string>(fileInfo->getSize())));
+	}
+
+	foreach (JingleFileTransferFileInfo::HashElementMap::value_type hashElement, fileInfo->getHashes()) {
+		boost::shared_ptr<XMLElement> hash = boost::make_shared<XMLElement>("hash", "urn:xmpp:hashes:1", Base64::encode(hashElement.second));
+		hash->setAttribute("algo", hashElement.first);
+		fileElement.addNode(hash);
+	}
+
+	return fileElement.serialize();
+}
+
+}
diff --git a/Swiften/Serializer/PayloadSerializers/JingleFileTransferFileInfoSerializer.h b/Swiften/Serializer/PayloadSerializers/JingleFileTransferFileInfoSerializer.h
new file mode 100644
index 0000000..0c9f2de
--- /dev/null
+++ b/Swiften/Serializer/PayloadSerializers/JingleFileTransferFileInfoSerializer.h
@@ -0,0 +1,24 @@
+/*
+ * Copyright (c) 2014 Isode Limited.
+ * All rights reserved.
+ * See the COPYING file for more information.
+ */
+
+#pragma once
+
+#include <Swiften/Base/API.h>
+#include <Swiften/Serializer/GenericPayloadSerializer.h>
+#include <Swiften/Elements/JingleFileTransferFileInfo.h>
+
+#include <Swiften/Serializer/XML/XMLElement.h>
+
+namespace Swift {
+	class PayloadSerializerCollection;
+
+	class SWIFTEN_API JingleFileTransferFileInfoSerializer : public GenericPayloadSerializer<JingleFileTransferFileInfo> {
+		public:
+			JingleFileTransferFileInfoSerializer();
+
+			virtual std::string serializePayload(boost::shared_ptr<JingleFileTransferFileInfo>)  const;
+	};
+}
diff --git a/Swiften/Serializer/PayloadSerializers/JingleFileTransferHashSerializer.cpp b/Swiften/Serializer/PayloadSerializers/JingleFileTransferHashSerializer.cpp
index 2bd3afa..f416ddc 100644
--- a/Swiften/Serializer/PayloadSerializers/JingleFileTransferHashSerializer.cpp
+++ b/Swiften/Serializer/PayloadSerializers/JingleFileTransferHashSerializer.cpp
@@ -4,6 +4,12 @@
  * See Documentation/Licenses/BSD-simplified.txt for more information.
  */
 
+/*
+ * Copyright (c) 2015 Isode Limited.
+ * All rights reserved.
+ * See the COPYING file for more information.
+ */
+
 #include <Swiften/Serializer/PayloadSerializers/JingleFileTransferHashSerializer.h>
 
 #include <string>
@@ -16,7 +22,7 @@
 #include <Swiften/Serializer/XML/XMLNode.h>
 #include <Swiften/Serializer/XML/XMLElement.h>
 #include <Swiften/Serializer/XML/XMLRawTextNode.h>
-
+#include <Swiften/Serializer/PayloadSerializers/JingleFileTransferFileInfoSerializer.h>
 
 namespace Swift {
 
@@ -27,17 +33,14 @@ std::string JingleFileTransferHashSerializer::serializePayload(boost::shared_ptr
 	// code for version urn:xmpp:jingle:apps:file-transfer:2
 	//XMLElement hash("hash", "urn:xmpp:jingle:apps:file-transfer:info:2", payload->getHash());
 
-	// code for version urn:xmpp:jingle:apps:file-transfer:3
-	XMLElement checksum("checksum", "urn:xmpp:jingle:apps:file-transfer:3");
-	boost::shared_ptr<XMLElement> file = boost::make_shared<XMLElement>("file");
+	// code for version urn:xmpp:jingle:apps:file-transfer:4
+	XMLElement checksum("checksum", "urn:xmpp:jingle:apps:file-transfer:4");
+
+	JingleFileTransferFileInfoSerializer  fileSerializer;
+
+	boost::shared_ptr<XMLRawTextNode> file = boost::make_shared<XMLRawTextNode>(fileSerializer.serialize(boost::make_shared<JingleFileTransferFileInfo>(payload->getFileInfo())));
+
 	checksum.addNode(file);
-	boost::shared_ptr<XMLElement> hashes = boost::make_shared<XMLElement>("hashes", "urn:xmpp:hashes:0");
-	file->addNode(hashes);
-	foreach(const JingleFileTransferHash::HashesMap::value_type& pair, payload->getHashes()) {
-		boost::shared_ptr<XMLElement> hash = boost::make_shared<XMLElement>("hash", "", pair.second);
-		hash->setAttribute("algo", pair.first);
-		hashes->addNode(hash);
-	}
 
 	return checksum.serialize();
 }
diff --git a/Swiften/Serializer/PayloadSerializers/UnitTest/JingleSerializersTest.cpp b/Swiften/Serializer/PayloadSerializers/UnitTest/JingleSerializersTest.cpp
index e3ec8fc..722c039 100644
--- a/Swiften/Serializer/PayloadSerializers/UnitTest/JingleSerializersTest.cpp
+++ b/Swiften/Serializer/PayloadSerializers/UnitTest/JingleSerializersTest.cpp
@@ -4,6 +4,12 @@
  * See Documentation/Licenses/BSD-simplified.txt for more information.
  */
 
+/*
+ * Copyright (c) 2015 Isode Limited.
+ * All rights reserved.
+ * See the COPYING file for more information.
+ */
+
 #include <boost/shared_ptr.hpp>
 #include <boost/smart_ptr/make_shared.hpp>
 
@@ -15,13 +21,14 @@
 #include <Swiften/Serializer/PayloadSerializers/JinglePayloadSerializer.h>
 #include <Swiften/Serializer/PayloadSerializers/FullPayloadSerializerCollection.h>
 #include <Swiften/Elements/JingleFileTransferDescription.h>
-#include <Swiften/Elements/StreamInitiationFileInfo.h>
+#include <Swiften/Elements/JingleFileTransferFileInfo.h>
 #include <Swiften/Elements/JingleIBBTransportPayload.h>
 #include <Swiften/Elements/JingleS5BTransportPayload.h>
 #include <Swiften/Elements/JingleFileTransferHash.h>
 #include <Swiften/Elements/JinglePayload.h>
 #include <Swiften/Elements/JingleFileTransferReceived.h>
 #include <Swiften/Base/DateTime.h>
+#include <Swiften/StringCodecs/Base64.h>
 
 using namespace Swift;
 
@@ -38,7 +45,6 @@ class JingleSerializersTest : public CppUnit::TestFixture {
 		CPPUNIT_TEST(testSerialize_Xep0234_Example3);
 		CPPUNIT_TEST(testSerialize_Xep0234_Example5);
 		CPPUNIT_TEST(testSerialize_Xep0234_Example8);
-		CPPUNIT_TEST(testSerialize_Xep0234_Example10);
 		CPPUNIT_TEST(testSerialize_Xep0234_Example13);
 
 		CPPUNIT_TEST(testSerialize_Xep0260_Example1);
@@ -179,30 +185,27 @@ class JingleSerializersTest : public CppUnit::TestFixture {
 		
 		// http://xmpp.org/extensions/xep-0234.html#example-1
 		void testSerialize_Xep0234_Example1() {
-			std::string expected =	"<description xmlns=\"urn:xmpp:jingle:apps:file-transfer:3\">"
-							"<offer>"
-								"<file"
-									" date=\"1969-07-21T02:56:15Z\""
-									" hash=\"552da749930852c69ae5d2141d3766b1\""
-									" name=\"test.txt\""
-									" size=\"1022\""
-									" xmlns=\"http://jabber.org/protocol/si/profile/file-transfer\">"
-									"<desc>This is a test. If this were a real file...</desc>"
-									"<range/>"
-								"</file>"
-							"</offer>"
-						"</description>";
+			std::string expected =	"<description xmlns=\"urn:xmpp:jingle:apps:file-transfer:4\">"
+					"<file>"
+						"<date>1969-07-21T02:56:15Z</date>"
+						"<desc>This is a test. If this were a real file...</desc>"
+						"<name>test.txt</name>"
+						"<range/>"
+						"<size>1022</size>"
+						"<hash algo=\"sha-1\" xmlns=\"urn:xmpp:hashes:1\">VS2nSZMIUsaa5dIUHTdmsQ==</hash>"
+					"</file>"
+				"</description>";
 			JingleFileTransferDescription::ref desc = boost::make_shared<JingleFileTransferDescription>();
-			StreamInitiationFileInfo fileInfo;
+			JingleFileTransferFileInfo fileInfo;
 			
 			fileInfo.setDate(stringToDateTime("1969-07-21T02:56:15Z"));
-			fileInfo.setHash("552da749930852c69ae5d2141d3766b1");
+			fileInfo.addHash(HashElement("sha-1", Base64::decode("VS2nSZMIUsaa5dIUHTdmsQ==")));
 			fileInfo.setSize(1022);
 			fileInfo.setName("test.txt");
 			fileInfo.setDescription("This is a test. If this were a real file...");
 			fileInfo.setSupportsRangeRequests(true);
 			
-			desc->addOffer(fileInfo);
+			desc->setFileInfo(fileInfo);
 			
 			CPPUNIT_ASSERT_EQUAL(expected, boost::make_shared<JingleFileTransferDescriptionSerializer>()->serialize(desc));
 		}
@@ -215,18 +218,15 @@ class JingleSerializersTest : public CppUnit::TestFixture {
 					" sid=\"851ba2\""
 					" xmlns=\"urn:xmpp:jingle:1\">"
 					"<content creator=\"initiator\" name=\"a-file-offer\">"
-						"<description xmlns=\"urn:xmpp:jingle:apps:file-transfer:3\">"
-							"<offer>"
-								"<file"
-									" date=\"1969-07-21T02:56:15Z\""
-									" hash=\"552da749930852c69ae5d2141d3766b1\""
-									" name=\"test.txt\""
-									" size=\"1022\""
-									" xmlns=\"http://jabber.org/protocol/si/profile/file-transfer\">"
-									"<desc>This is a test. If this were a real file...</desc>"
-									"<range/>"
-								"</file>"
-							"</offer>"
+						"<description xmlns=\"urn:xmpp:jingle:apps:file-transfer:4\">"
+							"<file>"
+								"<date>1969-07-21T02:56:15Z</date>"
+								"<desc>This is a test. If this were a real file...</desc>"
+								"<name>test.txt</name>"
+								"<range/>"
+								"<size>1022</size>"
+								"<hash algo=\"sha-1\" xmlns=\"urn:xmpp:hashes:1\">VS2nSZMIUsaa5dIUHTdmsQ==</hash>"
+							"</file>"
 						"</description>"
 						/*"<transport xmlns=\"urn:xmpp:jingle:transports:s5b:1\""
 							" mode=\"tcp\""
@@ -263,15 +263,15 @@ class JingleSerializersTest : public CppUnit::TestFixture {
 			content->setName("a-file-offer");
 			
 			JingleFileTransferDescription::ref description = boost::make_shared<JingleFileTransferDescription>();
-			StreamInitiationFileInfo fileInfo;
+			JingleFileTransferFileInfo fileInfo;
 			fileInfo.setName("test.txt");
 			fileInfo.setSize(1022);
-			fileInfo.setHash("552da749930852c69ae5d2141d3766b1");
+			fileInfo.addHash(HashElement("sha-1", Base64::decode("VS2nSZMIUsaa5dIUHTdmsQ==")));
 			fileInfo.setDate(stringToDateTime("1969-07-21T02:56:15Z"));
 			fileInfo.setDescription("This is a test. If this were a real file...");
 			fileInfo.setSupportsRangeRequests(true);
 			
-			description->addOffer(fileInfo);
+			description->setFileInfo(fileInfo);
 			content->addDescription(description);
 			payload->addPayload(content);
 			
@@ -316,11 +316,9 @@ class JingleSerializersTest : public CppUnit::TestFixture {
 					" initiator=\"romeo@montague.lit/orchard\""
 					" sid=\"a73sjjvkla37jfea\""
 					" xmlns=\"urn:xmpp:jingle:1\">"
-					"<checksum xmlns=\"urn:xmpp:jingle:apps:file-transfer:3\">"
+					"<checksum xmlns=\"urn:xmpp:jingle:apps:file-transfer:4\">"
 						"<file>"
-							"<hashes xmlns=\"urn:xmpp:hashes:0\">"
-								"<hash algo=\"sha-1\">552da749930852c69ae5d2141d3766b1</hash>"
-							"</hashes>"
+							"<hash algo=\"sha-1\" xmlns=\"urn:xmpp:hashes:1\">VS2nSZMIUsaa5dIUHTdmsQ==</hash>"
 						"</file>"
 					"</checksum>"
 				"</jingle>";
@@ -331,82 +329,12 @@ class JingleSerializersTest : public CppUnit::TestFixture {
 			payload->setSessionID("a73sjjvkla37jfea");
 			
 			JingleFileTransferHash::ref hash = boost::make_shared<JingleFileTransferHash>();
-			hash->setHash("sha-1", "552da749930852c69ae5d2141d3766b1");
+			hash->getFileInfo().addHash(HashElement("sha-1", Base64::decode("VS2nSZMIUsaa5dIUHTdmsQ==")));
 			
 			payload->addPayload(hash);
 			
 			CPPUNIT_ASSERT_EQUAL(expected, createTestling()->serialize(payload));
 		}
-		
-		// http://xmpp.org/extensions/xep-0234.html#example-10
-		void testSerialize_Xep0234_Example10() {
-			std::string expected = 
-				"<jingle"
-					" action=\"session-initiate\""
-					" initiator=\"romeo@montague.lit/orchard\""
-					" sid=\"uj3b2\""
-					" xmlns=\"urn:xmpp:jingle:1\">"
-					"<content creator=\"initiator\" name=\"a-file-request\">"
-						"<description"
-							" xmlns=\"urn:xmpp:jingle:apps:file-transfer:3\">"
-							"<request>"
-								"<file"
-									" hash=\"552da749930852c69ae5d2141d3766b1\""
-									" xmlns=\"http://jabber.org/protocol/si/profile/file-transfer\">"
-									"<range offset=\"270336\"/>"
-								"</file>"
-							"</request>"
-						"</description>"
-						/*"<transport"
-							" mode=\"tcp\""
-							" sid=\"xig361fj\""
-							" xmlns=\"urn:xmpp:jingle:transports:s5b:1\">"
-							"<candidate"
-								" cid=\"ht567dq\""
-								" host=\"192.169.1.10\""
-								" jid=\"juliet@capulet.lit/balcony\""
-								" port=\"6539\""
-								" priority=\"8257636\""
-								" type=\"direct\"/>"
-							"<candidate"
-								" cid=\"hr65dqyd\""
-								" host=\"134.102.201.180\""
-								" jid=\"juliet@capulet.lit/balcony\""
-								" port=\"16453\""
-								" priority=\"7929856\""
-								" type=\"assisted\"/>"
-							"<candidate"
-								" cid=\"grt654q2\""
-								" host=\"2001:638:708:30c9:219:d1ff:fea4:a17d\""
-								" jid=\"juliet@capulet.lit/balcony\""
-								" port=\"6539\""
-								" priority=\"8257606\""
-								" type=\"direct\"/>"
-						"</transport>"*/
-					"</content>"
-				"</jingle>";
-			
-			JinglePayload::ref payload = boost::make_shared<JinglePayload>();
-			payload->setAction(JinglePayload::SessionInitiate);
-			payload->setInitiator(JID("romeo@montague.lit/orchard"));
-			payload->setSessionID("uj3b2");
-			
-			StreamInitiationFileInfo fileInfo;
-			fileInfo.setHash("552da749930852c69ae5d2141d3766b1");
-			fileInfo.setRangeOffset(270336);
-			
-			JingleFileTransferDescription::ref desc = boost::make_shared<JingleFileTransferDescription>();
-			desc->addRequest(fileInfo);
-			
-			JingleContentPayload::ref content = boost::make_shared<JingleContentPayload>();
-			content->setCreator(JingleContentPayload::InitiatorCreator);
-			content->setName("a-file-request");
-			content->addDescription(desc);
-			
-			payload->addPayload(content);
-			
-			CPPUNIT_ASSERT_EQUAL(expected, createTestling()->serialize(payload));
-		}
 
 		// http://xmpp.org/extensions/xep-0234.html#example-10
 		void testSerialize_Xep0234_Example13() {
-- 
cgit v0.10.2-6-g49f6