diff options
Diffstat (limited to 'Swiften/FileTransfer')
80 files changed, 4970 insertions, 385 deletions
diff --git a/Swiften/FileTransfer/ByteArrayReadBytestream.h b/Swiften/FileTransfer/ByteArrayReadBytestream.h index d459658..9311099 100644 --- a/Swiften/FileTransfer/ByteArrayReadBytestream.h +++ b/Swiften/FileTransfer/ByteArrayReadBytestream.h @@ -6,31 +6,47 @@ #pragma once -#include "Swiften/FileTransfer/ReadBytestream.h" -#include "Swiften/Base/ByteArray.h" +#include <vector> +#include <boost/smart_ptr/make_shared.hpp> + +#include <Swiften/Base/Algorithm.h> +#include <Swiften/FileTransfer/ReadBytestream.h> +#include <Swiften/Base/ByteArray.h> namespace Swift { class ByteArrayReadBytestream : public ReadBytestream { public: - ByteArrayReadBytestream(const ByteArray& data) : data(data), position(0) { + ByteArrayReadBytestream(const std::vector<unsigned char>& data) : data(data), position(0), dataComplete(true) { } - virtual ByteArray read(size_t size) { + virtual boost::shared_ptr<ByteArray> read(size_t size) { size_t readSize = size; - if (position + readSize > data.getSize()) { - readSize = data.getSize() - position; + if (position + readSize > data.size()) { + readSize = data.size() - position; } - ByteArray result(data.getData() + position, readSize); + boost::shared_ptr<ByteArray> result = boost::make_shared<ByteArray>(data.begin() + position, data.begin() + position + readSize); + + onRead(*result); position += readSize; return result; } virtual bool isFinished() const { - return position >= data.getSize(); + return position >= data.size() && dataComplete; + } + + virtual void setDataComplete(bool b) { + dataComplete = b; + } + + void addData(const std::vector<unsigned char>& moreData) { + append(data, moreData); + onDataAvailable(); } private: - ByteArray data; + std::vector<unsigned char> data; size_t position; + bool dataComplete; }; } diff --git a/Swiften/FileTransfer/ByteArrayWriteBytestream.h b/Swiften/FileTransfer/ByteArrayWriteBytestream.h new file mode 100644 index 0000000..ef97ed9 --- /dev/null +++ b/Swiften/FileTransfer/ByteArrayWriteBytestream.h @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2010 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#pragma once + +#include <Swiften/FileTransfer/WriteBytestream.h> + +namespace Swift { + class ByteArrayWriteBytestream : public WriteBytestream { + public: + ByteArrayWriteBytestream() { + } + + virtual void write(const std::vector<unsigned char>& bytes) { + data.insert(data.end(), bytes.begin(), bytes.end()); + onWrite(bytes); + } + + const std::vector<unsigned char>& getData() const { + return data; + } + + private: + std::vector<unsigned char> data; + }; +} diff --git a/Swiften/FileTransfer/BytestreamsRequest.h b/Swiften/FileTransfer/BytestreamsRequest.h index 9757bfa..fee09ee 100644 --- a/Swiften/FileTransfer/BytestreamsRequest.h +++ b/Swiften/FileTransfer/BytestreamsRequest.h @@ -8,8 +8,8 @@ #include <boost/shared_ptr.hpp> -#include "Swiften/Queries/GenericRequest.h" -#include "Swiften/Elements/Bytestreams.h" +#include <Swiften/Queries/GenericRequest.h> +#include <Swiften/Elements/Bytestreams.h> namespace Swift { class BytestreamsRequest : public GenericRequest<Bytestreams> { @@ -20,8 +20,15 @@ namespace Swift { return ref(new BytestreamsRequest(jid, payload, router)); } + static ref create(const JID& from, const JID& to, boost::shared_ptr<Bytestreams> payload, IQRouter* router) { + return ref(new BytestreamsRequest(from, to, payload, router)); + } + private: BytestreamsRequest(const JID& jid, boost::shared_ptr<Bytestreams> payload, IQRouter* router) : GenericRequest<Bytestreams>(IQ::Set, jid, payload, router) { } + + BytestreamsRequest(const JID& from, const JID& to, boost::shared_ptr<Bytestreams> payload, IQRouter* router) : GenericRequest<Bytestreams>(IQ::Set, from, to, payload, router) { + } }; } diff --git a/Swiften/FileTransfer/ConnectivityManager.cpp b/Swiften/FileTransfer/ConnectivityManager.cpp new file mode 100644 index 0000000..7d25991 --- /dev/null +++ b/Swiften/FileTransfer/ConnectivityManager.cpp @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include "ConnectivityManager.h" + +#include <boost/bind.hpp> + +#include <Swiften/Base/foreach.h> +#include <Swiften/Base/Log.h> +#include <Swiften/Network/NetworkInterface.h> +#include <Swiften/Network/NATTraversalGetPublicIPRequest.h> +#include <Swiften/Network/NATTraversalRemovePortForwardingRequest.h> +#include <Swiften/Network/NATTraverser.h> +#include <Swiften/Network/PlatformNetworkEnvironment.h> + +namespace Swift { + +ConnectivityManager::ConnectivityManager(NATTraverser* worker) : natTraversalWorker(worker) { + +} + +ConnectivityManager::~ConnectivityManager() { + std::set<int> leftOpenPorts = ports; + foreach(int port, leftOpenPorts) { + removeListeningPort(port); + } +} + +void ConnectivityManager::addListeningPort(int port) { + ports.insert(port); + boost::shared_ptr<NATTraversalGetPublicIPRequest> getIPRequest = natTraversalWorker->createGetPublicIPRequest(); + if (getIPRequest) { + getIPRequest->onResult.connect(boost::bind(&ConnectivityManager::natTraversalGetPublicIPResult, this, _1)); + getIPRequest->run(); + } + + boost::shared_ptr<NATTraversalForwardPortRequest> forwardPortRequest = natTraversalWorker->createForwardPortRequest(port, port); + if (forwardPortRequest) { + forwardPortRequest->onResult.connect(boost::bind(&ConnectivityManager::natTraversalForwardPortResult, this, _1)); + forwardPortRequest->run(); + } +} + +void ConnectivityManager::removeListeningPort(int port) { + SWIFT_LOG(debug) << "remove listening port " << port << std::endl; + ports.erase(port); + boost::shared_ptr<NATTraversalRemovePortForwardingRequest> removePortForwardingRequest = natTraversalWorker->createRemovePortForwardingRequest(port, port); + if (removePortForwardingRequest) { + removePortForwardingRequest->run(); + } +} + +std::vector<HostAddressPort> ConnectivityManager::getHostAddressPorts() const { + PlatformNetworkEnvironment env; + std::vector<HostAddressPort> results; + + std::vector<HostAddress> addresses; + + std::vector<NetworkInterface> networkInterfaces; + foreach (const NetworkInterface& iface, networkInterfaces) { + foreach (const HostAddress& address, iface.getAddresses()) { + foreach (int port, ports) { + results.push_back(HostAddressPort(address, port)); + } + } + } + + return results; +} + +std::vector<HostAddressPort> ConnectivityManager::getAssistedHostAddressPorts() const { + std::vector<HostAddressPort> results; + + if (publicAddress) { + foreach (int port, ports) { + results.push_back(HostAddressPort(publicAddress.get(), port)); + } + } + + return results; +} + +void ConnectivityManager::natTraversalGetPublicIPResult(boost::optional<HostAddress> address) { + if (address) { + publicAddress = address; + SWIFT_LOG(debug) << "Public IP discovered as " << publicAddress.get().toString() << "." << std::endl; + } else { + SWIFT_LOG(debug) << "No public IP discoverable." << std::endl; + } +} + +void ConnectivityManager::natTraversalForwardPortResult(boost::optional<NATPortMapping> mapping) { + if (mapping) { + SWIFT_LOG(debug) << "Mapping port was successful." << std::endl; + } else { + SWIFT_LOG(debug) << "Mapping port has failed." << std::endl; + } +} + + +} diff --git a/Swiften/FileTransfer/ConnectivityManager.h b/Swiften/FileTransfer/ConnectivityManager.h new file mode 100644 index 0000000..c094c02 --- /dev/null +++ b/Swiften/FileTransfer/ConnectivityManager.h @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <vector> +#include <set> + +#include <boost/optional.hpp> + +#include <Swiften/Network/HostAddressPort.h> +#include <Swiften/Network/NATTraverser.h> +#include <Swiften/Network/NATTraversalForwardPortRequest.h> +#include <Swiften/Network/NATPortMapping.h> + +namespace Swift { + +class NATTraverser; + +class ConnectivityManager { +public: + ConnectivityManager(NATTraverser*); + ~ConnectivityManager(); +public: + void addListeningPort(int port); + void removeListeningPort(int port); + + std::vector<HostAddressPort> getHostAddressPorts() const; + std::vector<HostAddressPort> getAssistedHostAddressPorts() const; + +private: + void natTraversalGetPublicIPResult(boost::optional<HostAddress> address); + void natTraversalForwardPortResult(boost::optional<NATPortMapping> mapping); + +private: + NATTraverser* natTraversalWorker; + + std::set<int> ports; + boost::optional<HostAddress> publicAddress; +}; + +} diff --git a/Swiften/FileTransfer/DefaultLocalJingleTransportCandidateGenerator.cpp b/Swiften/FileTransfer/DefaultLocalJingleTransportCandidateGenerator.cpp new file mode 100644 index 0000000..4b205cb --- /dev/null +++ b/Swiften/FileTransfer/DefaultLocalJingleTransportCandidateGenerator.cpp @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include "DefaultLocalJingleTransportCandidateGenerator.h" + +#include <vector> + +#include <boost/shared_ptr.hpp> +#include <boost/smart_ptr/make_shared.hpp> + +#include <Swiften/Base/foreach.h> +#include <Swiften/Base/Log.h> +#include <Swiften/Elements/JingleIBBTransportPayload.h> +#include <Swiften/Elements/JingleS5BTransportPayload.h> +#include <Swiften/FileTransfer/ConnectivityManager.h> +#include <Swiften/FileTransfer/SOCKS5BytestreamRegistry.h> +#include <Swiften/FileTransfer/SOCKS5BytestreamProxy.h> + +namespace Swift { + +DefaultLocalJingleTransportCandidateGenerator::DefaultLocalJingleTransportCandidateGenerator(ConnectivityManager* connectivityManager, SOCKS5BytestreamRegistry* s5bRegistry, SOCKS5BytestreamProxy* s5bProxy, JID& ownJID) : connectivityManager(connectivityManager), s5bRegistry(s5bRegistry), s5bProxy(s5bProxy), ownJID(ownJID) { +} + +DefaultLocalJingleTransportCandidateGenerator::~DefaultLocalJingleTransportCandidateGenerator() { +} + +void DefaultLocalJingleTransportCandidateGenerator::generateLocalTransportCandidates(JingleTransportPayload::ref transportPayload) { + if (boost::dynamic_pointer_cast<JingleIBBTransportPayload>(transportPayload)) { + JingleTransportPayload::ref payL = boost::make_shared<JingleTransportPayload>(); + payL->setSessionID(transportPayload->getSessionID()); + onLocalTransportCandidatesGenerated(payL); + } + if (boost::dynamic_pointer_cast<JingleS5BTransportPayload>(transportPayload)) { + JingleS5BTransportPayload::ref payL = boost::make_shared<JingleS5BTransportPayload>(); + payL->setSessionID(transportPayload->getSessionID()); + payL->setMode(JingleS5BTransportPayload::TCPMode); + + const unsigned long localPreference = 0; + + // get direct candidates + std::vector<HostAddressPort> directCandidates = connectivityManager->getHostAddressPorts(); + foreach(HostAddressPort addressPort, directCandidates) { + JingleS5BTransportPayload::Candidate candidate; + candidate.type = JingleS5BTransportPayload::Candidate::DirectType; + candidate.jid = ownJID; + candidate.hostPort = addressPort; + candidate.priority = 65536 * 126 + localPreference; + candidate.cid = idGenerator.generateID(); + payL->addCandidate(candidate); + } + + // get assissted candidates + std::vector<HostAddressPort> assisstedCandidates = connectivityManager->getAssistedHostAddressPorts(); + foreach(HostAddressPort addressPort, assisstedCandidates) { + JingleS5BTransportPayload::Candidate candidate; + candidate.type = JingleS5BTransportPayload::Candidate::AssistedType; + candidate.jid = ownJID; + candidate.hostPort = addressPort; + candidate.priority = 65536 * 120 + localPreference; + candidate.cid = idGenerator.generateID(); + payL->addCandidate(candidate); + } + + // get proxy candidates + std::vector<S5BProxyRequest::ref> proxyCandidates = s5bProxy->getS5BProxies(); + foreach(S5BProxyRequest::ref proxy, proxyCandidates) { + if (proxy->getStreamHost()) { // FIXME: Added this test, because there were cases where this wasn't initialized. Investigate this. (Remko) + JingleS5BTransportPayload::Candidate candidate; + candidate.type = JingleS5BTransportPayload::Candidate::ProxyType; + candidate.jid = (*proxy->getStreamHost()).jid; + candidate.hostPort = (*proxy->getStreamHost()).addressPort; + candidate.priority = 65536 * 10 + localPreference; + candidate.cid = idGenerator.generateID(); + payL->addCandidate(candidate); + } + } + + onLocalTransportCandidatesGenerated(payL); + } + +} + +bool DefaultLocalJingleTransportCandidateGenerator::isActualCandidate(JingleTransportPayload::ref transportPayload) { + if (!transportPayload.get()) return false; + return false; +} + +int DefaultLocalJingleTransportCandidateGenerator::getPriority(JingleTransportPayload::ref /* transportPayload */) { + return 0; +} + +JingleTransport::ref DefaultLocalJingleTransportCandidateGenerator::selectTransport(JingleTransportPayload::ref /* transportPayload */) { + return JingleTransport::ref(); +} + +} diff --git a/Swiften/FileTransfer/DefaultLocalJingleTransportCandidateGenerator.h b/Swiften/FileTransfer/DefaultLocalJingleTransportCandidateGenerator.h new file mode 100644 index 0000000..7d45491 --- /dev/null +++ b/Swiften/FileTransfer/DefaultLocalJingleTransportCandidateGenerator.h @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <Swiften/FileTransfer/LocalJingleTransportCandidateGenerator.h> + +#include <Swiften/Base/IDGenerator.h> +#include <Swiften/JID/JID.h> + +namespace Swift { + +class SOCKS5BytestreamRegistry; +class SOCKS5BytestreamProxy; +class ConnectivityManager; + +class DefaultLocalJingleTransportCandidateGenerator : public LocalJingleTransportCandidateGenerator { +public: + DefaultLocalJingleTransportCandidateGenerator(ConnectivityManager* connectivityManager, SOCKS5BytestreamRegistry* s5bRegistry, SOCKS5BytestreamProxy* s5bProxy, JID& ownJID); + virtual ~DefaultLocalJingleTransportCandidateGenerator(); + + virtual void generateLocalTransportCandidates(JingleTransportPayload::ref); + + virtual bool isActualCandidate(JingleTransportPayload::ref); + virtual int getPriority(JingleTransportPayload::ref); + virtual JingleTransport::ref selectTransport(JingleTransportPayload::ref); + +private: + IDGenerator idGenerator; + ConnectivityManager* connectivityManager; + SOCKS5BytestreamRegistry* s5bRegistry; + SOCKS5BytestreamProxy* s5bProxy; + JID ownJID; +}; + +} diff --git a/Swiften/FileTransfer/DefaultLocalJingleTransportCandidateGeneratorFactory.cpp b/Swiften/FileTransfer/DefaultLocalJingleTransportCandidateGeneratorFactory.cpp new file mode 100644 index 0000000..ed0386e --- /dev/null +++ b/Swiften/FileTransfer/DefaultLocalJingleTransportCandidateGeneratorFactory.cpp @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include "DefaultLocalJingleTransportCandidateGeneratorFactory.h" + +#include <Swiften/FileTransfer/DefaultLocalJingleTransportCandidateGenerator.h> +#include <Swiften/Base/Log.h> + +namespace Swift { + +DefaultLocalJingleTransportCandidateGeneratorFactory::DefaultLocalJingleTransportCandidateGeneratorFactory(ConnectivityManager* connectivityManager, SOCKS5BytestreamRegistry* s5bRegistry, SOCKS5BytestreamProxy* s5bProxy, const JID& ownJID) : connectivityManager(connectivityManager), s5bRegistry(s5bRegistry), s5bProxy(s5bProxy), ownJID(ownJID) { +} + +DefaultLocalJingleTransportCandidateGeneratorFactory::~DefaultLocalJingleTransportCandidateGeneratorFactory() { +} + +LocalJingleTransportCandidateGenerator* DefaultLocalJingleTransportCandidateGeneratorFactory::createCandidateGenerator() { + return new DefaultLocalJingleTransportCandidateGenerator(connectivityManager, s5bRegistry, s5bProxy, ownJID); +} + + +} diff --git a/Swiften/FileTransfer/DefaultLocalJingleTransportCandidateGeneratorFactory.h b/Swiften/FileTransfer/DefaultLocalJingleTransportCandidateGeneratorFactory.h new file mode 100644 index 0000000..511d0a1 --- /dev/null +++ b/Swiften/FileTransfer/DefaultLocalJingleTransportCandidateGeneratorFactory.h @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <Swiften/FileTransfer/LocalJingleTransportCandidateGeneratorFactory.h> + +#include <Swiften/JID/JID.h> + +namespace Swift { + +class ConnectivityManager; +class SOCKS5BytestreamRegistry; +class SOCKS5BytestreamProxy; + +class DefaultLocalJingleTransportCandidateGeneratorFactory : public LocalJingleTransportCandidateGeneratorFactory{ +public: + DefaultLocalJingleTransportCandidateGeneratorFactory(ConnectivityManager* connectivityManager, SOCKS5BytestreamRegistry* s5bRegistry, SOCKS5BytestreamProxy* s5bProxy, const JID& ownJID); + virtual ~DefaultLocalJingleTransportCandidateGeneratorFactory(); + + LocalJingleTransportCandidateGenerator* createCandidateGenerator(); + +private: + ConnectivityManager* connectivityManager; + SOCKS5BytestreamRegistry* s5bRegistry; + SOCKS5BytestreamProxy* s5bProxy; + JID ownJID; +}; + +} diff --git a/Swiften/FileTransfer/DefaultRemoteJingleTransportCandidateSelector.cpp b/Swiften/FileTransfer/DefaultRemoteJingleTransportCandidateSelector.cpp new file mode 100644 index 0000000..32b4df8 --- /dev/null +++ b/Swiften/FileTransfer/DefaultRemoteJingleTransportCandidateSelector.cpp @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include "DefaultRemoteJingleTransportCandidateSelector.h" + +#include <boost/smart_ptr/make_shared.hpp> +#include <boost/bind.hpp> + +#include <Swiften/Base/Log.h> +#include <Swiften/Base/boost_bsignals.h> +#include <Swiften/Base/foreach.h> +#include <Swiften/Elements/JingleS5BTransportPayload.h> +#include <Swiften/Network/ConnectionFactory.h> +#include <Swiften/FileTransfer/SOCKS5BytestreamRegistry.h> + +namespace Swift { + +DefaultRemoteJingleTransportCandidateSelector::DefaultRemoteJingleTransportCandidateSelector(ConnectionFactory* connectionFactory, TimerFactory* timerFactory) : connectionFactory(connectionFactory), timerFactory(timerFactory) { +} + +DefaultRemoteJingleTransportCandidateSelector::~DefaultRemoteJingleTransportCandidateSelector() { +} + +void DefaultRemoteJingleTransportCandidateSelector::addRemoteTransportCandidates(JingleTransportPayload::ref transportPayload) { + JingleS5BTransportPayload::ref s5bPayload; + transportSID = transportPayload->getSessionID(); + if ((s5bPayload = boost::dynamic_pointer_cast<JingleS5BTransportPayload>(transportPayload))) { + foreach(JingleS5BTransportPayload::Candidate c, s5bPayload->getCandidates()) { + candidates.push(c); + } + } +} + +void DefaultRemoteJingleTransportCandidateSelector::selectCandidate() { + tryNextCandidate(true); +} + +void DefaultRemoteJingleTransportCandidateSelector::tryNextCandidate(bool error) { + if (error) { + if (s5bSession) { + SWIFT_LOG(debug) << "failed to connect" << std::endl; + } + if (candidates.empty()) { + // failed to connect to any of the candidates + // issue an error + SWIFT_LOG(debug) << "out of candidates )=" << std::endl; + JingleS5BTransportPayload::ref failed = boost::make_shared<JingleS5BTransportPayload>(); + failed->setCandidateError(true); + failed->setSessionID(transportSID); + onRemoteTransportCandidateSelectFinished(failed); + } else { + lastCandidate = candidates.top(); + // only try direct or assisted for now + if (lastCandidate.type == JingleS5BTransportPayload::Candidate::DirectType || + lastCandidate.type == JingleS5BTransportPayload::Candidate::AssistedType || lastCandidate.type == JingleS5BTransportPayload::Candidate::ProxyType ) { + // create connection + connection = connectionFactory->createConnection(); + s5bSession = boost::make_shared<SOCKS5BytestreamClientSession>(connection, lastCandidate.hostPort, SOCKS5BytestreamRegistry::getHostname(transportSID, requester, target), timerFactory); + + // bind onReady to this method + s5bSession->onSessionReady.connect(boost::bind(&DefaultRemoteJingleTransportCandidateSelector::tryNextCandidate, this, _1)); + + std::string candidateType; + if (lastCandidate.type == JingleS5BTransportPayload::Candidate::DirectType) { + candidateType = "direct"; + } else if (lastCandidate.type == JingleS5BTransportPayload::Candidate::AssistedType) { + candidateType = "assisted"; + } else if (lastCandidate.type == JingleS5BTransportPayload::Candidate::ProxyType) { + candidateType = "proxy"; + } + + // initiate connect + SWIFT_LOG(debug) << "try to connect to candidate of type " << candidateType << " : " << lastCandidate.hostPort.toString() << std::endl; + s5bSession->start(); + + // that's it. we're gonna be called back + candidates.pop(); + } else { + s5bSession.reset(); + candidates.pop(); + tryNextCandidate(true); + } + } + } else { + // we have a working connection, hooray + JingleS5BTransportPayload::ref success = boost::make_shared<JingleS5BTransportPayload>(); + success->setCandidateUsed(lastCandidate.cid); + success->setSessionID(transportSID); + onRemoteTransportCandidateSelectFinished(success); + } +} + +void DefaultRemoteJingleTransportCandidateSelector::setMinimumPriority(int priority) { + SWIFT_LOG(debug) << "priority: " << priority << std::endl; +} + +void DefaultRemoteJingleTransportCandidateSelector::setRequesterTargtet(const JID& requester, const JID& target) { + this->requester = requester; + this->target = target; +} + +SOCKS5BytestreamClientSession::ref DefaultRemoteJingleTransportCandidateSelector::getS5BSession() { + return s5bSession; +} + +bool DefaultRemoteJingleTransportCandidateSelector::isActualCandidate(JingleTransportPayload::ref /* transportPayload */) { + return false; +} + +int DefaultRemoteJingleTransportCandidateSelector::getPriority(JingleTransportPayload::ref /* transportPayload */) { + return 0; +} + +JingleTransport::ref DefaultRemoteJingleTransportCandidateSelector::selectTransport(JingleTransportPayload::ref /* transportPayload */) { + return JingleTransport::ref(); +} + +} diff --git a/Swiften/FileTransfer/DefaultRemoteJingleTransportCandidateSelector.h b/Swiften/FileTransfer/DefaultRemoteJingleTransportCandidateSelector.h new file mode 100644 index 0000000..255acd9 --- /dev/null +++ b/Swiften/FileTransfer/DefaultRemoteJingleTransportCandidateSelector.h @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <queue> +#include <vector> + +#include <boost/shared_ptr.hpp> + +#include <Swiften/JID/JID.h> +#include <Swiften/Network/Connection.h> +#include <Swiften/FileTransfer/SOCKS5BytestreamClientSession.h> +#include <Swiften/FileTransfer/RemoteJingleTransportCandidateSelector.h> +#include <Swiften/Elements/JingleS5BTransportPayload.h> + + +namespace Swift { + +class ConnectionFactory; +class TimerFactory; + +class DefaultRemoteJingleTransportCandidateSelector : public RemoteJingleTransportCandidateSelector { +public: + DefaultRemoteJingleTransportCandidateSelector(ConnectionFactory*, TimerFactory*); + virtual ~DefaultRemoteJingleTransportCandidateSelector(); + + virtual void addRemoteTransportCandidates(JingleTransportPayload::ref); + virtual void selectCandidate(); + virtual void setMinimumPriority(int); + void setRequesterTargtet(const JID& requester, const JID& target); + virtual SOCKS5BytestreamClientSession::ref getS5BSession(); + + virtual bool isActualCandidate(JingleTransportPayload::ref); + virtual int getPriority(JingleTransportPayload::ref); + virtual JingleTransport::ref selectTransport(JingleTransportPayload::ref); + +private: + void tryNextCandidate(bool error); + +private: + ConnectionFactory* connectionFactory; + TimerFactory* timerFactory; + + std::priority_queue<JingleS5BTransportPayload::Candidate, std::vector<JingleS5BTransportPayload::Candidate>, JingleS5BTransportPayload::CompareCandidate> candidates; + + std::string transportSID; + boost::shared_ptr<Connection> connection; + boost::shared_ptr<SOCKS5BytestreamClientSession> s5bSession; + JingleS5BTransportPayload::Candidate lastCandidate; + JID requester; + JID target; +}; + +} diff --git a/Swiften/FileTransfer/DefaultRemoteJingleTransportCandidateSelectorFactory.cpp b/Swiften/FileTransfer/DefaultRemoteJingleTransportCandidateSelectorFactory.cpp new file mode 100644 index 0000000..8ebbf46 --- /dev/null +++ b/Swiften/FileTransfer/DefaultRemoteJingleTransportCandidateSelectorFactory.cpp @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include "DefaultRemoteJingleTransportCandidateSelectorFactory.h" + +#include <Swiften/FileTransfer/DefaultRemoteJingleTransportCandidateSelector.h> + +#include <Swiften/Base/Log.h> + +namespace Swift { + +DefaultRemoteJingleTransportCandidateSelectorFactory::DefaultRemoteJingleTransportCandidateSelectorFactory(ConnectionFactory* connectionFactory, TimerFactory* timerFactory) : connectionFactory(connectionFactory), timerFactory(timerFactory) { +} + +DefaultRemoteJingleTransportCandidateSelectorFactory::~DefaultRemoteJingleTransportCandidateSelectorFactory() { +} + +RemoteJingleTransportCandidateSelector* DefaultRemoteJingleTransportCandidateSelectorFactory::createCandidateSelector() { + return new DefaultRemoteJingleTransportCandidateSelector(connectionFactory, timerFactory); +} + +} diff --git a/Swiften/FileTransfer/DefaultRemoteJingleTransportCandidateSelectorFactory.h b/Swiften/FileTransfer/DefaultRemoteJingleTransportCandidateSelectorFactory.h new file mode 100644 index 0000000..ca29e1f --- /dev/null +++ b/Swiften/FileTransfer/DefaultRemoteJingleTransportCandidateSelectorFactory.h @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <Swiften/FileTransfer/RemoteJingleTransportCandidateSelectorFactory.h> + +namespace Swift { + +class ConnectionFactory; +class TimerFactory; + +class DefaultRemoteJingleTransportCandidateSelectorFactory : public RemoteJingleTransportCandidateSelectorFactory { +public: + DefaultRemoteJingleTransportCandidateSelectorFactory(ConnectionFactory*, TimerFactory*); + virtual ~DefaultRemoteJingleTransportCandidateSelectorFactory(); + + RemoteJingleTransportCandidateSelector* createCandidateSelector(); + +private: + ConnectionFactory* connectionFactory; + TimerFactory* timerFactory; +}; + +} diff --git a/Swiften/FileTransfer/FileReadBytestream.cpp b/Swiften/FileTransfer/FileReadBytestream.cpp index c08747b..a8946a0 100644 --- a/Swiften/FileTransfer/FileReadBytestream.cpp +++ b/Swiften/FileTransfer/FileReadBytestream.cpp @@ -6,8 +6,10 @@ #include <boost/filesystem/fstream.hpp> #include <cassert> +#include <boost/smart_ptr/make_shared.hpp> -#include "Swiften/FileTransfer/FileReadBytestream.h" +#include <Swiften/FileTransfer/FileReadBytestream.h> +#include <Swiften/Base/ByteArray.h> namespace Swift { @@ -21,15 +23,16 @@ FileReadBytestream::~FileReadBytestream() { } } -ByteArray FileReadBytestream::read(size_t size) { +boost::shared_ptr<ByteArray> FileReadBytestream::read(size_t size) { if (!stream) { stream = new boost::filesystem::ifstream(file, std::ios_base::in|std::ios_base::binary); } - ByteArray result; - result.resize(size); + boost::shared_ptr<ByteArray> result = boost::make_shared<ByteArray>(); + result->resize(size); assert(stream->good()); - stream->read(reinterpret_cast<char*>(result.getData()), size); - result.resize(stream->gcount()); + stream->read(reinterpret_cast<char*>(vecptr(*result)), size); + result->resize(stream->gcount()); + onRead(*result); return result; } diff --git a/Swiften/FileTransfer/FileReadBytestream.h b/Swiften/FileTransfer/FileReadBytestream.h index 055e194..e9db2a4 100644 --- a/Swiften/FileTransfer/FileReadBytestream.h +++ b/Swiften/FileTransfer/FileReadBytestream.h @@ -6,10 +6,10 @@ #pragma once -#include <boost/filesystem.hpp> +#include <boost/filesystem/path.hpp> #include <boost/filesystem/fstream.hpp> -#include "Swiften/FileTransfer/ReadBytestream.h" +#include <Swiften/FileTransfer/ReadBytestream.h> namespace Swift { class FileReadBytestream : public ReadBytestream { @@ -17,7 +17,7 @@ namespace Swift { FileReadBytestream(const boost::filesystem::path& file); ~FileReadBytestream(); - virtual ByteArray read(size_t size) ; + virtual boost::shared_ptr< std::vector<unsigned char> > read(size_t size); virtual bool isFinished() const; private: diff --git a/Swiften/FileTransfer/FileTransfer.h b/Swiften/FileTransfer/FileTransfer.h new file mode 100644 index 0000000..336c51c --- /dev/null +++ b/Swiften/FileTransfer/FileTransfer.h @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <boost/cstdint.hpp> +#include <boost/optional.hpp> +#include <boost/shared_ptr.hpp> + +#include <Swiften/Base/boost_bsignals.h> +#include <Swiften/FileTransfer/FileTransferError.h> + +namespace Swift { + +class FileTransfer { +public: + struct State { + enum FTState { + Canceled, + Failed, + Finished, + Negotiating, + Transferring, + WaitingForStart, + WaitingForAccept, + }; + + FTState state; + std::string message; + + State(FTState state) : state(state), message("") {} + State(FTState state, std::string message) : state(state), message(message) {} + }; + +public: + typedef boost::shared_ptr<FileTransfer> ref; + +public: + boost::uintmax_t fileSizeInBytes; + std::string filename; + std::string algo; + std::string hash; + +public: + virtual void cancel() = 0; + +public: + boost::signal<void (int /* proccessedBytes */)> onProcessedBytes; + boost::signal<void (State)> onStateChange; + boost::signal<void (boost::optional<FileTransferError>)> onFinished; + +public: + virtual ~FileTransfer() {} +}; + +} diff --git a/Swiften/FileTransfer/FileTransferManager.cpp b/Swiften/FileTransfer/FileTransferManager.cpp new file mode 100644 index 0000000..69be852 --- /dev/null +++ b/Swiften/FileTransfer/FileTransferManager.cpp @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include <Swiften/FileTransfer/FileTransferManager.h> + +namespace Swift { + +FileTransferManager::~FileTransferManager() { +} + +} diff --git a/Swiften/FileTransfer/FileTransferManager.h b/Swiften/FileTransfer/FileTransferManager.h new file mode 100644 index 0000000..d59f029 --- /dev/null +++ b/Swiften/FileTransfer/FileTransferManager.h @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <string> +#include <boost/filesystem.hpp> +#include <boost/date_time/posix_time/posix_time.hpp> + +#include <Swiften/Base/boost_bsignals.h> +#include <Swiften/JID/JID.h> +#include <Swiften/FileTransfer/OutgoingFileTransfer.h> +#include <Swiften/FileTransfer/IncomingFileTransfer.h> + +namespace Swift { + class ReadBytestream; + class S5BProxyRequest; + + class FileTransferManager { + public: + virtual ~FileTransferManager(); + + virtual void startListeningOnPort(int port) = 0; + + virtual OutgoingFileTransfer::ref createOutgoingFileTransfer(const JID& to, const boost::filesystem::path& filepath, const std::string& description, boost::shared_ptr<ReadBytestream> bytestream) = 0; + virtual OutgoingFileTransfer::ref createOutgoingFileTransfer(const JID& to, const std::string& filename, const std::string& description, const boost::uintmax_t sizeInBytes, const boost::posix_time::ptime& lastModified, boost::shared_ptr<ReadBytestream> bytestream) = 0; + + boost::signal<void (IncomingFileTransfer::ref)> onIncomingFileTransfer; + }; +} diff --git a/Swiften/FileTransfer/FileTransferManagerImpl.cpp b/Swiften/FileTransfer/FileTransferManagerImpl.cpp new file mode 100644 index 0000000..83320b2 --- /dev/null +++ b/Swiften/FileTransfer/FileTransferManagerImpl.cpp @@ -0,0 +1,133 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include <Swiften/FileTransfer/FileTransferManagerImpl.h> + +#include <boost/bind.hpp> +#include <boost/cstdint.hpp> + +#include <Swiften/Base/foreach.h> +#include "Swiften/Disco/EntityCapsProvider.h" +#include <Swiften/JID/JID.h> +#include <Swiften/Elements/StreamInitiationFileInfo.h> +#include <Swiften/FileTransfer/SOCKS5BytestreamProxyFinder.h> +#include <Swiften/FileTransfer/ConnectivityManager.h> +#include <Swiften/FileTransfer/OutgoingFileTransferManager.h> +#include <Swiften/FileTransfer/IncomingFileTransferManager.h> +#include <Swiften/FileTransfer/DefaultLocalJingleTransportCandidateGeneratorFactory.h> +#include <Swiften/FileTransfer/DefaultRemoteJingleTransportCandidateSelectorFactory.h> +#include <Swiften/FileTransfer/SOCKS5BytestreamRegistry.h> +#include <Swiften/FileTransfer/SOCKS5BytestreamServer.h> +#include <Swiften/FileTransfer/SOCKS5BytestreamProxy.h> +#include <Swiften/Presence/PresenceOracle.h> +#include <Swiften/Elements/Presence.h> +#include <Swiften/Network/ConnectionFactory.h> +#include <Swiften/Network/ConnectionServerFactory.h> +#include <Swiften/Network/HostAddress.h> +#include <Swiften/Network/NATTraverser.h> + +namespace Swift { + +FileTransferManagerImpl::FileTransferManagerImpl(const JID& ownFullJID, JingleSessionManager* jingleSessionManager, IQRouter* router, EntityCapsProvider* capsProvider, PresenceOracle* presOracle, ConnectionFactory* connectionFactory, ConnectionServerFactory* connectionServerFactory, TimerFactory* timerFactory, NATTraverser* natTraverser) : ownJID(ownFullJID), jingleSM(jingleSessionManager), iqRouter(router), capsProvider(capsProvider), presenceOracle(presOracle), timerFactory(timerFactory), connectionFactory(connectionFactory), connectionServerFactory(connectionServerFactory), natTraverser(natTraverser), bytestreamServer(NULL), s5bProxyFinder(NULL) { + assert(!ownFullJID.isBare()); + + connectivityManager = new ConnectivityManager(natTraverser); + bytestreamRegistry = new SOCKS5BytestreamRegistry(); + bytestreamProxy = new SOCKS5BytestreamProxy(connectionFactory, timerFactory); + + localCandidateGeneratorFactory = new DefaultLocalJingleTransportCandidateGeneratorFactory(connectivityManager, bytestreamRegistry, bytestreamProxy, ownFullJID); + remoteCandidateSelectorFactory = new DefaultRemoteJingleTransportCandidateSelectorFactory(connectionFactory, timerFactory); + outgoingFTManager = new OutgoingFileTransferManager(jingleSM, iqRouter, capsProvider, remoteCandidateSelectorFactory, localCandidateGeneratorFactory, bytestreamRegistry, bytestreamProxy); + incomingFTManager = new IncomingFileTransferManager(jingleSM, iqRouter, remoteCandidateSelectorFactory, localCandidateGeneratorFactory, bytestreamRegistry, bytestreamProxy, timerFactory); + incomingFTManager->onIncomingFileTransfer.connect(onIncomingFileTransfer); +} + +FileTransferManagerImpl::~FileTransferManagerImpl() { + if (s5bProxyFinder) { + s5bProxyFinder->stop(); + delete s5bProxyFinder; + } + if (bytestreamServer) { + bytestreamServer->stop(); + delete bytestreamServer; + } + delete incomingFTManager; + delete outgoingFTManager; + delete remoteCandidateSelectorFactory; + delete localCandidateGeneratorFactory; + delete connectivityManager; +} + +void FileTransferManagerImpl::startListeningOnPort(int port) { + // TODO: create a server for each interface we're on + SWIFT_LOG(debug) << "Start listening on port " << port << " and hope it's not in use." << std::endl; + boost::shared_ptr<ConnectionServer> server = connectionServerFactory->createConnectionServer(HostAddress("0.0.0.0"), port); + server->start(); + bytestreamServer = new SOCKS5BytestreamServer(server, bytestreamRegistry); + bytestreamServer->start(); + connectivityManager->addListeningPort(port); + + s5bProxyFinder = new SOCKS5BytestreamProxyFinder(ownJID.getDomain(), iqRouter); + s5bProxyFinder->onProxyFound.connect(boost::bind(&FileTransferManagerImpl::addS5BProxy, this, _1)); + s5bProxyFinder->start(); +} + +void FileTransferManagerImpl::addS5BProxy(S5BProxyRequest::ref proxy) { + bytestreamProxy->addS5BProxy(proxy); +} + +boost::optional<JID> FileTransferManagerImpl::highestPriorityJIDSupportingFileTransfer(const JID& bareJID) { + JID fullReceipientJID; + int priority = INT_MIN; + + //getAllPresence(bareJID) gives you all presences for the bare JID (i.e. all resources) Remko Tronçon @ 11:11 + std::vector<Presence::ref> presences = presenceOracle->getAllPresence(bareJID); + + //iterate over them + foreach(Presence::ref pres, presences) { + if (pres->getPriority() > priority) { + // look up caps from the jid + DiscoInfo::ref info = capsProvider->getCaps(pres->getFrom()); + if (info && info->hasFeature(DiscoInfo::JingleFeature) && info->hasFeature(DiscoInfo::JingleFTFeature) && (info->hasFeature(DiscoInfo::JingleTransportsIBBFeature) || info->hasFeature(DiscoInfo::JingleTransportsS5BFeature))) { + + priority = pres->getPriority(); + fullReceipientJID = pres->getFrom(); + } + } + } + + return fullReceipientJID.isValid() ? boost::optional<JID>(fullReceipientJID) : boost::optional<JID>(); +} + +OutgoingFileTransfer::ref FileTransferManagerImpl::createOutgoingFileTransfer(const JID& to, const boost::filesystem::path& filepath, const std::string& description, boost::shared_ptr<ReadBytestream> bytestream) { + std::string filename = filepath.filename(); + boost::uintmax_t sizeInBytes = boost::filesystem::file_size(filepath); + boost::posix_time::ptime lastModified = boost::posix_time::from_time_t(boost::filesystem::last_write_time(filepath)); + return createOutgoingFileTransfer(to, filename, description, sizeInBytes, lastModified, bytestream); +} + +OutgoingFileTransfer::ref FileTransferManagerImpl::createOutgoingFileTransfer(const JID& to, const std::string& filename, const std::string& description, const boost::uintmax_t sizeInBytes, const boost::posix_time::ptime& lastModified, boost::shared_ptr<ReadBytestream> bytestream) { + StreamInitiationFileInfo fileInfo; + fileInfo.setDate(lastModified); + fileInfo.setSize(sizeInBytes); + fileInfo.setName(filename); + fileInfo.setDescription(description); + + JID receipient = to; + + if(receipient.isBare()) { + boost::optional<JID> fullJID = highestPriorityJIDSupportingFileTransfer(receipient); + if (fullJID.is_initialized()) { + receipient = fullJID.get(); + } else { + return OutgoingFileTransfer::ref(); + } + } + + return outgoingFTManager->createOutgoingFileTransfer(ownJID, receipient, bytestream, fileInfo); +} + +} diff --git a/Swiften/FileTransfer/FileTransferManagerImpl.h b/Swiften/FileTransfer/FileTransferManagerImpl.h new file mode 100644 index 0000000..248b437 --- /dev/null +++ b/Swiften/FileTransfer/FileTransferManagerImpl.h @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <vector> +#include <string> + +#include <boost/filesystem.hpp> +#include <boost/date_time/posix_time/posix_time.hpp> +#include <boost/optional.hpp> + +#include <Swiften/FileTransfer/FileTransferManager.h> +#include <Swiften/Base/boost_bsignals.h> +#include <Swiften/JID/JID.h> +#include <Swiften/FileTransfer/OutgoingFileTransfer.h> +#include <Swiften/FileTransfer/IncomingFileTransfer.h> +#include <Swiften/Elements/S5BProxyRequest.h> + +namespace Swift { + class Client; + class ConnectionFactory; + class ConnectionServerFactory; + class ConnectivityManager; + class EntityCapsProvider; + class IQRouter; + class IncomingFileTransferManager; + class JingleSessionManager; + class LocalJingleTransportCandidateGeneratorFactory; + class OutgoingFileTransferManager; + class NATTraverser; + class PresenceOracle; + class ReadBytestream; + class RemoteJingleTransportCandidateSelectorFactory; + class SOCKS5BytestreamRegistry; + class SOCKS5BytestreamServer; + class SOCKS5BytestreamProxy; + class TimerFactory; + class SOCKS5BytestreamProxyFinder; + + class FileTransferManagerImpl : public FileTransferManager { + public: + FileTransferManagerImpl(const JID& ownFullJID, JingleSessionManager* jingleSessionManager, IQRouter* router, EntityCapsProvider* capsProvider, PresenceOracle* presOracle, ConnectionFactory* connectionFactory, ConnectionServerFactory* connectionServerFactory, TimerFactory* timerFactory, NATTraverser* natTraverser); + ~FileTransferManagerImpl(); + + void startListeningOnPort(int port); + void addS5BProxy(S5BProxyRequest::ref proxy); + + OutgoingFileTransfer::ref createOutgoingFileTransfer(const JID& to, const boost::filesystem::path& filepath, const std::string& description, boost::shared_ptr<ReadBytestream> bytestream); + OutgoingFileTransfer::ref createOutgoingFileTransfer(const JID& to, const std::string& filename, const std::string& description, const boost::uintmax_t sizeInBytes, const boost::posix_time::ptime& lastModified, boost::shared_ptr<ReadBytestream> bytestream); + + private: + boost::optional<JID> highestPriorityJIDSupportingFileTransfer(const JID& bareJID); + + private: + JID ownJID; + + OutgoingFileTransferManager* outgoingFTManager; + IncomingFileTransferManager* incomingFTManager; + RemoteJingleTransportCandidateSelectorFactory* remoteCandidateSelectorFactory; + LocalJingleTransportCandidateGeneratorFactory* localCandidateGeneratorFactory; + JingleSessionManager* jingleSM; + IQRouter* iqRouter; + EntityCapsProvider* capsProvider; + PresenceOracle* presenceOracle; + + TimerFactory* timerFactory; + ConnectionFactory* connectionFactory; + ConnectionServerFactory* connectionServerFactory; + NATTraverser* natTraverser; + SOCKS5BytestreamRegistry* bytestreamRegistry; + SOCKS5BytestreamServer* bytestreamServer; + SOCKS5BytestreamProxy* bytestreamProxy; + ConnectivityManager* connectivityManager; + SOCKS5BytestreamProxyFinder* s5bProxyFinder; + }; + +} diff --git a/Swiften/FileTransfer/FileWriteBytestream.cpp b/Swiften/FileTransfer/FileWriteBytestream.cpp index 4d29bd1..6a22c6a 100644 --- a/Swiften/FileTransfer/FileWriteBytestream.cpp +++ b/Swiften/FileTransfer/FileWriteBytestream.cpp @@ -7,7 +7,7 @@ #include <boost/filesystem/fstream.hpp> #include <cassert> -#include "Swiften/FileTransfer/FileWriteBytestream.h" +#include <Swiften/FileTransfer/FileWriteBytestream.h> namespace Swift { @@ -21,12 +21,20 @@ FileWriteBytestream::~FileWriteBytestream() { } } -void FileWriteBytestream::write(const ByteArray& data) { +void FileWriteBytestream::write(const std::vector<unsigned char>& data) { if (!stream) { stream = new boost::filesystem::ofstream(file, std::ios_base::out|std::ios_base::binary); } assert(stream->good()); - stream->write(reinterpret_cast<const char*>(data.getData()), data.getSize()); + stream->write(reinterpret_cast<const char*>(&data[0]), data.size()); + onWrite(data); +} + +void FileWriteBytestream::close() { + if (stream) { + stream->close(); + stream = NULL; + } } } diff --git a/Swiften/FileTransfer/FileWriteBytestream.h b/Swiften/FileTransfer/FileWriteBytestream.h index c6f7b39..82c4a65 100644 --- a/Swiften/FileTransfer/FileWriteBytestream.h +++ b/Swiften/FileTransfer/FileWriteBytestream.h @@ -6,10 +6,10 @@ #pragma once -#include <boost/filesystem.hpp> +#include <boost/filesystem/path.hpp> #include <boost/filesystem/fstream.hpp> -#include "Swiften/FileTransfer/WriteBytestream.h" +#include <Swiften/FileTransfer/WriteBytestream.h> namespace Swift { class FileWriteBytestream : public WriteBytestream { @@ -17,7 +17,8 @@ namespace Swift { FileWriteBytestream(const boost::filesystem::path& file); ~FileWriteBytestream(); - virtual void write(const ByteArray&); + virtual void write(const std::vector<unsigned char>&); + void close(); private: boost::filesystem::path file; diff --git a/Swiften/FileTransfer/IBBReceiveSession.cpp b/Swiften/FileTransfer/IBBReceiveSession.cpp index 5c90757..1a2bb3a 100644 --- a/Swiften/FileTransfer/IBBReceiveSession.cpp +++ b/Swiften/FileTransfer/IBBReceiveSession.cpp @@ -4,31 +4,107 @@ * See Documentation/Licenses/GPLv3.txt for more information. */ -#include "Swiften/FileTransfer/IBBReceiveSession.h" +#include <Swiften/FileTransfer/IBBReceiveSession.h> #include <boost/bind.hpp> -#include "Swiften/Queries/IQRouter.h" -#include "Swiften/FileTransfer/IBBRequest.h" -#include "Swiften/FileTransfer/BytestreamException.h" +#include <Swiften/Base/Log.h> +#include <Swiften/Queries/IQRouter.h> +#include <Swiften/FileTransfer/IBBRequest.h> +#include <Swiften/FileTransfer/BytestreamException.h> +#include <Swiften/Queries/SetResponder.h> + +#include <cassert> namespace Swift { -IBBReceiveSession::IBBReceiveSession(const std::string& id, const JID& from, size_t size, WriteBytestream::ref bytestream, IQRouter* router) : SetResponder<IBB>(router), id(id), from(from), size(size), bytestream(bytestream), router(router), sequenceNumber(0), active(false), receivedSize(0) { +class IBBReceiveSession::IBBResponder : public SetResponder<IBB> { + public: + IBBResponder(IBBReceiveSession* session, IQRouter* router) : SetResponder<IBB>(router), session(session), sequenceNumber(0), receivedSize(0) { + } + + virtual bool handleSetRequest(const JID& from, const JID&, const std::string& id, IBB::ref ibb) { + if (from == session->from && ibb->getStreamID() == session->id) { + if (ibb->getAction() == IBB::Data) { + if (sequenceNumber == ibb->getSequenceNumber()) { + session->onDataReceived(ibb->getData()); + receivedSize += ibb->getData().size(); + sequenceNumber++; + sendResponse(from, id, IBB::ref()); + if (receivedSize >= session->size) { + if (receivedSize > session->size) { + std::cerr << "Warning: Received more data than expected" << std::endl; + } + session->finish(boost::optional<FileTransferError>()); + } + } + else { + SWIFT_LOG(warning) << "Received data out of order" << std::endl; + sendError(from, id, ErrorPayload::NotAcceptable, ErrorPayload::Cancel); + session->finish(FileTransferError(FileTransferError::ClosedError)); + } + } + else if (ibb->getAction() == IBB::Open) { + SWIFT_LOG(debug) << "IBB open received" << std::endl; + sendResponse(from, id, IBB::ref()); + } + else if (ibb->getAction() == IBB::Close) { + SWIFT_LOG(debug) << "IBB close received" << std::endl; + sendResponse(from, id, IBB::ref()); + session->finish(FileTransferError(FileTransferError::ClosedError)); + } + return true; + } + SWIFT_LOG(debug) << "wrong from/sessionID: " << from << " == " << session->from << " / " <<ibb->getStreamID() << " == " << session->id << std::endl; + return false; + } + + private: + IBBReceiveSession* session; + int sequenceNumber; + size_t receivedSize; +}; + + +IBBReceiveSession::IBBReceiveSession( + const std::string& id, + const JID& from, + const JID& to, + size_t size, + IQRouter* router) : + id(id), + from(from), + to(to), + size(size), + router(router), + active(false) { + assert(!id.empty()); + assert(from.isValid()); + responder = new IBBResponder(this, router); } IBBReceiveSession::~IBBReceiveSession() { + if (active) { + SWIFT_LOG(warning) << "Session still active" << std::endl; + } + delete responder; } void IBBReceiveSession::start() { + SWIFT_LOG(debug) << "receive session started" << std::endl; active = true; + responder->start(); } void IBBReceiveSession::stop() { - if (active && router->isAvailable()) { - IBBRequest::create(from, IBB::createIBBClose(id), router)->send(); + SWIFT_LOG(debug) << "receive session stopped" << std::endl; + responder->stop(); + if (active) { + if (router->isAvailable()) { + IBBRequest::create(to, from, IBB::createIBBClose(id), router)->send(); + } + finish(boost::optional<FileTransferError>()); } - finish(boost::optional<FileTransferError>()); } void IBBReceiveSession::finish(boost::optional<FileTransferError> error) { @@ -36,34 +112,4 @@ void IBBReceiveSession::finish(boost::optional<FileTransferError> error) { onFinished(error); } -bool IBBReceiveSession::handleSetRequest(const JID& from, const JID&, const std::string& id, IBB::ref ibb) { - if (from == this->from && ibb->getStreamID() == id) { - if (ibb->getAction() == IBB::Data) { - if (sequenceNumber == ibb->getSequenceNumber()) { - bytestream->write(ibb->getData()); - receivedSize += ibb->getData().getSize(); - if (receivedSize >= size) { - if (receivedSize > size) { - std::cerr << "Warning: Received more data than expected" << std::endl; - } - finish(boost::optional<FileTransferError>()); - } - } - else { - sendError(from, id, ErrorPayload::NotAcceptable, ErrorPayload::Cancel); - finish(FileTransferError(FileTransferError::ClosedError)); - } - } - else if (ibb->getAction() == IBB::Open) { - sendResponse(from, id, IBB::ref()); - } - else if (ibb->getAction() == IBB::Close) { - sendResponse(from, id, IBB::ref()); - finish(FileTransferError(FileTransferError::ClosedError)); - } - return true; - } - return false; -} - } diff --git a/Swiften/FileTransfer/IBBReceiveSession.h b/Swiften/FileTransfer/IBBReceiveSession.h index 6d936de..d1c47bf 100644 --- a/Swiften/FileTransfer/IBBReceiveSession.h +++ b/Swiften/FileTransfer/IBBReceiveSession.h @@ -7,27 +7,39 @@ #pragma once #include <boost/shared_ptr.hpp> -#include <boost/optional.hpp> +#include <boost/optional/optional_fwd.hpp> -#include "Swiften/Base/boost_bsignals.h" -#include "Swiften/FileTransfer/WriteBytestream.h" -#include "Swiften/JID/JID.h" -#include "Swiften/Elements/IBB.h" -#include "Swiften/Elements/ErrorPayload.h" -#include "Swiften/FileTransfer/FileTransferError.h" -#include "Swiften/Queries/SetResponder.h" +#include <Swiften/Base/boost_bsignals.h> +#include <Swiften/FileTransfer/WriteBytestream.h> +#include <Swiften/JID/JID.h> +#include <Swiften/Elements/IBB.h> +#include <Swiften/FileTransfer/FileTransferError.h> namespace Swift { class IQRouter; - class IBBReceiveSession : public SetResponder<IBB> { + class IBBReceiveSession { public: - IBBReceiveSession(const std::string& id, const JID& from, size_t size, WriteBytestream::ref bytestream, IQRouter* router); + IBBReceiveSession( + const std::string& id, + const JID& from, + const JID& to, + size_t size, + IQRouter* router); ~IBBReceiveSession(); void start(); void stop(); + const JID& getSender() const { + return from; + } + + const JID& getReceiver() const { + return to; + } + + boost::signal<void (const std::vector<unsigned char>&)> onDataReceived; boost::signal<void (boost::optional<FileTransferError>)> onFinished; private: @@ -35,13 +47,15 @@ namespace Swift { void finish(boost::optional<FileTransferError>); private: + class IBBResponder; + friend class IBBResponder; + std::string id; JID from; + JID to; size_t size; - WriteBytestream::ref bytestream; IQRouter* router; - int sequenceNumber; + IBBResponder* responder; bool active; - size_t receivedSize; }; } diff --git a/Swiften/FileTransfer/IBBRequest.h b/Swiften/FileTransfer/IBBRequest.h index f104277..58be173 100644 --- a/Swiften/FileTransfer/IBBRequest.h +++ b/Swiften/FileTransfer/IBBRequest.h @@ -6,8 +6,8 @@ #pragma once -#include "Swiften/Queries/GenericRequest.h" -#include "Swiften/Elements/IBB.h" +#include <Swiften/Queries/GenericRequest.h> +#include <Swiften/Elements/IBB.h> namespace Swift { @@ -15,12 +15,12 @@ namespace Swift { public: typedef boost::shared_ptr<IBBRequest> ref; - static ref create(const JID& jid, boost::shared_ptr<IBB> payload, IQRouter* router) { - return ref(new IBBRequest(jid, payload, router)); + static ref create(const JID& from, const JID& to, boost::shared_ptr<IBB> payload, IQRouter* router) { + return ref(new IBBRequest(from, to, payload, router)); } private: - IBBRequest(const JID& jid, boost::shared_ptr<IBB> payload, IQRouter* router) : GenericRequest<IBB>(IQ::Set, jid, payload, router) { + IBBRequest(const JID& from, const JID& to, boost::shared_ptr<IBB> payload, IQRouter* router) : GenericRequest<IBB>(IQ::Set, from, to, payload, router) { } }; } diff --git a/Swiften/FileTransfer/IBBSendSession.cpp b/Swiften/FileTransfer/IBBSendSession.cpp index 0fb47d3..c24cc0a 100644 --- a/Swiften/FileTransfer/IBBSendSession.cpp +++ b/Swiften/FileTransfer/IBBSendSession.cpp @@ -4,24 +4,27 @@ * See Documentation/Licenses/GPLv3.txt for more information. */ -#include "Swiften/FileTransfer/IBBSendSession.h" +#include <Swiften/FileTransfer/IBBSendSession.h> #include <boost/bind.hpp> -#include "Swiften/Queries/IQRouter.h" -#include "Swiften/FileTransfer/IBBRequest.h" -#include "Swiften/FileTransfer/BytestreamException.h" +#include <Swiften/Base/ByteArray.h> +#include <Swiften/Queries/IQRouter.h> +#include <Swiften/FileTransfer/IBBRequest.h> +#include <Swiften/FileTransfer/BytestreamException.h> namespace Swift { -IBBSendSession::IBBSendSession(const std::string& id, const JID& to, boost::shared_ptr<ReadBytestream> bytestream, IQRouter* router) : id(id), to(to), bytestream(bytestream), router(router), blockSize(4096), sequenceNumber(0), active(false) { +IBBSendSession::IBBSendSession(const std::string& id, const JID& from, const JID& to, boost::shared_ptr<ReadBytestream> bytestream, IQRouter* router) : id(id), from(from), to(to), bytestream(bytestream), router(router), blockSize(4096), sequenceNumber(0), active(false), waitingForData(false) { + bytestream->onDataAvailable.connect(boost::bind(&IBBSendSession::handleDataAvailable, this)); } IBBSendSession::~IBBSendSession() { + bytestream->onDataAvailable.disconnect(boost::bind(&IBBSendSession::handleDataAvailable, this)); } void IBBSendSession::start() { - IBBRequest::ref request = IBBRequest::create(to, IBB::createIBBOpen(id, blockSize), router); + IBBRequest::ref request = IBBRequest::create(from, to, IBB::createIBBOpen(id, blockSize), router); request->onResponse.connect(boost::bind(&IBBSendSession::handleIBBResponse, this, _1, _2)); active = true; request->send(); @@ -29,24 +32,15 @@ void IBBSendSession::start() { void IBBSendSession::stop() { if (active && router->isAvailable()) { - IBBRequest::create(to, IBB::createIBBClose(id), router)->send(); + IBBRequest::create(from, to, IBB::createIBBClose(id), router)->send(); } finish(boost::optional<FileTransferError>()); } void IBBSendSession::handleIBBResponse(IBB::ref, ErrorPayload::ref error) { - if (!error) { + if (!error && active) { if (!bytestream->isFinished()) { - try { - ByteArray data = bytestream->read(blockSize); - IBBRequest::ref request = IBBRequest::create(to, IBB::createIBBData(id, sequenceNumber, data), router); - sequenceNumber++; - request->onResponse.connect(boost::bind(&IBBSendSession::handleIBBResponse, this, _1, _2)); - request->send(); - } - catch (const BytestreamException& e) { - finish(FileTransferError(FileTransferError::ReadError)); - } + sendMoreData(); } else { finish(boost::optional<FileTransferError>()); @@ -57,9 +51,35 @@ void IBBSendSession::handleIBBResponse(IBB::ref, ErrorPayload::ref error) { } } +void IBBSendSession::sendMoreData() { + try { + boost::shared_ptr<ByteArray> data = bytestream->read(blockSize); + if (!data->empty()) { + waitingForData = false; + IBBRequest::ref request = IBBRequest::create(from, to, IBB::createIBBData(id, sequenceNumber, *data), router); + sequenceNumber++; + request->onResponse.connect(boost::bind(&IBBSendSession::handleIBBResponse, this, _1, _2)); + request->send(); + onBytesSent(data->size()); + } + else { + waitingForData = true; + } + } + catch (const BytestreamException&) { + finish(FileTransferError(FileTransferError::ReadError)); + } +} + void IBBSendSession::finish(boost::optional<FileTransferError> error) { active = false; onFinished(error); } +void IBBSendSession::handleDataAvailable() { + if (waitingForData) { + sendMoreData(); + } +} + } diff --git a/Swiften/FileTransfer/IBBSendSession.h b/Swiften/FileTransfer/IBBSendSession.h index bef7bec..abd217b 100644 --- a/Swiften/FileTransfer/IBBSendSession.h +++ b/Swiften/FileTransfer/IBBSendSession.h @@ -9,41 +9,54 @@ #include <boost/shared_ptr.hpp> #include <boost/optional.hpp> -#include "Swiften/Base/boost_bsignals.h" -#include "Swiften/FileTransfer/ReadBytestream.h" -#include "Swiften/JID/JID.h" -#include "Swiften/Elements/IBB.h" -#include "Swiften/Elements/ErrorPayload.h" -#include "Swiften/FileTransfer/FileTransferError.h" +#include <Swiften/Base/boost_bsignals.h> +#include <Swiften/FileTransfer/ReadBytestream.h> +#include <Swiften/JID/JID.h> +#include <Swiften/Elements/IBB.h> +#include <Swiften/Elements/ErrorPayload.h> +#include <Swiften/FileTransfer/FileTransferError.h> namespace Swift { class IQRouter; class IBBSendSession { public: - IBBSendSession(const std::string& id, const JID& to, boost::shared_ptr<ReadBytestream> bytestream, IQRouter* router); + IBBSendSession(const std::string& id, const JID& from, const JID& to, boost::shared_ptr<ReadBytestream> bytestream, IQRouter* router); ~IBBSendSession(); void start(); void stop(); + const JID& getSender() const { + return from; + } + + const JID& getReceiver() const { + return to; + } + void setBlockSize(int blockSize) { this->blockSize = blockSize; } boost::signal<void (boost::optional<FileTransferError>)> onFinished; + boost::signal<void (int)> onBytesSent; private: void handleIBBResponse(IBB::ref, ErrorPayload::ref); void finish(boost::optional<FileTransferError>); + void sendMoreData(); + void handleDataAvailable(); private: std::string id; + JID from; JID to; boost::shared_ptr<ReadBytestream> bytestream; IQRouter* router; int blockSize; int sequenceNumber; bool active; + bool waitingForData; }; } diff --git a/Swiften/FileTransfer/IncomingFileTransfer.cpp b/Swiften/FileTransfer/IncomingFileTransfer.cpp index 238ccce..7c97e4d 100644 --- a/Swiften/FileTransfer/IncomingFileTransfer.cpp +++ b/Swiften/FileTransfer/IncomingFileTransfer.cpp @@ -4,20 +4,11 @@ * See Documentation/Licenses/GPLv3.txt for more information. */ -#include "Swiften/FileTransfer/IncomingFileTransfer.h" +#include <Swiften/FileTransfer/IncomingFileTransfer.h> namespace Swift { IncomingFileTransfer::~IncomingFileTransfer() { - -} - -/*void IncomingFileTransfer::accept(WriteBytestream::ref) { - } -void IncomingFileTransfer::stop() { - -}*/ - } diff --git a/Swiften/FileTransfer/IncomingFileTransfer.h b/Swiften/FileTransfer/IncomingFileTransfer.h index 4286ef0..5b53d54 100644 --- a/Swiften/FileTransfer/IncomingFileTransfer.h +++ b/Swiften/FileTransfer/IncomingFileTransfer.h @@ -8,16 +8,21 @@ #include <boost/shared_ptr.hpp> -#include "Swiften/Base/boost_bsignals.h" -#include "Swiften/FileTransfer/WriteBytestream.h" +#include <Swiften/Base/boost_bsignals.h> +#include <Swiften/JID/JID.h> +#include <Swiften/FileTransfer/FileTransfer.h> +#include <Swiften/FileTransfer/WriteBytestream.h> namespace Swift { - class IncomingFileTransfer { + class IncomingFileTransfer : public FileTransfer { public: typedef boost::shared_ptr<IncomingFileTransfer> ref; virtual ~IncomingFileTransfer(); virtual void accept(WriteBytestream::ref) = 0; + + virtual const JID& getSender() const = 0; + virtual const JID& getRecipient() const = 0; }; } diff --git a/Swiften/FileTransfer/IncomingFileTransferManager.cpp b/Swiften/FileTransfer/IncomingFileTransferManager.cpp index 5535840..22e8bf9 100644 --- a/Swiften/FileTransfer/IncomingFileTransferManager.cpp +++ b/Swiften/FileTransfer/IncomingFileTransferManager.cpp @@ -10,13 +10,17 @@ #include <Swiften/Elements/JingleDescription.h> #include <Swiften/Elements/JingleFileTransferDescription.h> -#include <Swiften/Elements/JingleIBBTransport.h> +#include <Swiften/Elements/JingleIBBTransportPayload.h> +#include <Swiften/Elements/JingleS5BTransportPayload.h> #include <Swiften/Jingle/JingleSessionManager.h> +#include <Swiften/Jingle/Jingle.h> #include <Swiften/FileTransfer/IncomingJingleFileTransfer.h> namespace Swift { -IncomingFileTransferManager::IncomingFileTransferManager(JingleSessionManager* jingleSessionManager, IQRouter* router) : jingleSessionManager(jingleSessionManager), router(router) { +IncomingFileTransferManager::IncomingFileTransferManager(JingleSessionManager* jingleSessionManager, IQRouter* router, + RemoteJingleTransportCandidateSelectorFactory* remoteFactory, + LocalJingleTransportCandidateGeneratorFactory* localFactory, SOCKS5BytestreamRegistry* bytestreamRegistry, SOCKS5BytestreamProxy* bytestreamProxy, TimerFactory* timerFactory) : jingleSessionManager(jingleSessionManager), router(router), remoteFactory(remoteFactory), localFactory(localFactory), bytestreamRegistry(bytestreamRegistry), bytestreamProxy(bytestreamProxy), timerFactory(timerFactory) { jingleSessionManager->addIncomingSessionHandler(this); } @@ -24,16 +28,22 @@ IncomingFileTransferManager::~IncomingFileTransferManager() { jingleSessionManager->removeIncomingSessionHandler(this); } -bool IncomingFileTransferManager::handleIncomingJingleSession(IncomingJingleSession::ref session) { - JingleContent::ref content = session->getContentWithDescription<JingleFileTransferDescription>(); - if (content) { - // Check for supported transports - if (content->getTransport<JingleIBBTransport>()) { - IncomingJingleFileTransfer::ref transfer = boost::make_shared<IncomingJingleFileTransfer>(session); - onIncomingFileTransfer(transfer); +bool IncomingFileTransferManager::handleIncomingJingleSession(JingleSession::ref session, const std::vector<JingleContentPayload::ref>& contents, const JID& recipient) { + if (JingleContentPayload::ref content = Jingle::getContentWithDescription<JingleFileTransferDescription>(contents)) { + if (content->getTransport<JingleIBBTransportPayload>() || content->getTransport<JingleS5BTransportPayload>()) { + + JingleFileTransferDescription::ref description = content->getDescription<JingleFileTransferDescription>(); + + if (description && description->getOffers().size() == 1) { + IncomingJingleFileTransfer::ref transfer = boost::make_shared<IncomingJingleFileTransfer>(recipient, session, content, remoteFactory, localFactory, router, bytestreamRegistry, bytestreamProxy, timerFactory); + onIncomingFileTransfer(transfer); + } else { + std::cerr << "Received a file-transfer request with no description or more than one file!" << std::endl; + session->sendTerminate(JinglePayload::Reason::FailedApplication); + } } else { - session->terminate(JinglePayload::Reason::UnsupportedTransports); + session->sendTerminate(JinglePayload::Reason::UnsupportedTransports); } return true; } diff --git a/Swiften/FileTransfer/IncomingFileTransferManager.h b/Swiften/FileTransfer/IncomingFileTransferManager.h index a54b5cd..2d1c07f 100644 --- a/Swiften/FileTransfer/IncomingFileTransferManager.h +++ b/Swiften/FileTransfer/IncomingFileTransferManager.h @@ -15,19 +15,29 @@ namespace Swift { class IQRouter; class JingleSessionManager; + class RemoteJingleTransportCandidateSelectorFactory; + class LocalJingleTransportCandidateGeneratorFactory; + class SOCKS5BytestreamRegistry; + class SOCKS5BytestreamProxy; + class TimerFactory; class IncomingFileTransferManager : public IncomingJingleSessionHandler { public: - IncomingFileTransferManager(JingleSessionManager* jingleSessionManager, IQRouter* router); + IncomingFileTransferManager(JingleSessionManager* jingleSessionManager, IQRouter* router, RemoteJingleTransportCandidateSelectorFactory* remoteFactory, LocalJingleTransportCandidateGeneratorFactory* localFactory, SOCKS5BytestreamRegistry* bytestreamRegistry, SOCKS5BytestreamProxy* bytestreamProxy, TimerFactory* timerFactory); ~IncomingFileTransferManager(); boost::signal<void (IncomingFileTransfer::ref)> onIncomingFileTransfer; private: - bool handleIncomingJingleSession(IncomingJingleSession::ref session); + bool handleIncomingJingleSession(JingleSession::ref session, const std::vector<JingleContentPayload::ref>& contents, const JID& recipient); private: JingleSessionManager* jingleSessionManager; IQRouter* router; + RemoteJingleTransportCandidateSelectorFactory* remoteFactory; + LocalJingleTransportCandidateGeneratorFactory* localFactory; + SOCKS5BytestreamRegistry* bytestreamRegistry; + SOCKS5BytestreamProxy* bytestreamProxy; + TimerFactory* timerFactory; }; } diff --git a/Swiften/FileTransfer/IncomingJingleFileTransfer.cpp b/Swiften/FileTransfer/IncomingJingleFileTransfer.cpp index cb2f65c..0871568 100644 --- a/Swiften/FileTransfer/IncomingJingleFileTransfer.cpp +++ b/Swiften/FileTransfer/IncomingJingleFileTransfer.cpp @@ -6,14 +6,516 @@ #include <Swiften/FileTransfer/IncomingJingleFileTransfer.h> +#include <boost/bind.hpp> +#include <boost/smart_ptr/make_shared.hpp> + +#include <Swiften/Base/Log.h> +#include <Swiften/Base/foreach.h> +#include <Swiften/Elements/JingleIBBTransportPayload.h> +#include <Swiften/Elements/JingleS5BTransportPayload.h> +#include <Swiften/Elements/JingleFileTransferHash.h> +#include <Swiften/Elements/S5BProxyRequest.h> +#include <Swiften/FileTransfer/IncrementalBytestreamHashCalculator.h> +#include <Swiften/FileTransfer/JingleIncomingIBBTransport.h> +#include <Swiften/FileTransfer/LocalJingleTransportCandidateGenerator.h> +#include <Swiften/FileTransfer/LocalJingleTransportCandidateGeneratorFactory.h> +#include <Swiften/FileTransfer/RemoteJingleTransportCandidateSelector.h> +#include <Swiften/FileTransfer/RemoteJingleTransportCandidateSelectorFactory.h> +#include <Swiften/FileTransfer/SOCKS5BytestreamRegistry.h> +#include <Swiften/FileTransfer/SOCKS5BytestreamProxy.h> +#include <Swiften/Network/TimerFactory.h> +#include <Swiften/Queries/GenericRequest.h> + namespace Swift { -IncomingJingleFileTransfer::IncomingJingleFileTransfer(IncomingJingleSession::ref session) : session(session) { +IncomingJingleFileTransfer::IncomingJingleFileTransfer( + const JID& ourJID, + JingleSession::ref session, + JingleContentPayload::ref content, + RemoteJingleTransportCandidateSelectorFactory* candidateSelectorFactory, + LocalJingleTransportCandidateGeneratorFactory* candidateGeneratorFactory, + IQRouter* router, + SOCKS5BytestreamRegistry* registry, + SOCKS5BytestreamProxy* proxy, + TimerFactory* timerFactory) : + ourJID(ourJID), + session(session), + router(router), + timerFactory(timerFactory), + initialContent(content), + state(Initial), + receivedBytes(0), + s5bRegistry(registry), + s5bProxy(proxy), + remoteTransportCandidateSelectFinished(false), + localTransportCandidateSelectFinished(false), + serverSession(0) { + + candidateSelector = candidateSelectorFactory->createCandidateSelector(); + candidateSelector->onRemoteTransportCandidateSelectFinished.connect(boost::bind(&IncomingJingleFileTransfer::handleRemoteTransportCandidateSelectFinished, this, _1)); + + candidateGenerator = candidateGeneratorFactory->createCandidateGenerator(); + candidateGenerator->onLocalTransportCandidatesGenerated.connect(boost::bind(&IncomingJingleFileTransfer::handleLocalTransportCandidatesGenerated, this, _1)); + + session->onTransportInfoReceived.connect(boost::bind(&IncomingJingleFileTransfer::handleTransportInfoReceived, this, _1, _2)); + session->onTransportReplaceReceived.connect(boost::bind(&IncomingJingleFileTransfer::handleTransportReplaceReceived, this, _1, _2)); + session->onSessionTerminateReceived.connect(boost::bind(&IncomingJingleFileTransfer::handleSessionTerminateReceived, this, _1)); + session->onSessionInfoReceived.connect(boost::bind(&IncomingJingleFileTransfer::handleSessionInfoReceived, this, _1)); + + description = initialContent->getDescription<JingleFileTransferDescription>(); + assert(description); + assert(description->getOffers().size() == 1); + StreamInitiationFileInfo fileInfo = description->getOffers().front(); + fileSizeInBytes = fileInfo.getSize(); + filename = fileInfo.getName(); + hash = fileInfo.getHash(); + algo = fileInfo.getAlgo(); + + waitOnHashTimer = timerFactory->createTimer(5000); + waitOnHashTimer->onTick.connect(boost::bind(&IncomingJingleFileTransfer::finishOffTransfer, this)); +} + +IncomingJingleFileTransfer::~IncomingJingleFileTransfer() { + stream->onWrite.disconnect(boost::bind(&IncrementalBytestreamHashCalculator::feedData, hashCalculator, _1)); + delete hashCalculator; + + session->onSessionTerminateReceived.disconnect(boost::bind(&IncomingJingleFileTransfer::handleSessionTerminateReceived, this)); + session->onTransportReplaceReceived.disconnect(boost::bind(&IncomingJingleFileTransfer::handleTransportReplaceReceived, this, _1, _2)); + session->onTransportInfoReceived.disconnect(boost::bind(&IncomingJingleFileTransfer::handleTransportInfoReceived, this, _1, _2)); + + candidateGenerator->onLocalTransportCandidatesGenerated.disconnect(boost::bind(&IncomingJingleFileTransfer::handleLocalTransportCandidatesGenerated, this, _1)); + delete candidateGenerator; + candidateSelector->onRemoteTransportCandidateSelectFinished.disconnect(boost::bind(&IncomingJingleFileTransfer::handleRemoteTransportCandidateSelectFinished, this, _1)); + delete candidateSelector; } void IncomingJingleFileTransfer::accept(WriteBytestream::ref stream) { + assert(!this->stream); this->stream = stream; + + hashCalculator = new IncrementalBytestreamHashCalculator( algo == "md5" || hash.empty() , algo == "sha-1" || hash.empty() ); + stream->onWrite.connect(boost::bind(&IncrementalBytestreamHashCalculator::feedData, hashCalculator, _1)); + stream->onWrite.connect(boost::bind(&IncomingJingleFileTransfer::handleWriteStreamDataReceived, this, _1)); + onStateChange(FileTransfer::State(FileTransfer::State::Negotiating)); + if (JingleIBBTransportPayload::ref ibbTransport = initialContent->getTransport<JingleIBBTransportPayload>()) { + SWIFT_LOG(debug) << "Got IBB transport payload!" << std::endl; + setActiveTransport(createIBBTransport(ibbTransport)); + session->sendAccept(getContentID(), initialContent->getDescriptions()[0], ibbTransport); + } + else if (JingleS5BTransportPayload::ref s5bTransport = initialContent->getTransport<JingleS5BTransportPayload>()) { + SWIFT_LOG(debug) << "Got S5B transport payload!" << std::endl; + state = CreatingInitialTransports; + s5bSessionID = s5bTransport->getSessionID().empty() ? idGenerator.generateID() : s5bTransport->getSessionID(); + s5bDestination = SOCKS5BytestreamRegistry::getHostname(s5bSessionID, ourJID, session->getInitiator()); + s5bRegistry->addWriteBytestream(s5bDestination, stream); + fillCandidateMap(theirCandidates, s5bTransport); + candidateSelector->addRemoteTransportCandidates(s5bTransport); + candidateSelector->setRequesterTargtet(session->getInitiator(), ourJID); + s5bTransport->setSessionID(s5bSessionID); + candidateGenerator->generateLocalTransportCandidates(s5bTransport); + } + else { + assert(false); + } +} + +const JID& IncomingJingleFileTransfer::getSender() const { + return session->getInitiator(); +} + +const JID& IncomingJingleFileTransfer::getRecipient() const { + return ourJID; +} + +void IncomingJingleFileTransfer::cancel() { + session->sendTerminate(JinglePayload::Reason::Cancel); + + if (activeTransport) activeTransport->stop(); + if (serverSession) serverSession->stop(); + if (clientSession) clientSession->stop(); + onStateChange(FileTransfer::State(FileTransfer::State::Canceled)); +} + +void IncomingJingleFileTransfer::handleLocalTransportCandidatesGenerated(JingleTransportPayload::ref candidates) { + if (state == CreatingInitialTransports) { + if (JingleS5BTransportPayload::ref s5bCandidates = boost::dynamic_pointer_cast<JingleS5BTransportPayload>(candidates)) { + //localTransportCandidateSelectFinished = true; + //JingleS5BTransportPayload::ref emptyCandidates = boost::make_shared<JingleS5BTransportPayload>(); + //emptyCandidates->setSessionID(s5bCandidates->getSessionID()); + fillCandidateMap(ourCandidates, s5bCandidates); + session->sendAccept(getContentID(), initialContent->getDescriptions()[0], s5bCandidates); + + state = NegotiatingTransport; + candidateSelector->selectCandidate(); + } + } + else { + SWIFT_LOG(debug) << "Unhandled state!" << std::endl; + } +} + + +void IncomingJingleFileTransfer::handleRemoteTransportCandidateSelectFinished(JingleTransportPayload::ref transport) { + SWIFT_LOG(debug) << std::endl; + if (state == Terminated) { + return; + } + if (JingleS5BTransportPayload::ref s5bPayload = boost::dynamic_pointer_cast<JingleS5BTransportPayload>(transport)) { + //remoteTransportCandidateSelectFinished = true; + //selectedRemoteTransportCandidate = transport; + ourCandidate = s5bPayload; + //checkCandidateSelected(); + decideOnUsedTransport(); + session->sendTransportInfo(getContentID(), s5bPayload); + } + else { + SWIFT_LOG(debug) << "Expected something different here." << std::endl; + } +} + +void IncomingJingleFileTransfer::checkCandidateSelected() { + assert(false); + if (localTransportCandidateSelectFinished && remoteTransportCandidateSelectFinished) { + if (candidateGenerator->isActualCandidate(selectedLocalTransportCandidate) && candidateSelector->isActualCandidate(selectedRemoteTransportCandidate)) { + if (candidateGenerator->getPriority(selectedLocalTransportCandidate) > candidateSelector->getPriority(selectedRemoteTransportCandidate)) { + setActiveTransport(candidateGenerator->selectTransport(selectedLocalTransportCandidate)); + } + else { + setActiveTransport(candidateSelector->selectTransport(selectedRemoteTransportCandidate)); + } + } + else if (candidateSelector->isActualCandidate(selectedRemoteTransportCandidate)) { + setActiveTransport(candidateSelector->selectTransport(selectedRemoteTransportCandidate)); + } + else if (candidateGenerator->isActualCandidate(selectedLocalTransportCandidate)) { + setActiveTransport(candidateGenerator->selectTransport(selectedLocalTransportCandidate)); + } + else { + state = WaitingForFallbackOrTerminate; + } + } +} + +void IncomingJingleFileTransfer::setActiveTransport(JingleTransport::ref transport) { + state = Transferring; + onStateChange(FileTransfer::State(FileTransfer::State::Transferring)); + activeTransport = transport; + activeTransport->onDataReceived.connect(boost::bind(&IncomingJingleFileTransfer::handleTransportDataReceived, this, _1)); + activeTransport->onFinished.connect(boost::bind(&IncomingJingleFileTransfer::handleTransferFinished, this, _1)); + activeTransport->start(); +} + +bool IncomingJingleFileTransfer::verifyReceviedData() { + if (algo.empty() || hash.empty()) { + SWIFT_LOG(debug) << "no verification possible, skipping" << std::endl; + return true; + } else { + if (algo == "sha-1") { + SWIFT_LOG(debug) << "verify data via SHA-1 hash: " << (hash == hashCalculator->getSHA1String()) << std::endl; + return hash == hashCalculator->getSHA1String(); + } + else if (algo == "md5") { + SWIFT_LOG(debug) << "verify data via MD5 hash: " << (hash == hashCalculator->getMD5String()) << std::endl; + return hash == hashCalculator->getMD5String(); + } + else { + SWIFT_LOG(debug) << "no verification possible, skipping" << std::endl; + return true; + } + } +} + +void IncomingJingleFileTransfer::finishOffTransfer() { + if (verifyReceviedData()) { + onStateChange(FileTransfer::State(FileTransfer::State::Finished)); + session->sendTerminate(JinglePayload::Reason::Success); + } else { + onStateChange(FileTransfer::State(FileTransfer::State::Failed, "Verification failed.")); + session->sendTerminate(JinglePayload::Reason::MediaError); + } + state = Terminated; + waitOnHashTimer->stop(); +} + +void IncomingJingleFileTransfer::handleSessionInfoReceived(JinglePayload::ref jinglePayload) { + if (state == Terminated) { + return; + } + JingleFileTransferHash::ref transferHash = jinglePayload->getPayload<JingleFileTransferHash>(); + if (transferHash) { + SWIFT_LOG(debug) << "Recevied hash information." << std::endl; + if (transferHash->getHashes().find("sha-1") != transferHash->getHashes().end()) { + algo = "sha-1"; + hash = transferHash->getHashes().find("sha-1")->second; + } + else if (transferHash->getHashes().find("md5") != transferHash->getHashes().end()) { + algo = "md5"; + hash = transferHash->getHashes().find("md5")->second; + } + checkIfAllDataReceived(); + } +} + +void IncomingJingleFileTransfer::handleSessionTerminateReceived(boost::optional<JinglePayload::Reason> reason) { + SWIFT_LOG(debug) << "session terminate received" << std::endl; + if (activeTransport) activeTransport->stop(); + if (reason && reason.get().type == JinglePayload::Reason::Cancel) { + onStateChange(FileTransfer::State(FileTransfer::State::Canceled, "Other user canceled the transfer.")); + } + else if (reason && reason.get().type == JinglePayload::Reason::Success) { + /*if (verifyReceviedData()) { + onStateChange(FileTransfer::State(FileTransfer::State::Finished)); + } else { + onStateChange(FileTransfer::State(FileTransfer::State::Failed, "Verification failed.")); + }*/ + } + state = Terminated; +} + +void IncomingJingleFileTransfer::checkIfAllDataReceived() { + if (receivedBytes == fileSizeInBytes) { + SWIFT_LOG(debug) << "All data received." << std::endl; + if (hash.empty()) { + SWIFT_LOG(debug) << "No hash information yet. Waiting 5 seconds on hash info." << std::endl; + waitOnHashTimer->start(); + } else { + SWIFT_LOG(debug) << "We already have hash info using " << algo << " algorithm. Finishing off transfer." << std::endl; + finishOffTransfer(); + } + } + else if (receivedBytes > fileSizeInBytes) { + SWIFT_LOG(debug) << "We got more than we could handle!" << std::endl; + } +} + +void IncomingJingleFileTransfer::handleTransportDataReceived(const std::vector<unsigned char>& data) { + SWIFT_LOG(debug) << data.size() << " bytes received" << std::endl; + onProcessedBytes(data.size()); + stream->write(data); + receivedBytes += data.size(); + checkIfAllDataReceived(); +} + +void IncomingJingleFileTransfer::handleWriteStreamDataReceived(const std::vector<unsigned char>& data) { + receivedBytes += data.size(); + checkIfAllDataReceived(); +} + +void IncomingJingleFileTransfer::useOurCandidateChoiceForTransfer(JingleS5BTransportPayload::Candidate candidate) { + SWIFT_LOG(debug) << std::endl; + if (candidate.type == JingleS5BTransportPayload::Candidate::ProxyType) { + // get proxy client session from remoteCandidateSelector + clientSession = candidateSelector->getS5BSession(); + + // wait on <activated/> transport-info + } else { + // ask s5b client + clientSession = candidateSelector->getS5BSession(); + if (clientSession) { + state = Transferring; + SWIFT_LOG(debug) << clientSession->getAddressPort().toString() << std::endl; + clientSession->onBytesReceived.connect(boost::bind(boost::ref(onProcessedBytes), _1)); + clientSession->onFinished.connect(boost::bind(&IncomingJingleFileTransfer::handleTransferFinished, this, _1)); + clientSession->startReceiving(stream); + } else { + SWIFT_LOG(debug) << "No S5B client session found!!!" << std::endl; + } + } +} + +void IncomingJingleFileTransfer::useTheirCandidateChoiceForTransfer(JingleS5BTransportPayload::Candidate candidate) { + SWIFT_LOG(debug) << std::endl; + + if (candidate.type == JingleS5BTransportPayload::Candidate::ProxyType) { + // get proxy client session from s5bRegistry + clientSession = s5bProxy->createSOCKS5BytestreamClientSession(candidate.hostPort, SOCKS5BytestreamRegistry::getHostname(s5bSessionID, ourJID, session->getInitiator())); + clientSession->onSessionReady.connect(boost::bind(&IncomingJingleFileTransfer::proxySessionReady, this, candidate.jid, _1)); + clientSession->start(); + + // on reply send activate + } else { + // ask s5b server + serverSession = s5bRegistry->getConnectedSession(s5bDestination); + if (serverSession) { + state = Transferring; + serverSession->onBytesReceived.connect(boost::bind(boost::ref(onProcessedBytes), _1)); + serverSession->onFinished.connect(boost::bind(&IncomingJingleFileTransfer::handleTransferFinished, this, _1)); + serverSession->startTransfer(); + } else { + SWIFT_LOG(debug) << "No S5B server session found!!!" << std::endl; + } + } +} + +void IncomingJingleFileTransfer::fillCandidateMap(CandidateMap& map, JingleS5BTransportPayload::ref s5bPayload) { + map.clear(); + foreach (JingleS5BTransportPayload::Candidate candidate, s5bPayload->getCandidates()) { + map[candidate.cid] = candidate; + } +} + + +void IncomingJingleFileTransfer::decideOnUsedTransport() { + if (ourCandidate && theirCandidate) { + if (ourCandidate->hasCandidateError() && theirCandidate->hasCandidateError()) { + state = WaitingForFallbackOrTerminate; + return; + } + std::string our_cid = ourCandidate->getCandidateUsed(); + std::string their_cid = theirCandidate->getCandidateUsed(); + if (ourCandidate->hasCandidateError() && !their_cid.empty()) { + useTheirCandidateChoiceForTransfer(ourCandidates[their_cid]); + onStateChange(FileTransfer::State(FileTransfer::State::Transferring)); + } + else if (theirCandidate->hasCandidateError() && !our_cid.empty()) { + useOurCandidateChoiceForTransfer(theirCandidates[our_cid]); + onStateChange(FileTransfer::State(FileTransfer::State::Transferring)); + } + else if (!our_cid.empty() && !their_cid.empty()) { + // compare priorites, if same they win + if (ourCandidates.find(their_cid) == ourCandidates.end() || theirCandidates.find(our_cid) == theirCandidates.end()) { + SWIFT_LOG(debug) << "Didn't recognize candidate IDs!" << std::endl; + session->sendTerminate(JinglePayload::Reason::FailedTransport); + onStateChange(FileTransfer::State(FileTransfer::State::Canceled, "Failed to negotiate candidate.")); + onFinished(FileTransferError(FileTransferError::PeerError)); + return; + } + + JingleS5BTransportPayload::Candidate our_candidate = theirCandidates[our_cid]; + JingleS5BTransportPayload::Candidate their_candidate = ourCandidates[their_cid]; + if (our_candidate.priority > their_candidate.priority) { + useOurCandidateChoiceForTransfer(our_candidate); + } + else if (our_candidate.priority < their_candidate.priority) { + useTheirCandidateChoiceForTransfer(their_candidate); + } + else { + useTheirCandidateChoiceForTransfer(their_candidate); + } + onStateChange(FileTransfer::State(FileTransfer::State::Transferring)); + } + else { + assert(false); + } + } else { + SWIFT_LOG(debug) << "Can't make a transport decision yet." << std::endl; + } +} + +void IncomingJingleFileTransfer::proxySessionReady(const JID& proxy, bool error) { + if (error) { + // indicate proxy error + } else { + // activate proxy + activateProxySession(proxy); + } +} + +void IncomingJingleFileTransfer::activateProxySession(const JID &proxy) { + S5BProxyRequest::ref proxyRequest = boost::make_shared<S5BProxyRequest>(); + proxyRequest->setSID(s5bSessionID); + proxyRequest->setActivate(session->getInitiator()); + + boost::shared_ptr<GenericRequest<S5BProxyRequest> > request = boost::make_shared<GenericRequest<S5BProxyRequest> >(IQ::Set, proxy, proxyRequest, router); + request->onResponse.connect(boost::bind(&IncomingJingleFileTransfer::handleActivateProxySessionResult, this, _1, _2)); + request->send(); +} + +void IncomingJingleFileTransfer::handleActivateProxySessionResult(boost::shared_ptr<S5BProxyRequest> /*request*/, ErrorPayload::ref error) { + SWIFT_LOG(debug) << std::endl; + if (error) { + SWIFT_LOG(debug) << "ERROR" << std::endl; + } else { + // send activated to other jingle party + JingleS5BTransportPayload::ref proxyActivate = boost::make_shared<JingleS5BTransportPayload>(); + proxyActivate->setActivated(theirCandidate->getCandidateUsed()); + session->sendTransportInfo(getContentID(), proxyActivate); + + // start transferring + clientSession->onBytesReceived.connect(boost::bind(boost::ref(onProcessedBytes), _1)); + clientSession->onFinished.connect(boost::bind(&IncomingJingleFileTransfer::handleTransferFinished, this, _1)); + clientSession->startReceiving(stream); + onStateChange(FileTransfer::State(FileTransfer::State::Transferring)); + } +} + +void IncomingJingleFileTransfer::handleTransportInfoReceived(const JingleContentID&, JingleTransportPayload::ref transport) { + SWIFT_LOG(debug) << "transport info received" << std::endl; + if (state == Terminated) { + return; + } + if (JingleS5BTransportPayload::ref s5bPayload = boost::dynamic_pointer_cast<JingleS5BTransportPayload>(transport)) { + if (!s5bPayload->getActivated().empty()) { + if (ourCandidate->getCandidateUsed() == s5bPayload->getActivated()) { + clientSession->onBytesReceived.connect(boost::bind(boost::ref(onProcessedBytes), _1)); + clientSession->onFinished.connect(boost::bind(&IncomingJingleFileTransfer::handleTransferFinished, this, _1)); + clientSession->startReceiving(stream); + onStateChange(FileTransfer::State(FileTransfer::State::Transferring)); + } else { + SWIFT_LOG(debug) << "ourCandidateChoice doesn't match activated proxy candidate!" << std::endl; + JingleS5BTransportPayload::ref proxyError = boost::make_shared<JingleS5BTransportPayload>(); + proxyError->setProxyError(true); + proxyError->setSessionID(s5bSessionID); + session->sendTransportInfo(getContentID(), proxyError); + } + } else { + theirCandidate = s5bPayload; + decideOnUsedTransport(); + } + } + else { + SWIFT_LOG(debug) << "Expected something different here." << std::endl; + } + /*localTransportCandidateSelectFinished = true; + selectedLocalTransportCandidate = transport; + if (candidateGenerator->isActualCandidate(transport)) { + candidateSelector->setMinimumPriority(candidateGenerator->getPriority(transport)); + }*/ + //checkCandidateSelected(); +} + +void IncomingJingleFileTransfer::handleTransportReplaceReceived(const JingleContentID& content, JingleTransportPayload::ref transport) { + if (state == Terminated) { + return; + } + if (JingleIBBTransportPayload::ref ibbTransport = boost::dynamic_pointer_cast<JingleIBBTransportPayload>(transport)) { + SWIFT_LOG(debug) << "transport replaced with IBB" << std::endl; + setActiveTransport(createIBBTransport(ibbTransport)); + session->sendTransportAccept(content, ibbTransport); + } else { + SWIFT_LOG(debug) << "transport replaced failed" << std::endl; + session->sendTransportReject(content, transport); + } +} + +void IncomingJingleFileTransfer::stopActiveTransport() { + if (activeTransport) { + activeTransport->stop(); + activeTransport->onDataReceived.disconnect(boost::bind(&IncomingJingleFileTransfer::handleTransportDataReceived, this, _1)); + } +} + +JingleIncomingIBBTransport::ref IncomingJingleFileTransfer::createIBBTransport(JingleIBBTransportPayload::ref ibbTransport) { + // TODO: getOffer() -> getOffers correction + return boost::make_shared<JingleIncomingIBBTransport>(session->getInitiator(), getRecipient(), ibbTransport->getSessionID(), description->getOffers()[0].getSize(), router); +} + +JingleContentID IncomingJingleFileTransfer::getContentID() const { + return JingleContentID(initialContent->getName(), initialContent->getCreator()); +} + +void IncomingJingleFileTransfer::handleTransferFinished(boost::optional<FileTransferError> error) { + if (state == Terminated) { + return; + } + + if (error) { + session->sendTerminate(JinglePayload::Reason::ConnectivityError); + onStateChange(FileTransfer::State(FileTransfer::State::Failed)); + onFinished(error); + } + // } } diff --git a/Swiften/FileTransfer/IncomingJingleFileTransfer.h b/Swiften/FileTransfer/IncomingJingleFileTransfer.h index d69449e..4ae0bfb 100644 --- a/Swiften/FileTransfer/IncomingJingleFileTransfer.h +++ b/Swiften/FileTransfer/IncomingJingleFileTransfer.h @@ -7,21 +7,128 @@ #pragma once #include <boost/shared_ptr.hpp> +#include <boost/cstdint.hpp> -#include <Swiften/Jingle/IncomingJingleSession.h> +#include <Swiften/Base/IDGenerator.h> +#include <Swiften/Network/Timer.h> +#include <Swiften/Jingle/JingleSession.h> +#include <Swiften/Jingle/JingleContentID.h> #include <Swiften/FileTransfer/IncomingFileTransfer.h> +#include <Swiften/FileTransfer/JingleTransport.h> +#include <Swiften/FileTransfer/JingleIncomingIBBTransport.h> +#include <Swiften/FileTransfer/SOCKS5BytestreamClientSession.h> +#include <Swiften/FileTransfer/SOCKS5BytestreamServerSession.h> +#include <Swiften/Elements/JingleContentPayload.h> +#include <Swiften/Elements/JingleFileTransferDescription.h> +#include <Swiften/Elements/JingleIBBTransportPayload.h> +#include <Swiften/Elements/JingleS5BTransportPayload.h> +#include <Swiften/Elements/S5BProxyRequest.h> +#include <Swiften/Elements/ErrorPayload.h> namespace Swift { + class IQRouter; + class RemoteJingleTransportCandidateSelectorFactory; + class LocalJingleTransportCandidateGeneratorFactory; + class RemoteJingleTransportCandidateSelector; + class LocalJingleTransportCandidateGenerator; + class SOCKS5BytestreamRegistry; + class SOCKS5BytestreamProxy; + class IncrementalBytestreamHashCalculator; + class IncomingJingleFileTransfer : public IncomingFileTransfer { public: typedef boost::shared_ptr<IncomingJingleFileTransfer> ref; + enum State { + Initial, + CreatingInitialTransports, + NegotiatingTransport, + Transferring, + WaitingForFallbackOrTerminate, + Terminated + }; - IncomingJingleFileTransfer(IncomingJingleSession::ref session); + IncomingJingleFileTransfer( + const JID& recipient, + JingleSession::ref, + JingleContentPayload::ref content, + RemoteJingleTransportCandidateSelectorFactory*, + LocalJingleTransportCandidateGeneratorFactory*, + IQRouter* router, + SOCKS5BytestreamRegistry* bytestreamRegistry, + SOCKS5BytestreamProxy* bytestreamProxy, + TimerFactory*); + ~IncomingJingleFileTransfer(); virtual void accept(WriteBytestream::ref); + virtual const JID& getSender() const; + virtual const JID& getRecipient() const; + void cancel(); + + private: + void handleSessionTerminateReceived(boost::optional<JinglePayload::Reason>); + void handleSessionInfoReceived(JinglePayload::ref); + void handleTransportReplaceReceived(const JingleContentID&, JingleTransportPayload::ref); + void handleTransportInfoReceived(const JingleContentID&, JingleTransportPayload::ref); + void handleLocalTransportCandidatesGenerated(JingleTransportPayload::ref candidates); + void handleRemoteTransportCandidateSelectFinished(JingleTransportPayload::ref candidate); + void setActiveTransport(JingleTransport::ref transport); + void handleTransportDataReceived(const std::vector<unsigned char>& data); + void handleWriteStreamDataReceived(const std::vector<unsigned char>& data); + void stopActiveTransport(); + void checkCandidateSelected(); + JingleIncomingIBBTransport::ref createIBBTransport(JingleIBBTransportPayload::ref ibbTransport); + JingleContentID getContentID() const; + void checkIfAllDataReceived(); + bool verifyReceviedData(); + void finishOffTransfer(); + void handleTransferFinished(boost::optional<FileTransferError>); + + private: + typedef std::map<std::string, JingleS5BTransportPayload::Candidate> CandidateMap; + + private: + void activateProxySession(const JID &proxy); + void handleActivateProxySessionResult(boost::shared_ptr<S5BProxyRequest> request, ErrorPayload::ref error); + void proxySessionReady(const JID& proxy, bool error); + + private: + void useOurCandidateChoiceForTransfer(JingleS5BTransportPayload::Candidate candidate); + void useTheirCandidateChoiceForTransfer(JingleS5BTransportPayload::Candidate candidate); + void decideOnUsedTransport(); + void fillCandidateMap(CandidateMap& map, JingleS5BTransportPayload::ref s5bPayload); private: - IncomingJingleSession::ref session; + JID ourJID; + JingleSession::ref session; + IQRouter* router; + TimerFactory* timerFactory; + JingleContentPayload::ref initialContent; + State state; + JingleFileTransferDescription::ref description; WriteBytestream::ref stream; + boost::uintmax_t receivedBytes; + IncrementalBytestreamHashCalculator* hashCalculator; + Timer::ref waitOnHashTimer; + IDGenerator idGenerator; + + RemoteJingleTransportCandidateSelector* candidateSelector; + LocalJingleTransportCandidateGenerator* candidateGenerator; + SOCKS5BytestreamRegistry* s5bRegistry; + SOCKS5BytestreamProxy* s5bProxy; + bool remoteTransportCandidateSelectFinished; + JingleTransportPayload::ref selectedRemoteTransportCandidate; + bool localTransportCandidateSelectFinished; + JingleTransportPayload::ref selectedLocalTransportCandidate; + + JingleS5BTransportPayload::ref ourCandidate; + JingleS5BTransportPayload::ref theirCandidate; + CandidateMap ourCandidates; + CandidateMap theirCandidates; + SOCKS5BytestreamClientSession::ref clientSession; + std::string s5bDestination; + std::string s5bSessionID; + SOCKS5BytestreamServerSession* serverSession; + + JingleTransport::ref activeTransport; }; } diff --git a/Swiften/FileTransfer/IncrementalBytestreamHashCalculator.cpp b/Swiften/FileTransfer/IncrementalBytestreamHashCalculator.cpp new file mode 100644 index 0000000..6b53a1b --- /dev/null +++ b/Swiften/FileTransfer/IncrementalBytestreamHashCalculator.cpp @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include <Swiften/FileTransfer/IncrementalBytestreamHashCalculator.h> + +#include <Swiften/StringCodecs/Hexify.h> +#include <Swiften/StringCodecs/MD5.h> +#include <Swiften/StringCodecs/SHA1.h> + +namespace Swift { + +IncrementalBytestreamHashCalculator::IncrementalBytestreamHashCalculator(bool doMD5, bool doSHA1) { + md5Hasher = doMD5 ? new MD5() : NULL; + sha1Hasher = doSHA1 ? new SHA1() : NULL; +} + +IncrementalBytestreamHashCalculator::~IncrementalBytestreamHashCalculator() { + delete md5Hasher; + delete sha1Hasher; +} + +void IncrementalBytestreamHashCalculator::feedData(const ByteArray& data) { + if (md5Hasher) { + md5Hasher->update(data); + } + if (sha1Hasher) { + sha1Hasher->update(data); + } +} +/* +void IncrementalBytestreamHashCalculator::feedData(const SafeByteArray& data) { + if (md5Hasher) { + md5Hasher->update(createByteArray(data.data(), data.size())); + } + if (sha1Hasher) { + sha1Hasher->update(createByteArray(data.data(), data.size())); + } +}*/ + +std::string IncrementalBytestreamHashCalculator::getSHA1String() { + if (sha1Hasher) { + ByteArray result = sha1Hasher->getHash(); + return Hexify::hexify(result); + } else { + return std::string(); + } +} + +std::string IncrementalBytestreamHashCalculator::getMD5String() { + if (md5Hasher) { + ByteArray result = md5Hasher->getHash(); + return Hexify::hexify(result); + } else { + return std::string(); + } +} + +} diff --git a/Swiften/FileTransfer/IncrementalBytestreamHashCalculator.h b/Swiften/FileTransfer/IncrementalBytestreamHashCalculator.h new file mode 100644 index 0000000..64f4b5f --- /dev/null +++ b/Swiften/FileTransfer/IncrementalBytestreamHashCalculator.h @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <Swiften/Base/ByteArray.h> +#include <Swiften/Base/SafeByteArray.h> + +namespace Swift { + +class MD5; +class SHA1; + +class IncrementalBytestreamHashCalculator { +public: + IncrementalBytestreamHashCalculator(bool doMD5, bool doSHA1); + ~IncrementalBytestreamHashCalculator(); + + void feedData(const ByteArray& data); + //void feedData(const SafeByteArray& data); + + std::string getSHA1String(); + std::string getMD5String(); + +private: + MD5* md5Hasher; + SHA1* sha1Hasher; +}; + +} diff --git a/Swiften/FileTransfer/JingleIncomingIBBTransport.cpp b/Swiften/FileTransfer/JingleIncomingIBBTransport.cpp new file mode 100644 index 0000000..ccca641 --- /dev/null +++ b/Swiften/FileTransfer/JingleIncomingIBBTransport.cpp @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2011 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#include <Swiften/FileTransfer/JingleIncomingIBBTransport.h> + +namespace Swift { + +JingleIncomingIBBTransport::JingleIncomingIBBTransport(const JID& from, const JID& to, const std::string& id, size_t size, IQRouter* router) : ibbSession(id, from, to, size, router) { + ibbSession.onDataReceived.connect(boost::ref(onDataReceived)); + ibbSession.onFinished.connect(boost::ref(onFinished)); +} + +void JingleIncomingIBBTransport::start() { + ibbSession.start(); +} + +void JingleIncomingIBBTransport::stop() { + ibbSession.stop(); +} + +} diff --git a/Swiften/FileTransfer/JingleIncomingIBBTransport.h b/Swiften/FileTransfer/JingleIncomingIBBTransport.h new file mode 100644 index 0000000..be18a2d --- /dev/null +++ b/Swiften/FileTransfer/JingleIncomingIBBTransport.h @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2011 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#pragma once + +#include <boost/shared_ptr.hpp> + +#include <Swiften/FileTransfer/JingleTransport.h> +#include <Swiften/FileTransfer/IBBReceiveSession.h> + +namespace Swift { + class JingleIncomingIBBTransport : public JingleTransport { + public: + typedef boost::shared_ptr<JingleIncomingIBBTransport> ref; + + JingleIncomingIBBTransport(const JID& from, const JID& to, const std::string& id, size_t size, IQRouter* router); + + virtual void start(); + virtual void stop(); + + private: + IBBReceiveSession ibbSession; + }; +} diff --git a/Swiften/FileTransfer/JingleTransport.cpp b/Swiften/FileTransfer/JingleTransport.cpp new file mode 100644 index 0000000..c507922 --- /dev/null +++ b/Swiften/FileTransfer/JingleTransport.cpp @@ -0,0 +1,15 @@ +/* + * Copyright (c) 2011 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#include <Swiften/FileTransfer/JingleTransport.h> + +namespace Swift { + +JingleTransport::~JingleTransport() { + +} + +} diff --git a/Swiften/FileTransfer/JingleTransport.h b/Swiften/FileTransfer/JingleTransport.h new file mode 100644 index 0000000..fa296e8 --- /dev/null +++ b/Swiften/FileTransfer/JingleTransport.h @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2011 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#pragma once + +#include <boost/shared_ptr.hpp> + +#include <Swiften/Base/boost_bsignals.h> +#include <Swiften/FileTransfer/FileTransfer.h> + +namespace Swift { + class JingleTransport { + public: + typedef boost::shared_ptr<JingleTransport> ref; + + virtual ~JingleTransport(); + + virtual void start() = 0; + virtual void stop() = 0; + + boost::signal<void (const std::vector<unsigned char>&)> onDataReceived; + boost::signal<void (boost::optional<FileTransferError>)> onFinished; + }; +} diff --git a/Swiften/FileTransfer/LocalJingleTransportCandidateGenerator.cpp b/Swiften/FileTransfer/LocalJingleTransportCandidateGenerator.cpp new file mode 100644 index 0000000..852902b --- /dev/null +++ b/Swiften/FileTransfer/LocalJingleTransportCandidateGenerator.cpp @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2011 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#include <Swiften/FileTransfer/LocalJingleTransportCandidateGenerator.h> + +namespace Swift { + +LocalJingleTransportCandidateGenerator::~LocalJingleTransportCandidateGenerator() { +} + +} diff --git a/Swiften/FileTransfer/LocalJingleTransportCandidateGenerator.h b/Swiften/FileTransfer/LocalJingleTransportCandidateGenerator.h new file mode 100644 index 0000000..041afe3 --- /dev/null +++ b/Swiften/FileTransfer/LocalJingleTransportCandidateGenerator.h @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2011 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#pragma once + +#include <Swiften/Base/boost_bsignals.h> + +#include <Swiften/Elements/JingleTransportPayload.h> +#include <Swiften/FileTransfer/JingleTransport.h> + +namespace Swift { + class LocalJingleTransportCandidateGenerator { + public: + virtual ~LocalJingleTransportCandidateGenerator(); + /** + * Should call onLocalTransportCandidatesGenerated if it has finished discovering local candidates. + */ + virtual void generateLocalTransportCandidates(JingleTransportPayload::ref) = 0; + + virtual bool isActualCandidate(JingleTransportPayload::ref) = 0; + virtual int getPriority(JingleTransportPayload::ref) = 0; + virtual JingleTransport::ref selectTransport(JingleTransportPayload::ref) = 0; + + boost::signal<void (JingleTransportPayload::ref)> onLocalTransportCandidatesGenerated; + }; +} diff --git a/Swiften/FileTransfer/LocalJingleTransportCandidateGeneratorFactory.cpp b/Swiften/FileTransfer/LocalJingleTransportCandidateGeneratorFactory.cpp new file mode 100644 index 0000000..a1e3874 --- /dev/null +++ b/Swiften/FileTransfer/LocalJingleTransportCandidateGeneratorFactory.cpp @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2011 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#include <Swiften/FileTransfer/LocalJingleTransportCandidateGeneratorFactory.h> + +namespace Swift { + +LocalJingleTransportCandidateGeneratorFactory::~LocalJingleTransportCandidateGeneratorFactory() { +} + +} diff --git a/Swiften/FileTransfer/LocalJingleTransportCandidateGeneratorFactory.h b/Swiften/FileTransfer/LocalJingleTransportCandidateGeneratorFactory.h new file mode 100644 index 0000000..c969fc7 --- /dev/null +++ b/Swiften/FileTransfer/LocalJingleTransportCandidateGeneratorFactory.h @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2011 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#pragma once + +namespace Swift { + class LocalJingleTransportCandidateGenerator; + + class LocalJingleTransportCandidateGeneratorFactory { + public: + virtual ~LocalJingleTransportCandidateGeneratorFactory(); + + virtual LocalJingleTransportCandidateGenerator* createCandidateGenerator() = 0; + }; +} diff --git a/Swiften/FileTransfer/OutgoingFileTransfer.cpp b/Swiften/FileTransfer/OutgoingFileTransfer.cpp index 32f7e17..94d4348 100644 --- a/Swiften/FileTransfer/OutgoingFileTransfer.cpp +++ b/Swiften/FileTransfer/OutgoingFileTransfer.cpp @@ -4,75 +4,11 @@ * See Documentation/Licenses/GPLv3.txt for more information. */ -#include "Swiften/FileTransfer/OutgoingFileTransfer.h" - -#include <boost/bind.hpp> - -#include "Swiften/FileTransfer/StreamInitiationRequest.h" -#include "Swiften/FileTransfer/BytestreamsRequest.h" -#include "Swiften/FileTransfer/SOCKS5BytestreamServer.h" -#include "Swiften/FileTransfer/IBBSendSession.h" +#include <Swiften/FileTransfer/OutgoingFileTransfer.h> namespace Swift { -OutgoingFileTransfer::OutgoingFileTransfer(const std::string& id, const JID& from, const JID& to, const std::string& name, int size, const std::string& description, boost::shared_ptr<ReadBytestream> bytestream, IQRouter* iqRouter, SOCKS5BytestreamServer* socksServer) : id(id), from(from), to(to), name(name), size(size), description(description), bytestream(bytestream), iqRouter(iqRouter), socksServer(socksServer) { -} - -void OutgoingFileTransfer::start() { - StreamInitiation::ref streamInitiation(new StreamInitiation()); - streamInitiation->setID(id); - streamInitiation->setFileInfo(StreamInitiationFileInfo(name, description, size)); - //streamInitiation->addProvidedMethod("http://jabber.org/protocol/bytestreams"); - streamInitiation->addProvidedMethod("http://jabber.org/protocol/ibb"); - StreamInitiationRequest::ref request = StreamInitiationRequest::create(to, streamInitiation, iqRouter); - request->onResponse.connect(boost::bind(&OutgoingFileTransfer::handleStreamInitiationRequestResponse, this, _1, _2)); - request->send(); -} - -void OutgoingFileTransfer::stop() { -} - -void OutgoingFileTransfer::handleStreamInitiationRequestResponse(StreamInitiation::ref response, ErrorPayload::ref error) { - if (error) { - finish(FileTransferError()); - } - else { - if (response->getRequestedMethod() == "http://jabber.org/protocol/bytestreams") { - socksServer->addBytestream(id, from, to, bytestream); - Bytestreams::ref bytestreams(new Bytestreams()); - bytestreams->setStreamID(id); - HostAddressPort addressPort = socksServer->getAddressPort(); - bytestreams->addStreamHost(Bytestreams::StreamHost(addressPort.getAddress().toString(), from, addressPort.getPort())); - BytestreamsRequest::ref request = BytestreamsRequest::create(to, bytestreams, iqRouter); - request->onResponse.connect(boost::bind(&OutgoingFileTransfer::handleBytestreamsRequestResponse, this, _1, _2)); - request->send(); - } - else if (response->getRequestedMethod() == "http://jabber.org/protocol/ibb") { - ibbSession = boost::shared_ptr<IBBSendSession>(new IBBSendSession(id, to, bytestream, iqRouter)); - ibbSession->onFinished.connect(boost::bind(&OutgoingFileTransfer::handleIBBSessionFinished, this, _1)); - ibbSession->start(); - } - } -} - -void OutgoingFileTransfer::handleBytestreamsRequestResponse(Bytestreams::ref, ErrorPayload::ref error) { - if (error) { - finish(FileTransferError()); - } - //socksServer->onTransferFinished.connect(); -} - -void OutgoingFileTransfer::finish(boost::optional<FileTransferError> error) { - if (ibbSession) { - ibbSession->onFinished.disconnect(boost::bind(&OutgoingFileTransfer::handleIBBSessionFinished, this, _1)); - ibbSession.reset(); - } - socksServer->removeBytestream(id, from, to); - onFinished(error); -} - -void OutgoingFileTransfer::handleIBBSessionFinished(boost::optional<FileTransferError> error) { - finish(error); +OutgoingFileTransfer::~OutgoingFileTransfer() { } } diff --git a/Swiften/FileTransfer/OutgoingFileTransfer.h b/Swiften/FileTransfer/OutgoingFileTransfer.h index a694c13..1ec1ae3 100644 --- a/Swiften/FileTransfer/OutgoingFileTransfer.h +++ b/Swiften/FileTransfer/OutgoingFileTransfer.h @@ -8,45 +8,16 @@ #include <boost/shared_ptr.hpp> -#include "Swiften/FileTransfer/ReadBytestream.h" -#include "Swiften/Base/boost_bsignals.h" -#include "Swiften/FileTransfer/FileTransferError.h" -#include "Swiften/FileTransfer/SOCKS5BytestreamServer.h" -#include "Swiften/JID/JID.h" -#include "Swiften/Elements/StreamInitiation.h" -#include "Swiften/Elements/Bytestreams.h" -#include "Swiften/Elements/ErrorPayload.h" -#include "Swiften/FileTransfer/IBBSendSession.h" +#include <Swiften/FileTransfer/FileTransfer.h> namespace Swift { - class IQRouter; - class SOCKS5BytestreamServer; - - class OutgoingFileTransfer { + class OutgoingFileTransfer : public FileTransfer { public: - OutgoingFileTransfer(const std::string& id, const JID& from, const JID& to, const std::string& name, int size, const std::string& description, boost::shared_ptr<ReadBytestream> bytestream, IQRouter* iqRouter, SOCKS5BytestreamServer* socksServer); - - void start(); - void stop(); - - boost::signal<void (const boost::optional<FileTransferError>&)> onFinished; - - private: - void handleStreamInitiationRequestResponse(StreamInitiation::ref, ErrorPayload::ref); - void handleBytestreamsRequestResponse(Bytestreams::ref, ErrorPayload::ref); - void finish(boost::optional<FileTransferError> error); - void handleIBBSessionFinished(boost::optional<FileTransferError> error); + typedef boost::shared_ptr<OutgoingFileTransfer> ref; + public: + virtual ~OutgoingFileTransfer(); - private: - std::string id; - JID from; - JID to; - std::string name; - int size; - std::string description; - boost::shared_ptr<ReadBytestream> bytestream; - IQRouter* iqRouter; - SOCKS5BytestreamServer* socksServer; - boost::shared_ptr<IBBSendSession> ibbSession; + virtual void start() = 0; + virtual void stop() = 0; }; } diff --git a/Swiften/FileTransfer/OutgoingFileTransferManager.cpp b/Swiften/FileTransfer/OutgoingFileTransferManager.cpp new file mode 100644 index 0000000..6f23bb7 --- /dev/null +++ b/Swiften/FileTransfer/OutgoingFileTransferManager.cpp @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include "OutgoingFileTransferManager.h" + +#include <boost/smart_ptr/make_shared.hpp> + +#include <Swiften/JID/JID.h> +#include <Swiften/Jingle/JingleSessionManager.h> +#include <Swiften/Jingle/JingleSessionImpl.h> +#include <Swiften/Jingle/JingleContentID.h> +#include <Swiften/FileTransfer/OutgoingJingleFileTransfer.h> +#include <Swiften/Base/IDGenerator.h> + +namespace Swift { + +OutgoingFileTransferManager::OutgoingFileTransferManager(JingleSessionManager* jingleSessionManager, IQRouter* router, EntityCapsProvider* capsProvider, RemoteJingleTransportCandidateSelectorFactory* remoteFactory, LocalJingleTransportCandidateGeneratorFactory* localFactory, SOCKS5BytestreamRegistry* bytestreamRegistry, SOCKS5BytestreamProxy* bytestreamProxy) : jsManager(jingleSessionManager), iqRouter(router), capsProvider(capsProvider), remoteFactory(remoteFactory), localFactory(localFactory), bytestreamRegistry(bytestreamRegistry), bytestreamProxy(bytestreamProxy) { + idGenerator = new IDGenerator(); +} + +OutgoingFileTransferManager::~OutgoingFileTransferManager() { + delete idGenerator; +} + +boost::shared_ptr<OutgoingFileTransfer> OutgoingFileTransferManager::createOutgoingFileTransfer(const JID& from, const JID& receipient, boost::shared_ptr<ReadBytestream> readBytestream, const StreamInitiationFileInfo& fileInfo) { + // check if receipient support Jingle FT + + + JingleSessionImpl::ref jingleSession = boost::make_shared<JingleSessionImpl>(from, receipient, idGenerator->generateID(), iqRouter); + + //jsManager->getSession(receipient, idGenerator->generateID()); + assert(jingleSession); + jsManager->registerOutgoingSession(from, jingleSession); + boost::shared_ptr<OutgoingJingleFileTransfer> jingleFT = boost::shared_ptr<OutgoingJingleFileTransfer>(new OutgoingJingleFileTransfer(jingleSession, remoteFactory, localFactory, iqRouter, idGenerator, from, receipient, readBytestream, fileInfo, bytestreamRegistry, bytestreamProxy)); + + // otherwise try SI + + // else fail + + return jingleFT; +} + +} diff --git a/Swiften/FileTransfer/OutgoingFileTransferManager.h b/Swiften/FileTransfer/OutgoingFileTransferManager.h new file mode 100644 index 0000000..c686001 --- /dev/null +++ b/Swiften/FileTransfer/OutgoingFileTransferManager.h @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <boost/shared_ptr.hpp> + +#include <Swiften/JID/JID.h> + +namespace Swift { + +class JingleSessionManager; +class IQRouter; +class EntityCapsProvider; +class RemoteJingleTransportCandidateSelectorFactory; +class LocalJingleTransportCandidateGeneratorFactory; +class OutgoingFileTransfer; +class JID; +class IDGenerator; +class ReadBytestream; +class StreamInitiationFileInfo; +class SOCKS5BytestreamRegistry; +class SOCKS5BytestreamProxy; + +class OutgoingFileTransferManager { +public: + OutgoingFileTransferManager(JingleSessionManager* jingleSessionManager, IQRouter* router, EntityCapsProvider* capsProvider, RemoteJingleTransportCandidateSelectorFactory* remoteFactory, LocalJingleTransportCandidateGeneratorFactory* localFactory, SOCKS5BytestreamRegistry* bytestreamRegistry, SOCKS5BytestreamProxy* bytestreamProxy); + ~OutgoingFileTransferManager(); + + boost::shared_ptr<OutgoingFileTransfer> createOutgoingFileTransfer(const JID& from, const JID& to, boost::shared_ptr<ReadBytestream>, const StreamInitiationFileInfo&); + +private: + JingleSessionManager* jsManager; + IQRouter* iqRouter; + EntityCapsProvider* capsProvider; + RemoteJingleTransportCandidateSelectorFactory* remoteFactory; + LocalJingleTransportCandidateGeneratorFactory* localFactory; + IDGenerator *idGenerator; + SOCKS5BytestreamRegistry* bytestreamRegistry; + SOCKS5BytestreamProxy* bytestreamProxy; +}; + +} diff --git a/Swiften/FileTransfer/OutgoingJingleFileTransfer.cpp b/Swiften/FileTransfer/OutgoingJingleFileTransfer.cpp new file mode 100644 index 0000000..5e2a1c3 --- /dev/null +++ b/Swiften/FileTransfer/OutgoingJingleFileTransfer.cpp @@ -0,0 +1,402 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include "OutgoingJingleFileTransfer.h" + +#include <boost/bind.hpp> +#include <boost/smart_ptr/make_shared.hpp> + +#include <Swiften/Base/boost_bsignals.h> +#include <Swiften/Base/foreach.h> +#include <Swiften/Base/IDGenerator.h> +#include <Swiften/Jingle/JingleContentID.h> +#include <Swiften/Elements/JingleFileTransferDescription.h> +#include <Swiften/Elements/JingleFileTransferHash.h> +#include <Swiften/Elements/JingleTransportPayload.h> +#include <Swiften/Elements/JingleIBBTransportPayload.h> +#include <Swiften/Elements/JingleS5BTransportPayload.h> +#include <Swiften/Queries/GenericRequest.h> +#include <Swiften/FileTransfer/IBBSendSession.h> +#include <Swiften/FileTransfer/IncrementalBytestreamHashCalculator.h> +#include <Swiften/FileTransfer/LocalJingleTransportCandidateGenerator.h> +#include <Swiften/FileTransfer/LocalJingleTransportCandidateGeneratorFactory.h> +#include <Swiften/FileTransfer/RemoteJingleTransportCandidateSelector.h> +#include <Swiften/FileTransfer/RemoteJingleTransportCandidateSelectorFactory.h> +#include <Swiften/FileTransfer/SOCKS5BytestreamRegistry.h> +#include <Swiften/FileTransfer/SOCKS5BytestreamProxy.h> +#include <Swiften/StringCodecs/Hexify.h> +#include <Swiften/StringCodecs/SHA1.h> + +#include <Swiften/Base/Log.h> + +namespace Swift { + +OutgoingJingleFileTransfer::OutgoingJingleFileTransfer(JingleSession::ref session, + RemoteJingleTransportCandidateSelectorFactory* remoteFactory, + LocalJingleTransportCandidateGeneratorFactory* localFactory, + IQRouter* router, + IDGenerator *idGenerator, + const JID& fromJID, + const JID& toJID, + boost::shared_ptr<ReadBytestream> readStream, + const StreamInitiationFileInfo &fileInfo, + SOCKS5BytestreamRegistry* bytestreamRegistry, + SOCKS5BytestreamProxy* bytestreamProxy) : + session(session), remoteFactory(remoteFactory), localFactory(localFactory), router(router), idGenerator(idGenerator), fromJID(fromJID), toJID(toJID), readStream(readStream), fileInfo(fileInfo), s5bRegistry(bytestreamRegistry), s5bProxy(bytestreamProxy), serverSession(NULL), contentID(JingleContentID(idGenerator->generateID(), JingleContentPayload::InitiatorCreator)), canceled(false) { + session->onSessionAcceptReceived.connect(boost::bind(&OutgoingJingleFileTransfer::handleSessionAcceptReceived, this, _1, _2, _3)); + session->onSessionTerminateReceived.connect(boost::bind(&OutgoingJingleFileTransfer::handleSessionTerminateReceived, this, _1)); + session->onTransportInfoReceived.connect(boost::bind(&OutgoingJingleFileTransfer::handleTransportInfoReceived, this, _1, _2)); + session->onTransportAcceptReceived.connect(boost::bind(&OutgoingJingleFileTransfer::handleTransportAcceptReceived, this, _1, _2)); + fileSizeInBytes = fileInfo.getSize(); + filename = fileInfo.getName(); + + localCandidateGenerator = localFactory->createCandidateGenerator(); + localCandidateGenerator->onLocalTransportCandidatesGenerated.connect(boost::bind(&OutgoingJingleFileTransfer::handleLocalTransportCandidatesGenerated, this, _1)); + + remoteCandidateSelector = remoteFactory->createCandidateSelector(); + remoteCandidateSelector->onRemoteTransportCandidateSelectFinished.connect(boost::bind(&OutgoingJingleFileTransfer::handleRemoteTransportCandidateSelectFinished, this, _1)); + // calculate both, MD5 and SHA-1 since we don't know which one the other side supports + hashCalculator = new IncrementalBytestreamHashCalculator(true, true); + this->readStream->onRead.connect(boost::bind(&IncrementalBytestreamHashCalculator::feedData, hashCalculator, _1)); +} + +OutgoingJingleFileTransfer::~OutgoingJingleFileTransfer() { + readStream->onRead.disconnect(boost::bind(&IncrementalBytestreamHashCalculator::feedData, hashCalculator, _1)); + delete hashCalculator; +} + +void OutgoingJingleFileTransfer::start() { + onStateChange(FileTransfer::State(FileTransfer::State::WaitingForStart)); + + s5bSessionID = s5bRegistry->generateSessionID(); + SWIFT_LOG(debug) << "S5B SessionID: " << s5bSessionID << std::endl; + + //s5bProxy->connectToProxies(s5bSessionID); + + JingleS5BTransportPayload::ref transport = boost::make_shared<JingleS5BTransportPayload>(); + localCandidateGenerator->generateLocalTransportCandidates(transport); +} + +void OutgoingJingleFileTransfer::stop() { + +} + +void OutgoingJingleFileTransfer::cancel() { + canceled = true; + session->sendTerminate(JinglePayload::Reason::Cancel); + + if (ibbSession) { + ibbSession->stop(); + } + SOCKS5BytestreamServerSession *serverSession = s5bRegistry->getConnectedSession(SOCKS5BytestreamRegistry::getHostname(s5bSessionID, session->getInitiator(), toJID)); + if (serverSession) { + serverSession->stop(); + } + if (clientSession) { + clientSession->stop(); + } + onStateChange(FileTransfer::State(FileTransfer::State::Canceled)); +} + +void OutgoingJingleFileTransfer::handleSessionAcceptReceived(const JingleContentID& id, JingleDescription::ref /* decription */, JingleTransportPayload::ref transportPayload) { + if (canceled) { + return; + } + onStateChange(FileTransfer::State(FileTransfer::State::Negotiating)); + + JingleIBBTransportPayload::ref ibbPayload; + JingleS5BTransportPayload::ref s5bPayload; + if ((ibbPayload = boost::dynamic_pointer_cast<JingleIBBTransportPayload>(transportPayload))) { + ibbSession = boost::make_shared<IBBSendSession>(ibbPayload->getSessionID(), fromJID, toJID, readStream, router); + ibbSession->setBlockSize(ibbPayload->getBlockSize()); + ibbSession->onBytesSent.connect(boost::bind(boost::ref(onProcessedBytes), _1)); + ibbSession->onFinished.connect(boost::bind(&OutgoingJingleFileTransfer::handleTransferFinished, this, _1)); + ibbSession->start(); + onStateChange(FileTransfer::State(FileTransfer::State::Transferring)); + } + else if ((s5bPayload = boost::dynamic_pointer_cast<JingleS5BTransportPayload>(transportPayload))) { + fillCandidateMap(theirCandidates, s5bPayload); + remoteCandidateSelector->setRequesterTargtet(toJID, session->getInitiator()); + remoteCandidateSelector->addRemoteTransportCandidates(s5bPayload); + remoteCandidateSelector->selectCandidate(); + } + else { + // TODO: error handling + SWIFT_LOG(debug) << "Unknown transport payload! Try replaceing with IBB." << std::endl; + replaceTransportWithIBB(id); + } +} + +void OutgoingJingleFileTransfer::handleSessionTerminateReceived(boost::optional<JinglePayload::Reason> reason) { + if (canceled) { + return; + } + + if (ibbSession) { + ibbSession->stop(); + } + if (clientSession) { + clientSession->stop(); + } + if (serverSession) { + serverSession->stop(); + } + + if (reason.is_initialized() && reason.get().type == JinglePayload::Reason::Cancel) { + onStateChange(FileTransfer::State(FileTransfer::State::Canceled)); + onFinished(FileTransferError(FileTransferError::PeerError)); + } else if (reason.is_initialized() && reason.get().type == JinglePayload::Reason::Success) { + onStateChange(FileTransfer::State(FileTransfer::State::Finished)); + onFinished(boost::optional<FileTransferError>()); + } else { + onStateChange(FileTransfer::State(FileTransfer::State::Failed)); + onFinished(FileTransferError(FileTransferError::PeerError)); + } + canceled = true; +} + +void OutgoingJingleFileTransfer::handleTransportAcceptReceived(const JingleContentID& /* contentID */, JingleTransportPayload::ref transport) { + if (canceled) { + return; + } + + if (JingleIBBTransportPayload::ref ibbPayload = boost::dynamic_pointer_cast<JingleIBBTransportPayload>(transport)) { + ibbSession = boost::make_shared<IBBSendSession>(ibbPayload->getSessionID(), fromJID, toJID, readStream, router); + ibbSession->setBlockSize(ibbPayload->getBlockSize()); + ibbSession->onBytesSent.connect(boost::bind(boost::ref(onProcessedBytes), _1)); + ibbSession->onFinished.connect(boost::bind(&OutgoingJingleFileTransfer::handleTransferFinished, this, _1)); + ibbSession->start(); + onStateChange(FileTransfer::State(FileTransfer::State::Transferring)); + } else { + // error handling + SWIFT_LOG(debug) << "Replacing with anything other than IBB isn't supported yet." << std::endl; + session->sendTerminate(JinglePayload::Reason::FailedTransport); + onStateChange(FileTransfer::State(FileTransfer::State::Failed)); + } +} + +void OutgoingJingleFileTransfer::startTransferViaOurCandidateChoice(JingleS5BTransportPayload::Candidate candidate) { + SWIFT_LOG(debug) << "Transferring data using our candidate." << std::endl; + if (candidate.type == JingleS5BTransportPayload::Candidate::ProxyType) { + // get proxy client session from remoteCandidateSelector + clientSession = remoteCandidateSelector->getS5BSession(); + + // wait on <activated/> transport-info + } else { + clientSession = remoteCandidateSelector->getS5BSession(); + clientSession->onBytesSent.connect(boost::bind(boost::ref(onProcessedBytes), _1)); + clientSession->onFinished.connect(boost::bind(&OutgoingJingleFileTransfer::handleTransferFinished, this, _1)); + clientSession->startSending(readStream); + onStateChange(FileTransfer::State(FileTransfer::State::Transferring)); + } + assert(clientSession); +} + +void OutgoingJingleFileTransfer::startTransferViaTheirCandidateChoice(JingleS5BTransportPayload::Candidate candidate) { + SWIFT_LOG(debug) << "Transferring data using their candidate." << std::endl; + if (candidate.type == JingleS5BTransportPayload::Candidate::ProxyType) { + // connect to proxy + clientSession = s5bProxy->createSOCKS5BytestreamClientSession(candidate.hostPort, SOCKS5BytestreamRegistry::getHostname(s5bSessionID, session->getInitiator(), toJID)); + clientSession->onSessionReady.connect(boost::bind(&OutgoingJingleFileTransfer::proxySessionReady, this, candidate.jid, _1)); + clientSession->start(); + + // on reply send activate + + } else { + serverSession = s5bRegistry->getConnectedSession(SOCKS5BytestreamRegistry::getHostname(s5bSessionID, session->getInitiator(), toJID)); + serverSession->onBytesSent.connect(boost::bind(boost::ref(onProcessedBytes), _1)); + serverSession->onFinished.connect(boost::bind(&OutgoingJingleFileTransfer::handleTransferFinished, this, _1)); + serverSession->startTransfer(); + } + onStateChange(FileTransfer::State(FileTransfer::State::Transferring)); +} + +// decide on candidates according to http://xmpp.org/extensions/xep-0260.html#complete +void OutgoingJingleFileTransfer::decideOnCandidates() { + if (ourCandidateChoice && theirCandidateChoice) { + std::string our_cid = ourCandidateChoice->getCandidateUsed(); + std::string their_cid = theirCandidateChoice->getCandidateUsed(); + if (ourCandidateChoice->hasCandidateError() && theirCandidateChoice->hasCandidateError()) { + replaceTransportWithIBB(contentID); + } + else if (!our_cid.empty() && theirCandidateChoice->hasCandidateError()) { + // use our candidate + startTransferViaOurCandidateChoice(theirCandidates[our_cid]); + } + else if (!their_cid.empty() && ourCandidateChoice->hasCandidateError()) { + // use their candidate + startTransferViaTheirCandidateChoice(ourCandidates[their_cid]); + } + else if (!our_cid.empty() && !their_cid.empty()) { + // compare priorites, if same we win + if (ourCandidates.find(their_cid) == ourCandidates.end() || theirCandidates.find(our_cid) == theirCandidates.end()) { + SWIFT_LOG(debug) << "Didn't recognize candidate IDs!" << std::endl; + session->sendTerminate(JinglePayload::Reason::FailedTransport); + onStateChange(FileTransfer::State(FileTransfer::State::Failed)); + onFinished(FileTransferError(FileTransferError::PeerError)); + return; + } + + JingleS5BTransportPayload::Candidate ourCandidate = theirCandidates[our_cid]; + JingleS5BTransportPayload::Candidate theirCandidate = ourCandidates[their_cid]; + if (ourCandidate.priority > theirCandidate.priority) { + startTransferViaOurCandidateChoice(ourCandidate); + } + else if (ourCandidate.priority < theirCandidate.priority) { + startTransferViaTheirCandidateChoice(theirCandidate); + } + else { + startTransferViaOurCandidateChoice(ourCandidate); + } + } + } else { + SWIFT_LOG(debug) << "Can't make a decision yet!" << std::endl; + } +} + +void OutgoingJingleFileTransfer::fillCandidateMap(CandidateMap& map, JingleS5BTransportPayload::ref s5bPayload) { + map.clear(); + foreach (JingleS5BTransportPayload::Candidate candidate, s5bPayload->getCandidates()) { + map[candidate.cid] = candidate; + } +} + +void OutgoingJingleFileTransfer::proxySessionReady(const JID& proxy, bool error) { + if (error) { + // indicate proxy error + } else { + // activate proxy + activateProxySession(proxy); + } +} + +void OutgoingJingleFileTransfer::activateProxySession(const JID& proxy) { + S5BProxyRequest::ref proxyRequest = boost::make_shared<S5BProxyRequest>(); + proxyRequest->setSID(s5bSessionID); + proxyRequest->setActivate(toJID); + + boost::shared_ptr<GenericRequest<S5BProxyRequest> > request = boost::make_shared<GenericRequest<S5BProxyRequest> >(IQ::Set, proxy, proxyRequest, router); + request->onResponse.connect(boost::bind(&OutgoingJingleFileTransfer::handleActivateProxySessionResult, this, _1, _2)); + request->send(); +} + +void OutgoingJingleFileTransfer::handleActivateProxySessionResult(boost::shared_ptr<S5BProxyRequest> /*request*/, ErrorPayload::ref error) { + if (error) { + SWIFT_LOG(debug) << "ERROR" << std::endl; + } else { + // send activated to other jingle party + JingleS5BTransportPayload::ref proxyActivate = boost::make_shared<JingleS5BTransportPayload>(); + proxyActivate->setActivated(theirCandidateChoice->getCandidateUsed()); + session->sendTransportInfo(contentID, proxyActivate); + + // start transferring + clientSession->onBytesSent.connect(boost::bind(boost::ref(onProcessedBytes), _1)); + clientSession->onFinished.connect(boost::bind(&OutgoingJingleFileTransfer::handleTransferFinished, this, _1)); + clientSession->startSending(readStream); + onStateChange(FileTransfer::State(FileTransfer::State::Transferring)); + } +} + +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()); + session->sendInfo(hashElement); +} + +void OutgoingJingleFileTransfer::handleTransportInfoReceived(const JingleContentID& /* contentID */, JingleTransportPayload::ref transport) { + if (canceled) { + return; + } + if (JingleS5BTransportPayload::ref s5bPayload = boost::dynamic_pointer_cast<JingleS5BTransportPayload>(transport)) { + if (s5bPayload->hasCandidateError() || !s5bPayload->getCandidateUsed().empty()) { + theirCandidateChoice = s5bPayload; + decideOnCandidates(); + } else if(!s5bPayload->getActivated().empty()) { + if (ourCandidateChoice->getCandidateUsed() == s5bPayload->getActivated()) { + clientSession->onBytesSent.connect(boost::bind(boost::ref(onProcessedBytes), _1)); + clientSession->onFinished.connect(boost::bind(&OutgoingJingleFileTransfer::handleTransferFinished, this, _1)); + clientSession->startSending(readStream); + onStateChange(FileTransfer::State(FileTransfer::State::Transferring)); + } else { + SWIFT_LOG(debug) << "ourCandidateChoice doesn't match activated proxy candidate!" << std::endl; + JingleS5BTransportPayload::ref proxyError = boost::make_shared<JingleS5BTransportPayload>(); + proxyError->setProxyError(true); + proxyError->setSessionID(s5bSessionID); + session->sendTransportInfo(contentID, proxyError); + } + } + } +} + +void OutgoingJingleFileTransfer::handleLocalTransportCandidatesGenerated(JingleTransportPayload::ref payload) { + if (canceled) { + return; + } + JingleFileTransferDescription::ref description = boost::make_shared<JingleFileTransferDescription>(); + description->addOffer(fileInfo); + + JingleTransportPayload::ref transport; + if (JingleIBBTransportPayload::ref ibbTransport = boost::dynamic_pointer_cast<JingleIBBTransportPayload>(payload)) { + ibbTransport->setBlockSize(4096); + ibbTransport->setSessionID(idGenerator->generateID()); + transport = ibbTransport; + } + else if (JingleS5BTransportPayload::ref s5bTransport = boost::dynamic_pointer_cast<JingleS5BTransportPayload>(payload)) { + //fillCandidateMap(ourCandidates, s5bTransport); + //s5bTransport->setSessionID(s5bSessionID); + + JingleS5BTransportPayload::ref emptyCandidates = boost::make_shared<JingleS5BTransportPayload>(); + emptyCandidates->setSessionID(s5bTransport->getSessionID()); + fillCandidateMap(ourCandidates, emptyCandidates); + + transport = emptyCandidates; + s5bRegistry->addReadBytestream(SOCKS5BytestreamRegistry::getHostname(s5bSessionID, session->getInitiator(), toJID), readStream); + } + else { + SWIFT_LOG(debug) << "Unknown tranport payload: " << typeid(*payload).name() << std::endl; + return; + } + session->sendInitiate(contentID, description, transport); + onStateChange(FileTransfer::State(FileTransfer::State::WaitingForAccept)); +} + +void OutgoingJingleFileTransfer::handleRemoteTransportCandidateSelectFinished(JingleTransportPayload::ref payload) { + if (canceled) { + return; + } + if (JingleS5BTransportPayload::ref s5bPayload = boost::dynamic_pointer_cast<JingleS5BTransportPayload>(payload)) { + ourCandidateChoice = s5bPayload; + session->sendTransportInfo(contentID, s5bPayload); + decideOnCandidates(); + } +} + +void OutgoingJingleFileTransfer::replaceTransportWithIBB(const JingleContentID& id) { + SWIFT_LOG(debug) << "Both parties failed. Replace transport with IBB." << std::endl; + JingleIBBTransportPayload::ref ibbTransport = boost::make_shared<JingleIBBTransportPayload>(); + ibbTransport->setBlockSize(4096); + ibbTransport->setSessionID(idGenerator->generateID()); + session->sendTransportReplace(id, ibbTransport); +} + +void OutgoingJingleFileTransfer::handleTransferFinished(boost::optional<FileTransferError> error) { + if (error) { + session->sendTerminate(JinglePayload::Reason::ConnectivityError); + onStateChange(FileTransfer::State(FileTransfer::State::Failed)); + onFinished(error); + } else { + sendSessionInfoHash(); + /* + session->terminate(JinglePayload::Reason::Success); + onStateChange(FileTransfer::State(FileTransfer::State::Finished)); + */ + } + // +} + +} diff --git a/Swiften/FileTransfer/OutgoingJingleFileTransfer.h b/Swiften/FileTransfer/OutgoingJingleFileTransfer.h new file mode 100644 index 0000000..ff7bfc7 --- /dev/null +++ b/Swiften/FileTransfer/OutgoingJingleFileTransfer.h @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <boost/shared_ptr.hpp> + +#include <Swiften/Elements/JingleContentPayload.h> +#include <Swiften/Elements/JingleS5BTransportPayload.h> +#include <Swiften/Elements/StreamInitiationFileInfo.h> +#include <Swiften/Elements/S5BProxyRequest.h> +#include <Swiften/Elements/ErrorPayload.h> +#include <Swiften/FileTransfer/OutgoingFileTransfer.h> +#include <Swiften/FileTransfer/SOCKS5BytestreamClientSession.h> +#include <Swiften/FileTransfer/SOCKS5BytestreamServerSession.h> +#include <Swiften/Jingle/JingleContentID.h> +#include <Swiften/Jingle/JingleSession.h> +#include <Swiften/StringCodecs/SHA1.h> + +namespace Swift { + +class RemoteJingleTransportCandidateSelectorFactory; +class RemoteJingleTransportCandidateSelector; +class LocalJingleTransportCandidateGeneratorFactory; +class LocalJingleTransportCandidateGenerator; +class IQRouter; +class ReadBytestream; +class IBBSendSession; +class IDGenerator; +class IncrementalBytestreamHashCalculator; +class SOCKS5BytestreamRegistry; +class SOCKS5BytestreamProxy; + +class OutgoingJingleFileTransfer : public OutgoingFileTransfer { +public: + OutgoingJingleFileTransfer(JingleSession::ref, + RemoteJingleTransportCandidateSelectorFactory*, + LocalJingleTransportCandidateGeneratorFactory*, + IQRouter*, + IDGenerator*, + const JID& from, + const JID& to, + boost::shared_ptr<ReadBytestream>, + const StreamInitiationFileInfo&, + SOCKS5BytestreamRegistry*, + SOCKS5BytestreamProxy*); + virtual ~OutgoingJingleFileTransfer(); + + void start(); + void stop(); + + void cancel(); + +private: + void handleSessionAcceptReceived(const JingleContentID&, JingleDescription::ref, JingleTransportPayload::ref); + void handleSessionTerminateReceived(boost::optional<JinglePayload::Reason> reason); + void handleTransportAcceptReceived(const JingleContentID&, JingleTransportPayload::ref); + void handleTransportInfoReceived(const JingleContentID&, JingleTransportPayload::ref); + + void handleLocalTransportCandidatesGenerated(JingleTransportPayload::ref); + void handleRemoteTransportCandidateSelectFinished(JingleTransportPayload::ref); + +private: + void replaceTransportWithIBB(const JingleContentID&); + void handleTransferFinished(boost::optional<FileTransferError>); + void activateProxySession(const JID &proxy); + void handleActivateProxySessionResult(boost::shared_ptr<S5BProxyRequest> request, ErrorPayload::ref error); + void proxySessionReady(const JID& proxy, bool error); + +private: + typedef std::map<std::string, JingleS5BTransportPayload::Candidate> CandidateMap; + +private: + void startTransferViaOurCandidateChoice(JingleS5BTransportPayload::Candidate); + void startTransferViaTheirCandidateChoice(JingleS5BTransportPayload::Candidate); + void decideOnCandidates(); + void fillCandidateMap(CandidateMap& map, JingleS5BTransportPayload::ref s5bPayload); + +private: + void sendSessionInfoHash(); + +private: + JingleSession::ref session; + RemoteJingleTransportCandidateSelector* remoteCandidateSelector; + RemoteJingleTransportCandidateSelectorFactory* remoteFactory; + LocalJingleTransportCandidateGenerator* localCandidateGenerator; + LocalJingleTransportCandidateGeneratorFactory* localFactory; + + IQRouter* router; + IDGenerator* idGenerator; + JID fromJID; + JID toJID; + boost::shared_ptr<ReadBytestream> readStream; + StreamInitiationFileInfo fileInfo; + IncrementalBytestreamHashCalculator *hashCalculator; + + boost::shared_ptr<IBBSendSession> ibbSession; + + JingleS5BTransportPayload::ref ourCandidateChoice; + JingleS5BTransportPayload::ref theirCandidateChoice; + CandidateMap ourCandidates; + CandidateMap theirCandidates; + + SOCKS5BytestreamRegistry* s5bRegistry; + SOCKS5BytestreamProxy* s5bProxy; + SOCKS5BytestreamClientSession::ref clientSession; + SOCKS5BytestreamServerSession* serverSession; + JingleContentID contentID; + std::string s5bSessionID; + + bool canceled; +}; + +} diff --git a/Swiften/FileTransfer/OutgoingSIFileTransfer.cpp b/Swiften/FileTransfer/OutgoingSIFileTransfer.cpp new file mode 100644 index 0000000..8a8237a --- /dev/null +++ b/Swiften/FileTransfer/OutgoingSIFileTransfer.cpp @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2010 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#include <Swiften/FileTransfer/OutgoingSIFileTransfer.h> + +#include <boost/bind.hpp> + +#include <Swiften/FileTransfer/StreamInitiationRequest.h> +#include <Swiften/FileTransfer/BytestreamsRequest.h> +#include <Swiften/FileTransfer/SOCKS5BytestreamServer.h> +#include <Swiften/FileTransfer/IBBSendSession.h> + +namespace Swift { + +OutgoingSIFileTransfer::OutgoingSIFileTransfer(const std::string& id, const JID& from, const JID& to, const std::string& name, int size, const std::string& description, boost::shared_ptr<ReadBytestream> bytestream, IQRouter* iqRouter, SOCKS5BytestreamServer* socksServer) : id(id), from(from), to(to), name(name), size(size), description(description), bytestream(bytestream), iqRouter(iqRouter), socksServer(socksServer) { +} + +void OutgoingSIFileTransfer::start() { + StreamInitiation::ref streamInitiation(new StreamInitiation()); + streamInitiation->setID(id); + streamInitiation->setFileInfo(StreamInitiationFileInfo(name, description, size)); + //streamInitiation->addProvidedMethod("http://jabber.org/protocol/bytestreams"); + streamInitiation->addProvidedMethod("http://jabber.org/protocol/ibb"); + StreamInitiationRequest::ref request = StreamInitiationRequest::create(to, streamInitiation, iqRouter); + request->onResponse.connect(boost::bind(&OutgoingSIFileTransfer::handleStreamInitiationRequestResponse, this, _1, _2)); + request->send(); +} + +void OutgoingSIFileTransfer::stop() { +} + +void OutgoingSIFileTransfer::handleStreamInitiationRequestResponse(StreamInitiation::ref response, ErrorPayload::ref error) { + if (error) { + finish(FileTransferError()); + } + else { + if (response->getRequestedMethod() == "http://jabber.org/protocol/bytestreams") { + socksServer->addReadBytestream(id, from, to, bytestream); + Bytestreams::ref bytestreams(new Bytestreams()); + bytestreams->setStreamID(id); + HostAddressPort addressPort = socksServer->getAddressPort(); + bytestreams->addStreamHost(Bytestreams::StreamHost(addressPort.getAddress().toString(), from, addressPort.getPort())); + BytestreamsRequest::ref request = BytestreamsRequest::create(to, bytestreams, iqRouter); + request->onResponse.connect(boost::bind(&OutgoingSIFileTransfer::handleBytestreamsRequestResponse, this, _1, _2)); + request->send(); + } + else if (response->getRequestedMethod() == "http://jabber.org/protocol/ibb") { + ibbSession = boost::shared_ptr<IBBSendSession>(new IBBSendSession(id, from, to, bytestream, iqRouter)); + ibbSession->onFinished.connect(boost::bind(&OutgoingSIFileTransfer::handleIBBSessionFinished, this, _1)); + ibbSession->start(); + } + } +} + +void OutgoingSIFileTransfer::handleBytestreamsRequestResponse(Bytestreams::ref, ErrorPayload::ref error) { + if (error) { + finish(FileTransferError()); + } + //socksServer->onTransferFinished.connect(); +} + +void OutgoingSIFileTransfer::finish(boost::optional<FileTransferError> error) { + if (ibbSession) { + ibbSession->onFinished.disconnect(boost::bind(&OutgoingSIFileTransfer::handleIBBSessionFinished, this, _1)); + ibbSession.reset(); + } + socksServer->removeReadBytestream(id, from, to); + onFinished(error); +} + +void OutgoingSIFileTransfer::handleIBBSessionFinished(boost::optional<FileTransferError> error) { + finish(error); +} + +} diff --git a/Swiften/FileTransfer/OutgoingSIFileTransfer.h b/Swiften/FileTransfer/OutgoingSIFileTransfer.h new file mode 100644 index 0000000..584eb60 --- /dev/null +++ b/Swiften/FileTransfer/OutgoingSIFileTransfer.h @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2010 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#pragma once + +#include <boost/shared_ptr.hpp> + +#include <Swiften/FileTransfer/OutgoingFileTransfer.h> +#include <Swiften/FileTransfer/ReadBytestream.h> +#include <Swiften/Base/boost_bsignals.h> +#include <Swiften/FileTransfer/FileTransferError.h> +#include <Swiften/FileTransfer/SOCKS5BytestreamServer.h> +#include <Swiften/JID/JID.h> +#include <Swiften/Elements/StreamInitiation.h> +#include <Swiften/Elements/Bytestreams.h> +#include <Swiften/Elements/ErrorPayload.h> +#include <Swiften/FileTransfer/IBBSendSession.h> + +namespace Swift { + class IQRouter; + class SOCKS5BytestreamServer; + + class OutgoingSIFileTransfer : public OutgoingFileTransfer { + public: + OutgoingSIFileTransfer(const std::string& id, const JID& from, const JID& to, const std::string& name, int size, const std::string& description, boost::shared_ptr<ReadBytestream> bytestream, IQRouter* iqRouter, SOCKS5BytestreamServer* socksServer); + + virtual void start(); + virtual void stop(); + + boost::signal<void (const boost::optional<FileTransferError>&)> onFinished; + + private: + void handleStreamInitiationRequestResponse(StreamInitiation::ref, ErrorPayload::ref); + void handleBytestreamsRequestResponse(Bytestreams::ref, ErrorPayload::ref); + void finish(boost::optional<FileTransferError> error); + void handleIBBSessionFinished(boost::optional<FileTransferError> error); + + private: + std::string id; + JID from; + JID to; + std::string name; + int size; + std::string description; + boost::shared_ptr<ReadBytestream> bytestream; + IQRouter* iqRouter; + SOCKS5BytestreamServer* socksServer; + boost::shared_ptr<IBBSendSession> ibbSession; + }; +} diff --git a/Swiften/FileTransfer/ReadBytestream.cpp b/Swiften/FileTransfer/ReadBytestream.cpp index 705906c..5fa10d8 100644 --- a/Swiften/FileTransfer/ReadBytestream.cpp +++ b/Swiften/FileTransfer/ReadBytestream.cpp @@ -4,7 +4,7 @@ * See Documentation/Licenses/GPLv3.txt for more information. */ -#include "Swiften/FileTransfer/ReadBytestream.h" +#include <Swiften/FileTransfer/ReadBytestream.h> namespace Swift { diff --git a/Swiften/FileTransfer/ReadBytestream.h b/Swiften/FileTransfer/ReadBytestream.h index 4da2bc2..c94e4d3 100644 --- a/Swiften/FileTransfer/ReadBytestream.h +++ b/Swiften/FileTransfer/ReadBytestream.h @@ -6,13 +6,26 @@ #pragma once -#include "Swiften/Base/ByteArray.h" +#include <boost/shared_ptr.hpp> +#include <vector> + +#include <Swiften/Base/boost_bsignals.h> namespace Swift { class ReadBytestream { public: virtual ~ReadBytestream(); - virtual ByteArray read(size_t size) = 0; + + /** + * Return an empty vector if no more data is available. + * Use onDataAvailable signal for signaling there is data available again. + */ + virtual boost::shared_ptr< std::vector<unsigned char> > read(size_t size) = 0; + virtual bool isFinished() const = 0; + + public: + boost::signal<void ()> onDataAvailable; + boost::signal<void (const std::vector<unsigned char>&)> onRead; }; } diff --git a/Swiften/FileTransfer/RemoteJingleTransportCandidateSelector.cpp b/Swiften/FileTransfer/RemoteJingleTransportCandidateSelector.cpp new file mode 100644 index 0000000..338f221 --- /dev/null +++ b/Swiften/FileTransfer/RemoteJingleTransportCandidateSelector.cpp @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2011 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#include <Swiften/FileTransfer/RemoteJingleTransportCandidateSelector.h> + +namespace Swift { + +RemoteJingleTransportCandidateSelector::~RemoteJingleTransportCandidateSelector() { +} + +} diff --git a/Swiften/FileTransfer/RemoteJingleTransportCandidateSelector.h b/Swiften/FileTransfer/RemoteJingleTransportCandidateSelector.h new file mode 100644 index 0000000..f8df8f9 --- /dev/null +++ b/Swiften/FileTransfer/RemoteJingleTransportCandidateSelector.h @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2011 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#pragma once + +#include <Swiften/Base/boost_bsignals.h> + +#include <Swiften/JID/JID.h> +#include <Swiften/Elements/JingleTransportPayload.h> +#include <Swiften/FileTransfer/JingleTransport.h> +#include <Swiften/FileTransfer/SOCKS5BytestreamClientSession.h> + +namespace Swift { + class RemoteJingleTransportCandidateSelector { + public: + virtual ~RemoteJingleTransportCandidateSelector(); + + virtual void addRemoteTransportCandidates(JingleTransportPayload::ref) = 0; + virtual void selectCandidate() = 0; + virtual void setMinimumPriority(int) = 0; + virtual void setRequesterTargtet(const JID&, const JID&) {} + virtual SOCKS5BytestreamClientSession::ref getS5BSession() { return SOCKS5BytestreamClientSession::ref(); } + + virtual bool isActualCandidate(JingleTransportPayload::ref) = 0; + virtual int getPriority(JingleTransportPayload::ref) = 0; + virtual JingleTransport::ref selectTransport(JingleTransportPayload::ref) = 0; + + boost::signal<void (JingleTransportPayload::ref)> onRemoteTransportCandidateSelectFinished; + }; +} diff --git a/Swiften/FileTransfer/RemoteJingleTransportCandidateSelectorFactory.cpp b/Swiften/FileTransfer/RemoteJingleTransportCandidateSelectorFactory.cpp new file mode 100644 index 0000000..36b7cba --- /dev/null +++ b/Swiften/FileTransfer/RemoteJingleTransportCandidateSelectorFactory.cpp @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2011 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#include <Swiften/FileTransfer/RemoteJingleTransportCandidateSelectorFactory.h> + +namespace Swift { + +RemoteJingleTransportCandidateSelectorFactory::~RemoteJingleTransportCandidateSelectorFactory() { +} + +} diff --git a/Swiften/FileTransfer/RemoteJingleTransportCandidateSelectorFactory.h b/Swiften/FileTransfer/RemoteJingleTransportCandidateSelectorFactory.h new file mode 100644 index 0000000..caa3097 --- /dev/null +++ b/Swiften/FileTransfer/RemoteJingleTransportCandidateSelectorFactory.h @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2011 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#pragma once + +namespace Swift { + class RemoteJingleTransportCandidateSelector; + + class RemoteJingleTransportCandidateSelectorFactory { + public: + virtual ~RemoteJingleTransportCandidateSelectorFactory(); + + virtual RemoteJingleTransportCandidateSelector* createCandidateSelector() = 0; + }; +} diff --git a/Swiften/FileTransfer/SConscript b/Swiften/FileTransfer/SConscript index ea9e7bb..4e79992 100644 --- a/Swiften/FileTransfer/SConscript +++ b/Swiften/FileTransfer/SConscript @@ -1,19 +1,48 @@ -Import("swiften_env") +Import("swiften_env", "env") sources = [ "OutgoingFileTransfer.cpp", + "OutgoingSIFileTransfer.cpp", + "OutgoingJingleFileTransfer.cpp", + "OutgoingFileTransferManager.cpp", "IncomingFileTransfer.cpp", "IncomingJingleFileTransfer.cpp", - "IncomingFileTransferManager.cpp", + "IncomingFileTransferManager.cpp", + "RemoteJingleTransportCandidateSelector.cpp", + "RemoteJingleTransportCandidateSelectorFactory.cpp", + "LocalJingleTransportCandidateGenerator.cpp", + "LocalJingleTransportCandidateGeneratorFactory.cpp", + "DefaultRemoteJingleTransportCandidateSelectorFactory.cpp", + "DefaultLocalJingleTransportCandidateGeneratorFactory.cpp", + "DefaultRemoteJingleTransportCandidateSelector.cpp", + "DefaultLocalJingleTransportCandidateGenerator.cpp", + "JingleTransport.cpp", + "JingleIncomingIBBTransport.cpp", "ReadBytestream.cpp", "WriteBytestream.cpp", "FileReadBytestream.cpp", "FileWriteBytestream.cpp", + "SOCKS5BytestreamClientSession.cpp", "SOCKS5BytestreamServer.cpp", "SOCKS5BytestreamServerSession.cpp", "SOCKS5BytestreamRegistry.cpp", + "SOCKS5BytestreamProxy.cpp", + "SOCKS5BytestreamProxyFinder.cpp", "IBBSendSession.cpp", "IBBReceiveSession.cpp", + "FileTransferManager.cpp", + "FileTransferManagerImpl.cpp", + "IncrementalBytestreamHashCalculator.cpp", + "ConnectivityManager.cpp", ] swiften_env.Append(SWIFTEN_OBJECTS = swiften_env.SwiftenObject(sources)) + +env.Append(UNITTEST_SOURCES = [ + File("UnitTest/SOCKS5BytestreamServerSessionTest.cpp"), + File("UnitTest/SOCKS5BytestreamClientSessionTest.cpp"), + File("UnitTest/IBBSendSessionTest.cpp"), + File("UnitTest/IBBReceiveSessionTest.cpp"), + File("UnitTest/IncomingJingleFileTransferTest.cpp"), + File("UnitTest/OutgoingJingleFileTransferTest.cpp"), + ]) diff --git a/Swiften/FileTransfer/SOCKS5BytestreamClientSession.cpp b/Swiften/FileTransfer/SOCKS5BytestreamClientSession.cpp new file mode 100644 index 0000000..cd555e5 --- /dev/null +++ b/Swiften/FileTransfer/SOCKS5BytestreamClientSession.cpp @@ -0,0 +1,236 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include "SOCKS5BytestreamClientSession.h" + +#include <boost/bind.hpp> + +#include <Swiften/Base/Algorithm.h> +#include <Swiften/Base/SafeByteArray.h> +#include <Swiften/Base/Concat.h> +#include <Swiften/Base/Log.h> +#include <Swiften/StringCodecs/SHA1.h> +#include <Swiften/StringCodecs/Hexify.h> +#include <Swiften/FileTransfer/BytestreamException.h> +#include <Swiften/Network/TimerFactory.h> +#include <Swiften/Base/ByteArray.h> + +namespace Swift { + +SOCKS5BytestreamClientSession::SOCKS5BytestreamClientSession(boost::shared_ptr<Connection> connection, const HostAddressPort& addressPort, const std::string& destination, TimerFactory* timerFactory) : + connection(connection), addressPort(addressPort), destination(destination), state(Initial), chunkSize(131072) { + connection->onConnectFinished.connect(boost::bind(&SOCKS5BytestreamClientSession::handleConnectFinished, this, _1)); + connection->onDisconnected.connect(boost::bind(&SOCKS5BytestreamClientSession::handleDisconnected, this, _1)); + weFailedTimeout = timerFactory->createTimer(2000); + weFailedTimeout->onTick.connect(boost::bind(&SOCKS5BytestreamClientSession::handleWeFailedTimeout, this)); +} + +void SOCKS5BytestreamClientSession::start() { + assert(state == Initial); + SWIFT_LOG(debug) << "Trying to connect via TCP to " << addressPort.toString() << "." << std::endl; + weFailedTimeout->start(); + connection->connect(addressPort); +} + +void SOCKS5BytestreamClientSession::stop() { + connection->disconnect(); + connection->onDataWritten.disconnect(boost::bind(&SOCKS5BytestreamClientSession::sendData, this)); + connection->onDataRead.disconnect(boost::bind(&SOCKS5BytestreamClientSession::handleDataRead, this, _1)); + readBytestream.reset(); + state = Finished; +} + +void SOCKS5BytestreamClientSession::process() { + SWIFT_LOG(debug) << "unprocessedData.size(): " << unprocessedData.size() << std::endl; + ByteArray bndAddress; + switch(state) { + case Initial: + hello(); + break; + case Hello: + if (unprocessedData.size() > 1) { + unsigned char version = unprocessedData[0]; + unsigned char authMethod = unprocessedData[1]; + if (version != 5 || authMethod != 0) { + // signal failure to upper level + finish(true); + return; + } + unprocessedData.clear(); + authenticate(); + } + break; + case Authenticating: + if (unprocessedData.size() < 5) { + // need more data to start progressing + break; + } + if (unprocessedData[0] != '\x05') { + // wrong version + // disconnect & signal failure + finish(true); + break; + } + if (unprocessedData[1] != '\x00') { + // no success + // disconnect & signal failure + finish(true); + break; + } + if (unprocessedData[3] != '\x03') { + // we expect x'03' = DOMAINNAME here + // discconect & signal failure + finish(true); + break; + } + if (static_cast<size_t>(unprocessedData[4]) + 1 > unprocessedData.size() + 5) { + // complete domainname and port not available yet + break; + } + bndAddress = createByteArray(&vecptr(unprocessedData)[5], unprocessedData[4]); + if (unprocessedData[unprocessedData[4] + 5] != 0 && bndAddress == createByteArray(destination)) { + // we expect a 0 as port + // disconnect and fail + finish(true); + } + unprocessedData.clear(); + state = Ready; + SWIFT_LOG(debug) << "session ready" << std::endl; + // issue ready signal so the bytestream can be used for reading or writing + weFailedTimeout->stop(); + onSessionReady(false); + break; + case Ready: + SWIFT_LOG(debug) << "Received further data in Ready state." << std::endl; + break; + case Reading: + case Writing: + case Finished: + SWIFT_LOG(debug) << "Unexpected receive of data. Current state: " << state << std::endl; + SWIFT_LOG(debug) << "Data: " << Hexify::hexify(unprocessedData) << std::endl; + unprocessedData.clear(); + //assert(false); + } +} + +void SOCKS5BytestreamClientSession::hello() { + // Version 5, 1 auth method, No authentication + const SafeByteArray hello = createSafeByteArray("\x05\x01\x00", 3); + connection->write(hello); + state = Hello; +} + +void SOCKS5BytestreamClientSession::authenticate() { + SWIFT_LOG(debug) << std::endl; + SafeByteArray header = createSafeByteArray("\x05\x01\x00\x03", 4); + SafeByteArray message = header; + append(message, createSafeByteArray(destination.size())); + authenticateAddress = createByteArray(destination); + append(message, authenticateAddress); + append(message, createSafeByteArray("\x00\x00", 2)); // 2 byte for port + connection->write(message); + state = Authenticating; +} + +void SOCKS5BytestreamClientSession::startReceiving(boost::shared_ptr<WriteBytestream> writeStream) { + if (state == Ready) { + state = Reading; + writeBytestream = writeStream; + writeBytestream->write(unprocessedData); + onBytesReceived(unprocessedData.size()); + unprocessedData.clear(); + } else { + SWIFT_LOG(debug) << "Session isn't ready for transfer yet!" << std::endl; + } +} + +void SOCKS5BytestreamClientSession::startSending(boost::shared_ptr<ReadBytestream> readStream) { + if (state == Ready) { + state = Writing; + readBytestream = readStream; + connection->onDataWritten.connect(boost::bind(&SOCKS5BytestreamClientSession::sendData, this)); + sendData(); + } else { + SWIFT_LOG(debug) << "Session isn't ready for transfer yet!" << std::endl; + } +} + +HostAddressPort SOCKS5BytestreamClientSession::getAddressPort() const { + return addressPort; +} + +void SOCKS5BytestreamClientSession::sendData() { + if (!readBytestream->isFinished()) { + try { + boost::shared_ptr<ByteArray> dataToSend = readBytestream->read(chunkSize); + connection->write(createSafeByteArray(*dataToSend)); + onBytesSent(dataToSend->size()); + } + catch (const BytestreamException&) { + finish(true); + } + } + else { + finish(false); + } +} + +void SOCKS5BytestreamClientSession::finish(bool error) { + weFailedTimeout->stop(); + connection->disconnect(); + connection->onDataWritten.disconnect(boost::bind(&SOCKS5BytestreamClientSession::sendData, this)); + connection->onDataRead.disconnect(boost::bind(&SOCKS5BytestreamClientSession::handleDataRead, this, _1)); + readBytestream.reset(); + if (state == Initial || state == Hello || state == Authenticating) { + onSessionReady(true); + } + else { + state = Finished; + if (error) { + onFinished(boost::optional<FileTransferError>(FileTransferError::ReadError)); + } else { + onFinished(boost::optional<FileTransferError>()); + } + } +} + +void SOCKS5BytestreamClientSession::handleConnectFinished(bool error) { + if (error) { + SWIFT_LOG(debug) << "Failed to connect via TCP to " << addressPort.toString() << "." << std::endl; + finish(true); + } else { + SWIFT_LOG(debug) << "Successfully connected via TCP" << addressPort.toString() << "." << std::endl; + weFailedTimeout->start(); + connection->onDataRead.connect(boost::bind(&SOCKS5BytestreamClientSession::handleDataRead, this, _1)); + process(); + } +} + +void SOCKS5BytestreamClientSession::handleDataRead(boost::shared_ptr<SafeByteArray> data) { + SWIFT_LOG(debug) << "state: " << state << " data.size() = " << data->size() << std::endl; + if (state != Reading) { + append(unprocessedData, *data); + process(); + } + else { + writeBytestream->write(createByteArray(vecptr(*data), data->size())); + onBytesReceived(data->size()); + } +} + +void SOCKS5BytestreamClientSession::handleDisconnected(const boost::optional<Connection::Error>& error) { + SWIFT_LOG(debug) << (error ? (error == Connection::ReadError ? "Read Error" : "Write Error") : "No Error") << std::endl; + if (error) { + finish(true); + } +} + +void SOCKS5BytestreamClientSession::handleWeFailedTimeout() { + SWIFT_LOG(debug) << "Failed due to timeout!" << std::endl; + finish(true); +} + +} diff --git a/Swiften/FileTransfer/SOCKS5BytestreamClientSession.h b/Swiften/FileTransfer/SOCKS5BytestreamClientSession.h new file mode 100644 index 0000000..ea45955 --- /dev/null +++ b/Swiften/FileTransfer/SOCKS5BytestreamClientSession.h @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <boost/shared_ptr.hpp> +#include <boost/optional.hpp> + +#include <Swiften/Base/ByteArray.h> +#include <Swiften/FileTransfer/FileTransferError.h> +#include <Swiften/FileTransfer/WriteBytestream.h> +#include <Swiften/FileTransfer/ReadBytestream.h> +#include <Swiften/JID/JID.h> +#include <Swiften/Network/Connection.h> +#include <Swiften/Network/HostAddressPort.h> +#include <Swiften/Network/Timer.h> + +namespace Swift { + +class SOCKS5BytestreamRegistry; +class Connection; +class TimerFactory; + +/** + * A session which has been connected to a SOCKS5 server (requester). + * + */ +class SOCKS5BytestreamClientSession { +public: + enum State { + Initial, + Hello, + Authenticating, + Ready, + Writing, + Reading, + Finished, + }; + +public: + typedef boost::shared_ptr<SOCKS5BytestreamClientSession> ref; + +public: + SOCKS5BytestreamClientSession(boost::shared_ptr<Connection> connection, const HostAddressPort&, const std::string&, TimerFactory*); + + void start(); + void stop(); + + void startReceiving(boost::shared_ptr<WriteBytestream>); + void startSending(boost::shared_ptr<ReadBytestream>); + + HostAddressPort getAddressPort() const; + + boost::signal<void (bool /*error*/)> onSessionReady; + + boost::signal<void (boost::optional<FileTransferError>)> onFinished; + boost::signal<void (int)> onBytesSent; + boost::signal<void (int)> onBytesReceived; + +private: + void process(); + void hello(); + void authenticate(); + + void handleConnectFinished(bool error); + void handleDataRead(boost::shared_ptr<SafeByteArray>); + void handleDisconnected(const boost::optional<Connection::Error>&); + void handleWeFailedTimeout(); + + void finish(bool error); + void sendData(); + +private: + boost::shared_ptr<Connection> connection; + HostAddressPort addressPort; + std::string destination; // hexify(SHA1(sessionID + requester + target)) + + State state; + int destinationPort; + + ByteArray unprocessedData; + ByteArray authenticateAddress; + + int chunkSize; + boost::shared_ptr<WriteBytestream> writeBytestream; + boost::shared_ptr<ReadBytestream> readBytestream; + + Timer::ref weFailedTimeout; +}; + +} diff --git a/Swiften/FileTransfer/SOCKS5BytestreamProxy.cpp b/Swiften/FileTransfer/SOCKS5BytestreamProxy.cpp new file mode 100644 index 0000000..9599fd1 --- /dev/null +++ b/Swiften/FileTransfer/SOCKS5BytestreamProxy.cpp @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include "SOCKS5BytestreamProxy.h" + +#include <boost/smart_ptr/make_shared.hpp> + +#include <Swiften/Base/foreach.h> +#include <Swiften/FileTransfer/SOCKS5BytestreamClientSession.h> +#include <Swiften/Base/Log.h> + +namespace Swift { + +SOCKS5BytestreamProxy::SOCKS5BytestreamProxy(ConnectionFactory *connFactory, TimerFactory *timeFactory) : connectionFactory(connFactory), timerFactory(timeFactory) { + +} + +void SOCKS5BytestreamProxy::addS5BProxy(S5BProxyRequest::ref proxy) { + localS5BProxies.push_back(proxy); +} + +const std::vector<S5BProxyRequest::ref>& SOCKS5BytestreamProxy::getS5BProxies() const { + return localS5BProxies; +} + +void SOCKS5BytestreamProxy::connectToProxies(const std::string& sessionID) { + SWIFT_LOG(debug) << "session ID: " << sessionID << std::endl; + ProxyJIDClientSessionMap clientSessions; + + foreach(S5BProxyRequest::ref proxy, localS5BProxies) { + boost::shared_ptr<Connection> conn = connectionFactory->createConnection(); + + boost::shared_ptr<SOCKS5BytestreamClientSession> session = boost::make_shared<SOCKS5BytestreamClientSession>(conn, proxy->getStreamHost().get().addressPort, sessionID, timerFactory); + clientSessions[proxy->getStreamHost().get().jid] = session; + session->start(); + } + + proxySessions[sessionID] = clientSessions; +} + +boost::shared_ptr<SOCKS5BytestreamClientSession> SOCKS5BytestreamProxy::getProxySessionAndCloseOthers(const JID& proxyJID, const std::string& sessionID) { + // checking parameters + if (proxySessions.find(sessionID) == proxySessions.end()) { + return boost::shared_ptr<SOCKS5BytestreamClientSession>(); + } + if (proxySessions[sessionID].find(proxyJID) == proxySessions[sessionID].end()) { + return boost::shared_ptr<SOCKS5BytestreamClientSession>(); + } + + // get active session + boost::shared_ptr<SOCKS5BytestreamClientSession> activeSession = proxySessions[sessionID][proxyJID]; + proxySessions[sessionID].erase(proxyJID); + + // close other sessions + foreach(const ProxyJIDClientSessionMap::value_type& myPair, proxySessions[sessionID]) { + myPair.second->stop(); + } + + proxySessions.erase(sessionID); + + return activeSession; +} + +boost::shared_ptr<SOCKS5BytestreamClientSession> SOCKS5BytestreamProxy::createSOCKS5BytestreamClientSession(HostAddressPort addressPort, const std::string& destAddr) { + SOCKS5BytestreamClientSession::ref connection = boost::make_shared<SOCKS5BytestreamClientSession>(connectionFactory->createConnection(), addressPort, destAddr, timerFactory); + return connection; +} + +} diff --git a/Swiften/FileTransfer/SOCKS5BytestreamProxy.h b/Swiften/FileTransfer/SOCKS5BytestreamProxy.h new file mode 100644 index 0000000..8f80cea --- /dev/null +++ b/Swiften/FileTransfer/SOCKS5BytestreamProxy.h @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <string> +#include <vector> + +#include <Swiften/Elements/S5BProxyRequest.h> +#include <Swiften/FileTransfer/SOCKS5BytestreamClientSession.h> +#include <Swiften/JID/JID.h> +#include <Swiften/Network/ConnectionFactory.h> +#include <Swiften/Network/TimerFactory.h> + +namespace Swift { + +/** + * - manages list of working S5B proxies + * - creates initial connections (for the candidates you provide) + */ +class SOCKS5BytestreamProxy { +public: + SOCKS5BytestreamProxy(ConnectionFactory*, TimerFactory*); + + void addS5BProxy(S5BProxyRequest::ref); + const std::vector<S5BProxyRequest::ref>& getS5BProxies() const; + + void connectToProxies(const std::string& sessionID); + boost::shared_ptr<SOCKS5BytestreamClientSession> getProxySessionAndCloseOthers(const JID& proxyJID, const std::string& sessionID); + + boost::shared_ptr<SOCKS5BytestreamClientSession> createSOCKS5BytestreamClientSession(HostAddressPort addressPort, const std::string& destAddr); + +private: + ConnectionFactory* connectionFactory; + TimerFactory* timerFactory; + + typedef std::map<JID, boost::shared_ptr<SOCKS5BytestreamClientSession> > ProxyJIDClientSessionMap; + std::map<std::string, ProxyJIDClientSessionMap> proxySessions; + + std::vector<S5BProxyRequest::ref> localS5BProxies; +}; + +} diff --git a/Swiften/FileTransfer/SOCKS5BytestreamProxyFinder.cpp b/Swiften/FileTransfer/SOCKS5BytestreamProxyFinder.cpp new file mode 100644 index 0000000..9d7505b --- /dev/null +++ b/Swiften/FileTransfer/SOCKS5BytestreamProxyFinder.cpp @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include <Swiften/FileTransfer/SOCKS5BytestreamProxyFinder.h> + +#include <boost/smart_ptr/make_shared.hpp> +#include <boost/bind.hpp> + +#include <Swiften/Base/Log.h> +#include <Swiften/Elements/S5BProxyRequest.h> +#include <Swiften/Queries/GenericRequest.h> +#include <Swiften/Queries/IQRouter.h> + +namespace Swift { + +SOCKS5BytestreamProxyFinder::SOCKS5BytestreamProxyFinder(const JID& service, IQRouter *iqRouter) : service(service), iqRouter(iqRouter) { +} + +SOCKS5BytestreamProxyFinder::~SOCKS5BytestreamProxyFinder() { +} + +void SOCKS5BytestreamProxyFinder::start() { + serviceWalker = boost::make_shared<DiscoServiceWalker>(service, iqRouter); + serviceWalker->onServiceFound.connect(boost::bind(&SOCKS5BytestreamProxyFinder::handleServiceFound, this, _1, _2)); + serviceWalker->beginWalk(); +} + +void SOCKS5BytestreamProxyFinder::stop() { + serviceWalker->endWalk(); + serviceWalker->onServiceFound.disconnect(boost::bind(&SOCKS5BytestreamProxyFinder::handleServiceFound, this, _1, _2)); + serviceWalker.reset(); +} + +void SOCKS5BytestreamProxyFinder::sendBytestreamQuery(const JID& jid) { + S5BProxyRequest::ref proxyRequest = boost::make_shared<S5BProxyRequest>(); + boost::shared_ptr<GenericRequest<S5BProxyRequest> > request = boost::make_shared<GenericRequest<S5BProxyRequest> >(IQ::Get, jid, proxyRequest, iqRouter); + request->onResponse.connect(boost::bind(&SOCKS5BytestreamProxyFinder::handleProxyResponse, this, _1, _2)); + request->send(); +} + +void SOCKS5BytestreamProxyFinder::handleServiceFound(const JID& jid, boost::shared_ptr<DiscoInfo> discoInfo) { + if (discoInfo->hasFeature(DiscoInfo::Bytestream)) { + sendBytestreamQuery(jid); + } +} + +void SOCKS5BytestreamProxyFinder::handleProxyResponse(boost::shared_ptr<S5BProxyRequest> request, ErrorPayload::ref error) { + if (error) { + SWIFT_LOG(debug) << "ERROR" << std::endl; + } else { + if (request) { + onProxyFound(request); + } else { + //assert(false); + } + } +} + +} diff --git a/Swiften/FileTransfer/SOCKS5BytestreamProxyFinder.h b/Swiften/FileTransfer/SOCKS5BytestreamProxyFinder.h new file mode 100644 index 0000000..071e03a --- /dev/null +++ b/Swiften/FileTransfer/SOCKS5BytestreamProxyFinder.h @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <boost/shared_ptr.hpp> + +#include <Swiften/Disco/DiscoServiceWalker.h> +#include <Swiften/Network/HostAddressPort.h> +#include <Swiften/Elements/S5BProxyRequest.h> + +namespace Swift { + +class JID; +class IQRouter; + +class SOCKS5BytestreamProxyFinder { +public: + SOCKS5BytestreamProxyFinder(const JID& service, IQRouter *iqRouter); + ~SOCKS5BytestreamProxyFinder(); + + void start(); + void stop(); + + boost::signal<void(boost::shared_ptr<S5BProxyRequest>)> onProxyFound; + +private: + void sendBytestreamQuery(const JID&); + + void handleServiceFound(const JID&, boost::shared_ptr<DiscoInfo>); + void handleProxyResponse(boost::shared_ptr<S5BProxyRequest>, ErrorPayload::ref); +private: + JID service; + IQRouter* iqRouter; + boost::shared_ptr<DiscoServiceWalker> serviceWalker; + std::vector<boost::shared_ptr<GenericRequest<S5BProxyRequest> > > requests; +}; + +} diff --git a/Swiften/FileTransfer/SOCKS5BytestreamRegistry.cpp b/Swiften/FileTransfer/SOCKS5BytestreamRegistry.cpp index 7f889b1..ffc4298 100644 --- a/Swiften/FileTransfer/SOCKS5BytestreamRegistry.cpp +++ b/Swiften/FileTransfer/SOCKS5BytestreamRegistry.cpp @@ -4,29 +4,69 @@ * See Documentation/Licenses/GPLv3.txt for more information. */ -#include "Swiften/FileTransfer/SOCKS5BytestreamRegistry.h" +#include <Swiften/FileTransfer/SOCKS5BytestreamRegistry.h> + +#include <boost/smart_ptr/make_shared.hpp> + +#include <Swiften/Base/Algorithm.h> +#include <Swiften/Base/Log.h> +#include <Swiften/Base/foreach.h> +#include <Swiften/StringCodecs/SHA1.h> +#include <Swiften/StringCodecs/Hexify.h> namespace Swift { SOCKS5BytestreamRegistry::SOCKS5BytestreamRegistry() { } -void SOCKS5BytestreamRegistry::addBytestream(const std::string& destination, boost::shared_ptr<ReadBytestream> byteStream) { - byteStreams[destination] = byteStream; +void SOCKS5BytestreamRegistry::addReadBytestream(const std::string& destination, boost::shared_ptr<ReadBytestream> byteStream) { + readBytestreams[destination] = byteStream; } -void SOCKS5BytestreamRegistry::removeBytestream(const std::string& destination) { - byteStreams.erase(destination); +void SOCKS5BytestreamRegistry::removeReadBytestream(const std::string& destination) { + readBytestreams.erase(destination); } -boost::shared_ptr<ReadBytestream> SOCKS5BytestreamRegistry::getBytestream(const std::string& destination) const { - BytestreamMap::const_iterator i = byteStreams.find(destination); - if (i != byteStreams.end()) { +boost::shared_ptr<ReadBytestream> SOCKS5BytestreamRegistry::getReadBytestream(const std::string& destination) const { + ReadBytestreamMap::const_iterator i = readBytestreams.find(destination); + if (i != readBytestreams.end()) { return i->second; } return boost::shared_ptr<ReadBytestream>(); } +void SOCKS5BytestreamRegistry::addWriteBytestream(const std::string& destination, boost::shared_ptr<WriteBytestream> byteStream) { + writeBytestreams[destination] = byteStream; +} + +void SOCKS5BytestreamRegistry::removeWriteBytestream(const std::string& destination) { + writeBytestreams.erase(destination); +} + +boost::shared_ptr<WriteBytestream> SOCKS5BytestreamRegistry::getWriteBytestream(const std::string& destination) const { + WriteBytestreamMap::const_iterator i = writeBytestreams.find(destination); + if (i != writeBytestreams.end()) { + return i->second; + } + return boost::shared_ptr<WriteBytestream>(); +} + +std::string SOCKS5BytestreamRegistry::generateSessionID() { + return idGenerator.generateID(); +} + +SOCKS5BytestreamServerSession* SOCKS5BytestreamRegistry::getConnectedSession(const std::string& destination) { + if (serverSessions.find(destination) != serverSessions.end()) { + return serverSessions[destination]; + } else { + SWIFT_LOG(debug) << "No active connction for stream ID " << destination << " found!" << std::endl; + return NULL; + } +} + +std::string SOCKS5BytestreamRegistry::getHostname(const std::string& sessionID, const JID& requester, const JID& target) { + return Hexify::hexify(SHA1::getHash(createSafeByteArray(sessionID + requester.toString() + target.toString()))); +} } diff --git a/Swiften/FileTransfer/SOCKS5BytestreamRegistry.h b/Swiften/FileTransfer/SOCKS5BytestreamRegistry.h index 7cee256..779aabb 100644 --- a/Swiften/FileTransfer/SOCKS5BytestreamRegistry.h +++ b/Swiften/FileTransfer/SOCKS5BytestreamRegistry.h @@ -7,23 +7,58 @@ #pragma once #include <boost/shared_ptr.hpp> -#include <map> +#include <map> #include <string> -#include "Swiften/FileTransfer/ReadBytestream.h" +#include <vector> +#include <set> + +#include <Swiften/Base/IDGenerator.h> +#include <Swiften/JID/JID.h> +#include <Swiften/Elements/S5BProxyRequest.h> +#include <Swiften/FileTransfer/ReadBytestream.h> +#include <Swiften/FileTransfer/WriteBytestream.h> +#include <Swiften/FileTransfer/SOCKS5BytestreamServerSession.h> +#include <Swiften/FileTransfer/SOCKS5BytestreamClientSession.h> +#include <Swiften/Network/HostAddressPort.h> namespace Swift { class SOCKS5BytestreamRegistry { public: SOCKS5BytestreamRegistry(); - boost::shared_ptr<ReadBytestream> getBytestream(const std::string& destination) const; - void addBytestream(const std::string& destination, boost::shared_ptr<ReadBytestream> byteStream); - void removeBytestream(const std::string& destination); + boost::shared_ptr<ReadBytestream> getReadBytestream(const std::string& destination) const; + void addReadBytestream(const std::string& destination, boost::shared_ptr<ReadBytestream> byteStream); + void removeReadBytestream(const std::string& destination); + + boost::shared_ptr<WriteBytestream> getWriteBytestream(const std::string& destination) const; + void addWriteBytestream(const std::string& destination, boost::shared_ptr<WriteBytestream> byteStream); + void removeWriteBytestream(const std::string& destination); + + /** + * Generate a new session ID to use for new S5B streams. + */ + std::string generateSessionID(); + + /** + * Start an actual transfer. + */ + SOCKS5BytestreamServerSession* getConnectedSession(const std::string& destination); + + public: + static std::string getHostname(const std::string& sessionID, const JID& requester, const JID& target); private: - typedef std::map<std::string, boost::shared_ptr<ReadBytestream> > BytestreamMap; - BytestreamMap byteStreams; + friend class SOCKS5BytestreamServerSession; + + typedef std::map<std::string, boost::shared_ptr<ReadBytestream> > ReadBytestreamMap; + ReadBytestreamMap readBytestreams; + + typedef std::map<std::string, boost::shared_ptr<WriteBytestream> > WriteBytestreamMap; + WriteBytestreamMap writeBytestreams; + + std::map<std::string, SOCKS5BytestreamServerSession*> serverSessions; + + IDGenerator idGenerator; }; } - diff --git a/Swiften/FileTransfer/SOCKS5BytestreamServer.cpp b/Swiften/FileTransfer/SOCKS5BytestreamServer.cpp index 9bc49ae..90fed7a 100644 --- a/Swiften/FileTransfer/SOCKS5BytestreamServer.cpp +++ b/Swiften/FileTransfer/SOCKS5BytestreamServer.cpp @@ -4,17 +4,19 @@ * See Documentation/Licenses/GPLv3.txt for more information. */ -#include "Swiften/FileTransfer/SOCKS5BytestreamServer.h" +#include <Swiften/FileTransfer/SOCKS5BytestreamServer.h> #include <boost/bind.hpp> -#include "Swiften/StringCodecs/Hexify.h" -#include "Swiften/StringCodecs/SHA1.h" -#include "Swiften/FileTransfer/SOCKS5BytestreamServerSession.h" +#include <Swiften/Base/Log.h> +#include <Swiften/StringCodecs/Hexify.h> +#include <Swiften/StringCodecs/SHA1.h> +#include <Swiften/FileTransfer/SOCKS5BytestreamServerSession.h> +#include <Swiften/FileTransfer/SOCKS5BytestreamRegistry.h> namespace Swift { -SOCKS5BytestreamServer::SOCKS5BytestreamServer(boost::shared_ptr<ConnectionServer> connectionServer) : connectionServer(connectionServer) { +SOCKS5BytestreamServer::SOCKS5BytestreamServer(boost::shared_ptr<ConnectionServer> connectionServer, SOCKS5BytestreamRegistry* registry) : connectionServer(connectionServer), registry(registry) { } void SOCKS5BytestreamServer::start() { @@ -25,20 +27,20 @@ void SOCKS5BytestreamServer::stop() { connectionServer->onNewConnection.disconnect(boost::bind(&SOCKS5BytestreamServer::handleNewConnection, this, _1)); } -void SOCKS5BytestreamServer::addBytestream(const std::string& id, const JID& from, const JID& to, boost::shared_ptr<ReadBytestream> byteStream) { - bytestreams.addBytestream(getSOCKSDestinationAddress(id, from, to), byteStream); +void SOCKS5BytestreamServer::addReadBytestream(const std::string& id, const JID& from, const JID& to, boost::shared_ptr<ReadBytestream> byteStream) { + registry->addReadBytestream(getSOCKSDestinationAddress(id, from, to), byteStream); } -void SOCKS5BytestreamServer::removeBytestream(const std::string& id, const JID& from, const JID& to) { - bytestreams.removeBytestream(getSOCKSDestinationAddress(id, from, to)); +void SOCKS5BytestreamServer::removeReadBytestream(const std::string& id, const JID& from, const JID& to) { + registry->removeReadBytestream(getSOCKSDestinationAddress(id, from, to)); } std::string SOCKS5BytestreamServer::getSOCKSDestinationAddress(const std::string& id, const JID& from, const JID& to) { - return Hexify::hexify(SHA1::getHash(ByteArray(id + from.toString() + to.toString()))); + return Hexify::hexify(SHA1::getHash(createByteArray(id + from.toString() + to.toString()))); } void SOCKS5BytestreamServer::handleNewConnection(boost::shared_ptr<Connection> connection) { - boost::shared_ptr<SOCKS5BytestreamServerSession> session(new SOCKS5BytestreamServerSession(connection, &bytestreams)); + boost::shared_ptr<SOCKS5BytestreamServerSession> session(new SOCKS5BytestreamServerSession(connection, registry)); sessions.push_back(session); session->start(); } diff --git a/Swiften/FileTransfer/SOCKS5BytestreamServer.h b/Swiften/FileTransfer/SOCKS5BytestreamServer.h index d5a62bb..6bb598e 100644 --- a/Swiften/FileTransfer/SOCKS5BytestreamServer.h +++ b/Swiften/FileTransfer/SOCKS5BytestreamServer.h @@ -9,26 +9,26 @@ #include <boost/shared_ptr.hpp> #include <map> -#include "Swiften/Network/ConnectionServer.h" +#include <Swiften/Network/ConnectionServer.h> #include <string> -#include "Swiften/JID/JID.h" -#include "Swiften/FileTransfer/ReadBytestream.h" -#include "Swiften/FileTransfer/SOCKS5BytestreamRegistry.h" +#include <Swiften/JID/JID.h> +#include <Swiften/FileTransfer/ReadBytestream.h> +#include <Swiften/FileTransfer/SOCKS5BytestreamRegistry.h> namespace Swift { class SOCKS5BytestreamServerSession; class SOCKS5BytestreamServer { public: - SOCKS5BytestreamServer(boost::shared_ptr<ConnectionServer> connectionServer); + SOCKS5BytestreamServer(boost::shared_ptr<ConnectionServer> connectionServer, SOCKS5BytestreamRegistry* registry); HostAddressPort getAddressPort() const; void start(); void stop(); - void addBytestream(const std::string& id, const JID& from, const JID& to, boost::shared_ptr<ReadBytestream> byteStream); - void removeBytestream(const std::string& id, const JID& from, const JID& to); + void addReadBytestream(const std::string& id, const JID& from, const JID& to, boost::shared_ptr<ReadBytestream> byteStream); + void removeReadBytestream(const std::string& id, const JID& from, const JID& to); /*protected: boost::shared_ptr<ReadBytestream> getBytestream(const std::string& dest);*/ @@ -42,7 +42,7 @@ namespace Swift { friend class SOCKS5BytestreamServerSession; boost::shared_ptr<ConnectionServer> connectionServer; - SOCKS5BytestreamRegistry bytestreams; + SOCKS5BytestreamRegistry* registry; std::vector<boost::shared_ptr<SOCKS5BytestreamServerSession> > sessions; }; } diff --git a/Swiften/FileTransfer/SOCKS5BytestreamServerSession.cpp b/Swiften/FileTransfer/SOCKS5BytestreamServerSession.cpp index 9951f7a..f660fda 100644 --- a/Swiften/FileTransfer/SOCKS5BytestreamServerSession.cpp +++ b/Swiften/FileTransfer/SOCKS5BytestreamServerSession.cpp @@ -4,17 +4,23 @@ * See Documentation/Licenses/GPLv3.txt for more information. */ -#include "Swiften/FileTransfer/SOCKS5BytestreamServerSession.h" +#include <Swiften/FileTransfer/SOCKS5BytestreamServerSession.h> #include <boost/bind.hpp> +#include <iostream> -#include "Swiften/Base/ByteArray.h" -#include "Swiften/FileTransfer/SOCKS5BytestreamRegistry.h" -#include "Swiften/FileTransfer/BytestreamException.h" +#include <Swiften/Base/ByteArray.h> +#include <Swiften/Base/SafeByteArray.h> +#include <Swiften/Base/Algorithm.h> +#include <Swiften/Base/Concat.h> +#include <Swiften/Base/Log.h> +#include <Swiften/FileTransfer/SOCKS5BytestreamRegistry.h> +#include <Swiften/FileTransfer/BytestreamException.h> namespace Swift { -SOCKS5BytestreamServerSession::SOCKS5BytestreamServerSession(boost::shared_ptr<Connection> connection, SOCKS5BytestreamRegistry* bytestreams) : connection(connection), bytestreams(bytestreams), state(Initial), chunkSize(4096) { +SOCKS5BytestreamServerSession::SOCKS5BytestreamServerSession(boost::shared_ptr<Connection> connection, SOCKS5BytestreamRegistry* bytestreams) : connection(connection), bytestreams(bytestreams), state(Initial), chunkSize(131072) { + connection->onDisconnected.connect(boost::bind(&SOCKS5BytestreamServerSession::handleDisconnected, this, _1)); } SOCKS5BytestreamServerSession::~SOCKS5BytestreamServerSession() { @@ -25,68 +31,111 @@ SOCKS5BytestreamServerSession::~SOCKS5BytestreamServerSession() { } void SOCKS5BytestreamServerSession::start() { + SWIFT_LOG(debug) << std::endl; connection->onDataRead.connect(boost::bind(&SOCKS5BytestreamServerSession::handleDataRead, this, _1)); state = WaitingForAuthentication; } void SOCKS5BytestreamServerSession::stop() { - finish(false); + connection->onDataWritten.disconnect(boost::bind(&SOCKS5BytestreamServerSession::sendData, this)); + connection->onDataRead.disconnect(boost::bind(&SOCKS5BytestreamServerSession::handleDataRead, this, _1)); + connection->disconnect(); + state = Finished; +} + +void SOCKS5BytestreamServerSession::startTransfer() { + if (state == ReadyForTransfer) { + if (readBytestream) { + state = WritingData; + connection->onDataWritten.connect(boost::bind(&SOCKS5BytestreamServerSession::sendData, this)); + sendData(); + } + else if(writeBytestream) { + state = ReadingData; + writeBytestream->write(unprocessedData); + onBytesReceived(unprocessedData.size()); + unprocessedData.clear(); + } + } else { + SWIFT_LOG(debug) << "Not ready for transfer!" << std::endl; + } } -void SOCKS5BytestreamServerSession::handleDataRead(const ByteArray& data) { - unprocessedData += data; - process(); +HostAddressPort SOCKS5BytestreamServerSession::getAddressPort() const { + return connection->getLocalAddress(); +} + +void SOCKS5BytestreamServerSession::handleDataRead(boost::shared_ptr<SafeByteArray> data) { + if (state != ReadingData) { + append(unprocessedData, *data); + process(); + } else { + writeBytestream->write(createByteArray(vecptr(*data), data->size())); + onBytesReceived(data->size()); + } +} + +void SOCKS5BytestreamServerSession::handleDisconnected(const boost::optional<Connection::Error>& error) { + SWIFT_LOG(debug) << (error ? (error == Connection::ReadError ? "Read Error" : "Write Error") : "No Error") << std::endl; + if (error) { + finish(true); + } } void SOCKS5BytestreamServerSession::process() { if (state == WaitingForAuthentication) { - if (unprocessedData.getSize() >= 2) { + if (unprocessedData.size() >= 2) { size_t authCount = unprocessedData[1]; size_t i = 2; - while (i < 2 + authCount && i < unprocessedData.getSize()) { + while (i < 2 + authCount && i < unprocessedData.size()) { // Skip authentication mechanism ++i; } if (i == 2 + authCount) { // Authentication message is complete - if (i != unprocessedData.getSize()) { - std::cerr << "SOCKS5BytestreamServerSession: Junk after authentication mechanism"; + if (i != unprocessedData.size()) { + SWIFT_LOG(debug) << "Junk after authentication mechanism" << std::endl; } unprocessedData.clear(); - connection->write(ByteArray("\x05\x00", 2)); + connection->write(createSafeByteArray("\x05\x00", 2)); state = WaitingForRequest; } } } else if (state == WaitingForRequest) { - if (unprocessedData.getSize() >= 5) { + if (unprocessedData.size() >= 5) { ByteArray requestID; size_t i = 5; size_t hostnameSize = unprocessedData[4]; - while (i < 5 + hostnameSize && i < unprocessedData.getSize()) { - requestID += unprocessedData[i]; + while (i < 5 + hostnameSize && i < unprocessedData.size()) { + requestID.push_back(unprocessedData[i]); ++i; } - // Skip the port: + // Skip the port: 2 byte large, one already skipped. Add one for comparison with size i += 2; - if (i >= unprocessedData.getSize()) { - if (i != unprocessedData.getSize()) { - std::cerr << "SOCKS5BytestreamServerSession: Junk after authentication mechanism"; + if (i <= unprocessedData.size()) { + if (i != unprocessedData.size()) { + SWIFT_LOG(debug) << "Junk after authentication mechanism" << std::endl; } - bytestream = bytestreams->getBytestream(requestID.toString()); - ByteArray result("\x05", 1); - result += bytestream ? 0x0 : 0x4; - result += ByteArray("\x00\x03", 2); - result += static_cast<char>(requestID.getSize()); - result += requestID + ByteArray("\x00\x00", 2); - if (!bytestream) { + unprocessedData.clear(); + std::string streamID = byteArrayToString(requestID); + readBytestream = bytestreams->getReadBytestream(streamID); + writeBytestream = bytestreams->getWriteBytestream(streamID); + SafeByteArray result = createSafeByteArray("\x05", 1); + result.push_back((readBytestream || writeBytestream) ? 0x0 : 0x4); + append(result, createByteArray("\x00\x03", 2)); + result.push_back(static_cast<char>(requestID.size())); + append(result, concat(requestID, createByteArray("\x00\x00", 2))); + if (!readBytestream && !writeBytestream) { + SWIFT_LOG(debug) << "Readstream or Wrtiestream with ID " << streamID << " not found!" << std::endl; connection->write(result); finish(true); } else { - state = SendingData; - connection->onDataWritten.connect(boost::bind(&SOCKS5BytestreamServerSession::sendData, this)); + SWIFT_LOG(deubg) << "Found " << (readBytestream ? "Readstream" : "Writestream") << ". Sent OK." << std::endl; connection->write(result); + bytestreams->serverSessions[streamID] = this; + state = ReadyForTransfer; } } } @@ -94,11 +143,13 @@ void SOCKS5BytestreamServerSession::process() { } void SOCKS5BytestreamServerSession::sendData() { - if (!bytestream->isFinished()) { + if (!readBytestream->isFinished()) { try { - connection->write(bytestream->read(chunkSize)); + SafeByteArray dataToSend = createSafeByteArray(*readBytestream->read(chunkSize)); + connection->write(dataToSend); + onBytesSent(dataToSend.size()); } - catch (const BytestreamException& e) { + catch (const BytestreamException&) { finish(true); } } @@ -110,9 +161,14 @@ void SOCKS5BytestreamServerSession::sendData() { void SOCKS5BytestreamServerSession::finish(bool error) { connection->onDataWritten.disconnect(boost::bind(&SOCKS5BytestreamServerSession::sendData, this)); connection->onDataRead.disconnect(boost::bind(&SOCKS5BytestreamServerSession::handleDataRead, this, _1)); - bytestream.reset(); + connection->onDisconnected.disconnect(boost::bind(&SOCKS5BytestreamServerSession::handleDisconnected, this, _1)); + readBytestream.reset(); state = Finished; - onFinished(error); + if (error) { + onFinished(boost::optional<FileTransferError>(FileTransferError::PeerError)); + } else { + onFinished(boost::optional<FileTransferError>()); + } } } diff --git a/Swiften/FileTransfer/SOCKS5BytestreamServerSession.h b/Swiften/FileTransfer/SOCKS5BytestreamServerSession.h index f430f5d..4557a36 100644 --- a/Swiften/FileTransfer/SOCKS5BytestreamServerSession.h +++ b/Swiften/FileTransfer/SOCKS5BytestreamServerSession.h @@ -8,20 +8,27 @@ #include <boost/shared_ptr.hpp> -#include "Swiften/Base/boost_bsignals.h" -#include "Swiften/Network/Connection.h" -#include "Swiften/FileTransfer/ReadBytestream.h" +#include <Swiften/Base/boost_bsignals.h> +#include <Swiften/Network/Connection.h> +#include <Swiften/FileTransfer/ReadBytestream.h> +#include <Swiften/FileTransfer/WriteBytestream.h> +#include <Swiften/FileTransfer/FileTransferError.h> namespace Swift { class SOCKS5BytestreamRegistry; class SOCKS5BytestreamServerSession { public: + typedef boost::shared_ptr<SOCKS5BytestreamServerSession> ref; + + public: enum State { Initial, WaitingForAuthentication, WaitingForRequest, - SendingData, + ReadyForTransfer, + ReadingData, + WritingData, Finished, }; @@ -35,12 +42,18 @@ namespace Swift { void start(); void stop(); - boost::signal<void (bool /* error */)> onFinished; + void startTransfer(); + HostAddressPort getAddressPort() const; + + boost::signal<void (boost::optional<FileTransferError>)> onFinished; + boost::signal<void (int)> onBytesSent; + boost::signal<void (int)> onBytesReceived; private: void finish(bool error); void process(); - void handleDataRead(const ByteArray&); + void handleDataRead(boost::shared_ptr<SafeByteArray>); + void handleDisconnected(const boost::optional<Connection::Error>&); void sendData(); private: @@ -49,6 +62,7 @@ namespace Swift { ByteArray unprocessedData; State state; int chunkSize; - boost::shared_ptr<ReadBytestream> bytestream; + boost::shared_ptr<ReadBytestream> readBytestream; + boost::shared_ptr<WriteBytestream> writeBytestream; }; } diff --git a/Swiften/FileTransfer/StreamInitiationRequest.h b/Swiften/FileTransfer/StreamInitiationRequest.h index f516d4b..658a8a9 100644 --- a/Swiften/FileTransfer/StreamInitiationRequest.h +++ b/Swiften/FileTransfer/StreamInitiationRequest.h @@ -6,8 +6,8 @@ #pragma once -#include "Swiften/Queries/GenericRequest.h" -#include "Swiften/Elements/StreamInitiation.h" +#include <Swiften/Queries/GenericRequest.h> +#include <Swiften/Elements/StreamInitiation.h> namespace Swift { @@ -19,8 +19,15 @@ namespace Swift { return ref(new StreamInitiationRequest(jid, payload, router)); } + static ref create(const JID& from, const JID& to, boost::shared_ptr<StreamInitiation> payload, IQRouter* router) { + return ref(new StreamInitiationRequest(from, to, payload, router)); + } + private: StreamInitiationRequest(const JID& jid, boost::shared_ptr<StreamInitiation> payload, IQRouter* router) : GenericRequest<StreamInitiation>(IQ::Set, jid, payload, router) { } + + StreamInitiationRequest(const JID& from, const JID& to, boost::shared_ptr<StreamInitiation> payload, IQRouter* router) : GenericRequest<StreamInitiation>(IQ::Set, from, to, payload, router) { + } }; } diff --git a/Swiften/FileTransfer/UnitTest/DummyFileTransferManager.h b/Swiften/FileTransfer/UnitTest/DummyFileTransferManager.h new file mode 100644 index 0000000..ae06cd3 --- /dev/null +++ b/Swiften/FileTransfer/UnitTest/DummyFileTransferManager.h @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <string> +#include <boost/filesystem.hpp> +#include <boost/date_time/posix_time/posix_time.hpp> + +#include <Swiften/FileTransfer/FileTransferManager.h> + +namespace Swift { + class DummyFileTransferManager : public FileTransferManager { + public: + DummyFileTransferManager() : FileTransferManager() { + } + + virtual OutgoingFileTransfer::ref createOutgoingFileTransfer(const JID&, const boost::filesystem::path&, const std::string&, boost::shared_ptr<ReadBytestream>) { + return OutgoingFileTransfer::ref(); + } + + virtual OutgoingFileTransfer::ref createOutgoingFileTransfer(const JID&, const std::string&, const std::string&, const boost::uintmax_t, const boost::posix_time::ptime&, boost::shared_ptr<ReadBytestream>) { + return OutgoingFileTransfer::ref(); + } + + virtual void startListeningOnPort(int) { + } + + virtual void addS5BProxy(boost::shared_ptr<S5BProxyRequest>) { + } + + }; +} diff --git a/Swiften/FileTransfer/UnitTest/IBBReceiveSessionTest.cpp b/Swiften/FileTransfer/UnitTest/IBBReceiveSessionTest.cpp new file mode 100644 index 0000000..c62636d --- /dev/null +++ b/Swiften/FileTransfer/UnitTest/IBBReceiveSessionTest.cpp @@ -0,0 +1,188 @@ +/* + * Copyright (c) 2010 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + + +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> +#include <vector> +#include <boost/bind.hpp> +#include <boost/smart_ptr/make_shared.hpp> + +#include <Swiften/Base/ByteArray.h> +#include <Swiften/FileTransfer/IBBReceiveSession.h> +#include <Swiften/Queries/IQRouter.h> +#include <Swiften/Client/DummyStanzaChannel.h> + +using namespace Swift; + +class IBBReceiveSessionTest : public CppUnit::TestFixture { + CPPUNIT_TEST_SUITE(IBBReceiveSessionTest); + CPPUNIT_TEST(testOpen); + CPPUNIT_TEST(testReceiveData); + CPPUNIT_TEST(testReceiveMultipleData); + CPPUNIT_TEST(testReceiveDataForOtherSession); + CPPUNIT_TEST(testReceiveDataOutOfOrder); + CPPUNIT_TEST(testReceiveLastData); + CPPUNIT_TEST(testReceiveClose); + CPPUNIT_TEST(testStopWhileActive); + CPPUNIT_TEST_SUITE_END(); + + public: + void setUp() { + stanzaChannel = new DummyStanzaChannel(); + iqRouter = new IQRouter(stanzaChannel); + finished = false; + } + + void tearDown() { + delete iqRouter; + delete stanzaChannel; + } + + void testOpen() { + boost::shared_ptr<IBBReceiveSession> testling(createSession("foo@bar.com/baz", "mysession")); + testling->start(); + stanzaChannel->onIQReceived(createIBBRequest(IBB::createIBBOpen("mysession", 0x10), "foo@bar.com/baz", "id-open")); + + CPPUNIT_ASSERT(stanzaChannel->isResultAtIndex(0, "id-open")); + CPPUNIT_ASSERT(!finished); + + testling->stop(); + } + + void testReceiveData() { + boost::shared_ptr<IBBReceiveSession> testling(createSession("foo@bar.com/baz", "mysession")); + testling->start(); + stanzaChannel->onIQReceived(createIBBRequest(IBB::createIBBOpen("mysession", 0x10), "foo@bar.com/baz", "id-open")); + + stanzaChannel->onIQReceived(createIBBRequest(IBB::createIBBData("mysession", 0, createByteArray("abc")), "foo@bar.com/baz", "id-a")); + + CPPUNIT_ASSERT(stanzaChannel->isResultAtIndex(1, "id-a")); + CPPUNIT_ASSERT(createByteArray("abc") == receivedData); + CPPUNIT_ASSERT(!finished); + + testling->stop(); + } + + void testReceiveMultipleData() { + boost::shared_ptr<IBBReceiveSession> testling(createSession("foo@bar.com/baz", "mysession")); + testling->start(); + stanzaChannel->onIQReceived(createIBBRequest(IBB::createIBBOpen("mysession", 0x10), "foo@bar.com/baz", "id-open")); + + stanzaChannel->onIQReceived(createIBBRequest(IBB::createIBBData("mysession", 0, createByteArray("abc")), "foo@bar.com/baz", "id-a")); + stanzaChannel->onIQReceived(createIBBRequest(IBB::createIBBData("mysession", 1, createByteArray("def")), "foo@bar.com/baz", "id-b")); + + CPPUNIT_ASSERT(stanzaChannel->isResultAtIndex(2, "id-b")); + CPPUNIT_ASSERT(createByteArray("abcdef") == receivedData); + CPPUNIT_ASSERT(!finished); + + testling->stop(); + } + + void testReceiveDataForOtherSession() { + boost::shared_ptr<IBBReceiveSession> testling(createSession("foo@bar.com/baz", "mysession")); + testling->start(); + stanzaChannel->onIQReceived(createIBBRequest(IBB::createIBBOpen("mysession", 0x10), "foo@bar.com/baz", "id-open")); + + stanzaChannel->onIQReceived(createIBBRequest(IBB::createIBBData("othersession", 0, createByteArray("abc")), "foo@bar.com/baz", "id-a")); + + CPPUNIT_ASSERT(stanzaChannel->isErrorAtIndex(1, "id-a")); + + testling->stop(); + } + + void testReceiveDataOutOfOrder() { + boost::shared_ptr<IBBReceiveSession> testling(createSession("foo@bar.com/baz", "mysession")); + testling->start(); + stanzaChannel->onIQReceived(createIBBRequest(IBB::createIBBOpen("mysession", 0x10), "foo@bar.com/baz", "id-open")); + + stanzaChannel->onIQReceived(createIBBRequest(IBB::createIBBData("mysession", 0, createByteArray("abc")), "foo@bar.com/baz", "id-a")); + stanzaChannel->onIQReceived(createIBBRequest(IBB::createIBBData("mysession", 0, createByteArray("def")), "foo@bar.com/baz", "id-b")); + + CPPUNIT_ASSERT(stanzaChannel->isErrorAtIndex(2, "id-b")); + CPPUNIT_ASSERT(finished); + CPPUNIT_ASSERT(error); + + testling->stop(); + } + + void testReceiveLastData() { + boost::shared_ptr<IBBReceiveSession> testling(createSession("foo@bar.com/baz", "mysession", 6)); + testling->start(); + stanzaChannel->onIQReceived(createIBBRequest(IBB::createIBBOpen("mysession", 0x10), "foo@bar.com/baz", "id-open")); + + stanzaChannel->onIQReceived(createIBBRequest(IBB::createIBBData("mysession", 0, createByteArray("abc")), "foo@bar.com/baz", "id-a")); + stanzaChannel->onIQReceived(createIBBRequest(IBB::createIBBData("mysession", 1, createByteArray("def")), "foo@bar.com/baz", "id-b")); + + CPPUNIT_ASSERT(stanzaChannel->isResultAtIndex(2, "id-b")); + CPPUNIT_ASSERT(createByteArray("abcdef") == receivedData); + CPPUNIT_ASSERT(finished); + CPPUNIT_ASSERT(!error); + + testling->stop(); + } + + void testReceiveClose() { + boost::shared_ptr<IBBReceiveSession> testling(createSession("foo@bar.com/baz", "mysession")); + testling->start(); + stanzaChannel->onIQReceived(createIBBRequest(IBB::createIBBOpen("mysession", 0x10), "foo@bar.com/baz", "id-open")); + + stanzaChannel->onIQReceived(createIBBRequest(IBB::createIBBClose("mysession"), "foo@bar.com/baz", "id-close")); + + CPPUNIT_ASSERT(finished); + CPPUNIT_ASSERT(error); + + testling->stop(); + } + + void testStopWhileActive() { + boost::shared_ptr<IBBReceiveSession> testling(createSession("foo@bar.com/baz", "mysession")); + testling->start(); + stanzaChannel->onIQReceived(createIBBRequest(IBB::createIBBOpen("mysession", 0x10), "foo@bar.com/baz", "id-open")); + + testling->stop(); + + CPPUNIT_ASSERT(stanzaChannel->isRequestAtIndex<IBB>(1, JID("foo@bar.com/baz"), IQ::Set)); + IBB::ref ibb = stanzaChannel->sentStanzas[1]->getPayload<IBB>(); + CPPUNIT_ASSERT_EQUAL(IBB::Close, ibb->getAction()); + CPPUNIT_ASSERT_EQUAL(std::string("mysession"), ibb->getStreamID()); + CPPUNIT_ASSERT(finished); + CPPUNIT_ASSERT(!error); + } + + private: + IQ::ref createIBBRequest(IBB::ref ibb, const JID& from, const std::string& id) { + IQ::ref request = IQ::createRequest(IQ::Set, JID("baz@fum.com/dum"), id, ibb); + request->setFrom(from); + return request; + } + + IBBReceiveSession* createSession(const std::string& from, const std::string& id, size_t size = 0x1000) { + IBBReceiveSession* session = new IBBReceiveSession(id, JID(from), JID(), size, iqRouter); + session->onDataReceived.connect(boost::bind(&IBBReceiveSessionTest::handleDataReceived, this, _1)); + session->onFinished.connect(boost::bind(&IBBReceiveSessionTest::handleFinished, this, _1)); + return session; + } + + + void handleFinished(boost::optional<FileTransferError> error) { + finished = true; + this->error = error; + } + + void handleDataReceived(const std::vector<unsigned char>& data) { + receivedData.insert(receivedData.end(), data.begin(), data.end()); + } + + private: + DummyStanzaChannel* stanzaChannel; + IQRouter* iqRouter; + bool finished; + boost::optional<FileTransferError> error; + std::vector<unsigned char> receivedData; +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(IBBReceiveSessionTest); diff --git a/Swiften/FileTransfer/UnitTest/IBBSendSessionTest.cpp b/Swiften/FileTransfer/UnitTest/IBBSendSessionTest.cpp index e89ef93..d12f99e 100644 --- a/Swiften/FileTransfer/UnitTest/IBBSendSessionTest.cpp +++ b/Swiften/FileTransfer/UnitTest/IBBSendSessionTest.cpp @@ -4,17 +4,16 @@ * See Documentation/Licenses/GPLv3.txt for more information. */ -#include "Swiften/Base/ByteArray.h" - #include <cppunit/extensions/HelperMacros.h> #include <cppunit/extensions/TestFactoryRegistry.h> #include <vector> #include <boost/bind.hpp> -#include "Swiften/FileTransfer/IBBSendSession.h" -#include "Swiften/FileTransfer/ByteArrayReadBytestream.h" -#include "Swiften/Queries/IQRouter.h" -#include "Swiften/Client/DummyStanzaChannel.h" +#include <Swiften/Base/ByteArray.h> +#include <Swiften/FileTransfer/IBBSendSession.h> +#include <Swiften/FileTransfer/ByteArrayReadBytestream.h> +#include <Swiften/Queries/IQRouter.h> +#include <Swiften/Client/DummyStanzaChannel.h> using namespace Swift; @@ -27,13 +26,19 @@ class IBBSendSessionTest : public CppUnit::TestFixture { CPPUNIT_TEST(testErrorResponseFinishesWithError); CPPUNIT_TEST(testStopDuringSessionCloses); CPPUNIT_TEST(testStopAfterFinishedDoesNotClose); + CPPUNIT_TEST(testDataStreamPauseStopsSendingData); + CPPUNIT_TEST(testDataStreamResumeAfterPauseSendsData); + CPPUNIT_TEST(testDataStreamResumeBeforePauseDoesNotSendData); + CPPUNIT_TEST(testDataStreamResumeAfterResumeDoesNotSendData); + CPPUNIT_TEST_SUITE_END(); public: void setUp() { stanzaChannel = new DummyStanzaChannel(); iqRouter = new IQRouter(stanzaChannel); - bytestream = boost::shared_ptr<ByteArrayReadBytestream>(new ByteArrayReadBytestream(ByteArray("abcdefg"))); + bytestream = boost::shared_ptr<ByteArrayReadBytestream>(new ByteArrayReadBytestream(createByteArray("abcdefg"))); + finished = false; } void tearDown() { @@ -42,7 +47,7 @@ class IBBSendSessionTest : public CppUnit::TestFixture { } void testStart() { - std::auto_ptr<IBBSendSession> testling = createSession("foo@bar.com/baz"); + boost::shared_ptr<IBBSendSession> testling = createSession("foo@bar.com/baz"); testling->setBlockSize(1234); testling->start(); @@ -56,7 +61,7 @@ class IBBSendSessionTest : public CppUnit::TestFixture { } void testStart_ResponseStartsSending() { - std::auto_ptr<IBBSendSession> testling = createSession("foo@bar.com/baz"); + boost::shared_ptr<IBBSendSession> testling = createSession("foo@bar.com/baz"); testling->setBlockSize(3); testling->start(); @@ -66,13 +71,13 @@ class IBBSendSessionTest : public CppUnit::TestFixture { CPPUNIT_ASSERT(stanzaChannel->isRequestAtIndex<IBB>(1, JID("foo@bar.com/baz"), IQ::Set)); IBB::ref ibb = stanzaChannel->sentStanzas[1]->getPayload<IBB>(); CPPUNIT_ASSERT_EQUAL(IBB::Data, ibb->getAction()); - CPPUNIT_ASSERT_EQUAL(ByteArray("abc"), ibb->getData()); + CPPUNIT_ASSERT(createByteArray("abc") == ibb->getData()); CPPUNIT_ASSERT_EQUAL(0, ibb->getSequenceNumber()); CPPUNIT_ASSERT_EQUAL(std::string("myid"), ibb->getStreamID()); } void testResponseContinuesSending() { - std::auto_ptr<IBBSendSession> testling = createSession("foo@bar.com/baz"); + boost::shared_ptr<IBBSendSession> testling = createSession("foo@bar.com/baz"); testling->setBlockSize(3); testling->start(); stanzaChannel->onIQReceived(createIBBResult()); @@ -82,13 +87,13 @@ class IBBSendSessionTest : public CppUnit::TestFixture { CPPUNIT_ASSERT(stanzaChannel->isRequestAtIndex<IBB>(2, JID("foo@bar.com/baz"), IQ::Set)); IBB::ref ibb = stanzaChannel->sentStanzas[2]->getPayload<IBB>(); CPPUNIT_ASSERT_EQUAL(IBB::Data, ibb->getAction()); - CPPUNIT_ASSERT_EQUAL(ByteArray("def"), ibb->getData()); + CPPUNIT_ASSERT(createByteArray("def") == ibb->getData()); CPPUNIT_ASSERT_EQUAL(1, ibb->getSequenceNumber()); CPPUNIT_ASSERT_EQUAL(std::string("myid"), ibb->getStreamID()); } void testRespondToAllFinishes() { - std::auto_ptr<IBBSendSession> testling = createSession("foo@bar.com/baz"); + boost::shared_ptr<IBBSendSession> testling = createSession("foo@bar.com/baz"); testling->setBlockSize(3); testling->start(); stanzaChannel->onIQReceived(createIBBResult()); @@ -101,7 +106,7 @@ class IBBSendSessionTest : public CppUnit::TestFixture { } void testErrorResponseFinishesWithError() { - std::auto_ptr<IBBSendSession> testling = createSession("foo@bar.com/baz"); + boost::shared_ptr<IBBSendSession> testling = createSession("foo@bar.com/baz"); testling->setBlockSize(3); testling->start(); stanzaChannel->onIQReceived(IQ::createError(JID("baz@fum.com/foo"), stanzaChannel->sentStanzas[0]->getTo(), stanzaChannel->sentStanzas[0]->getID())); @@ -111,7 +116,7 @@ class IBBSendSessionTest : public CppUnit::TestFixture { } void testStopDuringSessionCloses() { - std::auto_ptr<IBBSendSession> testling = createSession("foo@bar.com/baz"); + boost::shared_ptr<IBBSendSession> testling = createSession("foo@bar.com/baz"); testling->setBlockSize(3); testling->start(); testling->stop(); @@ -126,7 +131,7 @@ class IBBSendSessionTest : public CppUnit::TestFixture { } void testStopAfterFinishedDoesNotClose() { - std::auto_ptr<IBBSendSession> testling = createSession("foo@bar.com/baz"); + boost::shared_ptr<IBBSendSession> testling = createSession("foo@bar.com/baz"); testling->setBlockSize(16); testling->start(); stanzaChannel->onIQReceived(createIBBResult()); @@ -137,15 +142,74 @@ class IBBSendSessionTest : public CppUnit::TestFixture { CPPUNIT_ASSERT_EQUAL(2, static_cast<int>(stanzaChannel->sentStanzas.size())); } - + + void testDataStreamPauseStopsSendingData() { + boost::shared_ptr<IBBSendSession> testling = createSession("foo@bar.com/baz"); + bytestream->setDataComplete(false); + testling->setBlockSize(3); + testling->start(); + stanzaChannel->onIQReceived(createIBBResult()); + stanzaChannel->onIQReceived(createIBBResult()); + stanzaChannel->onIQReceived(createIBBResult()); + stanzaChannel->onIQReceived(createIBBResult()); + + CPPUNIT_ASSERT(!finished); + CPPUNIT_ASSERT(!error); + + CPPUNIT_ASSERT_EQUAL(4, static_cast<int>(stanzaChannel->sentStanzas.size())); + } + + void testDataStreamResumeAfterPauseSendsData() { + boost::shared_ptr<IBBSendSession> testling = createSession("foo@bar.com/baz"); + bytestream->setDataComplete(false); + testling->setBlockSize(3); + testling->start(); + stanzaChannel->onIQReceived(createIBBResult()); + stanzaChannel->onIQReceived(createIBBResult()); + stanzaChannel->onIQReceived(createIBBResult()); + stanzaChannel->onIQReceived(createIBBResult()); + + bytestream->addData(createByteArray("xyz")); + + CPPUNIT_ASSERT_EQUAL(5, static_cast<int>(stanzaChannel->sentStanzas.size())); + } + + void testDataStreamResumeBeforePauseDoesNotSendData() { + boost::shared_ptr<IBBSendSession> testling = createSession("foo@bar.com/baz"); + bytestream->setDataComplete(false); + testling->setBlockSize(3); + testling->start(); + stanzaChannel->onIQReceived(createIBBResult()); + + bytestream->addData(createByteArray("xyz")); + + CPPUNIT_ASSERT_EQUAL(2, static_cast<int>(stanzaChannel->sentStanzas.size())); + } + + void testDataStreamResumeAfterResumeDoesNotSendData() { + boost::shared_ptr<IBBSendSession> testling = createSession("foo@bar.com/baz"); + bytestream->setDataComplete(false); + testling->setBlockSize(3); + testling->start(); + stanzaChannel->onIQReceived(createIBBResult()); + stanzaChannel->onIQReceived(createIBBResult()); + stanzaChannel->onIQReceived(createIBBResult()); + stanzaChannel->onIQReceived(createIBBResult()); + + bytestream->addData(createByteArray("xyz")); + bytestream->addData(createByteArray("xuv")); + + CPPUNIT_ASSERT_EQUAL(5, static_cast<int>(stanzaChannel->sentStanzas.size())); + } + private: IQ::ref createIBBResult() { return IQ::createResult(JID("baz@fum.com/dum"), stanzaChannel->sentStanzas[stanzaChannel->sentStanzas.size()-1]->getTo(), stanzaChannel->sentStanzas[stanzaChannel->sentStanzas.size()-1]->getID(), boost::shared_ptr<IBB>()); } private: - std::auto_ptr<IBBSendSession> createSession(const std::string& to) { - std::auto_ptr<IBBSendSession> session(new IBBSendSession("myid", JID(to), bytestream, iqRouter)); + boost::shared_ptr<IBBSendSession> createSession(const std::string& to) { + boost::shared_ptr<IBBSendSession> session(new IBBSendSession("myid", JID(), JID(to), bytestream, iqRouter)); session->onFinished.connect(boost::bind(&IBBSendSessionTest::handleFinished, this, _1)); return session; } diff --git a/Swiften/FileTransfer/UnitTest/IncomingJingleFileTransferTest.cpp b/Swiften/FileTransfer/UnitTest/IncomingJingleFileTransferTest.cpp new file mode 100644 index 0000000..4c6ae72 --- /dev/null +++ b/Swiften/FileTransfer/UnitTest/IncomingJingleFileTransferTest.cpp @@ -0,0 +1,282 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> + +#include <boost/smart_ptr/make_shared.hpp> + +#include <Swiften/Base/ByteArray.h> +#include <Swiften/Base/Log.h> +#include <Swiften/Client/DummyStanzaChannel.h> +#include <Swiften/Elements/IBB.h> +#include <Swiften/Elements/JingleIBBTransportPayload.h> +#include <Swiften/Elements/JingleS5BTransportPayload.h> +#include <Swiften/FileTransfer/ByteArrayWriteBytestream.h> +#include <Swiften/FileTransfer/IncomingJingleFileTransfer.h> +#include <Swiften/FileTransfer/LocalJingleTransportCandidateGenerator.h> +#include <Swiften/FileTransfer/LocalJingleTransportCandidateGeneratorFactory.h> +#include <Swiften/FileTransfer/RemoteJingleTransportCandidateSelector.h> +#include <Swiften/FileTransfer/RemoteJingleTransportCandidateSelectorFactory.h> +#include <Swiften/FileTransfer/SOCKS5BytestreamRegistry.h> +#include <Swiften/FileTransfer/SOCKS5BytestreamProxy.h> +#include <Swiften/Jingle/FakeJingleSession.h> +#include <Swiften/Network/DummyTimerFactory.h> +#include <Swiften/EventLoop/DummyEventLoop.h> +#include <Swiften/Network/DummyConnectionFactory.h> +#include <Swiften/Network/PlatformNATTraversalWorker.h> +#include <Swiften/Queries/IQRouter.h> + +#include <iostream> + +using namespace Swift; +using namespace boost; + +class FakeRemoteJingleTransportCandidateSelector : public RemoteJingleTransportCandidateSelector { + void addRemoteTransportCandidates(JingleTransportPayload::ref cand) { + candidate = cand; + } + + void selectCandidate() { + boost::shared_ptr<JingleS5BTransportPayload> payload = make_shared<JingleS5BTransportPayload>(); + payload->setCandidateError(true); + payload->setSessionID(candidate->getSessionID()); + onRemoteTransportCandidateSelectFinished(payload); + } + + void setMinimumPriority(int) { + + } + + bool isActualCandidate(JingleTransportPayload::ref) { + return false; + } + + int getPriority(JingleTransportPayload::ref) { + return 0; + } + + JingleTransport::ref selectTransport(JingleTransportPayload::ref) { + return JingleTransport::ref(); + } + +private: + JingleTransportPayload::ref candidate; +}; + +class FakeRemoteJingleTransportCandidateSelectorFactory : public RemoteJingleTransportCandidateSelectorFactory { +public: + virtual ~FakeRemoteJingleTransportCandidateSelectorFactory() { + + } + + virtual RemoteJingleTransportCandidateSelector* createCandidateSelector() { + return new FakeRemoteJingleTransportCandidateSelector(); + } +}; + +class FakeLocalJingleTransportCandidateGenerator : public LocalJingleTransportCandidateGenerator { +public: + virtual void generateLocalTransportCandidates(JingleTransportPayload::ref payload) { + JingleS5BTransportPayload::ref payL = make_shared<JingleS5BTransportPayload>(); + payL->setSessionID(payload->getSessionID()); + onLocalTransportCandidatesGenerated(payL); + } + + void emitonLocalTransportCandidatesGenerated(JingleTransportPayload::ref payload) { + onLocalTransportCandidatesGenerated(payload); + } + + virtual bool isActualCandidate(JingleTransportPayload::ref) { + return false; + } + + virtual int getPriority(JingleTransportPayload::ref) { + return 0; + } + + virtual JingleTransport::ref selectTransport(JingleTransportPayload::ref) { + return JingleTransport::ref(); + } +}; + +class FakeLocalJingleTransportCandidateGeneratorFactory : public LocalJingleTransportCandidateGeneratorFactory { +public: + virtual LocalJingleTransportCandidateGenerator* createCandidateGenerator() { + return new FakeLocalJingleTransportCandidateGenerator(); + } +}; + +class IncomingJingleFileTransferTest : public CppUnit::TestFixture { + CPPUNIT_TEST_SUITE(IncomingJingleFileTransferTest); + 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 make_shared<IncomingJingleFileTransfer>(ourJID, shared_ptr<JingleSession>(fakeJingleSession), jingleContentPayload, fakeRJTCSF.get(), fakeLJTCF.get(), iqRouter, bytestreamRegistry, bytestreamProxy, timerFactory); + } + + 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() { + eventLoop = new DummyEventLoop(); + fakeJingleSession = new FakeJingleSession("foo@bar.com/baz", "mysession"); + 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 SOCKS5BytestreamProxy(connectionFactory, timerFactory); + } + + void tearDown() { + delete bytestreamProxy; + delete connectionFactory; + delete timerFactory; + delete bytestreamRegistry; + delete iqRouter; + delete stanzaChannel; + delete eventLoop; + } + + // Tests whether IncomingJingleFileTransfer would accept a IBB only file transfer. + void test_AcceptOnyIBBSendsSessionAccept() { + //1. create your test incoming file transfer + shared_ptr<JingleFileTransferDescription> desc = make_shared<JingleFileTransferDescription>(); + desc->addOffer(StreamInitiationFileInfo("foo.txt", "", 10)); + jingleContentPayload->addDescription(desc); + JingleIBBTransportPayload::ref tpRef = make_shared<JingleIBBTransportPayload>(); + tpRef->setSessionID("mysession"); + jingleContentPayload->addTransport(tpRef); + + shared_ptr<IncomingJingleFileTransfer> fileTransfer = createTestling(); + + //2. do 'accept' on a dummy writebytestream (you'll have to look if there already is one) + shared_ptr<ByteArrayWriteBytestream> byteStream = make_shared<ByteArrayWriteBytestream>(); + fileTransfer->accept(byteStream); + + // check whether accept has been called + getCall<FakeJingleSession::AcceptCall>(0); + } + + void test_OnlyIBBTransferReceiveWorks() { + //1. create your test incoming file transfer + shared_ptr<JingleFileTransferDescription> desc = make_shared<JingleFileTransferDescription>(); + desc->addOffer(StreamInitiationFileInfo("file.txt", "", 10)); + jingleContentPayload->addDescription(desc); + JingleIBBTransportPayload::ref tpRef = make_shared<JingleIBBTransportPayload>(); + tpRef->setSessionID("mysession"); + jingleContentPayload->addTransport(tpRef); + + shared_ptr<IncomingJingleFileTransfer> fileTransfer = createTestling(); + + //2. do 'accept' on a dummy writebytestream (you'll have to look if there already is one) + shared_ptr<ByteArrayWriteBytestream> byteStream = make_shared<ByteArrayWriteBytestream>(); + fileTransfer->accept(byteStream); + + // check whether accept has been called + getCall<FakeJingleSession::AcceptCall>(0); + stanzaChannel->onIQReceived(createIBBRequest(IBB::createIBBOpen("mysession", 0x10), "foo@bar.com/baz", "id-open")); + stanzaChannel->onIQReceived(createIBBRequest(IBB::createIBBData("mysession", 0, createByteArray("abc")), "foo@bar.com/baz", "id-a")); + CPPUNIT_ASSERT(createByteArray("abc") == byteStream->getData()); + } + + void test_AcceptFailingS5BFallsBackToIBB() { + //1. create your test incoming file transfer + addFileTransferDescription(); + + // add SOCKS5BytestreamTransportPayload + JingleS5BTransportPayload::ref payLoad = addJingleS5BPayload(); + + shared_ptr<IncomingJingleFileTransfer> fileTransfer = createTestling(); + + //2. do 'accept' on a dummy writebytestream (you'll have to look if there already is one) + shared_ptr<ByteArrayWriteBytestream> byteStream = make_shared<ByteArrayWriteBytestream>(); + fileTransfer->accept(byteStream); + + // check whether accept has been called + FakeJingleSession::AcceptCall acceptCall = getCall<FakeJingleSession::AcceptCall>(0); + CPPUNIT_ASSERT_EQUAL(payLoad->getSessionID(), acceptCall.payload->getSessionID()); + + // check for candidate error + FakeJingleSession::InfoTransportCall infoTransportCall = getCall<FakeJingleSession::InfoTransportCall>(1); + JingleS5BTransportPayload::ref s5bPayload = dynamic_pointer_cast<JingleS5BTransportPayload>(infoTransportCall.payload); + CPPUNIT_ASSERT(s5bPayload->hasCandidateError()); + + // indicate transport replace (Romeo) + fakeJingleSession->onTransportReplaceReceived(getContentID(), addJingleIBBPayload()); + + FakeJingleSession::AcceptTransportCall acceptTransportCall = getCall<FakeJingleSession::AcceptTransportCall>(2); + + // send a bit of data + stanzaChannel->onIQReceived(createIBBRequest(IBB::createIBBOpen("mysession", 0x10), "foo@bar.com/baz", "id-open")); + stanzaChannel->onIQReceived(createIBBRequest(IBB::createIBBData("mysession", 0, createByteArray("abc")), "foo@bar.com/baz", "id-a")); + CPPUNIT_ASSERT(createByteArray("abc") == byteStream->getData()); + } + + void test_S5BTransferReceiveTest() { + addFileTransferDescription(); + JingleS5BTransportPayload::ref payLoad = addJingleS5BPayload(); + } + +private: + void addFileTransferDescription() { + shared_ptr<JingleFileTransferDescription> desc = make_shared<JingleFileTransferDescription>(); + desc->addOffer(StreamInitiationFileInfo("file.txt", "", 10)); + jingleContentPayload->addDescription(desc); + } + + shared_ptr<JingleS5BTransportPayload> addJingleS5BPayload() { + JingleS5BTransportPayload::ref payLoad = make_shared<JingleS5BTransportPayload>(); + payLoad->setSessionID("mysession"); + jingleContentPayload->addTransport(payLoad); + return payLoad; + } + + shared_ptr<JingleIBBTransportPayload> addJingleIBBPayload() { + JingleIBBTransportPayload::ref payLoad = make_shared<JingleIBBTransportPayload>(); + payLoad->setSessionID("mysession"); + jingleContentPayload->addTransport(payLoad); + return payLoad; + } + + JingleContentID getContentID() const { + return JingleContentID(jingleContentPayload->getName(), jingleContentPayload->getCreator()); + } + + template <typename T> T getCall(int i) const { + size_t index = static_cast<size_t>(i); + CPPUNIT_ASSERT(index < fakeJingleSession->calledCommands.size()); + T* cmd = boost::get<T>(&fakeJingleSession->calledCommands[index]); + CPPUNIT_ASSERT(cmd); + return *cmd; + } + +private: + EventLoop* eventLoop; + FakeJingleSession* fakeJingleSession; + shared_ptr<JingleContentPayload> jingleContentPayload; + shared_ptr<FakeRemoteJingleTransportCandidateSelectorFactory> fakeRJTCSF; + shared_ptr<FakeLocalJingleTransportCandidateGeneratorFactory> fakeLJTCF; + DummyStanzaChannel* stanzaChannel; + IQRouter* iqRouter; + SOCKS5BytestreamRegistry* bytestreamRegistry; + DummyConnectionFactory* connectionFactory; + SOCKS5BytestreamProxy* bytestreamProxy; + DummyTimerFactory* timerFactory; +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(IncomingJingleFileTransferTest); diff --git a/Swiften/FileTransfer/UnitTest/OutgoingJingleFileTransferTest.cpp b/Swiften/FileTransfer/UnitTest/OutgoingJingleFileTransferTest.cpp new file mode 100644 index 0000000..0c324bf --- /dev/null +++ b/Swiften/FileTransfer/UnitTest/OutgoingJingleFileTransferTest.cpp @@ -0,0 +1,283 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> + +#include <boost/bind.hpp> +#include <boost/optional.hpp> +#include <boost/smart_ptr/make_shared.hpp> + +#include <Swiften/FileTransfer/OutgoingJingleFileTransfer.h> +#include <Swiften/Jingle/FakeJingleSession.h> +#include <Swiften/FileTransfer/RemoteJingleTransportCandidateSelectorFactory.h> +#include <Swiften/FileTransfer/RemoteJingleTransportCandidateSelector.h> +#include <Swiften/FileTransfer/LocalJingleTransportCandidateGeneratorFactory.h> +#include <Swiften/FileTransfer/LocalJingleTransportCandidateGenerator.h> +#include <Swiften/Queries/IQRouter.h> +#include <Swiften/Client/DummyStanzaChannel.h> +#include <Swiften/FileTransfer/ByteArrayReadBytestream.h> +#include <Swiften/FileTransfer/SOCKS5BytestreamRegistry.h> +#include <Swiften/FileTransfer/SOCKS5BytestreamProxy.h> + +#include <Swiften/Elements/JingleIBBTransportPayload.h> +#include <Swiften/Elements/JingleS5BTransportPayload.h> +#include <Swiften/Elements/JingleFileTransferDescription.h> +#include <Swiften/Elements/IBB.h> +#include <Swiften/Base/ByteArray.h> +#include <Swiften/Base/IDGenerator.h> +#include <Swiften/EventLoop/DummyEventLoop.h> +#include <Swiften/Network/PlatformNATTraversalWorker.h> +#include <Swiften/Network/DummyTimerFactory.h> +#include <Swiften/Network/DummyConnection.h> +#include <Swiften/Network/ConnectionFactory.h> +#include <Swiften/Network/DummyConnectionFactory.h> + +#include <Swiften/Base/Log.h> + +#include <iostream> + +using namespace Swift; + +class OFakeRemoteJingleTransportCandidateSelector : public RemoteJingleTransportCandidateSelector { + void addRemoteTransportCandidates(JingleTransportPayload::ref cand) { + candidate = cand; + } + + void selectCandidate() { + JingleS5BTransportPayload::ref payload = boost::make_shared<JingleS5BTransportPayload>(); + payload->setCandidateError(true); + payload->setSessionID(candidate->getSessionID()); + onRemoteTransportCandidateSelectFinished(payload); + } + + void setMinimumPriority(int) { + + } + + bool isActualCandidate(JingleTransportPayload::ref) { + return false; + } + + int getPriority(JingleTransportPayload::ref) { + return 0; + } + + JingleTransport::ref selectTransport(JingleTransportPayload::ref) { + return JingleTransport::ref(); + } + +private: + JingleTransportPayload::ref candidate; +}; + +class OFakeRemoteJingleTransportCandidateSelectorFactory : public RemoteJingleTransportCandidateSelectorFactory { +public: + virtual ~OFakeRemoteJingleTransportCandidateSelectorFactory() { + + } + + virtual RemoteJingleTransportCandidateSelector* createCandidateSelector() { + return new OFakeRemoteJingleTransportCandidateSelector(); + } +}; + +class OFakeLocalJingleTransportCandidateGenerator : public LocalJingleTransportCandidateGenerator { +public: + virtual void generateLocalTransportCandidates(JingleTransportPayload::ref /* payload */) { + //JingleTransportPayload::ref payL = make_shared<JingleTransportPayload>(); + //payL->setSessionID(payload->getSessionID()); + JingleS5BTransportPayload::ref payL = boost::make_shared<JingleS5BTransportPayload>(); + + onLocalTransportCandidatesGenerated(payL); + } + + void emitonLocalTransportCandidatesGenerated(JingleTransportPayload::ref payload) { + onLocalTransportCandidatesGenerated(payload); + } + + virtual bool isActualCandidate(JingleTransportPayload::ref) { + return false; + } + + virtual int getPriority(JingleTransportPayload::ref) { + return 0; + } + + virtual JingleTransport::ref selectTransport(JingleTransportPayload::ref) { + return JingleTransport::ref(); + } +}; + +class OFakeLocalJingleTransportCandidateGeneratorFactory : public LocalJingleTransportCandidateGeneratorFactory { +public: + virtual LocalJingleTransportCandidateGenerator* createCandidateGenerator() { + return new OFakeLocalJingleTransportCandidateGenerator(); + } +}; + +class OutgoingJingleFileTransferTest : public CppUnit::TestFixture { + CPPUNIT_TEST_SUITE(OutgoingJingleFileTransferTest); + CPPUNIT_TEST(test_SendSessionInitiateOnStart); + CPPUNIT_TEST(test_IBBStartsAfterSendingSessionAccept); + CPPUNIT_TEST(test_ReceiveSessionTerminateAfterSessionInitiate); + CPPUNIT_TEST_SUITE_END(); + + class FTStatusHelper { + public: + bool finishedCalled; + FileTransferError::Type error; + void handleFileTransferFinished(boost::optional<FileTransferError> error) { + finishedCalled = true; + if (error.is_initialized()) this->error = error.get().getType(); + } + }; +public: + + boost::shared_ptr<OutgoingJingleFileTransfer> createTestling() { + JID to("test@foo.com/bla"); + StreamInitiationFileInfo fileInfo; + fileInfo.setDescription("some file"); + fileInfo.setName("test.bin"); + fileInfo.setHash("asdjasdas"); + fileInfo.setSize(1024 * 1024); + return boost::shared_ptr<OutgoingJingleFileTransfer>(new OutgoingJingleFileTransfer(boost::shared_ptr<JingleSession>(fakeJingleSession), fakeRJTCSF.get(), fakeLJTCF.get(), iqRouter, idGen, JID(), to, stream, fileInfo, s5bRegistry, s5bProxy)); + } + + 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() { + fakeJingleSession = new FakeJingleSession("foo@bar.com/baz", "mysession"); + jingleContentPayload = boost::make_shared<JingleContentPayload>(); + fakeRJTCSF = boost::make_shared<OFakeRemoteJingleTransportCandidateSelectorFactory>(); + fakeLJTCF = boost::make_shared<OFakeLocalJingleTransportCandidateGeneratorFactory>(); + stanzaChannel = new DummyStanzaChannel(); + iqRouter = new IQRouter(stanzaChannel); + eventLoop = new DummyEventLoop(); + timerFactory = new DummyTimerFactory(); + connectionFactory = new DummyConnectionFactory(eventLoop); + s5bRegistry = new SOCKS5BytestreamRegistry(); + s5bProxy = new SOCKS5BytestreamProxy(connectionFactory, timerFactory); + + data.clear(); + for (int n=0; n < 1024 * 1024; ++n) { + data.push_back(34); + } + + stream = boost::make_shared<ByteArrayReadBytestream>(data); + + idGen = new IDGenerator(); + } + + void tearDown() { + delete idGen; + delete s5bRegistry; + delete connectionFactory; + delete timerFactory; + delete eventLoop; + delete iqRouter; + delete stanzaChannel; + } + + + void test_SendSessionInitiateOnStart() { + boost::shared_ptr<OutgoingJingleFileTransfer> transfer = createTestling(); + transfer->start(); + FakeJingleSession::InitiateCall call = getCall<FakeJingleSession::InitiateCall>(0); + JingleFileTransferDescription::ref description = boost::dynamic_pointer_cast<JingleFileTransferDescription>(call.description); + CPPUNIT_ASSERT(description); + CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), description->getOffers().size()); + CPPUNIT_ASSERT(static_cast<size_t>(1048576) == description->getOffers()[0].getSize()); + + JingleS5BTransportPayload::ref transport = boost::dynamic_pointer_cast<JingleS5BTransportPayload>(call.payload); + CPPUNIT_ASSERT(transport); + } + + void test_IBBStartsAfterSendingSessionAccept() { + boost::shared_ptr<OutgoingJingleFileTransfer> transfer = createTestling(); + transfer->start(); + + FakeJingleSession::InitiateCall call = getCall<FakeJingleSession::InitiateCall>(0); + // FIXME: we initiate with SOCSK5 now and not IBB, needs to be fixed. + /* + fakeJingleSession->onSessionAcceptReceived(call.id, call.description, call.payload); + IQ::ref iqOpenStanza = stanzaChannel->getStanzaAtIndex<IQ>(0); + CPPUNIT_ASSERT(iqOpenStanza); + */ + } + + void test_ReceiveSessionTerminateAfterSessionInitiate() { + boost::shared_ptr<OutgoingJingleFileTransfer> transfer = createTestling(); + transfer->start(); + + getCall<FakeJingleSession::InitiateCall>(0); + + FTStatusHelper helper; + helper.finishedCalled = false; + transfer->onFinished.connect(bind(&FTStatusHelper::handleFileTransferFinished, &helper, _1)); + fakeJingleSession->onSessionTerminateReceived(JinglePayload::Reason(JinglePayload::Reason::Busy)); + CPPUNIT_ASSERT_EQUAL(true, helper.finishedCalled); + CPPUNIT_ASSERT(FileTransferError::PeerError == helper.error); + } + + +//TODO: some more testcases + +private: + void addFileTransferDescription() { + boost::shared_ptr<JingleFileTransferDescription> desc = boost::make_shared<JingleFileTransferDescription>(); + desc->addOffer(StreamInitiationFileInfo()); + jingleContentPayload->addDescription(desc); + } + + boost::shared_ptr<JingleS5BTransportPayload> addJingleS5BPayload() { + JingleS5BTransportPayload::ref payLoad = boost::make_shared<JingleS5BTransportPayload>(); + payLoad->setSessionID("mysession"); + jingleContentPayload->addTransport(payLoad); + return payLoad; + } + + boost::shared_ptr<JingleIBBTransportPayload> addJingleIBBPayload() { + JingleIBBTransportPayload::ref payLoad = boost::make_shared<JingleIBBTransportPayload>(); + payLoad->setSessionID("mysession"); + jingleContentPayload->addTransport(payLoad); + return payLoad; + } + + JingleContentID getContentID() const { + return JingleContentID(jingleContentPayload->getName(), jingleContentPayload->getCreator()); + } + + template <typename T> T getCall(int i) const { + size_t index = static_cast<size_t>(i); + CPPUNIT_ASSERT(index < fakeJingleSession->calledCommands.size()); + T* cmd = boost::get<T>(&fakeJingleSession->calledCommands[index]); + CPPUNIT_ASSERT(cmd); + return *cmd; + } + +private: + std::vector<unsigned char> data; + boost::shared_ptr<ByteArrayReadBytestream> stream; + FakeJingleSession* fakeJingleSession; + boost::shared_ptr<JingleContentPayload> jingleContentPayload; + boost::shared_ptr<OFakeRemoteJingleTransportCandidateSelectorFactory> fakeRJTCSF; + boost::shared_ptr<OFakeLocalJingleTransportCandidateGeneratorFactory> fakeLJTCF; + DummyStanzaChannel* stanzaChannel; + IQRouter* iqRouter; + IDGenerator* idGen; + EventLoop *eventLoop; + SOCKS5BytestreamRegistry* s5bRegistry; + SOCKS5BytestreamProxy* s5bProxy; + DummyTimerFactory* timerFactory; + DummyConnectionFactory* connectionFactory; +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(OutgoingJingleFileTransferTest); diff --git a/Swiften/FileTransfer/UnitTest/SOCKS5BytestreamClientSessionTest.cpp b/Swiften/FileTransfer/UnitTest/SOCKS5BytestreamClientSessionTest.cpp new file mode 100644 index 0000000..527e0ca --- /dev/null +++ b/Swiften/FileTransfer/UnitTest/SOCKS5BytestreamClientSessionTest.cpp @@ -0,0 +1,306 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include <Swiften/Base/ByteArray.h> +#include <QA/Checker/IO.h> + +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> + +#include <boost/bind.hpp> +#include <boost/random/mersenne_twister.hpp> +#include <boost/random/uniform_int.hpp> +#include <boost/random/variate_generator.hpp> +#include <boost/smart_ptr/make_shared.hpp> + +#include <Swiften/Base/Algorithm.h> +#include <Swiften/Base/ByteArray.h> +#include <Swiften/Base/Concat.h> +#include <Swiften/Base/Log.h> +#include <Swiften/Base/StartStopper.h> +#include <Swiften/EventLoop/DummyEventLoop.h> +#include <Swiften/FileTransfer/ByteArrayReadBytestream.h> +#include <Swiften/FileTransfer/ByteArrayWriteBytestream.h> +#include <Swiften/FileTransfer/SOCKS5BytestreamClientSession.h> +#include <Swiften/FileTransfer/SOCKS5BytestreamRegistry.h> +#include <Swiften/JID/JID.h> +#include <Swiften/Network/DummyConnection.h> +#include <Swiften/Network/DummyTimerFactory.h> +#include <Swiften/StringCodecs/Hexify.h> + +using namespace Swift; + +boost::mt19937 randomGen; + +class SOCKS5BytestreamClientSessionTest : public CppUnit::TestFixture { + CPPUNIT_TEST_SUITE(SOCKS5BytestreamClientSessionTest); + CPPUNIT_TEST(testForSessionReady); + CPPUNIT_TEST(testErrorHandlingHello); + CPPUNIT_TEST(testErrorHandlingRequest); + CPPUNIT_TEST(testWriteBytestream); + CPPUNIT_TEST(testReadBytestream); + CPPUNIT_TEST_SUITE_END(); + + const HostAddressPort destinationAddressPort; + const std::string destination; + +public: + SOCKS5BytestreamClientSessionTest() : destinationAddressPort(HostAddressPort(HostAddress("127.0.0.1"), 8888)), + destination(SOCKS5BytestreamRegistry::getHostname("foo", JID("requester@example.com/test"), JID("target@example.com/test"))), eventLoop(NULL), timerFactory(NULL) { } + + void setUp() { + randomGen.seed(time(NULL)); + eventLoop = new DummyEventLoop(); + timerFactory = new DummyTimerFactory(); + connection = boost::make_shared<MockeryConnection>(failingPorts, true, eventLoop); + //connection->onDataSent.connect(boost::bind(&SOCKS5BytestreamServerSessionTest::handleDataWritten, this, _1)); + //stream1 = boost::shared_ptr<ByteArrayReadBytestream>(new ByteArrayReadBytestream(createByteArray("abcdefg"))); +// connection->onDataRead.connect(boost::bind(&SOCKS5BytestreamClientSessionTest::handleDataRead, this, _1)); + } + + void tearDown() { + //connection.reset(); + delete timerFactory; + delete eventLoop; + } + + void testForSessionReady() { + TestHelper helper; + connection->onDataSent.connect(boost::bind(&TestHelper::handleConnectionDataWritten, &helper, _1)); + + SOCKS5BytestreamClientSession::ref clientSession = boost::make_shared<SOCKS5BytestreamClientSession>(connection, destinationAddressPort, destination, timerFactory); + clientSession->onSessionReady.connect(boost::bind(&TestHelper::handleSessionReady, &helper, _1)); + + clientSession->start(); + eventLoop->processEvents(); + CPPUNIT_ASSERT(createByteArray("\x05\x01\x00", 3) == helper.unprocessedInput); + + helper.unprocessedInput.clear(); + serverRespondHelloOK(); + eventLoop->processEvents(); + CPPUNIT_ASSERT_EQUAL(createByteArray("\x05\x01\x00\x03", 4), createByteArray(&helper.unprocessedInput[0], 4)); + CPPUNIT_ASSERT_EQUAL(createByteArray(destination.size()), createByteArray(helper.unprocessedInput[4])); + CPPUNIT_ASSERT_EQUAL(createByteArray(destination), createByteArray(&helper.unprocessedInput[5], destination.size())); + CPPUNIT_ASSERT_EQUAL(createByteArray("\x00", 1), createByteArray(&helper.unprocessedInput[5 + destination.size()], 1)); + + helper.unprocessedInput.clear(); + serverRespondRequestOK(); + eventLoop->processEvents(); + CPPUNIT_ASSERT_EQUAL(true, helper.sessionReadyCalled); + CPPUNIT_ASSERT_EQUAL(false, helper.sessionReadyError); + } + + void testErrorHandlingHello() { + TestHelper helper; + connection->onDataSent.connect(boost::bind(&TestHelper::handleConnectionDataWritten, &helper, _1)); + + SOCKS5BytestreamClientSession::ref clientSession = boost::make_shared<SOCKS5BytestreamClientSession>(connection, destinationAddressPort, destination, timerFactory); + clientSession->onSessionReady.connect(boost::bind(&TestHelper::handleSessionReady, &helper, _1)); + + clientSession->start(); + eventLoop->processEvents(); + CPPUNIT_ASSERT_EQUAL(createByteArray("\x05\x01\x00", 3), helper.unprocessedInput); + + helper.unprocessedInput.clear(); + serverRespondHelloAuthFail(); + eventLoop->processEvents(); + + CPPUNIT_ASSERT_EQUAL(true, helper.sessionReadyCalled); + CPPUNIT_ASSERT_EQUAL(true, helper.sessionReadyError); + CPPUNIT_ASSERT_EQUAL(true, connection->disconnectCalled); + } + + void testErrorHandlingRequest() { + TestHelper helper; + connection->onDataSent.connect(boost::bind(&TestHelper::handleConnectionDataWritten, &helper, _1)); + + SOCKS5BytestreamClientSession::ref clientSession = boost::make_shared<SOCKS5BytestreamClientSession>(connection, destinationAddressPort, destination, timerFactory); + clientSession->onSessionReady.connect(boost::bind(&TestHelper::handleSessionReady, &helper, _1)); + + clientSession->start(); + eventLoop->processEvents(); + CPPUNIT_ASSERT_EQUAL(createByteArray("\x05\x01\x00", 3), helper.unprocessedInput); + + helper.unprocessedInput.clear(); + serverRespondHelloOK(); + eventLoop->processEvents(); + CPPUNIT_ASSERT_EQUAL(createByteArray("\x05\x01\x00\x03", 4), createByteArray(&helper.unprocessedInput[0], 4)); + CPPUNIT_ASSERT_EQUAL(createByteArray(destination.size()), createByteArray(helper.unprocessedInput[4])); + CPPUNIT_ASSERT_EQUAL(createByteArray(destination), createByteArray(&helper.unprocessedInput[5], destination.size())); + CPPUNIT_ASSERT_EQUAL(createByteArray("\x00", 1), createByteArray(&helper.unprocessedInput[5 + destination.size()], 1)); + + helper.unprocessedInput.clear(); + serverRespondRequestFail(); + eventLoop->processEvents(); + CPPUNIT_ASSERT_EQUAL(true, helper.sessionReadyCalled); + CPPUNIT_ASSERT_EQUAL(true, helper.sessionReadyError); + CPPUNIT_ASSERT_EQUAL(true, connection->disconnectCalled); + } + + void testWriteBytestream() { + TestHelper helper; + connection->onDataSent.connect(boost::bind(&TestHelper::handleConnectionDataWritten, &helper, _1)); + + SOCKS5BytestreamClientSession::ref clientSession = boost::make_shared<SOCKS5BytestreamClientSession>(connection, destinationAddressPort, destination, timerFactory); + clientSession->onSessionReady.connect(boost::bind(&TestHelper::handleSessionReady, &helper, _1)); + + clientSession->start(); + eventLoop->processEvents(); + + helper.unprocessedInput.clear(); + serverRespondHelloOK(); + eventLoop->processEvents(); + + helper.unprocessedInput.clear(); + serverRespondRequestOK(); + eventLoop->processEvents(); + CPPUNIT_ASSERT_EQUAL(true, helper.sessionReadyCalled); + CPPUNIT_ASSERT_EQUAL(false, helper.sessionReadyError); + + boost::shared_ptr<ByteArrayWriteBytestream> output = boost::make_shared<ByteArrayWriteBytestream>(); + clientSession->startReceiving(output); + + ByteArray transferData = generateRandomByteArray(1024); + connection->onDataRead(createSafeByteArrayRef(transferData.data(), transferData.size())); + CPPUNIT_ASSERT_EQUAL(transferData, output->getData()); + } + + void testReadBytestream() { + TestHelper helper; + connection->onDataSent.connect(boost::bind(&TestHelper::handleConnectionDataWritten, &helper, _1)); + + SOCKS5BytestreamClientSession::ref clientSession = boost::make_shared<SOCKS5BytestreamClientSession>(connection, destinationAddressPort, destination, timerFactory); + clientSession->onSessionReady.connect(boost::bind(&TestHelper::handleSessionReady, &helper, _1)); + + clientSession->start(); + eventLoop->processEvents(); + + helper.unprocessedInput.clear(); + serverRespondHelloOK(); + eventLoop->processEvents(); + + helper.unprocessedInput.clear(); + serverRespondRequestOK(); + eventLoop->processEvents(); + CPPUNIT_ASSERT_EQUAL(true, helper.sessionReadyCalled); + CPPUNIT_ASSERT_EQUAL(false, helper.sessionReadyError); + + helper.unprocessedInput.clear(); + ByteArray transferData = generateRandomByteArray(1024); + boost::shared_ptr<ByteArrayReadBytestream> input = boost::make_shared<ByteArrayReadBytestream>(transferData); + clientSession->startSending(input); + eventLoop->processEvents(); + + CPPUNIT_ASSERT_EQUAL(createByteArray(transferData.data(), transferData.size()), helper.unprocessedInput); + } + + + +private: + static ByteArray generateRandomByteArray(size_t len) { + boost::uniform_int<> dist(0, 255); + boost::variate_generator<boost::mt19937&, boost::uniform_int<> > randomByte(randomGen, dist); + ByteArray result(len); + for (size_t i=0; i < len; ++i ) { + result[i] = randomByte(); + } + return result; + } + + // Server responses + void serverRespondHelloOK() { + connection->onDataRead(createSafeByteArrayRef("\x05\00", 2)); + } + + void serverRespondHelloAuthFail() { + connection->onDataRead(createSafeByteArrayRef("\x05\xFF", 2)); + } + + void serverRespondRequestOK() { + boost::shared_ptr<SafeByteArray> dataToSend = createSafeByteArrayRef("\x05\x00\x00\x03", 4); + append(*dataToSend, createSafeByteArray(destination.size())); + append(*dataToSend, createSafeByteArray(destination)); + append(*dataToSend, createSafeByteArray("\x00", 1)); + connection->onDataRead(dataToSend); + } + + void serverRespondRequestFail() { + boost::shared_ptr<SafeByteArray> correctData = createSafeByteArrayRef("\x05\x00\x00\x03", 4); + append(*correctData, createSafeByteArray(destination.size())); + append(*correctData, createSafeByteArray(destination)); + append(*correctData, createSafeByteArray("\x00", 1)); + + boost::shared_ptr<SafeByteArray> dataToSend; + //ByteArray failingData = Hexify::unhexify("8417947d1d305c72c11520ea7d2c6e787396705e72c312c6ccc3f66613d7cae1b91b7ab48e8b59a17d559c15fb51"); + //append(dataToSend, failingData); + //SWIFT_LOG(debug) << "hexed: " << Hexify::hexify(failingData) << std::endl; + do { + ByteArray rndArray = generateRandomByteArray(correctData->size()); + dataToSend = createSafeByteArrayRef(rndArray.data(), rndArray.size()); + } while (*dataToSend == *correctData); + connection->onDataRead(dataToSend); + } + +private: + struct TestHelper { + TestHelper() : sessionReadyCalled(false), sessionReadyError(false) {} + ByteArray unprocessedInput; + bool sessionReadyCalled; + bool sessionReadyError; + + void handleConnectionDataWritten(const SafeByteArray& data) { + append(unprocessedInput, data); + //SWIFT_LOG(debug) << "unprocessedInput (" << unprocessedInput.size() << "): " << Hexify::hexify(unprocessedInput) << std::endl; + } + + void handleSessionReady(bool error) { + sessionReadyCalled = true; + sessionReadyError = error; + } + }; + + +private: + struct MockeryConnection : public Connection, public EventOwner, public boost::enable_shared_from_this<MockeryConnection> { + public: + MockeryConnection(const std::vector<HostAddressPort>& failingPorts, bool isResponsive, EventLoop* eventLoop) : eventLoop(eventLoop), failingPorts(failingPorts), isResponsive(isResponsive), disconnectCalled(false) {} + + void listen() { assert(false); } + void connect(const HostAddressPort& address) { + hostAddressPort = address; + if (isResponsive) { + bool fail = std::find(failingPorts.begin(), failingPorts.end(), address) != failingPorts.end(); + eventLoop->postEvent(boost::bind(boost::ref(onConnectFinished), fail)); + } + } + + HostAddressPort getLocalAddress() const { return HostAddressPort(); } + void disconnect() { + disconnectCalled = true; + } + + void write(const SafeByteArray& data) { + eventLoop->postEvent(boost::ref(onDataWritten), shared_from_this()); + onDataSent(data); + } + + boost::signal<void (const SafeByteArray&)> onDataSent; + + EventLoop* eventLoop; + boost::optional<HostAddressPort> hostAddressPort; + std::vector<HostAddressPort> failingPorts; + bool isResponsive; + bool disconnectCalled; + }; + +private: + DummyEventLoop* eventLoop; + DummyTimerFactory* timerFactory; + boost::shared_ptr<MockeryConnection> connection; + const std::vector<HostAddressPort> failingPorts; +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(SOCKS5BytestreamClientSessionTest); diff --git a/Swiften/FileTransfer/UnitTest/SOCKS5BytestreamServerSessionTest.cpp b/Swiften/FileTransfer/UnitTest/SOCKS5BytestreamServerSessionTest.cpp index c6d246d..4fe72c0 100644 --- a/Swiften/FileTransfer/UnitTest/SOCKS5BytestreamServerSessionTest.cpp +++ b/Swiften/FileTransfer/UnitTest/SOCKS5BytestreamServerSessionTest.cpp @@ -4,18 +4,19 @@ * See Documentation/Licenses/GPLv3.txt for more information. */ -#include "Swiften/Base/ByteArray.h" +#include <Swiften/Base/ByteArray.h> #include <cppunit/extensions/HelperMacros.h> #include <cppunit/extensions/TestFactoryRegistry.h> #include <boost/bind.hpp> -#include "Swiften/FileTransfer/SOCKS5BytestreamServerSession.h" -#include "Swiften/FileTransfer/ByteArrayReadBytestream.h" -#include "Swiften/FileTransfer/SOCKS5BytestreamRegistry.h" -#include "Swiften/Network/DummyConnection.h" -#include "Swiften/EventLoop/DummyEventLoop.h" -#include "Swiften/Base/StartStopper.h" +#include <Swiften/Base/Concat.h> +#include <Swiften/FileTransfer/SOCKS5BytestreamServerSession.h> +#include <Swiften/FileTransfer/ByteArrayReadBytestream.h> +#include <Swiften/FileTransfer/SOCKS5BytestreamRegistry.h> +#include <Swiften/Network/DummyConnection.h> +#include <Swiften/EventLoop/DummyEventLoop.h> +#include <Swiften/Base/StartStopper.h> using namespace Swift; @@ -33,122 +34,127 @@ class SOCKS5BytestreamServerSessionTest : public CppUnit::TestFixture { void setUp() { receivedDataChunks = 0; eventLoop = new DummyEventLoop(); + bytestreams = new SOCKS5BytestreamRegistry(); connection = boost::shared_ptr<DummyConnection>(new DummyConnection(eventLoop)); connection->onDataSent.connect(boost::bind(&SOCKS5BytestreamServerSessionTest::handleDataWritten, this, _1)); - stream1 = boost::shared_ptr<ByteArrayReadBytestream>(new ByteArrayReadBytestream(ByteArray("abcdefg"))); + stream1 = boost::shared_ptr<ByteArrayReadBytestream>(new ByteArrayReadBytestream(createByteArray("abcdefg"))); } void tearDown() { connection.reset(); + delete bytestreams; delete eventLoop; } void testAuthenticate() { - std::auto_ptr<SOCKS5BytestreamServerSession> testling(createSession()); + boost::shared_ptr<SOCKS5BytestreamServerSession> testling(createSession()); StartStopper<SOCKS5BytestreamServerSession> stopper(testling.get()); - receive(ByteArray("\x05\x02\x01\x02")); + receive(createSafeByteArray("\x05\x02\x01\x02")); - CPPUNIT_ASSERT_EQUAL(ByteArray("\x05\x00", 2), receivedData); + CPPUNIT_ASSERT(createByteArray("\x05\x00", 2) == receivedData); } void testAuthenticate_Chunked() { - std::auto_ptr<SOCKS5BytestreamServerSession> testling(createSession()); + boost::shared_ptr<SOCKS5BytestreamServerSession> testling(createSession()); StartStopper<SOCKS5BytestreamServerSession> stopper(testling.get()); - receive(ByteArray("\x05\x02\x01")); + receive(createSafeByteArray("\x05\x02\x01")); - CPPUNIT_ASSERT_EQUAL(0, static_cast<int>(receivedData.getSize())); - receive(ByteArray("\x01")); - CPPUNIT_ASSERT_EQUAL(ByteArray("\x05\x00", 2), receivedData); + CPPUNIT_ASSERT_EQUAL(0, static_cast<int>(receivedData.size())); + receive(createSafeByteArray("\x01")); + CPPUNIT_ASSERT(createByteArray("\x05\x00", 2) == receivedData); } void testRequest() { - std::auto_ptr<SOCKS5BytestreamServerSession> testling(createSession()); + boost::shared_ptr<SOCKS5BytestreamServerSession> testling(createSession()); StartStopper<SOCKS5BytestreamServerSession> stopper(testling.get()); - bytestreams.addBytestream("abcdef", stream1); + bytestreams->addReadBytestream("abcdef", stream1); authenticate(); - ByteArray hostname("abcdef"); - receive(ByteArray("\x05\x01\x00\x03", 4) + hostname.getSize() + hostname + ByteArray("\x00\x00", 2)); - CPPUNIT_ASSERT_EQUAL(ByteArray("\x05\x00\x00\x03\x06\x61\x62\x63\x64\x65\x66\x00\x00", 13), ByteArray(receivedData.getData(), 13)); + ByteArray hostname(createByteArray("abcdef")); + receive(concat(createSafeByteArray("\x05\x01\x00\x03", 4), createSafeByteArray(hostname.size()), createSafeByteArray(hostname), createSafeByteArray("\x00\x00", 2))); + CPPUNIT_ASSERT(createByteArray("\x05\x00\x00\x03\x06\x61\x62\x63\x64\x65\x66\x00\x00", 13) == createByteArray(&receivedData[0], 13)); } void testRequest_UnknownBytestream() { - std::auto_ptr<SOCKS5BytestreamServerSession> testling(createSession()); + boost::shared_ptr<SOCKS5BytestreamServerSession> testling(createSession()); StartStopper<SOCKS5BytestreamServerSession> stopper(testling.get()); authenticate(); - ByteArray hostname("abcdef"); - receive(ByteArray("\x05\x01\x00\x03", 4) + hostname.getSize() + hostname + ByteArray("\x00\x00", 2)); - CPPUNIT_ASSERT_EQUAL(ByteArray("\x05\x04\x00\x03\x06\x61\x62\x63\x64\x65\x66\x00\x00", 13), receivedData); + ByteArray hostname(createByteArray("abcdef")); + receive(concat(createSafeByteArray("\x05\x01\x00\x03", 4), createSafeByteArray(hostname.size()), createSafeByteArray(hostname), createSafeByteArray("\x00\x00", 2))); + CPPUNIT_ASSERT(createByteArray("\x05\x04\x00\x03\x06\x61\x62\x63\x64\x65\x66\x00\x00", 13) == receivedData); } void testReceiveData() { - std::auto_ptr<SOCKS5BytestreamServerSession> testling(createSession()); + boost::shared_ptr<SOCKS5BytestreamServerSession> testling(createSession()); StartStopper<SOCKS5BytestreamServerSession> stopper(testling.get()); - bytestreams.addBytestream("abcdef", stream1); + bytestreams->addReadBytestream("abcdef", stream1); authenticate(); request("abcdef"); eventLoop->processEvents(); + testling->startTransfer(); skipHeader("abcdef"); + eventLoop->processEvents(); - CPPUNIT_ASSERT_EQUAL(ByteArray("abcdefg"), receivedData); + CPPUNIT_ASSERT(createByteArray("abcdefg") == receivedData); CPPUNIT_ASSERT_EQUAL(2, receivedDataChunks); } void testReceiveData_Chunked() { - std::auto_ptr<SOCKS5BytestreamServerSession> testling(createSession()); + boost::shared_ptr<SOCKS5BytestreamServerSession> testling(createSession()); testling->setChunkSize(3); StartStopper<SOCKS5BytestreamServerSession> stopper(testling.get()); - bytestreams.addBytestream("abcdef", stream1); + bytestreams->addReadBytestream("abcdef", stream1); authenticate(); request("abcdef"); eventLoop->processEvents(); - + testling->startTransfer(); + eventLoop->processEvents(); skipHeader("abcdef"); - CPPUNIT_ASSERT_EQUAL(ByteArray("abcdefg"), receivedData); + CPPUNIT_ASSERT(createByteArray("abcdefg") == receivedData); CPPUNIT_ASSERT_EQUAL(4, receivedDataChunks); } private: - void receive(const ByteArray& data) { + void receive(const SafeByteArray& data) { connection->receive(data); eventLoop->processEvents(); } void authenticate() { - receive(ByteArray("\x05\x02\x01\x02")); + receive(createSafeByteArray("\x05\x02\x01\x02")); receivedData.clear(); receivedDataChunks = 0; } void request(const std::string& hostname) { - receive(ByteArray("\x05\x01\x00\x03", 4) + hostname.size() + hostname + ByteArray("\x00\x00", 2)); + receive(concat(createSafeByteArray("\x05\x01\x00\x03", 4), createSafeByteArray(hostname.size()), createSafeByteArray(hostname), createSafeByteArray("\x00\x00", 2))); } void skipHeader(const std::string& hostname) { int headerSize = 7 + hostname.size(); - receivedData = ByteArray(receivedData.getData() + headerSize, receivedData.getSize() - headerSize); + receivedData = createByteArray(&receivedData[headerSize], receivedData.size() - headerSize); } - void handleDataWritten(const ByteArray& data) { - receivedData += data; + void handleDataWritten(const SafeByteArray& data) { + receivedData.insert(receivedData.end(), data.begin(), data.end()); receivedDataChunks++; } private: SOCKS5BytestreamServerSession* createSession() { - SOCKS5BytestreamServerSession* session = new SOCKS5BytestreamServerSession(connection, &bytestreams); + SOCKS5BytestreamServerSession* session = new SOCKS5BytestreamServerSession(connection, bytestreams); return session; } private: DummyEventLoop* eventLoop; - SOCKS5BytestreamRegistry bytestreams; + SOCKS5BytestreamRegistry* bytestreams; boost::shared_ptr<DummyConnection> connection; - ByteArray receivedData; + std::vector<unsigned char> receivedData; int receivedDataChunks; boost::shared_ptr<ByteArrayReadBytestream> stream1; }; diff --git a/Swiften/FileTransfer/WriteBytestream.cpp b/Swiften/FileTransfer/WriteBytestream.cpp index f1a5afc..8d10193 100644 --- a/Swiften/FileTransfer/WriteBytestream.cpp +++ b/Swiften/FileTransfer/WriteBytestream.cpp @@ -4,7 +4,7 @@ * See Documentation/Licenses/GPLv3.txt for more information. */ -#include "Swiften/FileTransfer/WriteBytestream.h" +#include <Swiften/FileTransfer/WriteBytestream.h> namespace Swift { diff --git a/Swiften/FileTransfer/WriteBytestream.h b/Swiften/FileTransfer/WriteBytestream.h index 1dc791c..fb6f2f1 100644 --- a/Swiften/FileTransfer/WriteBytestream.h +++ b/Swiften/FileTransfer/WriteBytestream.h @@ -7,8 +7,9 @@ #pragma once #include <boost/shared_ptr.hpp> +#include <vector> -#include "Swiften/Base/ByteArray.h" +#include <Swiften/Base/boost_bsignals.h> namespace Swift { class WriteBytestream { @@ -17,6 +18,8 @@ namespace Swift { virtual ~WriteBytestream(); - virtual void write(const ByteArray&) = 0; + virtual void write(const std::vector<unsigned char>&) = 0; + + boost::signal<void (const std::vector<unsigned char>&)> onWrite; }; } |