From 1f9ca4c9041ffe8b3330150284d54e0870f82b20 Mon Sep 17 00:00:00 2001
From: Tobias Markmann <tm@ayena.de>
Date: Sun, 11 Jan 2015 17:19:54 +0100
Subject: Add debugging helper and FileTransferTest.

FileTransferTests tests file-transfer interoperability with Swiften
itself. It can test all combinations of FileTransferOptions or a specific
combination when given.

Test-Information:

Inspected XML logs to ensure it does what it is supposed to do.

Change-Id: I06215b60419dd23b367d01a2f038245a6c977720

diff --git a/Swiften/Base/Debug.cpp b/Swiften/Base/Debug.cpp
new file mode 100644
index 0000000..82dbec6
--- /dev/null
+++ b/Swiften/Base/Debug.cpp
@@ -0,0 +1,141 @@
+/*
+ * Copyright (c) 2015 Isode Limited.
+ * All rights reserved.
+ * See the COPYING file for more information.
+ */
+
+#include <Swiften/Base/Debug.h>
+
+#include <iostream>
+
+#include <boost/shared_ptr.hpp>
+
+#include <Swiften/Client/ClientError.h>
+#include <Swiften/Serializer/PayloadSerializer.h>
+#include <Swiften/Serializer/PayloadSerializers/FullPayloadSerializerCollection.h>
+#include <Swiften/Serializer/XMPPSerializer.h>
+
+std::ostream& operator<<(std::ostream& os, const Swift::ClientError& error) {
+	os << "ClientError(";
+	switch(error.getType()) {
+		case Swift::ClientError::UnknownError:
+			os << "UnknownError";
+			break;
+		case Swift::ClientError::DomainNameResolveError:
+			os << "DomainNameResolveError";
+			break;
+		case Swift::ClientError::ConnectionError:
+			os << "ConnectionError";
+			break;
+		case Swift::ClientError::ConnectionReadError:
+			os << "ConnectionReadError";
+			break;
+		case Swift::ClientError::ConnectionWriteError:
+			os << "ConnectionWriteError";
+			break;
+		case Swift::ClientError::XMLError:
+			os << "XMLError";
+			break;
+		case Swift::ClientError::AuthenticationFailedError:
+			os << "AuthenticationFailedError";
+			break;
+		case Swift::ClientError::CompressionFailedError:
+			os << "CompressionFailedError";
+			break;
+		case Swift::ClientError::ServerVerificationFailedError:
+			os << "ServerVerificationFailedError";
+			break;
+		case Swift::ClientError::NoSupportedAuthMechanismsError:
+			os << "NoSupportedAuthMechanismsError";
+			break;
+		case Swift::ClientError::UnexpectedElementError:
+			os << "UnexpectedElementError";
+			break;
+		case Swift::ClientError::ResourceBindError:
+			os << "ResourceBindError";
+			break;
+		case Swift::ClientError::SessionStartError:
+			os << "SessionStartError";
+			break;
+		case Swift::ClientError::StreamError:
+			os << "StreamError";
+			break;
+		case Swift::ClientError::TLSError:
+			os << "TLSError";
+			break;
+		case Swift::ClientError::ClientCertificateLoadError:
+			os << "ClientCertificateLoadError";
+			break;
+		case Swift::ClientError::ClientCertificateError:
+			os << "ClientCertificateError";
+			break;
+		case Swift::ClientError::CertificateCardRemoved:
+			os << "CertificateCardRemoved";
+			break;
+		case Swift::ClientError::UnknownCertificateError:
+			os << "UnknownCertificateError";
+			break;
+		case Swift::ClientError::CertificateExpiredError:
+			os << "CertificateExpiredError";
+			break;
+		case Swift::ClientError::CertificateNotYetValidError:
+			os << "CertificateNotYetValidError";
+			break;
+		case Swift::ClientError::CertificateSelfSignedError:
+			os << "CertificateSelfSignedError";
+			break;
+		case Swift::ClientError::CertificateRejectedError:
+			os << "CertificateRejectedError";
+			break;
+		case Swift::ClientError::CertificateUntrustedError:
+			os << "CertificateUntrustedError";
+			break;
+		case Swift::ClientError::InvalidCertificatePurposeError:
+			os << "InvalidCertificatePurposeError";
+			break;
+		case Swift::ClientError::CertificatePathLengthExceededError:
+			os << "CertificatePathLengthExceededError";
+			break;
+		case Swift::ClientError::InvalidCertificateSignatureError:
+			os << "InvalidCertificateSignatureError";
+			break;
+		case Swift::ClientError::InvalidCAError:
+			os << "InvalidCAError";
+			break;
+		case Swift::ClientError::InvalidServerIdentityError:
+			os << "InvalidServerIdentityError";
+			break;
+		case Swift::ClientError::RevokedError:
+			os << "RevokedError";
+			break;
+		case Swift::ClientError::RevocationCheckFailedError:
+			os << "RevocationCheckFailedError";
+			break;
+	}
+	os << ")";
+	return os;
+}
+
+std::ostream& operator<<(std::ostream& os, Swift::Element* ele) {
+	using namespace Swift;
+
+	boost::shared_ptr<Element> element = boost::shared_ptr<Element>(ele);
+
+	boost::shared_ptr<Payload> payload = boost::dynamic_pointer_cast<Payload>(element);
+	if (payload) {
+		 FullPayloadSerializerCollection payloadSerializerCollection;
+		 PayloadSerializer *serializer = payloadSerializerCollection.getPayloadSerializer(payload);
+		 os << "Payload(" << serializer->serialize(payload) << ")";
+		 return os;
+	}
+	boost::shared_ptr<ToplevelElement> topLevelElement = boost::dynamic_pointer_cast<ToplevelElement>(element);
+	if (topLevelElement) {
+		FullPayloadSerializerCollection payloadSerializerCollection;
+		XMPPSerializer xmppSerializer(&payloadSerializerCollection, ClientStreamType, false);
+		SafeByteArray serialized = xmppSerializer.serializeElement(topLevelElement);
+		os << "TopLevelElement(" << safeByteArrayToString(serialized) << ")";
+		return os;
+	}
+	os << "Element(Unknown)";
+	return os;
+}
diff --git a/Swiften/Base/Debug.h b/Swiften/Base/Debug.h
new file mode 100644
index 0000000..33d439e
--- /dev/null
+++ b/Swiften/Base/Debug.h
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2015 Isode Limited.
+ * All rights reserved.
+ * See the COPYING file for more information.
+ */
+
+#include <iosfwd>
+
+namespace Swift {
+	class ClientError;
+	class Element;
+}
+
+namespace boost {
+	template<class T> class shared_ptr;
+}
+
+std::ostream& operator<<(std::ostream& os, const Swift::ClientError& error);
+
+std::ostream& operator<<(std::ostream& os, Swift::Element* ele);
diff --git a/Swiften/QA/FileTransferTest/.gitignore b/Swiften/QA/FileTransferTest/.gitignore
new file mode 100644
index 0000000..768c501
--- /dev/null
+++ b/Swiften/QA/FileTransferTest/.gitignore
@@ -0,0 +1 @@
+FileTransferTest
diff --git a/Swiften/QA/FileTransferTest/FileTransferTest.cpp b/Swiften/QA/FileTransferTest/FileTransferTest.cpp
new file mode 100644
index 0000000..764dffb
--- /dev/null
+++ b/Swiften/QA/FileTransferTest/FileTransferTest.cpp
@@ -0,0 +1,331 @@
+/*
+ * Copyright (c) 2014-2015 Isode Limited.
+ * All rights reserved.
+ * See the COPYING file for more information.
+ */
+
+#include <fstream>
+
+#include <boost/numeric/conversion/cast.hpp>
+#include <boost/filesystem.hpp>
+
+#include <Swiften/Base/sleep.h>
+#include <Swiften/Base/foreach.h>
+#include <Swiften/Base/Log.h>
+#include <Swiften/Client/ClientXMLTracer.h>
+#include <Swiften/Client/Client.h>
+#include <Swiften/EventLoop/SimpleEventLoop.h>
+#include <Swiften/Network/BoostNetworkFactories.h>
+#include <Swiften/Network/Timer.h>
+#include <Swiften/Network/TimerFactory.h>
+#include <Swiften/Disco/EntityCapsProvider.h>
+#include <Swiften/Elements/Presence.h>
+#include <Swiften/FileTransfer/ReadBytestream.h>
+#include <Swiften/Base/BoostRandomGenerator.h>
+#include <Swiften/FileTransfer/FileReadBytestream.h>
+#include <Swiften/FileTransfer/OutgoingFileTransfer.h>
+#include <Swiften/FileTransfer/FileTransferManager.h>
+#include <Swiften/Disco/ClientDiscoManager.h>
+#include <Swiften/FileTransfer/FileWriteBytestream.h>
+#include <Swiften/Base/Debug.h>
+
+using namespace Swift;
+
+static const std::string CLIENT_NAME = "Swiften FT Test";
+static const std::string CLIENT_NODE = "http://swift.im";
+
+static boost::shared_ptr<SimpleEventLoop> eventLoop;
+static boost::shared_ptr<BoostNetworkFactories> networkFactories;
+
+BoostRandomGenerator randGen;
+
+enum Candidate {
+	InBandBytestream = 1,
+	S5B_Direct = 2,
+	S5B_Proxied = 4,
+	S5B_Assisted = 8,
+};
+
+class FileTransferTest {
+	public:
+		FileTransferTest(int senderCandidates, int receiverCandidates) : senderCandidates_(senderCandidates), senderIsDone_(false), receiverCandidates_(receiverCandidates), receiverIsDone_(false) {
+			sender_ = boost::make_shared<Client>(JID(getenv("SWIFT_FILETRANSFERTEST_JID")), getenv("SWIFT_FILETRANSFERTEST_PASS"), networkFactories.get());
+			sender_->onDisconnected.connect(boost::bind(&FileTransferTest::handleSenderDisconnected, this, _1));
+			sender_->onConnected.connect(boost::bind(&FileTransferTest::handleSenderConnected, this));
+			sender_->getEntityCapsProvider()->onCapsChanged.connect(boost::bind(&FileTransferTest::handleSenderCapsChanged, this, _1));
+
+			receiver_ = boost::make_shared<Client>(JID(getenv("SWIFT_FILETRANSFERTEST2_JID")), getenv("SWIFT_FILETRANSFERTEST2_PASS"), networkFactories.get());
+			receiver_->onConnected.connect(boost::bind(&FileTransferTest::handleReceiverConnected, this));
+			receiver_->onDisconnected.connect(boost::bind(&FileTransferTest::handleReceiverDisconnected, this, _1));
+
+			new ClientXMLTracer(sender_.get());
+			new ClientXMLTracer(receiver_.get());
+
+			ClientOptions options;
+			options.useTLS = ClientOptions::NeverUseTLS;
+			options.useStreamCompression = false;
+			options.useStreamResumption = false;
+			options.useAcks = false;
+
+			sender_->connect(options);
+			receiver_->connect(options);
+
+			timeOut_ = networkFactories->getTimerFactory()->createTimer(60000);
+			timeOut_->onTick.connect(boost::bind(&FileTransferTest::handleTimeOut, this));
+
+			// Create randomly sized data to exchange.
+			sendFilePath_ = boost::filesystem::unique_path();
+			receiveFilePath_ = boost::filesystem::unique_path();
+
+			size_t size = 1024 + boost::numeric_cast<size_t>(randGen.generateRandomInteger(1024 * 10));
+			sendData_.resize(size);
+			for (size_t n = 0; n < sendData_.size(); n++) {
+				sendData_[n] = boost::numeric_cast<unsigned char>(randGen.generateRandomInteger(255));
+			}
+
+			std::ofstream outfile(sendFilePath_.native().c_str(), std::ios::out | std::ios::binary);
+			outfile.write(reinterpret_cast<char *>(&sendData_[0]), sendData_.size());
+			outfile.close();
+		}
+
+		~FileTransferTest() {
+			timeOut_->stop();
+
+			if(boost::filesystem::exists(sendFilePath_)) {
+				boost::filesystem::remove(sendFilePath_);
+			}
+
+			if(boost::filesystem::exists(receiveFilePath_)) {
+				boost::filesystem::remove(receiveFilePath_);
+			}
+		}
+
+		void handleSenderConnected() {
+			sender_->sendPresence(Presence::create());
+		}
+
+		void handleReceiverConnected() {
+			receiver_->getFileTransferManager()->onIncomingFileTransfer.connect(boost::bind(&FileTransferTest::handleReceiverIncomingFileTransfer, this, _1));
+
+			DiscoInfo discoInfo;
+			discoInfo.addIdentity(DiscoInfo::Identity(CLIENT_NAME, "client", "pc"));
+			discoInfo.addFeature(DiscoInfo::JingleFeature);
+			discoInfo.addFeature(DiscoInfo::JingleFTFeature);
+			discoInfo.addFeature(DiscoInfo::Bytestream);
+			discoInfo.addFeature(DiscoInfo::JingleTransportsIBBFeature);
+			discoInfo.addFeature(DiscoInfo::JingleTransportsS5BFeature);
+			receiver_->getDiscoManager()->setCapsNode(CLIENT_NODE);
+			receiver_->getDiscoManager()->setDiscoInfo(discoInfo);
+			receiver_->getPresenceSender()->sendPresence(Presence::create());
+		}
+
+		void handleReceiverIncomingFileTransfer(IncomingFileTransfer::ref transfer) {
+			incomingFileTransfers_.push_back(transfer);
+			boost::shared_ptr<FileWriteBytestream> out = boost::make_shared<FileWriteBytestream>(receiveFilePath_.native());
+			transfer->onFinished.connect(boost::bind(&FileTransferTest::handleReceiverFileTransferFinished, this, _1, out));
+
+			FileTransferOptions options;
+			options = options.withInBandAllowed(receiverCandidates_ & InBandBytestream);
+			options = options.withDirectAllowed(receiverCandidates_ & S5B_Direct);
+			options = options.withAssistedAllowed(receiverCandidates_ & S5B_Assisted);
+			options = options.withProxiedAllowed(receiverCandidates_ & S5B_Proxied);
+
+			std::cout << "Incoming transfer options: " << "IBB (" << options.isInBandAllowed() << ")" << ", ";
+			std::cout << "S5B Direct (" << options.isDirectAllowed() << ")" << ", ";
+			std::cout << "S5B Assisted (" << options.isAssistedAllowed() << ")" << ", ";
+			std::cout << "S5B Proxied (" << options.isProxiedAllowed() << ")" << std::endl;
+
+			transfer->accept(out, options);
+		}
+
+		void handleSenderCapsChanged(const JID &jid) {
+			if (receiver_ && (receiver_->getJID().toBare() == jid.toBare())) {
+				boost::shared_ptr<FileReadBytestream> fileStream = boost::make_shared<FileReadBytestream>(sendFilePath_);
+
+				FileTransferOptions options;
+				options = options.withInBandAllowed(senderCandidates_ & InBandBytestream);
+				options = options.withDirectAllowed(senderCandidates_ & S5B_Direct);
+				options = options.withAssistedAllowed(senderCandidates_ & S5B_Assisted);
+				options = options.withProxiedAllowed(senderCandidates_ & S5B_Proxied);
+
+				std::cout << "Outgoing transfer options: " << "IBB (" << options.isInBandAllowed() << ")" << ", ";
+				std::cout << "S5B Direct (" << options.isDirectAllowed() << ")" << ", ";
+				std::cout << "S5B Assisted (" << options.isAssistedAllowed() << ")" << ", ";
+				std::cout << "S5B Proxied (" << options.isProxiedAllowed() << ")" << std::endl;
+
+				outgoingFileTransfer_ = sender_->getFileTransferManager()->createOutgoingFileTransfer(jid.toBare(), sendFilePath_, "Some File!", fileStream, options);
+
+				if (outgoingFileTransfer_) {
+					outgoingFileTransfer_->onFinished.connect(boost::bind(&FileTransferTest::handleSenderFileTransferFinished, this, _1));
+					outgoingFileTransfer_->start();
+				} else {
+					std::cout << "ERROR: No outgoing file transfer returned." << std::endl;
+					endTest();
+				}
+			}
+		}
+
+		void handleReceiverFileTransferFinished(const boost::optional<FileTransferError>& error, boost::shared_ptr<FileWriteBytestream> out) {
+			out->close();
+			receiverError_ = error;
+			receiverIsDone_ = true;
+			if (senderIsDone_) {
+				timeOut_->stop();
+				timeOut_ = networkFactories->getTimerFactory()->createTimer(1000);
+				timeOut_->onTick.connect(boost::bind(&FileTransferTest::endTest, this));
+				timeOut_->start();
+			}
+		}
+
+		void handleSenderDisconnected(const boost::optional<ClientError>& error) {
+			if (error) {
+				std::cout << this << " " << "handleSenderDisconnected: error: " << error.get() << std::endl;
+			}
+			sender_.reset();
+			if (!sender_ && !receiver_) {
+				eventLoop->stop();
+			}
+		}
+
+		void handleReceiverDisconnected(const boost::optional<ClientError>& error) {
+			if (error) {
+				std::cout << this << " " << "handleReceiverDisconnected: error: " << error.get() << std::endl;
+			}
+			receiver_.reset();
+			if (!sender_ && !receiver_) {
+				eventLoop->stop();
+			}
+		}
+
+		void handleSenderFileTransferFinished(const boost::optional<FileTransferError>& error) {
+			senderError_ = error;
+			senderIsDone_ = true;
+			if (receiverIsDone_) {
+				timeOut_->stop();
+				timeOut_ = networkFactories->getTimerFactory()->createTimer(1000);
+				timeOut_->onTick.connect(boost::bind(&FileTransferTest::endTest, this));
+				timeOut_->start();
+			}
+		}
+
+		void run() {
+			timeOut_->start();
+			eventLoop->run();
+		}
+
+		void endTest() {
+			if (sender_) {
+				sender_->disconnect();
+			}
+			if (receiver_) {
+				receiver_->disconnect();
+			}
+		}
+
+		void handleTimeOut() {
+			std::cout << "Test timed out!!!" << std::endl;
+			endTest();
+		}
+
+		bool isDone() const {
+			return senderIsDone_ && receiverIsDone_;
+		}
+
+		bool wasSuccessful() const {
+			return !senderError_ && !receiverError_;
+		}
+
+	private:
+		int senderCandidates_;
+		boost::shared_ptr<Client> sender_;
+		ByteArray sendData_;
+		OutgoingFileTransfer::ref outgoingFileTransfer_;
+		boost::filesystem::path sendFilePath_;
+		boost::optional<FileTransferError> senderError_;
+		bool senderIsDone_;
+
+		int receiverCandidates_;
+		boost::shared_ptr<Client> receiver_;
+		ByteArray receiveData_;
+		std::vector<IncomingFileTransfer::ref> incomingFileTransfers_;
+		boost::filesystem::path receiveFilePath_;
+		boost::optional<FileTransferError> receiverError_;
+		bool receiverIsDone_;
+
+		Timer::ref timeOut_;
+};
+
+bool runTest(int senderCandidates, int receiverCandidates) {
+	bool success = false;
+
+	std::cout << "senderCandidates: " << senderCandidates << ", receiverCandidates: " << receiverCandidates << std::endl;
+	bool expectSuccess = (senderCandidates & receiverCandidates) > 0;
+
+	eventLoop = boost::make_shared<SimpleEventLoop>();
+	networkFactories = boost::make_shared<BoostNetworkFactories>(eventLoop.get());
+
+	boost::shared_ptr<FileTransferTest> testRun = boost::make_shared<FileTransferTest>(senderCandidates, receiverCandidates);
+
+	testRun->run();
+
+	if (testRun->isDone()) {
+		bool wasSuccessful = testRun->wasSuccessful();
+		if (expectSuccess == wasSuccessful) {
+			success = true;
+		} else {
+			std::cout << "expected success: " << expectSuccess << ", wasSuccessful: " << wasSuccessful << std::endl;
+		}
+	} else {
+		std::cout << "Failed to run test! Sender candidates = " << senderCandidates << ", receiver candidates = " << receiverCandidates << "." << std::endl;
+	}
+
+	testRun.reset();
+	networkFactories.reset();
+	eventLoop->runUntilEvents();
+
+	eventLoop->stop();
+	eventLoop.reset();
+
+	return success;
+}
+
+/**
+ *	This program test file-transfer	 interop between Swift and itself with various connection candidates.
+ *	The all combinations of the candidates, IBB, S5B (direct) and S5B (proxied), on sender and receiver side are tested.
+ */
+int main(int argc, char** argv) {
+	int failedTests = 0;
+
+	std::vector<std::pair<int, int> > failedTestPairs;
+	std::cout << "Swiften File-Transfer Connectivity Test Suite" << std::endl;
+	if (argc == 1) {
+		for (int n = 0; n < (1 << 7); n++) {
+			int senderCandidates = n & 0xF;
+			int receiverCandidates = (n >> 4) & 0xF;
+			std::cout << "Run test " << n + 1 << " of " << (1 << 7) << ", (" << senderCandidates << ", " << receiverCandidates << ")" << std::endl;
+			if (!runTest(senderCandidates, receiverCandidates)) {
+				failedTests++;
+				failedTestPairs.push_back(std::pair<int, int>(senderCandidates, receiverCandidates));
+			}
+		}
+
+		typedef std::pair<int, int> IntPair;
+		foreach(IntPair failedTest, failedTestPairs) {
+			std::cout << "Failed test: " << "( " << failedTest.first << ", " << failedTest.second << ") " << std::endl;
+		}
+	}
+	else if (argc == 3) {
+		Log::setLogLevel(Log::debug);
+		int senderCandidates = atoi(argv[1]);
+		int receiverCandidates = atoi(argv[2]);
+		if (!runTest(senderCandidates, receiverCandidates)) {
+			failedTests++;
+		}
+	}
+	else {
+		std::cout << "Usage:" << std::endl;
+		std::cout << "\t- to test all combinations pass no arguments" << std::endl;
+		std::cout << "\t- to test a specific combination pass two integers describing sender and receiver candidates" << std::endl;
+	}
+	return failedTests;
+}
diff --git a/Swiften/QA/FileTransferTest/SConscript b/Swiften/QA/FileTransferTest/SConscript
new file mode 100644
index 0000000..d17b12a
--- /dev/null
+++ b/Swiften/QA/FileTransferTest/SConscript
@@ -0,0 +1,17 @@
+import os
+
+Import("env")
+
+if env["TEST"] :
+	myenv = env.Clone()
+	myenv.UseFlags(myenv["SWIFTEN_FLAGS"])
+	myenv.UseFlags(myenv["SWIFTEN_DEP_FLAGS"])
+
+	for i in ["SWIFT_FILETRANSFERTEST_JID", "SWIFT_FILETRANSFERTEST_PASS", "SWIFT_FILETRANSFERTEST2_JID", "SWIFT_FILETRANSFERTEST2_PASS"]:
+		if ARGUMENTS.get(i.lower(), False) :
+			myenv["ENV"][i] = ARGUMENTS[i.lower()]
+		elif os.environ.get(i, "") :
+			myenv["ENV"][i] = os.environ[i]
+
+	tester = myenv.Program("FileTransferTest", ["FileTransferTest.cpp"])
+	myenv.Test(tester, "system")
diff --git a/Swiften/QA/SConscript b/Swiften/QA/SConscript
index 2f2be6e..2135ef4 100644
--- a/Swiften/QA/SConscript
+++ b/Swiften/QA/SConscript
@@ -9,4 +9,5 @@ SConscript(dirs = [
 		"TLSTest",
 		"ScriptedTests",
 		"ProxyProviderTest",
+		"FileTransferTest",
 	])
diff --git a/Swiften/SConscript b/Swiften/SConscript
index 685b3d5..340688b 100644
--- a/Swiften/SConscript
+++ b/Swiften/SConscript
@@ -101,6 +101,7 @@ if env["SCONS_STAGE"] == "build" :
 
 # TODO: Move all this to a submodule SConscript
 	sources = [
+			"Base/Debug.cpp",
 			"Chat/ChatStateTracker.cpp",
 			"Chat/ChatStateNotifier.cpp",
 			"Client/ClientSessionStanzaChannel.cpp",
-- 
cgit v0.10.2-6-g49f6