changeset 551:a5e1c91abb8e

Irccd: split services, closes #730 While here: - delete interrupt_service, no longer needed, - delete net.hpp, net_util.hpp, no longer needed.
author David Demelier <markand@malikania.fr>
date Fri, 24 Nov 2017 20:09:45 +0100
parents 64cb98dd8e9d
children bf6a1fb7c45a
files irccd/main.cpp libcommon/CMakeLists.txt libcommon/irccd/net.hpp libcommon/irccd/net_util.hpp libirccd-js/irccd/js/js_plugin.cpp libirccd-js/irccd/js/plugin_jsapi.cpp libirccd-js/irccd/js/server_jsapi.cpp libirccd-test/irccd/plugin_test.cpp libirccd/CMakeLists.txt libirccd/irccd/command.cpp libirccd/irccd/command_service.cpp libirccd/irccd/command_service.hpp libirccd/irccd/config.cpp libirccd/irccd/irccd.cpp libirccd/irccd/irccd.hpp libirccd/irccd/plugin_service.cpp libirccd/irccd/plugin_service.hpp libirccd/irccd/rule_service.cpp libirccd/irccd/rule_service.hpp libirccd/irccd/server_service.cpp libirccd/irccd/server_service.hpp libirccd/irccd/service.cpp libirccd/irccd/service.hpp libirccd/irccd/transport_service.cpp tests/js-plugin/main.cpp tests/plugin-ask/main.cpp tests/plugin-auth/main.cpp tests/plugin-hangman/main.cpp tests/plugin-history/main.cpp tests/plugin-logger/main.cpp tests/plugin-plugin/main.cpp tests/rules/main.cpp
diffstat 32 files changed, 1470 insertions(+), 5405 deletions(-) [+]
line wrap: on
line diff
--- a/irccd/main.cpp	Fri Nov 24 20:05:15 2017 +0100
+++ b/irccd/main.cpp	Fri Nov 24 20:09:45 2017 +0100
@@ -33,15 +33,17 @@
 #include <csignal>
 #include <iostream>
 
-#include "command.hpp"
-#include "logger.hpp"
-#include "options.hpp"
-#include "service.hpp"
-#include "string_util.hpp"
-#include "system.hpp"
-#include "transport_service.hpp"
-#include "config.hpp"
-#include "irccd.hpp"
+#include <irccd/command_service.hpp>
+#include <irccd/config.hpp>
+#include <irccd/irccd.hpp>
+#include <irccd/logger.hpp>
+#include <irccd/options.hpp>
+#include <irccd/plugin_service.hpp>
+#include <irccd/rule_service.hpp>
+#include <irccd/server_service.hpp>
+#include <irccd/string_util.hpp>
+#include <irccd/system.hpp>
+#include <irccd/transport_service.hpp>
 
 #if defined(HAVE_JS)
 #   include <irccd/js/directory_jsapi.hpp>
@@ -62,6 +64,7 @@
 
 namespace {
 
+std::atomic<bool> running{true};
 std::unique_ptr<irccd> instance;
 
 void usage()
@@ -101,7 +104,7 @@
 
 void stop(int)
 {
-    instance->stop();
+    running = false;
 }
 
 void init(int& argc, char**& argv)
@@ -362,6 +365,8 @@
      * associated objects before any other static global values such as
      * loggers.
      */
-    instance->run();
+    while (running)
+        service.run();
+
     instance = nullptr;
 }
--- a/libcommon/CMakeLists.txt	Fri Nov 24 20:05:15 2017 +0100
+++ b/libcommon/CMakeLists.txt	Fri Nov 24 20:09:45 2017 +0100
@@ -26,10 +26,8 @@
     ${libcommon_SOURCE_DIR}/irccd/ini.hpp
     ${libcommon_SOURCE_DIR}/irccd/json_util.hpp
     ${libcommon_SOURCE_DIR}/irccd/logger.hpp
-    ${libcommon_SOURCE_DIR}/irccd/net.hpp
     ${libcommon_SOURCE_DIR}/irccd/network_errc.hpp
     ${libcommon_SOURCE_DIR}/irccd/network_stream.hpp
-    ${libcommon_SOURCE_DIR}/irccd/net_util.hpp
     ${libcommon_SOURCE_DIR}/irccd/options.hpp
     ${libcommon_SOURCE_DIR}/irccd/signals.hpp
     ${libcommon_SOURCE_DIR}/irccd/string_util.hpp
--- a/libcommon/irccd/net.hpp	Fri Nov 24 20:05:15 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,3734 +0,0 @@
-/*
- * net.hpp -- portable C++ socket wrapper
- *
- * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#ifndef IRCCD_NET_HPP
-#define IRCCD_NET_HPP
-
-/**
- * \file net.hpp
- * \brief Networking
- * \author David Demelier <markand@malikania.fr>
- */
-
-/**
- * \defgroup net-module-tcp Network TCP support
- * \brief TCP support in the networking module.
- */
-
-/**
- * \defgroup net-module-udp Network UDP support
- * \brief UDP support in networking module.
- */
-
-/**
- * \defgroup net-module-tls Network TLS support
- * \brief TLS support in networking module.
- */
-
-/**
- * \defgroup net-module-addresses Network predefined addresses
- * \brief Predefined addresses for sockets
- */
-
-/**
- * \defgroup net-module-options Network predefined options
- * \brief Predefined options for sockets
- */
-
-/**
- * \defgroup net-module-backends Network predefined backends
- * \brief Predefined backends for Listener
- */
-
-/**
- * \defgroup net-module-resolv Network resolver
- * \brief Resolv functions
- */
-
-/**
- * \page Networking Networking
- *
- *   - \subpage net-options
- *   - \subpage net-concepts
- */
-
-/**
- * \page net-options User options
- *
- * The user may set the following variables before compiling these files:
- *
- * # General options
- *
- * - **NET_NO_AUTO_INIT**: (bool) Set to 0 if you don't want Socket class to
- *   automatically calls init function and finish functions.
- *
- * - **NET_NO_SSL**: (bool) Set to 0 if you don't have access to OpenSSL
- *   library.
- *
- * - **NET_NO_AUTO_SSL_INIT**: (bool) Set to 0 if you don't want Socket class
- *   with Tls to automatically init the OpenSSL library. You will need to call
- *   ssl::init and ssl::finish.
- *
- * # General compatibility options.
- *
- * The following options are auto detected but you can override them if you
- * want.
- *
- * - **NET_HAVE_INET_PTON**: (bool) Set to 1 if you have inet_pton function.
- *   True for all platforms and Windows
- *   if _WIN32_WINNT is greater or equal to 0x0600.
- *
- * - **NET_HAVE_INET_NTOP**: (bool) Same as above.
- *
- * **Note:** On Windows, it is highly encouraged to set _WIN32_WINNT to at least
- * 0x0600 on MinGW.
- *
- * # Options for Listener class
- *
- * Feature detection, multiple implementations may be avaible, for example,
- * Linux has poll, select and epoll.
- *
- * We assume that `select(2)` is always available.
- *
- * Of course, you can set the variables yourself if you test it with your build
- * system.
- *
- * - **NET_HAVE_POLL**: Defined on all BSD, Linux. Also defined on Windows
- *   if _WIN32_WINNT is set to 0x0600 or greater.
- * - **NET_HAVE_KQUEUE**: Defined on all BSD and Apple.
- * - **NET_HAVE_EPOLL**: Defined on Linux only.
- * - **NET_DEFAULT_BACKEND**: Which backend to use (e.g. `Select`).
- *
- * The preference priority is ordered from left to right.
- *
- * | System        | Backend                 | Class name   |
- * |---------------|-------------------------|--------------|
- * | Linux         | epoll(7)                | Epoll        |
- * | *BSD          | kqueue(2)               | Kqueue       |
- * | Windows       | poll(2), select(2)      | Poll, Select |
- * | Mac OS X      | kqueue(2)               | Kqueue       |
- */
-
-/**
- * \page net-concepts Concepts
- *
- *   - \subpage net-concept-backend
- *   - \subpage net-concept-option
- *   - \subpage net-concept-stream
- *   - \subpage net-concept-datagram
- */
-
-/**
- * \page net-concept-backend Backend (Concept)
- *
- * A backend is an interface for the Listener class. It is primarily designed to
- * be the most suitable for the host environment.
- *
- * The backend must be default constructible, it is highly encouraged to be move
- * constructible.
- *
- * This concepts requires the following functions:
- *
- * # name
- *
- * Get the backend name, informational only.
- *
- * ## Synopsis
- *
- * ````
- * std::string name() const;
- * ````
- *
- * ## Returns
- *
- * The backend name.
- *
- * # set
- *
- * Set one or more condition for the given handle.
- *
- * ## Synopsis
- *
- * ````
- * void set(const ListenerTable &table, Handle handle, Condition condition,
- * bool add);
- * ````
- *
- * ## Arguments
- *
- *   - **table**: the current table of sockets,
- *   - **handle**: the handle to set,
- *   - **condition**: the condition to add (may be OR'ed),
- *   - **add**: hint set to true if the handle is not currently registered.
- *
- * # unset
- *
- * Unset one or more condition for the given handle.
- *
- * ## Synopsis
- *
- * ````
- * void unset(const ListenerTable &table, Handle handle, Condition condition,
- * bool remove);
- * ````
- *
- * ## Arguments
- *
- *   - **table**: the current table of sockets,
- *   - **handle**: the handle to update,
- *   - **condition**: the condition to remove (may be OR'ed),
- *   - **remove**: hint set to true if the handle will be completely removed.
- *
- * # wait
- *
- * Wait for multiple sockets to be ready.
- *
- * ## Synopsis
- *
- * ````
- * std::vector<ListenerStatus> wait(const ListenerTable &table, int ms);
- * ````
- *
- * ## Arguments
- *
- *   - **table**: the current table,
- *   - **ms**: the number to wait in milliseconds, negative means forever.
- *
- * ## Returns
- *
- * The list of sockets ready paired to their condition.
- */
-
-/**
- * \page net-concept-option Option (Concept)
- *
- * An option can be set or get from a socket.
- *
- * If an operation is not available, provides the function but throws an
- * exception with ENOSYS message.
- *
- * This concepts requires the following functions:
- *
- * # Option (constructor)
- *
- * At least one default constructor must be present.
- *
- * ## Synopsis
- *
- * ````
- * Option() noexcept;
- * ````
- *
- * # set
- *
- * Set the option.
- *
- * ## Synopsis
- *
- * ````
- * template <typename Address>
- * void set(Socket &sc) const;
- * ````
- *
- * ## Arguments
- *
- *   - **sc**: the socket.
- *
- * # get
- *
- * Get an option, T can be any type.
- *
- * ## Synopsis
- *
- * ````
- * template <typename Address>
- * T get(Socket &sc) const;
- * ````
- *
- * ## Arguments
- *
- *   - **sc**: the socket.
- *
- * ## Returns
- *
- * The value.
- */
-
-/**
- * \page net-concept-stream Stream (Concept)
- *
- * This concepts requires the following functions:
- *
- * # type
- *
- * Return the type of socket, usually `SOCK_STREAM`.
- *
- * ## Synopsis
- *
- * ````
- * int type() const noexcept;
- * ````
- *
- * ## Returns
- *
- * The type of socket.
- *
- * # connect
- *
- * Connect to the given address.
- *
- * ## Synopsis
- *
- * ````
- * void connect(const sockaddr *address, socklen_t length); // (0)
- * void connect(const Address &address); // 1 (Optional)
- * ````
- *
- * ## Arguments
- *
- *   - **address**: the address,
- *   - **length**: the address length.
- *
- * ## Throws
- *
- *   - net::WouldBlockError: if the operation would block,
- *   - net::Error: on other error.
- *
- * # accept
- *
- * Accept a new client.
- *
- * If no pending connection is available and operation would block, the
- * implementation must throw WouldBlockError. Any other error can be thrown
- * otherwise a valid socket must be returned.
- *
- * ## Synopsis
- *
- * ````
- * Socket accept();
- * ````
- *
- * ## Returns
- *
- * The new socket.
- *
- * ## Throws
- *
- *   - net::WouldBlockError: if the operation would block,
- *   - net::Error: on other error.
- *
- * # recv
- *
- * Receive data.
- *
- * ## Synopsis
- *
- * ````
- * unsigned recv(void *data, unsigned length);
- * ````
- *
- * ## Arguments
- *
- *   - **data**: the destination buffer,
- *   - **length**: the destination buffer length.
- *
- * ## Returns
- *
- * The number of bytes sent.
- *
- * ## Throws
- *
- *   - net::WouldBlockError: if the operation would block,
- *   - net::Error: on other error.
- *
- * # send
- *
- * Send data.
- *
- * ## Synopsis
- *
- * ````
- * unsigned send(const void *data, unsigned length);
- * ````
- *
- * ## Arguments
- *
- *   - **data**: the data to send,
- *   - **length**: the data length.
- *
- * ## Returns
- *
- * The number of bytes sent.
- *
- * ## Throws
- *
- *   - net::WouldBlockError: if the operation would block,
- *   - net::Error: on other error.
- */
-
-/**
- * \page net-concept-datagram Datagram (Concept)
- *
- * This concepts requires the following functions:
- *
- * # type
- *
- * Return the type of socket, usually `SOCK_DGRAM`.
- *
- * ## Synopsis
- *
- * ````
- * int type() const noexcept;
- * ````
- *
- * ## Returns
- *
- * The type of socket.
- *
- * # recvfrom
- *
- * Receive data.
- *
- * ## Synopsis
- *
- * ````
- * unsigned recvfrom(void *data, unsigned length, sockaddr *address,
- *     socklen_t *addrlen);
- * unsigned recvfrom(void *data, unsigned length, Address *source)
- * ````
- *
- * ## Arguments
- *
- *   - **data**: the data,
- *   - **length**: the length,
- *   - **address**: the source address,
- *   - **addrlen**: the source address in/out length.
- *
- * ## Returns
- *
- * The number of bytes received.
- *
- * ## Throws
- *
- *   - net::WouldBlockError: if the operation would block,
- *   - net::Error: on other error.
- *
- * # sendto
- *
- * ````
- * unsigned sendto(const void *data, unsigned length, const sockaddr *address,
- *     socklen_t addrlen);
- * unsigned sendto(const void *data, unsigned length, const Address &address);
- * ````
- *
- * ## Arguments
- *
- *   - **data**: the data to send,
- *   - **length**: the data length,
- *   - **address**: the destination address,
- *   - **addrlen**: the destination address length.
- *
- * ## Returns
- *
- * The number of bytes sent.
- *
- * ## Throws
- *
- *   - net::WouldBlockError: if the operation would block,
- *   - net::Error: on other error.
- */
-
-/*
- * Headers to include.
- * ------------------------------------------------------------------
- */
-
-/*
- * Include Windows headers before because it brings _WIN32_WINNT if not
- * specified by the user.
- */
-#if defined(_WIN32)
-#   include <winsock2.h>
-#   include <ws2tcpip.h>
-#else
-#   include <sys/ioctl.h>
-#   include <sys/types.h>
-#   include <sys/socket.h>
-#   include <sys/un.h>
-
-#   include <arpa/inet.h>
-
-#   include <netinet/in.h>
-#   include <netinet/tcp.h>
-
-#   include <fcntl.h>
-#   include <netdb.h>
-#   include <unistd.h>
-#endif
-
-#include <algorithm>
-#include <atomic>
-#include <cassert>
-#include <cerrno>
-#include <chrono>
-#include <climits>
-#include <cstddef>
-#include <cstdlib>
-#include <cstring>
-#include <iterator>
-#include <memory>
-#include <mutex>
-#include <string>
-#include <unordered_map>
-#include <vector>
-
-#if !defined(NET_NO_SSL)
-
-#include <openssl/err.h>
-#include <openssl/evp.h>
-#include <openssl/ssl.h>
-#include <openssl/opensslv.h>
-
-#endif // !NET_NO_SSL
-
-/*
- * Determine which I/O multiplexing implementations are available.
- * ------------------------------------------------------------------
- *
- * May define the following:
- *
- *   - NET_HAVE_EPOLL
- *   - NET_HAVE_KQUEUE
- *   - NET_HAVE_POLL
- */
-
-#if defined(_WIN32)
-#   if _WIN32_WINNT >= 0x0600 && !defined(NET_HAVE_POLL)
-#       define NET_HAVE_POLL
-#   endif
-#elif defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__)
-#   if !defined(NET_HAVE_KQUEUE)
-#       define NET_HAVE_KQUEUE
-#   endif
-#   if !defined(NET_HAVE_POLL)
-#       define NET_HAVE_POLL
-#   endif
-#elif defined(__linux__)
-#   if !defined(NET_HAVE_EPOLL)
-#       define NET_HAVE_EPOLL
-#   endif
-#   if !defined(NET_HAVE_POLL)
-#       define NET_HAVE_POLL
-#   endif
-#endif
-
-/*
- * Compatibility macros.
- * ------------------------------------------------------------------
- */
-
-/**
- * \brief Tells if inet_pton is available
- */
-#if !defined(NET_HAVE_INET_PTON)
-#   if defined(_WIN32)
-#       if _WIN32_WINNT >= 0x0600
-#           define NET_HAVE_INET_PTON
-#       endif
-#   else
-#       define NET_HAVE_INET_PTON
-#   endif
-#endif
-
-/**
- * \brief Tells if inet_ntop is available
- */
-#if !defined(NET_HAVE_INET_NTOP)
-#   if defined(_WIN32)
-#       if _WIN32_WINNT >= 0x0600
-#           define NET_HAVE_INET_NTOP
-#       endif
-#   else
-#       define NET_HAVE_INET_NTOP
-#   endif
-#endif
-
-/*
- * Define NET_DEFAULT_BACKEND.
- * ------------------------------------------------------------------
- *
- * Define the default I/O multiplexing implementation to use if not specified.
- */
-
-/**
- * \brief Defines the default backend
- */
-#if defined(_WIN32)
-#   if !defined(NET_DEFAULT_BACKEND)
-#       if defined(NET_HAVE_POLL)
-#           define NET_DEFAULT_BACKEND Poll
-#       else
-#           define NET_DEFAULT_BACKEND Select
-#       endif
-#   endif
-#elif defined(__linux__)
-#   include <sys/epoll.h>
-
-#   if !defined(NET_DEFAULT_BACKEND)
-#       define NET_DEFAULT_BACKEND Epoll
-#   endif
-#elif defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__DragonFly__) || defined(__APPLE__)
-#   include <sys/types.h>
-#   include <sys/event.h>
-#   include <sys/time.h>
-
-#   if !defined(NET_DEFAULT_BACKEND)
-#       define NET_DEFAULT_BACKEND Kqueue
-#   endif
-#else
-#   if !defined(NET_DEFAULT_BACKEND)
-#       define NET_DEFAULT_BACKEND Select
-#   endif
-#endif
-
-#if defined(NET_HAVE_POLL) && !defined(_WIN32)
-#    include <poll.h>
-#endif
-
-#if !defined(NET_NO_SSL)
-#   if OPENSSL_VERSION_NUMBER >= 0x10100000L
-#       define NET_SSL_DEFAULT_METHOD TLS_method
-#   else
-#       define NET_SSL_DEFAULT_METHOD TLSv1_method
-#   endif
-#endif
-
-namespace irccd {
-
-/**
- * The network namespace.
- */
-namespace net {
-
-/*
- * Portables types.
- * ------------------------------------------------------------------
- *
- * The following types are defined differently between Unix and Windows.
- */
-
-#if defined(_WIN32)
-
-/**
- * Socket type, SOCKET.
- */
-using Handle = SOCKET;
-
-/**
- * Argument to pass to set.
- */
-using ConstArg = const char *;
-
-/**
- * Argument to pass to get.
- */
-using Arg = char *;
-
-#else
-
-/**
- * Socket type, int.
- */
-using Handle = int;
-
-/**
- * Argument to pass to set.
- */
-using ConstArg = const void *;
-
-/**
- * Argument to pass to get.
- */
-using Arg = void *;
-
-#endif
-
-/*
- * Portable constants.
- * ------------------------------------------------------------------
- *
- * These constants are needed to check functions return codes, they are rarely
- * needed in end user code.
- */
-
-#if defined(_WIN32)
-
-/**
- * Socket creation failure or invalidation.
- */
-const Handle Invalid{INVALID_SOCKET};
-
-/**
- * Socket operation failure.
- */
-const int Failure{SOCKET_ERROR};
-
-#else
-
-/**
- * Socket creation failure or invalidation.
- */
-const Handle Invalid{-1};
-
-/**
- * Socket operation failure.
- */
-const int Failure{-1};
-
-#endif
-
-/**
- * Close the socket library.
- */
-inline void finish() noexcept
-{
-#if defined(_WIN32)
-    WSACleanup();
-#endif
-}
-
-/**
- * Initialize the socket library. Except if you defined NET_NO_AUTO_INIT, you
- * don't need to call this
- * function manually.
- */
-inline void init() noexcept
-{
-#if defined(_WIN32)
-    static std::atomic<bool> initialized;
-    static std::mutex mutex;
-
-    std::lock_guard<std::mutex> lock(mutex);
-
-    if (!initialized) {
-        initialized = true;
-
-        WSADATA wsa;
-        WSAStartup(MAKEWORD(2, 2), &wsa);
-
-        /*
-         * If NET_NO_AUTO_INIT is not set then the user must also call finish
-         * himself.
-         */
-#if !defined(NET_NO_AUTO_INIT)
-        atexit(finish);
-#endif
-    }
-#endif
-}
-
-/**
- * Get the last system error.
- *
- * \param errn the error number (errno or WSAGetLastError)
- * \return the error
- */
-inline std::string error(int errn)
-{
-#if defined(_WIN32)
-    LPSTR str = nullptr;
-    std::string errmsg = "Unknown error";
-
-    FormatMessageA(
-        FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
-        NULL,
-        errn,
-        MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
-        (LPSTR)&str, 0, NULL);
-
-
-    if (str) {
-        errmsg = std::string(str);
-        LocalFree(str);
-    }
-
-    return errmsg;
-#else
-    return strerror(errn);
-#endif
-}
-
-/**
- * Get the last socket system error. The error is set from errno or from
- * WSAGetLastError on Windows.
- *
- * \return a string message
- */
-inline std::string error()
-{
-#if defined(_WIN32)
-    return error(WSAGetLastError());
-#else
-    return error(errno);
-#endif
-}
-
-#if !defined(NET_NO_SSL)
-
-/**
- * \brief SSL namespace
- */
-namespace ssl {
-
-/**
- * \enum Method
- * \brief Which OpenSSL method to use.
- */
-enum Method {
-    Tlsv1,      //!< TLS v1.2 (recommended)
-    Sslv3       //!< SSLv3
-};
-
-/**
- * Initialize the OpenSSL library. Except if you defined NET_NO_AUTO_SSL_INIT,
- * you don't need to call this function manually.
- */
-inline void init() noexcept
-{
-    static std::atomic<bool> initialized;
-    static std::mutex mutex;
-
-    std::lock_guard<std::mutex> lock(mutex);
-
-    if (!initialized) {
-        initialized = true;
-
-        SSL_library_init();
-        SSL_load_error_strings();
-        OpenSSL_add_all_algorithms();
-
-#if !defined(NET_NO_AUTO_SSL_INIT)
-        atexit(finish);
-#endif
-    }
-}
-
-/**
- * Close the OpenSSL library.
- */
-inline void finish() noexcept
-{
-    ERR_free_strings();
-}
-
-} // !ssl
-
-#endif // !NET_NO_SSL
-
-/*
- * Error class.
- * ------------------------------------------------------------------
- *
- * This is the main exception thrown on socket operations.
- */
-
-/**
- * \brief Base class for sockets error.
- */
-class Error : public std::exception {
-private:
-    std::string m_message;
-
-public:
-    /**
-     * Construct the error using the specified error from the system.
-     *
-     * \param code the error code
-     * \warning the code must be a Winsock error or errno on Unix
-     */
-    inline Error(int code) noexcept
-        : m_message(error(code))
-    {
-    }
-
-    /**
-     * Construct the error using the custom message.
-     *
-     * \param message the message
-     */
-    inline Error(std::string message) noexcept
-        : m_message(std::move(message))
-    {
-    }
-
-    /**
-     * Construct the error using the last message from the system.
-     */
-    inline Error() noexcept
-#if defined(_WIN32)
-        : Error(WSAGetLastError())
-#else
-        : Error(errno)
-#endif
-    {
-    }
-
-    /**
-     * Get the error (only the error content).
-     *
-     * \return the error
-     */
-    const char *what() const noexcept override
-    {
-        return m_message.c_str();
-    }
-};
-
-/**
- * \brief Timeout occured.
- */
-class TimeoutError : public std::exception {
-public:
-    /**
-     * Get the error message.
-     *
-     * \return the message
-     */
-    const char *what() const noexcept override
-    {
-        return std::strerror(ETIMEDOUT);
-    }
-};
-
-/**
- * \brief Operation would block.
- */
-class WouldBlockError : public std::exception {
-public:
-    /**
-     * Get the error message.
-     *
-     * \return the message
-     */
-    const char *what() const noexcept override
-    {
-        return std::strerror(EWOULDBLOCK);
-    }
-};
-
-/**
- * \brief Operation requires sending data to complete.
- */
-class WantWriteError : public std::exception {
-public:
-    /**
-     * Get the error message.
-     *
-     * \return the message
-     */
-    const char *what() const noexcept override
-    {
-        return "operation requires writing to complete";
-    }
-};
-
-/**
- * \brief Operation requires reading data to complete.
- */
-class WantReadError : public std::exception {
-public:
-    /**
-     * Get the error message.
-     *
-     * \return the message
-     */
-    const char *what() const noexcept override
-    {
-        return "operation requires read to complete";
-    }
-};
-
-/*
- * Condition enum
- * ------------------------------------------------------------------
- *
- * Defines if we must wait for reading or writing.
- */
-
-/**
- * \enum Condition
- * \brief Define the required condition for the socket.
- */
-enum class Condition {
-    None,                       //!< No condition is required
-    Readable = (1 << 0),        //!< The socket must be readable
-    Writable = (1 << 1)         //!< The socket must be writable
-};
-
-/**
- * Apply bitwise XOR.
- *
- * \param v1 the first value
- * \param v2 the second value
- * \return the new value
- */
-inline Condition operator^(Condition v1, Condition v2) noexcept
-{
-    return static_cast<Condition>(static_cast<int>(v1) ^ static_cast<int>(v2));
-}
-
-/**
- * Apply bitwise AND.
- *
- * \param v1 the first value
- * \param v2 the second value
- * \return the new value
- */
-inline Condition operator&(Condition v1, Condition v2) noexcept
-{
-    return static_cast<Condition>(static_cast<int>(v1) & static_cast<int>(v2));
-}
-
-/**
- * Apply bitwise OR.
- *
- * \param v1 the first value
- * \param v2 the second value
- * \return the new value
- */
-inline Condition operator|(Condition v1, Condition v2) noexcept
-{
-    return static_cast<Condition>(static_cast<int>(v1) | static_cast<int>(v2));
-}
-
-/**
- * Apply bitwise NOT.
- *
- * \param v the value
- * \return the complement
- */
-inline Condition operator~(Condition v) noexcept
-{
-    return static_cast<Condition>(~static_cast<int>(v));
-}
-
-/**
- * Assign bitwise OR.
- *
- * \param v1 the first value
- * \param v2 the second value
- * \return the new value
- */
-inline Condition &operator|=(Condition &v1, Condition v2) noexcept
-{
-    v1 = static_cast<Condition>(static_cast<int>(v1) | static_cast<int>(v2));
-
-    return v1;
-}
-
-/**
- * Assign bitwise AND.
- *
- * \param v1 the first value
- * \param v2 the second value
- * \return the new value
- */
-inline Condition &operator&=(Condition &v1, Condition v2) noexcept
-{
-    v1 = static_cast<Condition>(static_cast<int>(v1) & static_cast<int>(v2));
-
-    return v1;
-}
-
-/**
- * Assign bitwise XOR.
- *
- * \param v1 the first value
- * \param v2 the second value
- * \return the new value
- */
-inline Condition &operator^=(Condition &v1, Condition v2) noexcept
-{
-    v1 = static_cast<Condition>(static_cast<int>(v1) ^ static_cast<int>(v2));
-
-    return v1;
-}
-
-/**
- * \brief Generic socket address storage.
- * \ingroup net-module-addresses
- */
-class Address {
-private:
-    sockaddr_storage m_storage;
-    socklen_t m_length;
-
-public:
-    /**
-     * Construct empty address.
-     */
-    inline Address() noexcept
-        : m_storage{}
-        , m_length(0)
-    {
-    }
-
-    /**
-     * Construct address from existing one.
-     *
-     * \pre address != nullptr
-     * \param address the address
-     * \param length the address length
-     */
-    inline Address(const sockaddr *address, socklen_t length) noexcept
-        : m_length(length)
-    {
-        assert(address);
-
-        std::memcpy(&m_storage, address, length);
-    }
-
-    /**
-     * Get the underlying address.
-     *
-     * \return the address
-     */
-    inline const sockaddr *get() const noexcept
-    {
-        return reinterpret_cast<const sockaddr *>(&m_storage);
-    }
-
-    /**
-     * Overloaded function
-     *
-     * \return the address
-     */
-    inline sockaddr *get() noexcept
-    {
-        return reinterpret_cast<sockaddr *>(&m_storage);
-    }
-
-    /**
-     * Get the underlying address as the given type (e.g sockaddr_in).
-     *
-     * \return the address reference
-     */
-    template <typename T>
-    inline const T &as() const noexcept
-    {
-        return reinterpret_cast<const T &>(m_storage);
-    }
-
-    /**
-     * Overloaded function
-     *
-     * \return the address reference
-     */
-    template <typename T>
-    inline T &as() noexcept
-    {
-        return reinterpret_cast<T &>(m_storage);
-    }
-
-    /**
-     * Get the underlying address length.
-     *
-     * \return the length
-     */
-    inline socklen_t length() const noexcept
-    {
-        return m_length;
-    }
-
-    /**
-     * Get the address domain.
-     *
-     * \return the domain
-     */
-    inline int domain() const noexcept
-    {
-        return m_storage.ss_family;
-    }
-};
-
-/**
- * \brief Address iterator.
- * \ingroup net-module-addresses
- * \see resolve
- *
- * This iterator can be used to try to connect to an host.
- *
- * When you use resolve with unspecified domain or socket type, the function may
- * retrieve several different addresses that you can iterate over to try to
- * connect to.
- *
- * Example:
- *
- * ````cpp
- * SocketTcp sc;
- * AddressIterator end, it = resolve("hostname.test", "80");
- *
- * while (!connected_condition && it != end)
- *   sc.connect(it->address(), it->length());
- * ````
- *
- * When an iterator equals to a default constructed iterator, it is considered
- * not dereferenceable.
- */
-class AddressIterator : public std::iterator<std::forward_iterator_tag, Address> {
-private:
-    std::vector<Address> m_addresses;
-    std::size_t m_index{0};
-
-public:
-    /**
-     * Construct a null iterator.
-     *
-     * The default constructed iterator is not dereferenceable.
-     */
-    inline AddressIterator() noexcept = default;
-
-    /**
-     * Construct an address iterator with a set of addresses.
-     *
-     * \pre index < m_addresses.size()
-     * \param addresses the addresses
-     * \param index the first index
-     */
-    inline AddressIterator(std::vector<Address> addresses, std::size_t index = 0) noexcept
-        : m_addresses(std::move(addresses))
-        , m_index(index)
-    {
-        assert(index < m_addresses.size());
-    }
-
-    /**
-     * Get the generic address.
-     *
-     * \pre this is dereferenceable
-     * \return the generic address
-     */
-    inline const Address &operator*() const noexcept
-    {
-        assert(m_index <= m_addresses.size());
-
-        return m_addresses[m_index];
-    }
-
-    /**
-     * Overloaded function.
-     *
-     * \pre this is dereferenceable
-     * \return the generic address
-     */
-    inline Address &operator*() noexcept
-    {
-        assert(m_index <= m_addresses.size());
-
-        return m_addresses[m_index];
-    }
-
-    /**
-     * Get the generic address.
-     *
-     * \pre this is dereferenceable
-     * \return the generic address
-     */
-    inline const Address *operator->() const noexcept
-    {
-        assert(m_index <= m_addresses.size());
-
-        return &m_addresses[m_index];
-    }
-
-    /**
-     * Overloaded function.
-     *
-     * \pre this is dereferenceable
-     * \return the generic address
-     */
-    inline Address *operator->() noexcept
-    {
-        assert(m_index <= m_addresses.size());
-
-        return &m_addresses[m_index];
-    }
-
-    /**
-     * Pre-increment the iterator.
-     *
-     * \return this
-     */
-    inline AddressIterator &operator++(int) noexcept
-    {
-        if (m_index + 1 >= m_addresses.size()) {
-            m_addresses.clear();
-            m_index = 0;
-        } else
-            m_index ++;
-
-        return *this;
-    }
-
-    /**
-     * Post-increment the iterator.
-     *
-     * \return copy of this
-     */
-    inline AddressIterator operator++() noexcept
-    {
-        AddressIterator save = *this;
-
-        if (m_index + 1 >= m_addresses.size()) {
-            m_addresses.clear();
-            m_index = 0;
-        } else
-            m_index ++;
-
-        return save;
-    }
-
-    friend bool operator==(const AddressIterator &, const AddressIterator &) noexcept;
-    friend bool operator!=(const AddressIterator &, const AddressIterator &) noexcept;
-};
-
-/**
- * Compare two address iterators.
- *
- * \param i1 the first iterator
- * \param i2 the second iterator
- * \return true if they equal
- */
-inline bool operator==(const AddressIterator &i1, const AddressIterator &i2) noexcept
-{
-    return i1.m_addresses == i2.m_addresses && i1.m_index == i2.m_index;
-}
-
-/**
- * Compare two address iterators.
- *
- * \param i1 the first iterator
- * \param i2 the second iterator
- * \return false if they equal
- */
-inline bool operator!=(const AddressIterator &i1, const AddressIterator &i2) noexcept
-{
-    return !(i1 == i2);
-}
-
-/**
- * Compare two generic addresses.
- *
- * \param a1 the first address
- * \param a2 the second address
- * \return true if they equal
- */
-inline bool operator==(const Address &a1, const Address &a2) noexcept
-{
-    return a1.length() == a2.length() && std::memcmp(a1.get(), a2.get(), a1.length()) == 0;
-}
-
-/**
- * Compare two generic addresses.
- *
- * \param a1 the first address
- * \param a2 the second address
- * \return false if they equal
- */
-inline bool operator!=(const Address &a1, const Address &a2) noexcept
-{
-    return !(a1 == a2);
-}
-
-/**
- * \brief Base socket class.
- */
-class Socket {
-protected:
-    /**
-     * The native handle.
-     */
-    Handle m_handle{Invalid};
-
-public:
-    /**
-     * Create a socket handle.
-     *
-     * This is the primary function and the only one that creates the socket
-     * handle, all other constructors are just overloaded functions.
-     *
-     * \param domain the domain AF_*
-     * \param type the type SOCK_*
-     * \param protocol the protocol
-     * \throw Error on errors
-     */
-    Socket(int domain, int type, int protocol)
-    {
-#if !defined(NET_NO_AUTO_INIT)
-        init();
-#endif
-        m_handle = ::socket(domain, type, protocol);
-
-        if (m_handle == Invalid)
-            throw Error();
-    }
-
-    /**
-     * Create the socket with an already defined handle and its protocol.
-     *
-     * \param handle the handle
-     */
-    explicit inline Socket(Handle handle) noexcept
-        : m_handle(handle)
-    {
-    }
-
-    /**
-     * Create an invalid socket. Can be used when you cannot instanciate the
-     * socket immediately.
-     */
-    explicit inline Socket(std::nullptr_t) noexcept
-        : m_handle(Invalid)
-    {
-    }
-
-    /**
-     * Copy constructor deleted.
-     */
-    Socket(const Socket &) = delete;
-
-    /**
-     * Transfer ownership from other to this.
-     *
-     * \param other the other socket
-     */
-    inline Socket(Socket &&other) noexcept
-        : m_handle(other.m_handle)
-    {
-        other.m_handle = Invalid;
-    }
-
-    /**
-     * Default destructor.
-     */
-    virtual ~Socket()
-    {
-        close();
-    }
-
-    /**
-     * Tells if the socket is not invalid.
-     *
-     * \return true if not invalid
-     */
-    inline bool isOpen() const noexcept
-    {
-        return m_handle != Invalid;
-    }
-
-    /**
-     * Set an option for the socket. Wrapper of setsockopt(2).
-     *
-     * \pre isOpen()
-     * \param level the setting level
-     * \param name the name
-     * \param arg the value
-     * \throw Error on errors
-     */
-    template <typename Argument>
-    inline void set(int level, int name, const Argument &arg)
-    {
-        assert(m_handle != Invalid);
-
-        if (::setsockopt(m_handle, level, name, (ConstArg)&arg, sizeof (arg)) == Failure)
-            throw Error();
-    }
-
-    /**
-     * Object-oriented option setter.
-     *
-     * The object must have `set(Socket &) const`.
-     *
-     * \pre isOpen()
-     * \param option the option
-     * \throw Error on errors
-     */
-    template <typename Option>
-    inline void set(const Option &option)
-    {
-        assert(m_handle != Invalid);
-
-        option.set(*this);
-    }
-
-    /**
-     * Get an option for the socket. Wrapper of getsockopt(2).
-     *
-     * \pre isOpen()
-     * \param level the setting level
-     * \param name the name
-     * \return the value
-     * \throw Error on errors
-     */
-    template <typename Argument>
-    Argument get(int level, int name)
-    {
-        assert(m_handle != Invalid);
-
-        Argument desired, result{};
-        socklen_t size = sizeof (result);
-
-        if (::getsockopt(m_handle, level, name, (Arg)&desired, &size) == Failure)
-            throw Error();
-
-        std::memcpy(&result, &desired, size);
-
-        return result;
-    }
-
-    /**
-     * Object-oriented option getter.
-     *
-     * The object must have `T get(Socket &) const`, T can be any type and it is
-     * the value returned from this function.
-     *
-     * \pre isOpen()
-     * \return the same value as get() in the option
-     * \throw Error on errors
-     */
-    template <typename Option>
-    inline auto get() -> decltype(std::declval<Option>().get(*this))
-    {
-        assert(m_handle != Invalid);
-
-        return Option().get(*this);
-    }
-
-    /**
-     * Get the native handle.
-     *
-     * \return the handle
-     * \warning Not portable
-     */
-    inline Handle handle() const noexcept
-    {
-        return m_handle;
-    }
-
-    /**
-     * Bind using a native address.
-     *
-     * \pre isOpen()
-     * \param address the address
-     * \param length the size
-     * \throw Error on errors
-     */
-    inline void bind(const sockaddr *address, socklen_t length)
-    {
-        assert(m_handle != Invalid);
-
-        if (::bind(m_handle, address, length) == Failure)
-            throw Error();
-    }
-
-    /**
-     * Overload that takes an address.
-     *
-     * \pre isOpen()
-     * \param address the address
-     * \throw Error on errors
-     */
-    inline void bind(const Address &address)
-    {
-        assert(m_handle != Invalid);
-
-        bind(address.get(), address.length());
-    }
-
-    /**
-     * Listen for pending connection.
-     *
-     * \pre isOpen()
-     * \param max the maximum number
-     * \throw Error on errors
-     */
-    inline void listen(int max = 128)
-    {
-        assert(m_handle != Invalid);
-
-        if (::listen(m_handle, max) == Failure)
-            throw Error();
-    }
-
-    /**
-     * Get the local name. This is a wrapper of getsockname().
-     *
-     * \pre isOpen()
-     * \return the address
-     * \throw Error on failures
-     */
-    Address getsockname() const
-    {
-        assert(m_handle != Invalid);
-
-        sockaddr_storage ss;
-        socklen_t length = sizeof (sockaddr_storage);
-
-        if (::getsockname(m_handle, reinterpret_cast<sockaddr *>(&ss), &length) == Failure)
-            throw Error();
-
-        return Address(reinterpret_cast<sockaddr *>(&ss), length);
-    }
-
-    /**
-     * Get connected address. This is a wrapper for getpeername().
-     *
-     * \pre isOpen()
-     * \return the address
-     * \throw Error on failures
-     */
-    Address getpeername() const
-    {
-        assert(m_handle != Invalid);
-
-        sockaddr_storage ss;
-        socklen_t length = sizeof (sockaddr_storage);
-
-        if (::getpeername(m_handle, reinterpret_cast<sockaddr *>(&ss), &length) == Failure)
-            throw Error();
-
-        return Address(reinterpret_cast<sockaddr *>(&ss), length);
-    }
-
-    /**
-     * Close the socket.
-     *
-     * Automatically called from the destructor.
-     */
-    void close()
-    {
-        if (m_handle != Invalid) {
-#if defined(_WIN32)
-            ::closesocket(m_handle);
-#else
-            ::close(m_handle);
-#endif
-            m_handle = Invalid;
-        }
-    }
-
-    /**
-     * Assignment operator forbidden.
-     *
-     * \return *this
-     */
-    Socket &operator=(const Socket &) = delete;
-
-    /**
-     * Transfer ownership from other to this. The other socket is left
-     * invalid and will not be closed.
-     *
-     * \param other the other socket
-     * \return this
-     */
-    Socket &operator=(Socket &&other) noexcept
-    {
-        m_handle = other.m_handle;
-        other.m_handle = Invalid;
-
-        return *this;
-    }
-};
-
-/**
- * Compare two sockets.
- *
- * \param s1 the first socket
- * \param s2 the second socket
- * \return true if they equals
- */
-inline bool operator==(const Socket &s1, const Socket &s2)
-{
-    return s1.handle() == s2.handle();
-}
-
-/**
- * Compare two sockets.
- *
- * \param s1 the first socket
- * \param s2 the second socket
- * \return true if they are different
- */
-inline bool operator!=(const Socket &s1, const Socket &s2)
-{
-    return s1.handle() != s2.handle();
-}
-
-/**
- * Compare two sockets.
- *
- * \param s1 the first socket
- * \param s2 the second socket
- * \return true if s1 < s2
- */
-inline bool operator<(const Socket &s1, const Socket &s2)
-{
-    return s1.handle() < s2.handle();
-}
-
-/**
- * Compare two sockets.
- *
- * \param s1 the first socket
- * \param s2 the second socket
- * \return true if s1 > s2
- */
-inline bool operator>(const Socket &s1, const Socket &s2)
-{
-    return s1.handle() > s2.handle();
-}
-
-/**
- * Compare two sockets.
- *
- * \param s1 the first socket
- * \param s2 the second socket
- * \return true if s1 <= s2
- */
-inline bool operator<=(const Socket &s1, const Socket &s2)
-{
-    return s1.handle() <= s2.handle();
-}
-
-/**
- * Compare two sockets.
- *
- * \param s1 the first socket
- * \param s2 the second socket
- * \return true if s1 >= s2
- */
-inline bool operator>=(const Socket &s1, const Socket &s2)
-{
-    return s1.handle() >= s2.handle();
-}
-
-/**
- * \brief Clear TCP implementation.
- * \ingroup net-module-tcp
- *
- * This is the basic TCP protocol that implements recv, send, connect and accept
- * as wrappers of the usual C functions.
- */
-class TcpSocket : public Socket {
-public:
-    /**
-     * Inherited constructors.
-     */
-    using Socket::Socket;
-
-    /**
-     * Construct a TCP socket.
-     *
-     * \param domain the domain
-     * \param protocol the protocol
-     * \throw Error on errors
-     */
-    inline TcpSocket(int domain, int protocol)
-        : Socket(domain, SOCK_STREAM, protocol)
-    {
-    }
-
-    /**
-     * Get the type of the socket.
-     *
-     * \return the type
-     */
-    inline int type() const noexcept
-    {
-        return SOCK_STREAM;
-    }
-
-    /**
-     * Initiate connection.
-     *
-     * \param address the address
-     * \param length the address length
-     * \throw WouldBlockError if the socket is marked non-blocking and
-     * connection cannot be established immediately
-     * \throw Error on other errors
-     */
-    void connect(const sockaddr *address, socklen_t length)
-    {
-        if (::connect(this->m_handle, address, length) == Failure) {
-#if defined(_WIN32)
-            int error = WSAGetLastError();
-
-            if (error == WSAEWOULDBLOCK)
-                throw WouldBlockError();
-            else
-                throw Error(error);
-#else
-            if (errno == EINPROGRESS)
-                throw WouldBlockError();
-            else
-                throw Error();
-#endif
-        }
-    }
-
-    /**
-     * Overloaded function.
-     *
-     * \param address the address
-     * \throw WouldBlockError if the socket is marked non-blocking and
-     * connection cannot be established immediately
-     * \throw Error on other errors
-     */
-    void connect(const Address &address)
-    {
-        connect(address.get(), address.length());
-    }
-
-    /**
-     * Accept a new client.
-     *
-     * If there are no pending connection, an invalid socket is returned.
-     *
-     * \return the new socket
-     * \throw WouldBlockError if the socket is marked non-blocking and no
-     * connection are available
-     * \throw Error on other errors
-     */
-    TcpSocket accept()
-    {
-        Handle handle = ::accept(this->m_handle, nullptr, 0);
-
-        if (handle == Invalid) {
-#if defined(_WIN32)
-            int error = WSAGetLastError();
-
-            if (error == WSAEWOULDBLOCK)
-                throw WouldBlockError();
-            else
-                throw Error(error);
-#else
-            if (errno == EWOULDBLOCK || errno == EAGAIN)
-                throw WouldBlockError();
-            else
-                throw Error();
-#endif
-        }
-
-        return TcpSocket(handle);
-    }
-
-    /**
-     * Receive some data.
-     *
-     * \param data the destination buffer
-     * \param length the buffer length
-     * \return the number of bytes received
-     */
-    unsigned recv(void *data, unsigned length)
-    {
-        int max = length > INT_MAX ? INT_MAX : static_cast<int>(length);
-        int nbread = ::recv(this->m_handle, (Arg)data, max, 0);
-
-        if (nbread == Failure) {
-#if defined(_WIN32)
-            int error = WSAGetLastError();
-
-            if (error == WSAEWOULDBLOCK)
-                throw WouldBlockError();
-            else
-                throw Error(error);
-#else
-            if (errno == EAGAIN || errno == EWOULDBLOCK)
-                throw WouldBlockError();
-            else
-                throw Error();
-#endif
-        }
-
-        return static_cast<unsigned>(nbread);
-    }
-
-    /**
-     * Send some data.
-     *
-     * \param data the data to send
-     * \param length the length
-     * \return the number of bytes sent
-     */
-    unsigned send(const void *data, unsigned length)
-    {
-        int max = length > INT_MAX ? INT_MAX : static_cast<int>(length);
-        int nbsent = ::send(this->m_handle, (ConstArg)data, max, 0);
-
-        if (nbsent == Failure) {
-#if defined(_WIN32)
-            int error = WSAGetLastError();
-
-            if (error == WSAEWOULDBLOCK)
-                throw WouldBlockError();
-            else
-                throw Error();
-#else
-            if (errno == EAGAIN || errno == EWOULDBLOCK)
-                throw WouldBlockError();
-            else
-                throw Error();
-#endif
-        }
-
-        return static_cast<unsigned>(nbsent);
-    }
-};
-
-/**
- * \brief Clear UDP type.
- *
- * This class is the basic implementation of UDP sockets.
- */
-class UdpSocket : public Socket {
-public:
-    /**
-     * Inherited constructors.
-     */
-    using Socket::Socket;
-
-    /**
-     * Construct a TCP socket.
-     *
-     * \param domain the domain
-     * \param protocol the protocol
-     * \throw Error on errors
-     */
-    inline UdpSocket(int domain, int protocol)
-        : Socket(domain, SOCK_DGRAM, protocol)
-    {
-    }
-
-    /**
-     * Get the type of the socket.
-     *
-     * \return the type
-     */
-    inline int type() const noexcept
-    {
-        return SOCK_DGRAM;
-    }
-
-    /**
-     * Receive some data.
-     *
-     * \param data the data
-     * \param length the length
-     * \param address the source address
-     * \param addrlen the source address in/out length
-     * \return the number of bytes received
-     * \throw WouldBlockError if the socket is marked non-blocking and the
-     * operation would block
-     * \throw Error on other errors
-     */
-    unsigned recvfrom(void *data, unsigned length, sockaddr *address, socklen_t *addrlen)
-    {
-        int max = length > INT_MAX ? INT_MAX : static_cast<int>(length);
-        int nbread = ::recvfrom(this->m_handle, (Arg)data, max, 0, address, addrlen);
-
-        if (nbread == Failure) {
-#if defined(_WIN32)
-            int error = WSAGetLastError();
-
-            if (error == EWOULDBLOCK)
-                throw WouldBlockError();
-            else
-                throw Error(error);
-#else
-            if (errno == EAGAIN || errno == EWOULDBLOCK)
-                throw WouldBlockError();
-            else
-                throw Error();
-#endif
-        }
-
-        return static_cast<unsigned>(nbread);
-    }
-
-    /**
-     * Overloaded function.
-     *
-     * \param data the data
-     * \param length the length
-     * \param source the source information (optional)
-     * \throw WouldBlockError if the socket is marked non-blocking and the
-     * operation would block
-     * \throw Error on other errors
-     */
-    inline unsigned recvfrom(void *data, unsigned length, Address *source = nullptr)
-    {
-        sockaddr_storage st;
-        socklen_t socklen = sizeof (sockaddr_storage);
-
-        auto nr = recvfrom(data, length, reinterpret_cast<sockaddr *>(&st), &socklen);
-
-        if (source)
-            *source = Address(reinterpret_cast<const sockaddr *>(&st), socklen);
-
-        return nr;
-    }
-
-    /**
-     * Send some data.
-     *
-     * \param data the data to send
-     * \param length the data length
-     * \param address the destination address
-     * \param addrlen the destination address length
-     * \return the number of bytes sent
-     * \throw WouldBlockError if the socket is marked non-blocking and the
-     * operation would block
-     * \throw Error on other errors
-     */
-    unsigned sendto(const void *data, unsigned length, const sockaddr *address, socklen_t addrlen)
-    {
-        int max = length > INT_MAX ? INT_MAX : static_cast<int>(length);
-        int nbsent = ::sendto(this->m_handle, (ConstArg)data, max, 0, address, addrlen);
-
-        if (nbsent == Failure) {
-#if defined(_WIN32)
-            int error = WSAGetLastError();
-
-            if (error == EWOULDBLOCK)
-                throw WouldBlockError();
-            else
-                throw Error(error);
-#else
-            if (errno == EAGAIN || errno == EWOULDBLOCK)
-                throw WouldBlockError();
-            else
-                throw Error();
-#endif
-        }
-
-        return static_cast<unsigned>(nbsent);
-    }
-
-    /**
-     * Overloaded function
-     *
-     * \param data the data to send
-     * \param length the data length
-     * \param address the destination address
-     * \return the number of bytes sent
-     * \throw WouldBlockError if the socket is marked non-blocking and the
-     * operation would block
-     * \throw Error on other errors
-     */
-    inline unsigned sendto(const void *data, unsigned length, const Address &address)
-    {
-        return sendto(data, length, address.get(), address.length());
-    }
-};
-
-#if !defined(NET_NO_SSL)
-
-/**
- * \brief Experimental TLS support.
- * \ingroup net-module-tls
- * \warning This class is highly experimental.
- */
-class TlsSocket : public Socket {
-public:
-    /**
-     * \brief SSL connection mode.
-     */
-    enum Mode {
-        Server,         //!< Use Server when you accept a socket server side,
-        Client          //!< Use Client when you connect to a server.
-    };
-
-private:
-    using Context = std::shared_ptr<SSL_CTX>;
-    using Ssl = std::unique_ptr<SSL, void (*)(SSL *)>;
-
-    // Determine if we created a TlsSocket from a temporary or a lvalue.
-    bool m_mustclose{false};
-
-    Context m_context;
-    Ssl m_ssl{nullptr, nullptr};
-
-    inline std::string error()
-    {
-        BIO *bio = BIO_new(BIO_s_mem());
-        char *buf = nullptr;
-
-        ERR_print_errors(bio);
-
-        std::size_t length = BIO_get_mem_data (bio, &buf);
-        std::string result(buf, length);
-
-        BIO_free(bio);
-
-        return result;
-    }
-
-    template <typename Function>
-    void wrap(Function &&function)
-    {
-        auto ret = function();
-
-        if (ret <= 0) {
-            int no = SSL_get_error(m_ssl.get(), ret);
-
-            switch (no) {
-            case SSL_ERROR_WANT_READ:
-                throw WantReadError();
-            case SSL_ERROR_WANT_WRITE:
-                throw WantWriteError();
-            default:
-                throw Error(error());
-            }
-        }
-    }
-
-    void create(Mode mode, const SSL_METHOD *method)
-    {
-#if !defined(NET_NO_SSL_AUTO_INIT)
-        ssl::init();
-#endif
-        m_context = Context(SSL_CTX_new(method), SSL_CTX_free);
-        m_ssl = Ssl(SSL_new(m_context.get()), SSL_free);
-
-        SSL_set_fd(m_ssl.get(), this->m_handle);
-
-        if (mode == Server)
-            SSL_set_accept_state(m_ssl.get());
-        else
-            SSL_set_connect_state(m_ssl.get());
-    }
-
-public:
-    /**
-     * Create a socket around an existing one.
-     *
-     * The original socket is moved to this instance and must not be used
-     * anymore.
-     *
-     * \param sock the TCP socket
-     * \param mode the mode
-     * \param method the method
-     */
-    TlsSocket(TcpSocket &&sock, Mode mode = Server, const SSL_METHOD *method = NET_SSL_DEFAULT_METHOD())
-        : Socket(std::move(sock))
-        , m_mustclose(true)
-    {
-        create(mode, method);
-    }
-
-    /**
-     * Wrap a socket around an existing one without taking ownership.
-     *
-     * The original socket must still exist until this TlsSocket is closed.
-     *
-     * \param sock the TCP socket
-     * \param mode the mode
-     * \param method the method
-     */
-    TlsSocket(TcpSocket &sock, Mode mode = Server, const SSL_METHOD *method = NET_SSL_DEFAULT_METHOD())
-        : Socket(sock.handle())
-    {
-        create(mode, method);
-    }
-
-    /**
-     * Destroy the socket if owned.
-     */
-    ~TlsSocket()
-    {
-        /**
-         * If the socket has been created from a rvalue this class owns the
-         * socket and will close it in the parent destructor.
-         *
-         * Otherwise, when created from a lvalue, mark this socket as invalid
-         * to avoid double close'ing it as two sockets points to the same
-         * descriptor.
-         */
-        if (!m_mustclose)
-            m_handle = Invalid;
-    }
-
-    /**
-     * Get the type of socket.
-     *
-     * \return the type
-     */
-    inline int type() const noexcept
-    {
-        return SOCK_STREAM;
-    }
-
-    /**
-     * Use the specified private key file.
-     *
-     * \param file the path to the private key
-     * \param type the type of file
-     */
-    inline void setPrivateKey(std::string file, int type = SSL_FILETYPE_PEM)
-    {
-        if (SSL_use_PrivateKey_file(m_ssl.get(), file.c_str(), type) != 1)
-            throw Error(error());
-    }
-
-    /**
-     * Use the specified certificate file.
-     *
-     * \param file the path to the file
-     * \param type the type of file
-     */
-    inline void setCertificate(std::string file, int type = SSL_FILETYPE_PEM)
-    {
-        if (SSL_use_certificate_file(m_ssl.get(), file.c_str(), type) != 1)
-            throw Error(error());
-    }
-
-    /**
-     * Do handshake, needed in some case when you have non blocking sockets.
-     */
-    void handshake()
-    {
-        wrap([this] () -> int {
-            return SSL_do_handshake(m_ssl.get());
-        });
-    }
-
-    /**
-     * Receive some data.
-     *
-     * \param data the destination buffer
-     * \param length the buffer length
-     * \return the number of bytes received
-     */
-    unsigned recv(void *data, unsigned length)
-    {
-        int max = length > INT_MAX ? INT_MAX : static_cast<int>(length);
-        int nbread = 0;
-
-        wrap([&] () -> int {
-            return nbread = SSL_read(m_ssl.get(), data, max);
-        });
-
-        return static_cast<unsigned>(nbread < 0 ? 0 : nbread);
-    }
-
-    /**
-     * Send some data.
-     *
-     * \param data the data to send
-     * \param length the length
-     * \return the number of bytes sent
-     */
-    unsigned send(const void *data, unsigned length)
-    {
-        int max = length > INT_MAX ? INT_MAX : static_cast<int>(length);
-        int nbsent = 0;
-
-        wrap([&] () -> int {
-            return nbsent = SSL_write(m_ssl.get(), data, max);
-        });
-
-        return static_cast<unsigned>(nbsent < 0 ? 0 : nbsent);
-    }
-};
-
-#endif // !NET_NO_SSL
-
-/**
- * \brief IPv4 functions.
- */
-namespace ipv4 {
-
-/**
- * Create an address to bind on any.
- *
- * \param port the port
- * \return the address
- */
-inline Address any(std::uint16_t port)
-{
-    sockaddr_in sin;
-    socklen_t length = sizeof (sockaddr_in);
-
-    std::memset(&sin, 0, sizeof (sockaddr_in));
-    sin.sin_family = AF_INET;
-    sin.sin_addr.s_addr = INADDR_ANY;
-    sin.sin_port = htons(port);
-
-    return Address(reinterpret_cast<const sockaddr *>(&sin), length);
-}
-
-/**
- * Create an address from a IPv4 string.
- *
- * \param ip the ip address
- * \param port the port
- * \return the address
- * \throw Error if inet_pton is unavailable
- */
-inline Address pton(const std::string &ip, std::uint16_t port)
-{
-#if defined(NET_HAVE_INET_PTON)
-#if !defined(NET_NO_AUTO_INIT)
-    init();
-#endif
-
-    sockaddr_in sin;
-    socklen_t length = sizeof (sockaddr_in);
-
-    std::memset(&sin, 0, sizeof (sockaddr_in));
-    sin.sin_family = AF_INET;
-    sin.sin_port = htons(port);
-
-    if (inet_pton(AF_INET, ip.c_str(), &sin.sin_addr) <= 0)
-        throw Error();
-
-    return Address(reinterpret_cast<const sockaddr *>(&sin), length);
-#else
-    (void)ip;
-    (void)port;
-
-    throw Error(std::strerror(ENOSYS));
-#endif
-}
-
-/**
- * Get the underlying ip from the given address.
- *
- * \pre address.domain() == AF_INET
- * \param address the IPv6 address
- * \return the ip address
- * \throw Error if inet_ntop is unavailable
- */
-inline std::string ntop(const Address &address)
-{
-    assert(address.domain() == AF_INET);
-
-#if !defined(NET_NO_AUTO_INIT)
-    init();
-#endif
-
-#if defined(NET_HAVE_INET_NTOP)
-    char result[INET_ADDRSTRLEN + 1];
-
-    std::memset(result, 0, sizeof (result));
-
-    if (!inet_ntop(AF_INET, const_cast<in_addr *>(&address.as<sockaddr_in>().sin_addr), result, sizeof (result)))
-        throw Error();
-
-    return result;
-#else
-    (void)address;
-
-    throw Error(std::strerror(ENOSYS));
-#endif
-}
-
-/**
- * Get the port from the IPv4 address.
- *
- * \pre address.domain() == AF_INET4
- * \param address the address
- * \return the port
- */
-inline std::uint16_t port(const Address &address) noexcept
-{
-    assert(address.domain() == AF_INET);
-
-    return ntohs(address.as<sockaddr_in>().sin_port);
-}
-
-} // !ipv4
-
-/**
- * \brief IPv6 functions.
- */
-namespace ipv6 {
-
-/**
- * Create an address to bind on any.
- *
- * \param port the port
- * \return the address
- */
-inline Address any(std::uint16_t port)
-{
-    sockaddr_in6 sin6;
-    socklen_t length = sizeof (sockaddr_in6);
-
-    std::memset(&sin6, 0, sizeof (sockaddr_in6));
-    sin6.sin6_family = AF_INET6;
-    sin6.sin6_addr = in6addr_any;
-    sin6.sin6_port = htons(port);
-
-    return Address(reinterpret_cast<const sockaddr *>(&sin6), length);
-}
-
-/**
- * Create an address from a IPv4 string.
- *
- * \param ip the ip address
- * \param port the port
- * \return the address
- * \throw Error if inet_pton is unavailable
- */
-inline Address pton(const std::string &ip, std::uint16_t port)
-{
-#if defined(NET_HAVE_INET_PTON)
-#if !defined(NET_NO_AUTO_INIT)
-    init();
-#endif
-
-    sockaddr_in6 sin6;
-    socklen_t length = sizeof (sockaddr_in6);
-
-    std::memset(&sin6, 0, sizeof (sockaddr_in6));
-    sin6.sin6_family = AF_INET6;
-    sin6.sin6_port = htons(port);
-
-    if (inet_pton(AF_INET6, ip.c_str(), &sin6.sin6_addr) <= 0)
-        throw Error();
-
-    return Address(reinterpret_cast<const sockaddr *>(&sin6), length);
-#else
-    (void)ip;
-    (void)port;
-
-    throw Error(std::strerror(ENOSYS));
-#endif
-}
-
-/**
- * Get the underlying ip from the given address.
- *
- * \pre address.domain() == AF_INET6
- * \param address the IPv6 address
- * \return the ip address
- * \throw Error if inet_ntop is unavailable
- */
-inline std::string ntop(const Address &address)
-{
-    assert(address.domain() == AF_INET6);
-
-#if defined(NET_HAVE_INET_NTOP)
-#if !defined(NET_NO_AUTO_INIT)
-    init();
-#endif
-
-    char ret[INET6_ADDRSTRLEN];
-
-    std::memset(ret, 0, sizeof (ret));
-
-    if (!inet_ntop(AF_INET6, const_cast<in6_addr *>(&address.as<sockaddr_in6>().sin6_addr), ret, sizeof (ret)))
-        throw Error();
-
-    return ret;
-#else
-    (void)address;
-
-    throw Error(std::strerror(ENOSYS));
-#endif
-}
-
-/**
- * Get the port from the IPv6 address.
- *
- * \pre address.domain() == AF_INET6
- * \param address the address
- * \return the port
- */
-inline std::uint16_t port(const Address &address) noexcept
-{
-    assert(address.domain() == AF_INET6);
-
-    return ntohs(address.as<sockaddr_in6>().sin6_port);
-}
-
-} // !ipv6
-
-#if !defined(_WIN32)
-
-/**
- * \brief Unix domain functions.
- */
-namespace local {
-
-/**
- * Construct an address to a path.
- *
- * \pre !path.empty()
- * \param path the path
- * \param rm remove the file before (default: false)
- */
-inline Address create(const std::string &path, bool rm = false) noexcept
-{
-    assert(!path.empty());
-
-    // Silently remove the file even if it fails.
-    if (rm)
-        remove(path.c_str());
-
-    sockaddr_un sun;
-    socklen_t length;
-
-    std::memset(sun.sun_path, 0, sizeof (sun.sun_path));
-    std::strncpy(sun.sun_path, path.c_str(), sizeof (sun.sun_path) - 1);
-
-    sun.sun_family = AF_LOCAL;
-
-#if defined(NET_HAVE_SUN_LEN)
-    length = SUN_LEN(&sun);
-#else
-    length = sizeof (sun);
-#endif
-
-    return Address(reinterpret_cast<const sockaddr *>(&sun), length);
-}
-
-/**
- * Get the path from the address.
- *
- * \pre address.domain() == AF_LOCAL
- * \param address the local address
- * \return the path to the socket file
- */
-inline std::string path(const Address &address)
-{
-    assert(address.domain() == AF_LOCAL);
-
-    return reinterpret_cast<const sockaddr_un *>(address.get())->sun_path;
-}
-
-} // !local
-
-#endif // !_WIN32
-
-/**
- * \brief Predefined options.
- */
-namespace option {
-
-/**
- * \ingroup net-module-options
- * \brief Set or get the blocking-mode for a socket.
- * \warning On Windows, it's not possible to check if the socket is blocking or
- * not.
- */
-class SockBlockMode {
-private:
-    bool m_value;
-
-public:
-    /**
-     * Create the option.
-     *
-     * By default the blocking mode is set to true.
-     *
-     * \param value set to true to make blocking sockets
-     */
-    inline SockBlockMode(bool value = true) noexcept
-        : m_value(value)
-    {
-    }
-
-    /**
-     * Set the option.
-     *
-     * \param sc the socket
-     * \throw Error on errors
-     */
-    void set(Socket &sc) const
-    {
-#if defined(O_NONBLOCK) && !defined(_WIN32)
-        int flags;
-
-        if ((flags = fcntl(sc.handle(), F_GETFL, 0)) < 0)
-            flags = 0;
-
-        if (m_value)
-            flags &= ~(O_NONBLOCK);
-        else
-            flags |= O_NONBLOCK;
-
-        if (fcntl(sc.handle(), F_SETFL, flags) < 0)
-            throw Error();
-#else
-        unsigned long flags = (m_value) ? 0 : 1;
-
-        if (ioctlsocket(sc.handle(), FIONBIO, &flags) == Failure)
-            throw Error();
-#endif
-    }
-
-    /**
-     * Get the option.
-     *
-     * \param sc the socket
-     * \return the value
-     * \throw Error on errors
-     */
-    bool get(Socket &sc) const
-    {
-#if defined(O_NONBLOCK) && !defined(_WIN32)
-        int flags = fcntl(sc.handle(), F_GETFL, 0);
-
-        if (flags < 0)
-            throw Error();
-
-        return !(flags & O_NONBLOCK);
-#else
-        (void)sc;
-
-        throw Error(std::strerror(ENOSYS));
-#endif
-    }
-};
-
-/**
- * \ingroup net-module-options
- * \brief Set or get the input buffer.
- */
-class SockReceiveBuffer {
-private:
-    int m_value;
-
-public:
-    /**
-     * Create the option.
-     *
-     * \param size the buffer size
-     */
-    inline SockReceiveBuffer(int size = 2048) noexcept
-        : m_value(size)
-    {
-    }
-
-    /**
-     * Set the option.
-     *
-     * \param sc the socket
-     * \throw Error on errors
-     */
-    inline void set(Socket &sc) const
-    {
-        sc.set(SOL_SOCKET, SO_RCVBUF, m_value);
-    }
-
-    /**
-     * Get the option.
-     *
-     * \param sc the socket
-     * \return the value
-     * \throw Error on errors
-     */
-    inline int get(Socket &sc) const
-    {
-        return sc.get<int>(SOL_SOCKET, SO_RCVBUF);
-    }
-};
-
-/**
- * \ingroup net-module-options
- * \brief Reuse address, must be used before calling Socket::bind
- */
-class SockReuseAddress {
-private:
-    bool m_value;
-
-public:
-    /**
-     * Create the option.
-     *
-     * By default the option reuses the address.
-     *
-     * \param value set to true to reuse the address
-     */
-    inline SockReuseAddress(bool value = true) noexcept
-        : m_value(value)
-    {
-    }
-
-    /**
-     * Set the option.
-     *
-     * \param sc the socket
-     * \throw Error on errors
-     */
-    inline void set(Socket &sc) const
-    {
-        sc.set(SOL_SOCKET, SO_REUSEADDR, m_value ? 1 : 0);
-    }
-
-    /**
-     * Get the option.
-     *
-     * \param sc the socket
-     * \return the value
-     * \throw Error on errors
-     */
-    inline bool get(Socket &sc) const
-    {
-        return sc.get<int>(SOL_SOCKET, SO_REUSEADDR) != 0;
-    }
-};
-
-/**
- * \ingroup net-module-options
- * \brief Set or get the output buffer.
- */
-class SockSendBuffer {
-private:
-    int m_value;
-
-public:
-    /**
-     * Create the option.
-     *
-     * \param size the buffer size
-     */
-    inline SockSendBuffer(int size = 2048) noexcept
-        : m_value(size)
-    {
-    }
-
-    /**
-     * Set the option.
-     *
-     * \param sc the socket
-     * \throw Error on errors
-     */
-    inline void set(Socket &sc) const
-    {
-        sc.set(SOL_SOCKET, SO_SNDBUF, m_value);
-    }
-
-    /**
-     * Get the option.
-     *
-     * \param sc the socket
-     * \return the value
-     * \throw Error on errors
-     */
-    inline int get(Socket &sc) const
-    {
-        return sc.get<int>(SOL_SOCKET, SO_SNDBUF);
-    }
-};
-
-/**
- * \ingroup net-module-options
- * \brief Set this option if you want to disable nagle's algorithm.
- */
-class TcpNoDelay {
-private:
-    bool m_value;
-
-public:
-    /**
-     * Create the option.
-     *
-     * By default disable TCP delay.
-     *
-     * \param value set to true to disable TCP delay
-     */
-    inline TcpNoDelay(bool value = true) noexcept
-        : m_value(value)
-    {
-    }
-
-    /**
-     * Set the option.
-     *
-     * \param sc the socket
-     * \throw Error on errors
-     */
-    inline void set(Socket &sc) const
-    {
-        sc.set(IPPROTO_TCP, TCP_NODELAY, m_value ? 1 : 0);
-    }
-
-    /**
-     * Get the option.
-     *
-     * \param sc the socket
-     * \return the value
-     * \throw Error on errors
-     */
-    inline bool get(Socket &sc) const
-    {
-        return sc.get<int>(IPPROTO_TCP, TCP_NODELAY) != 0;
-    }
-};
-
-/**
- * \ingroup net-module-options
- * \brief Control IPPROTO_IPV6/IPV6_V6ONLY
- *
- * Note: some systems may or not set this option by default so it's a good idea
- * to set it in any case to either
- * false or true if portability is a concern.
- */
-class Ipv6Only {
-private:
-    bool m_value;
-
-public:
-    /**
-     * Create the option.
-     *
-     * By default with want IPv6 only.
-     *
-     * \param value set to true to use IPv6 only
-     */
-    inline Ipv6Only(bool value = true) noexcept
-        : m_value(value)
-    {
-    }
-
-    /**
-     * Set the option.
-     *
-     * \param sc the socket
-     * \throw Error on errors
-     */
-    inline void set(Socket &sc) const
-    {
-        sc.set(IPPROTO_IPV6, IPV6_V6ONLY, m_value ? 1 : 0);
-    }
-
-    /**
-     * Get the option.
-     *
-     * \param sc the socket
-     * \return the value
-     * \throw Error on errors
-     */
-    inline bool get(Socket &sc) const
-    {
-        return sc.get<int>(IPPROTO_IPV6, IPV6_V6ONLY) != 0;
-    }
-};
-
-} // !option
-
-/**
- * \brief Result of polling
- *
- * Result of a select call, returns the first ready socket found with its
- * flags.
- */
-class ListenerStatus {
-public:
-    Handle socket;              //!< which socket is ready
-    Condition flags;            //!< the flags
-};
-
-/**
- * Table used in the socket listener to store which sockets have been
- * set in which directions.
- */
-using ListenerTable = std::unordered_map<Handle, Condition>;
-
-/**
- * \brief Predefined backends for Listener.
- */
-namespace backend {
-
-#if defined(NET_HAVE_EPOLL)
-
-/**
- * \ingroup net-module-backends
- * \brief Linux's epoll.
- */
-class Epoll {
-private:
-    int m_handle{-1};
-    std::vector<epoll_event> m_events;
-
-    Epoll(const Epoll &) = delete;
-    Epoll &operator=(const Epoll &) = delete;
-
-    std::uint32_t toEpoll(Condition condition) const noexcept
-    {
-        std::uint32_t events = 0;
-
-        if ((condition & Condition::Readable) == Condition::Readable)
-            events |= EPOLLIN;
-        if ((condition & Condition::Writable) == Condition::Writable)
-            events |= EPOLLOUT;
-
-        return events;
-    }
-
-    Condition toCondition(std::uint32_t events) const noexcept
-    {
-        Condition condition = Condition::None;
-
-        if ((events & EPOLLIN) || (events & EPOLLHUP))
-            condition |= Condition::Readable;
-        if (events & EPOLLOUT)
-            condition |= Condition::Writable;
-
-        return condition;
-    }
-
-    void update(Handle h, int op, int eflags)
-    {
-        epoll_event ev;
-
-        std::memset(&ev, 0, sizeof (epoll_event));
-
-        ev.events = eflags;
-        ev.data.fd = h;
-
-        if (epoll_ctl(m_handle, op, h, &ev) < 0)
-            throw Error();
-    }
-
-public:
-    /**
-     * Create epoll.
-     *
-     * \throw Error on failures
-     */
-    inline Epoll()
-        : m_handle(epoll_create1(0))
-    {
-        if (m_handle < 0)
-            throw Error();
-    }
-
-    /**
-     * Move constructor.
-     *
-     * \param other the other backend
-     */
-    inline Epoll(Epoll &&other) noexcept
-        : m_handle(other.m_handle)
-    {
-        other.m_handle = -1;
-    }
-
-    /**
-     * Close the kqueue descriptor.
-     */
-    inline ~Epoll()
-    {
-        if (m_handle != -1)
-            close(m_handle);
-    }
-
-    /**
-     * Get the backend name.
-     *
-     * \return kqueue
-     */
-    inline std::string name() const noexcept
-    {
-        return "epoll";
-    }
-
-    /**
-     * For set and unset, we need to apply the whole flags required, so if the
-     * socket was set to Connection::Readable and user **adds**
-     * Connection::Writable, we must set both.
-     *
-     * \param table the listener table
-     * \param h the handle
-     * \param condition the condition
-     * \param add set to true if the socket is new to the backend
-     * \throw Error on failures
-     */
-    void set(const ListenerTable &table, Handle h, Condition condition, bool add)
-    {
-        if (add) {
-            update(h, EPOLL_CTL_ADD, toEpoll(condition));
-            m_events.resize(m_events.size() + 1);
-        } else
-            update(h, EPOLL_CTL_MOD, toEpoll(table.at(h) | condition));
-    }
-
-    /**
-     * Unset is a bit complicated case because Listener tells us which
-     * flag to remove but to update epoll descriptor we need to pass
-     * the effective flags that we want to be applied.
-     *
-     * So we put the same flags that are currently effective and remove the
-     * requested one.
-     *
-     * \param table the listener table
-     * \param h the handle
-     * \param condition the condition
-     * \param add set to true if the socket is new to the backend
-     * \throw Error on failures
-     */
-    void unset(const ListenerTable &table, Handle h, Condition condition, bool remove)
-    {
-        if (remove) {
-            update(h, EPOLL_CTL_DEL, 0);
-            m_events.resize(m_events.size() - 1);
-        } else
-            update(h, EPOLL_CTL_MOD, toEpoll(table.at(h) & ~(condition)));
-    }
-
-    /**
-     * Wait for sockets to be ready.
-     *
-     * \param ms the milliseconds timeout
-     * \return the sockets ready
-     * \throw Error on failures
-     */
-    std::vector<ListenerStatus> wait(const ListenerTable &, int ms)
-    {
-        int ret = epoll_wait(m_handle, m_events.data(), m_events.size(), ms);
-        std::vector<ListenerStatus> result;
-
-        if (ret == 0)
-            throw TimeoutError();
-        if (ret < 0)
-            throw Error();
-
-        for (int i = 0; i < ret; ++i)
-            result.push_back(ListenerStatus{m_events[i].data.fd, toCondition(m_events[i].events)});
-
-        return result;
-    }
-
-    /**
-     * Move operator.
-     *
-     * \param other the other
-     * \return this
-     */
-    inline Epoll &operator=(Epoll &&other)
-    {
-        m_handle = other.m_handle;
-        other.m_handle = -1;
-
-        return *this;
-    }
-};
-
-#endif // !NET_HAVE_EPOLL
-
-#if defined(NET_HAVE_KQUEUE)
-
-/**
- * \ingroup net-module-backends
- * \brief Implements kqueue(2).
- *
- * This implementation is available on all BSD and Mac OS X. It is better than
- * poll(2) because it's O(1), however it's a bit more memory consuming.
- */
-class Kqueue {
-private:
-    std::vector<struct kevent> m_result;
-    int m_handle;
-
-    Kqueue(const Kqueue &) = delete;
-    Kqueue &operator=(const Kqueue &) = delete;
-
-    void update(Handle h, int filter, int kflags)
-    {
-        struct kevent ev;
-
-        EV_SET(&ev, h, filter, kflags, 0, 0, nullptr);
-
-        if (kevent(m_handle, &ev, 1, nullptr, 0, nullptr) < 0)
-            throw Error();
-    }
-
-public:
-    /**
-     * Create kqueue.
-     *
-     * \throw Error on failures
-     */
-    inline Kqueue()
-        : m_handle(kqueue())
-    {
-        if (m_handle < 0)
-            throw Error();
-    }
-
-    /**
-     * Move constructor.
-     *
-     * \param other the other backend
-     */
-    inline Kqueue(Kqueue &&other) noexcept
-        : m_handle(other.m_handle)
-    {
-        other.m_handle = -1;
-    }
-
-    /**
-     * Close the kqueue descriptor.
-     */
-    inline ~Kqueue()
-    {
-        if (m_handle != -1)
-            close(m_handle);
-    }
-
-    /**
-     * Get the backend name.
-     *
-     * \return kqueue
-     */
-    inline std::string name() const noexcept
-    {
-        return "kqueue";
-    }
-
-    /**
-     * Set socket.
-     *
-     * \param h the handle
-     * \param condition the condition
-     * \param add set to true if the socket is new to the backend
-     * \throw Error on failures
-     */
-    void set(const ListenerTable &, Handle h, Condition condition, bool add)
-    {
-        if ((condition & Condition::Readable) == Condition::Readable)
-            update(h, EVFILT_READ, EV_ADD | EV_ENABLE);
-        if ((condition & Condition::Writable) == Condition::Writable)
-            update(h, EVFILT_WRITE, EV_ADD | EV_ENABLE);
-        if (add)
-            m_result.resize(m_result.size() + 1);
-    }
-
-    /**
-     * Unset socket.
-     *
-     * \param h the handle
-     * \param condition the condition
-     * \param remove set to true if the socket is completely removed
-     * \throw Error on failures
-     */
-    void unset(const ListenerTable &, Handle h, Condition condition, bool remove)
-    {
-        if ((condition & Condition::Readable) == Condition::Readable)
-            update(h, EVFILT_READ, EV_DELETE);
-        if ((condition & Condition::Writable) == Condition::Writable)
-            update(h, EVFILT_WRITE, EV_DELETE);
-        if (remove)
-            m_result.resize(m_result.size() - 1);
-    }
-
-    /**
-     * Wait for sockets to be ready.
-     *
-     * \param ms the milliseconds timeout
-     * \return the sockets ready
-     * \throw Error on failures
-     */
-    std::vector<ListenerStatus> wait(const ListenerTable &, int ms)
-    {
-        std::vector<ListenerStatus> sockets;
-        timespec ts = { 0, 0 };
-        timespec *pts = (ms <= 0) ? nullptr : &ts;
-
-        ts.tv_sec = ms / 1000;
-        ts.tv_nsec = (ms % 1000) * 1000000;
-
-        int nevents = kevent(m_handle, nullptr, 0, &m_result[0], m_result.capacity(), pts);
-
-        if (nevents == 0)
-            throw TimeoutError();
-        if (nevents < 0)
-            throw Error();
-
-        for (int i = 0; i < nevents; ++i) {
-            sockets.push_back(ListenerStatus{
-                static_cast<Handle>(m_result[i].ident),
-                m_result[i].filter == EVFILT_READ ? Condition::Readable : Condition::Writable
-            });
-        }
-
-        return sockets;
-    }
-
-    /**
-     * Move operator.
-     *
-     * \param other the other
-     * \return this
-     */
-    inline Kqueue &operator=(Kqueue &&other) noexcept
-    {
-        m_handle = other.m_handle;
-        other.m_handle = -1;
-
-        return *this;
-    }
-};
-
-#endif // !NET_HAVE_KQUEUE
-
-#if defined(NET_HAVE_POLL)
-
-/**
- * \ingroup net-module-backends
- * \brief Implements poll(2).
- *
- * Poll is widely supported and is better than select(2). It is still not the
- * best option as selecting the sockets is O(n).
- */
-class Poll {
-private:
-    std::vector<pollfd> m_fds;
-
-    short toPoll(Condition condition) const noexcept
-    {
-        short result = 0;
-
-        if ((condition & Condition::Readable) == Condition::Readable)
-            result |= POLLIN;
-        if ((condition & Condition::Writable) == Condition::Writable)
-            result |= POLLOUT;
-
-        return result;
-    }
-
-    Condition toCondition(short &event) const noexcept
-    {
-        Condition condition = Condition::None;
-
-        /*
-         * Poll implementations mark the socket differently regarding the
-         * disconnection of a socket.
-         *
-         * At least, even if POLLHUP or POLLIN is set, recv() always return 0 so
-         * we mark the socket as readable.
-         */
-        if ((event & POLLIN) || (event & POLLHUP))
-            condition |= Condition::Readable;
-        if (event & POLLOUT)
-            condition |= Condition::Writable;
-
-        // Reset event for safety.
-        event = 0;
-
-        return condition;
-    }
-
-public:
-    /**
-     * Get the backend name.
-     *
-     * \return kqueue
-     */
-    inline std::string name() const noexcept
-    {
-        return "poll";
-    }
-
-    /**
-     * Set socket.
-     *
-     * \param h the handle
-     * \param condition the condition
-     * \param add set to true if the socket is new to the backend
-     * \throw Error on failures
-     */
-    void set(const ListenerTable &, Handle h, Condition condition, bool add)
-    {
-        if (add)
-            m_fds.push_back(pollfd{h, toPoll(condition), 0});
-        else {
-            auto it = std::find_if(m_fds.begin(), m_fds.end(), [&] (const pollfd &pfd) {
-                return pfd.fd == h;
-            });
-
-            it->events |= toPoll(condition);
-        }
-    }
-
-    /**
-     * Unset socket.
-     *
-     * \param h the handle
-     * \param condition the condition
-     * \param remove set to true if the socket is completely removed
-     * \throw Error on failures
-     */
-    void unset(const ListenerTable &, Handle h, Condition condition, bool remove)
-    {
-        auto it = std::find_if(m_fds.begin(), m_fds.end(), [&] (const pollfd &pfd) {
-            return pfd.fd == h;
-        });
-
-        if (remove)
-            m_fds.erase(it);
-        else
-            it->events &= ~(toPoll(condition));
-    }
-
-    /**
-     * Wait for sockets to be ready.
-     *
-     * \param ms the milliseconds timeout
-     * \return the sockets ready
-     * \throw Error on failures
-     */
-    std::vector<ListenerStatus> wait(const ListenerTable &, int ms)
-    {
-#if defined(_WIN32)
-        auto result = WSAPoll(m_fds.data(), (ULONG)m_fds.size(), ms);
-#else
-        auto result = poll(m_fds.data(), m_fds.size(), ms);
-#endif
-
-        if (result == 0)
-            throw TimeoutError();
-        if (result < 0)
-            throw Error();
-
-        std::vector<ListenerStatus> sockets;
-
-        for (auto &fd : m_fds)
-            if (fd.revents != 0)
-                sockets.push_back(ListenerStatus{fd.fd, toCondition(fd.revents)});
-
-        return sockets;
-    }
-};
-
-#endif // !NET_HAVE_POLL
-
-/**
- * \ingroup net-module-backends
- * \brief Implements select(2)
- *
- * This class is the fallback of any other method, it is not preferred at all
- * for many reasons.
- */
-class Select {
-public:
-    /**
-     * Get the backend name.
-     *
-     * \return select
-     */
-    inline std::string name() const
-    {
-        return "select";
-    }
-
-    /**
-     * No-op.
-     */
-    inline void set(const ListenerTable &, Handle, Condition, bool) noexcept
-    {
-    }
-
-    /**
-     * No-op.
-     */
-    inline void unset(const ListenerTable &, Handle, Condition, bool) noexcept
-    {
-    }
-
-    /**
-     * Wait for sockets to be ready.
-     *
-     * \param table the listener table
-     * \param ms the milliseconds timeout
-     * \return the sockets ready
-     * \throw Error on failures
-     */
-    std::vector<ListenerStatus> wait(const ListenerTable &table, int ms)
-    {
-        timeval maxwait, *towait;
-        fd_set readset;
-        fd_set writeset;
-
-        FD_ZERO(&readset);
-        FD_ZERO(&writeset);
-
-        Handle max = 0;
-
-        for (const auto &pair : table) {
-            if ((pair.second & Condition::Readable) == Condition::Readable)
-                FD_SET(pair.first, &readset);
-            if ((pair.second & Condition::Writable) == Condition::Writable)
-                FD_SET(pair.first, &writeset);
-            if (pair.first > max)
-                max = pair.first;
-        }
-
-        maxwait.tv_sec = 0;
-        maxwait.tv_usec = ms * 1000;
-
-        // Set to nullptr for infinite timeout.
-        towait = (ms < 0) ? nullptr : &maxwait;
-
-        auto error = ::select(static_cast<int>(max + 1), &readset, &writeset, nullptr, towait);
-
-        if (error == Failure)
-            throw Error();
-        if (error == 0)
-            throw TimeoutError();
-
-        std::vector<ListenerStatus> sockets;
-
-        for (const auto &pair : table) {
-            if (FD_ISSET(pair.first, &readset))
-                sockets.push_back(ListenerStatus{pair.first, Condition::Readable});
-            if (FD_ISSET(pair.first, &writeset))
-                sockets.push_back(ListenerStatus{pair.first, Condition::Writable});
-        }
-
-        return sockets;
-    }
-};
-
-} // !backend
-
-/**
- * \brief Synchronous multiplexing
- *
- * Convenient wrapper around the select() system call.
- *
- * This class is implemented using a bridge pattern to allow different uses
- * of listener implementation.
- *
- * You should not reinstanciate a new Listener at each iteartion of your
- * main loop as it can be extremely costly. Instead use the same listener that
- * you can safely modify on the fly.
- *
- * Currently, poll, epoll, select and kqueue are available.
- */
-template <typename Backend = backend :: NET_DEFAULT_BACKEND>
-class Listener {
-private:
-    Backend m_backend;
-    ListenerTable m_table;
-
-public:
-    /**
-     * Construct an empty listener.
-     */
-    Listener() = default;
-
-    /**
-     * Get the backend.
-     *
-     * \return the backend
-     */
-    inline const Backend &backend() const noexcept
-    {
-        return m_backend;
-    }
-
-    /**
-     * Get the non-modifiable table.
-     *
-     * \return the table
-     */
-    inline const ListenerTable &table() const noexcept
-    {
-        return m_table;
-    }
-
-    /**
-     * Overloaded function.
-     *
-     * \return the iterator
-     */
-    inline ListenerTable::const_iterator begin() const noexcept
-    {
-        return m_table.begin();
-    }
-
-    /**
-     * Overloaded function.
-     *
-     * \return the iterator
-     */
-    inline ListenerTable::const_iterator cbegin() const noexcept
-    {
-        return m_table.cbegin();
-    }
-
-    /**
-     * Overloaded function.
-     *
-     * \return the iterator
-     */
-    inline ListenerTable::const_iterator end() const noexcept
-    {
-        return m_table.end();
-    }
-
-    /**
-     * Overloaded function.
-     *
-     * \return the iterator
-     */
-    inline ListenerTable::const_iterator cend() const noexcept
-    {
-        return m_table.cend();
-    }
-
-    /**
-     * Add or update a socket to the listener.
-     *
-     * If the socket is already placed with the appropriate flags, the
-     * function is a no-op.
-     *
-     * If incorrect flags are passed, the function does nothing.
-     *
-     * \param sc the socket
-     * \param condition the condition (may be OR'ed)
-     * \throw Error if the backend failed to set
-     */
-    void set(Handle sc, Condition condition)
-    {
-        // Invalid or useless flags.
-        if (condition == Condition::None || static_cast<int>(condition) > 0x3)
-            return;
-
-        auto it = m_table.find(sc);
-
-        // Do not update the table if the backend failed to add or update.
-        if (it == m_table.end()) {
-            m_backend.set(m_table, sc, condition, true);
-            m_table.emplace(sc, condition);
-        } else {
-            // Remove flag if already present.
-            if ((condition & Condition::Readable) == Condition::Readable &&
-                (it->second & Condition::Readable) == Condition::Readable)
-                condition &= ~(Condition::Readable);
-            if ((condition & Condition::Writable) == Condition::Writable &&
-                (it->second & Condition::Writable) == Condition::Writable)
-                condition &= ~(Condition::Writable);
-
-            // Still need a call?
-            if (condition != Condition::None) {
-                m_backend.set(m_table, sc, condition, false);
-                it->second |= condition;
-            }
-        }
-    }
-
-    /**
-     * Unset a socket from the listener, only the flags is removed
-     * unless the two flagss are requested.
-     *
-     * For example, if you added a socket for both reading and writing,
-     * unsetting the write flags will keep the socket for reading.
-     *
-     * \param sc the socket
-     * \param condition the condition (may be OR'ed)
-     * \see remove
-     */
-    void unset(Handle sc, Condition condition)
-    {
-        auto it = m_table.find(sc);
-
-        // Invalid or useless flags.
-        if (condition == Condition::None || static_cast<int>(condition) > 0x3 || it == m_table.end())
-            return;
-
-        // Like set, do not update if the socket is already at the appropriate state.
-        if ((condition & Condition::Readable) == Condition::Readable &&
-            (it->second & Condition::Readable) != Condition::Readable)
-            condition &= ~(Condition::Readable);
-        if ((condition & Condition::Writable) == Condition::Writable &&
-            (it->second & Condition::Writable) != Condition::Writable)
-            condition &= ~(Condition::Writable);
-
-        if (condition != Condition::None) {
-            // Determine if it's a complete removal.
-            bool removal = ((it->second) & ~(condition)) == Condition::None;
-
-            m_backend.unset(m_table, sc, condition, removal);
-
-            if (removal)
-                m_table.erase(it);
-            else
-                it->second &= ~(condition);
-        }
-    }
-
-    /**
-     * Remove completely the socket from the listener.
-     *
-     * It is a shorthand for unset(sc, Condition::Readable |
-     * Condition::Writable);
-     *
-     * \param sc the socket
-     */
-    inline void remove(Handle sc)
-    {
-        unset(sc, Condition::Readable | Condition::Writable);
-    }
-
-    /**
-     * Remove all sockets.
-     */
-    inline void clear()
-    {
-        while (!m_table.empty())
-            remove(m_table.begin()->first);
-    }
-
-    /**
-     * Get the number of sockets in the listener.
-     *
-     * \return the number of sockets
-     */
-    inline ListenerTable::size_type size() const noexcept
-    {
-        return m_table.size();
-    }
-
-    /**
-     * Select a socket. Waits for a specific amount of time specified as the
-     * duration.
-     *
-     * \param duration the duration
-     * \return the socket ready
-     */
-    template <typename Rep, typename Ratio>
-    inline ListenerStatus wait(const std::chrono::duration<Rep, Ratio> &duration)
-    {
-        auto cvt = std::chrono::duration_cast<std::chrono::milliseconds>(duration);
-        auto max = cvt.count() > INT_MAX ? INT_MAX : static_cast<int>(cvt.count());
-
-        return m_backend.wait(m_table, max)[0];
-    }
-
-    /**
-     * Overload with milliseconds.
-     *
-     * \param timeout the optional timeout in milliseconds
-     * \return the socket ready
-     */
-    inline ListenerStatus wait(long long int timeout = -1)
-    {
-        return wait(std::chrono::milliseconds(timeout));
-    }
-
-    /**
-     * Select multiple sockets.
-     *
-     * \param duration the duration
-     * \return the socket ready
-     */
-    template <typename Rep, typename Ratio>
-    inline std::vector<ListenerStatus> waitMultiple(const std::chrono::duration<Rep, Ratio> &duration)
-    {
-        auto cvt = std::chrono::duration_cast<std::chrono::milliseconds>(duration);
-
-        return m_backend.wait(m_table, cvt.count());
-    }
-
-    /**
-     * Overload with milliseconds.
-     *
-     * \param timeout the optional timeout in milliseconds
-     * \return the socket ready
-     */
-    inline std::vector<ListenerStatus> waitMultiple(int timeout = -1)
-    {
-        return waitMultiple(std::chrono::milliseconds(timeout));
-    }
-};
-
-/**
- * \ingroup net-module-resolv
- *
- * Resolve an hostname immediately.
- *
- * \param host the hostname
- * \param service the service (e.g. http or port number)
- * \param domain the domain (e.g. AF_INET)
- * \param type the type (e.g. SOCK_STREAM)
- * \return the address iterator
- * \throw Error on failures
- */
-inline AddressIterator resolve(const std::string &host,
-                               const std::string &service,
-                               int domain = AF_UNSPEC,
-                               int type = 0)
-{
-#if !defined(NET_NO_AUTO_INIT)
-        init();
-#endif
-
-    struct addrinfo hints, *res, *p;
-
-    std::memset(&hints, 0, sizeof (hints));
-    hints.ai_family = domain;
-    hints.ai_socktype = type;
-
-    int e = getaddrinfo(host.c_str(), service.c_str(), &hints, &res);
-
-    if (e != 0)
-        throw Error(gai_strerror(e));
-
-    std::vector<Address> addresses;
-
-    for (p = res; p != nullptr; p = p->ai_next)
-        addresses.push_back(Address(p->ai_addr, p->ai_addrlen));
-
-    return AddressIterator(addresses, 0);
-}
-
-/**
- * \ingroup net-module-resolv
- *
- * Resolve the first address.
- *
- * \param host the hostname
- * \param service the service name
- * \param domain the domain (e.g. AF_INET)
- * \param type the type (e.g. SOCK_STREAM)
- * \return the first generic address available
- * \throw Error on failures
- * \note do not use AF_UNSPEC and 0 as type for this function
- */
-inline Address resolveOne(const std::string &host, const std::string &service, int domain, int type)
-{
-    AddressIterator it = resolve(host, service, domain, type);
-    AddressIterator end;
-
-    if (it == end)
-        throw Error("no address available");
-
-    return *it;
-}
-
-} // !net
-
-} // !irccd
-
-#endif // !IRCCD_NET_HPP
--- a/libcommon/irccd/net_util.hpp	Fri Nov 24 20:05:15 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,141 +0,0 @@
-/*
- * net_util.hpp -- network utilities for pollable objects
- *
- * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#ifndef IRCCD_COMMON_NET_UTIL_HPP
-#define IRCCD_COMMON_NET_UTIL_HPP
-
-/**
- * \file net_util.hpp
- * \brief Network utilities for pollable objects.
- */
-
-#include "net.hpp"
-
-namespace irccd {
-
-/**
- * \brief Miscellaneous utilities for Pollable objects
- */
-namespace net_util {
-
-/**
- * \cond HIDDEN_SYMBOLS
- */
-
-inline void prepare(fd_set &, fd_set &, net::Handle &) noexcept
-{
-}
-
-/**
- * \endcond
- */
-
-/**
- * Call prepare function for every Pollable objects.
- *
- * \param in the input set
- * \param out the output set
- * \param max the maximum handle
- * \param first the first Pollable object
- * \param rest the additional Pollable objects
- */
-template <typename Pollable, typename... Rest>
-inline void prepare(fd_set &in, fd_set &out, net::Handle &max, Pollable &first, Rest&... rest)
-{
-    first.prepare(in, out, max);
-    prepare(in, out, max, rest...);
-}
-
-/**
- * \cond HIDDEN_SYMBOLS
- */
-
-inline void sync(fd_set &, fd_set &) noexcept
-{
-}
-
-/**
- * \endcond
- */
-
-/**
- * Call sync function for every Pollable objects.
- *
- * \param in the input set
- * \param out the output set
- * \param first the first Pollable object
- * \param rest the additional Pollable objects
- */
-template <typename Pollable, typename... Rest>
-inline void sync(fd_set &in, fd_set &out, Pollable &first, Rest&... rest)
-{
-    first.sync(in, out);
-    sync(in, out, rest...);
-}
-
-/**
- * Prepare and sync Pollable objects.
- *
- * \param timeout the timeout in milliseconds (< 0 means forever)
- * \param first the the first Pollable object
- * \param rest the additional Pollable objects
- */
-template <typename Pollable, typename... Rest>
-void poll(int timeout, Pollable &first, Rest&... rest)
-{
-    fd_set in, out;
-    timeval tv = { timeout / 1000, (timeout % 1000) * 1000 };
-
-    FD_ZERO(&in);
-    FD_ZERO(&out);
-
-    net::Handle max = 0;
-
-    prepare(in, out, max, first, rest...);
-
-    if (select(max + 1, &in, &out, nullptr, timeout < 0 ? nullptr : &tv) < 0 && errno != EINTR) {
-        throw std::runtime_error(std::strerror(errno));
-    } else {
-        sync(in, out, first, rest...);
-    }
-}
-
-/**
- * Parse a network message from an input buffer and remove it from it.
- *
- * \param input the buffer, will be updated
- * \return the message or empty string if there is nothing
- */
-inline std::string next_network(std::string& input)
-{
-    std::string result;
-    std::string::size_type pos = input.find("\r\n\r\n");
-
-    if ((pos = input.find("\r\n\r\n")) != std::string::npos) {
-        result = input.substr(0, pos);
-        input.erase(input.begin(), input.begin() + pos + 4);
-    }
-
-    return result;
-}
-
-} // !net_util
-
-} // !irccd
-
-#endif // !IRCCD_COMMON_NET_UTIL_HPP
--- a/libirccd-js/irccd/js/js_plugin.cpp	Fri Nov 24 20:05:15 2017 +0100
+++ b/libirccd-js/irccd/js/js_plugin.cpp	Fri Nov 24 20:09:45 2017 +0100
@@ -1,5 +1,5 @@
 /*
- * plugin-js.cpp -- Javascript plugins for irccd
+ * js_plugin.cpp -- Javascript plugins for irccd
  *
  * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
  *
@@ -24,12 +24,8 @@
 
 #include "irccd.hpp"
 #include "logger.hpp"
-//#include "plugin_module.hpp"
-//#include "server_module.hpp"
 #include "js_plugin.hpp"
-#include "service.hpp"
 #include "server_jsapi.hpp"
-//#include "timer.hpp"
 
 namespace irccd {
 
--- a/libirccd-js/irccd/js/plugin_jsapi.cpp	Fri Nov 24 20:05:15 2017 +0100
+++ b/libirccd-js/irccd/js/plugin_jsapi.cpp	Fri Nov 24 20:09:45 2017 +0100
@@ -17,7 +17,7 @@
  */
 
 #include <irccd/irccd.hpp>
-#include <irccd/service.hpp>
+#include <irccd/plugin_service.hpp>
 
 #include "irccd_jsapi.hpp"
 #include "js_plugin.hpp"
--- a/libirccd-js/irccd/js/server_jsapi.cpp	Fri Nov 24 20:05:15 2017 +0100
+++ b/libirccd-js/irccd/js/server_jsapi.cpp	Fri Nov 24 20:09:45 2017 +0100
@@ -21,8 +21,7 @@
 #include <unordered_map>
 
 #include <irccd.hpp>
-#include <irccd/server.hpp>
-#include <irccd/service.hpp>
+#include <irccd/server_service.hpp>
 
 #include "irccd_jsapi.hpp"
 #include "js_plugin.hpp"
--- a/libirccd-test/irccd/plugin_test.cpp	Fri Nov 24 20:05:15 2017 +0100
+++ b/libirccd-test/irccd/plugin_test.cpp	Fri Nov 24 20:09:45 2017 +0100
@@ -19,7 +19,8 @@
 #include <cassert>
 
 #include <irccd/logger.hpp>
-#include <irccd/service.hpp>
+#include <irccd/plugin_service.hpp>
+#include <irccd/server_service.hpp>
 
 #include <irccd/js/directory_jsapi.hpp>
 #include <irccd/js/elapsed_timer_jsapi.hpp>
--- a/libirccd/CMakeLists.txt	Fri Nov 24 20:05:15 2017 +0100
+++ b/libirccd/CMakeLists.txt	Fri Nov 24 20:09:45 2017 +0100
@@ -24,14 +24,17 @@
     HEADERS
     ${libirccd_SOURCE_DIR}/irccd/basic_transport_client.hpp
     ${libirccd_SOURCE_DIR}/irccd/command.hpp
+    ${libirccd_SOURCE_DIR}/irccd/command_service.hpp
     ${libirccd_SOURCE_DIR}/irccd/config.hpp
     ${libirccd_SOURCE_DIR}/irccd/dynlib_plugin.hpp
     ${libirccd_SOURCE_DIR}/irccd/irc.hpp
     ${libirccd_SOURCE_DIR}/irccd/irccd.hpp
     ${libirccd_SOURCE_DIR}/irccd/plugin.hpp
+    ${libirccd_SOURCE_DIR}/irccd/plugin_service.hpp
     ${libirccd_SOURCE_DIR}/irccd/rule.hpp
+    ${libirccd_SOURCE_DIR}/irccd/rule_service.hpp
     ${libirccd_SOURCE_DIR}/irccd/server.hpp
-    ${libirccd_SOURCE_DIR}/irccd/service.hpp
+    ${libirccd_SOURCE_DIR}/irccd/server_service.hpp
     ${libirccd_SOURCE_DIR}/irccd/transport_client.hpp
     ${libirccd_SOURCE_DIR}/irccd/transport_server.hpp
     ${libirccd_SOURCE_DIR}/irccd/transport_service.hpp
@@ -40,14 +43,17 @@
 set(
     SOURCES
     ${libirccd_SOURCE_DIR}/irccd/command.cpp
+    ${libirccd_SOURCE_DIR}/irccd/command_service.cpp
     ${libirccd_SOURCE_DIR}/irccd/config.cpp
     ${libirccd_SOURCE_DIR}/irccd/dynlib_plugin.cpp
     ${libirccd_SOURCE_DIR}/irccd/irc.cpp
     ${libirccd_SOURCE_DIR}/irccd/irccd.cpp
     ${libirccd_SOURCE_DIR}/irccd/plugin.cpp
+    ${libirccd_SOURCE_DIR}/irccd/plugin_service.cpp
     ${libirccd_SOURCE_DIR}/irccd/rule.cpp
+    ${libirccd_SOURCE_DIR}/irccd/rule_service.cpp
     ${libirccd_SOURCE_DIR}/irccd/server.cpp
-    ${libirccd_SOURCE_DIR}/irccd/service.cpp
+    ${libirccd_SOURCE_DIR}/irccd/server_service.cpp
     ${libirccd_SOURCE_DIR}/irccd/transport_client.cpp
     ${libirccd_SOURCE_DIR}/irccd/transport_server.cpp
     ${libirccd_SOURCE_DIR}/irccd/transport_service.cpp
--- a/libirccd/irccd/command.cpp	Fri Nov 24 20:05:15 2017 +0100
+++ b/libirccd/irccd/command.cpp	Fri Nov 24 20:09:45 2017 +0100
@@ -19,7 +19,9 @@
 #include "command.hpp"
 #include "irccd.hpp"
 #include "json_util.hpp"
-#include "service.hpp"
+#include "rule_service.hpp"
+#include "plugin_service.hpp"
+#include "server_service.hpp"
 #include "transport_client.hpp"
 #include "util.hpp"
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/command_service.cpp	Fri Nov 24 20:09:45 2017 +0100
@@ -0,0 +1,49 @@
+/*
+ * command_service.cpp -- command service
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "command_service.hpp"
+
+namespace irccd {
+
+bool command_service::contains(const std::string& name) const noexcept
+{
+    return find(name) != nullptr;
+}
+
+std::shared_ptr<command> command_service::find(const std::string& name) const noexcept
+{
+    auto it = std::find_if(commands_.begin(), commands_.end(), [&] (const auto& cmd) {
+        return cmd->name() == name;
+    });
+
+    return it == commands_.end() ? nullptr : *it;
+}
+
+void command_service::add(std::shared_ptr<command> command)
+{
+    auto it = std::find_if(commands_.begin(), commands_.end(), [&] (const auto& cmd) {
+        return cmd->name() == command->name();
+    });
+
+    if (it != commands_.end())
+        *it = std::move(command);
+    else
+        commands_.push_back(std::move(command));
+}
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/command_service.hpp	Fri Nov 24 20:09:45 2017 +0100
@@ -0,0 +1,80 @@
+/*
+ * command_service.hpp -- command service
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef IRCCD_COMMAND_SERVICE_HPP
+#define IRCCD_COMMAND_SERVICE_HPP
+
+/**
+ * \file command_service.hpp
+ * \brief Command service.
+ */
+
+#include <memory>
+#include <vector>
+
+#include "command.hpp"
+
+namespace irccd {
+
+/**
+ * \brief Store remote commands.
+ * \ingroup services
+ */
+class command_service {
+private:
+    std::vector<std::shared_ptr<command>> commands_;
+
+public:
+    /**
+     * Get all commands.
+     *
+     * \return the list of commands.
+     */
+    inline const std::vector<std::shared_ptr<command>>& commands() const noexcept
+    {
+        return commands_;
+    }
+
+    /**
+     * Tells if a command exists.
+     *
+     * \param name the command name
+     * \return true if the command exists
+     */
+    bool contains(const std::string& name) const noexcept;
+
+    /**
+     * Find a command by name.
+     *
+     * \param name the command name
+     * \return the command or empty one if not found
+     */
+    std::shared_ptr<command> find(const std::string& name) const noexcept;
+
+    /**
+     * Add a command or replace existing one.
+     *
+     * \pre command != nullptr
+     * \param command the command name
+     */
+    void add(std::shared_ptr<command> command);
+};
+
+} // !irccd
+
+#endif // !IRCCD_COMMAND_SERVICE_HPP
--- a/libirccd/irccd/config.cpp	Fri Nov 24 20:05:15 2017 +0100
+++ b/libirccd/irccd/config.cpp	Fri Nov 24 20:09:45 2017 +0100
@@ -23,10 +23,9 @@
 #include "config.hpp"
 #include "irccd.hpp"
 #include "logger.hpp"
-#include "plugin.hpp"
+#include "plugin_service.hpp"
 #include "rule.hpp"
 #include "server.hpp"
-#include "service.hpp"
 #include "string_util.hpp"
 #include "sysconfig.hpp"
 #include "system.hpp"
--- a/libirccd/irccd/irccd.cpp	Fri Nov 24 20:05:15 2017 +0100
+++ b/libirccd/irccd/irccd.cpp	Fri Nov 24 20:09:45 2017 +0100
@@ -16,12 +16,12 @@
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
 
+#include "command_service.hpp"
 #include "irccd.hpp"
-#include "logger.hpp"
-#include "net_util.hpp"
+#include "plugin_service.hpp"
+#include "rule_service.hpp"
+#include "server_service.hpp"
 #include "transport_service.hpp"
-#include "service.hpp"
-#include "util.hpp"
 
 namespace irccd {
 
@@ -29,7 +29,6 @@
     : config_(std::move(config))
     , service_(service)
     , command_service_(std::make_unique<command_service>())
-    , itr_service_(std::make_unique<interrupt_service>())
     , server_service_(std::make_unique<server_service>(*this))
     , tpt_service_(std::make_unique<transport_service>(*this))
     , rule_service_(std::make_unique<rule_service>())
@@ -39,70 +38,4 @@
 
 irccd::~irccd() = default;
 
-void irccd::post(std::function<void (irccd&)> ev) noexcept
-{
-    std::lock_guard<std::mutex> lock(mutex_);
-
-    events_.push_back(std::move(ev));
-    itr_service_->interrupt();
-}
-
-void irccd::run()
-{
-    for (;;)
-        service_.run();
-
-#if 0
-    while (running_) {
-        net_util::poll(100, *this);
-        service_.poll();
-    }
-#endif
-}
-
-void irccd::prepare(fd_set&, fd_set&, net::Handle&)
-{
-}
-
-void irccd::sync(fd_set& , fd_set& )
-{
-#if 0
-    if (!running_)
-        return;
-
-    net_util::sync(in, out, *itr_service_, *server_service_);
-
-    if (!running_)
-        return;
-
-    /*
-     * Make a copy because the events can add other events while we are
-     * iterating it. Also lock because the timers may alter these events too.
-     */
-    std::vector<std::function<void (irccd&)>> copy;
-
-    {
-        std::lock_guard<std::mutex> lock(mutex_);
-
-        copy = std::move(events_);
-        events_.clear();
-    }
-
-    if (copy.size() > 0)
-        log::debug() << "irccd: dispatching " << copy.size() << " event"
-                     << (copy.size() > 1 ? "s" : "") << std::endl;
-
-    for (auto& ev : copy)
-        ev(*this);
-#endif
-}
-
-void irccd::stop()
-{
-    log::debug() << "irccd: requesting to stop now" << std::endl;
-
-    running_ = false;
-    itr_service_->interrupt();
-}
-
 } // !irccd
--- a/libirccd/irccd/irccd.hpp	Fri Nov 24 20:05:15 2017 +0100
+++ b/libirccd/irccd/irccd.hpp	Fri Nov 24 20:09:45 2017 +0100
@@ -24,17 +24,13 @@
  * \brief Base class for irccd front end.
  */
 
-#include <atomic>
-#include <functional>
+#include "sysconfig.hpp"
+
 #include <memory>
-#include <mutex>
-#include <vector>
 
 #include <boost/asio.hpp>
 
 #include "config.hpp"
-#include "net.hpp"
-#include "sysconfig.hpp"
 
 /**
  * \brief Main irccd namespace
@@ -42,7 +38,6 @@
 namespace irccd {
 
 class command_service;
-class interrupt_service;
 class plugin_service;
 class rule_service;
 class server_service;
@@ -59,14 +54,8 @@
     // Main io service.
     boost::asio::io_service& service_;
 
-    // Main loop stuff.
-    std::atomic<bool> running_{true};
-    std::mutex mutex_;
-    std::vector<std::function<void (irccd&)>> events_;
-
     // Services.
     std::shared_ptr<command_service> command_service_;
-    std::shared_ptr<interrupt_service> itr_service_;
     std::shared_ptr<server_service> server_service_;
     std::shared_ptr<transport_service> tpt_service_;
     std::shared_ptr<rule_service> rule_service_;
@@ -184,40 +173,18 @@
     }
 
     /**
-     * Prepare the services for selection.
+     * Transient function for posting handlers.
      *
-     * \param in the input set
-     * \param out the output set
-     * \param max the maximum handle
-     */
-    void prepare(fd_set& in, fd_set& out, net::Handle& max);
-
-    /**
-     * Synchronize the services.
-     *
-     * \param in the input set
-     * \param out the output set
+     * \param h the handler
+     * \deprecated do not use
      */
-    void sync(fd_set& in, fd_set& out);
-
-    /**
-     * Add an event to the queue. This will immediately signals the event loop
-     * to interrupt itself to dispatch the pending events.
-     *
-     * \param ev the event
-     * \note Thread-safe
-     */
-    void post(std::function<void (irccd&)> ev) noexcept;
-
-    /**
-     * Loop forever by calling prepare and sync indefinitely.
-     */
-    void run();
-
-    /**
-     * Request to stop, usually from a signal.
-     */
-    void stop();
+    template <typename Handler>
+    inline void post(Handler h)
+    {
+        service_.post([this, h] () {
+            h(*this);
+        });
+    }
 };
 
 } // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/plugin_service.cpp	Fri Nov 24 20:09:45 2017 +0100
@@ -0,0 +1,198 @@
+/*
+ * plugin_service.cpp -- plugin service
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "config.hpp"
+#include "irccd.hpp"
+#include "logger.hpp"
+#include "plugin_service.hpp"
+#include "string_util.hpp"
+#include "system.hpp"
+
+namespace irccd {
+
+namespace {
+
+template <typename Map>
+Map to_map(const config& conf, const std::string& section)
+{
+    Map ret;
+
+    for (const auto& opt : conf.doc().get(section))
+        ret.emplace(opt.key(), opt.value());
+
+    return ret;
+}
+
+} // !namespace
+
+plugin_service::plugin_service(irccd& irccd) noexcept
+    : irccd_(irccd)
+{
+}
+
+plugin_service::~plugin_service()
+{
+    for (const auto& plugin : plugins_)
+        plugin->on_unload(irccd_);
+}
+
+bool plugin_service::has(const std::string& name) const noexcept
+{
+    return std::count_if(plugins_.cbegin(), plugins_.cend(), [&] (const auto& plugin) {
+        return plugin->name() == name;
+    }) > 0;
+}
+
+std::shared_ptr<plugin> plugin_service::get(const std::string& name) const noexcept
+{
+    auto it = std::find_if(plugins_.begin(), plugins_.end(), [&] (const auto& plugin) {
+        return plugin->name() == name;
+    });
+
+    if (it == plugins_.end())
+        return nullptr;
+
+    return *it;
+}
+
+std::shared_ptr<plugin> plugin_service::require(const std::string& name) const
+{
+    auto plugin = get(name);
+
+    if (!plugin)
+        throw std::invalid_argument(string_util::sprintf("plugin %s not found", name));
+
+    return plugin;
+}
+
+void plugin_service::add(std::shared_ptr<plugin> plugin)
+{
+    plugins_.push_back(std::move(plugin));
+}
+
+void plugin_service::add_loader(std::unique_ptr<plugin_loader> loader)
+{
+    loaders_.push_back(std::move(loader));
+}
+
+plugin_config plugin_service::config(const std::string& id)
+{
+    return to_map<plugin_config>(irccd_.config(), string_util::sprintf("plugin.%s", id));
+}
+
+plugin_formats plugin_service::formats(const std::string& id)
+{
+    return to_map<plugin_formats>(irccd_.config(), string_util::sprintf("format.%s", id));
+}
+
+plugin_paths plugin_service::paths(const std::string& id)
+{
+    auto defaults = to_map<plugin_paths>(irccd_.config(), "paths");
+    auto paths = to_map<plugin_paths>(irccd_.config(), string_util::sprintf("paths.%s", id));
+
+    // Fill defaults paths.
+    if (!defaults.count("cache"))
+        defaults.emplace("cache", sys::cachedir() + "/plugin/" + id);
+    if (!defaults.count("data"))
+        paths.emplace("data", sys::datadir() + "/plugin/" + id);
+    if (!defaults.count("config"))
+        paths.emplace("config", sys::sysconfigdir() + "/plugin/" + id);
+
+    // Now fill missing fields.
+    if (!paths.count("cache"))
+        paths.emplace("cache", defaults["cache"]);
+    if (!paths.count("data"))
+        paths.emplace("data", defaults["data"]);
+    if (!paths.count("config"))
+        paths.emplace("config", defaults["config"]);
+
+    return paths;
+}
+
+std::shared_ptr<plugin> plugin_service::open(const std::string& id,
+                                             const std::string& path)
+{
+    for (const auto& loader : loaders_) {
+        auto plugin = loader->open(id, path);
+
+        if (plugin)
+            return plugin;
+    }
+
+    return nullptr;
+}
+
+std::shared_ptr<plugin> plugin_service::find(const std::string& id)
+{
+    for (const auto& loader : loaders_) {
+        auto plugin = loader->find(id);
+
+        if (plugin)
+            return plugin;
+    }
+
+    return nullptr;
+}
+
+void plugin_service::load(std::string name, std::string path)
+{
+    if (has(name))
+        return;
+
+    try {
+        std::shared_ptr<plugin> plugin;
+
+        if (path.empty())
+            plugin = find(name);
+        else
+            plugin = open(name, std::move(path));
+
+        if (plugin) {
+            plugin->set_config(config(name));
+            plugin->set_formats(formats(name));
+            plugin->set_paths(paths(name));
+            plugin->on_load(irccd_);
+
+            add(std::move(plugin));
+        }
+    } catch (const std::exception& ex) {
+        log::warning(string_util::sprintf("plugin %s: %s", name, ex.what()));
+    }
+}
+
+void plugin_service::reload(const std::string& name)
+{
+    auto plugin = get(name);
+
+    if (plugin)
+        plugin->on_reload(irccd_);
+}
+
+void plugin_service::unload(const std::string& name)
+{
+    auto it = std::find_if(plugins_.begin(), plugins_.end(), [&] (const auto& plugin) {
+        return plugin->name() == name;
+    });
+
+    if (it != plugins_.end()) {
+        (*it)->on_unload(irccd_);
+        plugins_.erase(it);
+    }
+}
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/plugin_service.hpp	Fri Nov 24 20:09:45 2017 +0100
@@ -0,0 +1,184 @@
+/*
+ * plugin_service.hpp -- plugin service
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef IRCCD_PLUGIN_SERVICE_HPP
+#define IRCCD_PLUGIN_SERVICE_HPP
+
+/**
+ * \file plugin_service.hpp
+ * \brief Plugin service.
+ */
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "plugin.hpp"
+
+namespace irccd {
+
+class irccd;
+
+/**
+ * \brief Manage plugins.
+ * \ingroup services
+ */
+class plugin_service {
+private:
+    irccd& irccd_;
+    std::vector<std::shared_ptr<plugin>> plugins_;
+    std::vector<std::unique_ptr<plugin_loader>> loaders_;
+
+public:
+    /**
+     * Create the plugin service.
+     *
+     * \param irccd the irccd instance
+     */
+    plugin_service(irccd& irccd) noexcept;
+
+    /**
+     * Destroy plugins.
+     */
+    ~plugin_service();
+
+    /**
+     * Get the list of plugins.
+     *
+     * \return the list of plugins
+     */
+    inline const std::vector<std::shared_ptr<plugin>>& list() const noexcept
+    {
+        return plugins_;
+    }
+
+    /**
+     * Check if a plugin is loaded.
+     *
+     * \param name the plugin id
+     * \return true if has plugin
+     */
+    bool has(const std::string& name) const noexcept;
+
+    /**
+     * Get a loaded plugin or null if not found.
+     *
+     * \param name the plugin id
+     * \return the plugin or empty one if not found
+     */
+    std::shared_ptr<plugin> get(const std::string& name) const noexcept;
+
+    /**
+     * Find a loaded plugin.
+     *
+     * \param name the plugin id
+     * \return the plugin
+     * \throws std::out_of_range if not found
+     */
+    std::shared_ptr<plugin> require(const std::string& name) const;
+
+    /**
+     * Add the specified plugin to the registry.
+     *
+     * \pre plugin != nullptr
+     * \param plugin the plugin
+     * \note the plugin is only added to the list, no action is performed on it
+     */
+    void add(std::shared_ptr<plugin> plugin);
+
+    /**
+     * Add a loader.
+     *
+     * \param loader the loader
+     */
+    void add_loader(std::unique_ptr<plugin_loader> loader);
+
+    /**
+     * Get the configuration for the specified plugin.
+     *
+     * \return the configuration
+     */
+    plugin_config config(const std::string& id);
+
+    /**
+     * Get the formats for the specified plugin.
+     *
+     * \return the formats
+     */
+    plugin_formats formats(const std::string& id);
+
+    /**
+     * Get the paths for the specified plugin.
+     *
+     * If none is defined, return the default ones.
+     *
+     * \return the paths
+     */
+    plugin_paths paths(const std::string& id);
+
+    /**
+     * Generic function for opening the plugin at the given path.
+     *
+     * This function will search for every pluginLoader and call open() on it,
+     * the first one that success will be returned.
+     *
+     * \param id the plugin id
+     * \param path the path to the file
+     * \return the plugin or nullptr on failures
+     */
+    std::shared_ptr<plugin> open(const std::string& id,
+                                 const std::string& path);
+
+    /**
+     * Generic function for finding a plugin.
+     *
+     * \param id the plugin id
+     * \return the plugin or nullptr on failures
+     */
+    std::shared_ptr<plugin> find(const std::string& id);
+
+    /**
+     * Convenient wrapper that loads a plugin, call onLoad and add it to the
+     * registry.
+     *
+     * Any errors are printed using logger.
+     *
+     * \param name the name
+     * \param path the optional path (searched if empty)
+     */
+    void load(std::string name, std::string path = "");
+
+    /**
+     * Unload a plugin and remove it.
+     *
+     * \param name the plugin id
+     */
+    void unload(const std::string& name);
+
+    /**
+     * Reload a plugin by calling onReload.
+     *
+     * \param name the plugin name
+     * \throw std::exception on failures
+     */
+    void reload(const std::string& name);
+};
+
+} // !irccd
+
+#endif // !IRCCD_PLUGIN_SERVICE_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/rule_service.cpp	Fri Nov 24 20:09:45 2017 +0100
@@ -0,0 +1,88 @@
+/*
+ * rule_service.cpp -- rule service
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "logger.hpp"
+#include "rule_service.hpp"
+#include "string_util.hpp"
+
+namespace irccd {
+
+void rule_service::add(rule rule)
+{
+    rules_.push_back(std::move(rule));
+}
+
+void rule_service::insert(rule rule, unsigned position)
+{
+    assert(position <= rules_.size());
+
+    rules_.insert(rules_.begin() + position, std::move(rule));
+}
+
+void rule_service::remove(unsigned position)
+{
+    assert(position < rules_.size());
+
+    rules_.erase(rules_.begin() + position);
+}
+
+const rule &rule_service::require(unsigned position) const
+{
+    if (position >= rules_.size())
+        throw std::out_of_range("rule " + std::to_string(position) + " does not exist");
+
+    return rules_[position];
+}
+
+rule &rule_service::require(unsigned position)
+{
+    if (position >= rules_.size())
+        throw std::out_of_range("rule " + std::to_string(position) + " does not exist");
+
+    return rules_[position];
+}
+
+bool rule_service::solve(const std::string& server,
+                         const std::string& channel,
+                         const std::string& origin,
+                         const std::string& plugin,
+                         const std::string& event) noexcept
+{
+    bool result = true;
+
+    log::debug(string_util::sprintf("rule: solving for server=%s, channel=%s, origin=%s, plugin=%s, event=%s",
+        server, channel, origin, plugin, event));
+
+    int i = 0;
+    for (const auto& rule : rules_) {
+        log::debug() << "  candidate "   << i++ << ":\n"
+                     << "    servers: "  << string_util::join(rule.servers().begin(), rule.servers().end()) << "\n"
+                     << "    channels: " << string_util::join(rule.channels().begin(), rule.channels().end()) << "\n"
+                     << "    origins: "  << string_util::join(rule.origins().begin(), rule.origins().end()) << "\n"
+                     << "    plugins: "  << string_util::join(rule.plugins().begin(), rule.plugins().end()) << "\n"
+                     << "    events: "   << string_util::join(rule.events().begin(), rule.events().end()) << "\n"
+                     << "    action: "   << ((rule.action() == rule::action_type::accept) ? "accept" : "drop") << std::endl;
+
+        if (rule.match(server, channel, origin, plugin, event))
+            result = rule.action() == rule::action_type::accept;
+    }
+
+    return result;
+}
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/rule_service.hpp	Fri Nov 24 20:09:45 2017 +0100
@@ -0,0 +1,120 @@
+/*
+ * rule_service.hpp -- rule service
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef IRCCD_RULE_SERVICE_HPP
+#define IRCCD_RULE_SERVICE_HPP
+
+/**
+ * \file rule_service.hpp
+ * \brief Rule service.
+ */
+
+#include <vector>
+
+#include "rule.hpp"
+
+namespace irccd {
+
+/**
+ * \brief Store and solve rules.
+ * \ingroup services
+ */
+class rule_service {
+private:
+    std::vector<rule> rules_;
+
+public:
+    /**
+     * Get the list of rules.
+     *
+     * \return the list of rules
+     */
+    inline const std::vector<rule>& list() const noexcept
+    {
+        return rules_;
+    }
+
+    /**
+     * Get the number of rules.
+     *
+     * \return the number of rules
+     */
+    inline std::size_t length() const noexcept
+    {
+        return rules_.size();
+    }
+
+    /**
+     * Append a rule.
+     *
+     * \param rule the rule to append
+     */
+    void add(rule rule);
+
+    /**
+     * Insert a new rule at the specified position.
+     *
+     * \param rule the rule
+     * \param position the position
+     */
+    void insert(rule rule, unsigned position);
+
+    /**
+     * Remove a new rule from the specified position.
+     *
+     * \pre position must be valid
+     * \param position the position
+     */
+    void remove(unsigned position);
+
+    /**
+     * Get a rule at the specified index or throw an exception if not found.
+     *
+     * \param position the position
+     * \return the rule
+     * \throw std::out_of_range if position is invalid
+     */
+    const rule& require(unsigned position) const;
+
+    /**
+     * Overloaded function.
+     *
+     * \copydoc require
+     */
+    rule& require(unsigned position);
+
+    /**
+     * Resolve the action to execute with the specified list of rules.
+     *
+     * \param server the server name
+     * \param channel the channel name
+     * \param origin the origin
+     * \param plugin the plugin name
+     * \param event the event name (e.g onKick)
+     * \return true if the plugin must be called
+     */
+    bool solve(const std::string& server,
+               const std::string& channel,
+               const std::string& origin,
+               const std::string& plugin,
+               const std::string& event) noexcept;
+};
+
+} // !irccd
+
+#endif // !IRCCD_RULE_SERVICE_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/server_service.cpp	Fri Nov 24 20:09:45 2017 +0100
@@ -0,0 +1,568 @@
+/*
+ * server_service.hpp -- server service
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "irccd.hpp"
+#include "logger.hpp"
+#include "plugin_service.hpp"
+#include "rule_service.hpp"
+#include "server_service.hpp"
+#include "string_util.hpp"
+#include "transport_service.hpp"
+
+namespace irccd {
+
+class event_handler {
+public:
+    std::string server;
+    std::string origin;
+    std::string target;
+    std::function<std::string (plugin &)> function_name;
+    std::function<void (plugin &)> function_exec;
+
+    void operator()(irccd& irccd) const
+    {
+        for (auto& plugin : irccd.plugins().list()) {
+            auto eventname = function_name(*plugin);
+            auto allowed = irccd.rules().solve(server, target, origin, plugin->name(), eventname);
+
+            if (!allowed) {
+                log::debug() << "rule: event skipped on match" << std::endl;
+                continue;
+            }
+
+            log::debug() << "rule: event allowed" << std::endl;
+
+            // TODO: this is the responsability of plugin_service.
+            try {
+                function_exec(*plugin);
+            } catch (const std::exception& ex) {
+                log::warning() << "plugin " << plugin->name() << ": error: " << ex.what() << std::endl;
+            }
+        }
+    }
+};
+
+void server_service::handle_channel_mode(const channel_mode_event& ev)
+{
+    log::debug() << "server " << ev.server->name() << ": event onChannelMode:\n";
+    log::debug() << "  origin: " << ev.origin << "\n";
+    log::debug() << "  channel: " << ev.channel << "\n";
+    log::debug() << "  mode: " << ev.mode << "\n";
+    log::debug() << "  argument: " << ev.argument << std::endl;
+
+    irccd_.transports().broadcast(nlohmann::json::object({
+        { "event",      "onChannelMode"     },
+        { "server",     ev.server->name()   },
+        { "origin",     ev.origin           },
+        { "channel",    ev.channel          },
+        { "mode",       ev.mode             },
+        { "argument",   ev.argument         }
+    }));
+
+    irccd_.post(event_handler{ev.server->name(), ev.origin, ev.channel,
+        [=] (plugin&) -> std::string {
+            return "onChannelMode";
+        },
+        [=] (plugin& plugin) {
+            plugin.on_channel_mode(irccd_, ev);
+        }
+    });
+}
+
+void server_service::handle_channel_notice(const channel_notice_event& ev)
+{
+    log::debug() << "server " << ev.server->name() << ": event onChannelNotice:\n";
+    log::debug() << "  origin: " << ev.origin << "\n";
+    log::debug() << "  channel: " << ev.channel << "\n";
+    log::debug() << "  message: " << ev.message << std::endl;
+
+    irccd_.transports().broadcast(nlohmann::json::object({
+        { "event",      "onChannelNotice"   },
+        { "server",     ev.server->name()   },
+        { "origin",     ev.origin           },
+        { "channel",    ev.channel          },
+        { "message",    ev.message          }
+    }));
+
+    irccd_.post(event_handler{ev.server->name(), ev.origin, ev.channel,
+        [=] (plugin&) -> std::string {
+            return "onChannelNotice";
+        },
+        [=] (plugin& plugin) {
+            plugin.on_channel_notice(irccd_, ev);
+        }
+    });
+}
+
+void server_service::handle_connect(const connect_event& ev)
+{
+    log::debug() << "server " << ev.server->name() << ": event onConnect" << std::endl;
+
+    irccd_.transports().broadcast(nlohmann::json::object({
+        { "event",      "onConnect"         },
+        { "server",     ev.server->name()   }
+    }));
+
+    irccd_.post(event_handler{ev.server->name(), /* origin */ "", /* channel */ "",
+        [=] (plugin&) -> std::string {
+            return "onConnect";
+        },
+        [=] (plugin& plugin) {
+            plugin.on_connect(irccd_, ev);
+        }
+    });
+}
+
+void server_service::handle_invite(const invite_event& ev)
+{
+    log::debug() << "server " << ev.server->name() << ": event onInvite:\n";
+    log::debug() << "  origin: " << ev.origin << "\n";
+    log::debug() << "  channel: " << ev.channel << "\n";
+    log::debug() << "  target: " << ev.nickname << std::endl;
+
+    irccd_.transports().broadcast(nlohmann::json::object({
+        { "event",      "onInvite"          },
+        { "server",     ev.server->name()   },
+        { "origin",     ev.origin           },
+        { "channel",    ev.channel          }
+    }));
+
+    irccd_.post(event_handler{ev.server->name(), ev.origin, ev.channel,
+        [=] (plugin&) -> std::string {
+            return "onInvite";
+        },
+        [=] (plugin& plugin) {
+            plugin.on_invite(irccd_, ev);
+        }
+    });
+}
+
+void server_service::handle_join(const join_event& ev)
+{
+    log::debug() << "server " << ev.server->name() << ": event onJoin:\n";
+    log::debug() << "  origin: " << ev.origin << "\n";
+    log::debug() << "  channel: " << ev.channel << std::endl;
+
+    irccd_.transports().broadcast(nlohmann::json::object({
+        { "event",      "onJoin"            },
+        { "server",     ev.server->name()   },
+        { "origin",     ev.origin           },
+        { "channel",    ev.channel          }
+    }));
+
+    irccd_.post(event_handler{ev.server->name(), ev.origin, ev.channel,
+        [=] (plugin&) -> std::string {
+            return "onJoin";
+        },
+        [=] (plugin& plugin) {
+            plugin.on_join(irccd_, ev);
+        }
+    });
+}
+
+void server_service::handle_kick(const kick_event& ev)
+{
+    log::debug() << "server " << ev.server->name() << ": event onKick:\n";
+    log::debug() << "  origin: " << ev.origin << "\n";
+    log::debug() << "  channel: " << ev.channel << "\n";
+    log::debug() << "  target: " << ev.target << "\n";
+    log::debug() << "  reason: " << ev.reason << std::endl;
+
+    irccd_.transports().broadcast(nlohmann::json::object({
+        { "event",      "onKick"            },
+        { "server",     ev.server->name()   },
+        { "origin",     ev.origin           },
+        { "channel",    ev.channel          },
+        { "target",     ev.target           },
+        { "reason",     ev.reason           }
+    }));
+
+    irccd_.post(event_handler{ev.server->name(), ev.origin, ev.channel,
+        [=] (plugin&) -> std::string {
+            return "onKick";
+        },
+        [=] (plugin& plugin) {
+            plugin.on_kick(irccd_, ev);
+        }
+    });
+}
+
+void server_service::handle_message(const message_event& ev)
+{
+    log::debug() << "server " << ev.server->name() << ": event onMessage:\n";
+    log::debug() << "  origin: " << ev.origin << "\n";
+    log::debug() << "  channel: " << ev.channel << "\n";
+    log::debug() << "  message: " << ev.message << std::endl;
+
+    irccd_.transports().broadcast(nlohmann::json::object({
+        { "event",      "onMessage"         },
+        { "server",     ev.server->name()   },
+        { "origin",     ev.origin           },
+        { "channel",    ev.channel          },
+        { "message",    ev.message          }
+    }));
+
+    irccd_.post(event_handler{ev.server->name(), ev.origin, ev.channel,
+        [=] (plugin& plugin) -> std::string {
+            return string_util::parse_message(
+                ev.message,
+                ev.server->command_char(),
+                plugin.name()
+            ).type == string_util::message_pack::type::command ? "onCommand" : "onMessage";
+        },
+        [=] (plugin& plugin) mutable {
+            auto copy = ev;
+            auto pack = string_util::parse_message(copy.message, copy.server->command_char(), plugin.name());
+
+            copy.message = pack.message;
+
+            if (pack.type == string_util::message_pack::type::command)
+                plugin.on_command(irccd_, copy);
+            else
+                plugin.on_message(irccd_, copy);
+        }
+    });
+}
+
+void server_service::handle_me(const me_event& ev)
+{
+    log::debug() << "server " << ev.server->name() << ": event onMe:\n";
+    log::debug() << "  origin: " << ev.origin << "\n";
+    log::debug() << "  target: " << ev.channel << "\n";
+    log::debug() << "  message: " << ev.message << std::endl;
+
+    irccd_.transports().broadcast(nlohmann::json::object({
+        { "event",      "onMe"              },
+        { "server",     ev.server->name()   },
+        { "origin",     ev.origin           },
+        { "target",     ev.channel          },
+        { "message",    ev.message          }
+    }));
+
+    irccd_.post(event_handler{ev.server->name(), ev.origin, ev.channel,
+        [=] (plugin&) -> std::string {
+            return "onMe";
+        },
+        [=] (plugin& plugin) {
+            plugin.on_me(irccd_, ev);
+        }
+    });
+}
+
+void server_service::handle_mode(const mode_event& ev)
+{
+    log::debug() << "server " << ev.server->name() << ": event onMode\n";
+    log::debug() << "  origin: " << ev.origin << "\n";
+    log::debug() << "  mode: " << ev.mode << std::endl;
+
+    irccd_.transports().broadcast(nlohmann::json::object({
+        { "event",      "onMode"            },
+        { "server",     ev.server->name()   },
+        { "origin",     ev.origin           },
+        { "mode",       ev.mode             }
+    }));
+
+    irccd_.post(event_handler{ev.server->name(), ev.origin, /* channel */ "",
+        [=] (plugin &) -> std::string {
+            return "onMode";
+        },
+        [=] (plugin &plugin) {
+            plugin.on_mode(irccd_, ev);
+        }
+    });
+}
+
+void server_service::handle_names(const names_event& ev)
+{
+    log::debug() << "server " << ev.server->name() << ": event onNames:\n";
+    log::debug() << "  channel: " << ev.channel << "\n";
+    log::debug() << "  names: " << string_util::join(ev.names.begin(), ev.names.end(), ", ") << std::endl;
+
+    auto names = nlohmann::json::array();
+
+    for (const auto& v : ev.names)
+        names.push_back(v);
+
+    irccd_.transports().broadcast(nlohmann::json::object({
+        { "event",      "onNames"           },
+        { "server",     ev.server->name()   },
+        { "channel",    ev.channel          },
+        { "names",      std::move(names)    }
+    }));
+
+    irccd_.post(event_handler{ev.server->name(), /* origin */ "", ev.channel,
+        [=] (plugin&) -> std::string {
+            return "onNames";
+        },
+        [=] (plugin& plugin) {
+            plugin.on_names(irccd_, ev);
+        }
+    });
+}
+
+void server_service::handle_nick(const nick_event& ev)
+{
+    log::debug() << "server " << ev.server->name() << ": event onNick:\n";
+    log::debug() << "  origin: " << ev.origin << "\n";
+    log::debug() << "  nickname: " << ev.nickname << std::endl;
+
+    irccd_.transports().broadcast(nlohmann::json::object({
+        { "event",      "onNick"            },
+        { "server",     ev.server->name()   },
+        { "origin",     ev.origin           },
+        { "nickname",   ev.nickname         }
+    }));
+
+    irccd_.post(event_handler{ev.server->name(), ev.origin, /* channel */ "",
+        [=] (plugin&) -> std::string {
+            return "onNick";
+        },
+        [=] (plugin& plugin) {
+            plugin.on_nick(irccd_, ev);
+        }
+    });
+}
+
+void server_service::handle_notice(const notice_event& ev)
+{
+    log::debug() << "server " << ev.server->name() << ": event onNotice:\n";
+    log::debug() << "  origin: " << ev.origin << "\n";
+    log::debug() << "  message: " << ev.message << std::endl;
+
+    irccd_.transports().broadcast(nlohmann::json::object({
+        { "event",      "onNotice"          },
+        { "server",     ev.server->name()   },
+        { "origin",     ev.origin           },
+        { "message",    ev.message          }
+    }));
+
+    irccd_.post(event_handler{ev.server->name(), ev.origin, /* channel */ "",
+        [=] (plugin&) -> std::string {
+            return "onNotice";
+        },
+        [=] (plugin& plugin) {
+            plugin.on_notice(irccd_, ev);
+        }
+    });
+}
+
+void server_service::handle_part(const part_event& ev)
+{
+    log::debug() << "server " << ev.server->name() << ": event onPart:\n";
+    log::debug() << "  origin: " << ev.origin << "\n";
+    log::debug() << "  channel: " << ev.channel << "\n";
+    log::debug() << "  reason: " << ev.reason << std::endl;
+
+    irccd_.transports().broadcast(nlohmann::json::object({
+        { "event",      "onPart"            },
+        { "server",     ev.server->name()   },
+        { "origin",     ev.origin           },
+        { "channel",    ev.channel          },
+        { "reason",     ev.reason           }
+    }));
+
+    irccd_.post(event_handler{ev.server->name(), ev.origin, ev.channel,
+        [=] (plugin&) -> std::string {
+            return "onPart";
+        },
+        [=] (plugin& plugin) {
+            plugin.on_part(irccd_, ev);
+        }
+    });
+}
+
+void server_service::handle_query(const query_event& ev)
+{
+    log::debug() << "server " << ev.server->name() << ": event onQuery:\n";
+    log::debug() << "  origin: " << ev.origin << "\n";
+    log::debug() << "  message: " << ev.message << std::endl;
+
+    irccd_.transports().broadcast(nlohmann::json::object({
+        { "event",      "onQuery"           },
+        { "server",     ev.server->name()   },
+        { "origin",     ev.origin           },
+        { "message",    ev.message          }
+    }));
+
+    irccd_.post(event_handler{ev.server->name(), ev.origin, /* channel */ "",
+        [=] (plugin& plugin) -> std::string {
+            return string_util::parse_message(
+                ev.message,
+                ev.server->command_char(),
+                plugin.name()
+            ).type == string_util::message_pack::type::command ? "onQueryCommand" : "onQuery";
+        },
+        [=] (plugin& plugin) mutable {
+            auto copy = ev;
+            auto pack = string_util::parse_message(copy.message, copy.server->command_char(), plugin.name());
+
+            copy.message = pack.message;
+
+            if (pack.type == string_util::message_pack::type::command)
+                plugin.on_query_command(irccd_, copy);
+            else
+                plugin.on_query(irccd_, copy);
+        }
+    });
+}
+
+void server_service::handle_topic(const topic_event& ev)
+{
+    log::debug() << "server " << ev.server->name() << ": event onTopic:\n";
+    log::debug() << "  origin: " << ev.origin << "\n";
+    log::debug() << "  channel: " << ev.channel << "\n";
+    log::debug() << "  topic: " << ev.topic << std::endl;
+
+    irccd_.transports().broadcast(nlohmann::json::object({
+        { "event",      "onTopic"           },
+        { "server",     ev.server->name()   },
+        { "origin",     ev.origin           },
+        { "channel",    ev.channel          },
+        { "topic",      ev.topic            }
+    }));
+
+    irccd_.post(event_handler{ev.server->name(), ev.origin, ev.channel,
+        [=] (plugin&) -> std::string {
+            return "onTopic";
+        },
+        [=] (plugin& plugin) {
+            plugin.on_topic(irccd_, ev);
+        }
+    });
+}
+
+void server_service::handle_whois(const whois_event& ev)
+{
+    log::debug() << "server " << ev.server->name() << ": event onWhois\n";
+    log::debug() << "  nickname: " << ev.whois.nick << "\n";
+    log::debug() << "  username: " << ev.whois.user << "\n";
+    log::debug() << "  host: " << ev.whois.host << "\n";
+    log::debug() << "  realname: " << ev.whois.realname << "\n";
+    log::debug() << "  channels: " << string_util::join(ev.whois.channels.begin(), ev.whois.channels.end(), ", ") << std::endl;
+
+    irccd_.transports().broadcast(nlohmann::json::object({
+        { "event",      "onWhois"           },
+        { "server",     ev.server->name()   },
+        { "nickname",   ev.whois.nick       },
+        { "username",   ev.whois.user       },
+        { "host",       ev.whois.host       },
+        { "realname",   ev.whois.realname   }
+    }));
+
+    irccd_.post(event_handler{ev.server->name(), /* origin */ "", /* channel */ "",
+        [=] (plugin&) -> std::string {
+            return "onWhois";
+        },
+        [=] (plugin& plugin) {
+            plugin.on_whois(irccd_, ev);
+        }
+    });
+}
+
+server_service::server_service(irccd &irccd)
+    : irccd_(irccd)
+{
+}
+
+bool server_service::has(const std::string& name) const noexcept
+{
+    return std::count_if(servers_.cbegin(), servers_.end(), [&] (const auto& server) {
+        return server->name() == name;
+    }) > 0;
+}
+
+void server_service::add(std::shared_ptr<server> server)
+{
+    assert(!has(server->name()));
+
+    std::weak_ptr<class server> ptr(server);
+
+    server->on_channel_mode.connect(boost::bind(&server_service::handle_channel_mode, this, _1));
+    server->on_channel_notice.connect(boost::bind(&server_service::handle_channel_notice, this, _1));
+    server->on_connect.connect(boost::bind(&server_service::handle_connect, this, _1));
+    server->on_invite.connect(boost::bind(&server_service::handle_invite, this, _1));
+    server->on_join.connect(boost::bind(&server_service::handle_join, this, _1));
+    server->on_kick.connect(boost::bind(&server_service::handle_kick, this, _1));
+    server->on_message.connect(boost::bind(&server_service::handle_message, this, _1));
+    server->on_me.connect(boost::bind(&server_service::handle_me, this, _1));
+    server->on_mode.connect(boost::bind(&server_service::handle_mode, this, _1));
+    server->on_names.connect(boost::bind(&server_service::handle_names, this, _1));
+    server->on_nick.connect(boost::bind(&server_service::handle_nick, this, _1));
+    server->on_notice.connect(boost::bind(&server_service::handle_notice, this, _1));
+    server->on_part.connect(boost::bind(&server_service::handle_part, this, _1));
+    server->on_query.connect(boost::bind(&server_service::handle_query, this, _1));
+    server->on_topic.connect(boost::bind(&server_service::handle_topic, this, _1));
+    server->on_whois.connect(boost::bind(&server_service::handle_whois, this, _1));
+    server->on_die.connect([this, ptr] () {
+        irccd_.post([=] (irccd&) {
+            auto server = ptr.lock();
+
+            if (server) {
+                log::info(string_util::sprintf("server %s: removed", server->name()));
+                servers_.erase(std::find(servers_.begin(), servers_.end(), server));
+            }
+        });
+    });
+
+    server->connect();
+    servers_.push_back(std::move(server));
+}
+
+std::shared_ptr<server> server_service::get(const std::string& name) const noexcept
+{
+    auto it = std::find_if(servers_.begin(), servers_.end(), [&] (const auto& server) {
+        return server->name() == name;
+    });
+
+    if (it == servers_.end())
+        return nullptr;
+
+    return *it;
+}
+
+std::shared_ptr<server> server_service::require(const std::string& name) const
+{
+    auto server = get(name);
+
+    if (!server)
+        throw std::invalid_argument(string_util::sprintf("server %s not found", name));
+
+    return server;
+}
+
+void server_service::remove(const std::string& name)
+{
+    auto it = std::find_if(servers_.begin(), servers_.end(), [&] (const auto& server) {
+        return server->name() == name;
+    });
+
+    if (it != servers_.end()) {
+        (*it)->disconnect();
+        servers_.erase(it);
+    }
+}
+
+void server_service::clear() noexcept
+{
+    for (auto &server : servers_)
+        server->disconnect();
+
+    servers_.clear();
+}
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/server_service.hpp	Fri Nov 24 20:09:45 2017 +0100
@@ -0,0 +1,130 @@
+/*
+ * server_service.hpp -- server service
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef IRCCD_SERVER_SERVICE_HPP
+#define IRCCD_SERVER_SERVICE_HPP
+
+/**
+ * \file server_service.hpp
+ * \brief Server service.
+ */
+
+#include <memory>
+#include <vector>
+
+#include "server.hpp"
+
+namespace irccd {
+
+class irccd;
+
+/**
+ * \brief Manage IRC servers.
+ * \ingroup services
+ */
+class server_service {
+private:
+    irccd& irccd_;
+    std::vector<std::shared_ptr<server>> servers_;
+
+    void handle_channel_mode(const channel_mode_event&);
+    void handle_channel_notice(const channel_notice_event&);
+    void handle_connect(const connect_event&);
+    void handle_invite(const invite_event&);
+    void handle_join(const join_event&);
+    void handle_kick(const kick_event&);
+    void handle_message(const message_event&);
+    void handle_me(const me_event&);
+    void handle_mode(const mode_event&);
+    void handle_names(const names_event&);
+    void handle_nick(const nick_event&);
+    void handle_notice(const notice_event&);
+    void handle_part(const part_event&);
+    void handle_query(const query_event&);
+    void handle_topic(const topic_event&);
+    void handle_whois(const whois_event&);
+
+public:
+    /**
+     * Create the server service.
+     */
+    server_service(irccd& instance);
+
+    /**
+     * Get the list of servers
+     *
+     * \return the servers
+     */
+    inline const std::vector<std::shared_ptr<server>>& servers() const noexcept
+    {
+        return servers_;
+    }
+
+    /**
+     * Check if a server exists.
+     *
+     * \param name the name
+     * \return true if exists
+     */
+    bool has(const std::string& name) const noexcept;
+
+    /**
+     * Add a new server to the application.
+     *
+     * \pre hasServer must return false
+     * \param sv the server
+     */
+    void add(std::shared_ptr<server> sv);
+
+    /**
+     * Get a server or empty one if not found
+     *
+     * \param name the server name
+     * \return the server or empty one if not found
+     */
+    std::shared_ptr<server> get(const std::string& name) const noexcept;
+
+    /**
+     * Find a server by name.
+     *
+     * \param name the server name
+     * \return the server
+     * \throw std::out_of_range if the server does not exist
+     */
+    std::shared_ptr<server> require(const std::string& name) const;
+
+    /**
+     * Remove a server from the irccd instance.
+     *
+     * The server if any, will be disconnected.
+     *
+     * \param name the server name
+     */
+    void remove(const std::string& name);
+
+    /**
+     * Remove all servers.
+     *
+     * All servers will be disconnected.
+     */
+    void clear() noexcept;
+};
+
+} // !irccd
+
+#endif // !IRCCD_SERVER_SERVICE_HPP
--- a/libirccd/irccd/service.cpp	Fri Nov 24 20:05:15 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,908 +0,0 @@
-/*
- * service.cpp -- irccd services
- *
- * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#include <algorithm>
-#include <array>
-#include <functional>
-#include <stdexcept>
-
-#include "config.hpp"
-#include "irccd.hpp"
-#include "logger.hpp"
-#include "service.hpp"
-#include "string_util.hpp"
-#include "system.hpp"
-#include "transport_service.hpp"
-
-using namespace std::string_literals;
-
-namespace irccd {
-
-/*
- * command_service.
- * ------------------------------------------------------------------
- */
-
-bool command_service::contains(const std::string& name) const noexcept
-{
-    return find(name) != nullptr;
-}
-
-std::shared_ptr<command> command_service::find(const std::string& name) const noexcept
-{
-    auto it = std::find_if(commands_.begin(), commands_.end(), [&] (const auto& cmd) {
-        return cmd->name() == name;
-    });
-
-    return it == commands_.end() ? nullptr : *it;
-}
-
-void command_service::add(std::shared_ptr<command> command)
-{
-    auto it = std::find_if(commands_.begin(), commands_.end(), [&] (const auto& cmd) {
-        return cmd->name() == command->name();
-    });
-
-    if (it != commands_.end())
-        *it = std::move(command);
-    else
-        commands_.push_back(std::move(command));
-}
-
-/*
- * interrupt_service.
- * ------------------------------------------------------------------
- */
-
-interrupt_service::interrupt_service()
-    : in_(AF_INET, 0)
-    , out_(AF_INET, 0)
-{
-    // Bind a socket to any port.
-    in_.set(net::option::SockReuseAddress(true));
-    in_.bind(net::ipv4::any(0));
-    in_.listen(1);
-
-    // Do the socket pair.
-    out_.connect(net::ipv4::pton("127.0.0.1", net::ipv4::port(in_.getsockname())));
-    in_ = in_.accept();
-    out_.set(net::option::SockBlockMode(false));
-}
-
-void interrupt_service::prepare(fd_set& in, fd_set&, net::Handle& max)
-{
-    FD_SET(in_.handle(), &in);
-
-    if (in_.handle() > max)
-        max = in_.handle();
-}
-
-void interrupt_service::sync(fd_set& in, fd_set&)
-{
-    if (FD_ISSET(in_.handle(), &in)) {
-        static std::array<char, 32> tmp;
-
-        try {
-            log::debug("irccd: interrupt service recv");
-            in_.recv(tmp.data(), 32);
-        } catch (const std::exception& ex) {
-            log::warning() << "irccd: interrupt service error: " << ex.what() << std::endl;
-        }
-    }
-}
-
-void interrupt_service::interrupt() noexcept
-{
-    try {
-        static char byte;
-
-        log::debug("irccd: interrupt service send");
-        out_.send(&byte, 1);
-    } catch (const std::exception& ex) {
-        log::warning() << "irccd: interrupt service error: " << ex.what() << std::endl;
-    }
-}
-
-/*
- * plugin_service.
- * ------------------------------------------------------------------
- */
-
-plugin_service::plugin_service(irccd& irccd) noexcept
-    : irccd_(irccd)
-{
-}
-
-plugin_service::~plugin_service()
-{
-    for (const auto& plugin : plugins_)
-        plugin->on_unload(irccd_);
-}
-
-bool plugin_service::has(const std::string& name) const noexcept
-{
-    return std::count_if(plugins_.cbegin(), plugins_.cend(), [&] (const auto& plugin) {
-        return plugin->name() == name;
-    }) > 0;
-}
-
-std::shared_ptr<plugin> plugin_service::get(const std::string& name) const noexcept
-{
-    auto it = std::find_if(plugins_.begin(), plugins_.end(), [&] (const auto& plugin) {
-        return plugin->name() == name;
-    });
-
-    if (it == plugins_.end())
-        return nullptr;
-
-    return *it;
-}
-
-std::shared_ptr<plugin> plugin_service::require(const std::string& name) const
-{
-    auto plugin = get(name);
-
-    if (!plugin)
-        throw std::invalid_argument(string_util::sprintf("plugin %s not found", name));
-
-    return plugin;
-}
-
-void plugin_service::add(std::shared_ptr<plugin> plugin)
-{
-    plugins_.push_back(std::move(plugin));
-}
-
-void plugin_service::add_loader(std::unique_ptr<plugin_loader> loader)
-{
-    loaders_.push_back(std::move(loader));
-}
-
-namespace {
-
-template <typename Map>
-Map to_map(const config& conf, const std::string& section)
-{
-    Map ret;
-
-    for (const auto& opt : conf.doc().get(section))
-        ret.emplace(opt.key(), opt.value());
-
-    return ret;
-}
-
-} // !namespace
-
-plugin_config plugin_service::config(const std::string& id)
-{
-    return to_map<plugin_config>(irccd_.config(), string_util::sprintf("plugin.%s", id));
-}
-
-plugin_formats plugin_service::formats(const std::string& id)
-{
-    return to_map<plugin_formats>(irccd_.config(), string_util::sprintf("format.%s", id));
-}
-
-plugin_paths plugin_service::paths(const std::string& id)
-{
-    auto defaults = to_map<plugin_paths>(irccd_.config(), "paths");
-    auto paths = to_map<plugin_paths>(irccd_.config(), string_util::sprintf("paths.%s", id));
-
-    // Fill defaults paths.
-    if (!defaults.count("cache"))
-        defaults.emplace("cache", sys::cachedir() + "/plugin/" + id);
-    if (!defaults.count("data"))
-        paths.emplace("data", sys::datadir() + "/plugin/" + id);
-    if (!defaults.count("config"))
-        paths.emplace("config", sys::sysconfigdir() + "/plugin/" + id);
-
-    // Now fill missing fields.
-    if (!paths.count("cache"))
-        paths.emplace("cache", defaults["cache"]);
-    if (!paths.count("data"))
-        paths.emplace("data", defaults["data"]);
-    if (!paths.count("config"))
-        paths.emplace("config", defaults["config"]);
-
-    return paths;
-}
-
-std::shared_ptr<plugin> plugin_service::open(const std::string& id,
-                                             const std::string& path)
-{
-    for (const auto& loader : loaders_) {
-        auto plugin = loader->open(id, path);
-
-        if (plugin)
-            return plugin;
-    }
-
-    return nullptr;
-}
-
-std::shared_ptr<plugin> plugin_service::find(const std::string& id)
-{
-    for (const auto& loader : loaders_) {
-        auto plugin = loader->find(id);
-
-        if (plugin)
-            return plugin;
-    }
-
-    return nullptr;
-}
-
-void plugin_service::load(std::string name, std::string path)
-{
-    if (has(name))
-        return;
-
-    try {
-        std::shared_ptr<plugin> plugin;
-
-        if (path.empty())
-            plugin = find(name);
-        else
-            plugin = open(name, std::move(path));
-
-        if (plugin) {
-            plugin->set_config(config(name));
-            plugin->set_formats(formats(name));
-            plugin->set_paths(paths(name));
-            plugin->on_load(irccd_);
-
-            add(std::move(plugin));
-        }
-    } catch (const std::exception& ex) {
-        log::warning(string_util::sprintf("plugin %s: %s", name, ex.what()));
-    }
-}
-
-void plugin_service::reload(const std::string& name)
-{
-    auto plugin = get(name);
-
-    if (plugin)
-        plugin->on_reload(irccd_);
-}
-
-void plugin_service::unload(const std::string& name)
-{
-    auto it = std::find_if(plugins_.begin(), plugins_.end(), [&] (const auto& plugin) {
-        return plugin->name() == name;
-    });
-
-    if (it != plugins_.end()) {
-        (*it)->on_unload(irccd_);
-        plugins_.erase(it);
-    }
-}
-
-/*
- * rule_service.
- * ------------------------------------------------------------------
- */
-
-void rule_service::add(rule rule)
-{
-    rules_.push_back(std::move(rule));
-}
-
-void rule_service::insert(rule rule, unsigned position)
-{
-    assert(position <= rules_.size());
-
-    rules_.insert(rules_.begin() + position, std::move(rule));
-}
-
-void rule_service::remove(unsigned position)
-{
-    assert(position < rules_.size());
-
-    rules_.erase(rules_.begin() + position);
-}
-
-const rule &rule_service::require(unsigned position) const
-{
-    if (position >= rules_.size())
-        throw std::out_of_range("rule " + std::to_string(position) + " does not exist");
-
-    return rules_[position];
-}
-
-rule &rule_service::require(unsigned position)
-{
-    if (position >= rules_.size())
-        throw std::out_of_range("rule " + std::to_string(position) + " does not exist");
-
-    return rules_[position];
-}
-
-bool rule_service::solve(const std::string& server,
-                         const std::string& channel,
-                         const std::string& origin,
-                         const std::string& plugin,
-                         const std::string& event) noexcept
-{
-    bool result = true;
-
-    log::debug(string_util::sprintf("rule: solving for server=%s, channel=%s, origin=%s, plugin=%s, event=%s",
-        server, channel, origin, plugin, event));
-
-    int i = 0;
-    for (const auto& rule : rules_) {
-        log::debug() << "  candidate "   << i++ << ":\n"
-                     << "    servers: "  << string_util::join(rule.servers().begin(), rule.servers().end()) << "\n"
-                     << "    channels: " << string_util::join(rule.channels().begin(), rule.channels().end()) << "\n"
-                     << "    origins: "  << string_util::join(rule.origins().begin(), rule.origins().end()) << "\n"
-                     << "    plugins: "  << string_util::join(rule.plugins().begin(), rule.plugins().end()) << "\n"
-                     << "    events: "   << string_util::join(rule.events().begin(), rule.events().end()) << "\n"
-                     << "    action: "   << ((rule.action() == rule::action_type::accept) ? "accept" : "drop") << std::endl;
-
-        if (rule.match(server, channel, origin, plugin, event))
-            result = rule.action() == rule::action_type::accept;
-    }
-
-    return result;
-}
-
-/*
- * server_service.
- * ------------------------------------------------------------------
- */
-
-class event_handler {
-public:
-    std::string server;
-    std::string origin;
-    std::string target;
-    std::function<std::string (plugin &)> function_name;
-    std::function<void (plugin &)> function_exec;
-
-    void operator()(irccd& irccd) const
-    {
-        for (auto& plugin : irccd.plugins().list()) {
-            auto eventname = function_name(*plugin);
-            auto allowed = irccd.rules().solve(server, target, origin, plugin->name(), eventname);
-
-            if (!allowed) {
-                log::debug() << "rule: event skipped on match" << std::endl;
-                continue;
-            }
-
-            log::debug() << "rule: event allowed" << std::endl;
-
-            // TODO: this is the responsability of plugin_service.
-            try {
-                function_exec(*plugin);
-            } catch (const std::exception& ex) {
-                log::warning() << "plugin " << plugin->name() << ": error: " << ex.what() << std::endl;
-            }
-        }
-    }
-};
-
-void server_service::handle_channel_mode(const channel_mode_event& ev)
-{
-    log::debug() << "server " << ev.server->name() << ": event onChannelMode:\n";
-    log::debug() << "  origin: " << ev.origin << "\n";
-    log::debug() << "  channel: " << ev.channel << "\n";
-    log::debug() << "  mode: " << ev.mode << "\n";
-    log::debug() << "  argument: " << ev.argument << std::endl;
-
-    irccd_.transports().broadcast(nlohmann::json::object({
-        { "event",      "onChannelMode"     },
-        { "server",     ev.server->name()   },
-        { "origin",     ev.origin           },
-        { "channel",    ev.channel          },
-        { "mode",       ev.mode             },
-        { "argument",   ev.argument         }
-    }));
-
-    irccd_.post(event_handler{ev.server->name(), ev.origin, ev.channel,
-        [=] (plugin&) -> std::string {
-            return "onChannelMode";
-        },
-        [=] (plugin& plugin) {
-            plugin.on_channel_mode(irccd_, ev);
-        }
-    });
-}
-
-void server_service::handle_channel_notice(const channel_notice_event& ev)
-{
-    log::debug() << "server " << ev.server->name() << ": event onChannelNotice:\n";
-    log::debug() << "  origin: " << ev.origin << "\n";
-    log::debug() << "  channel: " << ev.channel << "\n";
-    log::debug() << "  message: " << ev.message << std::endl;
-
-    irccd_.transports().broadcast(nlohmann::json::object({
-        { "event",      "onChannelNotice"   },
-        { "server",     ev.server->name()   },
-        { "origin",     ev.origin           },
-        { "channel",    ev.channel          },
-        { "message",    ev.message          }
-    }));
-
-    irccd_.post(event_handler{ev.server->name(), ev.origin, ev.channel,
-        [=] (plugin&) -> std::string {
-            return "onChannelNotice";
-        },
-        [=] (plugin& plugin) {
-            plugin.on_channel_notice(irccd_, ev);
-        }
-    });
-}
-
-void server_service::handle_connect(const connect_event& ev)
-{
-    log::debug() << "server " << ev.server->name() << ": event onConnect" << std::endl;
-
-    irccd_.transports().broadcast(nlohmann::json::object({
-        { "event",      "onConnect"         },
-        { "server",     ev.server->name()   }
-    }));
-
-    irccd_.post(event_handler{ev.server->name(), /* origin */ "", /* channel */ "",
-        [=] (plugin&) -> std::string {
-            return "onConnect";
-        },
-        [=] (plugin& plugin) {
-            plugin.on_connect(irccd_, ev);
-        }
-    });
-}
-
-void server_service::handle_invite(const invite_event& ev)
-{
-    log::debug() << "server " << ev.server->name() << ": event onInvite:\n";
-    log::debug() << "  origin: " << ev.origin << "\n";
-    log::debug() << "  channel: " << ev.channel << "\n";
-    log::debug() << "  target: " << ev.nickname << std::endl;
-
-    irccd_.transports().broadcast(nlohmann::json::object({
-        { "event",      "onInvite"          },
-        { "server",     ev.server->name()   },
-        { "origin",     ev.origin           },
-        { "channel",    ev.channel          }
-    }));
-
-    irccd_.post(event_handler{ev.server->name(), ev.origin, ev.channel,
-        [=] (plugin&) -> std::string {
-            return "onInvite";
-        },
-        [=] (plugin& plugin) {
-            plugin.on_invite(irccd_, ev);
-        }
-    });
-}
-
-void server_service::handle_join(const join_event& ev)
-{
-    log::debug() << "server " << ev.server->name() << ": event onJoin:\n";
-    log::debug() << "  origin: " << ev.origin << "\n";
-    log::debug() << "  channel: " << ev.channel << std::endl;
-
-    irccd_.transports().broadcast(nlohmann::json::object({
-        { "event",      "onJoin"            },
-        { "server",     ev.server->name()   },
-        { "origin",     ev.origin           },
-        { "channel",    ev.channel          }
-    }));
-
-    irccd_.post(event_handler{ev.server->name(), ev.origin, ev.channel,
-        [=] (plugin&) -> std::string {
-            return "onJoin";
-        },
-        [=] (plugin& plugin) {
-            plugin.on_join(irccd_, ev);
-        }
-    });
-}
-
-void server_service::handle_kick(const kick_event& ev)
-{
-    log::debug() << "server " << ev.server->name() << ": event onKick:\n";
-    log::debug() << "  origin: " << ev.origin << "\n";
-    log::debug() << "  channel: " << ev.channel << "\n";
-    log::debug() << "  target: " << ev.target << "\n";
-    log::debug() << "  reason: " << ev.reason << std::endl;
-
-    irccd_.transports().broadcast(nlohmann::json::object({
-        { "event",      "onKick"            },
-        { "server",     ev.server->name()   },
-        { "origin",     ev.origin           },
-        { "channel",    ev.channel          },
-        { "target",     ev.target           },
-        { "reason",     ev.reason           }
-    }));
-
-    irccd_.post(event_handler{ev.server->name(), ev.origin, ev.channel,
-        [=] (plugin&) -> std::string {
-            return "onKick";
-        },
-        [=] (plugin& plugin) {
-            plugin.on_kick(irccd_, ev);
-        }
-    });
-}
-
-void server_service::handle_message(const message_event& ev)
-{
-    log::debug() << "server " << ev.server->name() << ": event onMessage:\n";
-    log::debug() << "  origin: " << ev.origin << "\n";
-    log::debug() << "  channel: " << ev.channel << "\n";
-    log::debug() << "  message: " << ev.message << std::endl;
-
-    irccd_.transports().broadcast(nlohmann::json::object({
-        { "event",      "onMessage"         },
-        { "server",     ev.server->name()   },
-        { "origin",     ev.origin           },
-        { "channel",    ev.channel          },
-        { "message",    ev.message          }
-    }));
-
-    irccd_.post(event_handler{ev.server->name(), ev.origin, ev.channel,
-        [=] (plugin& plugin) -> std::string {
-            return string_util::parse_message(
-                ev.message,
-                ev.server->command_char(),
-                plugin.name()
-            ).type == string_util::message_pack::type::command ? "onCommand" : "onMessage";
-        },
-        [=] (plugin& plugin) mutable {
-            auto copy = ev;
-            auto pack = string_util::parse_message(copy.message, copy.server->command_char(), plugin.name());
-
-            copy.message = pack.message;
-
-            if (pack.type == string_util::message_pack::type::command)
-                plugin.on_command(irccd_, copy);
-            else
-                plugin.on_message(irccd_, copy);
-        }
-    });
-}
-
-void server_service::handle_me(const me_event& ev)
-{
-    log::debug() << "server " << ev.server->name() << ": event onMe:\n";
-    log::debug() << "  origin: " << ev.origin << "\n";
-    log::debug() << "  target: " << ev.channel << "\n";
-    log::debug() << "  message: " << ev.message << std::endl;
-
-    irccd_.transports().broadcast(nlohmann::json::object({
-        { "event",      "onMe"              },
-        { "server",     ev.server->name()   },
-        { "origin",     ev.origin           },
-        { "target",     ev.channel          },
-        { "message",    ev.message          }
-    }));
-
-    irccd_.post(event_handler{ev.server->name(), ev.origin, ev.channel,
-        [=] (plugin&) -> std::string {
-            return "onMe";
-        },
-        [=] (plugin& plugin) {
-            plugin.on_me(irccd_, ev);
-        }
-    });
-}
-
-void server_service::handle_mode(const mode_event& ev)
-{
-    log::debug() << "server " << ev.server->name() << ": event onMode\n";
-    log::debug() << "  origin: " << ev.origin << "\n";
-    log::debug() << "  mode: " << ev.mode << std::endl;
-
-    irccd_.transports().broadcast(nlohmann::json::object({
-        { "event",      "onMode"            },
-        { "server",     ev.server->name()   },
-        { "origin",     ev.origin           },
-        { "mode",       ev.mode             }
-    }));
-
-    irccd_.post(event_handler{ev.server->name(), ev.origin, /* channel */ "",
-        [=] (plugin &) -> std::string {
-            return "onMode";
-        },
-        [=] (plugin &plugin) {
-            plugin.on_mode(irccd_, ev);
-        }
-    });
-}
-
-void server_service::handle_names(const names_event& ev)
-{
-    log::debug() << "server " << ev.server->name() << ": event onNames:\n";
-    log::debug() << "  channel: " << ev.channel << "\n";
-    log::debug() << "  names: " << string_util::join(ev.names.begin(), ev.names.end(), ", ") << std::endl;
-
-    auto names = nlohmann::json::array();
-
-    for (const auto& v : ev.names)
-        names.push_back(v);
-
-    irccd_.transports().broadcast(nlohmann::json::object({
-        { "event",      "onNames"           },
-        { "server",     ev.server->name()   },
-        { "channel",    ev.channel          },
-        { "names",      std::move(names)    }
-    }));
-
-    irccd_.post(event_handler{ev.server->name(), /* origin */ "", ev.channel,
-        [=] (plugin&) -> std::string {
-            return "onNames";
-        },
-        [=] (plugin& plugin) {
-            plugin.on_names(irccd_, ev);
-        }
-    });
-}
-
-void server_service::handle_nick(const nick_event& ev)
-{
-    log::debug() << "server " << ev.server->name() << ": event onNick:\n";
-    log::debug() << "  origin: " << ev.origin << "\n";
-    log::debug() << "  nickname: " << ev.nickname << std::endl;
-
-    irccd_.transports().broadcast(nlohmann::json::object({
-        { "event",      "onNick"            },
-        { "server",     ev.server->name()   },
-        { "origin",     ev.origin           },
-        { "nickname",   ev.nickname         }
-    }));
-
-    irccd_.post(event_handler{ev.server->name(), ev.origin, /* channel */ "",
-        [=] (plugin&) -> std::string {
-            return "onNick";
-        },
-        [=] (plugin& plugin) {
-            plugin.on_nick(irccd_, ev);
-        }
-    });
-}
-
-void server_service::handle_notice(const notice_event& ev)
-{
-    log::debug() << "server " << ev.server->name() << ": event onNotice:\n";
-    log::debug() << "  origin: " << ev.origin << "\n";
-    log::debug() << "  message: " << ev.message << std::endl;
-
-    irccd_.transports().broadcast(nlohmann::json::object({
-        { "event",      "onNotice"          },
-        { "server",     ev.server->name()   },
-        { "origin",     ev.origin           },
-        { "message",    ev.message          }
-    }));
-
-    irccd_.post(event_handler{ev.server->name(), ev.origin, /* channel */ "",
-        [=] (plugin&) -> std::string {
-            return "onNotice";
-        },
-        [=] (plugin& plugin) {
-            plugin.on_notice(irccd_, ev);
-        }
-    });
-}
-
-void server_service::handle_part(const part_event& ev)
-{
-    log::debug() << "server " << ev.server->name() << ": event onPart:\n";
-    log::debug() << "  origin: " << ev.origin << "\n";
-    log::debug() << "  channel: " << ev.channel << "\n";
-    log::debug() << "  reason: " << ev.reason << std::endl;
-
-    irccd_.transports().broadcast(nlohmann::json::object({
-        { "event",      "onPart"            },
-        { "server",     ev.server->name()   },
-        { "origin",     ev.origin           },
-        { "channel",    ev.channel          },
-        { "reason",     ev.reason           }
-    }));
-
-    irccd_.post(event_handler{ev.server->name(), ev.origin, ev.channel,
-        [=] (plugin&) -> std::string {
-            return "onPart";
-        },
-        [=] (plugin& plugin) {
-            plugin.on_part(irccd_, ev);
-        }
-    });
-}
-
-void server_service::handle_query(const query_event& ev)
-{
-    log::debug() << "server " << ev.server->name() << ": event onQuery:\n";
-    log::debug() << "  origin: " << ev.origin << "\n";
-    log::debug() << "  message: " << ev.message << std::endl;
-
-    irccd_.transports().broadcast(nlohmann::json::object({
-        { "event",      "onQuery"           },
-        { "server",     ev.server->name()   },
-        { "origin",     ev.origin           },
-        { "message",    ev.message          }
-    }));
-
-    irccd_.post(event_handler{ev.server->name(), ev.origin, /* channel */ "",
-        [=] (plugin& plugin) -> std::string {
-            return string_util::parse_message(
-                ev.message,
-                ev.server->command_char(),
-                plugin.name()
-            ).type == string_util::message_pack::type::command ? "onQueryCommand" : "onQuery";
-        },
-        [=] (plugin& plugin) mutable {
-            auto copy = ev;
-            auto pack = string_util::parse_message(copy.message, copy.server->command_char(), plugin.name());
-
-            copy.message = pack.message;
-
-            if (pack.type == string_util::message_pack::type::command)
-                plugin.on_query_command(irccd_, copy);
-            else
-                plugin.on_query(irccd_, copy);
-        }
-    });
-}
-
-void server_service::handle_topic(const topic_event& ev)
-{
-    log::debug() << "server " << ev.server->name() << ": event onTopic:\n";
-    log::debug() << "  origin: " << ev.origin << "\n";
-    log::debug() << "  channel: " << ev.channel << "\n";
-    log::debug() << "  topic: " << ev.topic << std::endl;
-
-    irccd_.transports().broadcast(nlohmann::json::object({
-        { "event",      "onTopic"           },
-        { "server",     ev.server->name()   },
-        { "origin",     ev.origin           },
-        { "channel",    ev.channel          },
-        { "topic",      ev.topic            }
-    }));
-
-    irccd_.post(event_handler{ev.server->name(), ev.origin, ev.channel,
-        [=] (plugin&) -> std::string {
-            return "onTopic";
-        },
-        [=] (plugin& plugin) {
-            plugin.on_topic(irccd_, ev);
-        }
-    });
-}
-
-void server_service::handle_whois(const whois_event& ev)
-{
-    log::debug() << "server " << ev.server->name() << ": event onWhois\n";
-    log::debug() << "  nickname: " << ev.whois.nick << "\n";
-    log::debug() << "  username: " << ev.whois.user << "\n";
-    log::debug() << "  host: " << ev.whois.host << "\n";
-    log::debug() << "  realname: " << ev.whois.realname << "\n";
-    log::debug() << "  channels: " << string_util::join(ev.whois.channels.begin(), ev.whois.channels.end(), ", ") << std::endl;
-
-    irccd_.transports().broadcast(nlohmann::json::object({
-        { "event",      "onWhois"           },
-        { "server",     ev.server->name()   },
-        { "nickname",   ev.whois.nick       },
-        { "username",   ev.whois.user       },
-        { "host",       ev.whois.host       },
-        { "realname",   ev.whois.realname   }
-    }));
-
-    irccd_.post(event_handler{ev.server->name(), /* origin */ "", /* channel */ "",
-        [=] (plugin&) -> std::string {
-            return "onWhois";
-        },
-        [=] (plugin& plugin) {
-            plugin.on_whois(irccd_, ev);
-        }
-    });
-}
-
-server_service::server_service(irccd &irccd)
-    : irccd_(irccd)
-{
-}
-
-bool server_service::has(const std::string& name) const noexcept
-{
-    return std::count_if(servers_.cbegin(), servers_.end(), [&] (const auto& server) {
-        return server->name() == name;
-    }) > 0;
-}
-
-void server_service::add(std::shared_ptr<server> server)
-{
-    assert(!has(server->name()));
-
-    std::weak_ptr<class server> ptr(server);
-
-    server->on_channel_mode.connect(boost::bind(&server_service::handle_channel_mode, this, _1));
-    server->on_channel_notice.connect(boost::bind(&server_service::handle_channel_notice, this, _1));
-    server->on_connect.connect(boost::bind(&server_service::handle_connect, this, _1));
-    server->on_invite.connect(boost::bind(&server_service::handle_invite, this, _1));
-    server->on_join.connect(boost::bind(&server_service::handle_join, this, _1));
-    server->on_kick.connect(boost::bind(&server_service::handle_kick, this, _1));
-    server->on_message.connect(boost::bind(&server_service::handle_message, this, _1));
-    server->on_me.connect(boost::bind(&server_service::handle_me, this, _1));
-    server->on_mode.connect(boost::bind(&server_service::handle_mode, this, _1));
-    server->on_names.connect(boost::bind(&server_service::handle_names, this, _1));
-    server->on_nick.connect(boost::bind(&server_service::handle_nick, this, _1));
-    server->on_notice.connect(boost::bind(&server_service::handle_notice, this, _1));
-    server->on_part.connect(boost::bind(&server_service::handle_part, this, _1));
-    server->on_query.connect(boost::bind(&server_service::handle_query, this, _1));
-    server->on_topic.connect(boost::bind(&server_service::handle_topic, this, _1));
-    server->on_whois.connect(boost::bind(&server_service::handle_whois, this, _1));
-    server->on_die.connect([this, ptr] () {
-        irccd_.post([=] (irccd&) {
-            auto server = ptr.lock();
-
-            if (server) {
-                log::info(string_util::sprintf("server %s: removed", server->name()));
-                servers_.erase(std::find(servers_.begin(), servers_.end(), server));
-            }
-        });
-    });
-
-    server->connect();
-    servers_.push_back(std::move(server));
-}
-
-std::shared_ptr<server> server_service::get(const std::string& name) const noexcept
-{
-    auto it = std::find_if(servers_.begin(), servers_.end(), [&] (const auto& server) {
-        return server->name() == name;
-    });
-
-    if (it == servers_.end())
-        return nullptr;
-
-    return *it;
-}
-
-std::shared_ptr<server> server_service::require(const std::string& name) const
-{
-    auto server = get(name);
-
-    if (!server)
-        throw std::invalid_argument(string_util::sprintf("server %s not found", name));
-
-    return server;
-}
-
-void server_service::remove(const std::string& name)
-{
-    auto it = std::find_if(servers_.begin(), servers_.end(), [&] (const auto& server) {
-        return server->name() == name;
-    });
-
-    if (it != servers_.end()) {
-        (*it)->disconnect();
-        servers_.erase(it);
-    }
-}
-
-void server_service::clear() noexcept
-{
-    for (auto &server : servers_)
-        server->disconnect();
-
-    servers_.clear();
-}
-
-} // !irccd
--- a/libirccd/irccd/service.hpp	Fri Nov 24 20:05:15 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,468 +0,0 @@
-/*
- * service.hpp -- irccd services
- *
- * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#ifndef IRCCD_SERVICE_HPP
-#define IRCCD_SERVICE_HPP
-
-/**
- * \file service.hpp
- * \brief irccd services.
- */
-
-#include <memory>
-#include <unordered_map>
-#include <vector>
-
-#include <json.hpp>
-
-#include "command.hpp"
-#include "net.hpp"
-#include "plugin.hpp"
-#include "rule.hpp"
-#include "server.hpp"
-#include "sysconfig.hpp"
-
-namespace irccd {
-
-/*
- * command_service.
- * ------------------------------------------------------------------
- */
-
-/**
- * \brief Store remote commands.
- * \ingroup services
- */
-class command_service {
-private:
-    std::vector<std::shared_ptr<command>> commands_;
-
-public:
-    /**
-     * Get all commands.
-     *
-     * \return the list of commands.
-     */
-    inline const std::vector<std::shared_ptr<command>>& commands() const noexcept
-    {
-        return commands_;
-    }
-
-    /**
-     * Tells if a command exists.
-     *
-     * \param name the command name
-     * \return true if the command exists
-     */
-    bool contains(const std::string& name) const noexcept;
-
-    /**
-     * Find a command by name.
-     *
-     * \param name the command name
-     * \return the command or empty one if not found
-     */
-    std::shared_ptr<command> find(const std::string& name) const noexcept;
-
-    /**
-     * Add a command or replace existing one.
-     *
-     * \pre command != nullptr
-     * \param command the command name
-     */
-    void add(std::shared_ptr<command> command);
-};
-
-/*
- * interrupt_service.
- * ------------------------------------------------------------------
- */
-
-/**
- * \brief Interrupt irccd event loop.
- * \ingroup services
- */
-class interrupt_service {
-private:
-    net::TcpSocket in_;
-    net::TcpSocket out_;
-
-public:
-    /**
-     * Prepare the socket pair.
-     *
-     * \throw std::runtime_error on errors
-     */
-    interrupt_service();
-
-    /**
-     * \copydoc Service::prepare
-     */
-    void prepare(fd_set& in, fd_set& out, net::Handle& max);
-
-    /**
-     * \copydoc Service::sync
-     */
-    void sync(fd_set& in, fd_set& out);
-
-    /**
-     * Request interruption.
-     */
-    void interrupt() noexcept;
-};
-
-/*
- * plugin_service.
- * ------------------------------------------------------------------
- */
-
-/**
- * \brief Manage plugins.
- * \ingroup services
- */
-class plugin_service {
-private:
-    irccd& irccd_;
-    std::vector<std::shared_ptr<plugin>> plugins_;
-    std::vector<std::unique_ptr<plugin_loader>> loaders_;
-
-public:
-    /**
-     * Create the plugin service.
-     *
-     * \param irccd the irccd instance
-     */
-    plugin_service(irccd& irccd) noexcept;
-
-    /**
-     * Destroy plugins.
-     */
-    ~plugin_service();
-
-    /**
-     * Get the list of plugins.
-     *
-     * \return the list of plugins
-     */
-    inline const std::vector<std::shared_ptr<plugin>>& list() const noexcept
-    {
-        return plugins_;
-    }
-
-    /**
-     * Check if a plugin is loaded.
-     *
-     * \param name the plugin id
-     * \return true if has plugin
-     */
-    bool has(const std::string& name) const noexcept;
-
-    /**
-     * Get a loaded plugin or null if not found.
-     *
-     * \param name the plugin id
-     * \return the plugin or empty one if not found
-     */
-    std::shared_ptr<plugin> get(const std::string& name) const noexcept;
-
-    /**
-     * Find a loaded plugin.
-     *
-     * \param name the plugin id
-     * \return the plugin
-     * \throws std::out_of_range if not found
-     */
-    std::shared_ptr<plugin> require(const std::string& name) const;
-
-    /**
-     * Add the specified plugin to the registry.
-     *
-     * \pre plugin != nullptr
-     * \param plugin the plugin
-     * \note the plugin is only added to the list, no action is performed on it
-     */
-    void add(std::shared_ptr<plugin> plugin);
-
-    /**
-     * Add a loader.
-     *
-     * \param loader the loader
-     */
-    void add_loader(std::unique_ptr<plugin_loader> loader);
-
-    /**
-     * Get the configuration for the specified plugin.
-     *
-     * \return the configuration
-     */
-    plugin_config config(const std::string& id);
-
-    /**
-     * Get the formats for the specified plugin.
-     *
-     * \return the formats
-     */
-    plugin_formats formats(const std::string& id);
-
-    /**
-     * Get the paths for the specified plugin.
-     *
-     * If none is defined, return the default ones.
-     *
-     * \return the paths
-     */
-    plugin_paths paths(const std::string& id);
-
-    /**
-     * Generic function for opening the plugin at the given path.
-     *
-     * This function will search for every pluginLoader and call open() on it,
-     * the first one that success will be returned.
-     *
-     * \param id the plugin id
-     * \param path the path to the file
-     * \return the plugin or nullptr on failures
-     */
-    std::shared_ptr<plugin> open(const std::string& id,
-                                 const std::string& path);
-
-    /**
-     * Generic function for finding a plugin.
-     *
-     * \param id the plugin id
-     * \return the plugin or nullptr on failures
-     */
-    std::shared_ptr<plugin> find(const std::string& id);
-
-    /**
-     * Convenient wrapper that loads a plugin, call onLoad and add it to the
-     * registry.
-     *
-     * Any errors are printed using logger.
-     *
-     * \param name the name
-     * \param path the optional path (searched if empty)
-     */
-    void load(std::string name, std::string path = "");
-
-    /**
-     * Unload a plugin and remove it.
-     *
-     * \param name the plugin id
-     */
-    void unload(const std::string& name);
-
-    /**
-     * Reload a plugin by calling onReload.
-     *
-     * \param name the plugin name
-     * \throw std::exception on failures
-     */
-    void reload(const std::string& name);
-};
-
-/*
- * rule_service.
- * ------------------------------------------------------------------
- */
-
-/**
- * \brief Store and solve rules.
- * \ingroup services
- */
-class rule_service {
-private:
-    std::vector<rule> rules_;
-
-public:
-    /**
-     * Get the list of rules.
-     *
-     * \return the list of rules
-     */
-    inline const std::vector<rule>& list() const noexcept
-    {
-        return rules_;
-    }
-
-    /**
-     * Get the number of rules.
-     *
-     * \return the number of rules
-     */
-    inline std::size_t length() const noexcept
-    {
-        return rules_.size();
-    }
-
-    /**
-     * Append a rule.
-     *
-     * \param rule the rule to append
-     */
-    void add(rule rule);
-
-    /**
-     * Insert a new rule at the specified position.
-     *
-     * \param rule the rule
-     * \param position the position
-     */
-    void insert(rule rule, unsigned position);
-
-    /**
-     * Remove a new rule from the specified position.
-     *
-     * \pre position must be valid
-     * \param position the position
-     */
-    void remove(unsigned position);
-
-    /**
-     * Get a rule at the specified index or throw an exception if not found.
-     *
-     * \param position the position
-     * \return the rule
-     * \throw std::out_of_range if position is invalid
-     */
-    const rule& require(unsigned position) const;
-
-    /**
-     * Overloaded function.
-     *
-     * \copydoc require
-     */
-    rule& require(unsigned position);
-
-    /**
-     * Resolve the action to execute with the specified list of rules.
-     *
-     * \param server the server name
-     * \param channel the channel name
-     * \param origin the origin
-     * \param plugin the plugin name
-     * \param event the event name (e.g onKick)
-     * \return true if the plugin must be called
-     */
-    bool solve(const std::string& server,
-               const std::string& channel,
-               const std::string& origin,
-               const std::string& plugin,
-               const std::string& event) noexcept;
-};
-
-/*
- * server_service.
- * ------------------------------------------------------------------
- */
-
-/**
- * \brief Manage IRC servers.
- * \ingroup services
- */
-class server_service {
-private:
-    irccd& irccd_;
-    std::vector<std::shared_ptr<server>> servers_;
-
-    void handle_channel_mode(const channel_mode_event&);
-    void handle_channel_notice(const channel_notice_event&);
-    void handle_connect(const connect_event&);
-    void handle_invite(const invite_event&);
-    void handle_join(const join_event&);
-    void handle_kick(const kick_event&);
-    void handle_message(const message_event&);
-    void handle_me(const me_event&);
-    void handle_mode(const mode_event&);
-    void handle_names(const names_event&);
-    void handle_nick(const nick_event&);
-    void handle_notice(const notice_event&);
-    void handle_part(const part_event&);
-    void handle_query(const query_event&);
-    void handle_topic(const topic_event&);
-    void handle_whois(const whois_event&);
-
-public:
-    /**
-     * Create the server service.
-     */
-    server_service(irccd& instance);
-
-    /**
-     * Get the list of servers
-     *
-     * \return the servers
-     */
-    inline const std::vector<std::shared_ptr<server>>& servers() const noexcept
-    {
-        return servers_;
-    }
-
-    /**
-     * Check if a server exists.
-     *
-     * \param name the name
-     * \return true if exists
-     */
-    bool has(const std::string& name) const noexcept;
-
-    /**
-     * Add a new server to the application.
-     *
-     * \pre hasServer must return false
-     * \param sv the server
-     */
-    void add(std::shared_ptr<server> sv);
-
-    /**
-     * Get a server or empty one if not found
-     *
-     * \param name the server name
-     * \return the server or empty one if not found
-     */
-    std::shared_ptr<server> get(const std::string& name) const noexcept;
-
-    /**
-     * Find a server by name.
-     *
-     * \param name the server name
-     * \return the server
-     * \throw std::out_of_range if the server does not exist
-     */
-    std::shared_ptr<server> require(const std::string& name) const;
-
-    /**
-     * Remove a server from the irccd instance.
-     *
-     * The server if any, will be disconnected.
-     *
-     * \param name the server name
-     */
-    void remove(const std::string& name);
-
-    /**
-     * Remove all servers.
-     *
-     * All servers will be disconnected.
-     */
-    void clear() noexcept;
-};
-
-} // !irccd
-
-#endif // !IRCCD_SERVICE_HPP
--- a/libirccd/irccd/transport_service.cpp	Fri Nov 24 20:05:15 2017 +0100
+++ b/libirccd/irccd/transport_service.cpp	Fri Nov 24 20:09:45 2017 +0100
@@ -18,10 +18,9 @@
 
 #include <cassert>
 
-#include "command.hpp"
+#include "command_service.hpp"
 #include "irccd.hpp"
 #include "logger.hpp"
-#include "service.hpp"
 #include "transport_client.hpp"
 #include "transport_server.hpp"
 #include "transport_service.hpp"
--- a/tests/js-plugin/main.cpp	Fri Nov 24 20:05:15 2017 +0100
+++ b/tests/js-plugin/main.cpp	Fri Nov 24 20:09:45 2017 +0100
@@ -21,7 +21,7 @@
 #include <boost/test/unit_test.hpp>
 
 #include <irccd/irccd.hpp>
-#include <irccd/service.hpp>
+#include <irccd/plugin_service.hpp>
 
 #include <irccd/js/irccd_jsapi.hpp>
 #include <irccd/js/js_plugin.hpp>
--- a/tests/plugin-ask/main.cpp	Fri Nov 24 20:05:15 2017 +0100
+++ b/tests/plugin-ask/main.cpp	Fri Nov 24 20:09:45 2017 +0100
@@ -21,7 +21,6 @@
 
 #include <irccd/irccd.hpp>
 #include <irccd/server.hpp>
-#include <irccd/service.hpp>
 
 #include "plugin_test.hpp"
 
--- a/tests/plugin-auth/main.cpp	Fri Nov 24 20:05:15 2017 +0100
+++ b/tests/plugin-auth/main.cpp	Fri Nov 24 20:09:45 2017 +0100
@@ -21,7 +21,6 @@
 
 #include <irccd/irccd.hpp>
 #include <irccd/server.hpp>
-#include <irccd/service.hpp>
 
 #include "plugin_test.hpp"
 
--- a/tests/plugin-hangman/main.cpp	Fri Nov 24 20:05:15 2017 +0100
+++ b/tests/plugin-hangman/main.cpp	Fri Nov 24 20:09:45 2017 +0100
@@ -24,7 +24,6 @@
 
 #include <irccd/irccd.hpp>
 #include <irccd/server.hpp>
-#include <irccd/service.hpp>
 
 #include "plugin_test.hpp"
 
--- a/tests/plugin-history/main.cpp	Fri Nov 24 20:05:15 2017 +0100
+++ b/tests/plugin-history/main.cpp	Fri Nov 24 20:09:45 2017 +0100
@@ -23,7 +23,6 @@
 
 #include <irccd/irccd.hpp>
 #include <irccd/server.hpp>
-#include <irccd/service.hpp>
 
 #include "plugin_test.hpp"
 
--- a/tests/plugin-logger/main.cpp	Fri Nov 24 20:05:15 2017 +0100
+++ b/tests/plugin-logger/main.cpp	Fri Nov 24 20:09:45 2017 +0100
@@ -25,7 +25,6 @@
 #include <irccd/irccd.hpp>
 #include <irccd/logger.hpp>
 #include <irccd/server.hpp>
-#include <irccd/service.hpp>
 
 #include "plugin_test.hpp"
 
--- a/tests/plugin-plugin/main.cpp	Fri Nov 24 20:05:15 2017 +0100
+++ b/tests/plugin-plugin/main.cpp	Fri Nov 24 20:09:45 2017 +0100
@@ -21,8 +21,8 @@
 
 #include <irccd/irccd.hpp>
 #include <irccd/logger.hpp>
+#include <irccd/plugin_service.hpp>
 #include <irccd/server.hpp>
-#include <irccd/service.hpp>
 #include <irccd/string_util.hpp>
 
 #include "plugin_test.hpp"
--- a/tests/rules/main.cpp	Fri Nov 24 20:05:15 2017 +0100
+++ b/tests/rules/main.cpp	Fri Nov 24 20:09:45 2017 +0100
@@ -20,8 +20,7 @@
 #include <boost/test/unit_test.hpp>
 
 #include <irccd/logger.hpp>
-#include <irccd/rule.hpp>
-#include <irccd/service.hpp>
+#include <irccd/rule_service.hpp>
 
 namespace irccd {