diff options
Diffstat (limited to 'Swiften/Network')
-rw-r--r-- | Swiften/Network/BoostConnection.cpp | 100 | ||||
-rw-r--r-- | Swiften/Network/BoostConnection.h | 43 | ||||
-rw-r--r-- | Swiften/Network/BoostConnectionFactory.cpp | 12 | ||||
-rw-r--r-- | Swiften/Network/BoostConnectionFactory.h | 18 | ||||
-rw-r--r-- | Swiften/Network/Connection.h | 40 | ||||
-rw-r--r-- | Swiften/Network/ConnectionFactory.cpp | 8 | ||||
-rw-r--r-- | Swiften/Network/ConnectionFactory.h | 16 | ||||
-rw-r--r-- | Swiften/Network/DomainNameResolveException.h | 11 | ||||
-rw-r--r-- | Swiften/Network/DomainNameResolver.cpp | 176 | ||||
-rw-r--r-- | Swiften/Network/DomainNameResolver.h | 27 | ||||
-rw-r--r-- | Swiften/Network/HostAddress.cpp | 49 | ||||
-rw-r--r-- | Swiften/Network/HostAddress.h | 24 | ||||
-rw-r--r-- | Swiften/Network/HostAddressPort.h | 26 | ||||
-rw-r--r-- | Swiften/Network/Makefile.inc | 13 | ||||
-rw-r--r-- | Swiften/Network/Timer.cpp | 40 | ||||
-rw-r--r-- | Swiften/Network/Timer.h | 31 | ||||
-rw-r--r-- | Swiften/Network/UnitTest/HostAddressTest.cpp | 33 | ||||
-rw-r--r-- | Swiften/Network/UnitTest/Makefile.inc | 2 |
18 files changed, 669 insertions, 0 deletions
diff --git a/Swiften/Network/BoostConnection.cpp b/Swiften/Network/BoostConnection.cpp new file mode 100644 index 0000000..f055f6a --- /dev/null +++ b/Swiften/Network/BoostConnection.cpp @@ -0,0 +1,100 @@ +#include "Swiften/Network/BoostConnection.h" + +#include <iostream> +#include <boost/bind.hpp> +#include <boost/thread.hpp> + +#include "Swiften/EventLoop/MainEventLoop.h" +#include "Swiften/Base/String.h" +#include "Swiften/Base/ByteArray.h" +#include "Swiften/Network/DomainNameResolver.h" +#include "Swiften/Network/DomainNameResolveException.h" + +namespace Swift { + +static const size_t BUFFER_SIZE = 4096; + +BoostConnection::BoostConnection(const String& domain) : + Connection(domain), ioService_(0), thread_(0), socket_(0), readBuffer_(BUFFER_SIZE) { + ioService_ = new boost::asio::io_service(); +} + +BoostConnection::~BoostConnection() { + MainEventLoop::removeEventsFromOwner(this); + ioService_->stop(); + thread_->join(); + delete socket_; + delete thread_; + delete ioService_; +} + +void BoostConnection::connect() { + thread_ = new boost::thread(boost::bind(&BoostConnection::doConnect, this)); +} + +void BoostConnection::disconnect() { + if (ioService_) { + ioService_->post(boost::bind(&BoostConnection::doDisconnect, this)); + } +} + +void BoostConnection::write(const ByteArray& data) { + if (ioService_) { + ioService_->post(boost::bind(&BoostConnection::doWrite, this, data)); + } +} + +void BoostConnection::doConnect() { + DomainNameResolver resolver; + try { + HostAddressPort addressPort = resolver.resolve(getDomain().getUTF8String()); + socket_ = new boost::asio::ip::tcp::socket(*ioService_); + boost::asio::ip::tcp::endpoint endpoint( + boost::asio::ip::address::from_string(addressPort.getAddress().toString()), addressPort.getPort()); + socket_->async_connect( + endpoint, + boost::bind(&BoostConnection::handleConnectFinished, this, boost::asio::placeholders::error)); + ioService_->run(); + } + catch (const DomainNameResolveException& e) { + MainEventLoop::postEvent(boost::bind(boost::ref(onError), DomainNameResolveError), this); + } +} + +void BoostConnection::handleConnectFinished(const boost::system::error_code& error) { + if (!error) { + MainEventLoop::postEvent(boost::bind(boost::ref(onConnected)), this); + doRead(); + } + else if (error != boost::asio::error::operation_aborted) { + MainEventLoop::postEvent(boost::bind(boost::ref(onError), ConnectionError), this); + } +} + +void BoostConnection::doRead() { + socket_->async_read_some( + boost::asio::buffer(readBuffer_), + boost::bind(&BoostConnection::handleSocketRead, this, boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred)); +} + +void BoostConnection::doWrite(const ByteArray& data) { + boost::asio::write(*socket_, boost::asio::buffer(static_cast<const char*>(data.getData()), data.getSize())); +} + +void BoostConnection::handleSocketRead(const boost::system::error_code& error, size_t bytesTransferred) { + if (!error) { + MainEventLoop::postEvent(boost::bind(boost::ref(onDataRead), ByteArray(&readBuffer_[0], bytesTransferred)), this); + doRead(); + } + else if (error != boost::asio::error::operation_aborted) { + MainEventLoop::postEvent(boost::bind(boost::ref(onError), ReadError), this); + } +} + +void BoostConnection::doDisconnect() { + if (socket_) { + socket_->close(); + } +} + +} diff --git a/Swiften/Network/BoostConnection.h b/Swiften/Network/BoostConnection.h new file mode 100644 index 0000000..f8fa514 --- /dev/null +++ b/Swiften/Network/BoostConnection.h @@ -0,0 +1,43 @@ +#ifndef SWIFTEN_BoostConnection_H +#define SWIFTEN_BoostConnection_H + +#include <boost/asio.hpp> + +#include "Swiften/Network/Connection.h" + +namespace boost { + class thread; + namespace system { + class error_code; + } +} + +namespace Swift { + class BoostConnection : public Connection { + public: + BoostConnection(const String& domain); + ~BoostConnection(); + + virtual void connect(); + virtual void disconnect(); + virtual void write(const ByteArray& data); + + private: + void doConnect(); + void doDisconnect(); + + void handleConnectFinished(const boost::system::error_code& error); + void handleSocketRead(const boost::system::error_code& error, size_t bytesTransferred); + void doRead(); + void doWrite(const ByteArray&); + + private: + boost::asio::io_service* ioService_; + boost::thread* thread_; + boost::asio::ip::tcp::socket* socket_; + std::vector<char> readBuffer_; + bool disconnecting_; + }; +} + +#endif diff --git a/Swiften/Network/BoostConnectionFactory.cpp b/Swiften/Network/BoostConnectionFactory.cpp new file mode 100644 index 0000000..9c542ac --- /dev/null +++ b/Swiften/Network/BoostConnectionFactory.cpp @@ -0,0 +1,12 @@ +#include "Swiften/Network/BoostConnectionFactory.h" + +namespace Swift { + +BoostConnectionFactory::BoostConnectionFactory() { +} + +BoostConnection* BoostConnectionFactory::createConnection(const String& domain) { + return new BoostConnection(domain); +} + +} diff --git a/Swiften/Network/BoostConnectionFactory.h b/Swiften/Network/BoostConnectionFactory.h new file mode 100644 index 0000000..b6a27b2 --- /dev/null +++ b/Swiften/Network/BoostConnectionFactory.h @@ -0,0 +1,18 @@ +#ifndef SWIFTEN_BoostConnectionFactory_H +#define SWIFTEN_BoostConnectionFactory_H + +#include "Swiften/Network/ConnectionFactory.h" +#include "Swiften/Network/BoostConnection.h" + +namespace Swift { + class BoostConnection; + + class BoostConnectionFactory : public ConnectionFactory { + public: + BoostConnectionFactory(); + + virtual BoostConnection* createConnection(const String& domain); + }; +} + +#endif diff --git a/Swiften/Network/Connection.h b/Swiften/Network/Connection.h new file mode 100644 index 0000000..6d05eee --- /dev/null +++ b/Swiften/Network/Connection.h @@ -0,0 +1,40 @@ +#ifndef SWIFTEN_CONNECTION_H +#define SWIFTEN_CONNECTION_H + +#include <boost/signals.hpp> + +#include "Swiften/Base/ByteArray.h" +#include "Swiften/Base/String.h" + +namespace Swift { + class Connection { + public: + enum Error { + DomainNameResolveError, + ConnectionError, + ReadError + }; + + Connection(const String& domain) : domain_(domain) {} + virtual ~Connection() {} + + virtual void connect() = 0; + virtual void disconnect() = 0; + virtual void write(const ByteArray& data) = 0; + + public: + boost::signal<void ()> onConnected; + boost::signal<void (Error)> onError; + boost::signal<void (const ByteArray&)> onDataRead; + + protected: + const String& getDomain() const { + return domain_; + } + + private: + String domain_; + }; +} + +#endif diff --git a/Swiften/Network/ConnectionFactory.cpp b/Swiften/Network/ConnectionFactory.cpp new file mode 100644 index 0000000..686a165 --- /dev/null +++ b/Swiften/Network/ConnectionFactory.cpp @@ -0,0 +1,8 @@ +#include "Swiften/Network/ConnectionFactory.h" + +namespace Swift { + +ConnectionFactory::~ConnectionFactory() { +} + +} diff --git a/Swiften/Network/ConnectionFactory.h b/Swiften/Network/ConnectionFactory.h new file mode 100644 index 0000000..2af9ebf --- /dev/null +++ b/Swiften/Network/ConnectionFactory.h @@ -0,0 +1,16 @@ +#ifndef SWIFTEN_ConnectionFactory_H +#define SWIFTEN_ConnectionFactory_H + +namespace Swift { + class String; + class Connection; + + class ConnectionFactory { + public: + virtual ~ConnectionFactory(); + + virtual Connection* createConnection(const String& domain) = 0; + }; +} + +#endif diff --git a/Swiften/Network/DomainNameResolveException.h b/Swiften/Network/DomainNameResolveException.h new file mode 100644 index 0000000..a6cfbc6 --- /dev/null +++ b/Swiften/Network/DomainNameResolveException.h @@ -0,0 +1,11 @@ +#ifndef SWIFTEN_DOMAINNAMELOOKUPEXCEPTION_H +#define SWIFTEN_DOMAINNAMELOOKUPEXCEPTION_H + +namespace Swift { + class DomainNameResolveException { + public: + DomainNameResolveException() {} + }; +} + +#endif diff --git a/Swiften/Network/DomainNameResolver.cpp b/Swiften/Network/DomainNameResolver.cpp new file mode 100644 index 0000000..d2bbf0d --- /dev/null +++ b/Swiften/Network/DomainNameResolver.cpp @@ -0,0 +1,176 @@ +#include "Swiften/Network/DomainNameResolver.h" +#include "Swiften/Base/Platform.h" + +#include <stdlib.h> +#include <boost/asio.hpp> +#include <idna.h> +#ifdef SWIFTEN_PLATFORM_WINDOWS +#undef UNICODE +#include <windows.h> +#include <windns.h> +#ifndef DNS_TYPE_SRV +#define DNS_TYPE_SRV 33 +#endif +#else +#include <arpa/nameser.h> +#include <arpa/nameser_compat.h> +#include <resolv.h> +#endif + +#include "Swiften/Network/DomainNameResolveException.h" +#include "Swiften/Base/String.h" +#include "Swiften/Base/ByteArray.h" + +namespace Swift { + +DomainNameResolver::DomainNameResolver() { +} + +DomainNameResolver::~DomainNameResolver() { +} + +HostAddressPort DomainNameResolver::resolve(const String& domain) { + char* output; + if (idna_to_ascii_8z(domain.getUTF8Data(), &output, 0) == IDNA_SUCCESS) { + std::string outputString(output); + free(output); + return resolveDomain(outputString); + } + else { + return resolveDomain(domain.getUTF8String()); + } +} + +HostAddressPort DomainNameResolver::resolveDomain(const std::string& domain) { + try { + return resolveXMPPService(domain); + } + catch (const DomainNameResolveException&) { + } + return HostAddressPort(resolveHostName(domain), 5222); +} + +HostAddressPort DomainNameResolver::resolveXMPPService(const std::string& domain) { + std::string srvQuery = "_xmpp-client._tcp." + domain; + +#if defined(SWIFTEN_PLATFORM_WINDOWS) + DNS_RECORD* responses; + // FIXME: This conversion doesn't work if unicode is deffed above + if (DnsQuery(srvQuery.c_str(), DNS_TYPE_SRV, DNS_QUERY_STANDARD, NULL, &responses, NULL) != ERROR_SUCCESS) { + throw DomainNameResolveException(); + } + + DNS_RECORD* currentEntry = responses; + while (currentEntry) { + if (currentEntry->wType == DNS_TYPE_SRV) { + int port = currentEntry->Data.SRV.wPort; + try { + // The pNameTarget is actually a PCWSTR, so I would have expected this + // conversion to not work at all, but it does. + // Actually, it doesn't. Fix this and remove explicit cast + // Remove unicode undef above as well + std::string hostname((const char*) currentEntry->Data.SRV.pNameTarget); + HostAddress address = resolveHostName(hostname); + DnsRecordListFree(responses, DnsFreeRecordList); + return HostAddressPort(address, port); + } + catch (const DomainNameResolveException&) { + } + } + currentEntry = currentEntry->pNext; + } + DnsRecordListFree(responses, DnsFreeRecordList); + +#else + + ByteArray response; + response.resize(NS_PACKETSZ); + int responseLength = res_query(const_cast<char*>(srvQuery.c_str()), ns_c_in, ns_t_srv, reinterpret_cast<u_char*>(response.getData()), response.getSize()); + if (responseLength == -1) { + throw DomainNameResolveException(); + } + + // Parse header + HEADER* header = reinterpret_cast<HEADER*>(response.getData()); + unsigned char* messageStart = reinterpret_cast<unsigned char*>(response.getData()); + unsigned char* messageEnd = messageStart + responseLength; + unsigned char* currentEntry = messageStart + NS_HFIXEDSZ; + + // Skip over the queries + int queriesCount = ntohs(header->qdcount); + while (queriesCount > 0) { + int entryLength = dn_skipname(currentEntry, messageEnd); + if (entryLength < 0) { + throw DomainNameResolveException(); + } + currentEntry += entryLength + NS_QFIXEDSZ; + queriesCount--; + } + + // Process the SRV answers + int answersCount = ntohs(header->ancount); + while (answersCount > 0) { + int entryLength = dn_skipname(currentEntry, messageEnd); + currentEntry += entryLength; + currentEntry += NS_RRFIXEDSZ; + + // Uninteresting information + currentEntry += 2; // PRIORITY + currentEntry += 2; // WEIGHT + + // Port + if (currentEntry >= messageEnd) { + throw DomainNameResolveException(); + } + int port = ns_get16(currentEntry); + currentEntry += 2; + + // Hostname + if (currentEntry >= messageEnd) { + throw DomainNameResolveException(); + } + ByteArray entry; + entry.resize(NS_MAXDNAME); + entryLength = dn_expand(messageStart, messageEnd, currentEntry, entry.getData(), entry.getSize()); + if (entryLength < 0) { + throw DomainNameResolveException(); + } + try { + // Resolve the hostname + std::string hostname(entry.getData(), entryLength); + HostAddress address = resolveHostName(hostname); + return HostAddressPort(address, port); + } + catch (const DomainNameResolveException&) { + } + currentEntry += entryLength; + answersCount--; + } +#endif + + throw DomainNameResolveException(); +} + +HostAddress DomainNameResolver::resolveHostName(const std::string& hostname) { + boost::asio::io_service ioService; + boost::asio::ip::tcp::resolver resolver(ioService); + boost::asio::ip::tcp::resolver::query query(hostname, "5222"); + try { + boost::asio::ip::tcp::resolver::iterator endpointIterator = resolver.resolve(query); + if (endpointIterator == boost::asio::ip::tcp::resolver::iterator()) { + throw DomainNameResolveException(); + } + boost::asio::ip::address address = (*endpointIterator).endpoint().address(); + if (address.is_v4()) { + return HostAddress(&address.to_v4().to_bytes()[0], 4); + } + else { + return HostAddress(&address.to_v6().to_bytes()[0], 16); + } + } + catch (...) { + throw DomainNameResolveException(); + } +} + +} diff --git a/Swiften/Network/DomainNameResolver.h b/Swiften/Network/DomainNameResolver.h new file mode 100644 index 0000000..c7736b1 --- /dev/null +++ b/Swiften/Network/DomainNameResolver.h @@ -0,0 +1,27 @@ +#ifndef SWIFTEN_DOMAINNAMERESOLVER_H +#define SWIFTEN_DOMAINNAMERESOLVER_H + +#include <string> + +#include "Swiften/Base/String.h" +#include "Swiften/Network/HostAddress.h" +#include "Swiften/Network/HostAddressPort.h" + +namespace Swift { + class String; + + class DomainNameResolver { + public: + DomainNameResolver(); + virtual ~DomainNameResolver(); + + HostAddressPort resolve(const String& domain); + + private: + virtual HostAddressPort resolveDomain(const std::string& domain); + HostAddressPort resolveXMPPService(const std::string& domain); + HostAddress resolveHostName(const std::string& hostName); + }; +} + +#endif diff --git a/Swiften/Network/HostAddress.cpp b/Swiften/Network/HostAddress.cpp new file mode 100644 index 0000000..84a0012 --- /dev/null +++ b/Swiften/Network/HostAddress.cpp @@ -0,0 +1,49 @@ +#include "Swiften/Network/HostAddress.h" + +#include <boost/numeric/conversion/cast.hpp> +#include <cassert> +#include <sstream> +#include <iomanip> + +namespace Swift { + +HostAddress::HostAddress() { + for (int i = 0; i < 4; ++i) { + address_.push_back(0); + } +} + +HostAddress::HostAddress(const unsigned char* address, int length) { + assert(length == 4 || length == 16); + address_.reserve(length); + for (int i = 0; i < length; ++i) { + address_.push_back(address[i]); + } +} + +std::string HostAddress::toString() const { + if (address_.size() == 4) { + std::ostringstream result; + for (size_t i = 0; i < address_.size() - 1; ++i) { + result << boost::numeric_cast<unsigned int>(address_[i]) << "."; + } + result << boost::numeric_cast<unsigned int>(address_[address_.size() - 1]); + return result.str(); + } + else if (address_.size() == 16) { + std::ostringstream result; + result << std::hex; + result.fill('0'); + for (size_t i = 0; i < (address_.size() / 2) - 1; ++i) { + result << std::setw(2) << boost::numeric_cast<unsigned int>(address_[2*i]) << std::setw(2) << boost::numeric_cast<unsigned int>(address_[(2*i)+1]) << ":"; + } + result << std::setw(2) << boost::numeric_cast<unsigned int>(address_[address_.size() - 2]) << std::setw(2) << boost::numeric_cast<unsigned int>(address_[address_.size() - 1]); + return result.str(); + } + else { + assert(false); + return ""; + } +} + +} diff --git a/Swiften/Network/HostAddress.h b/Swiften/Network/HostAddress.h new file mode 100644 index 0000000..2c9760d --- /dev/null +++ b/Swiften/Network/HostAddress.h @@ -0,0 +1,24 @@ +#ifndef SWIFTEN_HOSTADDRESS +#define SWIFTEN_HOSTADDRESS + +#include <string> +#include <vector> + +namespace Swift { + class HostAddress { + public: + HostAddress(); + HostAddress(const unsigned char* address, int length); + + const std::vector<unsigned char>& getRawAddress() const { + return address_; + } + + std::string toString() const; + + private: + std::vector<unsigned char> address_; + }; +} + +#endif diff --git a/Swiften/Network/HostAddressPort.h b/Swiften/Network/HostAddressPort.h new file mode 100644 index 0000000..8668ae4 --- /dev/null +++ b/Swiften/Network/HostAddressPort.h @@ -0,0 +1,26 @@ +#ifndef SWIFTEN_HostAddressPort_H +#define SWIFTEN_HostAddressPort_H + +#include "Swiften/Network/HostAddress.h" + +namespace Swift { + class HostAddressPort { + public: + HostAddressPort(const HostAddress& address, int port) : address_(address), port_(port) { + } + + const HostAddress& getAddress() const { + return address_; + } + + int getPort() const { + return port_; + } + + private: + HostAddress address_; + int port_; + }; +} + +#endif diff --git a/Swiften/Network/Makefile.inc b/Swiften/Network/Makefile.inc new file mode 100644 index 0000000..055554a --- /dev/null +++ b/Swiften/Network/Makefile.inc @@ -0,0 +1,13 @@ +SWIFTEN_SOURCES += \ + Swiften/Network/HostAddress.cpp \ + Swiften/Network/DomainNameResolver.cpp \ + Swiften/Network/ConnectionFactory.cpp \ + Swiften/Network/BoostConnection.cpp \ + Swiften/Network/BoostConnectionFactory.cpp \ + Swiften/Network/Timer.cpp + +include Swiften/Network/UnitTest/Makefile.inc + +ifneq ($(WIN32),1) +LIBS += -lresolv +endif diff --git a/Swiften/Network/Timer.cpp b/Swiften/Network/Timer.cpp new file mode 100644 index 0000000..8b2b57f --- /dev/null +++ b/Swiften/Network/Timer.cpp @@ -0,0 +1,40 @@ +#include "Swiften/Network/Timer.h" + +#include <boost/date_time/posix_time/posix_time.hpp> + +#include "Swiften/EventLoop/MainEventLoop.h" + +namespace Swift { + +Timer::Timer(int milliseconds) : + timeout_(milliseconds), ioService_(0), thread_(0), timer_(0) { + ioService_ = new boost::asio::io_service(); +} + +Timer::~Timer() { + MainEventLoop::removeEventsFromOwner(this); + ioService_->stop(); + thread_->join(); + delete timer_; + delete thread_; + delete ioService_; +} + +void Timer::start() { + thread_ = new boost::thread(boost::bind(&Timer::doStart, this)); +} + +void Timer::doStart() { + timer_ = new boost::asio::deadline_timer(*ioService_); + timer_->expires_from_now(boost::posix_time::milliseconds(timeout_)); + timer_->async_wait(boost::bind(&Timer::handleTimerTick, this)); + ioService_->run(); +} + +void Timer::handleTimerTick() { + MainEventLoop::postEvent(boost::bind(boost::ref(onTick)), this); + timer_->expires_from_now(boost::posix_time::milliseconds(timeout_)); + timer_->async_wait(boost::bind(&Timer::handleTimerTick, this)); +} + +} diff --git a/Swiften/Network/Timer.h b/Swiften/Network/Timer.h new file mode 100644 index 0000000..8e4b4c2 --- /dev/null +++ b/Swiften/Network/Timer.h @@ -0,0 +1,31 @@ +#ifndef SWIFTEN_Timer_H +#define SWIFTEN_Timer_H + +#include <boost/asio.hpp> +#include <boost/signals.hpp> +#include <boost/thread.hpp> + +namespace Swift { + class Timer { + public: + Timer(int milliseconds); + ~Timer(); + + void start(); + + public: + boost::signal<void ()> onTick; + + private: + void doStart(); + void handleTimerTick(); + + private: + int timeout_; + boost::asio::io_service* ioService_; + boost::thread* thread_; + boost::asio::deadline_timer* timer_; + }; +} + +#endif diff --git a/Swiften/Network/UnitTest/HostAddressTest.cpp b/Swiften/Network/UnitTest/HostAddressTest.cpp new file mode 100644 index 0000000..b805647 --- /dev/null +++ b/Swiften/Network/UnitTest/HostAddressTest.cpp @@ -0,0 +1,33 @@ +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> + +#include "Swiften/Network/HostAddress.h" + +using namespace Swift; + +class HostAddressTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(HostAddressTest); + CPPUNIT_TEST(testToString); + CPPUNIT_TEST(testToString_IPv6); + CPPUNIT_TEST_SUITE_END(); + + public: + HostAddressTest() {} + + void testToString() { + unsigned char address[4] = {10, 0, 1, 253}; + HostAddress testling(address, 4); + + CPPUNIT_ASSERT_EQUAL(std::string("10.0.1.253"), testling.toString()); + } + + void testToString_IPv6() { + unsigned char address[16] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 17}; + HostAddress testling(address, 16); + + CPPUNIT_ASSERT_EQUAL(std::string("0102:0304:0506:0708:090a:0b0c:0d0e:0f11"), testling.toString()); + } +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(HostAddressTest); diff --git a/Swiften/Network/UnitTest/Makefile.inc b/Swiften/Network/UnitTest/Makefile.inc new file mode 100644 index 0000000..4f157f6 --- /dev/null +++ b/Swiften/Network/UnitTest/Makefile.inc @@ -0,0 +1,2 @@ +UNITTEST_SOURCES += \ + Swiften/Network/UnitTest/HostAddressTest.cpp |