summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorKevin Smith <git@kismith.co.uk>2011-11-12 16:56:21 (GMT)
committerKevin Smith <git@kismith.co.uk>2011-12-13 08:17:58 (GMT)
commit81c09a0f6a3e87b078340d7f35d0dea4c03f3a6d (patch)
tree4371c5808ee26b2b5ed79ace9ccb439ff2988945 /Swiften/Network/UnitTest/BOSHConnectionPoolTest.cpp
parentfd17fe0d239f97cedebe4ceffa234155bd299b68 (diff)
downloadswift-contrib-81c09a0f6a3e87b078340d7f35d0dea4c03f3a6d.zip
swift-contrib-81c09a0f6a3e87b078340d7f35d0dea4c03f3a6d.tar.bz2
BOSH Support for Swiften
This adds support for BOSH to Swiften. It does not expose it to Swift. Release-Notes: Swiften now allows connects over BOSH, if used appropriately.
Diffstat (limited to 'Swiften/Network/UnitTest/BOSHConnectionPoolTest.cpp')
-rw-r--r--Swiften/Network/UnitTest/BOSHConnectionPoolTest.cpp423
1 files changed, 423 insertions, 0 deletions
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);