diff options
| author | Kevin Smith <git@kismith.co.uk> | 2011-11-12 16:56:21 (GMT) | 
|---|---|---|
| committer | Kevin Smith <git@kismith.co.uk> | 2011-12-13 08:17:58 (GMT) | 
| commit | 81c09a0f6a3e87b078340d7f35d0dea4c03f3a6d (patch) | |
| tree | 4371c5808ee26b2b5ed79ace9ccb439ff2988945 | |
| parent | fd17fe0d239f97cedebe4ceffa234155bd299b68 (diff) | |
| download | swift-81c09a0f6a3e87b078340d7f35d0dea4c03f3a6d.zip swift-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.
43 files changed, 2136 insertions, 273 deletions
| diff --git a/Swift/Controllers/MainController.cpp b/Swift/Controllers/MainController.cpp index 2f9c42e..7ed53a2 100644 --- a/Swift/Controllers/MainController.cpp +++ b/Swift/Controllers/MainController.cpp @@ -504,12 +504,6 @@ void MainController::performLoginFromCachedCredentials() {  	ClientOptions clientOptions;  	clientOptions.forgetPassword = eagleMode_;  	clientOptions.useTLS = eagleMode_ ? ClientOptions::RequireTLS : ClientOptions::UseTLSWhenAvailable; -	if (clientJID.getDomain() == "wonderland.lit") { -		clientOptions.boshURL = URL("http", "192.168.1.185", 5280, "http-bind/"); -	} -	else if (clientJID.getDomain() == "prosody.doomsong.co.uk") { -		clientOptions.boshURL = URL("http", "192.168.1.130", 5280, "http-bind/"); -	}  	client_->connect(clientOptions);  } 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, | 
 Swift
 Swift