summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorKevin Smith <git@kismith.co.uk>2011-11-12 16:56:21 (GMT)
committerKevin Smith <git@kismith.co.uk>2011-12-13 08:17:58 (GMT)
commit81c09a0f6a3e87b078340d7f35d0dea4c03f3a6d (patch)
tree4371c5808ee26b2b5ed79ace9ccb439ff2988945 /Swiften
parentfd17fe0d239f97cedebe4ceffa234155bd299b68 (diff)
downloadswift-contrib-81c09a0f6a3e87b078340d7f35d0dea4c03f3a6d.zip
swift-contrib-81c09a0f6a3e87b078340d7f35d0dea4c03f3a6d.tar.bz2
BOSH Support for Swiften
This adds support for BOSH to Swiften. It does not expose it to Swift. Release-Notes: Swiften now allows connects over BOSH, if used appropriately.
Diffstat (limited to 'Swiften')
-rw-r--r--Swiften/Base/URL.h69
-rw-r--r--Swiften/Client/ClientOptions.h28
-rw-r--r--Swiften/Client/ClientSession.cpp2
-rw-r--r--Swiften/Client/ClientXMLTracer.cpp17
-rw-r--r--Swiften/Client/ClientXMLTracer.h3
-rw-r--r--Swiften/Client/CoreClient.cpp82
-rw-r--r--Swiften/Client/CoreClient.h5
-rw-r--r--Swiften/Client/UnitTest/ClientSessionTest.cpp4
-rw-r--r--Swiften/Component/UnitTest/ComponentSessionTest.cpp4
-rw-r--r--Swiften/Network/BOSHConnection.cpp340
-rw-r--r--Swiften/Network/BOSHConnection.h67
-rw-r--r--Swiften/Network/BOSHConnectionFactory.cpp15
-rw-r--r--Swiften/Network/BOSHConnectionFactory.h35
-rw-r--r--Swiften/Network/BOSHConnectionPool.cpp247
-rw-r--r--Swiften/Network/BOSHConnectionPool.h64
-rw-r--r--Swiften/Network/BoostNetworkFactories.cpp2
-rw-r--r--Swiften/Network/BoostNetworkFactories.h5
-rw-r--r--Swiften/Network/HTTPConnectProxiedConnection.cpp25
-rw-r--r--Swiften/Network/HTTPConnectProxiedConnection.h16
-rw-r--r--Swiften/Network/HTTPConnectProxiedConnectionFactory.cpp8
-rw-r--r--Swiften/Network/HTTPConnectProxiedConnectionFactory.h4
-rw-r--r--Swiften/Network/NetworkFactories.h2
-rw-r--r--Swiften/Network/SConscript5
-rw-r--r--Swiften/Network/TLSConnection.cpp91
-rw-r--r--Swiften/Network/TLSConnection.h46
-rw-r--r--Swiften/Network/TLSConnectionFactory.cpp28
-rw-r--r--Swiften/Network/TLSConnectionFactory.h27
-rw-r--r--Swiften/Network/UnitTest/BOSHConnectionPoolTest.cpp423
-rw-r--r--Swiften/Network/UnitTest/BOSHConnectionTest.cpp291
-rw-r--r--Swiften/Network/UnitTest/HTTPConnectProxiedConnectionTest.cpp2
-rw-r--r--Swiften/Parser/BOSHBodyExtractor.cpp2
-rw-r--r--Swiften/Parser/BOSHParser.cpp55
-rw-r--r--Swiften/Parser/BOSHParser.h47
-rw-r--r--Swiften/Parser/SConscript1
-rw-r--r--Swiften/QA/ClientTest/ClientTest.cpp16
-rw-r--r--Swiften/SConscript3
-rw-r--r--Swiften/Session/BOSHSessionStream.cpp215
-rw-r--r--Swiften/Session/BOSHSessionStream.h99
-rw-r--r--Swiften/Session/BasicSessionStream.cpp4
-rw-r--r--Swiften/Session/BasicSessionStream.h1
-rw-r--r--Swiften/Session/SessionStream.h1
-rw-r--r--Swiften/StreamStack/XMPPLayer.h2
42 files changed, 2136 insertions, 267 deletions
diff --git a/Swiften/Base/URL.h b/Swiften/Base/URL.h
new file mode 100644
index 0000000..7a5aa59
--- /dev/null
+++ b/Swiften/Base/URL.h
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 2011 Kevin Smith
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#pragma once
+
+#include <string>
+
+namespace Swift {
+
+class URL {
+ public:
+
+ URL() : scheme(""), user(""), password(""), host(""), port(-1), path(""), isEmpty(true) {
+ }
+
+ URL(const std::string& scheme, const std::string& host, int port, const std::string& path) : scheme(scheme), user(), password(), host(host), port(port), path(path), isEmpty(false) {
+
+ }
+
+ /**
+ * Whether the URL is empty.
+ */
+ bool empty() const {
+ return isEmpty;
+ }
+
+ /**
+ * Scheme used for the URL (http, https etc.)
+ */
+ const std::string& getScheme() const {
+ return scheme;
+ }
+
+ /**
+ * Hostname
+ */
+ const std::string& getHost() const {
+ return host;
+ }
+
+ /**
+ * Port number
+ */
+ int getPort() const {
+ return port;
+ }
+
+ /**
+ * Path
+ */
+ const std::string& getPath() const {
+ return path;
+ }
+
+
+
+ private:
+ std::string scheme;
+ std::string user;
+ std::string password;
+ std::string host;
+ int port;
+ std::string path;
+ bool isEmpty;
+ };
+}
diff --git a/Swiften/Client/ClientOptions.h b/Swiften/Client/ClientOptions.h
index 3b51a87..06bf947 100644
--- a/Swiften/Client/ClientOptions.h
+++ b/Swiften/Client/ClientOptions.h
@@ -6,6 +6,9 @@
#pragma once
+#include <Swiften/Base/URL.h>
+#include <Swiften/Base/SafeString.h>
+
namespace Swift {
struct ClientOptions {
enum UseTLS {
@@ -14,7 +17,7 @@ namespace Swift {
RequireTLS
};
- ClientOptions() : useStreamCompression(true), useTLS(UseTLSWhenAvailable), allowPLAINWithoutTLS(false), useStreamResumption(false), forgetPassword(false), useAcks(true) {
+ ClientOptions() : useStreamCompression(true), useTLS(UseTLSWhenAvailable), allowPLAINWithoutTLS(false), useStreamResumption(false), forgetPassword(false), useAcks(true), boshHTTPConnectProxyAuthID(""), boshHTTPConnectProxyAuthPassword("") {
}
/**
@@ -61,5 +64,28 @@ namespace Swift {
* Default: true
*/
bool useAcks;
+
+ /**
+ * If non-empty, use BOSH instead of direct TCP, with the given URL.
+ * The host currently needs to be specified by IP, rather than hostname.
+ * Default: empty (no BOSH)
+ */
+ URL boshURL;
+
+ /**
+ * If non-empty, BOSH connections will try to connect over this HTTP CONNECT
+ * proxy instead of directly.
+ * Must be specified by IP, rather than hostname.
+ * Default: empty (no proxy)
+ */
+ URL boshHTTPConnectProxyURL;
+
+ /**
+ * If this and matching Password are non-empty, BOSH connections over
+ * HTTP CONNECT proxies will use these credentials for proxy access.
+ * Default: empty (no authentication needed by the proxy)
+ */
+ SafeString boshHTTPConnectProxyAuthID;
+ SafeString boshHTTPConnectProxyAuthPassword;
};
}
diff --git a/Swiften/Client/ClientSession.cpp b/Swiften/Client/ClientSession.cpp
index 55e0bc2..bfc9313 100644
--- a/Swiften/Client/ClientSession.cpp
+++ b/Swiften/Client/ClientSession.cpp
@@ -181,7 +181,7 @@ void ClientSession::handleElement(boost::shared_ptr<Element> element) {
else if (useTLS == RequireTLS && !stream->isTLSEncrypted()) {
finishSession(Error::NoSupportedAuthMechanismsError);
}
- else if (useStreamCompression && streamFeatures->hasCompressionMethod("zlib")) {
+ else if (useStreamCompression && stream->supportsZLibCompression() && streamFeatures->hasCompressionMethod("zlib")) {
state = Compressing;
stream->writeElement(boost::make_shared<CompressRequest>("zlib"));
}
diff --git a/Swiften/Client/ClientXMLTracer.cpp b/Swiften/Client/ClientXMLTracer.cpp
index c1093eb..405e3d1 100644
--- a/Swiften/Client/ClientXMLTracer.cpp
+++ b/Swiften/Client/ClientXMLTracer.cpp
@@ -11,7 +11,7 @@
namespace Swift {
-ClientXMLTracer::ClientXMLTracer(CoreClient* client) {
+ClientXMLTracer::ClientXMLTracer(CoreClient* client, bool bosh) : bosh(bosh) {
beautifier = new XMLBeautifier(true, true);
client->onDataRead.connect(boost::bind(&ClientXMLTracer::printData, this, '<', _1));
client->onDataWritten.connect(boost::bind(&ClientXMLTracer::printData, this, '>', _1));
@@ -23,7 +23,20 @@ ClientXMLTracer::~ClientXMLTracer() {
void ClientXMLTracer::printData(char direction, const SafeByteArray& data) {
printLine(direction);
- std::cerr << beautifier->beautify(byteArrayToString(ByteArray(data.begin(), data.end()))) << std::endl;
+ if (bosh) {
+ std::string line = byteArrayToString(ByteArray(data.begin(), data.end()));
+ size_t endOfHTTP = line.find("\r\n\r\n");
+ if (false && endOfHTTP != std::string::npos) {
+ /* Disabled because it swallows bits of XML (namespaces, if I recall) */
+ std::cerr << line.substr(0, endOfHTTP) << std::endl << beautifier->beautify(line.substr(endOfHTTP)) << std::endl;
+ }
+ else {
+ std::cerr << line << std::endl;
+ }
+ }
+ else {
+ std::cerr << beautifier->beautify(byteArrayToString(ByteArray(data.begin(), data.end()))) << std::endl;
+ }
}
void ClientXMLTracer::printLine(char c) {
diff --git a/Swiften/Client/ClientXMLTracer.h b/Swiften/Client/ClientXMLTracer.h
index 0752faa..67040c4 100644
--- a/Swiften/Client/ClientXMLTracer.h
+++ b/Swiften/Client/ClientXMLTracer.h
@@ -13,7 +13,7 @@
namespace Swift {
class ClientXMLTracer {
public:
- ClientXMLTracer(CoreClient* client);
+ ClientXMLTracer(CoreClient* client, bool bosh = false);
~ClientXMLTracer();
private:
void printData(char direction, const SafeByteArray& data);
@@ -21,5 +21,6 @@ namespace Swift {
private:
XMLBeautifier *beautifier;
+ bool bosh;
};
}
diff --git a/Swiften/Client/CoreClient.cpp b/Swiften/Client/CoreClient.cpp
index 08f31a0..cef2b24 100644
--- a/Swiften/Client/CoreClient.cpp
+++ b/Swiften/Client/CoreClient.cpp
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2010 Remko Tronçon
+ * Copyright (c) 2010-2011 Remko Tronçon
* Licensed under the GNU General Public License v3.
* See Documentation/Licenses/GPLv3.txt for more information.
*/
@@ -20,6 +20,7 @@
#include <Swiften/Network/ProxyProvider.h>
#include <Swiften/TLS/PKCS12Certificate.h>
#include <Swiften/Session/BasicSessionStream.h>
+#include <Swiften/Session/BOSHSessionStream.h>
#include <Swiften/Queries/IQRouter.h>
#include <Swiften/Client/ClientSessionStanzaChannel.h>
#include <Swiften/Network/SOCKS5ProxiedConnectionFactory.h>
@@ -70,15 +71,52 @@ void CoreClient::connect(const std::string& host) {
proxyConnectionFactories.push_back(new HTTPConnectProxiedConnectionFactory(networkFactories->getConnectionFactory(), networkFactories->getProxyProvider()->getHTTPConnectProxy()));
}
std::vector<ConnectionFactory*> connectionFactories(proxyConnectionFactories);
- // connectionFactories.push_back(networkFactories->getConnectionFactory());
- connectionFactories.push_back(new BOSHConnectionFactory(networkFactories->getConnectionFactory()));
+ if (options.boshURL.empty()) {
+ connectionFactories.push_back(networkFactories->getConnectionFactory());
+ connector_ = boost::make_shared<ChainedConnector>(host, networkFactories->getDomainNameResolver(), connectionFactories, networkFactories->getTimerFactory());
+ connector_->onConnectFinished.connect(boost::bind(&CoreClient::handleConnectorFinished, this, _1));
+ connector_->setTimeoutMilliseconds(60*1000);
+ connector_->start();
+ }
+ else {
+ /* Autodiscovery of which proxy works is largely ok with a TCP session, because this is a one-off. With BOSH
+ * it would be quite painful given that potentially every stanza could be sent on a new connection.
+ */
+ //sessionStream_ = boost::make_shared<BOSHSessionStream>(boost::make_shared<BOSHConnectionFactory>(options.boshURL, networkFactories->getConnectionFactory(), networkFactories->getXMLParserFactory(), networkFactories->getTLSContextFactory()), getPayloadParserFactories(), getPayloadSerializers(), networkFactories->getTLSContextFactory(), networkFactories->getTimerFactory(), networkFactories->getXMLParserFactory(), networkFactories->getEventLoop(), host, options.boshHTTPConnectProxyURL, options.boshHTTPConnectProxyAuthID, options.boshHTTPConnectProxyAuthPassword);
+ sessionStream_ = boost::shared_ptr<BOSHSessionStream>(new BOSHSessionStream(boost::make_shared<BOSHConnectionFactory>(options.boshURL, networkFactories->getConnectionFactory(), networkFactories->getXMLParserFactory(), networkFactories->getTLSContextFactory()), getPayloadParserFactories(), getPayloadSerializers(), networkFactories->getTLSContextFactory(), networkFactories->getTimerFactory(), networkFactories->getXMLParserFactory(), networkFactories->getEventLoop(), host, options.boshHTTPConnectProxyURL, options.boshHTTPConnectProxyAuthID, options.boshHTTPConnectProxyAuthPassword));
+ sessionStream_->onDataRead.connect(boost::bind(&CoreClient::handleDataRead, this, _1));
+ sessionStream_->onDataWritten.connect(boost::bind(&CoreClient::handleDataWritten, this, _1));
+ bindSessionToStream();
+ }
+
+}
- connector_ = boost::make_shared<ChainedConnector>(host, networkFactories->getDomainNameResolver(), connectionFactories, networkFactories->getTimerFactory());
- connector_->onConnectFinished.connect(boost::bind(&CoreClient::handleConnectorFinished, this, _1));
- connector_->setTimeoutMilliseconds(60*1000);
- connector_->start();
+void CoreClient::bindSessionToStream() {
+ session_ = ClientSession::create(jid_, sessionStream_);
+ session_->setCertificateTrustChecker(certificateTrustChecker);
+ session_->setUseStreamCompression(options.useStreamCompression);
+ session_->setAllowPLAINOverNonTLS(options.allowPLAINWithoutTLS);
+ switch(options.useTLS) {
+ case ClientOptions::UseTLSWhenAvailable:
+ session_->setUseTLS(ClientSession::UseTLSWhenAvailable);
+ break;
+ case ClientOptions::NeverUseTLS:
+ session_->setUseTLS(ClientSession::NeverUseTLS);
+ break;
+ case ClientOptions::RequireTLS:
+ session_->setUseTLS(ClientSession::RequireTLS);
+ break;
+ }
+ session_->setUseAcks(options.useAcks);
+ stanzaChannel_->setSession(session_);
+ session_->onFinished.connect(boost::bind(&CoreClient::handleSessionFinished, this, _1));
+ session_->onNeedCredentials.connect(boost::bind(&CoreClient::handleNeedCredentials, this));
+ session_->start();
}
+/**
+ * Only called for TCP sessions. BOSH is handled inside the BOSHSessionStream.
+ */
void CoreClient::handleConnectorFinished(boost::shared_ptr<Connection> connection) {
resetConnector();
if (!connection) {
@@ -99,26 +137,7 @@ void CoreClient::handleConnectorFinished(boost::shared_ptr<Connection> connectio
sessionStream_->onDataRead.connect(boost::bind(&CoreClient::handleDataRead, this, _1));
sessionStream_->onDataWritten.connect(boost::bind(&CoreClient::handleDataWritten, this, _1));
- session_ = ClientSession::create(jid_, sessionStream_);
- session_->setCertificateTrustChecker(certificateTrustChecker);
- session_->setUseStreamCompression(options.useStreamCompression);
- session_->setAllowPLAINOverNonTLS(options.allowPLAINWithoutTLS);
- switch(options.useTLS) {
- case ClientOptions::UseTLSWhenAvailable:
- session_->setUseTLS(ClientSession::UseTLSWhenAvailable);
- break;
- case ClientOptions::NeverUseTLS:
- session_->setUseTLS(ClientSession::NeverUseTLS);
- break;
- case ClientOptions::RequireTLS:
- session_->setUseTLS(ClientSession::RequireTLS);
- break;
- }
- session_->setUseAcks(options.useAcks);
- stanzaChannel_->setSession(session_);
- session_->onFinished.connect(boost::bind(&CoreClient::handleSessionFinished, this, _1));
- session_->onNeedCredentials.connect(boost::bind(&CoreClient::handleNeedCredentials, this));
- session_->start();
+ bindSessionToStream();
}
}
@@ -339,9 +358,14 @@ void CoreClient::resetSession() {
sessionStream_->onDataRead.disconnect(boost::bind(&CoreClient::handleDataRead, this, _1));
sessionStream_->onDataWritten.disconnect(boost::bind(&CoreClient::handleDataWritten, this, _1));
- sessionStream_.reset();
- connection_->disconnect();
+ if (connection_) {
+ connection_->disconnect();
+ }
+ else if (boost::dynamic_pointer_cast<BOSHSessionStream>(sessionStream_)) {
+ sessionStream_->close();
+ }
+ sessionStream_.reset();
connection_.reset();
}
diff --git a/Swiften/Client/CoreClient.h b/Swiften/Client/CoreClient.h
index 3c089c1..c231fdc 100644
--- a/Swiften/Client/CoreClient.h
+++ b/Swiften/Client/CoreClient.h
@@ -29,7 +29,7 @@ namespace Swift {
class ClientSession;
class StanzaChannel;
class Stanza;
- class BasicSessionStream;
+ class SessionStream;
class CertificateTrustChecker;
class NetworkFactories;
class ClientSessionStanzaChannel;
@@ -207,6 +207,7 @@ namespace Swift {
void handleMessageReceived(boost::shared_ptr<Message>);
void handleStanzaAcked(boost::shared_ptr<Stanza>);
void purgePassword();
+ void bindSessionToStream();
void resetConnector();
void resetSession();
@@ -222,7 +223,7 @@ namespace Swift {
boost::shared_ptr<ChainedConnector> connector_;
std::vector<ConnectionFactory*> proxyConnectionFactories;
boost::shared_ptr<Connection> connection_;
- boost::shared_ptr<BasicSessionStream> sessionStream_;
+ boost::shared_ptr<SessionStream> sessionStream_;
boost::shared_ptr<ClientSession> session_;
std::string certificate_;
bool disconnectRequested_;
diff --git a/Swiften/Client/UnitTest/ClientSessionTest.cpp b/Swiften/Client/UnitTest/ClientSessionTest.cpp
index e9d1b21..22db8fc 100644
--- a/Swiften/Client/UnitTest/ClientSessionTest.cpp
+++ b/Swiften/Client/UnitTest/ClientSessionTest.cpp
@@ -403,6 +403,10 @@ class ClientSessionTest : public CppUnit::TestFixture {
return boost::shared_ptr<CertificateVerificationError>();
}
+ virtual bool supportsZLibCompression() {
+ return true;
+ }
+
virtual void addZLibCompression() {
compressed = true;
}
diff --git a/Swiften/Component/UnitTest/ComponentSessionTest.cpp b/Swiften/Component/UnitTest/ComponentSessionTest.cpp
index c27ade5..1541cce 100644
--- a/Swiften/Component/UnitTest/ComponentSessionTest.cpp
+++ b/Swiften/Component/UnitTest/ComponentSessionTest.cpp
@@ -142,6 +142,10 @@ class ComponentSessionTest : public CppUnit::TestFixture {
return boost::shared_ptr<CertificateVerificationError>();
}
+ virtual bool supportsZLibCompression() {
+ return true;
+ }
+
virtual void addZLibCompression() {
assert(false);
}
diff --git a/Swiften/Network/BOSHConnection.cpp b/Swiften/Network/BOSHConnection.cpp
index 549c652..09548e9 100644
--- a/Swiften/Network/BOSHConnection.cpp
+++ b/Swiften/Network/BOSHConnection.cpp
@@ -4,129 +4,281 @@
* See Documentation/Licenses/BSD-simplified.txt for more information.
*/
-#include "BOSHConnection.h"
+/*
+ * Copyright (c) 2011 Kevin Smith
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#include <Swiften/Network/BOSHConnection.h>
+
#include <boost/bind.hpp>
#include <boost/thread.hpp>
+#include <boost/lexical_cast.hpp>
#include <string>
#include <Swiften/Network/ConnectionFactory.h>
#include <Swiften/Base/Log.h>
#include <Swiften/Base/String.h>
+#include <Swiften/Base/Concat.h>
#include <Swiften/Base/ByteArray.h>
#include <Swiften/Network/HostAddressPort.h>
-#include <Swiften/Parser/BOSHParser.h>
+#include <Swiften/Network/TLSConnection.h>
+#include <Swiften/Parser/BOSHBodyExtractor.h>
namespace Swift {
- BOSHConnection::BOSHConnection(ConnectionFactory* connectionFactory)
- : connectionFactory_(connectionFactory), server_(HostAddressPort(HostAddress("0.0.0.0"), 0)), sid_()
- {
- reopenAfterAction = true;
- }
+BOSHConnection::BOSHConnection(const URL& boshURL, ConnectionFactory* connectionFactory, XMLParserFactory* parserFactory, TLSContextFactory* tlsFactory)
+ : boshURL_(boshURL),
+ connectionFactory_(connectionFactory),
+ parserFactory_(parserFactory),
+ sid_(),
+ waitingForStartResponse_(false),
+ pending_(false),
+ tlsFactory_(tlsFactory),
+ connectionReady_(false)
+{
+}
- BOSHConnection::~BOSHConnection() {
- if (newConnection_) {
- newConnection_->onDataRead.disconnect(boost::bind(&BOSHConnection::handleDataRead, shared_from_this(), _1));
- newConnection_->onDisconnected.disconnect(boost::bind(&BOSHConnection::handleDisconnected, shared_from_this(), _1));
- }
- if (currentConnection_) {
- currentConnection_->onDataRead.disconnect(boost::bind(&BOSHConnection::handleDataRead, shared_from_this(), _1));
- currentConnection_->onDisconnected.disconnect(boost::bind(&BOSHConnection::handleDisconnected, shared_from_this(), _1));
- }
+BOSHConnection::~BOSHConnection() {
+ if (connection_) {
+ connection_->onConnectFinished.disconnect(boost::bind(&BOSHConnection::handleConnectionConnectFinished, shared_from_this(), _1));
+ connection_->onDataRead.disconnect(boost::bind(&BOSHConnection::handleDataRead, shared_from_this(), _1));
+ connection_->onDisconnected.disconnect(boost::bind(&BOSHConnection::handleDisconnected, shared_from_this(), _1));
}
+ disconnect();
+}
- void BOSHConnection::connect(const HostAddressPort& server) {
- server_ = server;
- newConnection_ = connectionFactory_->createConnection();
- newConnection_->onConnectFinished.connect(boost::bind(&BOSHConnection::handleConnectionConnectFinished, shared_from_this(), _1));
- newConnection_->onDataRead.connect(boost::bind(&BOSHConnection::handleDataRead, shared_from_this(), _1));
- newConnection_->onDisconnected.connect(boost::bind(&BOSHConnection::handleDisconnected, shared_from_this(), _1));
- SWIFT_LOG(debug) << "connect to server " << server.getAddress().toString() << ":" << server.getPort() << std::endl;
- newConnection_->connect(HostAddressPort(HostAddress("85.10.192.88"), 5280));
- }
+void BOSHConnection::connect(const HostAddressPort& server) {
+ /* FIXME: Redundant parameter */
+ Connection::ref rawConnection = connectionFactory_->createConnection();
+ connection_ = (boshURL_.getScheme() == "https") ? boost::make_shared<TLSConnection>(rawConnection, tlsFactory_) : rawConnection;
+ connection_->onConnectFinished.connect(boost::bind(&BOSHConnection::handleConnectionConnectFinished, shared_from_this(), _1));
+ connection_->onDataRead.connect(boost::bind(&BOSHConnection::handleDataRead, shared_from_this(), _1));
+ connection_->onDisconnected.connect(boost::bind(&BOSHConnection::handleDisconnected, shared_from_this(), _1));
+ connection_->connect(HostAddressPort(HostAddress(boshURL_.getHost()), boshURL_.getPort()));
+}
- void BOSHConnection::listen() {
- assert(false);
+void BOSHConnection::listen() {
+ assert(false);
+}
+
+void BOSHConnection::disconnect() {
+ if(connection_) {
+ connection_->disconnect();
+ sid_ = "";
}
+}
- void BOSHConnection::disconnect() {
- if(newConnection_)
- newConnection_->disconnect();
+void BOSHConnection::restartStream() {
+ write(createSafeByteArray(""), true, false);
+}
+
+void BOSHConnection::terminateStream() {
+ write(createSafeByteArray(""), false, true);
+}
- if(currentConnection_)
- currentConnection_->disconnect();
- }
- void BOSHConnection::write(const SafeByteArray& data) {
- SWIFT_LOG(debug) << "write data: " << safeByteArrayToString(data) << std::endl;
+void BOSHConnection::write(const SafeByteArray& data) {
+ write(data, false, false);
+}
+
+std::pair<SafeByteArray, size_t> BOSHConnection::createHTTPRequest(const SafeByteArray& data, bool streamRestart, bool terminate, long rid, const std::string& sid, const URL& boshURL) {
+ size_t size;
+ std::stringstream content;
+ SafeByteArray contentTail = createSafeByteArray("</body>");
+ std::stringstream header;
+
+ content << "<body rid='" << rid << "' sid='" << sid << "'";
+ if (streamRestart) {
+ content << " xmpp:restart='true' xmlns:xmpp='urn:xmpp:xbosh'";
}
+ if (terminate) {
+ content << " type='terminate'";
+ }
+ content << " xmlns='http://jabber.org/protocol/httpbind'>";
- void BOSHConnection::handleConnectionConnectFinished(bool error) {
- newConnection_->onConnectFinished.disconnect(boost::bind(&BOSHConnection::handleConnectionConnectFinished, shared_from_this(), _1));
- if(error) {
- onConnectFinished(true);
- return;
- }
+ SafeByteArray safeContent = createSafeByteArray(content.str());
+ safeContent.insert(safeContent.end(), data.begin(), data.end());
+ safeContent.insert(safeContent.end(), contentTail.begin(), contentTail.end());
- if(sid_.size() == 0) {
- // Session Creation Request
- std::stringstream content;
- std::stringstream header;
-
- content << "<body content='text/xml; charset=utf-8'"
- << " from='ephraim@0x10.de'"
- << " hold='1'"
- << " to='0x10.de'"
- << " ver='1.6'"
- << " wait='60'"
- << " ack='1'"
- << " xml:lang='en'"
- << " xmlns='http://jabber.org/protocol/httpbind' />\r\n";
-
- header << "POST /http-bind HTTP/1.1\r\n"
- << "Host: 0x10.de:5280\r\n"
- << "Accept-Encoding: deflate\r\n"
- << "Content-Type: text/xml; charset=utf-8\r\n"
- << "Content-Length: " << content.str().size() << "\r\n\r\n"
- << content.str();
-
- SWIFT_LOG(debug) << "request: ";
- newConnection_->write(createSafeByteArray(header.str()));
- }
+ size = safeContent.size();
+
+ header << "POST /" << boshURL.getPath() << " HTTP/1.1\r\n"
+ << "Host: " << boshURL.getHost() << ":" << boshURL.getPort() << "\r\n"
+ /*<< "Accept-Encoding: deflate\r\n"*/
+ << "Content-Type: text/xml; charset=utf-8\r\n"
+ << "Content-Length: " << size << "\r\n\r\n";
+
+ SafeByteArray safeHeader = createSafeByteArray(header.str());
+ safeHeader.insert(safeHeader.end(), safeContent.begin(), safeContent.end());
+
+ return std::pair<SafeByteArray, size_t>(safeHeader, size);
+}
+
+void BOSHConnection::write(const SafeByteArray& data, bool streamRestart, bool terminate) {
+ assert(connectionReady_);
+ assert(!sid_.empty());
+
+ SafeByteArray safeHeader = createHTTPRequest(data, streamRestart, terminate, rid_, sid_, boshURL_).first;
+
+ onBOSHDataWritten(safeHeader);
+ connection_->write(safeHeader);
+ pending_ = true;
+
+ SWIFT_LOG(debug) << "write data: " << safeByteArrayToString(safeHeader) << std::endl;
+}
+
+void BOSHConnection::handleConnectionConnectFinished(bool error) {
+ connection_->onConnectFinished.disconnect(boost::bind(&BOSHConnection::handleConnectionConnectFinished, shared_from_this(), _1));
+ connectionReady_ = !error;
+ onConnectFinished(error);
+}
+
+void BOSHConnection::startStream(const std::string& to, unsigned long rid) {
+ assert(connectionReady_);
+ // Session Creation Request
+ std::stringstream content;
+ std::stringstream header;
+
+ content << "<body content='text/xml; charset=utf-8'"
+ << " hold='1'"
+ << " to='" << to << "'"
+ << " rid='" << rid << "'"
+ << " ver='1.6'"
+ << " wait='60'" /* FIXME: we probably want this configurable*/
+ /*<< " ack='0'" FIXME: support acks */
+ << " xml:lang='en'"
+ << " xmlns:xmpp='urn:xmpp:bosh'"
+ << " xmpp:version='1.0'"
+ << " xmlns='http://jabber.org/protocol/httpbind' />";
+
+ std::string contentString = content.str();
+
+ header << "POST /" << boshURL_.getPath() << " HTTP/1.1\r\n"
+ << "Host: " << boshURL_.getHost() << ":" << boshURL_.getPort() << "\r\n"
+ /*<< "Accept-Encoding: deflate\r\n"*/
+ << "Content-Type: text/xml; charset=utf-8\r\n"
+ << "Content-Length: " << contentString.size() << "\r\n\r\n"
+ << contentString;
+
+ waitingForStartResponse_ = true;
+ SafeByteArray safeHeader = createSafeByteArray(header.str());
+ onBOSHDataWritten(safeHeader);
+ connection_->write(safeHeader);
+ SWIFT_LOG(debug) << "write stream header: " << safeByteArrayToString(safeHeader) << std::endl;
+}
+
+void BOSHConnection::handleDataRead(boost::shared_ptr<SafeByteArray> data) {
+ onBOSHDataRead(*data.get());
+ buffer_ = concat(buffer_, *data.get());
+ std::string response = safeByteArrayToString(buffer_);
+ if (response.find("\r\n\r\n") == std::string::npos) {
+ onBOSHDataRead(createSafeByteArray("[[Previous read incomplete, pending]]"));
+ return;
+ }
+
+ std::string httpCode = response.substr(response.find(" ") + 1, 3);
+ if (httpCode != "200") {
+ onHTTPError(httpCode);
+ return;
}
- void BOSHConnection::handleDataRead(const SafeByteArray& data) {
- std::string response = safeByteArrayToString(data);
- assert(response.find("\r\n\r\n") != std::string::npos);
-
- SWIFT_LOG(debug) << "response: " << response.substr(response.find("\r\n\r\n") + 4) << std::endl;
-
- BOSHParser parser;
- if(parser.parse(response.substr(response.find("\r\n\r\n") + 4))) {
- sid_ = parser.getAttribute("sid");
- onConnectFinished(false);
- int bodyStartElementLength = 0;
- bool inQuote = false;
- for(size_t i= 0; i < response.size(); i++) {
- if(response.c_str()[i] == '\'' || response.c_str()[i] == '"') {
- inQuote = !inQuote;
- }
- else if(!inQuote && response.c_str()[i] == '>') {
- bodyStartElementLength = i + 1;
- break;
- }
+ BOSHBodyExtractor parser(parserFactory_, createByteArray(response.substr(response.find("\r\n\r\n") + 4)));
+ if (parser.getBody()) {
+ if ((*parser.getBody()).attributes.getAttribute("type") == "terminate") {
+ BOSHError::Type errorType = parseTerminationCondition((*parser.getBody()).attributes.getAttribute("condition"));
+ onSessionTerminated(errorType == BOSHError::NoError ? boost::shared_ptr<BOSHError>() : boost::make_shared<BOSHError>(errorType));
+ }
+ buffer_.clear();
+ if (waitingForStartResponse_) {
+ waitingForStartResponse_ = false;
+ sid_ = (*parser.getBody()).attributes.getAttribute("sid");
+ std::string requestsString = (*parser.getBody()).attributes.getAttribute("requests");
+ int requests = 2;
+ if (!requestsString.empty()) {
+ requests = boost::lexical_cast<size_t>(requestsString);
}
- SafeByteArray payload = createSafeByteArray(response.substr(bodyStartElementLength, response.size() - bodyStartElementLength - 7));
- SWIFT_LOG(debug) << "payload: " << safeByteArrayToString(payload) << std::endl;
- onDataRead(payload);
+ onSessionStarted(sid_, requests);
}
+ SafeByteArray payload = createSafeByteArray((*parser.getBody()).content);
+ /* Say we're good to go again, so don't add anything after here in the method */
+ pending_ = false;
+ onXMPPDataRead(payload);
}
- void BOSHConnection::handleDisconnected(const boost::optional<Error>& error) {
- onDisconnected(error);
- }
+}
- HostAddressPort BOSHConnection::getLocalAddress() const {
- return newConnection_->getLocalAddress();
+BOSHError::Type BOSHConnection::parseTerminationCondition(const std::string& text) {
+ BOSHError::Type condition = BOSHError::UndefinedCondition;
+ if (text == "bad-request") {
+ condition = BOSHError::BadRequest;
+ }
+ else if (text == "host-gone") {
+ condition = BOSHError::HostGone;
+ }
+ else if (text == "host-unknown") {
+ condition = BOSHError::HostUnknown;
+ }
+ else if (text == "improper-addressing") {
+ condition = BOSHError::ImproperAddressing;
+ }
+ else if (text == "internal-server-error") {
+ condition = BOSHError::InternalServerError;
+ }
+ else if (text == "item-not-found") {
+ condition = BOSHError::ItemNotFound;
+ }
+ else if (text == "other-request") {
+ condition = BOSHError::OtherRequest;
+ }
+ else if (text == "policy-violation") {
+ condition = BOSHError::PolicyViolation;
+ }
+ else if (text == "remote-connection-failed") {
+ condition = BOSHError::RemoteConnectionFailed;
+ }
+ else if (text == "remote-stream-error") {
+ condition = BOSHError::RemoteStreamError;
}
+ else if (text == "see-other-uri") {
+ condition = BOSHError::SeeOtherURI;
+ }
+ else if (text == "system-shutdown") {
+ condition = BOSHError::SystemShutdown;
+ }
+ else if (text == "") {
+ condition = BOSHError::NoError;
+ }
+ return condition;
+}
+
+const std::string& BOSHConnection::getSID() {
+ return sid_;
+}
+
+void BOSHConnection::setRID(unsigned long rid) {
+ rid_ = rid;
+}
+
+void BOSHConnection::setSID(const std::string& sid) {
+ sid_ = sid;
+}
+
+void BOSHConnection::handleDisconnected(const boost::optional<Error>& error) {
+ onDisconnected(error);
+ sid_ = "";
+ connectionReady_ = false;
+}
+
+HostAddressPort BOSHConnection::getLocalAddress() const {
+ return connection_->getLocalAddress();
+}
+
+bool BOSHConnection::isReadyToSend() {
+ /* Without pipelining you need to not send more without first receiving the response */
+ /* With pipelining you can. Assuming we can't, here */
+ return connectionReady_ && !pending_ && !waitingForStartResponse_ && !sid_.empty();
+}
+
}
diff --git a/Swiften/Network/BOSHConnection.h b/Swiften/Network/BOSHConnection.h
index 0da92ba..283ea10 100644
--- a/Swiften/Network/BOSHConnection.h
+++ b/Swiften/Network/BOSHConnection.h
@@ -4,6 +4,13 @@
* See Documentation/Licenses/BSD-simplified.txt for more information.
*/
+/*
+ * Copyright (c) 2011 Kevin Smith
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+
#pragma once
#include <boost/enable_shared_from_this.hpp>
@@ -11,6 +18,9 @@
#include <Swiften/Network/Connection.h>
#include <Swiften/Network/HostAddressPort.h>
#include <Swiften/Base/String.h>
+#include <Swiften/Base/URL.h>
+#include <Swiften/Base/Error.h>
+#include <Swiften/Session/SessionStream.h>
namespace boost {
class thread;
@@ -21,32 +31,73 @@ namespace boost {
namespace Swift {
class ConnectionFactory;
+ class XMLParserFactory;
+ class TLSContextFactory;
+
+ class BOSHError : public SessionStream::Error {
+ public:
+ enum Type {BadRequest, HostGone, HostUnknown, ImproperAddressing,
+ InternalServerError, ItemNotFound, OtherRequest, PolicyViolation,
+ RemoteConnectionFailed, RemoteStreamError, SeeOtherURI, SystemShutdown, UndefinedCondition,
+ NoError};
+ BOSHError(Type type) : SessionStream::Error(SessionStream::Error::ConnectionReadError), type(type) {}
+ Type getType() {return type;}
+ typedef boost::shared_ptr<BOSHError> ref;
+ private:
+ Type type;
+
+ };
+
class BOSHConnection : public Connection, public boost::enable_shared_from_this<BOSHConnection> {
public:
typedef boost::shared_ptr<BOSHConnection> ref;
- static ref create(ConnectionFactory* connectionFactory) {
- return ref(new BOSHConnection(connectionFactory));
+ static ref create(const URL& boshURL, ConnectionFactory* connectionFactory, XMLParserFactory* parserFactory, TLSContextFactory* tlsFactory) {
+ return ref(new BOSHConnection(boshURL, connectionFactory, parserFactory, tlsFactory));
}
virtual ~BOSHConnection();
virtual void listen();
virtual void connect(const HostAddressPort& address);
virtual void disconnect();
virtual void write(const SafeByteArray& data);
+
virtual HostAddressPort getLocalAddress() const;
+ const std::string& getSID();
+ void setRID(unsigned long rid);
+ void setSID(const std::string& sid);
+ void startStream(const std::string& to, unsigned long rid);
+ void terminateStream();
+ bool isReadyToSend();
+ void restartStream();
+ static std::pair<SafeByteArray, size_t> createHTTPRequest(const SafeByteArray& data, bool streamRestart, bool terminate, long rid, const std::string& sid, const URL& boshURL);
+
+ boost::signal<void (BOSHError::ref)> onSessionTerminated;
+ boost::signal<void (const std::string& /*sid*/, size_t /*requests*/)> onSessionStarted;
+ boost::signal<void (const SafeByteArray&)> onXMPPDataRead;
+ boost::signal<void (const SafeByteArray&)> onBOSHDataRead;
+ boost::signal<void (const SafeByteArray&)> onBOSHDataWritten;
+ boost::signal<void (const std::string&)> onHTTPError;
private:
- BOSHConnection(ConnectionFactory* connectionFactory);
+ BOSHConnection(const URL& boshURL, ConnectionFactory* connectionFactory, XMLParserFactory* parserFactory, TLSContextFactory* tlsFactory);
+
void handleConnectionConnectFinished(bool error);
- void handleDataRead(const SafeByteArray& data);
+ void handleDataRead(boost::shared_ptr<SafeByteArray> data);
void handleDisconnected(const boost::optional<Error>& error);
+ void write(const SafeByteArray& data, bool streamRestart, bool terminate); /* FIXME: refactor */
+ BOSHError::Type parseTerminationCondition(const std::string& text);
- bool reopenAfterAction;
+ URL boshURL_;
ConnectionFactory* connectionFactory_;
- HostAddressPort server_;
- boost::shared_ptr<Connection> newConnection_;
- boost::shared_ptr<Connection> currentConnection_;
+ XMLParserFactory* parserFactory_;
+ boost::shared_ptr<Connection> connection_;
std::string sid_;
+ bool waitingForStartResponse_;
+ unsigned long rid_;
+ SafeByteArray buffer_;
+ bool pending_;
+ TLSContextFactory* tlsFactory_;
+ bool connectionReady_;
};
}
diff --git a/Swiften/Network/BOSHConnectionFactory.cpp b/Swiften/Network/BOSHConnectionFactory.cpp
index 4c49cae..7b83034 100644
--- a/Swiften/Network/BOSHConnectionFactory.cpp
+++ b/Swiften/Network/BOSHConnectionFactory.cpp
@@ -4,18 +4,23 @@
* See Documentation/Licenses/BSD-simplified.txt for more information.
*/
-#include "BOSHConnectionFactory.h"
+/*
+ * Copyright (c) 2011 Kevin Smith
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#include <Swiften/Network/BOSHConnectionFactory.h>
#include <Swiften/Network/BOSHConnection.h>
namespace Swift {
-BOSHConnectionFactory::BOSHConnectionFactory(ConnectionFactory* connectionFactory) {
- connectionFactory_ = connectionFactory;
+BOSHConnectionFactory::BOSHConnectionFactory(const URL& boshURL, ConnectionFactory* connectionFactory, XMLParserFactory* xmlParserFactory, TLSContextFactory* tlsFactory) : boshURL(boshURL), connectionFactory(connectionFactory), xmlParserFactory(xmlParserFactory), tlsFactory(tlsFactory) {
}
-boost::shared_ptr<Connection> BOSHConnectionFactory::createConnection() {
- return BOSHConnection::create(connectionFactory_);
+boost::shared_ptr<Connection> BOSHConnectionFactory::createConnection(ConnectionFactory* overrideFactory) {
+ return BOSHConnection::create(boshURL, overrideFactory != NULL ? overrideFactory : connectionFactory, xmlParserFactory, tlsFactory);
}
}
diff --git a/Swiften/Network/BOSHConnectionFactory.h b/Swiften/Network/BOSHConnectionFactory.h
index 7431cf4..3750057 100644
--- a/Swiften/Network/BOSHConnectionFactory.h
+++ b/Swiften/Network/BOSHConnectionFactory.h
@@ -4,19 +4,40 @@
* See Documentation/Licenses/BSD-simplified.txt for more information.
*/
+/*
+ * Copyright (c) 2011 Kevin Smith
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
#pragma once
+#include <string>
+
#include <Swiften/Network/ConnectionFactory.h>
#include <Swiften/Network/HostAddressPort.h>
+#include <Swiften/TLS/TLSContextFactory.h>
+#include <Swiften/Base/URL.h>
namespace Swift {
- class BOSHConnectionFactory : public ConnectionFactory {
- public:
- BOSHConnectionFactory(ConnectionFactory* connectionFactory);
- virtual boost::shared_ptr<Connection> createConnection();
+class XMLParserFactory;
+
+class BOSHConnectionFactory {
+ public:
+ BOSHConnectionFactory(const URL& boshURL, ConnectionFactory* connectionFactory, XMLParserFactory* xmlParserFactory, TLSContextFactory* tlsFactory);
+
+ /**
+ * @param overrideFactory If non-NULL, creates a connection over the given factory instead.
+ */
+ boost::shared_ptr<Connection> createConnection(ConnectionFactory* overrideFactory);
+ ConnectionFactory* getRawConnectionFactory() {return connectionFactory;}
+ TLSContextFactory* getTLSContextFactory() {return tlsFactory;}
+ private:
+ URL boshURL;
+ ConnectionFactory* connectionFactory;
+ XMLParserFactory* xmlParserFactory;
+ TLSContextFactory* tlsFactory;
+};
- private:
- ConnectionFactory* connectionFactory_;
- };
}
diff --git a/Swiften/Network/BOSHConnectionPool.cpp b/Swiften/Network/BOSHConnectionPool.cpp
new file mode 100644
index 0000000..6c3ba7e
--- /dev/null
+++ b/Swiften/Network/BOSHConnectionPool.cpp
@@ -0,0 +1,247 @@
+/*
+ * Copyright (c) 2011 Kevin Smith
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+#include <Swiften/Network/BOSHConnectionPool.h>
+
+#include <climits>
+
+#include <boost/bind.hpp>
+#include <boost/lexical_cast.hpp>
+
+#include <Swiften/Base/foreach.h>
+#include <Swiften/Base/SafeString.h>
+#include <Swiften/Network/TLSConnectionFactory.h>
+#include <Swiften/Network/HTTPConnectProxiedConnectionFactory.h>
+
+namespace Swift {
+BOSHConnectionPool::BOSHConnectionPool(boost::shared_ptr<BOSHConnectionFactory> connectionFactory, const std::string& to, long initialRID, const URL& boshHTTPConnectProxyURL, const SafeString& boshHTTPConnectProxyAuthID, const SafeString& boshHTTPConnectProxyAuthPassword)
+ : connectionFactory(connectionFactory),
+ rid(initialRID),
+ pendingTerminate(false),
+ to(to),
+ requestLimit(2),
+ restartCount(0),
+ pendingRestart(false) {
+ tlsConnectionFactory = NULL;
+ if (boshHTTPConnectProxyURL.empty()) {
+ connectProxyFactory = NULL;
+ }
+ else {
+ ConnectionFactory* rawFactory = connectionFactory->getRawConnectionFactory();
+ if (boshHTTPConnectProxyURL.getScheme() == "https") {
+ tlsConnectionFactory = new TLSConnectionFactory(connectionFactory->getTLSContextFactory(), rawFactory);
+ rawFactory = tlsConnectionFactory;
+ }
+ connectProxyFactory = new HTTPConnectProxiedConnectionFactory(rawFactory, HostAddressPort(HostAddress(boshHTTPConnectProxyURL.getHost()), boshHTTPConnectProxyURL.getPort()), boshHTTPConnectProxyAuthID, boshHTTPConnectProxyAuthPassword);
+ }
+ createConnection();
+}
+
+BOSHConnectionPool::~BOSHConnectionPool() {
+ close();
+ delete connectProxyFactory;
+ delete tlsConnectionFactory;
+}
+
+void BOSHConnectionPool::write(const SafeByteArray& data) {
+ dataQueue.push_back(data);
+ tryToSendQueuedData();
+}
+
+void BOSHConnectionPool::handleDataRead(const SafeByteArray& data) {
+ onXMPPDataRead(data);
+ tryToSendQueuedData(); /* Will rebalance the connections */
+}
+
+void BOSHConnectionPool::restartStream() {
+ BOSHConnection::ref connection = getSuitableConnection();
+ if (connection) {
+ pendingRestart = false;
+ rid++;
+ connection->setRID(rid);
+ connection->restartStream();
+ restartCount++;
+ }
+ else {
+ pendingRestart = true;
+ }
+}
+
+void BOSHConnectionPool::writeFooter() {
+ pendingTerminate = true;
+ tryToSendQueuedData();
+}
+
+void BOSHConnectionPool::close() {
+ /* TODO: Send a terminate here. */
+ std::vector<BOSHConnection::ref> connectionCopies = connections;
+ foreach (BOSHConnection::ref connection, connectionCopies) {
+ if (connection) {
+ connection->disconnect();
+ destroyConnection(connection);
+ }
+ }
+}
+
+void BOSHConnectionPool::handleSessionStarted(const std::string& sessionID, size_t requests) {
+ sid = sessionID;
+ requestLimit = requests;
+ onSessionStarted();
+}
+
+void BOSHConnectionPool::handleConnectFinished(bool error, BOSHConnection::ref connection) {
+ if (error) {
+ onSessionTerminated(boost::make_shared<BOSHError>(BOSHError::UndefinedCondition));
+ /*TODO: We can probably manage to not terminate the stream here and use the rid/ack retry
+ * logic to just swallow the error and try again (some number of tries).
+ */
+ }
+ else {
+ if (sid.empty()) {
+ connection->startStream(to, rid);
+ }
+ if (pendingRestart) {
+ restartStream();
+ }
+ tryToSendQueuedData();
+ }
+}
+
+BOSHConnection::ref BOSHConnectionPool::getSuitableConnection() {
+ BOSHConnection::ref suitableConnection;
+ foreach (BOSHConnection::ref connection, connections) {
+ if (connection->isReadyToSend()) {
+ suitableConnection = connection;
+ break;
+ }
+ }
+
+ if (!suitableConnection && connections.size() < requestLimit) {
+ /* This is not a suitable connection because it won't have yet connected and added TLS if needed. */
+ BOSHConnection::ref newConnection = createConnection();
+ newConnection->setSID(sid);
+ }
+ assert(connections.size() <= requestLimit);
+ assert((!suitableConnection) || suitableConnection->isReadyToSend());
+ return suitableConnection;
+}
+
+void BOSHConnectionPool::tryToSendQueuedData() {
+ if (sid.empty()) {
+ /* If we've not got as far as stream start yet, pend */
+ return;
+ }
+
+ BOSHConnection::ref suitableConnection = getSuitableConnection();
+ bool sent = false;
+ bool toSend = !dataQueue.empty();
+ if (suitableConnection) {
+ if (toSend) {
+ rid++;
+ suitableConnection->setRID(rid);
+ SafeByteArray data;
+ foreach (const SafeByteArray& datum, dataQueue) {
+ data.insert(data.end(), datum.begin(), datum.end());
+ }
+ suitableConnection->write(data);
+ sent = true;
+ dataQueue.clear();
+ }
+ else if (pendingTerminate) {
+ rid++;
+ suitableConnection->setRID(rid);
+ suitableConnection->terminateStream();
+ sent = true;
+ onSessionTerminated(boost::shared_ptr<BOSHError>());
+ }
+ }
+ if (!pendingTerminate) {
+ /* Ensure there's always a session waiting to read data for us */
+ bool pending = false;
+ foreach (BOSHConnection::ref connection, connections) {
+ if (connection && !connection->isReadyToSend()) {
+ pending = true;
+ }
+ }
+ if (!pending) {
+ if (restartCount >= 1) {
+ /* Don't open a second connection until we've restarted the stream twice - i.e. we've authed and resource bound.*/
+ if (suitableConnection) {
+ rid++;
+ suitableConnection->setRID(rid);
+ suitableConnection->write(createSafeByteArray(""));
+ }
+ else {
+ /* My thought process I went through when writing this, to aid anyone else confused why this can happen...
+ *
+ * What to do here? I think this isn't possible.
+ If you didn't have two connections, suitable would have made one.
+ If you have two connections and neither is suitable, pending would be true.
+ If you have a non-pending connection, it's suitable.
+
+ If I decide to do something here, remove assert above.
+
+ Ah! Yes, because there's a period between creating the connection and it being connected. */
+ }
+ }
+ }
+ }
+}
+
+void BOSHConnectionPool::handleHTTPError(const std::string& /*errorCode*/) {
+ handleSessionTerminated(boost::make_shared<BOSHError>(BOSHError::UndefinedCondition));
+}
+
+void BOSHConnectionPool::handleConnectionDisconnected(const boost::optional<Connection::Error>& error, BOSHConnection::ref connection) {
+ destroyConnection(connection);
+ if (false && error) {
+ handleSessionTerminated(boost::make_shared<BOSHError>(BOSHError::UndefinedCondition));
+ }
+ else {
+ /* We might have just freed up a connection slot to send with */
+ tryToSendQueuedData();
+ }
+}
+
+boost::shared_ptr<BOSHConnection> BOSHConnectionPool::createConnection() {
+ BOSHConnection::ref connection = boost::dynamic_pointer_cast<BOSHConnection>(connectionFactory->createConnection(connectProxyFactory));
+ connection->onXMPPDataRead.connect(boost::bind(&BOSHConnectionPool::handleDataRead, this, _1));
+ connection->onSessionStarted.connect(boost::bind(&BOSHConnectionPool::handleSessionStarted, this, _1, _2));
+ connection->onBOSHDataRead.connect(boost::bind(&BOSHConnectionPool::handleBOSHDataRead, this, _1));
+ connection->onBOSHDataWritten.connect(boost::bind(&BOSHConnectionPool::handleBOSHDataWritten, this, _1));
+ connection->onDisconnected.connect(boost::bind(&BOSHConnectionPool::handleConnectionDisconnected, this, _1, connection));
+ connection->onConnectFinished.connect(boost::bind(&BOSHConnectionPool::handleConnectFinished, this, _1, connection));
+ connection->onSessionTerminated.connect(boost::bind(&BOSHConnectionPool::handleSessionTerminated, this, _1));
+ connection->onHTTPError.connect(boost::bind(&BOSHConnectionPool::handleHTTPError, this, _1));
+ connection->connect(HostAddressPort(HostAddress("0.0.0.0"), 0));
+ connections.push_back(connection);
+ return connection;
+}
+
+void BOSHConnectionPool::destroyConnection(boost::shared_ptr<BOSHConnection> connection) {
+ connections.erase(std::remove(connections.begin(), connections.end(), connection), connections.end());
+ connection->onXMPPDataRead.disconnect(boost::bind(&BOSHConnectionPool::handleDataRead, this, _1));
+ connection->onSessionStarted.disconnect(boost::bind(&BOSHConnectionPool::handleSessionStarted, this, _1, _2));
+ connection->onBOSHDataRead.disconnect(boost::bind(&BOSHConnectionPool::handleBOSHDataRead, this, _1));
+ connection->onBOSHDataWritten.disconnect(boost::bind(&BOSHConnectionPool::handleBOSHDataWritten, this, _1));
+ connection->onDisconnected.disconnect(boost::bind(&BOSHConnectionPool::handleConnectionDisconnected, this, _1, connection));
+ connection->onConnectFinished.disconnect(boost::bind(&BOSHConnectionPool::handleConnectFinished, this, _1, connection));
+ connection->onSessionTerminated.disconnect(boost::bind(&BOSHConnectionPool::handleSessionTerminated, this, _1));
+ connection->onHTTPError.disconnect(boost::bind(&BOSHConnectionPool::handleHTTPError, this, _1));
+}
+
+void BOSHConnectionPool::handleSessionTerminated(BOSHError::ref error) {
+ onSessionTerminated(error);
+}
+
+void BOSHConnectionPool::handleBOSHDataRead(const SafeByteArray& data) {
+ onBOSHDataRead(data);
+}
+
+void BOSHConnectionPool::handleBOSHDataWritten(const SafeByteArray& data) {
+ onBOSHDataWritten(data);
+}
+
+}
diff --git a/Swiften/Network/BOSHConnectionPool.h b/Swiften/Network/BOSHConnectionPool.h
new file mode 100644
index 0000000..85e598d
--- /dev/null
+++ b/Swiften/Network/BOSHConnectionPool.h
@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 2011 Kevin Smith
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+
+#pragma once
+
+#include <vector>
+
+#include <Swiften/Base/SafeString.h>
+#include <Swiften/Network/BOSHConnectionFactory.h>
+#include <Swiften/Network/BOSHConnection.h>
+
+namespace Swift {
+ class HTTPConnectProxiedConnectionFactory;
+ class TLSConnectionFactory;
+ class BOSHConnectionPool : public boost::bsignals::trackable {
+ public:
+ BOSHConnectionPool(boost::shared_ptr<BOSHConnectionFactory> factory, const std::string& to, long initialRID, const URL& boshHTTPConnectProxyURL, const SafeString& boshHTTPConnectProxyAuthID, const SafeString& boshHTTPConnectProxyAuthPassword);
+ ~BOSHConnectionPool();
+ void write(const SafeByteArray& data);
+ void writeFooter();
+ void close();
+ void restartStream();
+
+ boost::signal<void (BOSHError::ref)> onSessionTerminated;
+ boost::signal<void ()> onSessionStarted;
+ boost::signal<void (const SafeByteArray&)> onXMPPDataRead;
+ boost::signal<void (const SafeByteArray&)> onBOSHDataRead;
+ boost::signal<void (const SafeByteArray&)> onBOSHDataWritten;
+
+ private:
+ void handleDataRead(const SafeByteArray& data);
+ void handleSessionStarted(const std::string& sid, size_t requests);
+ void handleBOSHDataRead(const SafeByteArray& data);
+ void handleBOSHDataWritten(const SafeByteArray& data);
+ void handleSessionTerminated(BOSHError::ref condition);
+ void handleConnectFinished(bool, BOSHConnection::ref connection);
+ void handleConnectionDisconnected(const boost::optional<Connection::Error>& error, BOSHConnection::ref connection);
+ void handleHTTPError(const std::string& errorCode);
+
+ private:
+ BOSHConnection::ref createConnection();
+ void destroyConnection(BOSHConnection::ref connection);
+ void tryToSendQueuedData();
+ BOSHConnection::ref getSuitableConnection();
+
+ private:
+ boost::shared_ptr<BOSHConnectionFactory> connectionFactory;
+ std::vector<BOSHConnection::ref> connections;
+ std::string sid;
+ unsigned long rid;
+ std::vector<SafeByteArray> dataQueue;
+ bool pendingTerminate;
+ std::string to;
+ size_t requestLimit;
+ int restartCount;
+ bool pendingRestart;
+ HTTPConnectProxiedConnectionFactory* connectProxyFactory;
+ TLSConnectionFactory* tlsConnectionFactory;
+ };
+}
diff --git a/Swiften/Network/BoostNetworkFactories.cpp b/Swiften/Network/BoostNetworkFactories.cpp
index 2b4c04b..488e519 100644
--- a/Swiften/Network/BoostNetworkFactories.cpp
+++ b/Swiften/Network/BoostNetworkFactories.cpp
@@ -17,7 +17,7 @@
namespace Swift {
-BoostNetworkFactories::BoostNetworkFactories(EventLoop* eventLoop) {
+BoostNetworkFactories::BoostNetworkFactories(EventLoop* eventLoop) : eventLoop(eventLoop){
timerFactory = new BoostTimerFactory(ioServiceThread.getIOService(), eventLoop);
connectionFactory = new BoostConnectionFactory(ioServiceThread.getIOService(), eventLoop);
domainNameResolver = new PlatformDomainNameResolver(eventLoop);
diff --git a/Swiften/Network/BoostNetworkFactories.h b/Swiften/Network/BoostNetworkFactories.h
index 3d268d1..c9b12da 100644
--- a/Swiften/Network/BoostNetworkFactories.h
+++ b/Swiften/Network/BoostNetworkFactories.h
@@ -53,6 +53,10 @@ namespace Swift {
return proxyProvider;
}
+ virtual EventLoop* getEventLoop() const {
+ return eventLoop;
+ }
+
private:
BoostIOServiceThread ioServiceThread;
TimerFactory* timerFactory;
@@ -63,5 +67,6 @@ namespace Swift {
XMLParserFactory* xmlParserFactory;
PlatformTLSFactories* tlsFactories;
ProxyProvider* proxyProvider;
+ EventLoop* eventLoop;
};
}
diff --git a/Swiften/Network/HTTPConnectProxiedConnection.cpp b/Swiften/Network/HTTPConnectProxiedConnection.cpp
index e05a933..3e6c986 100644
--- a/Swiften/Network/HTTPConnectProxiedConnection.cpp
+++ b/Swiften/Network/HTTPConnectProxiedConnection.cpp
@@ -4,6 +4,13 @@
* See Documentation/Licenses/BSD-simplified.txt for more information.
*/
+/*
+ * Copyright (c) 2011 Kevin Smith
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+
#include <Swiften/Network/HTTPConnectProxiedConnection.h>
#include <iostream>
@@ -11,15 +18,17 @@
#include <boost/thread.hpp>
#include <boost/lexical_cast.hpp>
+#include <Swiften/Base/Algorithm.h>
#include <Swiften/Base/Log.h>
#include <Swiften/Base/String.h>
#include <Swiften/Base/ByteArray.h>
#include <Swiften/Network/HostAddressPort.h>
#include <Swiften/Network/ConnectionFactory.h>
+#include <Swiften/StringCodecs/Base64.h>
using namespace Swift;
-HTTPConnectProxiedConnection::HTTPConnectProxiedConnection(ConnectionFactory* connectionFactory, HostAddressPort proxy) : connectionFactory_(connectionFactory), proxy_(proxy), server_(HostAddressPort(HostAddress("0.0.0.0"), 0)) {
+HTTPConnectProxiedConnection::HTTPConnectProxiedConnection(ConnectionFactory* connectionFactory, HostAddressPort proxy, const SafeString& authID, const SafeString& authPassword) : connectionFactory_(connectionFactory), proxy_(proxy), server_(HostAddressPort(HostAddress("0.0.0.0"), 0)), authID_(authID), authPassword_(authPassword) {
connected_ = false;
}
@@ -65,8 +74,18 @@ void HTTPConnectProxiedConnection::handleConnectionConnectFinished(bool error) {
connection_->onConnectFinished.disconnect(boost::bind(&HTTPConnectProxiedConnection::handleConnectionConnectFinished, shared_from_this(), _1));
if (!error) {
std::stringstream connect;
- connect << "CONNECT " << server_.getAddress().toString() << ":" << server_.getPort() << " HTTP/1.1\r\n\r\n";
- connection_->write(createSafeByteArray(connect.str()));
+ connect << "CONNECT " << server_.getAddress().toString() << ":" << server_.getPort() << " HTTP/1.1\r\n";
+ SafeByteArray data = createSafeByteArray(connect.str());
+ if (!authID_.empty() && !authPassword_.empty()) {
+ append(data, createSafeByteArray("Proxy-Authorization: Basic "));
+ SafeByteArray credentials = authID_;
+ append(credentials, createSafeByteArray(":"));
+ append(credentials, authPassword_);
+ append(data, Base64::encode(credentials));
+ append(data, createSafeByteArray("\r\n"));
+ }
+ append(data, createSafeByteArray("\r\n"));
+ connection_->write(data);
}
else {
onConnectFinished(true);
diff --git a/Swiften/Network/HTTPConnectProxiedConnection.h b/Swiften/Network/HTTPConnectProxiedConnection.h
index d3f5b7a..02d3edd 100644
--- a/Swiften/Network/HTTPConnectProxiedConnection.h
+++ b/Swiften/Network/HTTPConnectProxiedConnection.h
@@ -4,12 +4,20 @@
* See Documentation/Licenses/BSD-simplified.txt for more information.
*/
+/*
+ * Copyright (c) 2011 Kevin Smith
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+
#pragma once
#include <boost/enable_shared_from_this.hpp>
#include <Swiften/Network/Connection.h>
#include <Swiften/Network/HostAddressPort.h>
+#include <Swiften/Base/SafeString.h>
namespace boost {
class thread;
@@ -27,8 +35,8 @@ namespace Swift {
~HTTPConnectProxiedConnection();
- static ref create(ConnectionFactory* connectionFactory, HostAddressPort proxy) {
- return ref(new HTTPConnectProxiedConnection(connectionFactory, proxy));
+ static ref create(ConnectionFactory* connectionFactory, HostAddressPort proxy, const SafeString& authID, const SafeString& authPassword) {
+ return ref(new HTTPConnectProxiedConnection(connectionFactory, proxy, authID, authPassword));
}
virtual void listen();
@@ -38,7 +46,7 @@ namespace Swift {
virtual HostAddressPort getLocalAddress() const;
private:
- HTTPConnectProxiedConnection(ConnectionFactory* connectionFactory, HostAddressPort proxy);
+ HTTPConnectProxiedConnection(ConnectionFactory* connectionFactory, HostAddressPort proxy, const SafeString& authID, const SafeString& authPassword);
void handleConnectionConnectFinished(bool error);
void handleDataRead(boost::shared_ptr<SafeByteArray> data);
@@ -49,6 +57,8 @@ namespace Swift {
ConnectionFactory* connectionFactory_;
HostAddressPort proxy_;
HostAddressPort server_;
+ SafeByteArray authID_;
+ SafeByteArray authPassword_;
boost::shared_ptr<Connection> connection_;
};
}
diff --git a/Swiften/Network/HTTPConnectProxiedConnectionFactory.cpp b/Swiften/Network/HTTPConnectProxiedConnectionFactory.cpp
index ab7f18e..6ad0228 100644
--- a/Swiften/Network/HTTPConnectProxiedConnectionFactory.cpp
+++ b/Swiften/Network/HTTPConnectProxiedConnectionFactory.cpp
@@ -10,11 +10,15 @@
namespace Swift {
-HTTPConnectProxiedConnectionFactory::HTTPConnectProxiedConnectionFactory(ConnectionFactory* connectionFactory, const HostAddressPort& proxy) : connectionFactory_(connectionFactory), proxy_(proxy) {
+HTTPConnectProxiedConnectionFactory::HTTPConnectProxiedConnectionFactory(ConnectionFactory* connectionFactory, const HostAddressPort& proxy) : connectionFactory_(connectionFactory), proxy_(proxy), authID_(""), authPassword_("") {
+}
+
+
+HTTPConnectProxiedConnectionFactory::HTTPConnectProxiedConnectionFactory(ConnectionFactory* connectionFactory, const HostAddressPort& proxy, const SafeString& authID, const SafeString& authPassword) : connectionFactory_(connectionFactory), proxy_(proxy), authID_(authID), authPassword_(authPassword) {
}
boost::shared_ptr<Connection> HTTPConnectProxiedConnectionFactory::createConnection() {
- return HTTPConnectProxiedConnection::create(connectionFactory_, proxy_);
+ return HTTPConnectProxiedConnection::create(connectionFactory_, proxy_, authID_, authPassword_);
}
}
diff --git a/Swiften/Network/HTTPConnectProxiedConnectionFactory.h b/Swiften/Network/HTTPConnectProxiedConnectionFactory.h
index b475586..ef3af66 100644
--- a/Swiften/Network/HTTPConnectProxiedConnectionFactory.h
+++ b/Swiften/Network/HTTPConnectProxiedConnectionFactory.h
@@ -8,16 +8,20 @@
#include <Swiften/Network/ConnectionFactory.h>
#include <Swiften/Network/HostAddressPort.h>
+#include <Swiften/Base/SafeString.h>
namespace Swift {
class HTTPConnectProxiedConnectionFactory : public ConnectionFactory {
public:
HTTPConnectProxiedConnectionFactory(ConnectionFactory* connectionFactory, const HostAddressPort& proxy);
+ HTTPConnectProxiedConnectionFactory(ConnectionFactory* connectionFactory, const HostAddressPort& proxy, const SafeString& authID, const SafeString& authPassword);
virtual boost::shared_ptr<Connection> createConnection();
private:
ConnectionFactory* connectionFactory_;
HostAddressPort proxy_;
+ SafeString authID_;
+ SafeString authPassword_;
};
}
diff --git a/Swiften/Network/NetworkFactories.h b/Swiften/Network/NetworkFactories.h
index 6eba2f3..ebb6d62 100644
--- a/Swiften/Network/NetworkFactories.h
+++ b/Swiften/Network/NetworkFactories.h
@@ -16,6 +16,7 @@ namespace Swift {
class TLSContextFactory;
class CertificateFactory;
class ProxyProvider;
+ class EventLoop;
/**
* An interface collecting network factories.
@@ -32,5 +33,6 @@ namespace Swift {
virtual XMLParserFactory* getXMLParserFactory() const = 0;
virtual TLSContextFactory* getTLSContextFactory() const = 0;
virtual ProxyProvider* getProxyProvider() const = 0;
+ virtual EventLoop* getEventLoop() const {};
};
}
diff --git a/Swiften/Network/SConscript b/Swiften/Network/SConscript
index 399cec8..4a5370f 100644
--- a/Swiften/Network/SConscript
+++ b/Swiften/Network/SConscript
@@ -16,7 +16,8 @@ sourceList = [
"BoostConnectionServerFactory.cpp",
"BoostIOServiceThread.cpp",
"BOSHConnection.cpp",
- "BOSHConnectionFactory.cpp"
+ "BOSHConnectionPool.cpp",
+ "BOSHConnectionFactory.cpp",
"ConnectionFactory.cpp",
"ConnectionServer.cpp",
"ConnectionServerFactory.cpp",
@@ -41,6 +42,8 @@ sourceList = [
"BoostNetworkFactories.cpp",
"NetworkEnvironment.cpp",
"Timer.cpp",
+ "TLSConnection.cpp",
+ "TLSConnectionFactory.cpp",
"BoostTimer.cpp",
"ProxyProvider.cpp",
"NullProxyProvider.cpp",
diff --git a/Swiften/Network/TLSConnection.cpp b/Swiften/Network/TLSConnection.cpp
new file mode 100644
index 0000000..543ee1e
--- /dev/null
+++ b/Swiften/Network/TLSConnection.cpp
@@ -0,0 +1,91 @@
+/*
+ * Copyright (c) 2011 Kevin Smith
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#include <Swiften/Network/TLSConnection.h>
+
+#include <boost/bind.hpp>
+
+#include <Swiften/Network/HostAddressPort.h>
+#include <Swiften/TLS/TLSContext.h>
+#include <Swiften/TLS/TLSContextFactory.h>
+
+namespace Swift {
+
+TLSConnection::TLSConnection(Connection::ref connection, TLSContextFactory* tlsFactory) : connection(connection) {
+ context = tlsFactory->createTLSContext();
+ context->onDataForNetwork.connect(boost::bind(&TLSConnection::handleTLSDataForNetwork, this, _1));
+ context->onDataForApplication.connect(boost::bind(&TLSConnection::handleTLSDataForApplication, this, _1));
+ context->onConnected.connect(boost::bind(&TLSConnection::handleTLSConnectFinished, this, false));
+ context->onError.connect(boost::bind(&TLSConnection::handleTLSConnectFinished, this, true));
+
+ connection->onConnectFinished.connect(boost::bind(&TLSConnection::handleRawConnectFinished, this, _1));
+ connection->onDataRead.connect(boost::bind(&TLSConnection::handleRawDataRead, this, _1));
+ connection->onDataWritten.connect(boost::bind(&TLSConnection::handleRawDataWritten, this));
+ connection->onDisconnected.connect(boost::bind(&TLSConnection::handleRawDisconnected, this, _1));
+}
+
+TLSConnection::~TLSConnection() {
+ connection->onConnectFinished.disconnect(boost::bind(&TLSConnection::handleRawConnectFinished, this, _1));
+ connection->onDataRead.disconnect(boost::bind(&TLSConnection::handleRawDataRead, this, _1));
+ connection->onDataWritten.disconnect(boost::bind(&TLSConnection::handleRawDataWritten, this));
+ connection->onDisconnected.disconnect(boost::bind(&TLSConnection::handleRawDisconnected, this, _1));
+ delete context;
+}
+
+void TLSConnection::handleTLSConnectFinished(bool error) {
+ onConnectFinished(error);
+ if (error) {
+ disconnect();
+ }
+}
+
+void TLSConnection::handleTLSDataForNetwork(const SafeByteArray& data) {
+ connection->write(data);
+}
+
+void TLSConnection::handleTLSDataForApplication(const SafeByteArray& data) {
+ onDataRead(boost::make_shared<SafeByteArray>(data));
+}
+
+void TLSConnection::connect(const HostAddressPort& address) {
+ connection->connect(address);
+}
+
+void TLSConnection::disconnect() {
+ connection->disconnect();
+}
+
+void TLSConnection::write(const SafeByteArray& data) {
+ context->handleDataFromApplication(data);
+}
+
+HostAddressPort TLSConnection::getLocalAddress() const {
+ return connection->getLocalAddress();
+}
+
+void TLSConnection::handleRawConnectFinished(bool error) {
+ connection->onConnectFinished.disconnect(boost::bind(&TLSConnection::handleRawConnectFinished, this, _1));
+ if (error) {
+ onConnectFinished(true);
+ }
+ else {
+ context->connect();
+ }
+}
+
+void TLSConnection::handleRawDisconnected(const boost::optional<Error>& error) {
+ onDisconnected(error);
+}
+
+void TLSConnection::handleRawDataRead(boost::shared_ptr<SafeByteArray> data) {
+ context->handleDataFromNetwork(*data);
+}
+
+void TLSConnection::handleRawDataWritten() {
+ onDataWritten();
+}
+
+}
diff --git a/Swiften/Network/TLSConnection.h b/Swiften/Network/TLSConnection.h
new file mode 100644
index 0000000..a798393
--- /dev/null
+++ b/Swiften/Network/TLSConnection.h
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2011 Kevin Smith
+ * 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/enable_shared_from_this.hpp>
+#include <Swiften/Base/boost_bsignals.h>
+
+#include <Swiften/Base/SafeByteArray.h>
+#include <Swiften/Network/Connection.h>
+
+namespace Swift {
+ class HostAddressPort;
+ class TLSContextFactory;
+ class TLSContext;
+
+ class TLSConnection : public Connection {
+ public:
+
+ TLSConnection(Connection::ref connection, TLSContextFactory* tlsFactory);
+ virtual ~TLSConnection();
+
+ virtual void listen() {assert(false);};
+ virtual void connect(const HostAddressPort& address);
+ virtual void disconnect();
+ virtual void write(const SafeByteArray& data);
+
+ virtual HostAddressPort getLocalAddress() const;
+
+ private:
+ void handleRawConnectFinished(bool error);
+ void handleRawDisconnected(const boost::optional<Error>& error);
+ void handleRawDataRead(boost::shared_ptr<SafeByteArray> data);
+ void handleRawDataWritten();
+ void handleTLSConnectFinished(bool error);
+ void handleTLSDataForNetwork(const SafeByteArray& data);
+ void handleTLSDataForApplication(const SafeByteArray& data);
+ private:
+ TLSContext* context;
+ Connection::ref connection;
+ };
+}
diff --git a/Swiften/Network/TLSConnectionFactory.cpp b/Swiften/Network/TLSConnectionFactory.cpp
new file mode 100644
index 0000000..0c21650
--- /dev/null
+++ b/Swiften/Network/TLSConnectionFactory.cpp
@@ -0,0 +1,28 @@
+/*
+ * Copyright (c) 2011 Kevin Smith
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#include <Swiften/Network/TLSConnectionFactory.h>
+
+#include <boost/shared_ptr.hpp>
+
+#include <Swiften/Network/TLSConnection.h>
+
+namespace Swift {
+
+TLSConnectionFactory::TLSConnectionFactory(TLSContextFactory* contextFactory, ConnectionFactory* connectionFactory) : contextFactory(contextFactory), connectionFactory(connectionFactory){
+
+}
+
+TLSConnectionFactory::~TLSConnectionFactory() {
+
+}
+
+
+boost::shared_ptr<Connection> TLSConnectionFactory::createConnection() {
+ return boost::make_shared<TLSConnection>(connectionFactory->createConnection(), contextFactory);
+}
+
+}
diff --git a/Swiften/Network/TLSConnectionFactory.h b/Swiften/Network/TLSConnectionFactory.h
new file mode 100644
index 0000000..32757a1
--- /dev/null
+++ b/Swiften/Network/TLSConnectionFactory.h
@@ -0,0 +1,27 @@
+/*
+ * Copyright (c) 2011 Kevin Smith
+ * 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/Network/ConnectionFactory.h>
+#include <Swiften/TLS/TLSContextFactory.h>
+
+namespace Swift {
+ class Connection;
+
+ class TLSConnectionFactory : public ConnectionFactory {
+ public:
+ TLSConnectionFactory(TLSContextFactory* contextFactory, ConnectionFactory* connectionFactory);
+ virtual ~TLSConnectionFactory();
+
+ virtual boost::shared_ptr<Connection> createConnection();
+ private:
+ TLSContextFactory* contextFactory;
+ ConnectionFactory* connectionFactory;
+ };
+}
diff --git a/Swiften/Network/UnitTest/BOSHConnectionPoolTest.cpp b/Swiften/Network/UnitTest/BOSHConnectionPoolTest.cpp
new file mode 100644
index 0000000..978bf3b
--- /dev/null
+++ b/Swiften/Network/UnitTest/BOSHConnectionPoolTest.cpp
@@ -0,0 +1,423 @@
+/*
+ * Copyright (c) 2011 Kevin Smith
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#include <QA/Checker/IO.h>
+
+#include <cppunit/extensions/HelperMacros.h>
+#include <cppunit/extensions/TestFactoryRegistry.h>
+
+#include <boost/optional.hpp>
+#include <boost/bind.hpp>
+#include <boost/smart_ptr/make_shared.hpp>
+#include <boost/shared_ptr.hpp>
+#include <boost/lexical_cast.hpp>
+
+#include <Swiften/Base/Algorithm.h>
+#include <Swiften/Network/Connection.h>
+#include <Swiften/Network/ConnectionFactory.h>
+#include <Swiften/Network/BOSHConnection.h>
+#include <Swiften/Network/BOSHConnectionFactory.h>
+#include <Swiften/Network/BOSHConnectionPool.h>
+#include <Swiften/Network/HostAddressPort.h>
+#include <Swiften/EventLoop/DummyEventLoop.h>
+#include <Swiften/Parser/PlatformXMLParserFactory.h>
+
+using namespace Swift;
+
+typedef boost::shared_ptr<BOSHConnectionPool> PoolRef;
+
+class BOSHConnectionPoolTest : public CppUnit::TestFixture {
+ CPPUNIT_TEST_SUITE(BOSHConnectionPoolTest);
+ CPPUNIT_TEST(testConnectionCount_OneWrite);
+ CPPUNIT_TEST(testConnectionCount_TwoWrites);
+ CPPUNIT_TEST(testConnectionCount_ThreeWrites);
+ CPPUNIT_TEST(testConnectionCount_ThreeWrites_ManualConnect);
+ CPPUNIT_TEST(testConnectionCount_ThreeWritesTwoReads);
+ CPPUNIT_TEST(testSession);
+ CPPUNIT_TEST(testWrite_Empty);
+ CPPUNIT_TEST_SUITE_END();
+
+ public:
+ void setUp() {
+ to = "wonderland.lit";
+ path = "http-bind";
+ port = "5280";
+ sid = "MyShinySID";
+ initial = "<body wait='60' "
+ "inactivity='30' "
+ "polling='5' "
+ "requests='2' "
+ "hold='1' "
+ "maxpause='120' "
+ "sid='" + sid + "' "
+ "ver='1.6' "
+ "from='wonderland.lit' "
+ "xmlns='http://jabber.org/protocol/httpbind'/>";
+ eventLoop = new DummyEventLoop();
+ connectionFactory = new MockConnectionFactory(eventLoop);
+ factory = boost::make_shared<BOSHConnectionFactory>(URL("http", to, 5280, path), connectionFactory, &parserFactory, static_cast<TLSContextFactory*>(NULL));
+ sessionTerminated = 0;
+ sessionStarted = 0;
+ initialRID = 2349876;
+ xmppDataRead.clear();
+ boshDataRead.clear();
+ boshDataWritten.clear();
+ }
+
+ void tearDown() {
+ eventLoop->processEvents();
+ delete connectionFactory;
+ delete eventLoop;
+ }
+
+ void testConnectionCount_OneWrite() {
+ PoolRef testling = createTestling();
+ CPPUNIT_ASSERT_EQUAL(st(1), connectionFactory->connections.size());
+ eventLoop->processEvents();
+ CPPUNIT_ASSERT_EQUAL(0, sessionStarted);
+ readResponse(initial, connectionFactory->connections[0]);
+ CPPUNIT_ASSERT_EQUAL(1, sessionStarted);
+ CPPUNIT_ASSERT_EQUAL(st(1), connectionFactory->connections.size());
+ testling->write(createSafeByteArray("<blah/>"));
+ eventLoop->processEvents();
+ CPPUNIT_ASSERT_EQUAL(st(1), connectionFactory->connections.size());
+ CPPUNIT_ASSERT_EQUAL(1, sessionStarted);
+ }
+
+ void testConnectionCount_TwoWrites() {
+ PoolRef testling = createTestling();
+ CPPUNIT_ASSERT_EQUAL(st(1), connectionFactory->connections.size());
+ eventLoop->processEvents();
+ readResponse(initial, connectionFactory->connections[0]);
+ testling->write(createSafeByteArray("<blah/>"));
+ CPPUNIT_ASSERT_EQUAL(st(1), connectionFactory->connections.size());
+ testling->write(createSafeByteArray("<bleh/>"));
+ eventLoop->processEvents();
+ CPPUNIT_ASSERT_EQUAL(st(2), connectionFactory->connections.size());
+ }
+
+ void testConnectionCount_ThreeWrites() {
+ PoolRef testling = createTestling();
+ CPPUNIT_ASSERT_EQUAL(st(1), connectionFactory->connections.size());
+ eventLoop->processEvents();
+ readResponse(initial, connectionFactory->connections[0]);
+ testling->restartStream();
+ readResponse("<body/>", connectionFactory->connections[0]);
+ testling->restartStream();
+ readResponse("<body/>", connectionFactory->connections[0]);
+ testling->write(createSafeByteArray("<blah/>"));
+ testling->write(createSafeByteArray("<bleh/>"));
+ testling->write(createSafeByteArray("<bluh/>"));
+ eventLoop->processEvents();
+ CPPUNIT_ASSERT(st(2) >= connectionFactory->connections.size());
+ }
+
+ void testConnectionCount_ThreeWrites_ManualConnect() {
+ connectionFactory->autoFinishConnect = false;
+ PoolRef testling = createTestling();
+ CPPUNIT_ASSERT_EQUAL(st(1), connectionFactory->connections.size());
+ CPPUNIT_ASSERT_EQUAL(st(0), boshDataWritten.size()); /* Connection not connected yet, can't send data */
+
+ connectionFactory->connections[0]->onConnectFinished(false);
+ eventLoop->processEvents();
+ CPPUNIT_ASSERT_EQUAL(st(1), boshDataWritten.size()); /* Connection finished, stream header sent */
+
+ readResponse(initial, connectionFactory->connections[0]);
+ CPPUNIT_ASSERT_EQUAL(st(1), connectionFactory->connections.size());
+ CPPUNIT_ASSERT_EQUAL(st(1), boshDataWritten.size()); /* Don't respond to initial data with a holding call */
+
+ testling->restartStream();
+ readResponse("<body/>", connectionFactory->connections[0]);
+ testling->restartStream();
+
+
+ testling->write(createSafeByteArray("<blah/>"));
+ CPPUNIT_ASSERT_EQUAL(st(2), connectionFactory->connections.size());
+ CPPUNIT_ASSERT_EQUAL(st(3), boshDataWritten.size()); /* New connection isn't up yet. */
+
+ connectionFactory->connections[1]->onConnectFinished(false);
+ eventLoop->processEvents();
+ CPPUNIT_ASSERT_EQUAL(st(4), boshDataWritten.size()); /* New connection ready. */
+
+ testling->write(createSafeByteArray("<bleh/>"));
+ testling->write(createSafeByteArray("<bluh/>"));
+ CPPUNIT_ASSERT_EQUAL(st(4), boshDataWritten.size()); /* New data can't be sent, no free connections. */
+ eventLoop->processEvents();
+ CPPUNIT_ASSERT(st(2) >= connectionFactory->connections.size());
+ }
+
+ void testConnectionCount_ThreeWritesTwoReads() {
+ boost::shared_ptr<MockConnection> c0;
+ boost::shared_ptr<MockConnection> c1;
+ long rid = initialRID;
+
+ PoolRef testling = createTestling();
+ CPPUNIT_ASSERT_EQUAL(st(1), connectionFactory->connections.size());
+ c0 = connectionFactory->connections[0];
+ eventLoop->processEvents();
+ CPPUNIT_ASSERT_EQUAL(st(1), boshDataWritten.size()); /* header*/
+
+ rid++;
+ readResponse(initial, c0);
+ CPPUNIT_ASSERT_EQUAL(st(1), boshDataWritten.size());
+ CPPUNIT_ASSERT_EQUAL(st(1), connectionFactory->connections.size());
+ CPPUNIT_ASSERT(!c0->pending);
+
+ rid++;
+ testling->restartStream();
+ readResponse("<body/>", connectionFactory->connections[0]);
+
+ rid++;
+ testling->write(createSafeByteArray("<blah/>"));
+ eventLoop->processEvents();
+ CPPUNIT_ASSERT_EQUAL(st(2), connectionFactory->connections.size()); /* 0 was waiting for response, open and send on 1 */
+ CPPUNIT_ASSERT_EQUAL(st(4), boshDataWritten.size()); /* data */
+ c1 = connectionFactory->connections[1];
+ std::string fullBody = "<body rid='" + boost::lexical_cast<std::string>(rid) + "' sid='" + sid + "' xmlns='http://jabber.org/protocol/httpbind'><blah/></body>"; /* check empty write */
+ CPPUNIT_ASSERT_EQUAL(fullBody, lastBody());
+ CPPUNIT_ASSERT(c0->pending);
+ CPPUNIT_ASSERT(c1->pending);
+
+
+ rid++;
+ readResponse("<body xmlns='http://jabber.org/protocol/httpbind'><message><splatploing/></message></body>", c0); /* Doesn't include necessary attributes - as the support is improved this'll start to fail */
+ eventLoop->processEvents();
+ CPPUNIT_ASSERT(!c0->pending);
+ CPPUNIT_ASSERT(c1->pending);
+ CPPUNIT_ASSERT_EQUAL(st(4), boshDataWritten.size()); /* don't send empty in [0], still have [1] waiting */
+ CPPUNIT_ASSERT_EQUAL(st(2), connectionFactory->connections.size());
+
+ rid++;
+ readResponse("<body xmlns='http://jabber.org/protocol/httpbind'><message><splatploing><blittlebarg/></splatploing></message></body>", c1);
+ eventLoop->processEvents();
+ CPPUNIT_ASSERT(!c1->pending);
+ CPPUNIT_ASSERT(c0->pending);
+ CPPUNIT_ASSERT_EQUAL(st(5), boshDataWritten.size()); /* empty to make room */
+ CPPUNIT_ASSERT_EQUAL(st(2), connectionFactory->connections.size());
+
+ rid++;
+ testling->write(createSafeByteArray("<bleh/>"));
+ CPPUNIT_ASSERT(c0->pending);
+ CPPUNIT_ASSERT(c1->pending);
+ CPPUNIT_ASSERT_EQUAL(st(6), boshDataWritten.size()); /* data */
+
+ rid++;
+ testling->write(createSafeByteArray("<bluh/>"));
+ CPPUNIT_ASSERT(c0->pending);
+ CPPUNIT_ASSERT(c1->pending);
+ CPPUNIT_ASSERT_EQUAL(st(6), boshDataWritten.size()); /* Don't send data, no room */
+ eventLoop->processEvents();
+ CPPUNIT_ASSERT_EQUAL(st(2), connectionFactory->connections.size());
+ }
+
+ void testSession() {
+ to = "prosody.doomsong.co.uk";
+ path = "http-bind/";
+ factory = boost::make_shared<BOSHConnectionFactory>(URL("http", to, 5280, path), connectionFactory, &parserFactory, static_cast<TLSContextFactory*>(NULL));
+
+
+ PoolRef testling = createTestling();
+ CPPUNIT_ASSERT_EQUAL(st(1), connectionFactory->connections.size());
+ eventLoop->processEvents();
+ CPPUNIT_ASSERT_EQUAL(st(1), boshDataWritten.size()); /* header*/
+ CPPUNIT_ASSERT_EQUAL(st(1), connectionFactory->connections.size());
+
+ std::string response = "<body authid='743da605-4c2e-4de1-afac-ac040dd4a940' xmpp:version='1.0' xmlns:stream='http://etherx.jabber.org/streams' xmlns:xmpp='urn:xmpp:xbosh' inactivity='60' wait='60' polling='5' secure='true' hold='1' from='prosody.doomsong.co.uk' ver='1.6' sid='743da605-4c2e-4de1-afac-ac040dd4a940' requests='2' xmlns='http://jabber.org/protocol/httpbind'><stream:features><auth xmlns='http://jabber.org/features/iq-auth'/><mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-sasl'><mechanism>SCRAM-SHA-1</mechanism><mechanism>DIGEST-MD5</mechanism></mechanisms></stream:features></body>";
+ readResponse(response, connectionFactory->connections[0]);
+ eventLoop->processEvents();
+ CPPUNIT_ASSERT_EQUAL(st(1), boshDataWritten.size());
+ CPPUNIT_ASSERT_EQUAL(st(1), connectionFactory->connections.size());
+
+ std::string send = "<auth xmlns=\"urn:ietf:params:xml:ns:xmpp-sasl\" mechanism=\"SCRAM-SHA-1\">biwsbj1hZG1pbixyPWZhOWE5ZDhiLWZmMDctNGE4Yy04N2E3LTg4YWRiNDQxZGUwYg==</auth>";
+ testling->write(createSafeByteArray(send));
+ eventLoop->processEvents();
+ CPPUNIT_ASSERT_EQUAL(st(2), boshDataWritten.size());
+ CPPUNIT_ASSERT_EQUAL(st(1), connectionFactory->connections.size());
+
+ response = "<body xmlns='http://jabber.org/protocol/httpbind' sid='743da605-4c2e-4de1-afac-ac040dd4a940' xmlns:stream = 'http://etherx.jabber.org/streams'><challenge xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>cj1mYTlhOWQ4Yi1mZjA3LTRhOGMtODdhNy04OGFkYjQ0MWRlMGJhZmZlMWNhMy1mMDJkLTQ5NzEtYjkyNS0yM2NlNWQ2MDQyMjYscz1OVGd5WkdWaFptTXRaVE15WXkwMFpXUmhMV0ZqTURRdFpqYzRNbUppWmpGa1pqWXgsaT00MDk2</challenge></body>";
+ readResponse(response, connectionFactory->connections[0]);
+ eventLoop->processEvents();
+ CPPUNIT_ASSERT_EQUAL(st(2), boshDataWritten.size());
+ CPPUNIT_ASSERT_EQUAL(st(1), connectionFactory->connections.size());
+
+ send = "<response xmlns=\"urn:ietf:params:xml:ns:xmpp-sasl\">Yz1iaXdzLHI9ZmE5YTlkOGItZmYwNy00YThjLTg3YTctODhhZGI0NDFkZTBiYWZmZTFjYTMtZjAyZC00OTcxLWI5MjUtMjNjZTVkNjA0MjI2LHA9aU11NWt3dDN2VWplU2RqL01Jb3VIRldkZjBnPQ==</response>";
+ testling->write(createSafeByteArray(send));
+ eventLoop->processEvents();
+ CPPUNIT_ASSERT_EQUAL(st(3), boshDataWritten.size());
+ CPPUNIT_ASSERT_EQUAL(st(1), connectionFactory->connections.size());
+
+ response = "<body xmlns='http://jabber.org/protocol/httpbind' sid='743da605-4c2e-4de1-afac-ac040dd4a940' xmlns:stream = 'http://etherx.jabber.org/streams'><success xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>dj1YNmNBY3BBOWxHNjNOOXF2bVQ5S0FacERrVm89</success></body>";
+ readResponse(response, connectionFactory->connections[0]);
+ eventLoop->processEvents();
+ CPPUNIT_ASSERT_EQUAL(st(3), boshDataWritten.size());
+ CPPUNIT_ASSERT_EQUAL(st(1), connectionFactory->connections.size());
+ }
+
+ void testWrite_Empty() {
+ boost::shared_ptr<MockConnection> c0;
+
+ PoolRef testling = createTestling();
+ c0 = connectionFactory->connections[0];
+ CPPUNIT_ASSERT_EQUAL(st(1), connectionFactory->connections.size());
+ eventLoop->processEvents();
+
+ readResponse(initial, c0);
+ CPPUNIT_ASSERT_EQUAL(st(1), boshDataWritten.size()); /* Shouldn't have sent anything extra */
+ testling->restartStream();
+ CPPUNIT_ASSERT_EQUAL(st(2), boshDataWritten.size());
+ readResponse("<body></body>", c0);
+ CPPUNIT_ASSERT_EQUAL(st(3), boshDataWritten.size());
+ std::string fullBody = "<body rid='" + boost::lexical_cast<std::string>(initialRID + 2) + "' sid='" + sid + "' xmlns='http://jabber.org/protocol/httpbind'></body>";
+ std::string response = boshDataWritten[2];
+ size_t bodyPosition = response.find("\r\n\r\n");
+ CPPUNIT_ASSERT_EQUAL(fullBody, response.substr(bodyPosition+4));
+
+
+ }
+
+ private:
+
+ PoolRef createTestling() {
+ PoolRef pool = boost::make_shared<BOSHConnectionPool>(factory, to, initialRID, URL(), "", "");
+ pool->onXMPPDataRead.connect(boost::bind(&BOSHConnectionPoolTest::handleXMPPDataRead, this, _1));
+ pool->onBOSHDataRead.connect(boost::bind(&BOSHConnectionPoolTest::handleBOSHDataRead, this, _1));
+ pool->onBOSHDataWritten.connect(boost::bind(&BOSHConnectionPoolTest::handleBOSHDataWritten, this, _1));
+ pool->onSessionStarted.connect(boost::bind(&BOSHConnectionPoolTest::handleSessionStarted, this));
+ pool->onSessionTerminated.connect(boost::bind(&BOSHConnectionPoolTest::handleSessionTerminated, this));
+ return pool;
+ }
+
+ std::string lastBody() {
+ std::string response = boshDataWritten[boshDataWritten.size() - 1];
+ size_t bodyPosition = response.find("\r\n\r\n");
+ return response.substr(bodyPosition+4);
+ }
+
+ size_t st(int val) {
+ return static_cast<size_t>(val);
+ }
+
+ void handleXMPPDataRead(const SafeByteArray& d) {
+ xmppDataRead.push_back(safeByteArrayToString(d));
+ }
+
+ void handleBOSHDataRead(const SafeByteArray& d) {
+ boshDataRead.push_back(safeByteArrayToString(d));
+ }
+
+ void handleBOSHDataWritten(const SafeByteArray& d) {
+ boshDataWritten.push_back(safeByteArrayToString(d));
+ }
+
+
+ void handleSessionStarted() {
+ sessionStarted++;
+ }
+
+ void handleSessionTerminated() {
+ sessionTerminated++;
+ }
+
+ struct MockConnection : public Connection {
+ public:
+ MockConnection(const std::vector<HostAddressPort>& failingPorts, EventLoop* eventLoop, bool autoFinishConnect) : eventLoop(eventLoop), failingPorts(failingPorts), disconnected(false), pending(false), autoFinishConnect(autoFinishConnect) {
+ }
+
+ void listen() { assert(false); }
+
+ void connect(const HostAddressPort& address) {
+ hostAddressPort = address;
+ bool fail = std::find(failingPorts.begin(), failingPorts.end(), address) != failingPorts.end();
+ if (autoFinishConnect) {
+ eventLoop->postEvent(boost::bind(boost::ref(onConnectFinished), fail));
+ }
+ }
+
+ HostAddressPort getLocalAddress() const { return HostAddressPort(); }
+
+ void disconnect() {
+ disconnected = true;
+ onDisconnected(boost::optional<Connection::Error>());
+ }
+
+ void write(const SafeByteArray& d) {
+ append(dataWritten, d);
+ pending = true;
+ }
+
+ EventLoop* eventLoop;
+ boost::optional<HostAddressPort> hostAddressPort;
+ std::vector<HostAddressPort> failingPorts;
+ ByteArray dataWritten;
+ bool disconnected;
+ bool pending;
+ bool autoFinishConnect;
+ };
+
+ struct MockConnectionFactory : public ConnectionFactory {
+ MockConnectionFactory(EventLoop* eventLoop, bool autoFinishConnect = true) : eventLoop(eventLoop), autoFinishConnect(autoFinishConnect) {
+ }
+
+ boost::shared_ptr<Connection> createConnection() {
+ boost::shared_ptr<MockConnection> connection = boost::make_shared<MockConnection>(failingPorts, eventLoop, autoFinishConnect);
+ connections.push_back(connection);
+ return connection;
+ }
+
+ EventLoop* eventLoop;
+ std::vector< boost::shared_ptr<MockConnection> > connections;
+ std::vector<HostAddressPort> failingPorts;
+ bool autoFinishConnect;
+ };
+
+ void readResponse(const std::string& response, boost::shared_ptr<MockConnection> connection) {
+ connection->pending = false;
+ boost::shared_ptr<SafeByteArray> data1 = boost::make_shared<SafeByteArray>(createSafeByteArray(
+ "HTTP/1.1 200 OK\r\n"
+ "Content-Type: text/xml; charset=utf-8\r\n"
+ "Access-Control-Allow-Origin: *\r\n"
+ "Access-Control-Allow-Headers: Content-Type\r\n"
+ "Content-Length: "));
+ connection->onDataRead(data1);
+ boost::shared_ptr<SafeByteArray> data2 = boost::make_shared<SafeByteArray>(createSafeByteArray(boost::lexical_cast<std::string>(response.size())));
+ connection->onDataRead(data2);
+ boost::shared_ptr<SafeByteArray> data3 = boost::make_shared<SafeByteArray>(createSafeByteArray("\r\n\r\n"));
+ connection->onDataRead(data3);
+ boost::shared_ptr<SafeByteArray> data4 = boost::make_shared<SafeByteArray>(createSafeByteArray(response));
+ connection->onDataRead(data4);
+ }
+
+ std::string fullRequestFor(const std::string& data) {
+ std::string body = data;
+ std::string result = "POST /" + path + " HTTP/1.1\r\n"
+ + "Host: " + to + ":" + port + "\r\n"
+ + "Content-Type: text/xml; charset=utf-8\r\n"
+ + "Content-Length: " + boost::lexical_cast<std::string>(body.size()) + "\r\n\r\n"
+ + body;
+ return result;
+ }
+
+ private:
+ DummyEventLoop* eventLoop;
+ MockConnectionFactory* connectionFactory;
+ std::vector<std::string> xmppDataRead;
+ std::vector<std::string> boshDataRead;
+ std::vector<std::string> boshDataWritten;
+ PlatformXMLParserFactory parserFactory;
+ std::string to;
+ std::string path;
+ std::string port;
+ std::string sid;
+ std::string initial;
+ long initialRID;
+ boost::shared_ptr<BOSHConnectionFactory> factory;
+ int sessionStarted;
+ int sessionTerminated;
+
+};
+
+
+CPPUNIT_TEST_SUITE_REGISTRATION(BOSHConnectionPoolTest);
diff --git a/Swiften/Network/UnitTest/BOSHConnectionTest.cpp b/Swiften/Network/UnitTest/BOSHConnectionTest.cpp
new file mode 100644
index 0000000..9215725
--- /dev/null
+++ b/Swiften/Network/UnitTest/BOSHConnectionTest.cpp
@@ -0,0 +1,291 @@
+/*
+ * Copyright (c) 2011 Kevin Smith
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#include <QA/Checker/IO.h>
+
+#include <cppunit/extensions/HelperMacros.h>
+#include <cppunit/extensions/TestFactoryRegistry.h>
+
+#include <boost/optional.hpp>
+#include <boost/bind.hpp>
+#include <boost/smart_ptr/make_shared.hpp>
+#include <boost/shared_ptr.hpp>
+#include <boost/lexical_cast.hpp>
+
+#include <Swiften/Base/Algorithm.h>
+#include <Swiften/Network/Connection.h>
+#include <Swiften/Network/ConnectionFactory.h>
+#include <Swiften/Network/BOSHConnection.h>
+#include <Swiften/Network/HostAddressPort.h>
+#include <Swiften/EventLoop/DummyEventLoop.h>
+#include <Swiften/Parser/PlatformXMLParserFactory.h>
+
+using namespace Swift;
+
+class BOSHConnectionTest : public CppUnit::TestFixture {
+ CPPUNIT_TEST_SUITE(BOSHConnectionTest);
+ CPPUNIT_TEST(testHeader);
+ CPPUNIT_TEST(testReadiness_ok);
+ CPPUNIT_TEST(testReadiness_pending);
+ CPPUNIT_TEST(testReadiness_disconnect);
+ CPPUNIT_TEST(testReadiness_noSID);
+ CPPUNIT_TEST(testWrite_Receive);
+ CPPUNIT_TEST(testWrite_ReceiveTwice);
+ CPPUNIT_TEST(testRead_Fragment);
+ CPPUNIT_TEST(testHTTPRequest);
+ CPPUNIT_TEST(testHTTPRequest_Empty);
+ CPPUNIT_TEST_SUITE_END();
+
+ public:
+ void setUp() {
+ eventLoop = new DummyEventLoop();
+ connectionFactory = new MockConnectionFactory(eventLoop);
+ connectFinished = false;
+ disconnected = false;
+ dataRead.clear();
+ }
+
+ void tearDown() {
+ eventLoop->processEvents();
+ delete connectionFactory;
+ delete eventLoop;
+ }
+
+ void testHeader() {
+ BOSHConnection::ref testling = createTestling();
+ testling->connect(HostAddressPort(HostAddress("127.0.0.1"), 5280));
+ eventLoop->processEvents();
+ testling->startStream("wonderland.lit", 1);
+ std::string initial("<body wait='60' "
+ "inactivity='30' "
+ "polling='5' "
+ "requests='2' "
+ "hold='1' "
+ "maxpause='120' "
+ "sid='MyShinySID' "
+ "ver='1.6' "
+ "from='wonderland.lit' "
+ "xmlns='http://jabber.org/protocol/httpbind'/>");
+ readResponse(initial, connectionFactory->connections[0]);
+ CPPUNIT_ASSERT_EQUAL(std::string("MyShinySID"), sid);
+ CPPUNIT_ASSERT(testling->isReadyToSend());
+ }
+
+ void testReadiness_ok() {
+ BOSHConnection::ref testling = createTestling();
+ testling->connect(HostAddressPort(HostAddress("127.0.0.1"), 5280));
+ eventLoop->processEvents();
+ testling->setSID("blahhhhh");
+ CPPUNIT_ASSERT(testling->isReadyToSend());
+ }
+
+ void testReadiness_pending() {
+ BOSHConnection::ref testling = createTestling();
+ testling->connect(HostAddressPort(HostAddress("127.0.0.1"), 5280));
+ eventLoop->processEvents();
+ testling->setSID("mySID");
+ CPPUNIT_ASSERT(testling->isReadyToSend());
+ testling->write(createSafeByteArray("<mypayload/>"));
+ CPPUNIT_ASSERT(!testling->isReadyToSend());
+ readResponse("<body><blah/></body>", connectionFactory->connections[0]);
+ CPPUNIT_ASSERT(testling->isReadyToSend());
+ }
+
+ void testReadiness_disconnect() {
+ BOSHConnection::ref testling = createTestling();
+ testling->connect(HostAddressPort(HostAddress("127.0.0.1"), 5280));
+ eventLoop->processEvents();
+ testling->setSID("mySID");
+ CPPUNIT_ASSERT(testling->isReadyToSend());
+ connectionFactory->connections[0]->onDisconnected(false);
+ CPPUNIT_ASSERT(!testling->isReadyToSend());
+ }
+
+
+ void testReadiness_noSID() {
+ BOSHConnection::ref testling = createTestling();
+ testling->connect(HostAddressPort(HostAddress("127.0.0.1"), 5280));
+ eventLoop->processEvents();
+ CPPUNIT_ASSERT(!testling->isReadyToSend());
+ }
+
+ void testWrite_Receive() {
+ BOSHConnection::ref testling = createTestling();
+ testling->connect(HostAddressPort(HostAddress("127.0.0.1"), 5280));
+ eventLoop->processEvents();
+ testling->setSID("mySID");
+ testling->write(createSafeByteArray("<mypayload/>"));
+ readResponse("<body><blah/></body>", connectionFactory->connections[0]);
+ CPPUNIT_ASSERT_EQUAL(std::string("<blah/>"), byteArrayToString(dataRead));
+
+ }
+
+ void testWrite_ReceiveTwice() {
+ BOSHConnection::ref testling = createTestling();
+ testling->connect(HostAddressPort(HostAddress("127.0.0.1"), 5280));
+ eventLoop->processEvents();
+ testling->setSID("mySID");
+ testling->write(createSafeByteArray("<mypayload/>"));
+ readResponse("<body><blah/></body>", connectionFactory->connections[0]);
+ CPPUNIT_ASSERT_EQUAL(std::string("<blah/>"), byteArrayToString(dataRead));
+ dataRead.clear();
+ testling->write(createSafeByteArray("<mypayload2/>"));
+ readResponse("<body><bleh/></body>", connectionFactory->connections[0]);
+ CPPUNIT_ASSERT_EQUAL(std::string("<bleh/>"), byteArrayToString(dataRead));
+ }
+
+ void testRead_Fragment() {
+ BOSHConnection::ref testling = createTestling();
+ testling->connect(HostAddressPort(HostAddress("127.0.0.1"), 5280));
+ eventLoop->processEvents();
+ CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), connectionFactory->connections.size());
+ boost::shared_ptr<MockConnection> connection = connectionFactory->connections[0];
+ boost::shared_ptr<SafeByteArray> data1 = boost::make_shared<SafeByteArray>(createSafeByteArray(
+ "HTTP/1.1 200 OK\r\n"
+ "Content-Type: text/xml; charset=utf-8\r\n"
+ "Access-Control-Allow-Origin: *\r\n"
+ "Access-Control-Allow-Headers: Content-Type\r\n"
+ "Content-Length: 64\r\n"));
+ boost::shared_ptr<SafeByteArray> data2 = boost::make_shared<SafeByteArray>(createSafeByteArray(
+ "\r\n<body xmlns='http://jabber.org/protocol/httpbind'>"
+ "<bl"));
+ boost::shared_ptr<SafeByteArray> data3 = boost::make_shared<SafeByteArray>(createSafeByteArray(
+ "ah/>"
+ "</body>"));
+ connection->onDataRead(data1);
+ connection->onDataRead(data2);
+ CPPUNIT_ASSERT(dataRead.empty());
+ connection->onDataRead(data3);
+ CPPUNIT_ASSERT_EQUAL(std::string("<blah/>"), byteArrayToString(dataRead));
+ }
+
+ void testHTTPRequest() {
+ std::string data = "<blah/>";
+ std::string sid = "wigglebloom";
+ std::string fullBody = "<body xmlns='http://jabber.org/protocol/httpbind' sid='" + sid + "' rid='20'>" + data + "</body>";
+ std::pair<SafeByteArray, size_t> http = BOSHConnection::createHTTPRequest(createSafeByteArray(data), false, false, 20, sid, URL());
+ CPPUNIT_ASSERT_EQUAL(fullBody.size(), http.second);
+ }
+
+ void testHTTPRequest_Empty() {
+ std::string data = "";
+ std::string sid = "wigglebloomsickle";
+ std::string fullBody = "<body rid='42' sid='" + sid + "' xmlns='http://jabber.org/protocol/httpbind'>" + data + "</body>";
+ std::pair<SafeByteArray, size_t> http = BOSHConnection::createHTTPRequest(createSafeByteArray(data), false, false, 42, sid, URL());
+ CPPUNIT_ASSERT_EQUAL(fullBody.size(), http.second);
+ std::string response = safeByteArrayToString(http.first);
+ size_t bodyPosition = response.find("\r\n\r\n");
+ CPPUNIT_ASSERT_EQUAL(fullBody, response.substr(bodyPosition+4));
+ }
+
+ private:
+
+ BOSHConnection::ref createTestling() {
+ BOSHConnection::ref c = BOSHConnection::create(URL("http", "wonderland.lit", 5280, "http-bind"), connectionFactory, &parserFactory, static_cast<TLSContextFactory*>(NULL));
+ c->onConnectFinished.connect(boost::bind(&BOSHConnectionTest::handleConnectFinished, this, _1));
+ c->onDisconnected.connect(boost::bind(&BOSHConnectionTest::handleDisconnected, this, _1));
+ c->onXMPPDataRead.connect(boost::bind(&BOSHConnectionTest::handleDataRead, this, _1));
+ c->onSessionStarted.connect(boost::bind(&BOSHConnectionTest::handleSID, this, _1));
+ c->setRID(42);
+ return c;
+ }
+
+ void handleConnectFinished(bool error) {
+ connectFinished = true;
+ connectFinishedWithError = error;
+ }
+
+ void handleDisconnected(const boost::optional<Connection::Error>& e) {
+ disconnected = true;
+ disconnectedError = e;
+ }
+
+ void handleDataRead(const SafeByteArray& d) {
+ append(dataRead, d);
+ }
+
+ void handleSID(const std::string& s) {
+ sid = s;
+ }
+
+ struct MockConnection : public Connection {
+ public:
+ MockConnection(const std::vector<HostAddressPort>& failingPorts, EventLoop* eventLoop) : eventLoop(eventLoop), failingPorts(failingPorts), disconnected(false) {
+ }
+
+ void listen() { assert(false); }
+
+ void connect(const HostAddressPort& address) {
+ hostAddressPort = address;
+ 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() {
+ disconnected = true;
+ onDisconnected(boost::optional<Connection::Error>());
+ }
+
+ void write(const SafeByteArray& d) {
+ append(dataWritten, d);
+ }
+
+ EventLoop* eventLoop;
+ boost::optional<HostAddressPort> hostAddressPort;
+ std::vector<HostAddressPort> failingPorts;
+ ByteArray dataWritten;
+ bool disconnected;
+ };
+
+ struct MockConnectionFactory : public ConnectionFactory {
+ MockConnectionFactory(EventLoop* eventLoop) : eventLoop(eventLoop) {
+ }
+
+ boost::shared_ptr<Connection> createConnection() {
+ boost::shared_ptr<MockConnection> connection = boost::make_shared<MockConnection>(failingPorts, eventLoop);
+ connections.push_back(connection);
+ return connection;
+ }
+
+ EventLoop* eventLoop;
+ std::vector< boost::shared_ptr<MockConnection> > connections;
+ std::vector<HostAddressPort> failingPorts;
+ };
+
+ void readResponse(const std::string& response, boost::shared_ptr<MockConnection> connection) {
+ boost::shared_ptr<SafeByteArray> data1 = boost::make_shared<SafeByteArray>(createSafeByteArray(
+ "HTTP/1.1 200 OK\r\n"
+ "Content-Type: text/xml; charset=utf-8\r\n"
+ "Access-Control-Allow-Origin: *\r\n"
+ "Access-Control-Allow-Headers: Content-Type\r\n"
+ "Content-Length: "));
+ connection->onDataRead(data1);
+ boost::shared_ptr<SafeByteArray> data2 = boost::make_shared<SafeByteArray>(createSafeByteArray(boost::lexical_cast<std::string>(response.size())));
+ connection->onDataRead(data2);
+ boost::shared_ptr<SafeByteArray> data3 = boost::make_shared<SafeByteArray>(createSafeByteArray("\r\n\r\n"));
+ connection->onDataRead(data3);
+ boost::shared_ptr<SafeByteArray> data4 = boost::make_shared<SafeByteArray>(createSafeByteArray(response));
+ connection->onDataRead(data4);
+ }
+
+
+ private:
+ DummyEventLoop* eventLoop;
+ MockConnectionFactory* connectionFactory;
+ bool connectFinished;
+ bool connectFinishedWithError;
+ bool disconnected;
+ boost::optional<Connection::Error> disconnectedError;
+ ByteArray dataRead;
+ PlatformXMLParserFactory parserFactory;
+ std::string sid;
+
+};
+
+
+CPPUNIT_TEST_SUITE_REGISTRATION(BOSHConnectionTest);
diff --git a/Swiften/Network/UnitTest/HTTPConnectProxiedConnectionTest.cpp b/Swiften/Network/UnitTest/HTTPConnectProxiedConnectionTest.cpp
index 133773f..c0252d4 100644
--- a/Swiften/Network/UnitTest/HTTPConnectProxiedConnectionTest.cpp
+++ b/Swiften/Network/UnitTest/HTTPConnectProxiedConnectionTest.cpp
@@ -163,7 +163,7 @@ class HTTPConnectProxiedConnectionTest : public CppUnit::TestFixture {
private:
HTTPConnectProxiedConnection::ref createTestling() {
- boost::shared_ptr<HTTPConnectProxiedConnection> c = HTTPConnectProxiedConnection::create(connectionFactory, proxyHost);
+ boost::shared_ptr<HTTPConnectProxiedConnection> c = HTTPConnectProxiedConnection::create(connectionFactory, proxyHost, "", "");
c->onConnectFinished.connect(boost::bind(&HTTPConnectProxiedConnectionTest::handleConnectFinished, this, _1));
c->onDisconnected.connect(boost::bind(&HTTPConnectProxiedConnectionTest::handleDisconnected, this, _1));
c->onDataRead.connect(boost::bind(&HTTPConnectProxiedConnectionTest::handleDataRead, this, _1));
diff --git a/Swiften/Parser/BOSHBodyExtractor.cpp b/Swiften/Parser/BOSHBodyExtractor.cpp
index d8759a3..eeebe8a 100644
--- a/Swiften/Parser/BOSHBodyExtractor.cpp
+++ b/Swiften/Parser/BOSHBodyExtractor.cpp
@@ -124,6 +124,8 @@ BOSHBodyExtractor::BOSHBodyExtractor(XMLParserFactory* parserFactory, const Byte
BOSHBodyParserClient parserClient(this);
boost::shared_ptr<XMLParser> parser(parserFactory->createXMLParser(&parserClient));
if (!parser->parse(std::string(reinterpret_cast<const char*>(vecptr(data)), std::distance(data.begin(), i)))) {
+ /* TODO: This needs to be only validating the BOSH <body> element, so that XMPP parsing errors are caught at
+ the correct higher layer */
body = boost::optional<BOSHBody>();
return;
}
diff --git a/Swiften/Parser/BOSHParser.cpp b/Swiften/Parser/BOSHParser.cpp
deleted file mode 100644
index 9fb218a..0000000
--- a/Swiften/Parser/BOSHParser.cpp
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright (c) 2011 Thilo Cestonaro
- * Licensed under the simplified BSD license.
- * See Documentation/Licenses/BSD-simplified.txt for more information.
- */
-
-#include <cassert>
-
-#include <Swiften/Parser/BOSHParser.h>
-#include <Swiften/Parser/XMLParser.h>
-#include <Swiften/Parser/PlatformXMLParserFactory.h>
-
-namespace Swift {
-
-BOSHParser::BOSHParser() :
- xmlParser_(0),
- level_(-1),
- parseErrorOccurred_(false)
-{
- xmlParser_ = PlatformXMLParserFactory().createXMLParser(this);
-}
-
-BOSHParser::~BOSHParser() {
- delete xmlParser_;
-}
-
-bool BOSHParser::parse(const std::string& data) {
- bool xmlParseResult = xmlParser_->parse(data);
- return xmlParseResult && !parseErrorOccurred_;
-}
-
-void BOSHParser::handleStartElement(const std::string& /*element*/, const std::string& /*ns*/, const AttributeMap& attributes) {
- if (!parseErrorOccurred_) {
- if (level_ == BoshTopLevel) {
- boshBodyAttributes_ = attributes;
- }
- }
- ++level_;
-}
-
-void BOSHParser::handleEndElement(const std::string& /*element*/, const std::string& /*ns*/) {
- assert(level_ > BoshTopLevel);
- --level_;
- if (!parseErrorOccurred_) {
-
- }
-}
-
-void BOSHParser::handleCharacterData(const std::string& /*data*/) {
- if (!parseErrorOccurred_) {
-
- }
-}
-
-}
diff --git a/Swiften/Parser/BOSHParser.h b/Swiften/Parser/BOSHParser.h
deleted file mode 100644
index 69b3d13..0000000
--- a/Swiften/Parser/BOSHParser.h
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright (c) 2011 Thilo Cestonaro
- * Licensed under the simplified BSD license.
- * See Documentation/Licenses/BSD-simplified.txt for more information.
- */
-
-#pragma once
-
-#include <boost/noncopyable.hpp>
-
-#include <Swiften/Parser/XMLParserClient.h>
-#include <Swiften/Parser/AttributeMap.h>
-
-namespace Swift {
- class XMLParser;
-
- class BOSHParser : public XMLParserClient, boost::noncopyable {
- public:
- BOSHParser();
- ~BOSHParser();
-
- bool parse(const std::string&);
-
- std::string getAttribute(const std::string& attribute, const std::string& ns = "") const {
- return boshBodyAttributes_.getAttribute(attribute, ns);
- }
- private:
- virtual void handleStartElement(
- const std::string& element,
- const std::string& ns,
- const AttributeMap& attributes);
- virtual void handleEndElement(const std::string& element, const std::string& ns);
- virtual void handleCharacterData(const std::string& data);
-
- private:
- AttributeMap boshBodyAttributes_;
- XMLParser* xmlParser_;
- enum Level {
- BoshTopLevel = -1,
- TopLevel = 0,
- StreamLevel = 1,
- ElementLevel = 2
- };
- int level_;
- bool parseErrorOccurred_;
- };
-}
diff --git a/Swiften/Parser/SConscript b/Swiften/Parser/SConscript
index dd19238..e4c2778 100644
--- a/Swiften/Parser/SConscript
+++ b/Swiften/Parser/SConscript
@@ -11,7 +11,6 @@ sources = [
"AuthChallengeParser.cpp",
"AuthSuccessParser.cpp",
"AuthResponseParser.cpp",
- "BOSHParser.cpp",
"CompressParser.cpp",
"ElementParser.cpp",
"IQParser.cpp",
diff --git a/Swiften/QA/ClientTest/ClientTest.cpp b/Swiften/QA/ClientTest/ClientTest.cpp
index 4515893..3b8734e 100644
--- a/Swiften/QA/ClientTest/ClientTest.cpp
+++ b/Swiften/QA/ClientTest/ClientTest.cpp
@@ -28,12 +28,13 @@ enum TestStage {
Reconnect
};
TestStage stage;
+ClientOptions options;
void handleDisconnected(boost::optional<ClientError> e) {
std::cout << "Disconnected: " << e << std::endl;
if (stage == FirstConnect) {
stage = Reconnect;
- client->connect();
+ client->connect(options);
}
else {
eventLoop.stop();
@@ -66,13 +67,22 @@ int main(int, char**) {
return -1;
}
+ char* boshHost = getenv("SWIFT_CLIENTTEST_BOSH_HOST");
+ char* boshPort = getenv("SWIFT_CLIENTTEST_BOSH_PORT");
+ char* boshPath = getenv("SWIFT_CLIENTTEST_BOSH_PATH");
+
+ if (boshHost && boshPort && boshPath) {
+ std::cout << "Using BOSH with URL: http://" << boshHost << ":" << boshPort << "/" << boshPath << std::endl;
+ options.boshURL = URL("http", boshHost, atoi(boshPort), boshPath);
+ }
+
client = new Swift::Client(JID(jid), std::string(pass), &networkFactories);
- ClientXMLTracer* tracer = new ClientXMLTracer(client);
+ ClientXMLTracer* tracer = new ClientXMLTracer(client, !options.boshURL.empty());
client->onConnected.connect(&handleConnected);
client->onDisconnected.connect(boost::bind(&handleDisconnected, _1));
client->setAlwaysTrustCertificates();
stage = FirstConnect;
- client->connect();
+ client->connect(options);
{
Timer::ref timer = networkFactories.getTimerFactory()->createTimer(60000);
diff --git a/Swiften/SConscript b/Swiften/SConscript
index 8c3ad42..9e61fc6 100644
--- a/Swiften/SConscript
+++ b/Swiften/SConscript
@@ -191,6 +191,7 @@ if env["SCONS_STAGE"] == "build" :
"Session/SessionTracer.cpp",
"Session/SessionStream.cpp",
"Session/BasicSessionStream.cpp",
+ "Session/BOSHSessionStream.cpp",
"StringCodecs/Base64.cpp",
"StringCodecs/SHA1.cpp",
"StringCodecs/SHA256.cpp",
@@ -285,6 +286,8 @@ if env["SCONS_STAGE"] == "build" :
File("Network/UnitTest/ConnectorTest.cpp"),
File("Network/UnitTest/ChainedConnectorTest.cpp"),
File("Network/UnitTest/HTTPConnectProxiedConnectionTest.cpp"),
+ File("Network/UnitTest/BOSHConnectionTest.cpp"),
+ File("Network/UnitTest/BOSHConnectionPoolTest.cpp"),
File("Parser/PayloadParsers/UnitTest/BodyParserTest.cpp"),
File("Parser/PayloadParsers/UnitTest/DiscoInfoParserTest.cpp"),
File("Parser/PayloadParsers/UnitTest/ErrorParserTest.cpp"),
diff --git a/Swiften/Session/BOSHSessionStream.cpp b/Swiften/Session/BOSHSessionStream.cpp
new file mode 100644
index 0000000..95390f4
--- /dev/null
+++ b/Swiften/Session/BOSHSessionStream.cpp
@@ -0,0 +1,215 @@
+/*
+ * Copyright (c) 2011 Kevin Smith
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+
+#include <Swiften/Session/BOSHSessionStream.h>
+
+#include <boost/bind.hpp>
+#include <boost/random/mersenne_twister.hpp>
+#include <boost/random/uniform_int.hpp>
+#include <boost/random/variate_generator.hpp>
+
+#include <Swiften/Base/foreach.h>
+#include <Swiften/Elements/StreamType.h>
+#include <Swiften/StreamStack/XMPPLayer.h>
+#include <Swiften/StreamStack/StreamStack.h>
+#include <Swiften/StreamStack/ConnectionLayer.h>
+#include <Swiften/StreamStack/WhitespacePingLayer.h>
+#include <Swiften/StreamStack/CompressionLayer.h>
+#include <Swiften/StreamStack/TLSLayer.h>
+#include <Swiften/TLS/TLSContextFactory.h>
+#include <Swiften/TLS/TLSContext.h>
+#include <Swiften/EventLoop/EventLoop.h>
+
+namespace Swift {
+
+BOSHSessionStream::BOSHSessionStream(
+ boost::shared_ptr<BOSHConnectionFactory> connectionFactory, /*FIXME: probably rip out*/
+ PayloadParserFactoryCollection* payloadParserFactories,
+ PayloadSerializerCollection* payloadSerializers,
+ TLSContextFactory* tlsContextFactory,
+ TimerFactory* timerFactory,
+ XMLParserFactory* xmlParserFactory,
+ EventLoop* eventLoop,
+ const std::string& to,
+ const URL& boshHTTPConnectProxyURL,
+ const SafeString& boshHTTPConnectProxyAuthID,
+ const SafeString& boshHTTPConnectProxyAuthPassword) :
+ available(false),
+ payloadParserFactories(payloadParserFactories),
+ payloadSerializers(payloadSerializers),
+ tlsContextFactory(tlsContextFactory),
+ timerFactory(timerFactory),
+ xmlParserFactory(xmlParserFactory),
+ eventLoop(eventLoop),
+ firstHeader(true) {
+
+ boost::mt19937 random;
+ boost::uniform_int<> dist(0, LONG_MAX);
+ random.seed(time(NULL));
+ boost::variate_generator<boost::mt19937&, boost::uniform_int<> > randomRID(random, dist);
+ long initialRID = randomRID();
+
+ connectionPool = new BOSHConnectionPool(connectionFactory, to, initialRID, boshHTTPConnectProxyURL, boshHTTPConnectProxyAuthID, boshHTTPConnectProxyAuthPassword);
+ connectionPool->onSessionTerminated.connect(boost::bind(&BOSHSessionStream::handlePoolSessionTerminated, this, _1));
+ connectionPool->onSessionStarted.connect(boost::bind(&BOSHSessionStream::handlePoolSessionStarted, this));
+ connectionPool->onXMPPDataRead.connect(boost::bind(&BOSHSessionStream::handlePoolXMPPDataRead, this, _1));
+ connectionPool->onBOSHDataRead.connect(boost::bind(&BOSHSessionStream::handlePoolBOSHDataRead, this, _1));
+ connectionPool->onBOSHDataWritten.connect(boost::bind(&BOSHSessionStream::handlePoolBOSHDataWritten, this, _1));
+
+ xmppLayer = new XMPPLayer(payloadParserFactories, payloadSerializers, xmlParserFactory, ClientStreamType);
+ xmppLayer->onStreamStart.connect(boost::bind(&BOSHSessionStream::handleStreamStartReceived, this, _1));
+ xmppLayer->onElement.connect(boost::bind(&BOSHSessionStream::handleElementReceived, this, _1));
+ xmppLayer->onError.connect(boost::bind(&BOSHSessionStream::handleXMPPError, this));
+ xmppLayer->onWriteData.connect(boost::bind(&BOSHSessionStream::handleXMPPLayerDataWritten, this, _1));
+
+ available = true;
+}
+
+BOSHSessionStream::~BOSHSessionStream() {
+ close();
+ connectionPool->onSessionTerminated.disconnect(boost::bind(&BOSHSessionStream::handlePoolSessionTerminated, this, _1));
+ connectionPool->onSessionStarted.disconnect(boost::bind(&BOSHSessionStream::handlePoolSessionStarted, this));
+ connectionPool->onXMPPDataRead.disconnect(boost::bind(&BOSHSessionStream::handlePoolXMPPDataRead, this, _1));
+ connectionPool->onBOSHDataRead.disconnect(boost::bind(&BOSHSessionStream::handlePoolBOSHDataRead, this, _1));
+ connectionPool->onBOSHDataWritten.disconnect(boost::bind(&BOSHSessionStream::handlePoolBOSHDataWritten, this, _1));
+ delete connectionPool;
+ connectionPool = NULL;
+ xmppLayer->onStreamStart.disconnect(boost::bind(&BOSHSessionStream::handleStreamStartReceived, this, _1));
+ xmppLayer->onElement.disconnect(boost::bind(&BOSHSessionStream::handleElementReceived, this, _1));
+ xmppLayer->onError.disconnect(boost::bind(&BOSHSessionStream::handleXMPPError, this));
+ xmppLayer->onWriteData.disconnect(boost::bind(&BOSHSessionStream::handleXMPPLayerDataWritten, this, _1));
+ delete xmppLayer;
+ xmppLayer = NULL;
+}
+
+void BOSHSessionStream::handlePoolXMPPDataRead(const SafeByteArray& data) {
+ xmppLayer->handleDataRead(data);
+}
+
+void BOSHSessionStream::writeElement(boost::shared_ptr<Element> element) {
+ assert(available);
+ xmppLayer->writeElement(element);
+}
+
+void BOSHSessionStream::writeFooter() {
+ connectionPool->writeFooter();
+}
+
+void BOSHSessionStream::writeData(const std::string& data) {
+ assert(available);
+ xmppLayer->writeData(data);
+}
+
+void BOSHSessionStream::close() {
+ connectionPool->close();
+}
+
+bool BOSHSessionStream::isOpen() {
+ return available;
+}
+
+bool BOSHSessionStream::supportsTLSEncryption() {
+ return false;
+}
+
+void BOSHSessionStream::addTLSEncryption() {
+ assert(available);
+}
+
+bool BOSHSessionStream::isTLSEncrypted() {
+ return false;
+}
+
+Certificate::ref BOSHSessionStream::getPeerCertificate() const {
+ return Certificate::ref();
+}
+
+boost::shared_ptr<CertificateVerificationError> BOSHSessionStream::getPeerCertificateVerificationError() const {
+ return boost::shared_ptr<CertificateVerificationError>();
+}
+
+ByteArray BOSHSessionStream::getTLSFinishMessage() const {
+ return ByteArray();
+}
+
+bool BOSHSessionStream::supportsZLibCompression() {
+ return false;
+}
+
+void BOSHSessionStream::addZLibCompression() {
+
+}
+
+void BOSHSessionStream::setWhitespacePingEnabled(bool /*enabled*/) {
+ return;
+}
+
+void BOSHSessionStream::resetXMPPParser() {
+ xmppLayer->resetParser();
+}
+
+void BOSHSessionStream::handleStreamStartReceived(const ProtocolHeader& header) {
+ onStreamStartReceived(header);
+}
+
+void BOSHSessionStream::handleElementReceived(boost::shared_ptr<Element> element) {
+ onElementReceived(element);
+}
+
+void BOSHSessionStream::handleXMPPError() {
+ available = false;
+ onClosed(boost::shared_ptr<Error>(new Error(Error::ParseError)));
+}
+
+void BOSHSessionStream::handlePoolSessionStarted() {
+ fakeStreamHeaderReceipt();
+}
+
+void BOSHSessionStream::handlePoolSessionTerminated(BOSHError::ref error) {
+ eventLoop->postEvent(boost::bind(&BOSHSessionStream::fakeStreamFooterReceipt, this, error), shared_from_this());
+}
+
+void BOSHSessionStream::writeHeader(const ProtocolHeader& header) {
+ streamHeader = header;
+ /*First time we're told to do this, don't (the sending of the initial header is handled on connect)
+ On subsequent requests we should restart the stream the BOSH way.
+ */
+ if (!firstHeader) {
+ eventLoop->postEvent(boost::bind(&BOSHSessionStream::fakeStreamHeaderReceipt, this), shared_from_this());
+ eventLoop->postEvent(boost::bind(&BOSHConnectionPool::restartStream, connectionPool), shared_from_this());
+ }
+ firstHeader = false;
+}
+
+
+void BOSHSessionStream::fakeStreamHeaderReceipt() {
+ std::stringstream header;
+ header << "<stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' from='";
+ header << streamHeader.getTo() << "' id='dummy' version='1.0'>";
+
+ xmppLayer->handleDataRead(createSafeByteArray(header.str()));
+}
+
+void BOSHSessionStream::fakeStreamFooterReceipt(BOSHError::ref error) {
+ std::string footer("</stream:stream>");
+ xmppLayer->handleDataRead(createSafeByteArray(footer));
+ onClosed(error);
+}
+
+void BOSHSessionStream::handleXMPPLayerDataWritten(const SafeByteArray& data) {
+ eventLoop->postEvent(boost::bind(&BOSHConnectionPool::write, connectionPool, data), shared_from_this());
+}
+
+void BOSHSessionStream::handlePoolBOSHDataRead(const SafeByteArray& data) {
+ onDataRead(data);
+}
+
+void BOSHSessionStream::handlePoolBOSHDataWritten(const SafeByteArray& data) {
+ onDataWritten(data);
+}
+
+};
diff --git a/Swiften/Session/BOSHSessionStream.h b/Swiften/Session/BOSHSessionStream.h
new file mode 100644
index 0000000..75c1f2a
--- /dev/null
+++ b/Swiften/Session/BOSHSessionStream.h
@@ -0,0 +1,99 @@
+/*
+ * Copyright (c) 2011 Kevin Smith
+ * 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/SafeString.h>
+#include <Swiften/Base/SafeByteArray.h>
+#include <Swiften/Network/BOSHConnectionPool.h>
+#include <Swiften/Network/BOSHConnectionFactory.h>
+#include <Swiften/Session/SessionStream.h>
+#include <Swiften/Elements/StreamType.h>
+#include <Swiften/EventLoop/EventOwner.h>
+
+namespace Swift {
+ class TimerFactory;
+ class PayloadParserFactoryCollection;
+ class PayloadSerializerCollection;
+ class StreamStack;
+ class XMPPLayer;
+ class ConnectionLayer;
+ class CompressionLayer;
+ class XMLParserFactory;
+ class TLSContextFactory;
+ class EventLoop;
+
+ class BOSHSessionStream : public SessionStream, public EventOwner, public boost::enable_shared_from_this<BOSHSessionStream> {
+ public:
+ BOSHSessionStream(
+ boost::shared_ptr<BOSHConnectionFactory> connectionFactory,
+ PayloadParserFactoryCollection* payloadParserFactories,
+ PayloadSerializerCollection* payloadSerializers,
+ TLSContextFactory* tlsContextFactory,
+ TimerFactory* whitespacePingLayerFactory,
+ XMLParserFactory* xmlParserFactory,
+ EventLoop* eventLoop,
+ const std::string& to,
+ const URL& boshHTTPConnectProxyURL,
+ const SafeString& boshHTTPConnectProxyAuthID,
+ const SafeString& boshHTTPConnectProxyAuthPassword
+ );
+ ~BOSHSessionStream();
+
+ virtual void close();
+ virtual bool isOpen();
+
+ virtual void writeHeader(const ProtocolHeader& header);
+ virtual void writeElement(boost::shared_ptr<Element>);
+ virtual void writeFooter();
+ virtual void writeData(const std::string& data);
+
+ virtual bool supportsZLibCompression();
+ virtual void addZLibCompression();
+
+ virtual bool supportsTLSEncryption();
+ virtual void addTLSEncryption();
+ virtual bool isTLSEncrypted();
+ virtual Certificate::ref getPeerCertificate() const;
+ virtual boost::shared_ptr<CertificateVerificationError> getPeerCertificateVerificationError() const;
+ virtual ByteArray getTLSFinishMessage() const;
+
+ virtual void setWhitespacePingEnabled(bool);
+
+ virtual void resetXMPPParser();
+
+ private:
+ void handleXMPPError();
+ void handleStreamStartReceived(const ProtocolHeader&);
+ void handleElementReceived(boost::shared_ptr<Element>);
+ void handlePoolXMPPDataRead(const SafeByteArray& data);
+ void handleXMPPLayerDataWritten(const SafeByteArray& data);
+ void handlePoolSessionStarted();
+ void handlePoolBOSHDataRead(const SafeByteArray& data);
+ void handlePoolBOSHDataWritten(const SafeByteArray& data);
+ void handlePoolSessionTerminated(BOSHError::ref condition);
+
+ private:
+ void fakeStreamHeaderReceipt();
+ void fakeStreamFooterReceipt(BOSHError::ref error);
+
+ private:
+ BOSHConnectionPool* connectionPool;
+ bool available;
+ PayloadParserFactoryCollection* payloadParserFactories;
+ PayloadSerializerCollection* payloadSerializers;
+ TLSContextFactory* tlsContextFactory;
+ TimerFactory* timerFactory;
+ XMLParserFactory* xmlParserFactory;
+ XMPPLayer* xmppLayer;
+ ProtocolHeader streamHeader;
+ EventLoop* eventLoop;
+ bool firstHeader;
+ };
+
+}
diff --git a/Swiften/Session/BasicSessionStream.cpp b/Swiften/Session/BasicSessionStream.cpp
index 07a04b8..70bbeea 100644
--- a/Swiften/Session/BasicSessionStream.cpp
+++ b/Swiften/Session/BasicSessionStream.cpp
@@ -136,6 +136,10 @@ ByteArray BasicSessionStream::getTLSFinishMessage() const {
return tlsLayer->getContext()->getFinishMessage();
}
+bool BasicSessionStream::supportsZLibCompression() {
+ return true;
+}
+
void BasicSessionStream::addZLibCompression() {
compressionLayer = new CompressionLayer();
streamStack->addLayer(compressionLayer);
diff --git a/Swiften/Session/BasicSessionStream.h b/Swiften/Session/BasicSessionStream.h
index 2ed5ac6..b0c4331 100644
--- a/Swiften/Session/BasicSessionStream.h
+++ b/Swiften/Session/BasicSessionStream.h
@@ -47,6 +47,7 @@ namespace Swift {
virtual void writeFooter();
virtual void writeData(const std::string& data);
+ virtual bool supportsZLibCompression();
virtual void addZLibCompression();
virtual bool supportsTLSEncryption();
diff --git a/Swiften/Session/SessionStream.h b/Swiften/Session/SessionStream.h
index e6b9469..096f185 100644
--- a/Swiften/Session/SessionStream.h
+++ b/Swiften/Session/SessionStream.h
@@ -46,6 +46,7 @@ namespace Swift {
virtual void writeElement(boost::shared_ptr<Element>) = 0;
virtual void writeData(const std::string& data) = 0;
+ virtual bool supportsZLibCompression() = 0;
virtual void addZLibCompression() = 0;
virtual bool supportsTLSEncryption() = 0;
diff --git a/Swiften/StreamStack/XMPPLayer.h b/Swiften/StreamStack/XMPPLayer.h
index 9be00b2..81f0457 100644
--- a/Swiften/StreamStack/XMPPLayer.h
+++ b/Swiften/StreamStack/XMPPLayer.h
@@ -23,8 +23,10 @@ namespace Swift {
class XMPPSerializer;
class PayloadSerializerCollection;
class XMLParserFactory;
+ class BOSHSessionStream;
class XMPPLayer : public XMPPParserClient, public HighLayer, boost::noncopyable {
+ friend class BOSHSessionStream;
public:
XMPPLayer(
PayloadParserFactoryCollection* payloadParserFactories,