changeset 534:2326a4dc39e6

Irccd: rewrite transports in Boost.Asio, closes #681
author David Demelier <markand@malikania.fr>
date Fri, 17 Nov 2017 20:56:10 +0100
parents d5b6dd7c2311
children 7e9bf74e0fd5
files irccd/main.cpp libirccd-test/irccd/js_test.hpp libirccd-test/irccd/plugin_test.hpp libirccd/CMakeLists.txt libirccd/irccd/command.cpp libirccd/irccd/config.cpp libirccd/irccd/config.hpp libirccd/irccd/irccd.cpp libirccd/irccd/irccd.hpp libirccd/irccd/service.cpp libirccd/irccd/service.hpp libirccd/irccd/transport.cpp libirccd/irccd/transport.hpp libirccd/irccd/transport_client.cpp libirccd/irccd/transport_client.hpp libirccd/irccd/transport_server.cpp libirccd/irccd/transport_server.hpp tests/js-plugin/main.cpp
diffstat 18 files changed, 1098 insertions(+), 1007 deletions(-) [+]
line wrap: on
line diff
--- a/irccd/main.cpp	Fri Nov 17 19:12:32 2017 +0100
+++ b/irccd/main.cpp	Fri Nov 17 20:56:10 2017 +0100
@@ -271,7 +271,7 @@
     load_foreground(config.is_foreground(), options);
 
     // [transport]
-    for (const auto& transport : config.load_transports())
+    for (const auto& transport : config.load_transports(*instance))
         instance->transports().add(transport);
 
     // [server] section.
@@ -296,9 +296,11 @@
 
     init(argc, argv);
 
-    option::result options = parse(argc, argv);
+    boost::asio::io_service service;
 
-    instance = std::make_unique<class irccd>();
+    auto options = parse(argc, argv);
+
+    instance = std::make_unique<class irccd>(service);
     instance->commands().add(std::make_unique<plugin_config_command>());
     instance->commands().add(std::make_unique<plugin_info_command>());
     instance->commands().add(std::make_unique<plugin_list_command>());
--- a/libirccd-test/irccd/js_test.hpp	Fri Nov 17 19:12:32 2017 +0100
+++ b/libirccd-test/irccd/js_test.hpp	Fri Nov 17 20:56:10 2017 +0100
@@ -24,6 +24,8 @@
  * \brief Test fixture helper for Javascript modules.
  */
 
+#include <boost/asio.hpp>
+
 #include <irccd/irccd.hpp>
 
 #include <irccd/js/js_plugin.hpp>
@@ -53,7 +55,8 @@
     }
 
 public:
-    irccd irccd_;                               //!< Irccd instance.
+    boost::asio::io_service service_;
+    irccd irccd_{service_};                     //!< Irccd instance.
     std::shared_ptr<js_plugin> plugin_;         //!< Javascript plugin.
     std::shared_ptr<journal_server> server_;    //!< A journal server.
 
--- a/libirccd-test/irccd/plugin_test.hpp	Fri Nov 17 19:12:32 2017 +0100
+++ b/libirccd-test/irccd/plugin_test.hpp	Fri Nov 17 20:56:10 2017 +0100
@@ -24,6 +24,8 @@
  * \brief test fixture helper for Javascript plugins.
  */
 
+#include <boost/asio/io_service.hpp>
+
 #include <js/js_plugin.hpp>
 
 #include "irccd.hpp"
@@ -38,7 +40,8 @@
  */
 class plugin_test {
 protected:
-    irccd irccd_;
+    boost::asio::io_service service_;
+    irccd irccd_{service_};
     std::shared_ptr<js_plugin> plugin_;
     std::shared_ptr<journal_server> server_;
 
--- a/libirccd/CMakeLists.txt	Fri Nov 17 19:12:32 2017 +0100
+++ b/libirccd/CMakeLists.txt	Fri Nov 17 20:56:10 2017 +0100
@@ -30,7 +30,8 @@
     ${libirccd_SOURCE_DIR}/irccd/rule.hpp
     ${libirccd_SOURCE_DIR}/irccd/server.hpp
     ${libirccd_SOURCE_DIR}/irccd/service.hpp
-    ${libirccd_SOURCE_DIR}/irccd/transport.hpp
+    ${libirccd_SOURCE_DIR}/irccd/transport_client.hpp
+    ${libirccd_SOURCE_DIR}/irccd/transport_server.hpp
 )
 
 set(
@@ -43,7 +44,8 @@
     ${libirccd_SOURCE_DIR}/irccd/rule.cpp
     ${libirccd_SOURCE_DIR}/irccd/server.cpp
     ${libirccd_SOURCE_DIR}/irccd/service.cpp
-    ${libirccd_SOURCE_DIR}/irccd/transport.cpp
+    ${libirccd_SOURCE_DIR}/irccd/transport_client.cpp
+    ${libirccd_SOURCE_DIR}/irccd/transport_server.cpp
 )
 
 irccd_define_library(
--- a/libirccd/irccd/command.cpp	Fri Nov 17 19:12:32 2017 +0100
+++ b/libirccd/irccd/command.cpp	Fri Nov 17 20:56:10 2017 +0100
@@ -20,7 +20,7 @@
 #include "irccd.hpp"
 #include "json_util.hpp"
 #include "service.hpp"
-#include "transport.hpp"
+#include "transport_client.hpp"
 #include "util.hpp"
 
 using namespace std::string_literals;
@@ -66,8 +66,9 @@
      *
      * It's easier for the client to iterate over all.
      */
-    client.success("plugin-config", {
-        { "variables", variables }
+    client.send({
+        { "command",    "plugin-config" },
+        { "variables",  variables       }
     });
 }
 
@@ -161,7 +162,8 @@
 {
     auto plugin = irccd.plugins().require(json_util::require_identifier(args, "plugin"));
 
-    client.success("plugin-info", {
+    client.send({
+        { "command",    "plugin-info"       },
         { "author",     plugin->author()    },
         { "license",    plugin->license()   },
         { "summary",    plugin->summary()   },
@@ -181,8 +183,9 @@
     for (const auto& plugin : irccd.plugins().list())
         list += plugin->name();
 
-    client.success("plugin-list", {
-        { "list", list }
+    client.send({
+        { "command",    "plugin-list"   },
+        { "list",       list            }
     });
 }
 
@@ -292,6 +295,7 @@
     auto server = irccd.servers().require(json_util::require_identifier(args, "server"));
 
     // General stuff.
+    response.push_back({"command", "server-info"});
     response.push_back({"name", server->name()});
     response.push_back({"host", server->host()});
     response.push_back({"port", server->port()});
@@ -308,7 +312,7 @@
     if (server->flags() & server::ssl_verify)
         response.push_back({"sslVerify", true});
 
-    client.success("server-info", response);
+    client.send(response);
 }
 
 server_invite_command::server_invite_command()
@@ -367,8 +371,10 @@
     for (const auto& server : irccd.servers().servers())
         list.push_back(server->name());
 
-    json.push_back({"list", std::move(list)});
-    client.success("server-list", json);
+    client.send({
+        { "command",    "server-list"   },
+        { "list",       std::move(json) }
+    });
 }
 
 server_me_command::server_me_command()
@@ -547,7 +553,10 @@
     for (const auto& rule : irccd.rules().list())
         array.push_back(to_json(rule));
 
-    client.success("rule-list", {{ "list", std::move(array) }});
+    client.send({
+        { "command",    "rule-list"         },
+        { "list",       std::move(array)    }
+    });
 }
 
 rule_info_command::rule_info_command()
@@ -557,7 +566,10 @@
 
 void rule_info_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args)
 {
-    client.success("rule-info", to_json(irccd.rules().require(json_util::require_uint(args, "index"))));
+    auto json = to_json(irccd.rules().require(json_util::require_uint(args, "index")));
+
+    json.push_back({"command", "rule-info"});
+    client.send(std::move(json));
 }
 
 rule_remove_command::rule_remove_command()
--- a/libirccd/irccd/config.cpp	Fri Nov 17 19:12:32 2017 +0100
+++ b/libirccd/irccd/config.cpp	Fri Nov 17 20:56:10 2017 +0100
@@ -30,7 +30,7 @@
 #include "string_util.hpp"
 #include "sysconfig.hpp"
 #include "system.hpp"
-#include "transport.hpp"
+#include "transport_server.hpp"
 
 namespace irccd {
 
@@ -119,7 +119,7 @@
 #endif // !HAVE_SYSLOG
 }
 
-std::shared_ptr<transport_server> load_transport_ip(const ini::section& sc)
+std::shared_ptr<transport_server> load_transport_ip(boost::asio::io_service& service, const ini::section& sc)
 {
     assert(sc.key() == "transport");
 
@@ -144,7 +144,10 @@
     if ((it = sc.find("address")) != sc.end())
         address = it->value();
 
-    std::uint8_t mode = transport_server_ip::v4;
+    // 0011
+    //    ^ define IPv4
+    //   ^  define IPv6
+    auto mode = 1U;
 
     /*
      * Documentation stated family but code checked for 'domain' option.
@@ -154,16 +157,21 @@
      * See #637
      */
     if ((it = sc.find("domain")) != sc.end() || (it = sc.find("family")) != sc.end()) {
-        mode = 0;
-
         for (const auto& v : *it) {
             if (v == "ipv4")
-                mode |= transport_server_ip::v4;
+                mode |= (1U << 0);
             if (v == "ipv6")
-                mode |= transport_server_ip::v6;
+                mode |= (1U << 1);
         }
     }
 
+    if (mode == 0U)
+        throw std::invalid_argument("transport: family must at least have ipv4 or ipv6");
+
+    auto protocol = (mode & 0x2U)
+        ? boost::asio::ip::tcp::v4()
+        : boost::asio::ip::tcp::v6();
+
     // Optional SSL.
     std::string pkey;
     std::string cert;
@@ -180,21 +188,31 @@
         pkey = it->value();
     }
 
-    if (mode == 0)
-        throw std::invalid_argument("transport: family must at least have ipv4 or ipv6");
+    auto endpoint = (address == "*")
+        ? boost::asio::ip::tcp::endpoint(protocol, port)
+        : boost::asio::ip::tcp::endpoint(boost::asio::ip::address::from_string(address), port);
+
+    boost::asio::ip::tcp::acceptor acceptor(service, endpoint, true);
 
     if (pkey.empty())
-        return std::make_shared<transport_server_ip>(address, port, mode);
+        return std::make_shared<tcp_transport_server>(std::move(acceptor));
 
 #if defined(HAVE_SSL)
-    return std::make_shared<transport_server_tls>(pkey, cert, address, port, mode);
+    boost::asio::ssl::context ctx(boost::asio::ssl::context::sslv23);
+
+    ctx.use_private_key_file(pkey, boost::asio::ssl::context::pem);
+    ctx.use_certificate_file(cert, boost::asio::ssl::context::pem);
+
+    return std::make_shared<tls_transport_server>(std::move(acceptor), std::move(ctx));
 #else
     throw std::invalid_argument("transport: SSL disabled");
 #endif
 }
 
-std::shared_ptr<transport_server> load_transport_unix(const ini::section& sc)
+std::shared_ptr<transport_server> load_transport_unix(boost::asio::io_service& service, const ini::section& sc)
 {
+    using boost::asio::local::stream_protocol;
+
     assert(sc.key() == "transport");
 
 #if !defined(IRCCD_SYSTEM_WINDOWS)
@@ -203,7 +221,10 @@
     if (it == sc.end())
         throw std::invalid_argument("transport: missing 'path' parameter");
 
-    return std::make_shared<transport_server_local>(it->value());
+    stream_protocol::endpoint endpoint(it->value());
+    stream_protocol::acceptor acceptor(service, std::move(endpoint));
+
+    return std::make_shared<local_transport_server>(std::move(acceptor));
 #else
     (void)sc;
 
@@ -211,7 +232,7 @@
 #endif
 }
 
-std::shared_ptr<transport_server> load_transport(const ini::section& sc)
+std::shared_ptr<transport_server> load_transport(boost::asio::io_service& service, const ini::section& sc)
 {
     assert(sc.key() == "transport");
 
@@ -222,9 +243,9 @@
         throw std::invalid_argument("transport: missing 'type' parameter");
 
     if (it->value() == "ip")
-        transport = load_transport_ip(sc);
+        transport = load_transport_ip(service, sc);
     else if (it->value() == "unix")
-        transport = load_transport_unix(sc);
+        transport = load_transport_unix(service, sc);
     else
         throw std::invalid_argument(string_util::sprintf("transport: invalid type given: %s", it->value()));
 
@@ -472,13 +493,13 @@
     log::set_filter(std::move(filter));
 }
 
-std::vector<std::shared_ptr<transport_server>> config::load_transports() const
+std::vector<std::shared_ptr<transport_server>> config::load_transports(irccd& irccd) const
 {
     std::vector<std::shared_ptr<transport_server>> transports;
 
     for (const auto& section : document_)
         if (section.key() == "transport")
-            transports.push_back(load_transport(section));
+            transports.push_back(load_transport(irccd.service(), section));
 
     return transports;
 }
--- a/libirccd/irccd/config.hpp	Fri Nov 17 19:12:32 2017 +0100
+++ b/libirccd/irccd/config.hpp	Fri Nov 17 20:56:10 2017 +0100
@@ -146,9 +146,10 @@
     /**
      * Load transports.
      *
+     * \param irccd the irccd instance
      * \return the set of transports
      */
-    std::vector<std::shared_ptr<transport_server>> load_transports() const;
+    std::vector<std::shared_ptr<transport_server>> load_transports(irccd& irccd) const;
 
     /**
      * Load rules.
--- a/libirccd/irccd/irccd.cpp	Fri Nov 17 19:12:32 2017 +0100
+++ b/libirccd/irccd/irccd.cpp	Fri Nov 17 20:56:10 2017 +0100
@@ -24,8 +24,9 @@
 
 namespace irccd {
 
-irccd::irccd(std::string config)
+irccd::irccd(boost::asio::io_service& service, std::string config)
     : 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))
@@ -47,13 +48,15 @@
 
 void irccd::run()
 {
-    while (running_)
-        net_util::poll(250, *this);
+    while (running_) {
+        net_util::poll(100, *this);
+        service_.poll();
+    }
 }
 
 void irccd::prepare(fd_set& in, fd_set& out, net::Handle& max)
 {
-    net_util::prepare(in, out, max, *itr_service_, *server_service_, *tpt_service_);
+    net_util::prepare(in, out, max, *itr_service_, *server_service_);
 }
 
 void irccd::sync(fd_set& in, fd_set& out)
@@ -61,7 +64,7 @@
     if (!running_)
         return;
 
-    net_util::sync(in, out, *itr_service_, *server_service_, *tpt_service_);
+    net_util::sync(in, out, *itr_service_, *server_service_);
 
     if (!running_)
         return;
--- a/libirccd/irccd/irccd.hpp	Fri Nov 17 19:12:32 2017 +0100
+++ b/libirccd/irccd/irccd.hpp	Fri Nov 17 20:56:10 2017 +0100
@@ -30,6 +30,8 @@
 #include <mutex>
 #include <vector>
 
+#include <boost/asio.hpp>
+
 #include "config.hpp"
 #include "net.hpp"
 #include "sysconfig.hpp"
@@ -54,6 +56,9 @@
     // Configurations.
     class config config_;
 
+    // Main io service.
+    boost::asio::io_service& service_;
+
     // Main loop stuff.
     std::atomic<bool> running_{true};
     std::mutex mutex_;
@@ -78,9 +83,10 @@
     /**
      * Prepare standard services.
      *
+     * \param service the service
      * \param config the optional path to the configuration.
      */
-    irccd(std::string config = "");
+    irccd(boost::asio::io_service& service, std::string config = "");
 
     /**
      * Default destructor.
@@ -107,6 +113,16 @@
         config_ = std::move(cfg);
     }
 
+    inline const boost::asio::io_service& service() const noexcept
+    {
+        return service_;
+    }
+
+    inline boost::asio::io_service& service() noexcept
+    {
+        return service_;
+    }
+
     /**
      * Access the command service.
      *
--- a/libirccd/irccd/service.cpp	Fri Nov 17 19:12:32 2017 +0100
+++ b/libirccd/irccd/service.cpp	Fri Nov 17 20:56:10 2017 +0100
@@ -27,7 +27,8 @@
 #include "service.hpp"
 #include "string_util.hpp"
 #include "system.hpp"
-#include "transport.hpp"
+#include "transport_client.hpp"
+#include "transport_server.hpp"
 
 using namespace std::string_literals;
 
@@ -923,17 +924,11 @@
  * ------------------------------------------------------------------
  */
 
-void transport_service::handle_command(std::weak_ptr<transport_client> ptr, const nlohmann::json& object)
+void transport_service::handle_command(std::shared_ptr<transport_client> tc, const nlohmann::json& object)
 {
     assert(object.is_object());
 
     irccd_.post([=] (irccd&) {
-        // 0. Be sure the object still exists.
-        auto tc = ptr.lock();
-
-        if (!tc)
-            return;
-
         auto name = object.find("command");
         if (name == object.end() || !name->is_string()) {
             // TODO: send error.
@@ -955,15 +950,21 @@
     });
 }
 
-void transport_service::handle_die(std::weak_ptr<transport_client> ptr)
+void transport_service::do_accept(std::shared_ptr<transport_server> ts)
 {
-    irccd_.post([=] (irccd &) {
-        log::info("transport: client disconnected");
+    ts->accept([this, ts] (auto client, auto code) {
+        if (code) {
+            log::warning() << "transport: " << code.message() << std::endl;
+        } else {
+            client->recv([this, client] (auto json, auto code) {
+                if (code)
+                    log::warning() << "transport: " << code.message() << std::endl;
+                else
+                    handle_command(client, json);
+            });
+        }
 
-        auto tc = ptr.lock();
-
-        if (tc)
-            clients_.erase(std::find(clients_.begin(), clients_.end(), tc));
+        do_accept(ts);
     });
 }
 
@@ -972,58 +973,11 @@
 {
 }
 
-void transport_service::prepare(fd_set& in, fd_set& out, net::Handle& max)
-{
-    // Add transport servers.
-    for (const auto& transport : servers_) {
-        FD_SET(transport->handle(), &in);
-
-        if (transport->handle() > max)
-            max = transport->handle();
-    }
-
-    // Transport clients.
-    for (const auto& client : clients_)
-        client->prepare(in, out, max);
-}
-
-void transport_service::sync(fd_set& in, fd_set& out)
-{
-    // Transport clients.
-    for (const auto& client : clients_) {
-        try {
-            client->sync(in, out);
-        } catch (const std::exception& ex) {
-            log::info() << "transport: client disconnected: " << ex.what() << std::endl;
-            handle_die(client);
-        }
-    }
-
-    // Transport servers.
-    for (const auto& transport : servers_) {
-        if (!FD_ISSET(transport->handle(), &in))
-            continue;
-
-        log::debug("transport: new client connected");
-
-        std::shared_ptr<transport_client> client = transport->accept();
-        std::weak_ptr<transport_client> ptr(client);
-
-        try {
-            // Connect signals.
-            client->on_command.connect(boost::bind(&transport_service::handle_command, this, ptr, _1));
-            client->on_die.connect(boost::bind(&transport_service::handle_die, this, ptr));
-
-            // Register it.
-            clients_.push_back(std::move(client));
-        } catch (const std::exception& ex) {
-            log::info() << "transport: client disconnected: " << ex.what() << std::endl;
-        }
-    }
-}
-
 void transport_service::add(std::shared_ptr<transport_server> ts)
 {
+    assert(ts);
+
+    do_accept(ts);
     servers_.push_back(std::move(ts));
 }
 
@@ -1031,8 +985,8 @@
 {
     assert(json.is_object());
 
-    for (const auto& client : clients_)
-        if (client->state() == transport_client::state::ready)
+    for (const auto& servers : servers_)
+        for (const auto& client : servers->clients())
             client->send(json);
 }
 
--- a/libirccd/irccd/service.hpp	Fri Nov 17 19:12:32 2017 +0100
+++ b/libirccd/irccd/service.hpp	Fri Nov 17 20:56:10 2017 +0100
@@ -486,14 +486,15 @@
  * \ingroup services
  */
 class transport_service {
+public:
+    using servers_t = std::vector<std::shared_ptr<transport_server>>;
+
 private:
     irccd& irccd_;
+    servers_t servers_;
 
-    std::vector<std::shared_ptr<transport_server>> servers_;
-    std::vector<std::shared_ptr<transport_client>> clients_;
-
-    void handle_command(std::weak_ptr<transport_client>, const nlohmann::json&);
-    void handle_die(std::weak_ptr<transport_client>);
+    void handle_command(std::shared_ptr<transport_client>, const nlohmann::json&);
+    void do_accept(std::shared_ptr<transport_server>);
 
 public:
     /**
@@ -504,16 +505,6 @@
     transport_service(irccd& irccd) noexcept;
 
     /**
-     * \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);
-
-    /**
      * Add a transport server.
      *
      * \param ts the transport server
--- a/libirccd/irccd/transport.cpp	Fri Nov 17 19:12:32 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,456 +0,0 @@
-/*
- * transport.cpp -- irccd transports
- *
- * 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 <cassert>
-#include <cstdio>
-
-#include "transport.hpp"
-
-namespace irccd {
-
-/*
- * transport_client
- * ------------------------------------------------------------------
- */
-
-void transport_client::error(const std::string& msg)
-{
-    state_ = state::closing;
-
-    send({{ "error", msg }});
-}
-
-void transport_client::flush() noexcept
-{
-    for (std::size_t pos; (pos = input_.find("\r\n\r\n")) != std::string::npos; ) {
-        auto message = input_.substr(0, pos);
-
-        input_.erase(input_.begin(), input_.begin() + pos + 4);
-
-        try {
-            auto document = nlohmann::json::parse(message);
-
-            if (!document.is_object())
-                error("invalid argument");
-            else
-                on_command(document);
-        } catch (const std::exception& ex) {
-            error(ex.what());
-        }
-    }
-}
-
-void transport_client::authenticate() noexcept
-{
-    auto pos = input_.find("\r\n\r\n");
-
-    if (pos == std::string::npos)
-        return;
-
-    auto msg = input_.substr(0, pos);
-
-    input_.erase(input_.begin(), input_.begin() + pos + 4);
-
-    try {
-        auto doc = nlohmann::json::parse(msg);
-
-        if (!doc.is_object())
-            error("invalid argument");
-
-        auto cmd = doc.find("command");
-
-        if (cmd == doc.end() || !cmd->is_string() || *cmd != "auth")
-            error("authentication required");
-
-        auto pw = doc.find("password");
-        auto result = true;
-
-        if (pw == doc.end() || !pw->is_string() || *pw != parent_.password()) {
-            state_ = state::closing;
-            result = false;
-        } else
-            state_ = state::ready;
-
-        send({
-            { "response", "auth" },
-            { "result", result }
-        });
-    } catch (const std::exception& ex) {
-        error(ex.what());
-    }
-}
-
-void transport_client::recv() noexcept
-{
-    try {
-        std::string buffer;
-
-        buffer.resize(512);
-        buffer.resize(recv(&buffer[0], buffer.size()));
-
-        if (buffer.empty())
-            on_die();
-
-        input_ += std::move(buffer);
-    } catch (const std::exception &) {
-        on_die();
-    }
-}
-
-void transport_client::send() noexcept
-{
-    try {
-        auto ns = send(&output_[0], output_.size());
-
-        if (ns == 0)
-            on_die();
-
-        output_.erase(0, ns);
-    } catch (const std::exception&) {
-        on_die();
-    }
-}
-
-unsigned transport_client::recv(void* buffer, unsigned length)
-{
-    return socket_.recv(buffer, length);
-}
-
-unsigned transport_client::send(const void* buffer, unsigned length)
-{
-    return socket_.send(buffer, length);
-}
-
-transport_client::transport_client(transport_server& parent, net::TcpSocket socket)
-    : parent_(parent)
-    , socket_(std::move(socket))
-{
-    assert(socket_.isOpen());
-
-    socket_.set(net::option::SockBlockMode(false));
-
-    // Send some information.
-    auto object = nlohmann::json::object({
-        { "program",    "irccd"                 },
-        { "major",      IRCCD_VERSION_MAJOR     },
-        { "minor",      IRCCD_VERSION_MINOR     },
-        { "patch",      IRCCD_VERSION_PATCH     }
-    });
-
-#if defined(HAVE_JS)
-    object.push_back({"javascript", true});
-#endif
-#if defined(HAVE_SSL)
-    object.push_back({"ssl", true});
-#endif
-
-    send(object);
-}
-
-void transport_client::prepare(fd_set& in, fd_set& out, net::Handle& max)
-{
-    if (socket_.handle() > max)
-        max = socket_.handle();
-
-    switch (state_) {
-    case state::greeting:
-        FD_SET(socket_.handle(), &in);
-        FD_SET(socket_.handle(), &out);
-        break;
-    case state::authenticating:
-        FD_SET(socket_.handle(), &in);
-        break;
-    case state::ready:
-        FD_SET(socket_.handle(), &in);
-
-        if (!output_.empty())
-            FD_SET(socket_.handle(), &out);
-        break;
-    case state::closing:
-        if (!output_.empty())
-            FD_SET(socket_.handle(), &out);
-        else
-            on_die();
-        break;
-    default:
-        break;
-    }
-}
-
-void transport_client::sync(fd_set& in, fd_set& out)
-{
-    switch (state_) {
-    case state::greeting:
-        if (FD_ISSET(socket_.handle(), &in))
-            recv();
-        else if (FD_ISSET(socket_.handle(), &out))
-            send();
-
-        if (output_.empty())
-            state_ = parent_.password().empty() ? state::ready : state::authenticating;
-
-        break;
-    case state::authenticating:
-        if (FD_ISSET(socket_.handle(), &in))
-            recv();
-
-        authenticate();
-        break;
-    case state::ready:
-        if (FD_ISSET(socket_.handle(), &in))
-            recv();
-        if (FD_ISSET(socket_.handle(), &out))
-            send();
-
-        flush();
-        break;
-    case state::closing:
-        if (FD_ISSET(socket_.handle(), &out))
-            send();
-        break;
-    default:
-        break;
-    }
-}
-
-void transport_client::send(const nlohmann::json& json)
-{
-    assert(json.is_object());
-
-    output_ += json.dump();
-    output_ += "\r\n\r\n";
-}
-
-void transport_client::success(const std::string& cmd, nlohmann::json extra)
-{
-    assert(extra.is_object() || extra.is_null());
-
-    if (!extra.is_object())
-        extra = nlohmann::json::object();
-
-    extra["command"] = cmd;
-    extra["status"] = true;
-
-    output_ += extra.dump();
-    output_ += "\r\n\r\n";
-}
-
-void transport_client::error(const std::string& cmd, const std::string& error, nlohmann::json extra)
-{
-    assert(extra.is_object() || extra.is_null());
-
-    if (!extra.is_object())
-        extra = nlohmann::json::object();
-
-    extra["command"] = cmd;
-    extra["status"] = false;
-    extra["error"] = error;
-
-    output_ += extra.dump();
-    output_ += "\r\n\r\n";
-}
-
-/*
- * transport_client_tls
- * ------------------------------------------------------------------
- */
-
-#if defined(HAVE_SSL)
-
-void transport_client_tls::handshake()
-{
-    try {
-        ssl_.handshake();
-        handshake_ = handshake::ready;
-    } catch (const net::WantReadError&) {
-        handshake_ = handshake::read;
-    } catch (const net::WantWriteError&) {
-        handshake_ = handshake::write;
-    } catch (const std::exception&) {
-        on_die();
-    }
-}
-
-transport_client_tls::transport_client_tls(const std::string& pkey,
-                                           const std::string& cert,
-                                           transport_server& parent,
-                                           net::TcpSocket socket)
-    : transport_client(parent, std::move(socket))
-    , ssl_(socket_)
-{
-    ssl_.setPrivateKey(pkey);
-    ssl_.setCertificate(cert);
-
-    handshake();
-}
-
-unsigned transport_client_tls::recv(void* buffer, unsigned length)
-{
-    unsigned nread = 0;
-
-    try {
-        nread = ssl_.recv(buffer, length);
-    } catch (const net::WantReadError&) {
-        handshake_ = handshake::read;
-    } catch (const net::WantWriteError&) {
-        handshake_ = handshake::write;
-    } catch (const std::exception&) {
-        on_die();
-    }
-
-    return nread;
-}
-
-unsigned transport_client_tls::send(const void* buffer, unsigned length)
-{
-    unsigned nsent = 0;
-
-    try {
-        nsent = ssl_.send(buffer, length);
-    } catch (const net::WantReadError&) {
-        handshake_ = handshake::read;
-    } catch (const net::WantWriteError &) {
-        handshake_ = handshake::write;
-    } catch (const std::exception&) {
-        on_die();
-    }
-
-    return nsent;
-}
-
-void transport_client_tls::prepare(fd_set& in, fd_set& out, net::Handle& max)
-{
-    if (socket_.handle() > max)
-        max = socket_.handle();
-
-    switch (handshake_) {
-    case handshake::read:
-        FD_SET(socket_.handle(), &in);
-        break;
-    case handshake::write:
-        FD_SET(socket_.handle(), &out);
-        break;
-    default:
-        transport_client::prepare(in, out, max);
-        break;
-    }
-}
-
-void transport_client_tls::sync(fd_set& in, fd_set& out)
-{
-    switch (handshake_) {
-    case handshake::read:
-    case handshake::write:
-        handshake();
-        break;
-    default:
-        transport_client::sync(in, out);
-    }
-}
-
-#endif  // !HAVE_SSL
-
-/*
- * transport_server_ip
- * ------------------------------------------------------------------
- */
-
-transport_server_ip::transport_server_ip(const std::string& address,
-                                         std::uint16_t port,
-                                         std::uint8_t mode)
-    : transport_server(net::TcpSocket((mode & v6) ? AF_INET6 : AF_INET, 0))
-{
-    assert((mode & v6) || (mode & v4));
-
-    socket_.set(net::option::SockReuseAddress(true));
-
-    if (mode & v6) {
-        // Disable or enable IPv4 when using IPv6.
-        socket_.set(net::option::Ipv6Only(!(mode & v4)));
-
-        if (address == "*")
-            socket_.bind(net::ipv6::any(port));
-        else
-            socket_.bind(net::ipv6::pton(address, port));
-    } else {
-        if (address == "*")
-            socket_.bind(net::ipv4::any(port));
-        else
-            socket_.bind(net::ipv4::pton(address, port));
-    }
-
-    socket_.listen();
-}
-
-std::uint16_t transport_server_ip::port() const
-{
-    auto addr = socket_.getsockname();
-
-    return addr.domain() == AF_INET
-        ? ntohs(addr.as<sockaddr_in>().sin_port)
-        : ntohs(addr.as<sockaddr_in6>().sin6_port);
-}
-
-/*
- * transport_server_tls
- * ------------------------------------------------------------------
- */
-
-#if defined(HAVE_SSL)
-
-transport_server_tls::transport_server_tls(const std::string& pkey,
-                                           const std::string& cert,
-                                           const std::string& address,
-                                           std::uint16_t port,
-                                           std::uint8_t mode)
-    : transport_server_ip(address, port, mode)
-    , privatekey_(pkey)
-    , cert_(cert)
-{
-}
-
-std::unique_ptr<transport_client> transport_server_tls::accept()
-{
-    return std::make_unique<transport_client_tls>(privatekey_, cert_, *this, socket_.accept());
-}
-
-#endif  // !HAVE_SSL
-
-/*
- * transport_server_local
- * ------------------------------------------------------------------
- */
-
-#if !defined(IRCCD_SYSTEM_WINDOWS)
-
-transport_server_local::transport_server_local(std::string path)
-    : transport_server(net::TcpSocket(AF_LOCAL, 0))
-    , path_(std::move(path))
-{
-    socket_.bind(net::local::create(path_, true));
-    socket_.listen();
-}
-
-transport_server_local::~transport_server_local()
-{
-    ::remove(path_.c_str());
-}
-
-#endif
-
-} // !irccd
--- a/libirccd/irccd/transport.hpp	Fri Nov 17 19:12:32 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,424 +0,0 @@
-/*
- * transport.hpp -- irccd transports
- *
- * 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_TRANSPORT_HPP
-#define IRCCD_TRANSPORT_HPP
-
-/**
- * \file transport.hpp
- * \brief Irccd transports.
- */
-
-#include <cstdint>
-#include <memory>
-#include <string>
-
-#include <boost/signals2/signal.hpp>
-
-#include <json.hpp>
-
-#include "net.hpp"
-#include "sysconfig.hpp"
-
-namespace irccd {
-
-class transport_server;
-
-/**
- * \brief Client connected to irccd.
- *
- * This class emits a warning upon clients request through onCommand signal.
- */
-class transport_client {
-public:
-    /**
-     * \brief Client state
-     */
-    enum class state {
-        greeting,               //!< client is getting irccd info
-        authenticating,         //!< client requires authentication
-        ready,                  //!< client is ready to use
-        closing                 //!< client must disconnect
-    };
-
-    /**
-     * Signal: on_command
-     * ----------------------------------------------------------
-     *
-     * Arguments:
-     *   - the command
-     */
-    boost::signals2::signal<void (const nlohmann::json&)> on_command;
-
-    /**
-     * Signal: on_die
-     * ----------------------------------------------------------
-     *
-     * The client has disconnected.
-     */
-    boost::signals2::signal<void ()> on_die;
-
-private:
-    void error(const std::string& msg);
-    void flush() noexcept;
-    void authenticate() noexcept;
-
-protected:
-    state state_{state::greeting};      //!< current client state
-    transport_server& parent_;          //!< parent transport server
-    net::TcpSocket socket_;             //!< socket
-    std::string input_;                 //!< input buffer
-    std::string output_;                //!< output buffer
-
-    /**
-     * Fill the input buffer with available data.
-     */
-    void recv() noexcept;
-
-    /**
-     * Flush the output buffer from available pending data.
-     */
-    void send() noexcept;
-
-    /**
-     * Try to receive some data into the given buffer.
-     *
-     * \param buffer the destination buffer
-     * \param length the buffer length
-     * \return the number of bytes received
-     */
-    virtual unsigned recv(void* buffer, unsigned length);
-
-    /**
-     * Try to send some data into the given buffer.
-     *
-     * \param buffer the source buffer
-     * \param length the buffer length
-     * \return the number of bytes sent
-     */
-    virtual unsigned send(const void* buffer, unsigned length);
-
-public:
-    /**
-     * Create a transport client from the socket.
-     *
-     * \pre socket must be valid
-     * \param parent the parent server
-     * \param socket the new socket
-     */
-    transport_client(transport_server& parent, net::TcpSocket socket);
-
-    /**
-     * Virtual destructor defaulted.
-     */
-    virtual ~transport_client() = default;
-
-    /**
-     * Get the client state.
-     *
-     * \return the client state
-     */
-    inline enum state state() const noexcept
-    {
-        return state_;
-    }
-
-    /**
-     * Append some data to the output queue.
-     *
-     * \pre json.is_object()
-     * \param json the json object
-     */
-    void send(const nlohmann::json& json);
-
-    /**
-     * \copydoc Service::prepare
-     */
-    virtual void prepare(fd_set& in, fd_set& out, net::Handle& max);
-
-    /**
-     * \copydoc Service::sync
-     */
-    virtual void sync(fd_set& in, fd_set& out);
-
-    /**
-     * Send a successful command to the client with optional extra data
-     *
-     * \pre extra must be null or object
-     * \param cmd the command name
-     * \param extra the optional extra data
-     */
-    void success(const std::string& cmd, nlohmann::json extra = nullptr);
-
-    /**
-     * Send an error status to the client.
-     *
-     * \pre extra must be null or object
-     * \param cmd the command name
-     * \param error the error string
-     * \param extra the optional extra data
-     */
-    void error(const std::string& cmd,
-               const std::string& error,
-               nlohmann::json extra = nullptr);
-};
-
-/*
- * TransportClientTls
- * ------------------------------------------------------------------
- */
-
-#if defined(HAVE_SSL)
-
-/**
- * \brief TLS version of transport client.
- */
-class transport_client_tls : public transport_client {
-private:
-    enum class handshake {
-        write,
-        read,
-        ready
-    } handshake_{handshake::ready};
-
-    net::TlsSocket ssl_;
-
-    void handshake();
-
-protected:
-    /**
-     * \copydoc TransportClient::recv
-     */
-    unsigned recv(void* buffer, unsigned length) override;
-
-    /**
-     * \copydoc TransportClient::send
-     */
-    unsigned send(const void* buffer, unsigned length) override;
-
-public:
-    /**
-     * Create the transport client.
-     *
-     * \pre socket.isOpen()
-     * \param pkey the private key
-     * \param cert the certificate file
-     * \param socket the accepted socket
-     * \param parent the parent server
-     * \param socket the new socket
-     */
-    transport_client_tls(const std::string& pkey,
-                         const std::string& cert,
-                         transport_server& server,
-                         net::TcpSocket socket);
-
-    /**
-     * \copydoc TransportClient::prepare
-     */
-    void prepare(fd_set& in, fd_set& out, net::Handle& max) override;
-
-    /**
-     * \copydoc TransportClient::sync
-     */
-    void sync(fd_set& in, fd_set& out) override;
-};
-
-#endif  // !HAVE_SSL
-
-/*
- * TransportServer
- * ------------------------------------------------------------------
- */
-
-/**
- * \brief Bring networking between irccd and irccdctl.
- *
- * This class contains a master sockets for listening to TCP connections, it is
- * then processed by irccd.
- *
- * The transport class supports the following domains:
- *
- * | Domain                | Class                  |
- * |-----------------------|------------------------|
- * | IPv4, IPv6            | transport_server_ip    |
- * | Unix (not on Windows) | transport_server_local |
- *
- * Note: IPv4 and IPv6 can be combined, using TransportServer::IPv6 and its
- * option.
- */
-class transport_server {
-private:
-    transport_server(const transport_server&) = delete;
-    transport_server(transport_server&&) = delete;
-
-    transport_server& operator=(const transport_server&) = delete;
-    transport_server& operator=(transport_server&&) = delete;
-
-protected:
-    net::TcpSocket socket_;
-    std::string password_;
-
-public:
-    /**
-     * Default constructor.
-     */
-    inline transport_server(net::TcpSocket socket)
-        : socket_(std::move(socket))
-    {
-    }
-
-    /**
-     * Get the socket handle for this transport.
-     *
-     * \return the handle
-     */
-    inline net::Handle handle() const noexcept
-    {
-        return socket_.handle();
-    }
-
-    /**
-     * Get the password.
-     *
-     * \return the password
-     */
-    inline const std::string& password() const noexcept
-    {
-        return password_;
-    }
-
-    /**
-     * Set an optional password.
-     *
-     * \return the password
-     */
-    inline void set_password(std::string password) noexcept
-    {
-        password_ = std::move(password);
-    }
-
-    /**
-     * Destructor defaulted.
-     */
-    virtual ~transport_server() = default;
-
-    /**
-     * Accept a new client depending on the domain.
-     *
-     * \return the new client
-     */
-    virtual std::unique_ptr<transport_client> accept()
-    {
-        return std::make_unique<transport_client>(*this, socket_.accept());
-    }
-};
-
-/**
- * \brief Create IP transport.
- */
-class transport_server_ip : public transport_server {
-public:
-    /**
-     * \brief Domain to use.
-     */
-    enum mode {
-        v4 = (1 << 0),      //!< IPv6
-        v6 = (1 << 1)       //!< IPv4
-    };
-
-    /**
-     * Constructor.
-     * \pre mode > 0
-     * \param address the address (* for any)
-     * \param port the port number
-     * \param mode the domains to use (can be OR'ed)
-     */
-    transport_server_ip(const std::string& address,
-                        std::uint16_t port,
-                        std::uint8_t mode = v4);
-
-    /**
-     * Get the associated port.
-     *
-     * \return the port
-     */
-    std::uint16_t port() const;
-};
-
-#if defined(HAVE_SSL)
-
-/**
- * \brief TLS over IP transport.
- */
-class transport_server_tls : public transport_server_ip {
-private:
-    std::string privatekey_;
-    std::string cert_;
-
-public:
-    /**
-     * Constructor.
-     * \pre mode > 0
-     * \param pkey the private key file
-     * \param cert the certificate file
-     * \param address the address (* for any)
-     * \param port the port number
-     * \param mode the domains to use (can be OR'ed)
-     */
-    transport_server_tls(const std::string& pkey,
-                         const std::string& cert,
-                         const std::string& address,
-                         std::uint16_t port,
-                         std::uint8_t mode = mode::v4);
-
-    /**
-     * \copydoc TransportServer::accept
-     */
-    std::unique_ptr<transport_client> accept() override;
-};
-
-#endif // !HAVE_SSL
-
-#if !defined(IRCCD_SYSTEM_WINDOWS)
-
-/**
- * \brief Implementation of transports for Unix sockets.
- */
-class transport_server_local : public transport_server {
-private:
-    std::string path_;
-
-public:
-    /**
-     * Create a Unix transport.
-     *
-     * \param path the path
-     */
-    transport_server_local(std::string path);
-
-    /**
-     * Destroy the transport and remove the file.
-     */
-    ~transport_server_local();
-};
-
-#endif // !IRCCD_SYSTEM_WINDOWS
-
-} // !irccd
-
-#endif // !IRCCD_TRANSPORT_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/transport_client.cpp	Fri Nov 17 20:56:10 2017 +0100
@@ -0,0 +1,169 @@
+/*
+ * transport_client.cpp -- server side transport clients
+ *
+ * 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 <cassert>
+
+#include "transport_client.hpp"
+#include "transport_server.hpp"
+
+namespace irccd {
+
+/*
+ * transport_client::close
+ * ------------------------------------------------------------------
+ */
+void transport_client::close()
+{
+    state_ = state_t::closing;
+    output_.clear();
+    parent_.clients().erase(shared_from_this());
+}
+
+/*
+ * transport_client::flush
+ * ------------------------------------------------------------------
+ */
+void transport_client::flush()
+{
+    if (output_.empty())
+        return;
+
+    auto self = shared_from_this();
+    auto size = output_[0].first.size();
+
+    do_send(output_[0].first, [this, self, size] (auto code, auto xfer) {
+        if (output_[0].second)
+            output_[0].second(code);
+
+        output_.pop_front();
+
+        if (code || xfer != size || (output_.empty() && state_ == state_t::closing))
+            close();
+        else if (!output_.empty())
+            flush();
+    });
+}
+
+/*
+ * transport_client::recv
+ * ------------------------------------------------------------------
+ */
+void transport_client::recv(recv_t handler)
+{
+    assert(handler);
+
+    auto self = shared_from_this();
+
+    do_recv(input_, [this, self, handler] (auto code, auto xfer) {
+        if (code || xfer == 0) {
+            handler("", code);
+            close();
+            return;
+        }
+
+        std::string message(
+            boost::asio::buffers_begin(input_.data()),
+            boost::asio::buffers_begin(input_.data()) + xfer - 4
+        );
+
+        // Remove early in case of errors.
+        input_.consume(xfer);
+
+        try {
+            auto json = nlohmann::json::parse(message);
+
+            if (!json.is_object())
+                handler(nullptr, transport_error::invalid_message);
+            else
+                handler(json, code);
+        } catch (...) {
+            handler(nullptr, transport_error::invalid_message);
+        }
+    });
+}
+
+/*
+ * transport_client::send
+ * ------------------------------------------------------------------
+ */
+void transport_client::send(const nlohmann::json& data, send_t handler)
+{
+    assert(data.is_object());
+
+    if (state_ == state_t::closing)
+        return;
+
+    auto in_progress = !output_.empty();
+
+    output_.emplace_back(data.dump() + "\r\n\r\n", std::move(handler));
+
+    if (!in_progress)
+        flush();
+}
+
+/*
+ * transport_client::error
+ * ------------------------------------------------------------------
+ */
+void transport_client::error(const nlohmann::json& data, send_t handler)
+{
+    send(std::move(data), std::move(handler));
+    set_state(state_t::closing);
+}
+
+/*
+ * transport_category
+ * ------------------------------------------------------------------
+ */
+const boost::system::error_category& transport_category() noexcept
+{
+    static const class category : public boost::system::error_category {
+    public:
+        const char* name() const noexcept override
+        {
+            return "transport";
+        }
+
+        std::string message(int e) const override
+        {
+            switch (static_cast<transport_error>(e)) {
+            case transport_error::invalid_auth:
+                return "invalid authentication";
+            case transport_error::invalid_message:
+                return "invalid message";
+            case transport_error::incomplete_message:
+                return "incomplete message";
+            }
+
+            return "unknown error";
+        }
+    } cat;
+
+    return cat;
+}
+
+/*
+ * make_error_code
+ * ------------------------------------------------------------------
+ */
+boost::system::error_code make_error_code(transport_error e) noexcept
+{
+    return {static_cast<int>(e), transport_category()};
+}
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/transport_client.hpp	Fri Nov 17 20:56:10 2017 +0100
@@ -0,0 +1,391 @@
+/*
+ * transport_client.hpp -- server side transport clients
+ *
+ * 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_TRANSPORT_CLIENT_HPP
+#define IRCCD_TRANSPORT_CLIENT_HPP
+
+#include <deque>
+#include <memory>
+#include <functional>
+#include <string>
+#include <utility>
+
+#include <boost/asio.hpp>
+#include <boost/asio/ssl.hpp>
+
+#include "json.hpp"
+
+#include "errors.hpp"
+
+namespace irccd {
+
+class transport_server;
+
+/**
+ * \brief Error for transports.
+ */
+enum class transport_error : int {
+    invalid_auth = 1,       //! invalid authentication
+    invalid_message,        //! client has sent an invalid message
+    incomplete_message      //!< message requires more parameter
+};
+
+/**
+ * \brief Abstract transport client class.
+ *
+ * This class is responsible of receiving/sending data.
+ */
+class transport_client : public std::enable_shared_from_this<transport_client> {
+public:
+    /**
+     * Callback on receive operation.
+     */
+    using recv_t = std::function<void (const nlohmann::json&, const boost::system::error_code&)>;
+
+    /**
+     * Callback on send operation.
+     */
+    using send_t = std::function<void (const boost::system::error_code&)>;
+
+protected:
+    /**
+     * Handler for do_recv.
+     *
+     * The implementation should read until \r\n\r\n is found.
+     */
+    using do_recv_handler_t = std::function<void (const boost::system::error_code&, std::size_t)>;
+
+    /**
+     * Handler for do_send.
+     *
+     * The implementation must send the whole message.
+     */
+    using do_send_handler_t = std::function<void (const boost::system::error_code&, std::size_t)>;
+
+    /**
+     * Input buffer.
+     */
+    using input_t = boost::asio::streambuf;
+
+private:
+    using output_t = std::deque<std::pair<std::string, send_t>>;
+
+    enum class state_t {
+        authenticating,
+        ready,
+        closing
+    } state_{state_t::authenticating};
+
+    input_t input_;
+    output_t output_;
+    transport_server& parent_;
+
+    void close();
+    void flush();
+
+protected:
+    /**
+     * Start a read operation, the implementation must start reading without
+     * any checks.
+     *
+     * \pre handler is not null
+     * \param input the input buffer
+     * \param handler the completion handler
+     */
+    virtual void do_recv(input_t& input, do_recv_handler_t handler) = 0;
+
+    /**
+     * Start a send operation, the implementation has no checks to perform
+     * because it is already done in transport_client functions.
+     *
+     * The message buffer remains valid until completion is complete.
+     *
+     * \pre message is not empty
+     * \pre handler is not null
+     * \param message the data to send
+     * \param handler the completion handler
+     */
+    virtual void do_send(const std::string& message, do_send_handler_t handler) = 0;
+
+public:
+    /**
+     * Default constructor.
+     */
+    inline transport_client(transport_server& server) noexcept
+        : parent_(server)
+    {
+    }
+
+    /**
+     * Virtual destructor defaulted.
+     */
+    virtual ~transport_client() noexcept = default;
+
+    /**
+     * Get the transport server parent.
+     *
+     * \return the parent
+     */
+    inline const transport_server& parent() const noexcept
+    {
+        return parent_;
+    }
+
+    /**
+     * Overloaded function.
+     *
+     * \return the parent
+     */
+    inline transport_server& parent() noexcept
+    {
+        return parent_;
+    }
+
+    /**
+     * Get the current client state.
+     *
+     * \return the state
+     */
+    inline state_t state() const noexcept
+    {
+        return state_;
+    }
+
+    /**
+     * Set the client state.
+     *
+     * \param state the new state
+     */
+    inline void set_state(state_t state) noexcept
+    {
+        state_ = state;
+    }
+
+    /**
+     * Start a receive operation.
+     *
+     * \param handler the handler
+     */
+    void recv(recv_t handler);
+
+    /**
+     * Send or postpone some data to the client.
+     *
+     * If there are pending data, the operation will be ran once all other
+     * messages has been sent.
+     *
+     * \note if state is closing, no data is sent
+     * \pre data.is_object()
+     * \param data the message to send
+     * \param handler the optional completion handler
+     */
+    void send(const nlohmann::json& data, send_t handler = nullptr);
+
+    /**
+     * Convenient success message.
+     *
+     * \param cname the command name
+     * \param handler the optional handler
+     */
+    inline void success(const std::string& cname, send_t handler = nullptr)
+    {
+        assert(!cname.empty());
+
+        send({{ "command", cname }}, std::move(handler));
+    }
+
+    /**
+     * Send a error message, the state is set to closing.
+     *
+     * The invocation is similar to:
+     *
+     * ````cpp
+     * set_state(state_t::closing);
+     * send(message, handler);
+     * ````
+     *
+     * \pre message is not null
+     * \pre data.is_object()
+     * \param message the error message
+     * \param handler the handler
+     */
+    void error(const nlohmann::json& data, send_t handler = nullptr);
+
+    /**
+     * Convenient error overload.
+     *
+     * \param cname the command name
+     * \param reason the reason string
+     * \param handler the optional handler
+     */
+    inline void error(const std::string& cname, const std::string& reason, send_t handler = nullptr)
+    {
+        assert(!cname.empty());
+
+        error({
+            { "command",    cname   },
+            { "error",      reason  }
+        }, std::move(handler));
+    }
+
+    /**
+     * Convenient error overload.
+     *
+     * \param cname the command name
+     * \param reason the error code
+     * \param handler the optional handler
+     */
+    inline void error(const std::string& cname, network_error reason, send_t handler)
+    {
+        assert(!cname.empty());
+
+        error({
+            { "command",    cname                       },
+            { "error",      static_cast<int>(reason)    }
+        }, std::move(handler));
+    }
+};
+
+/**
+ * \brief Basic implementation for IP/Tcp and local sockets
+ *
+ * This class implements an recv/send function for:
+ *
+ *   - boost::asio::ip::tcp
+ *   - boost::asio::local::stream_protocol
+ *   - boost::asio::ssl::stream
+ */
+template <typename Socket>
+class basic_transport_client : public transport_client {
+protected:
+    Socket socket_;
+
+public:
+    /**
+     * Constructor.
+     *
+     * \param sock the socket
+     */
+    template <typename... Args>
+    inline basic_transport_client(transport_server& parent, Args&&... args) noexcept
+        : transport_client(parent)
+        , socket_(std::forward<Args>(args)...)
+    {
+    }
+
+    /**
+     * Get the underlying socket.
+     *
+     * \return the socket
+     */
+    inline const Socket& socket() const noexcept
+    {
+        return socket_;
+    }
+
+    /**
+     * Overloaded function.
+     *
+     * \return the socket
+     */
+    inline Socket& socket() noexcept
+    {
+        return socket_;
+    }
+
+    /**
+     * \copydoc transport_client::do_recv
+     */
+    void do_recv(input_t& input, do_recv_handler_t handler) override;
+
+    /**
+     * \copydoc transport_client::do_send
+     */
+    void do_send(const std::string& data, do_send_handler_t handler) override;
+};
+
+template <typename Socket>
+void basic_transport_client<Socket>::do_recv(input_t& input, do_recv_handler_t handler)
+{
+    auto self = shared_from_this();
+
+    boost::asio::async_read_until(socket_, input, "\r\n\r\n", [this, self, handler] (auto code, auto xfer) {
+        handler(code, xfer);
+    });
+}
+
+template <typename Socket>
+void basic_transport_client<Socket>::do_send(const std::string& data, do_send_handler_t handler)
+{
+    auto self = shared_from_this();
+
+    boost::asio::async_write(socket_, boost::asio::buffer(data), [this, self, handler] (auto code, auto xfer) {
+        handler(code, xfer);
+    });
+}
+
+/**
+ * \brief Secure layer client.
+ */
+class tls_transport_client : public basic_transport_client<boost::asio::ssl::stream<boost::asio::ip::tcp::socket>> {
+public:
+    /**
+     * Construct a secure layer client.
+     *
+     * \param parent the parent transport
+     * \param service the service to use
+     * \param context the context to reuse
+     */
+    tls_transport_client(transport_server& parent,
+                         boost::asio::io_service& service,
+                         boost::asio::ssl::context& context)
+        : basic_transport_client(parent, service, context)
+    {
+    }
+};
+
+/**
+ * Get the transport category.
+ *
+ * \return the singleton category
+ */
+const boost::system::error_category& transport_category() noexcept;
+
+/**
+ * Wrap the creation of an error_code based on transport_server::error.
+ *
+ * \param e the transport_server error code
+ * \return a boost::system::error_code with transport_category
+ */
+boost::system::error_code make_error_code(transport_error e) noexcept;
+
+} // !irccd
+
+namespace boost {
+
+namespace system {
+
+template <>
+struct is_error_code_enum<irccd::transport_error> : public std::true_type {
+};
+
+} // !system
+
+} // !boost
+
+#endif // !IRCCD_TRANSPORT_CLIENT_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/transport_server.cpp	Fri Nov 17 20:56:10 2017 +0100
@@ -0,0 +1,156 @@
+/*
+ * transport_server.cpp -- server side transports
+ *
+ * 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 <cassert>
+
+#include "transport_server.hpp"
+
+namespace irccd {
+
+/*
+ * transport_server::do_auth_check
+ * ------------------------------------------------------------------
+ */
+bool transport_server::do_auth_check(std::shared_ptr<transport_client> client,
+                                     nlohmann::json message,
+                                     accept_t handler)
+{
+    assert(client);
+    assert(handler);
+
+    auto command = message["command"];
+    auto password = message["password"];
+
+    if (!command.is_string() || !password.is_string()) {
+        handler(nullptr, transport_error::incomplete_message);
+        return false;
+    }
+
+    if (command != "auth" || password.get<std::string>() != password_) {
+        handler(nullptr, transport_error::invalid_auth);
+        return false;
+    }
+
+    return true;
+}
+
+/*
+ * transport_server::do_auth
+ * ------------------------------------------------------------------
+ */
+void transport_server::do_auth(std::shared_ptr<transport_client> client, accept_t handler)
+{
+    assert(client);
+    assert(handler);
+
+    client->recv([this, client, handler] (auto message, auto code) {
+        if (code)
+            handler(client, code);
+        if (do_auth_check(client, message, handler)) {
+            clients_.insert(client);
+            client->set_state(transport_client::state_t::ready);
+            handler(client, code);
+        }
+    });
+}
+
+/*
+ * transport_server::do_greetings
+ * ------------------------------------------------------------------
+ */
+void transport_server::do_greetings(std::shared_ptr<transport_client> client, accept_t handler)
+{
+    assert(client);
+    assert(handler);
+
+    // TODO: update this in irccd.
+    auto greetings = nlohmann::json({
+        { "irccd", "3.0.0" }
+    });
+
+    client->send(greetings, [this, client, handler] (auto code) {
+        if (code)
+            handler(client, code);
+        else if (!password_.empty())
+            do_auth(std::move(client), std::move(handler));
+        else {
+            clients_.insert(client);
+            handler(client, code);
+        }
+    });
+}
+
+/*
+ * transport_server::accept
+ * ------------------------------------------------------------------
+ */
+void transport_server::accept(accept_t handler)
+{
+    assert(handler);
+
+    do_accept([this, handler] (auto client, auto code) {
+        if (code)
+            handler(nullptr, code);
+        else
+            do_greetings(std::move(client), std::move(handler));
+    });
+}
+
+/*
+ * tls_transport_server::do_handshake
+ * ------------------------------------------------------------------
+ *
+ * Perform asynchronous SSL handshake.
+ */
+void tls_transport_server::do_handshake(std::shared_ptr<tls_transport_client> client, accept_t handler)
+{
+    client->socket().async_handshake(boost::asio::ssl::stream_base::server, [client, handler] (auto code) {
+        if (code)
+            handler(nullptr, code);
+        else
+            handler(std::move(client), std::move(code));
+    });
+}
+
+/*
+ * tls_transport_server::tls_transport_server
+ * ------------------------------------------------------------------
+ */
+tls_transport_server::tls_transport_server(acceptor_t acceptor, context_t context)
+    : tcp_transport_server(std::move(acceptor))
+    , context_(std::move(context))
+{
+}
+
+/*
+ * tls_transport_server::do_accept
+ * ------------------------------------------------------------------
+ */
+void tls_transport_server::do_accept(accept_t handler)
+{
+    auto client = std::make_shared<tls_transport_client>(*this, acceptor_.get_io_service(), context_);
+
+    acceptor_.async_accept(client->socket().lowest_layer(), [this, client, handler] (auto code) {
+        if (code)
+            handler(nullptr, code);
+        else
+            do_handshake(std::move(client), std::move(handler));
+    });
+}
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/transport_server.hpp	Fri Nov 17 20:56:10 2017 +0100
@@ -0,0 +1,244 @@
+/*
+ * transport_server.hpp -- server side transports
+ *
+ * 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_TRANSPORT_SERVER_HPP
+#define IRCCD_TRANSPORT_SERVER_HPP
+
+#include <cassert>
+#include <functional>
+#include <memory>
+#include <unordered_set>
+#include <type_traits>
+
+#include <boost/asio.hpp>
+#include <boost/asio/ssl.hpp>
+
+#include "transport_client.hpp"
+
+namespace irccd {
+
+/**
+ * \brief Abstract transport server class.
+ *
+ * This class create asynchronous operation to accept new clients.
+ */
+class transport_server {
+protected:
+    /**
+     * Set of clients.
+     */
+    using client_set_t = std::unordered_set<std::shared_ptr<transport_client>>;
+
+    /**
+     * Callback when a new client should be accepted.
+     */
+    using accept_t = std::function<void (std::shared_ptr<transport_client>, boost::system::error_code)>;
+
+private:
+    client_set_t clients_;
+    std::string password_;
+
+    bool do_auth_check(std::shared_ptr<transport_client>, nlohmann::json, accept_t);
+    void do_auth(std::shared_ptr<transport_client>, accept_t);
+    void do_greetings(std::shared_ptr<transport_client>, accept_t);
+
+protected:
+    /**
+     * Start accept operation, the implementation should not block and call
+     * the handler function on error or completion.
+     *
+     * \pre handler must not be null
+     * \param handler the handler function
+     */
+    virtual void do_accept(accept_t handler) = 0;
+
+public:
+    /**
+     * Default constructor.
+     */
+    transport_server() noexcept = default;
+
+    /**
+     * Virtual destructor defaulted.
+     */
+    virtual ~transport_server() noexcept = default;
+
+    /**
+     * Wrapper that automatically add the new client into the list.
+     *
+     * If handler is not null it is called on error or on successful accept
+     * operation.
+     *
+     * \param handler the handler
+     */
+    void accept(accept_t handler);
+
+    /**
+     * Get the clients.
+     *
+     * \return the clients
+     */
+    inline const client_set_t& clients() const noexcept
+    {
+        return clients_;
+    }
+
+    /**
+     * Overloaded function.
+     *
+     * \return the clients
+     */
+    inline client_set_t& clients() noexcept
+    {
+        return clients_;
+    }
+
+    /**
+     * Get the current password, empty string means no password.
+     *
+     * \return the password
+     */
+    inline const std::string& password() const noexcept
+    {
+        return password_;
+    }
+
+    /**
+     * Set an optional password, empty string means no password.
+     *
+     * \param password the password
+     */
+    inline void set_password(std::string password) noexcept
+    {
+        password_ = std::move(password);
+    }
+};
+
+/**
+ * \brief Basic implementation for IP/TCP and local sockets
+ *
+ * This class implements an accept function for:
+ *
+ *   - boost::asio::ip::tcp
+ *   - boost::asio::local::stream_protocol
+ */
+template <typename Protocol>
+class basic_transport_server : public transport_server {
+public:
+    /**
+     * Type for underlying socket.
+     */
+    using socket_t = typename Protocol::socket;
+
+    /**
+     * Type for underlying acceptor.
+     */
+    using acceptor_t = typename Protocol::acceptor;
+
+protected:
+    /**
+     * The acceptor object.
+     */
+    acceptor_t acceptor_;
+
+protected:
+    /**
+     * \copydoc transport_server::accept
+     */
+    void do_accept(accept_t handler) override;
+
+public:
+    /**
+     * Constructor with an acceptor in parameter.
+     *
+     * \pre acceptor.is_open()
+     * \param acceptor the already bound acceptor
+     */
+    basic_transport_server(acceptor_t acceptor);
+};
+
+template <typename Protocol>
+basic_transport_server<Protocol>::basic_transport_server(acceptor_t acceptor)
+    : acceptor_(std::move(acceptor))
+{
+    assert(acceptor_.is_open());
+}
+
+template <typename Protocol>
+void basic_transport_server<Protocol>::do_accept(accept_t handler)
+{
+    using client_type = basic_transport_client<socket_t>;
+
+    auto client = std::make_shared<client_type>(*this, acceptor_.get_io_service());
+
+    acceptor_.async_accept(client->socket(), [client, handler] (auto code) {
+        if (!code)
+            handler(std::move(client), std::move(code));
+        else
+            handler(nullptr, code);
+    });
+}
+
+/**
+ * Convenient type for IP/TCP
+ */
+using tcp_transport_server = basic_transport_server<boost::asio::ip::tcp>;
+
+#if !defined(_WIN32)
+
+/**
+ * Convenient type for UNIX local sockets.
+ */
+using local_transport_server = basic_transport_server<boost::asio::local::stream_protocol>;
+
+#endif // !_WIN32
+
+/**
+ * \brief Secure layer implementation.
+ */
+class tls_transport_server : public tcp_transport_server {
+public:
+    using context_t = boost::asio::ssl::context;
+
+private:
+    context_t context_;
+
+    void do_handshake(std::shared_ptr<tls_transport_client>, accept_t);
+
+protected:
+    /**
+     * \copydoc tcp_transport_server::do_accept
+     *
+     * This function does the same as tcp_transport_server::do_accept but it
+     * also perform a SSL handshake after a successful accept operation.
+     */
+    void do_accept(accept_t handler) override;
+
+public:
+    /**
+     * Construct a secure layer transport server.
+     *
+     * \param acceptor the acceptor
+     * \param context the SSL context
+     */
+    tls_transport_server(acceptor_t acceptor, context_t context);
+};
+
+} // !irccd
+
+#endif // !IRCCD_TRANSPORT_SERVER_HPP
--- a/tests/js-plugin/main.cpp	Fri Nov 17 19:12:32 2017 +0100
+++ b/tests/js-plugin/main.cpp	Fri Nov 17 20:56:10 2017 +0100
@@ -17,6 +17,7 @@
  */
 
 #define BOOST_TEST_MODULE "Javascript plugin object"
+#include <boost/asio.hpp>
 #include <boost/test/unit_test.hpp>
 
 #include <irccd/irccd.hpp>
@@ -30,7 +31,8 @@
 
 class js_plugin_test {
 protected:
-    irccd irccd_;
+    boost::asio::io_service service_;
+    irccd irccd_{service_};
     std::shared_ptr<js_plugin> plugin_;
 
     void load(std::string name, std::string path)
@@ -95,7 +97,8 @@
 
 class js_plugin_loader_test {
 protected:
-    irccd irccd_;
+    boost::asio::io_service service_;
+    irccd irccd_{service_};
     std::shared_ptr<plugin> plugin_;
 
     js_plugin_loader_test()