summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRemko Tronçon <git@el-tramo.be>2010-10-17 12:13:36 (GMT)
committerRemko Tronçon <git@el-tramo.be>2010-10-21 18:25:00 (GMT)
commit1b58ef2af54456004390a0888c3edf104e3baa99 (patch)
treedbe4ae29de1b765a88ea704dfaa1c03af4b196b3 /Swiften/FileTransfer
parent07402c4e3451f2084a1c3ddc5bacfb38a66899a7 (diff)
downloadswift-1b58ef2af54456004390a0888c3edf104e3baa99.zip
swift-1b58ef2af54456004390a0888c3edf104e3baa99.tar.bz2
Added beginnings of outgoing file transfer to Swiften.
Diffstat (limited to 'Swiften/FileTransfer')
-rw-r--r--Swiften/FileTransfer/ByteArrayReadBytestream.h36
-rw-r--r--Swiften/FileTransfer/BytestreamException.h17
-rw-r--r--Swiften/FileTransfer/BytestreamsRequest.h24
-rw-r--r--Swiften/FileTransfer/FileReadBytestream.cpp40
-rw-r--r--Swiften/FileTransfer/FileReadBytestream.h27
-rw-r--r--Swiften/FileTransfer/FileTransferError.h27
-rw-r--r--Swiften/FileTransfer/IBBRequest.h24
-rw-r--r--Swiften/FileTransfer/IBBSendSession.cpp65
-rw-r--r--Swiften/FileTransfer/IBBSendSession.h49
-rw-r--r--Swiften/FileTransfer/OutgoingFileTransfer.cpp78
-rw-r--r--Swiften/FileTransfer/OutgoingFileTransfer.h52
-rw-r--r--Swiften/FileTransfer/ReadBytestream.cpp14
-rw-r--r--Swiften/FileTransfer/ReadBytestream.h18
-rw-r--r--Swiften/FileTransfer/SConscript13
-rw-r--r--Swiften/FileTransfer/SOCKS5BytestreamRegistry.cpp32
-rw-r--r--Swiften/FileTransfer/SOCKS5BytestreamRegistry.h29
-rw-r--r--Swiften/FileTransfer/SOCKS5BytestreamServer.cpp51
-rw-r--r--Swiften/FileTransfer/SOCKS5BytestreamServer.h49
-rw-r--r--Swiften/FileTransfer/SOCKS5BytestreamServerSession.cpp118
-rw-r--r--Swiften/FileTransfer/SOCKS5BytestreamServerSession.h54
-rw-r--r--Swiften/FileTransfer/StreamInitiationRequest.h24
-rw-r--r--Swiften/FileTransfer/UnitTest/IBBSendSessionTest.cpp164
-rw-r--r--Swiften/FileTransfer/UnitTest/SOCKS5BytestreamServerSessionTest.cpp154
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);