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