summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRemko Tronçon <git@el-tramo.be>2009-06-01 08:48:42 (GMT)
committerRemko Tronçon <git@el-tramo.be>2009-06-01 09:24:28 (GMT)
commit2812bddd81f8a1b804c7460f4e14cd0aa393d129 (patch)
treed46294f35150c4f0f43deaf2d31fceaf945ae715 /Swiften/Network
downloadswift-2812bddd81f8a1b804c7460f4e14cd0aa393d129.zip
swift-2812bddd81f8a1b804c7460f4e14cd0aa393d129.tar.bz2
Import.
Diffstat (limited to 'Swiften/Network')
-rw-r--r--Swiften/Network/BoostConnection.cpp100
-rw-r--r--Swiften/Network/BoostConnection.h43
-rw-r--r--Swiften/Network/BoostConnectionFactory.cpp12
-rw-r--r--Swiften/Network/BoostConnectionFactory.h18
-rw-r--r--Swiften/Network/Connection.h40
-rw-r--r--Swiften/Network/ConnectionFactory.cpp8
-rw-r--r--Swiften/Network/ConnectionFactory.h16
-rw-r--r--Swiften/Network/DomainNameResolveException.h11
-rw-r--r--Swiften/Network/DomainNameResolver.cpp176
-rw-r--r--Swiften/Network/DomainNameResolver.h27
-rw-r--r--Swiften/Network/HostAddress.cpp49
-rw-r--r--Swiften/Network/HostAddress.h24
-rw-r--r--Swiften/Network/HostAddressPort.h26
-rw-r--r--Swiften/Network/Makefile.inc13
-rw-r--r--Swiften/Network/Timer.cpp40
-rw-r--r--Swiften/Network/Timer.h31
-rw-r--r--Swiften/Network/UnitTest/HostAddressTest.cpp33
-rw-r--r--Swiften/Network/UnitTest/Makefile.inc2
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