diff options
author | Remko Tronçon <git@el-tramo.be> | 2010-10-17 12:13:36 (GMT) |
---|---|---|
committer | Remko Tronçon <git@el-tramo.be> | 2010-10-21 18:25:00 (GMT) |
commit | 1b58ef2af54456004390a0888c3edf104e3baa99 (patch) | |
tree | dbe4ae29de1b765a88ea704dfaa1c03af4b196b3 /Swiften/FileTransfer | |
parent | 07402c4e3451f2084a1c3ddc5bacfb38a66899a7 (diff) | |
download | swift-contrib-1b58ef2af54456004390a0888c3edf104e3baa99.zip swift-contrib-1b58ef2af54456004390a0888c3edf104e3baa99.tar.bz2 |
Added beginnings of outgoing file transfer to Swiften.
Diffstat (limited to 'Swiften/FileTransfer')
23 files changed, 1159 insertions, 0 deletions
diff --git a/Swiften/FileTransfer/ByteArrayReadBytestream.h b/Swiften/FileTransfer/ByteArrayReadBytestream.h new file mode 100644 index 0000000..d459658 --- /dev/null +++ b/Swiften/FileTransfer/ByteArrayReadBytestream.h @@ -0,0 +1,36 @@ +/* + * 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/ReadBytestream.h" +#include "Swiften/Base/ByteArray.h" + +namespace Swift { + class ByteArrayReadBytestream : public ReadBytestream { + public: + ByteArrayReadBytestream(const ByteArray& data) : data(data), position(0) { + } + + virtual ByteArray read(size_t size) { + size_t readSize = size; + if (position + readSize > data.getSize()) { + readSize = data.getSize() - position; + } + ByteArray result(data.getData() + position, readSize); + position += readSize; + return result; + } + + virtual bool isFinished() const { + return position >= data.getSize(); + } + + private: + ByteArray data; + size_t position; + }; +} diff --git a/Swiften/FileTransfer/BytestreamException.h b/Swiften/FileTransfer/BytestreamException.h new file mode 100644 index 0000000..f38ef86 --- /dev/null +++ b/Swiften/FileTransfer/BytestreamException.h @@ -0,0 +1,17 @@ +/* + * 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 <stdexcept> + +namespace Swift { + class BytestreamException : public std::exception { + public: + BytestreamException() { + } + }; +} diff --git a/Swiften/FileTransfer/BytestreamsRequest.h b/Swiften/FileTransfer/BytestreamsRequest.h new file mode 100644 index 0000000..71b93ec --- /dev/null +++ b/Swiften/FileTransfer/BytestreamsRequest.h @@ -0,0 +1,24 @@ +/* + * 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/Queries/GenericRequest.h" +#include "Swiften/Elements/Bytestreams.h" +#include "Swiften/Base/Shared.h" + +namespace Swift { + class BytestreamsRequest : public GenericRequest<Bytestreams>, public Shared<BytestreamsRequest> { + public: + static ref create(const JID& jid, boost::shared_ptr<Bytestreams> payload, IQRouter* router) { + return ref(new BytestreamsRequest(jid, payload, router)); + } + + private: + BytestreamsRequest(const JID& jid, boost::shared_ptr<Bytestreams> payload, IQRouter* router) : GenericRequest<Bytestreams>(IQ::Set, jid, payload, router) { + } + }; +} diff --git a/Swiften/FileTransfer/FileReadBytestream.cpp b/Swiften/FileTransfer/FileReadBytestream.cpp new file mode 100644 index 0000000..7ed33af --- /dev/null +++ b/Swiften/FileTransfer/FileReadBytestream.cpp @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2010 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#include <boost/filesystem/fstream.hpp> +#include <cassert> + +#include "Swiften/FileTransfer/FileReadBytestream.h" + +namespace Swift { + +FileReadBytestream::FileReadBytestream(const boost::filesystem::path& file) : file(file), stream(NULL) { +} + +FileReadBytestream::~FileReadBytestream() { + if (stream) { + stream->close(); + stream = NULL; + } +} + +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); + assert(stream->good()); + stream->read(result.getData(), size); + result.resize(stream->gcount()); + return result; +} + +bool FileReadBytestream::isFinished() const { + return stream && !stream->good(); +} + +} diff --git a/Swiften/FileTransfer/FileReadBytestream.h b/Swiften/FileTransfer/FileReadBytestream.h new file mode 100644 index 0000000..055e194 --- /dev/null +++ b/Swiften/FileTransfer/FileReadBytestream.h @@ -0,0 +1,27 @@ +/* + * 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/filesystem.hpp> +#include <boost/filesystem/fstream.hpp> + +#include "Swiften/FileTransfer/ReadBytestream.h" + +namespace Swift { + class FileReadBytestream : public ReadBytestream { + public: + FileReadBytestream(const boost::filesystem::path& file); + ~FileReadBytestream(); + + virtual ByteArray read(size_t size) ; + virtual bool isFinished() const; + + private: + boost::filesystem::path file; + boost::filesystem::ifstream* stream; + }; +} diff --git a/Swiften/FileTransfer/FileTransferError.h b/Swiften/FileTransfer/FileTransferError.h new file mode 100644 index 0000000..b718927 --- /dev/null +++ b/Swiften/FileTransfer/FileTransferError.h @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2010 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#pragma once + +namespace Swift { + class FileTransferError { + public: + enum Type { + UnknownError, + PeerError, + ReadError, + }; + + FileTransferError(Type type = UnknownError) : type(type) {} + + Type getType() const { + return type; + } + + private: + Type type; + }; +} diff --git a/Swiften/FileTransfer/IBBRequest.h b/Swiften/FileTransfer/IBBRequest.h new file mode 100644 index 0000000..7269362 --- /dev/null +++ b/Swiften/FileTransfer/IBBRequest.h @@ -0,0 +1,24 @@ +/* + * 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/Queries/GenericRequest.h" +#include "Swiften/Elements/IBB.h" +#include "Swiften/Base/Shared.h" + +namespace Swift { + class IBBRequest : public GenericRequest<IBB>, public Shared<IBBRequest> { + public: + static ref create(const JID& jid, boost::shared_ptr<IBB> payload, IQRouter* router) { + return ref(new IBBRequest(jid, payload, router)); + } + + private: + IBBRequest(const JID& jid, boost::shared_ptr<IBB> payload, IQRouter* router) : GenericRequest<IBB>(IQ::Set, jid, payload, router) { + } + }; +} diff --git a/Swiften/FileTransfer/IBBSendSession.cpp b/Swiften/FileTransfer/IBBSendSession.cpp new file mode 100644 index 0000000..7246187 --- /dev/null +++ b/Swiften/FileTransfer/IBBSendSession.cpp @@ -0,0 +1,65 @@ +/* + * 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/IBBSendSession.h" + +#include <boost/bind.hpp> + +#include "Swiften/Queries/IQRouter.h" +#include "Swiften/FileTransfer/IBBRequest.h" +#include "Swiften/FileTransfer/BytestreamException.h" + +namespace Swift { + +IBBSendSession::IBBSendSession(const 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() { +} + +void IBBSendSession::start() { + IBBRequest::ref request = IBBRequest::create(to, IBB::createIBBOpen(id, blockSize), router); + request->onResponse.connect(boost::bind(&IBBSendSession::handleIBBResponse, this, _1, _2)); + active = true; + request->send(); +} + +void IBBSendSession::stop() { + if (active && router->isAvailable()) { + IBBRequest::create(to, IBB::createIBBClose(id), router)->send(); + } + finish(boost::optional<FileTransferError>()); +} + +void IBBSendSession::handleIBBResponse(IBB::ref, const boost::optional<ErrorPayload>& error) { + if (!error) { + 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)); + } + } + else { + finish(boost::optional<FileTransferError>()); + } + } + else { + finish(FileTransferError(FileTransferError::PeerError)); + } +} + +void IBBSendSession::finish(boost::optional<FileTransferError> error) { + active = false; + onFinished(error); +} + +} diff --git a/Swiften/FileTransfer/IBBSendSession.h b/Swiften/FileTransfer/IBBSendSession.h new file mode 100644 index 0000000..102bae2 --- /dev/null +++ b/Swiften/FileTransfer/IBBSendSession.h @@ -0,0 +1,49 @@ +/* + * 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 <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" + +namespace Swift { + class IQRouter; + + class IBBSendSession { + public: + IBBSendSession(const String& id, const JID& to, boost::shared_ptr<ReadBytestream> bytestream, IQRouter* router); + ~IBBSendSession(); + + void start(); + void stop(); + + void setBlockSize(int blockSize) { + this->blockSize = blockSize; + } + + boost::signal<void (boost::optional<FileTransferError>)> onFinished; + + private: + void handleIBBResponse(IBB::ref, const boost::optional<ErrorPayload>&); + void finish(boost::optional<FileTransferError>); + + private: + String id; + JID to; + boost::shared_ptr<ReadBytestream> bytestream; + IQRouter* router; + int blockSize; + int sequenceNumber; + bool active; + }; +} diff --git a/Swiften/FileTransfer/OutgoingFileTransfer.cpp b/Swiften/FileTransfer/OutgoingFileTransfer.cpp new file mode 100644 index 0000000..9f1dd1b --- /dev/null +++ b/Swiften/FileTransfer/OutgoingFileTransfer.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/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" + +namespace Swift { + +OutgoingFileTransfer::OutgoingFileTransfer(const String& id, const JID& from, const JID& to, const String& name, int size, const 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(StreamInitiation::FileInfo(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, const boost::optional<ErrorPayload>& 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, const boost::optional<ErrorPayload>& 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); +} + +} diff --git a/Swiften/FileTransfer/OutgoingFileTransfer.h b/Swiften/FileTransfer/OutgoingFileTransfer.h new file mode 100644 index 0000000..cfbfe21 --- /dev/null +++ b/Swiften/FileTransfer/OutgoingFileTransfer.h @@ -0,0 +1,52 @@ +/* + * 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/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 OutgoingFileTransfer { + public: + OutgoingFileTransfer(const String& id, const JID& from, const JID& to, const String& name, int size, const 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, const boost::optional<ErrorPayload>&); + void handleBytestreamsRequestResponse(Bytestreams::ref, const boost::optional<ErrorPayload>&); + void finish(boost::optional<FileTransferError> error); + void handleIBBSessionFinished(boost::optional<FileTransferError> error); + + private: + String id; + JID from; + JID to; + String name; + int size; + 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 new file mode 100644 index 0000000..705906c --- /dev/null +++ b/Swiften/FileTransfer/ReadBytestream.cpp @@ -0,0 +1,14 @@ +/* + * 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/ReadBytestream.h" + +namespace Swift { + +ReadBytestream::~ReadBytestream() { +} + +} diff --git a/Swiften/FileTransfer/ReadBytestream.h b/Swiften/FileTransfer/ReadBytestream.h new file mode 100644 index 0000000..4da2bc2 --- /dev/null +++ b/Swiften/FileTransfer/ReadBytestream.h @@ -0,0 +1,18 @@ +/* + * 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/Base/ByteArray.h" + +namespace Swift { + class ReadBytestream { + public: + virtual ~ReadBytestream(); + virtual ByteArray read(size_t size) = 0; + virtual bool isFinished() const = 0; + }; +} diff --git a/Swiften/FileTransfer/SConscript b/Swiften/FileTransfer/SConscript new file mode 100644 index 0000000..f42dd88 --- /dev/null +++ b/Swiften/FileTransfer/SConscript @@ -0,0 +1,13 @@ +Import("swiften_env") + +sources = [ + "OutgoingFileTransfer.cpp", + "ReadBytestream.cpp", + "FileReadBytestream.cpp", + "SOCKS5BytestreamServer.cpp", + "SOCKS5BytestreamServerSession.cpp", + "SOCKS5BytestreamRegistry.cpp", + "IBBSendSession.cpp", + ] + +swiften_env.Append(SWIFTEN_OBJECTS = swiften_env.StaticObject(sources)) diff --git a/Swiften/FileTransfer/SOCKS5BytestreamRegistry.cpp b/Swiften/FileTransfer/SOCKS5BytestreamRegistry.cpp new file mode 100644 index 0000000..a4715a0 --- /dev/null +++ b/Swiften/FileTransfer/SOCKS5BytestreamRegistry.cpp @@ -0,0 +1,32 @@ +/* + * 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/SOCKS5BytestreamRegistry.h" + +namespace Swift { + +SOCKS5BytestreamRegistry::SOCKS5BytestreamRegistry() { +} + +void SOCKS5BytestreamRegistry::addBytestream(const String& destination, boost::shared_ptr<ReadBytestream> byteStream) { + byteStreams[destination] = byteStream; +} + +void SOCKS5BytestreamRegistry::removeBytestream(const String& destination) { + byteStreams.erase(destination); +} + +boost::shared_ptr<ReadBytestream> SOCKS5BytestreamRegistry::getBytestream(const String& destination) const { + BytestreamMap::const_iterator i = byteStreams.find(destination); + if (i != byteStreams.end()) { + return i->second; + } + return boost::shared_ptr<ReadBytestream>(); +} + + +} + diff --git a/Swiften/FileTransfer/SOCKS5BytestreamRegistry.h b/Swiften/FileTransfer/SOCKS5BytestreamRegistry.h new file mode 100644 index 0000000..1afd03f --- /dev/null +++ b/Swiften/FileTransfer/SOCKS5BytestreamRegistry.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 <boost/shared_ptr.hpp> +#include <map> + +#include "Swiften/Base/String.h" +#include "Swiften/FileTransfer/ReadBytestream.h" + +namespace Swift { + class SOCKS5BytestreamRegistry { + public: + SOCKS5BytestreamRegistry(); + + boost::shared_ptr<ReadBytestream> getBytestream(const String& destination) const; + void addBytestream(const String& destination, boost::shared_ptr<ReadBytestream> byteStream); + void removeBytestream(const String& destination); + + private: + typedef std::map<String, boost::shared_ptr<ReadBytestream> > BytestreamMap; + BytestreamMap byteStreams; + }; +} + diff --git a/Swiften/FileTransfer/SOCKS5BytestreamServer.cpp b/Swiften/FileTransfer/SOCKS5BytestreamServer.cpp new file mode 100644 index 0000000..58506f3 --- /dev/null +++ b/Swiften/FileTransfer/SOCKS5BytestreamServer.cpp @@ -0,0 +1,51 @@ +/* + * 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/SOCKS5BytestreamServer.h" + +#include <boost/bind.hpp> + +#include "Swiften/StringCodecs/Hexify.h" +#include "Swiften/StringCodecs/SHA1.h" +#include "Swiften/FileTransfer/SOCKS5BytestreamServerSession.h" + +namespace Swift { + +SOCKS5BytestreamServer::SOCKS5BytestreamServer(boost::shared_ptr<ConnectionServer> connectionServer) : connectionServer(connectionServer) { +} + +void SOCKS5BytestreamServer::start() { + connectionServer->onNewConnection.connect(boost::bind(&SOCKS5BytestreamServer::handleNewConnection, this, _1)); +} + +void SOCKS5BytestreamServer::stop() { + connectionServer->onNewConnection.disconnect(boost::bind(&SOCKS5BytestreamServer::handleNewConnection, this, _1)); +} + +void SOCKS5BytestreamServer::addBytestream(const String& id, const JID& from, const JID& to, boost::shared_ptr<ReadBytestream> byteStream) { + bytestreams.addBytestream(getSOCKSDestinationAddress(id, from, to), byteStream); +} + +void SOCKS5BytestreamServer::removeBytestream(const String& id, const JID& from, const JID& to) { + bytestreams.removeBytestream(getSOCKSDestinationAddress(id, from, to)); +} + +String SOCKS5BytestreamServer::getSOCKSDestinationAddress(const String& id, const JID& from, const JID& to) { + return Hexify::hexify(SHA1::getHash(ByteArray(id + from.toString() + to.toString()))); +} + +void SOCKS5BytestreamServer::handleNewConnection(boost::shared_ptr<Connection> connection) { + boost::shared_ptr<SOCKS5BytestreamServerSession> session(new SOCKS5BytestreamServerSession(connection, &bytestreams)); + sessions.push_back(session); + session->start(); +} + +HostAddressPort SOCKS5BytestreamServer::getAddressPort() const { + return connectionServer->getAddressPort(); +} + +} + diff --git a/Swiften/FileTransfer/SOCKS5BytestreamServer.h b/Swiften/FileTransfer/SOCKS5BytestreamServer.h new file mode 100644 index 0000000..35a8d4f --- /dev/null +++ b/Swiften/FileTransfer/SOCKS5BytestreamServer.h @@ -0,0 +1,49 @@ +/* + * 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 <map> + +#include "Swiften/Network/ConnectionServer.h" +#include "Swiften/Base/String.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); + + HostAddressPort getAddressPort() const; + + void start(); + void stop(); + + void addBytestream(const String& id, const JID& from, const JID& to, boost::shared_ptr<ReadBytestream> byteStream); + void removeBytestream(const String& id, const JID& from, const JID& to); + + /*protected: + boost::shared_ptr<ReadBytestream> getBytestream(const String& dest);*/ + + private: + void handleNewConnection(boost::shared_ptr<Connection> connection); + + static String getSOCKSDestinationAddress(const String& id, const JID& from, const JID& to); + + private: + friend class SOCKS5BytestreamServerSession; + + boost::shared_ptr<ConnectionServer> connectionServer; + SOCKS5BytestreamRegistry bytestreams; + std::vector<boost::shared_ptr<SOCKS5BytestreamServerSession> > sessions; + }; +} + diff --git a/Swiften/FileTransfer/SOCKS5BytestreamServerSession.cpp b/Swiften/FileTransfer/SOCKS5BytestreamServerSession.cpp new file mode 100644 index 0000000..d22845a --- /dev/null +++ b/Swiften/FileTransfer/SOCKS5BytestreamServerSession.cpp @@ -0,0 +1,118 @@ +/* + * 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/SOCKS5BytestreamServerSession.h" + +#include <boost/bind.hpp> + +#include "Swiften/Base/ByteArray.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() { + if (state != Finished && state != Initial) { + std::cerr << "Warning: SOCKS5BytestremServerSession unfinished" << std::endl; + finish(false); + } +} + +void SOCKS5BytestreamServerSession::start() { + connection->onDataRead.connect(boost::bind(&SOCKS5BytestreamServerSession::handleDataRead, this, _1)); + state = WaitingForAuthentication; +} + +void SOCKS5BytestreamServerSession::stop() { + finish(false); +} + +void SOCKS5BytestreamServerSession::handleDataRead(const ByteArray& data) { + unprocessedData += data; + process(); +} + +void SOCKS5BytestreamServerSession::process() { + if (state == WaitingForAuthentication) { + if (unprocessedData.getSize() >= 2) { + int authCount = unprocessedData[1]; + int i = 2; + while (i < 2 + authCount && i < unprocessedData.getSize()) { + // Skip authentication mechanism + ++i; + } + if (i == 2 + authCount) { + // Authentication message is complete + if (i != unprocessedData.getSize()) { + std::cerr << "SOCKS5BytestreamServerSession: Junk after authentication mechanism"; + } + unprocessedData.clear(); + connection->write(ByteArray("\x05\x00", 2)); + state = WaitingForRequest; + } + } + } + else if (state == WaitingForRequest) { + if (unprocessedData.getSize() >= 5) { + ByteArray requestID; + int i = 5; + int hostnameSize = unprocessedData[4]; + while (i < 5 + hostnameSize && i < unprocessedData.getSize()) { + requestID += unprocessedData[i]; + ++i; + } + // Skip the port: + i += 2; + if (i >= unprocessedData.getSize()) { + if (i != unprocessedData.getSize()) { + std::cerr << "SOCKS5BytestreamServerSession: Junk after authentication mechanism"; + } + 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) { + connection->write(result); + finish(true); + } + else { + state = SendingData; + connection->onDataWritten.connect(boost::bind(&SOCKS5BytestreamServerSession::sendData, this)); + connection->write(result); + } + } + } + } +} + +void SOCKS5BytestreamServerSession::sendData() { + if (!bytestream->isFinished()) { + try { + connection->write(bytestream->read(chunkSize)); + } + catch (const BytestreamException& e) { + finish(true); + } + } + else { + finish(false); + } +} + +void SOCKS5BytestreamServerSession::finish(bool error) { + connection->onDataWritten.disconnect(boost::bind(&SOCKS5BytestreamServerSession::sendData, this)); + connection->onDataRead.disconnect(boost::bind(&SOCKS5BytestreamServerSession::handleDataRead, this, _1)); + bytestream.reset(); + state = Finished; + onFinished(error); +} + +} diff --git a/Swiften/FileTransfer/SOCKS5BytestreamServerSession.h b/Swiften/FileTransfer/SOCKS5BytestreamServerSession.h new file mode 100644 index 0000000..f430f5d --- /dev/null +++ b/Swiften/FileTransfer/SOCKS5BytestreamServerSession.h @@ -0,0 +1,54 @@ +/* + * 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/Base/boost_bsignals.h" +#include "Swiften/Network/Connection.h" +#include "Swiften/FileTransfer/ReadBytestream.h" + +namespace Swift { + class SOCKS5BytestreamRegistry; + + class SOCKS5BytestreamServerSession { + public: + enum State { + Initial, + WaitingForAuthentication, + WaitingForRequest, + SendingData, + Finished, + }; + + SOCKS5BytestreamServerSession(boost::shared_ptr<Connection> connection, SOCKS5BytestreamRegistry* registry); + ~SOCKS5BytestreamServerSession(); + + void setChunkSize(int chunkSize) { + this->chunkSize = chunkSize; + } + + void start(); + void stop(); + + boost::signal<void (bool /* error */)> onFinished; + + private: + void finish(bool error); + void process(); + void handleDataRead(const ByteArray&); + void sendData(); + + private: + boost::shared_ptr<Connection> connection; + SOCKS5BytestreamRegistry* bytestreams; + ByteArray unprocessedData; + State state; + int chunkSize; + boost::shared_ptr<ReadBytestream> bytestream; + }; +} diff --git a/Swiften/FileTransfer/StreamInitiationRequest.h b/Swiften/FileTransfer/StreamInitiationRequest.h new file mode 100644 index 0000000..64f51f2 --- /dev/null +++ b/Swiften/FileTransfer/StreamInitiationRequest.h @@ -0,0 +1,24 @@ +/* + * 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/Queries/GenericRequest.h" +#include "Swiften/Elements/StreamInitiation.h" +#include "Swiften/Base/Shared.h" + +namespace Swift { + class StreamInitiationRequest : public GenericRequest<StreamInitiation>, public Shared<StreamInitiationRequest> { + public: + static ref create(const JID& jid, boost::shared_ptr<StreamInitiation> payload, IQRouter* router) { + return ref(new StreamInitiationRequest(jid, payload, router)); + } + + private: + StreamInitiationRequest(const JID& jid, boost::shared_ptr<StreamInitiation> payload, IQRouter* router) : GenericRequest<StreamInitiation>(IQ::Set, jid, payload, router) { + } + }; +} diff --git a/Swiften/FileTransfer/UnitTest/IBBSendSessionTest.cpp b/Swiften/FileTransfer/UnitTest/IBBSendSessionTest.cpp new file mode 100644 index 0000000..9052439 --- /dev/null +++ b/Swiften/FileTransfer/UnitTest/IBBSendSessionTest.cpp @@ -0,0 +1,164 @@ +/* + * 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 "Swiften/FileTransfer/IBBSendSession.h" +#include "Swiften/FileTransfer/ByteArrayReadBytestream.h" +#include "Swiften/Queries/IQRouter.h" +#include "Swiften/Client/DummyStanzaChannel.h" + +using namespace Swift; + +class IBBSendSessionTest : public CppUnit::TestFixture { + CPPUNIT_TEST_SUITE(IBBSendSessionTest); + CPPUNIT_TEST(testStart); + CPPUNIT_TEST(testStart_ResponseStartsSending); + CPPUNIT_TEST(testResponseContinuesSending); + CPPUNIT_TEST(testRespondToAllFinishes); + CPPUNIT_TEST(testErrorResponseFinishesWithError); + CPPUNIT_TEST(testStopDuringSessionCloses); + CPPUNIT_TEST(testStopAfterFinishedDoesNotClose); + CPPUNIT_TEST_SUITE_END(); + + public: + void setUp() { + stanzaChannel = new DummyStanzaChannel(); + iqRouter = new IQRouter(stanzaChannel); + bytestream = boost::shared_ptr<ByteArrayReadBytestream>(new ByteArrayReadBytestream(ByteArray("abcdefg"))); + } + + void tearDown() { + delete iqRouter; + delete stanzaChannel; + } + + void testStart() { + std::auto_ptr<IBBSendSession> testling = createSession("foo@bar.com/baz"); + testling->setBlockSize(1234); + + testling->start(); + + CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(stanzaChannel->sentStanzas.size())); + CPPUNIT_ASSERT(stanzaChannel->isRequestAtIndex<IBB>(0, JID("foo@bar.com/baz"), IQ::Set)); + IBB::ref ibb = stanzaChannel->sentStanzas[0]->getPayload<IBB>(); + CPPUNIT_ASSERT_EQUAL(IBB::Open, ibb->getAction()); + CPPUNIT_ASSERT_EQUAL(1234, ibb->getBlockSize()); + CPPUNIT_ASSERT_EQUAL(String("myid"), ibb->getStreamID()); + } + + void testStart_ResponseStartsSending() { + std::auto_ptr<IBBSendSession> testling = createSession("foo@bar.com/baz"); + testling->setBlockSize(3); + testling->start(); + + stanzaChannel->onIQReceived(createIBBResult()); + + CPPUNIT_ASSERT_EQUAL(2, static_cast<int>(stanzaChannel->sentStanzas.size())); + 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_EQUAL(0, ibb->getSequenceNumber()); + CPPUNIT_ASSERT_EQUAL(String("myid"), ibb->getStreamID()); + } + + void testResponseContinuesSending() { + std::auto_ptr<IBBSendSession> testling = createSession("foo@bar.com/baz"); + testling->setBlockSize(3); + testling->start(); + stanzaChannel->onIQReceived(createIBBResult()); + stanzaChannel->onIQReceived(createIBBResult()); + + CPPUNIT_ASSERT_EQUAL(3, static_cast<int>(stanzaChannel->sentStanzas.size())); + 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_EQUAL(1, ibb->getSequenceNumber()); + CPPUNIT_ASSERT_EQUAL(String("myid"), ibb->getStreamID()); + } + + void testRespondToAllFinishes() { + std::auto_ptr<IBBSendSession> testling = createSession("foo@bar.com/baz"); + testling->setBlockSize(3); + testling->start(); + stanzaChannel->onIQReceived(createIBBResult()); + stanzaChannel->onIQReceived(createIBBResult()); + stanzaChannel->onIQReceived(createIBBResult()); + stanzaChannel->onIQReceived(createIBBResult()); + + CPPUNIT_ASSERT(finished); + CPPUNIT_ASSERT(!error); + } + + void testErrorResponseFinishesWithError() { + std::auto_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]->getID())); + + CPPUNIT_ASSERT(finished); + CPPUNIT_ASSERT(error); + } + + void testStopDuringSessionCloses() { + std::auto_ptr<IBBSendSession> testling = createSession("foo@bar.com/baz"); + testling->setBlockSize(3); + testling->start(); + testling->stop(); + + CPPUNIT_ASSERT_EQUAL(2, static_cast<int>(stanzaChannel->sentStanzas.size())); + 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(String("myid"), ibb->getStreamID()); + CPPUNIT_ASSERT(finished); + CPPUNIT_ASSERT(!error); + } + + void testStopAfterFinishedDoesNotClose() { + std::auto_ptr<IBBSendSession> testling = createSession("foo@bar.com/baz"); + testling->setBlockSize(16); + testling->start(); + stanzaChannel->onIQReceived(createIBBResult()); + stanzaChannel->onIQReceived(createIBBResult()); + CPPUNIT_ASSERT(finished); + + testling->stop(); + + CPPUNIT_ASSERT_EQUAL(2, static_cast<int>(stanzaChannel->sentStanzas.size())); + } + + private: + IQ::ref createIBBResult() { + return IQ::createResult(JID("baz@fum.com/dum"), stanzaChannel->sentStanzas[stanzaChannel->sentStanzas.size()-1]->getID(), boost::shared_ptr<IBB>()); + } + + private: + std::auto_ptr<IBBSendSession> createSession(const String& to) { + std::auto_ptr<IBBSendSession> session(new IBBSendSession("myid", JID(to), bytestream, iqRouter)); + session->onFinished.connect(boost::bind(&IBBSendSessionTest::handleFinished, this, _1)); + return session; + } + + void handleFinished(boost::optional<FileTransferError> error) { + finished = true; + this->error = error; + } + + private: + DummyStanzaChannel* stanzaChannel; + IQRouter* iqRouter; + bool finished; + boost::optional<FileTransferError> error; + boost::shared_ptr<ByteArrayReadBytestream> bytestream; +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(IBBSendSessionTest); diff --git a/Swiften/FileTransfer/UnitTest/SOCKS5BytestreamServerSessionTest.cpp b/Swiften/FileTransfer/UnitTest/SOCKS5BytestreamServerSessionTest.cpp new file mode 100644 index 0000000..126f971 --- /dev/null +++ b/Swiften/FileTransfer/UnitTest/SOCKS5BytestreamServerSessionTest.cpp @@ -0,0 +1,154 @@ +/* + * 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 <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" + +using namespace Swift; + +class SOCKS5BytestreamServerSessionTest : public CppUnit::TestFixture { + CPPUNIT_TEST_SUITE(SOCKS5BytestreamServerSessionTest); + CPPUNIT_TEST(testAuthenticate); + CPPUNIT_TEST(testAuthenticate_Chunked); + CPPUNIT_TEST(testRequest); + CPPUNIT_TEST(testRequest_UnknownBytestream); + CPPUNIT_TEST(testReceiveData); + CPPUNIT_TEST(testReceiveData_Chunked); + CPPUNIT_TEST_SUITE_END(); + + public: + void setUp() { + receivedDataChunks = 0; + eventLoop = new DummyEventLoop(); + connection = boost::shared_ptr<DummyConnection>(new DummyConnection()); + connection->onDataSent.connect(boost::bind(&SOCKS5BytestreamServerSessionTest::handleDataWritten, this, _1)); + stream1 = boost::shared_ptr<ByteArrayReadBytestream>(new ByteArrayReadBytestream(ByteArray("abcdefg"))); + } + + void tearDown() { + connection.reset(); + delete eventLoop; + } + + void testAuthenticate() { + std::auto_ptr<SOCKS5BytestreamServerSession> testling(createSession()); + StartStopper<SOCKS5BytestreamServerSession> stopper(testling.get()); + + receive(ByteArray("\x05\x02\x01\x02")); + + CPPUNIT_ASSERT_EQUAL(ByteArray("\x05\x00", 2), receivedData); + } + + void testAuthenticate_Chunked() { + std::auto_ptr<SOCKS5BytestreamServerSession> testling(createSession()); + StartStopper<SOCKS5BytestreamServerSession> stopper(testling.get()); + + receive(ByteArray("\x05\x02\x01")); + + CPPUNIT_ASSERT_EQUAL(0, static_cast<int>(receivedData.getSize())); + receive(ByteArray("\x01")); + CPPUNIT_ASSERT_EQUAL(ByteArray("\x05\x00", 2), receivedData); + } + + void testRequest() { + std::auto_ptr<SOCKS5BytestreamServerSession> testling(createSession()); + StartStopper<SOCKS5BytestreamServerSession> stopper(testling.get()); + bytestreams.addBytestream("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)); + } + + void testRequest_UnknownBytestream() { + std::auto_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); + } + + void testReceiveData() { + std::auto_ptr<SOCKS5BytestreamServerSession> testling(createSession()); + StartStopper<SOCKS5BytestreamServerSession> stopper(testling.get()); + bytestreams.addBytestream("abcdef", stream1); + authenticate(); + request("abcdef"); + eventLoop->processEvents(); + skipHeader("abcdef"); + + CPPUNIT_ASSERT_EQUAL(ByteArray("abcdefg"), receivedData); + CPPUNIT_ASSERT_EQUAL(2, receivedDataChunks); + } + + void testReceiveData_Chunked() { + std::auto_ptr<SOCKS5BytestreamServerSession> testling(createSession()); + testling->setChunkSize(3); + StartStopper<SOCKS5BytestreamServerSession> stopper(testling.get()); + bytestreams.addBytestream("abcdef", stream1); + authenticate(); + request("abcdef"); + eventLoop->processEvents(); + + skipHeader("abcdef"); + CPPUNIT_ASSERT_EQUAL(ByteArray("abcdefg"), receivedData); + CPPUNIT_ASSERT_EQUAL(4, receivedDataChunks); + } + + private: + void receive(const ByteArray& data) { + connection->receive(data); + eventLoop->processEvents(); + } + + void authenticate() { + receive(ByteArray("\x05\x02\x01\x02")); + receivedData.clear(); + receivedDataChunks = 0; + } + + void request(const String& hostname) { + receive(ByteArray("\x05\x01\x00\x03", 4) + hostname.getUTF8Size() + hostname + ByteArray("\x00\x00", 2)); + } + + void skipHeader(const String& hostname) { + int headerSize = 7 + hostname.getUTF8Size(); + receivedData = ByteArray(receivedData.getData() + headerSize, receivedData.getSize() - headerSize); + } + + + void handleDataWritten(const ByteArray& data) { + receivedData += data; + receivedDataChunks++; + } + + private: + SOCKS5BytestreamServerSession* createSession() { + SOCKS5BytestreamServerSession* session = new SOCKS5BytestreamServerSession(connection, &bytestreams); + return session; + } + + private: + DummyEventLoop* eventLoop; + SOCKS5BytestreamRegistry bytestreams; + boost::shared_ptr<DummyConnection> connection; + ByteArray receivedData; + int receivedDataChunks; + boost::shared_ptr<ByteArrayReadBytestream> stream1; +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(SOCKS5BytestreamServerSessionTest); |