changeset 670:95ac3ace1610

Common: introduce new io code To avoid code duplication in accept, connect, reading and writing we add a new set of classes in `io` namespaces located in the following files: - stream.hpp, acceptor.hpp, connector.hpp These classes consist of pure abstract interfaces for I/O. Then we reimplement them in the following files: - socket_stream.hpp, socket_acceptor.hpp, socket_connector.hpp, - tls_stream.hpp, tls_acceptor.hpp, tls_conncetor.hpp (for SSL). This allows future independant connections such as DBus, fifo or any other fancy optional stuff. We also no longer need large class hierarchy such as `connection` for irccdctl controller or transport_server, transport_client classes.
author David Demelier <markand@malikania.fr>
date Tue, 10 Apr 2018 21:20:30 +0200
parents 6eb4caea77a5
children 3a6c8101349e
files cmake/function/IrccdDefineTest.cmake irccdctl/cli.cpp irccdctl/main.cpp irccdctl/watch_cli.cpp libcommon/CMakeLists.txt libcommon/irccd/acceptor.hpp libcommon/irccd/connector.hpp libcommon/irccd/network_stream.hpp libcommon/irccd/socket_acceptor.hpp libcommon/irccd/socket_connector.hpp libcommon/irccd/socket_stream.hpp libcommon/irccd/stream.hpp libcommon/irccd/tls_acceptor.hpp libcommon/irccd/tls_connector.hpp libcommon/irccd/tls_stream.hpp libirccd-test/irccd/test/command_test.hpp libirccd/CMakeLists.txt libirccd/irccd/daemon/basic_transport_client.hpp libirccd/irccd/daemon/basic_transport_server.hpp libirccd/irccd/daemon/command/plugin_config_command.cpp libirccd/irccd/daemon/command/plugin_info_command.cpp libirccd/irccd/daemon/command/plugin_list_command.cpp libirccd/irccd/daemon/command/rule_info_command.cpp libirccd/irccd/daemon/command/rule_list_command.cpp libirccd/irccd/daemon/command/server_info_command.cpp libirccd/irccd/daemon/command/server_list_command.cpp libirccd/irccd/daemon/ip_transport_server.hpp libirccd/irccd/daemon/irc.cpp libirccd/irccd/daemon/irc.hpp libirccd/irccd/daemon/irccd.cpp libirccd/irccd/daemon/irccd.hpp libirccd/irccd/daemon/local_transport_server.hpp libirccd/irccd/daemon/plugin.cpp libirccd/irccd/daemon/plugin.hpp libirccd/irccd/daemon/rule.cpp libirccd/irccd/daemon/rule.hpp libirccd/irccd/daemon/server.cpp libirccd/irccd/daemon/server.hpp libirccd/irccd/daemon/service/transport_service.cpp libirccd/irccd/daemon/tls_transport_server.cpp libirccd/irccd/daemon/tls_transport_server.hpp libirccd/irccd/daemon/transport_client.cpp libirccd/irccd/daemon/transport_client.hpp libirccd/irccd/daemon/transport_server.cpp libirccd/irccd/daemon/transport_server.hpp libirccd/irccd/daemon/transport_util.cpp libirccdctl/CMakeLists.txt libirccdctl/irccd/ctl/basic_connection.hpp libirccdctl/irccd/ctl/connection.hpp libirccdctl/irccd/ctl/controller.cpp libirccdctl/irccd/ctl/controller.hpp libirccdctl/irccd/ctl/ip_connection.cpp libirccdctl/irccd/ctl/ip_connection.hpp libirccdctl/irccd/ctl/local_connection.cpp libirccdctl/irccd/ctl/local_connection.hpp tests/data/test.crt tests/data/test.key tests/src/libcommon/CMakeLists.txt tests/src/libcommon/io/CMakeLists.txt tests/src/libcommon/io/main.cpp tests/src/libcommon/network-stream/CMakeLists.txt tests/src/libcommon/network-stream/main.cpp tests/src/libirccd/command-plugin-config/main.cpp tests/src/libirccd/command-plugin-load/main.cpp tests/src/libirccd/command-plugin-reload/main.cpp tests/src/libirccd/command-plugin-unload/main.cpp tests/src/libirccd/command-rule-move/main.cpp tests/src/libirccd/command-server-invite/main.cpp tests/src/libirccd/command-server-join/main.cpp tests/src/libirccd/command-server-kick/main.cpp tests/src/libirccd/command-server-mode/main.cpp tests/src/libirccd/command-server-notice/main.cpp tests/src/libirccd/command-server-part/main.cpp tests/src/libirccd/command-server-reconnect/main.cpp tests/src/libirccd/command-server-topic/main.cpp
diffstat 75 files changed, 1822 insertions(+), 1897 deletions(-) [+]
line wrap: on
line diff
--- a/cmake/function/IrccdDefineTest.cmake	Fri Apr 06 22:06:07 2018 +0200
+++ b/cmake/function/IrccdDefineTest.cmake	Tue Apr 10 21:20:30 2018 +0200
@@ -76,6 +76,8 @@
         PRIVATE
             ${TEST_FLAGS}
             BOOST_TEST_DYN_LINK
+            TESTS_SOURCE_DIR="${tests_SOURCE_DIR}"
+            TESTS_BINARY_DIR="${tests_SOURCE_DIR}"
             CMAKE_BINARY_DIR="${CMAKE_BINARY_DIR}"
             CMAKE_SOURCE_DIR="${CMAKE_SOURCE_DIR}"
             CMAKE_CURRENT_BINARY_DIR="${CMAKE_CURRENT_BINARY_DIR}"
--- a/irccdctl/cli.cpp	Fri Apr 06 22:06:07 2018 +0200
+++ b/irccdctl/cli.cpp	Tue Apr 10 21:20:30 2018 +0200
@@ -16,8 +16,6 @@
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
 
-#include <boost/system/system_error.hpp>
-
 #include <irccd/json_util.hpp>
 #include <irccd/options.hpp>
 #include <irccd/string_util.hpp>
@@ -32,9 +30,9 @@
 
 void cli::recv_response(ctl::controller& ctl, nlohmann::json req, handler_t handler)
 {
-    ctl.recv([&ctl, req, handler, this] (auto code, auto message) {
+    ctl.read([&ctl, req, handler, this] (auto code, auto message) {
         if (code)
-            throw boost::system::system_error(code);
+            throw std::system_error(code);
 
         const auto c = json_util::parser(message).get<std::string>("command");
 
@@ -50,9 +48,9 @@
 
 void cli::request(ctl::controller& ctl, nlohmann::json req, handler_t handler)
 {
-    ctl.send(req, [&ctl, req, handler, this] (auto code) {
+    ctl.write(req, [&ctl, req, handler, this] (auto code) {
         if (code)
-            throw boost::system::system_error(code);
+            throw std::system_error(code);
 
         recv_response(ctl, std::move(req), std::move(handler));
     });
--- a/irccdctl/main.cpp	Fri Apr 06 22:06:07 2018 +0200
+++ b/irccdctl/main.cpp	Tue Apr 10 21:20:30 2018 +0200
@@ -26,15 +26,17 @@
 #include <irccd/config.hpp>
 #include <irccd/json_util.hpp>
 #include <irccd/options.hpp>
+#include <irccd/socket_connector.hpp>
 #include <irccd/string_util.hpp>
 #include <irccd/system.hpp>
 
-#include <irccd/ctl/controller.hpp>
-#include <irccd/ctl/ip_connection.hpp>
+#if defined(HAVE_SSL)
+#   include <irccd/tls_connector.hpp>
+#endif
 
-#if !defined(IRCCD_SYSTEM_WINDOWS)
-#   include <irccd/ctl/local_connection.hpp>
-#endif
+#include <irccd/daemon/transport_server.hpp>
+
+#include <irccd/ctl/controller.hpp>
 
 #include "plugin_config_cli.hpp"
 #include "plugin_info_cli.hpp"
@@ -68,6 +70,14 @@
 #include "alias.hpp"
 #include "cli.hpp"
 
+using boost::asio::ip::tcp;
+
+#if !defined(IRCCD_SYSTEM_WINDOWS)
+
+using boost::asio::local::stream_protocol;
+
+#endif
+
 namespace irccd {
 
 namespace ctl {
@@ -77,18 +87,10 @@
 // Main service;
 boost::asio::io_service service;
 
-#if defined(HAVE_SSL)
-
-// For tls_connection.
-boost::asio::ssl::context ctx(boost::asio::ssl::context::sslv23);
-
-#endif
-
 // Global options.
 bool verbose = false;
 
 // Connection to instance.
-std::unique_ptr<connection> conn;
 std::unique_ptr<controller> ctl;
 
 // List of all commands and alias.
@@ -106,6 +108,25 @@
 }
 
 /*
+ * resolve
+ * ------------------------------------------------------------------
+ *
+ * Block unless host/port has been resolved.
+ */
+std::vector<tcp::endpoint> resolve(const std::string& host, const std::string& name)
+{
+    std::vector<tcp::endpoint> endpoints;
+
+    tcp::resolver resolver(service);
+    tcp::resolver::query query(host, name);
+
+    for (auto it = resolver.resolve(query); it != tcp::resolver::iterator(); ++it)
+        endpoints.push_back(*it);
+
+    return endpoints;
+}
+
+/*
  * read_connect_ip
  * -------------------------------------------------------------------
  *
@@ -118,35 +139,30 @@
  * domain = "ipv4 or ipv6" (Optional, default: ipv4)
  * ssl = true | false
  */
-std::unique_ptr<connection> read_connect_ip(const ini::section& sc)
+std::unique_ptr<io::connector> read_connect_ip(const ini::section& sc)
 {
-    std::unique_ptr<connection> conn;
-    std::string host;
-    ini::section::const_iterator it;
-
-    if ((it = sc.find("host")) == sc.end())
-        throw std::invalid_argument("missing host parameter");
-
-    host = it->value();
+    const auto host = sc.get("host").value();
+    const auto port = sc.get("port").value();
 
-    if ((it = sc.find("port")) == sc.end())
-        throw std::invalid_argument("missing port parameter");
+    if (host.empty())
+        throw transport_error(transport_error::invalid_hostname);
+    if (port.empty())
+        throw transport_error(transport_error::invalid_port);
 
-    const auto port = string_util::to_uint<std::uint16_t>(it->value());
+    const auto endpoints = resolve(host, port);
 
-    if (!port)
-        throw std::invalid_argument("invalid port parameter");
+    if (string_util::is_boolean(sc.get("ssl").value())) {
+#if defined(HAVE_SSL)
+        // TODO: support more parameters.
+        boost::asio::ssl::context ctx(boost::asio::ssl::context::sslv23);
 
-    if ((it = sc.find("ssl")) != sc.end() && string_util::is_boolean(it->value()))
-#if defined(HAVE_SSL)
-        conn = std::make_unique<tls_connection>(service, ctx, host, *port);
+        return std::make_unique<io::tls_connector<>>(std::move(ctx), service, endpoints);
 #else
         throw std::runtime_error("SSL disabled");
 #endif
-    else
-        conn = std::make_unique<ip_connection>(service, host, *port);
+    }
 
-    return conn;
+    return std::make_unique<io::ip_connector>(service, endpoints);
 }
 
 /*
@@ -159,15 +175,17 @@
  * type = "unix"
  * path = "path to socket file"
  */
-std::unique_ptr<connection> read_connect_local(const ini::section& sc)
+std::unique_ptr<io::connector> read_connect_local(const ini::section& sc)
 {
 #if !defined(IRCCD_SYSTEM_WINDOWS)
-    auto it = sc.find("path");
+    using boost::asio::local::stream_protocol;
+
+    const auto it = sc.find("path");
 
     if (it == sc.end())
         throw std::invalid_argument("missing path parameter");
 
-    return std::make_unique<local_connection>(service, it->value());
+    return std::make_unique<io::local_connector>(service, it->value());
 #else
     (void)sc;
 
@@ -183,20 +201,22 @@
  */
 void read_connect(const ini::section& sc)
 {
-    auto it = sc.find("type");
+    const auto it = sc.find("type");
 
     if (it == sc.end())
         throw std::invalid_argument("missing type parameter");
 
+    std::unique_ptr<io::connector> connector;
+
     if (it->value() == "ip")
-        conn = read_connect_ip(sc);
+        connector = read_connect_ip(sc);
     else if (it->value() == "unix")
-        conn = read_connect_local(sc);
+        connector = read_connect_local(sc);
     else
         throw std::invalid_argument(string_util::sprintf("invalid type given: %s", it->value()));
 
-    if (conn) {
-        ctl = std::make_unique<controller>(*conn);
+    if (connector) {
+        ctl = std::make_unique<controller>(std::move(connector));
 
         auto password = sc.find("password");
 
@@ -216,7 +236,7 @@
  */
 void read_general(const ini::section& sc)
 {
-    auto value = sc.find("verbose");
+    const auto value = sc.find("verbose");
 
     if (value != sc.end())
         verbose = string_util::is_boolean(value->value());
@@ -293,26 +313,23 @@
  * -h host or ip
  * -p port
  */
-std::unique_ptr<connection> parse_connect_ip(const option::result& options)
+std::unique_ptr<io::connector> parse_connect_ip(const option::result& options)
 {
     option::result::const_iterator it;
 
     // Host (-h or --host).
     if ((it = options.find("-h")) == options.end() && (it = options.find("--host")) == options.end())
-        throw std::invalid_argument("missing host argument (-h or --host)");
+        throw transport_error(transport_error::invalid_hostname);
 
-    auto host = it->second;
+    const auto host = it->second;
 
     // Port (-p or --port).
     if ((it = options.find("-p")) == options.end() && (it = options.find("--port")) == options.end())
-        throw std::invalid_argument("missing port argument (-p or --port)");
-
-    const auto port = string_util::to_uint<std::uint16_t>(it->second);
+        throw transport_error(transport_error::invalid_port);
 
-    if (!port)
-        throw std::invalid_argument("invalid port argument");
+    const auto port = it->second;
 
-    return std::make_unique<ip_connection>(service, host, *port);
+    return std::make_unique<io::ip_connector>(service, resolve(host, port));
 }
 
 /*
@@ -323,7 +340,7 @@
  *
  * -P file
  */
-std::unique_ptr<connection> parse_connect_local(const option::result& options)
+std::unique_ptr<io::connector> parse_connect_local(const option::result& options)
 {
 #if !defined(IRCCD_SYSTEM_WINDOWS)
     option::result::const_iterator it;
@@ -331,7 +348,7 @@
     if ((it = options.find("-P")) == options.end() && (it = options.find("--path")) == options.end())
         throw std::invalid_argument("missing path parameter (-P or --path)");
 
-    return std::make_unique<local_connection>(service, it->second);
+    return std::make_unique<io::local_connector>(service, it->second);
 #else
     (void)options;
 
@@ -354,15 +371,17 @@
     if (it == options.end())
         it = options.find("--type");
 
+    std::unique_ptr<io::connector> connector;
+
     if (it->second == "ip" || it->second == "ipv6")
-        conn = parse_connect_ip(options);
+        connector = parse_connect_ip(options);
     if (it->second == "unix")
-        conn = parse_connect_local(options);
+        connector = parse_connect_local(options);
     else
         throw std::invalid_argument(string_util::sprintf("invalid type given: %s", it->second));
 
-    if (conn)
-        ctl = std::make_unique<controller>(*conn);
+    if (connector)
+        ctl = std::make_unique<controller>(std::move(connector));
 }
 
 option::result parse(int& argc, char**& argv)
@@ -507,11 +526,9 @@
 
 void do_connect()
 {
-    bool connected = false;
-
     ctl->connect([&] (auto code, auto info) {
         if (code)
-            throw boost::system::system_error(code);
+            throw std::system_error(code);
 
         if (verbose) {
             const json_util::parser parser(info);
@@ -525,13 +542,9 @@
                 std::cout << string_util::sprintf("connected to irccd %d.%d.%d\n",
                     *major, *minor, *patch);
         }
-
-        connected = true;
     });
 
-    while (!connected)
-        service.run();
-
+    service.run();
     service.reset();
 }
 
@@ -543,9 +556,7 @@
         args.push_back(argv[i]);
 
     exec(args);
-
-    while (ctl->get_conn().is_active())
-        service.run();
+    service.run();
 }
 
 } // !namespace
@@ -599,7 +610,7 @@
     try {
         irccd::ctl::do_connect();
         irccd::ctl::do_exec(argc, argv);
-    } catch (const boost::system::system_error& ex) {
+    } catch (const std::system_error& ex) {
         std::cerr << "abort: " << ex.code().message() << std::endl;
     } catch (const std::exception& ex) {
         std::cerr << "abort: " << ex.what() << std::endl;
--- a/irccdctl/watch_cli.cpp	Fri Apr 06 22:06:07 2018 +0200
+++ b/irccdctl/watch_cli.cpp	Tue Apr 10 21:20:30 2018 +0200
@@ -177,9 +177,9 @@
 
 void get_event(ctl::controller& ctl, std::string fmt)
 {
-    ctl.recv([&ctl, fmt] (auto code, auto message) {
+    ctl.read([&ctl, fmt] (auto code, auto message) {
         if (code)
-            throw boost::system::system_error(code);
+            throw std::system_error(code);
 
         const auto event = json_util::parser(message).get<std::string>("event");
         const auto it = events.find(event ? *event : "");
--- a/libcommon/CMakeLists.txt	Fri Apr 06 22:06:07 2018 +0200
+++ b/libcommon/CMakeLists.txt	Tue Apr 10 21:20:30 2018 +0200
@@ -22,15 +22,23 @@
 
 set(
     HEADERS
+    ${libcommon_SOURCE_DIR}/irccd/acceptor.hpp
     ${libcommon_SOURCE_DIR}/irccd/config.hpp
+    ${libcommon_SOURCE_DIR}/irccd/connector.hpp
     ${libcommon_SOURCE_DIR}/irccd/fs_util.hpp
     ${libcommon_SOURCE_DIR}/irccd/ini.hpp
     ${libcommon_SOURCE_DIR}/irccd/ini_util.hpp
     ${libcommon_SOURCE_DIR}/irccd/json_util.hpp
-    ${libcommon_SOURCE_DIR}/irccd/network_stream.hpp
     ${libcommon_SOURCE_DIR}/irccd/options.hpp
+    ${libcommon_SOURCE_DIR}/irccd/socket_acceptor.hpp
+    ${libcommon_SOURCE_DIR}/irccd/socket_connector.hpp
+    ${libcommon_SOURCE_DIR}/irccd/socket_stream.hpp
+    ${libcommon_SOURCE_DIR}/irccd/stream.hpp
     ${libcommon_SOURCE_DIR}/irccd/string_util.hpp
     ${libcommon_SOURCE_DIR}/irccd/system.hpp
+    ${libcommon_SOURCE_DIR}/irccd/tls_acceptor.hpp
+    ${libcommon_SOURCE_DIR}/irccd/tls_connector.hpp
+    ${libcommon_SOURCE_DIR}/irccd/tls_stream.hpp
     ${libcommon_SOURCE_DIR}/irccd/xdg.hpp
 )
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libcommon/irccd/acceptor.hpp	Tue Apr 10 21:20:30 2018 +0200
@@ -0,0 +1,77 @@
+/*
+ * acceptor.hpp -- abstract stream acceptor interface
+ *
+ * Copyright (c) 2013-2018 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_ACCEPTOR_HPP
+#define IRCCD_COMMON_ACCEPTOR_HPP
+
+/**
+ * \file acceptor.hpp
+ * \brief Abstract stream acceptor interface.
+ */
+
+#include <functional>
+#include <memory>
+#include <system_error>
+
+namespace irccd {
+
+namespace io {
+
+class stream;
+
+/**
+ * \brief Accept completion handler.
+ */
+using accept_handler = std::function<void (std::error_code, std::shared_ptr<stream>)>;
+
+/**
+ * \brief Abstract stream acceptor interface.
+ *
+ * This class is used to wait a new client in an asynchronous manner. Derived
+ * classes must implement a non-blocking accept function.
+ */
+class acceptor {
+public:
+    /**
+     * Default constructor.
+     */
+    acceptor() = default;
+
+    /**
+     * Virtual destructor defaulted.
+     */
+    virtual ~acceptor() = default;
+
+    /**
+     * Start asynchronous accept.
+     *
+     * Once the client is accepted, the original acceptor must be kept until it
+     * is destroyed.
+     *
+     * \pre another accept operation must not be running
+     * \pre handler != nullptr
+     * \param handler the handler
+     */
+    virtual void accept(accept_handler handler) = 0;
+};
+
+} // !io
+
+} // !irccd
+
+#endif // !IRCCD_COMMON_ACCEPTOR_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libcommon/irccd/connector.hpp	Tue Apr 10 21:20:30 2018 +0200
@@ -0,0 +1,79 @@
+/*
+ * connector.hpp -- abstract connection interface
+ *
+ * Copyright (c) 2013-2018 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_CONNECTOR_HPP
+#define IRCCD_COMMON_CONNECTOR_HPP
+
+/**
+ * \file connector.hpp
+ * \brief Abstract connection interface.
+ */
+
+#include <functional>
+#include <memory>
+#include <system_error>
+
+namespace irccd {
+
+namespace io {
+
+class stream;
+
+/**
+ * \brief Connect completion handler.
+ */
+using connect_handler = std::function<void (std::error_code, std::shared_ptr<stream>)>;
+
+/**
+ * \brief Abstract connection interface.
+ *
+ * This class is used to connect to a stream end point (usually sockets) in an
+ * asynchronous manner.
+ *
+ * Derived class must implement non-blocking connect function.
+ */
+class connector {
+public:
+    /**
+     * Default constructor.
+     */
+    connector() = default;
+
+    /**
+     * Virtual destructor defaulted.
+     */
+    virtual ~connector() = default;
+
+    /**
+     * Start asynchronous connect.
+     *
+     * Once the client is connected, the original acceptor must be kept until it
+     * is destroyed.
+     *
+     * \pre another connect operation must not be running
+     * \pre handler != nullptr
+     * \param handler the handler
+     */
+    virtual void connect(connect_handler handler) = 0;
+};
+
+} // !io
+
+} // !irccd
+
+#endif // !IRCCD_COMMON_CONNECTOR_HPP
--- a/libcommon/irccd/network_stream.hpp	Fri Apr 06 22:06:07 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,331 +0,0 @@
-/*
- * network_stream.hpp -- base shared network stream
- *
- * Copyright (c) 2013-2018 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_NETWORK_STREAM_HPP
-#define IRCCD_COMMON_NETWORK_STREAM_HPP
-
-/**
- * \file network_stream.hpp
- * \brief Base shared network stream.
- */
-
-#include "sysconfig.hpp"
-
-#include <deque>
-#include <functional>
-#include <string>
-#include <utility>
-
-#include <boost/asio.hpp>
-
-#if defined(HAVE_SSL)
-#   include <boost/asio/ssl.hpp>
-#endif
-
-#include <json.hpp>
-
-namespace irccd {
-
-/**
- * Read handler.
- *
- * Call this function when a receive operation has finished on success or
- * failure.
- */
-using network_recv_handler = std::function<void (boost::system::error_code, nlohmann::json)>;
-
-/**
- * Send handler.
- *
- * Call this function when a send operation has finished on success or failure.
- */
-using network_send_handler = std::function<void (boost::system::error_code)>;
-
-/**
- * \brief Base shared network stream.
- *
- * This class can be used to perform I/O over a networking socket, it is
- * implemented as asynchronous operations over Boost.Asio.
- *
- * All recv/send operations are placed in a queue and performed when possible.
- */
-template <typename Socket>
-class network_stream {
-private:
-    using rbuffer_t = boost::asio::streambuf;
-    using rqueue_t = std::deque<network_recv_handler>;
-    using squeue_t = std::deque<std::pair<std::string, network_send_handler>>;
-
-    Socket socket_;
-    rbuffer_t rbuffer_;
-    rqueue_t rqueue_;
-    squeue_t squeue_;
-
-    // Decomposition functions.
-    std::string eat(boost::system::error_code&, std::size_t) noexcept;
-    nlohmann::json parse(boost::system::error_code&, const std::string&) noexcept;
-
-    // I/O flushing.
-    void rflush();
-    void sflush();
-
-    // Wrap async_read_until/async_write.
-    void do_recv(network_recv_handler);
-    void do_send(const std::string&, network_send_handler);
-
-public:
-    /**
-     * Construct the stream.
-     *
-     * \param args the arguments to pass to the Socket constructor
-     */
-    template <typename... Args>
-    inline network_stream(Args&&... args)
-        : socket_(std::forward<Args>(args)...)
-    {
-    }
-
-    /**
-     * Get the underlying socket.
-     *
-     * \return the socket
-     */
-    inline const Socket& get_socket() const noexcept
-    {
-        return socket_;
-    }
-
-    /**
-     * Overloaded function.
-     *
-     * \return the socket
-     */
-    inline Socket& get_socket() noexcept
-    {
-        return socket_;
-    }
-
-    /**
-     * Tells if receive operations are pending.
-     *
-     * \return true if receiving is in progress
-     */
-    inline bool is_receiving() const noexcept
-    {
-        return !rqueue_.empty();
-    }
-
-    /**
-     * Tells if send operations are pending.
-     *
-     * \return true if sending is in progress
-     */
-    inline bool is_sending() const noexcept
-    {
-        return !squeue_.empty();
-    }
-
-    /**
-     * Tells if there are any I/O pending.
-     *
-     * \return true if sending is in progress
-     */
-    inline bool is_active() const noexcept
-    {
-        return is_receiving() || is_sending();
-    }
-
-    /**
-     * Request a receive operation.
-     *
-     * The handler must not throw exceptions and `this` must be valid in the
-     * lifetime of the handler.
-     *
-     * \pre handler != nullptr
-     * \param handler the handler
-     */
-    void recv(network_recv_handler);
-
-    /**
-     * Request a send operation.
-     *
-     * The handler must not throw exceptions and `this` must be valid in the
-     * lifetime of the handler.
-     *
-     * \pre json.is_object()
-     * \param json the json message
-     * \param handler the optional handler
-     */
-    void send(nlohmann::json json, network_send_handler = nullptr);
-};
-
-template <typename Socket>
-std::string network_stream<Socket>::eat(boost::system::error_code& code, std::size_t xfer) noexcept
-{
-    try {
-        std::string str(
-            boost::asio::buffers_begin(rbuffer_.data()),
-            boost::asio::buffers_begin(rbuffer_.data()) + xfer - 4
-        );
-
-        // Only clean buffer here, so user can still try again later.
-        rbuffer_.consume(xfer);
-        code = make_error_code(boost::system::errc::success);
-
-        return str;
-    } catch (...) {
-        code = make_error_code(boost::system::errc::not_enough_memory);
-        return "";
-    }
-}
-
-template <typename Socket>
-nlohmann::json network_stream<Socket>::parse(boost::system::error_code& code, const std::string& data) noexcept
-{
-    try {
-        const auto json = nlohmann::json::parse(data);
-
-        if (!json.is_object()) {
-            code = make_error_code(boost::system::errc::invalid_argument);
-            return nullptr;
-        }
-
-        code = make_error_code(boost::system::errc::success);
-
-        return json;
-    } catch (...) {
-        code = make_error_code(boost::system::errc::invalid_argument);
-        return nullptr;
-    }
-}
-
-template <typename Socket>
-void network_stream<Socket>::rflush()
-{
-    if (rqueue_.empty())
-        return;
-
-    do_recv([this] (auto code, auto json) noexcept {
-        if (rqueue_.front())
-            rqueue_.front()(code, std::move(json));
-
-        rqueue_.pop_front();
-
-        if (!code)
-            rflush();
-    });
-}
-
-template <typename Socket>
-void network_stream<Socket>::sflush()
-{
-    if (squeue_.empty())
-        return;
-
-    do_send(squeue_.front().first, [this] (auto code) noexcept {
-        if (squeue_.front().second)
-            squeue_.front().second(code);
-
-        squeue_.pop_front();
-
-        if (!code)
-            sflush();
-    });
-}
-
-template <typename Socket>
-void network_stream<Socket>::do_recv(network_recv_handler handler)
-{
-    boost::asio::async_read_until(socket_, rbuffer_, "\r\n\r\n", [this, handler] (auto code, auto xfer) noexcept {
-        if (code || xfer == 0U) {
-            handler(make_error_code(boost::system::errc::network_down), nullptr);
-            return;
-        }
-
-        auto str = eat(code, xfer);
-
-        if (code)
-            handler(std::move(code), nullptr);
-
-        auto message = parse(code, str);
-
-        handler(std::move(code), std::move(message));
-    });
-}
-
-template <typename Socket>
-void network_stream<Socket>::do_send(const std::string& str, network_send_handler handler)
-{
-    boost::asio::async_write(socket_, boost::asio::buffer(str), [handler] (auto code, auto xfer) noexcept {
-        if (code || xfer == 0U)
-            handler(make_error_code(boost::system::errc::network_down));
-        else
-            handler(code);
-    });
-}
-
-template <typename Socket>
-void network_stream<Socket>::recv(network_recv_handler handler)
-{
-    auto in_progress = !rqueue_.empty();
-
-    rqueue_.push_back(std::move(handler));
-
-    if (!in_progress)
-        rflush();
-}
-
-template <typename Socket>
-void network_stream<Socket>::send(nlohmann::json json, network_send_handler handler)
-{
-    assert(json.is_object());
-
-    auto in_progress = !squeue_.empty();
-
-    squeue_.emplace_back(json.dump(0) + "\r\n\r\n", std::move(handler));
-
-    if (!in_progress)
-        sflush();
-}
-
-/**
- * \brief Typedef for TCP/IP socket.
- */
-using ip_network_stream = network_stream<boost::asio::ip::tcp::socket>;
-
-#if !defined(IRCCD_SYSTEM_WINDOWS)
-
-/**
- * \brief Typedef for Unix socket.
- */
-using local_network_stream = network_stream<boost::asio::local::stream_protocol::socket>;
-
-#endif // !IRCCD_SYSTEM_WINDOWS
-
-#if defined(HAVE_SSL)
-
-/**
- * \brief Typedef for SSL sockets.
- */
-using tls_network_stream = network_stream<boost::asio::ssl::stream<boost::asio::ip::tcp::socket>>;
-
-#endif // !HAVE_SSL
-
-} // !irccd
-
-#endif // IRCCD_COMMON_NETWORK_STREAM_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libcommon/irccd/socket_acceptor.hpp	Tue Apr 10 21:20:30 2018 +0200
@@ -0,0 +1,160 @@
+/*
+ * socket_acceptor.hpp -- socket stream acceptor interface
+ *
+ * Copyright (c) 2013-2018 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_SOCKET_ACCEPTOR_HPP
+#define IRCCD_COMMON_SOCKET_ACCEPTOR_HPP
+
+/**
+ * \file socket_acceptor.hpp
+ * \brief Socket stream acceptor interface.
+ */
+
+#include <irccd/sysconfig.hpp>
+
+#include <memory>
+#include <system_error>
+
+#include <boost/asio.hpp>
+
+#include "acceptor.hpp"
+#include "socket_stream.hpp"
+
+namespace irccd {
+
+namespace io {
+
+/**
+ * \brief Socket stream acceptor interface.
+ * \tparam Protocol a Boost.Asio compatible protocol (e.g. ip::tcp)
+ */
+template <typename Protocol>
+class socket_acceptor : public acceptor {
+public:
+    /**
+     * Convenient acceptor alias.
+     */
+    using acceptor = typename Protocol::acceptor;
+
+    /**
+     * Convenient socket alias.
+     */
+    using socket = typename Protocol::socket;
+
+private:
+    acceptor acceptor_;
+
+#if !defined(NDEBUG)
+    bool is_accepting_{false};
+#endif
+
+protected:
+    /**
+     * Helper to accept on the real underlying socket.
+     *
+     * \param socket the real socket
+     * \param handler the handler
+     */
+    template <typename Socket, typename Handler>
+    void do_accept(Socket& socket, Handler handler);
+
+public:
+    /**
+     * Construct the socket_acceptor.
+     *
+     * \pre acceptor must be ready (is_open() returns true)
+     * \param acceptor the Boost.Asio acceptor
+     */
+    inline socket_acceptor(acceptor acceptor) noexcept
+        : acceptor_(std::move(acceptor))
+    {
+        assert(acceptor_.is_open());
+    }
+
+    /**
+     * Get the underlying acceptor.
+     *
+     * \return the acceptor
+     */
+    inline const acceptor& get_acceptor() const noexcept
+    {
+        return acceptor_;
+    }
+
+    /**
+     * Overloaded function.
+     *
+     * \return the acceptor
+     */
+    inline acceptor& get_acceptor() noexcept
+    {
+        return acceptor_;
+    }
+
+    /**
+     * \copydoc acceptor::accept
+     */
+    void accept(accept_handler handler) override;
+};
+
+template <typename Protocol>
+template <typename Socket, typename Handler>
+void socket_acceptor<Protocol>::do_accept(Socket& socket, Handler handler)
+{
+#if !defined(NDEBUG)
+    assert(!is_accepting_);
+#endif
+
+    acceptor_.async_accept(socket, [this, handler] (auto code) {
+#if !defined(NDEBUG)
+        is_accepting_ = false;
+#endif
+        handler(detail::convert(code));
+    });
+}
+
+template <typename Protocol>
+void socket_acceptor<Protocol>::accept(accept_handler handler)
+{
+    assert(handler);
+
+    const auto client = std::make_shared<socket_stream<socket>>(acceptor_.get_io_service());
+
+    do_accept(client->get_socket(), [this, client, handler] (auto code) {
+        handler(std::move(code), code ? nullptr : std::move(client));
+    });
+}
+
+/**
+ * Convenient TCP/IP acceptor type.
+ */
+using ip_acceptor = socket_acceptor<boost::asio::ip::tcp>;
+
+#if !defined(IRCCD_SYSTEM_WINDOWS)
+
+/**
+ * Convenient Unix acceptor type.
+ */
+using local_acceptor = socket_acceptor<boost::asio::local::stream_protocol>;
+
+#endif
+
+} // !io
+
+} // !irccd
+
+#endif // !IRCCD_COMMON_SOCKET_ACCEPTOR_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libcommon/irccd/socket_connector.hpp	Tue Apr 10 21:20:30 2018 +0200
@@ -0,0 +1,177 @@
+/*
+ * socket_connector.hpp -- socket connection interface
+ *
+ * Copyright (c) 2013-2018 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_SOCKET_CONNECTOR_HPP
+#define IRCCD_COMMON_SOCKET_CONNECTOR_HPP
+
+/**
+ * \file socket_connector.hpp
+ * \brief Socket connection interface.
+ */
+
+#include <irccd/sysconfig.hpp>
+
+#include <vector>
+
+#include "connector.hpp"
+#include "socket_stream.hpp"
+
+#include <boost/asio.hpp>
+
+namespace irccd {
+
+namespace io {
+
+/**
+ * \brief Socket connection interface.
+ * \tparam Protocol a Boost.Asio compatible protocol (e.g. ip::tcp)
+ */
+template <typename Protocol>
+class socket_connector : public connector {
+public:
+    /**
+     * Convenient endpoint alias.
+     */
+    using endpoint = typename Protocol::endpoint;
+
+    /**
+     * Convenient socket alias.
+     */
+    using socket = typename Protocol::socket;
+
+private:
+    boost::asio::io_service& service_;
+    std::vector<endpoint> endpoints_;
+
+#if !defined(NDEBUG)
+    bool is_connecting_{false};
+#endif
+
+protected:
+    /**
+     * Start trying to connect to all endpoints.
+     *
+     * \param socket the underlying socket
+     * \param handler handler with `void f(std::error_code)` signature
+     */
+    template <typename Socket, typename Handler>
+    void do_connect(Socket& socket, Handler handler);
+
+public:
+    /**
+     * Construct the socket connector with only one endpoint.
+     *
+     * \param service the service
+     * \param endpoint the unique endpoint
+     */
+    inline socket_connector(boost::asio::io_service& service, endpoint endpoint) noexcept
+        : service_(service)
+        , endpoints_{std::move(endpoint)}
+    {
+    }
+
+    /**
+     * Construct the socket connection.
+     *
+     * \param service the service
+     * \param eps the endpoints
+     */
+    inline socket_connector(boost::asio::io_service& service, std::vector<endpoint> eps) noexcept
+        : service_(service)
+        , endpoints_(std::move(eps))
+    {
+    }
+
+    /**
+     * Get the underlying I/O service.
+     *
+     * \return the I/O service
+     */
+    inline const boost::asio::io_service& get_io_service() const noexcept
+    {
+        return service_;
+    }
+
+    /**
+     * Overloaded function.
+     *
+     * \return the I/O service
+     */
+    inline boost::asio::io_service& get_io_service() noexcept
+    {
+        return service_;
+    }
+
+    /**
+     * \copydoc connector::connect
+     */
+    void connect(connect_handler handler);
+};
+
+template <typename Protocol>
+template <typename Socket, typename Handler>
+void socket_connector<Protocol>::do_connect(Socket& socket, Handler handler)
+{
+#if !defined(NDEBUG)
+    assert(!is_connecting_);
+    is_connecting_ = true;
+#endif
+
+    boost::asio::async_connect(socket, endpoints_.begin(), endpoints_.end(), [this, handler] (auto code, auto ep) {
+#if !defined(NDEBUG)
+        is_connecting_ = false;
+#endif
+
+        if (ep == endpoints_.end())
+            handler(make_error_code(std::errc::host_unreachable));
+        else
+            handler(detail::convert(code));
+    });
+}
+
+template <typename Protocol>
+void socket_connector<Protocol>::connect(connect_handler handler)
+{
+    assert(handler);
+
+    const auto stream = std::make_shared<socket_stream<socket>>(service_);
+
+    do_connect(stream->get_socket(), [handler, stream] (auto code) {
+        handler(code, code ? nullptr : std::move(stream));
+    });
+}
+
+/**
+ * Convenient TCP/IP connector type.
+ */
+using ip_connector = socket_connector<boost::asio::ip::tcp>;
+
+#if !defined(IRCCD_SYSTEM_WINDOWS)
+
+/**
+ * Convenient Unix conncetor type.
+ */
+using local_connector = socket_connector<boost::asio::local::stream_protocol>;
+
+#endif
+
+} // !io
+
+} // !irccd
+
+#endif // !IRCCD_COMMON_SOCKET_CONNECTOR_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libcommon/irccd/socket_stream.hpp	Tue Apr 10 21:20:30 2018 +0200
@@ -0,0 +1,246 @@
+/*
+ * socket_stream.hpp -- socket stream interface
+ *
+ * Copyright (c) 2013-2018 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_SOCKET_STREAM_HPP
+#define IRCCD_COMMON_SOCKET_STREAM_HPP
+
+/**
+ * \file socket_stream.hpp
+ * \brief Socket stream interface.
+ */
+
+#include <irccd/sysconfig.hpp>
+
+#include <cstddef>
+#include <cassert>
+#include <string>
+#include <system_error>
+#include <utility>
+
+#include <boost/asio.hpp>
+
+#include "stream.hpp"
+
+namespace irccd {
+
+namespace io {
+
+/**
+ * \cond HIDDEN_SYMBOLS
+ */
+
+namespace detail {
+
+/**
+ * Convert boost::system::error_code to std.
+ *
+ * \param code the error code
+ * \return the std::error_code
+ */
+inline std::error_code convert(boost::system::error_code code) noexcept
+{
+    return std::error_code(code.value(), std::system_category());
+}
+
+} // !detail
+
+/**
+ * \endcond
+ */
+
+/**
+ * \brief Socket implementation interface.
+ * \tparam Socket the Boost.Asio compatible socket.
+ *
+ * This class reimplements stream for Boost.Asio sockets.
+ */
+template <typename Socket>
+class socket_stream : public stream {
+private:
+    Socket socket_;
+    boost::asio::streambuf input_;
+    std::string output_;
+
+#if !defined(NDEBUG)
+    bool is_receiving{false};
+    bool is_sending{false};
+#endif
+
+    void handle_read(boost::system::error_code, std::size_t, read_handler);
+    void handle_write(boost::system::error_code, std::size_t, write_handler);
+
+public:
+    /**
+     * Create the socket stream.
+     *
+     * \param args the Socket constructor arguments
+     */
+    template <typename... Args>
+    inline socket_stream(Args&&... args)
+        : socket_(std::forward<Args>(args)...)
+    {
+    }
+
+    /**
+     * Get the underlying socket.
+     *
+     * \return the socket
+     */
+    inline const Socket& get_socket() const noexcept
+    {
+        return socket_;
+    }
+
+    /**
+     * Overloaded function
+     *
+     * \return the socket
+     */
+    inline Socket& get_socket() noexcept
+    {
+        return socket_;
+    }
+
+    /**
+     * \copydoc stream::read
+     */
+    void read(read_handler handler) override;
+
+    /**
+     * \copydoc stream::write
+     */
+    void write(const nlohmann::json& json, write_handler handler) override;
+};
+
+template <typename Socket>
+void socket_stream<Socket>::handle_read(boost::system::error_code code,
+                                        std::size_t xfer,
+                                        read_handler handler)
+{
+#if !defined(NDEBUG)
+    is_receiving = false;
+#endif
+
+    if (xfer == 0U) {
+        handler(make_error_code(std::errc::not_connected), nullptr);
+        return;
+    }
+    if (code) {
+        handler(detail::convert(code), nullptr);
+        return;
+    }
+
+    // 1. Convert the buffer safely.
+    std::string buffer;
+
+    try {
+        buffer = std::string(
+            boost::asio::buffers_begin(input_.data()),
+            boost::asio::buffers_begin(input_.data()) + xfer - /* \r\n\r\n */ 4
+        );
+
+        input_.consume(xfer);
+    } catch (const std::bad_alloc&) {
+        handler(make_error_code(std::errc::not_enough_memory), nullptr);
+        return;
+    }
+
+    // 2. Convert to JSON.
+    nlohmann::json doc;
+
+    try {
+        doc = nlohmann::json::parse(buffer);
+    } catch (const std::exception&) {
+        handler(make_error_code(std::errc::invalid_argument), nullptr);
+        return;
+    }
+
+    if (!doc.is_object())
+        handler(make_error_code(std::errc::invalid_argument), nullptr);
+    else
+        handler(std::error_code(), std::move(doc));
+}
+
+template <typename Socket>
+void socket_stream<Socket>::handle_write(boost::system::error_code code,
+                                         std::size_t xfer,
+                                         write_handler handler)
+{
+#if !defined(NDEBUG)
+    is_sending = false;
+#endif
+
+    if (xfer == 0)
+        handler(make_error_code(std::errc::not_connected));
+    else
+        handler(detail::convert(code));
+}
+
+template <typename Socket>
+void socket_stream<Socket>::read(read_handler handler)
+{
+#if !defined(NDEBUG)
+    assert(!is_receiving);
+    assert(handler);
+
+    is_receiving = true;
+#endif
+
+    boost::asio::async_read_until(get_socket(), input_, "\r\n\r\n", [this, handler] (auto code, auto xfer) {
+        handle_read(code, xfer, std::move(handler));
+    });
+}
+
+template <typename Socket>
+void socket_stream<Socket>::write(const nlohmann::json& json, write_handler handler)
+{
+#if !defined(NDEBUG)
+    assert(!is_sending);
+    assert(handler);
+
+    is_sending = true;
+#endif
+
+    output_ = json.dump(0) + "\r\n\r\n";
+
+    const auto buffer = boost::asio::buffer(output_.data(), output_.size());
+
+    boost::asio::async_write(get_socket(), buffer, [this, handler] (auto code, auto xfer) {
+        handle_write(code, xfer, std::move(handler));
+    });
+}
+
+/**
+ * Convenient TCP/IP stream type.
+ */
+using ip_stream = socket_stream<boost::asio::ip::tcp::socket>;
+
+#if !defined(IRCCD_SYSTEM_WINDOWS)
+
+/**
+ * Convenient Unix stream type.
+ */
+using local_stream = socket_stream<boost::asio::local::stream_protocol::socket>;
+
+#endif
+
+} // !io
+
+} // !irccd
+
+#endif // !IRCCD_COMMON_SOCKET_STREAM_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libcommon/irccd/stream.hpp	Tue Apr 10 21:20:30 2018 +0200
@@ -0,0 +1,91 @@
+/*
+ * stream.hpp -- abstract stream interface
+ *
+ * Copyright (c) 2013-2018 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_STREAM_HPP
+#define IRCCD_COMMON_STREAM_HPP
+
+/**
+ * \file stream.hpp
+ * \brief Abstract stream interface.
+ */
+
+#include <functional>
+#include <system_error>
+
+#include "json.hpp"
+
+namespace irccd {
+
+namespace io {
+
+/**
+ * \brief Read completion handler.
+ */
+using read_handler = std::function<void (std::error_code, nlohmann::json)>;
+
+/**
+ * \brief Write completion handler.
+ */
+using write_handler = std::function<void (std::error_code)>;
+
+/**
+ * \brief Abstract stream interface
+ *
+ * Abstract I/O interface that allows reading/writing from a stream in an
+ * asynchronous manner.
+ *
+ * The derived classes must implement non-blocking read and write operations.
+ */
+class stream {
+public:
+    /**
+     * Default constructor.
+     */
+    stream() = default;
+
+    /**
+     * Virtual destructor defaulted.
+     */
+    virtual ~stream() = default;
+
+    /**
+     * Start asynchronous read.
+     *
+     * \pre another read operation must not be running
+     * \pre handler != nullptr
+     * \param handler the handler
+     */
+    virtual void read(read_handler handler) = 0;
+
+    /**
+     * Start asynchronous write.
+     *
+     * \pre json.is_object()
+     * \pre another write operation must not be running
+     * \pre handler != nullptr
+     * \param json the JSON message
+     * \param handler the handler
+     */
+    virtual void write(const nlohmann::json& json, write_handler handler) = 0;
+};
+
+} // !io
+
+} // !irccd
+
+#endif // !IRCCD_COMMON_STREAM_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libcommon/irccd/tls_acceptor.hpp	Tue Apr 10 21:20:30 2018 +0200
@@ -0,0 +1,96 @@
+/*
+ * tls_acceptor.hpp -- TLS/SSL acceptors
+ *
+ * Copyright (c) 2013-2018 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_TLS_ACCEPTOR_HPP
+#define IRCCD_COMMON_TLS_ACCEPTOR_HPP
+
+/**
+ * \file tls_acceptor.hpp
+ * \brief TLS/SSL acceptors.
+ */
+
+#include <irccd/sysconfig.hpp>
+
+#if defined(HAVE_SSL)
+
+#include "socket_acceptor.hpp"
+#include "tls_stream.hpp"
+
+namespace irccd {
+
+namespace io {
+
+/**
+ * \brief TLS/SSL acceptors.
+ * \tparam Protocol a Boost.Asio compatible protocol (e.g. ip::tcp)
+ */
+template <typename Protocol = boost::asio::ip::tcp>
+class tls_acceptor : public socket_acceptor<Protocol> {
+private:
+    using socket = typename Protocol::socket;
+
+    boost::asio::ssl::context context_;
+
+public:
+    /**
+     * Construct a secure layer transport server.
+     *
+     * \param context the SSL context
+     * \param args the socket_acceptor arguments
+     */
+    template <typename... Args>
+    inline tls_acceptor(boost::asio::ssl::context context, Args&&... args)
+        : socket_acceptor<Protocol>(std::forward<Args>(args)...)
+        , context_(std::move(context))
+    {
+    }
+
+    /**
+     * \copydoc acceptor::accept
+     */
+    void accept(accept_handler handler) override;
+};
+
+template <typename Protocol>
+void tls_acceptor<Protocol>::accept(accept_handler handler)
+{
+    assert(handler);
+
+    auto client = std::make_shared<tls_stream<socket>>(this->get_acceptor().get_io_service(), this->context_);
+
+    socket_acceptor<Protocol>::do_accept(client->get_socket().lowest_layer(), [handler, client] (auto code) {
+        using boost::asio::ssl::stream_base;
+
+        if (code) {
+            handler(code, nullptr);
+            return;
+        }
+
+        client->get_socket().async_handshake(stream_base::server, [handler, client] (auto code) {
+            handler(detail::convert(code), code ? nullptr : std::move(client));
+        });
+    });
+}
+
+} // !io
+
+} // !irccd
+
+#endif // !HAVE_SSL
+
+#endif // !IRCCD_COMMON_TLS_ACCEPTOR_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libcommon/irccd/tls_connector.hpp	Tue Apr 10 21:20:30 2018 +0200
@@ -0,0 +1,95 @@
+/*
+ * tls_connector.hpp -- TLS/SSL connectors
+ *
+ * Copyright (c) 2013-2018 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_TLS_CONNECTOR_HPP
+#define IRCCD_COMMON_TLS_CONNECTOR_HPP
+
+/**
+ * \file tls_connector.hpp
+ * \brief TLS/SSL connectors.
+ */
+
+#include <irccd/sysconfig.hpp>
+
+#if defined(HAVE_SSL)
+
+#include "socket_connector.hpp"
+#include "tls_stream.hpp"
+
+namespace irccd {
+
+namespace io {
+
+/**
+ * \brief TLS/SSL connectors.
+ * \tparam Protocol a Boost.Asio compatible protocol (e.g. ip::tcp)
+ */
+template <typename Protocol = boost::asio::ip::tcp>
+class tls_connector : public socket_connector<Protocol> {
+private:
+    boost::asio::ssl::context context_;
+
+public:
+    /**
+     * Construct a secure layer transport server.
+     *
+     * \param context the SSL context
+     * \param args the arguments to socket_connector<Socket> constructor
+     */
+    template <typename... Args>
+    inline tls_connector(boost::asio::ssl::context context, Args&&... args)
+        : socket_connector<Protocol>(std::forward<Args>(args)...)
+        , context_(std::move(context))
+    {
+    }
+
+    /**
+     * \copydoc socket_connector::connect
+     */
+    void connect(connect_handler handler) override;
+};
+
+template <typename Protocol>
+void tls_connector<Protocol>::connect(connect_handler handler)
+{
+    using boost::asio::ssl::stream_base;
+    using socket = typename Protocol::socket;
+
+    assert(handler);
+
+    const auto stream = std::make_shared<tls_stream<socket>>(this->get_io_service(), context_);
+
+    socket_connector<Protocol>::do_connect(stream->get_socket().lowest_layer(), [this, handler, stream] (auto code) {
+        if (code) {
+            handler(code, nullptr);
+            return;
+        }
+
+        stream->get_socket().async_handshake(stream_base::client, [handler, stream] (auto code) {
+            handler(detail::convert(code), code ? nullptr : std::move(stream));
+        });
+    });
+}
+
+} // !io
+
+} // !irccd
+
+#endif // !HAVE_SSL
+
+#endif // !IRCCD_COMMON_TLS_CONNECTOR_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libcommon/irccd/tls_stream.hpp	Tue Apr 10 21:20:30 2018 +0200
@@ -0,0 +1,64 @@
+/*
+ * tls_stream.hpp -- TLS/SSL streams
+ *
+ * Copyright (c) 2013-2018 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_TLS_STREAM_HPP
+#define IRCCD_COMMON_TLS_STREAM_HPP
+
+/**
+ * \file tls_stream.hpp
+ * \brief TLS/SSL streams.
+ */
+
+#include <irccd/sysconfig.hpp>
+
+#if defined(HAVE_SSL)
+
+#include <boost/asio/ssl.hpp>
+
+#include "socket_stream.hpp"
+
+namespace irccd {
+
+namespace io {
+
+/**
+ * \brief TLS/SSL streams.
+ * \tparam Socket the Boost.Asio compatible socket.
+ */
+template <typename Socket = boost::asio::ip::tcp::socket>
+class tls_stream : public socket_stream<boost::asio::ssl::stream<Socket>> {
+public:
+    /**
+     * Constructor.
+     *
+     * \param args the arguments to boost::asio::ssl::stream<Socket>
+     */
+    template <typename... Args>
+    inline tls_stream(Args&&... args)
+        : socket_stream<boost::asio::ssl::stream<Socket>>(std::forward<Args>(args)...)
+    {
+    }
+};
+
+} // !io
+
+} // !irccd
+
+#endif // !HAVE_SSL
+
+#endif // !IRCCD_COMMON_TLS_STREAM_HPP
--- a/libirccd-test/irccd/test/command_test.hpp	Fri Apr 06 22:06:07 2018 +0200
+++ b/libirccd-test/irccd/test/command_test.hpp	Tue Apr 10 21:20:30 2018 +0200
@@ -21,12 +21,13 @@
 
 #include <memory>
 
-#include <irccd/daemon/ip_transport_server.hpp>
+#include <irccd/socket_acceptor.hpp>
+#include <irccd/socket_connector.hpp>
+
 #include <irccd/daemon/irccd.hpp>
 #include <irccd/daemon/logger.hpp>
 #include <irccd/daemon/service/transport_service.hpp>
 
-#include <irccd/ctl/ip_connection.hpp>
 #include <irccd/ctl/controller.hpp>
 
 namespace irccd {
@@ -51,16 +52,14 @@
     /**
      * Result for request function.
      */
-    using result = std::pair<nlohmann::json, boost::system::error_code>;
+    using result = std::pair<nlohmann::json, std::error_code>;
 
     boost::asio::io_service service_;
-    boost::asio::deadline_timer timer_;
 
     // daemon stuff.
     std::unique_ptr<irccd> daemon_;
 
     // controller stuff.
-    std::unique_ptr<ctl::connection> conn_;
     std::unique_ptr<ctl::controller> ctl_;
 
     command_test();
@@ -78,8 +77,8 @@
     {
         result r;
 
-        ctl_->send(std::move(json));
-        ctl_->recv([&] (auto result, auto message) {
+        ctl_->write(std::move(json));
+        ctl_->read([&] (auto result, auto message) {
             r.first = message;
             r.second = result;
         });
@@ -93,8 +92,7 @@
 
 template <typename... Commands>
 command_test<Commands...>::command_test()
-    : timer_(service_)
-    , daemon_(std::make_unique<irccd>(service_))
+    : daemon_(std::make_unique<irccd>(service_))
 {
     using boost::asio::ip::tcp;
 
@@ -102,38 +100,42 @@
     tcp::endpoint ep(tcp::v4(), 0);
     tcp::acceptor acc(service_, ep);
 
-    // Connect to the local bound port.
-    conn_ = std::make_unique<ctl::ip_connection>(service_, "127.0.0.1", acc.local_endpoint().port());
-    ctl_ = std::make_unique<ctl::controller>(*conn_);
+    // Create controller and transport server.
+    ctl_ = std::make_unique<ctl::controller>(
+        std::make_unique<io::ip_connector>(service_, acc.local_endpoint()));
+    daemon_->transports().add(std::make_unique<transport_server>(
+        std::make_unique<io::ip_acceptor>(std::move(acc))));
 
     // Add the server and the command.
     add<Commands...>();
     daemon_->set_log(std::make_unique<silent_logger>());
-    daemon_->transports().add(std::make_unique<ip_transport_server>(service_, std::move(acc)));
+
+    // Wait for controller to connect.
+    boost::asio::deadline_timer timer(service_);
 
-    timer_.expires_from_now(boost::posix_time::seconds(10));
-    timer_.async_wait([] (auto code) {
-        if (!code)
-            throw make_error_code(boost::system::errc::timed_out);
+    timer.expires_from_now(boost::posix_time::seconds(10));
+    timer.async_wait([] (auto code) {
+        if (code && code != boost::asio::error::operation_aborted)
+            throw std::system_error(make_error_code(std::errc::timed_out));
     });
 
     bool connected = false;
 
     ctl_->connect([&] (auto code, auto) {
+        timer.cancel();
+
         if (code)
-            throw code;
+            throw std::system_error(code);
 
         connected = true;
-        timer_.cancel();
     });
 
+    /**
+     * Irccd will block indefinitely since transport_service will wait for any
+     * new client again, so we need to check with a boolean.
+     */
     while (!connected)
         service_.poll();
-
-    service_.reset();
-
-    if (!connected)
-        throw std::runtime_error("unable to connect");
 }
 
 } // !irccd
--- a/libirccd/CMakeLists.txt	Fri Apr 06 22:06:07 2018 +0200
+++ b/libirccd/CMakeLists.txt	Tue Apr 10 21:20:30 2018 +0200
@@ -22,8 +22,6 @@
 
 set(
     HEADERS
-    ${libirccd_SOURCE_DIR}/irccd/daemon/basic_transport_client.hpp
-    ${libirccd_SOURCE_DIR}/irccd/daemon/basic_transport_server.hpp
     ${libirccd_SOURCE_DIR}/irccd/daemon/command.hpp
     ${libirccd_SOURCE_DIR}/irccd/daemon/command/plugin_config_command.hpp
     ${libirccd_SOURCE_DIR}/irccd/daemon/command/plugin_info_command.hpp
@@ -54,10 +52,8 @@
     ${libirccd_SOURCE_DIR}/irccd/daemon/command/server_reconnect_command.hpp
     ${libirccd_SOURCE_DIR}/irccd/daemon/command/server_topic_command.hpp
     ${libirccd_SOURCE_DIR}/irccd/daemon/dynlib_plugin.hpp
-    ${libirccd_SOURCE_DIR}/irccd/daemon/ip_transport_server.hpp
     ${libirccd_SOURCE_DIR}/irccd/daemon/irccd.hpp
     ${libirccd_SOURCE_DIR}/irccd/daemon/irc.hpp
-    ${libirccd_SOURCE_DIR}/irccd/daemon/local_transport_server.hpp
     ${libirccd_SOURCE_DIR}/irccd/daemon/logger.hpp
     ${libirccd_SOURCE_DIR}/irccd/daemon/plugin.hpp
     ${libirccd_SOURCE_DIR}/irccd/daemon/rule.hpp
@@ -71,7 +67,6 @@
     ${libirccd_SOURCE_DIR}/irccd/daemon/transport_client.hpp
     ${libirccd_SOURCE_DIR}/irccd/daemon/transport_server.hpp
     ${libirccd_SOURCE_DIR}/irccd/daemon/transport_util.hpp
-    $<$<BOOL:${HAVE_SSL}>:${libirccd_SOURCE_DIR}/irccd/daemon/tls_transport_server.hpp>
 )
 
 set(
@@ -119,7 +114,6 @@
     ${libirccd_SOURCE_DIR}/irccd/daemon/transport_client.cpp
     ${libirccd_SOURCE_DIR}/irccd/daemon/transport_server.cpp
     ${libirccd_SOURCE_DIR}/irccd/daemon/transport_util.cpp
-    $<$<BOOL:${HAVE_SSL}>:${libirccd_SOURCE_DIR}/irccd/daemon/tls_transport_server.cpp>
 )
 
 irccd_define_library(
--- a/libirccd/irccd/daemon/basic_transport_client.hpp	Fri Apr 06 22:06:07 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,101 +0,0 @@
-/*
- * basic_transport_client.hpp -- simple socket transport client
- *
- * Copyright (c) 2013-2018 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_BASIC_TRANSPORT_CLIENT_HPP
-#define IRCCD_BASIC_TRANSPORT_CLIENT_HPP
-
-#include <irccd/network_stream.hpp>
-
-#include "transport_client.hpp"
-
-namespace irccd {
-
-/**
- * \brief Transport client for sockets.
- *
- * This class implements do_recv/do_send for Boost.Asio based socket streams.
- */
-template <typename Socket>
-class basic_transport_client : public transport_client {
-private:
-    network_stream<Socket> stream_;
-
-protected:
-    /**
-     * \copydoc transport_client::do_recv
-     */
-    void do_recv(network_recv_handler handler) override
-    {
-        const auto self = shared_from_this();
-
-        stream_.recv([this, self, handler] (auto msg, auto code) noexcept {
-            handler(std::move(msg), std::move(code));
-        });
-    }
-
-    /**
-     * \copydoc transport_client::do_send
-     */
-    void do_send(nlohmann::json json, network_send_handler handler) override
-    {
-        const auto self = shared_from_this();
-
-        stream_.send(std::move(json), [this, self, handler] (auto code) noexcept {
-            if (handler)
-                handler(std::move(code));
-        });
-    }
-
-public:
-    /**
-     * Construct the client.
-     *
-     * \param parent the parent
-     * \param args the argument to pass to the network_stream
-     */
-    template <typename... Args>
-    inline basic_transport_client(transport_server& parent, Args&&... args)
-        : transport_client(parent)
-        , stream_(std::forward<Args>(args)...)
-    {
-    }
-
-    /**
-     * Get the underlying stream.
-     *
-     * \return the stream
-     */
-    inline const network_stream<Socket>& stream() const noexcept
-    {
-        return stream_;
-    }
-
-    /**
-     * Overloaded function.
-     *
-     * \return the stream
-     */
-    inline network_stream<Socket>& stream() noexcept
-    {
-        return stream_;
-    }
-};
-
-} // !irccd
-
-#endif // !IRCCD_BASIC_TRANSPORT_CLIENT_HPP
--- a/libirccd/irccd/daemon/basic_transport_server.hpp	Fri Apr 06 22:06:07 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,99 +0,0 @@
-/*
- * basic_transport_server.hpp -- simple socket transport servers
- *
- * Copyright (c) 2013-2018 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_DAEMON_BASIC_TRANSPORT_SERVER_HPP
-#define IRCCD_DAEMON_BASIC_TRANSPORT_SERVER_HPP
-
-/**
- * \file basic_transport_server.hpp
- * \brief Simple socket transport servers.
- */
-
-#include "basic_transport_client.hpp"
-#include "transport_server.hpp"
-
-namespace irccd {
-
-/**
- * \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_handler handler) override;
-
-public:
-    /**
-     * Constructor with an acceptor in parameter.
-     *
-     * \pre acceptor.is_open()
-     * \param service the io service
-     * \param acceptor the already bound acceptor
-     */
-    basic_transport_server(boost::asio::io_service& service, acceptor_t acceptor);
-};
-
-template <typename Protocol>
-basic_transport_server<Protocol>::basic_transport_server(boost::asio::io_service& service, acceptor_t acceptor)
-    : transport_server(service)
-    , acceptor_(std::move(acceptor))
-{
-    assert(acceptor_.is_open());
-}
-
-template <typename Protocol>
-void basic_transport_server<Protocol>::do_accept(accept_handler handler)
-{
-    auto client = std::make_shared<basic_transport_client<socket_t>>(*this, acceptor_.get_io_service());
-
-    acceptor_.async_accept(client->stream().get_socket(), [this, client, handler] (auto code) {
-        if (code)
-            handler(std::move(code), nullptr);
-        else
-            handler(std::move(code), std::move(client));
-    });
-}
-
-} // !irccd
-
-#endif // !IRCCD_DAEMON_BASIC_TRANSPORT_SERVER_HPP
--- a/libirccd/irccd/daemon/command/plugin_config_command.cpp	Fri Apr 06 22:06:07 2018 +0200
+++ b/libirccd/irccd/daemon/command/plugin_config_command.cpp	Tue Apr 10 21:20:30 2018 +0200
@@ -66,7 +66,7 @@
      *
      * It's easier for the client to iterate over all.
      */
-    client.send({
+    client.write({
         { "command",    "plugin-config" },
         { "variables",  variables       }
     });
--- a/libirccd/irccd/daemon/command/plugin_info_command.cpp	Fri Apr 06 22:06:07 2018 +0200
+++ b/libirccd/irccd/daemon/command/plugin_info_command.cpp	Tue Apr 10 21:20:30 2018 +0200
@@ -42,7 +42,7 @@
 
     const auto plugin = irccd.plugins().require(*id);
 
-    client.send({
+    client.write({
         { "command",    "plugin-info"           },
         { "author",     plugin->get_author()    },
         { "license",    plugin->get_license()   },
--- a/libirccd/irccd/daemon/command/plugin_list_command.cpp	Fri Apr 06 22:06:07 2018 +0200
+++ b/libirccd/irccd/daemon/command/plugin_list_command.cpp	Tue Apr 10 21:20:30 2018 +0200
@@ -37,7 +37,7 @@
     for (const auto& plugin : irccd.plugins().list())
         list += plugin->get_name();
 
-    client.send({
+    client.write({
         { "command",    "plugin-list"   },
         { "list",       list            }
     });
--- a/libirccd/irccd/daemon/command/rule_info_command.cpp	Fri Apr 06 22:06:07 2018 +0200
+++ b/libirccd/irccd/daemon/command/rule_info_command.cpp	Tue Apr 10 21:20:30 2018 +0200
@@ -43,7 +43,7 @@
     auto json = rule_util::to_json(irccd.rules().require(*index));
 
     json.push_back({"command", "rule-info"});
-    client.send(std::move(json));
+    client.write(std::move(json));
 }
 
 } // !irccd
--- a/libirccd/irccd/daemon/command/rule_list_command.cpp	Fri Apr 06 22:06:07 2018 +0200
+++ b/libirccd/irccd/daemon/command/rule_list_command.cpp	Tue Apr 10 21:20:30 2018 +0200
@@ -38,7 +38,7 @@
     for (const auto& rule : irccd.rules().list())
         array.push_back(rule_util::to_json(rule));
 
-    client.send({
+    client.write({
         { "command",    "rule-list"         },
         { "list",       std::move(array)    }
     });
--- a/libirccd/irccd/daemon/command/server_info_command.cpp	Fri Apr 06 22:06:07 2018 +0200
+++ b/libirccd/irccd/daemon/command/server_info_command.cpp	Tue Apr 10 21:20:30 2018 +0200
@@ -64,7 +64,7 @@
     if (server->get_flags() & server::ssl_verify)
         response.push_back({"sslVerify", true});
 
-    client.send(response);
+    client.write(response);
 }
 
 } // !irccd
--- a/libirccd/irccd/daemon/command/server_list_command.cpp	Fri Apr 06 22:06:07 2018 +0200
+++ b/libirccd/irccd/daemon/command/server_list_command.cpp	Tue Apr 10 21:20:30 2018 +0200
@@ -38,7 +38,7 @@
     for (const auto& server : irccd.servers().servers())
         list.push_back(server->get_name());
 
-    client.send({
+    client.write({
         { "command",    "server-list"   },
         { "list",       std::move(list) }
     });
--- a/libirccd/irccd/daemon/ip_transport_server.hpp	Fri Apr 06 22:06:07 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,38 +0,0 @@
-/*
- * ip_transport_server.hpp -- server side transports (TCP/IP support)
- *
- * Copyright (c) 2013-2018 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_DAEMON_IP_TRANSPORT_SERVER_HPP
-#define IRCCD_DAEMON_IP_TRANSPORT_SERVER_HPP
-
-/**
- * \file ip_transport_server.hpp
- * \brief Server side transports (TCP/IP support).
- */
-
-#include "basic_transport_server.hpp"
-
-namespace irccd {
-
-/**
- * Convenient type for IP/TCP
- */
-using ip_transport_server = basic_transport_server<boost::asio::ip::tcp>;
-
-} // !irccd
-
-#endif // !IRCCD_DAEMON_IP_TRANSPORT_SERVER_HPP
--- a/libirccd/irccd/daemon/irc.cpp	Fri Apr 06 22:06:07 2018 +0200
+++ b/libirccd/irccd/daemon/irc.cpp	Tue Apr 10 21:20:30 2018 +0200
@@ -108,7 +108,9 @@
     if (input_.empty())
         return;
 
-    do_recv(buffer_, [this] (auto code, auto message) {
+    auto self = shared_from_this();
+
+    do_recv(buffer_, [this, self] (auto code, auto message) {
         if (input_.front())
             input_.front()(code, std::move(message));
 
@@ -124,7 +126,9 @@
     if (output_.empty())
         return;
 
-    do_send(output_.front().first, [this] (auto code) {
+    auto self = shared_from_this();
+
+    do_send(output_.front().first, [this, self] (auto code) {
         if (output_.front().second)
             output_.front().second(code);
 
--- a/libirccd/irccd/daemon/irc.hpp	Fri Apr 06 22:06:07 2018 +0200
+++ b/libirccd/irccd/daemon/irc.hpp	Tue Apr 10 21:20:30 2018 +0200
@@ -356,7 +356,7 @@
 /**
  * \brief Abstract connection to a server.
  */
-class connection {
+class connection : public std::enable_shared_from_this<connection> {
 public:
     /**
      * Handler for connecting.
@@ -373,6 +373,11 @@
      */
     using send_t = std::function<void (boost::system::error_code)>;
 
+    /**
+     * Convenient alias.
+     */
+    using ptr = std::shared_ptr<connection>;
+
 private:
     using buffer_t = boost::asio::streambuf;
     using input_t = std::deque<recv_t>;
@@ -411,12 +416,12 @@
      */
     virtual void do_send(const std::string& data, send_t handler) noexcept = 0;
 
-public:
     /**
      * Default constructor.
      */
     connection() = default;
 
+public:
     /**
      * Virtual destructor defaulted.
      */
@@ -478,7 +483,6 @@
      */
     void do_send(const std::string& data, send_t handler) noexcept override;
 
-public:
     /**
      * Constructor.
      *
@@ -489,6 +493,19 @@
         , resolver_(service)
     {
     }
+
+public:
+    /**
+     * Wrap the connection as shared ptr.
+     *
+     * \param args the tls_connection constructor arguments
+     * \return the shared_ptr connection
+     */
+    template <typename... Args>
+    static inline std::shared_ptr<ip_connection> create(Args&&... args)
+    {
+        return std::shared_ptr<ip_connection>(new ip_connection(std::forward<Args>(args)...));
+    }
 };
 
 #if defined(HAVE_SSL)
@@ -518,18 +535,30 @@
      */
     void do_send(const std::string& data, send_t handler) noexcept override;
 
-public:
     /**
      * Constructor.
      *
      * \param service the io service
      */
-    inline tls_connection(boost::asio::io_service& service) noexcept
+    inline tls_connection(boost::asio::io_service& service)
         : context_(boost::asio::ssl::context::sslv23)
         , socket_(service, context_)
         , resolver_(service)
     {
     }
+
+public:
+    /**
+     * Wrap the connection as shared ptr.
+     *
+     * \param args the tls_connection constructor arguments
+     * \return the shared_ptr connection
+     */
+    template <typename... Args>
+    static inline std::shared_ptr<tls_connection> create(Args&&... args)
+    {
+        return std::shared_ptr<tls_connection>(new tls_connection(std::forward<Args>(args)...));
+    }
 };
 
 #endif // !HAVE_SSL
--- a/libirccd/irccd/daemon/irccd.cpp	Fri Apr 06 22:06:07 2018 +0200
+++ b/libirccd/irccd/daemon/irccd.cpp	Tue Apr 10 21:20:30 2018 +0200
@@ -263,9 +263,9 @@
     loaded_ = true;
 }
 
-const boost::system::error_category& irccd_category()
+const std::error_category& irccd_category()
 {
-    static const class category : public boost::system::error_category {
+    static const class category : public std::error_category {
     public:
         const char* name() const noexcept override
         {
@@ -298,7 +298,7 @@
     return category;
 }
 
-boost::system::error_code make_error_code(irccd_error::error e)
+std::error_code make_error_code(irccd_error::error e)
 {
     return {static_cast<int>(e), irccd_category()};
 }
--- a/libirccd/irccd/daemon/irccd.hpp	Fri Apr 06 22:06:07 2018 +0200
+++ b/libirccd/irccd/daemon/irccd.hpp	Tue Apr 10 21:20:30 2018 +0200
@@ -213,7 +213,7 @@
 /**
  * \brief Irccd error.
  */
-class irccd_error : public boost::system::system_error {
+class irccd_error : public std::system_error {
 public:
     /**
      * \brief Irccd related errors.
@@ -255,27 +255,23 @@
  *
  * \return the singleton
  */
-const boost::system::error_category& irccd_category();
+const std::error_category& irccd_category();
 
 /**
  * Create a boost::system::error_code from irccd_error::error enum.
  *
  * \param e the error code
  */
-boost::system::error_code make_error_code(irccd_error::error e);
+std::error_code make_error_code(irccd_error::error e);
 
 } // !irccd
 
-namespace boost {
-
-namespace system {
+namespace std {
 
 template <>
 struct is_error_code_enum<irccd::irccd_error::error> : public std::true_type {
 };
 
-} // !system
-
-} // !boost
+} // !std
 
 #endif // !IRCCD_DAEMON_IRCCD_HPP
--- a/libirccd/irccd/daemon/local_transport_server.hpp	Fri Apr 06 22:06:07 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,44 +0,0 @@
-/*
- * local_transport_server.hpp -- server side transports (Unix support)
- *
- * Copyright (c) 2013-2018 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_DAEMON_LOCAL_TRANSPORT_SERVER_HPP
-#define IRCCD_DAEMON_LOCAL_TRANSPORT_SERVER_HPP
-
-/**
- * \file local_transport_server.hpp
- * \brief Server side transports (Unix support).
- */
-
-#include <irccd/sysconfig.hpp>
-
-#if !defined(IRCCD_SYSTEM_WINDOWS)
-
-#include "basic_transport_server.hpp"
-
-namespace irccd {
-
-/**
- * Convenient type for UNIX local sockets.
- */
-using local_transport_server = basic_transport_server<boost::asio::local::stream_protocol>;
-
-#endif // !_WIN32
-
-} // !irccd
-
-#endif // !IRCCD_DAEMON_LOCAL_TRANSPORT_SERVER_HPP
--- a/libirccd/irccd/daemon/plugin.cpp	Fri Apr 06 22:06:07 2018 +0200
+++ b/libirccd/irccd/daemon/plugin.cpp	Tue Apr 10 21:20:30 2018 +0200
@@ -73,9 +73,9 @@
     what_ = oss.str();
 }
 
-const boost::system::error_category& plugin_category()
+const std::error_category& plugin_category()
 {
-    static const class category : public boost::system::error_category {
+    static const class category : public std::error_category {
     public:
         const char* name() const noexcept override
         {
@@ -102,7 +102,7 @@
     return category;
 }
 
-boost::system::error_code make_error_code(plugin_error::error e)
+std::error_code make_error_code(plugin_error::error e)
 {
     return {static_cast<int>(e), plugin_category()};
 }
--- a/libirccd/irccd/daemon/plugin.hpp	Fri Apr 06 22:06:07 2018 +0200
+++ b/libirccd/irccd/daemon/plugin.hpp	Tue Apr 10 21:20:30 2018 +0200
@@ -552,7 +552,7 @@
 /**
  * \brief Plugin error.
  */
-class plugin_error : public boost::system::system_error {
+class plugin_error : public std::system_error {
 public:
     /**
      * \brief Plugin related errors.
@@ -623,27 +623,23 @@
  *
  * \return the singleton
  */
-const boost::system::error_category& server_category();
+const std::error_category& plugin_category();
 
 /**
  * Create a boost::system::error_code from plugin_error::error enum.
  *
  * \param e the error code
  */
-boost::system::error_code make_error_code(plugin_error::error e);
+std::error_code make_error_code(plugin_error::error e);
 
 } // !irccd
 
-namespace boost {
-
-namespace system {
+namespace std {
 
 template <>
 struct is_error_code_enum<irccd::plugin_error::error> : public std::true_type {
 };
 
-} // !system
-
-} // !boost
+} // !std
 
 #endif // !IRCCD_DAEMON_PLUGIN_HPP
--- a/libirccd/irccd/daemon/rule.cpp	Fri Apr 06 22:06:07 2018 +0200
+++ b/libirccd/irccd/daemon/rule.cpp	Tue Apr 10 21:20:30 2018 +0200
@@ -56,9 +56,9 @@
            match_set(events_, event);
 }
 
-const boost::system::error_category& rule_category()
+const std::error_category& rule_category()
 {
-    static const class category : public boost::system::error_category {
+    static const class category : public std::error_category {
     public:
         const char* name() const noexcept override
         {
@@ -81,7 +81,7 @@
     return category;
 }
 
-boost::system::error_code make_error_code(rule_error::error e)
+std::error_code make_error_code(rule_error::error e)
 {
     return {static_cast<int>(e), rule_category()};
 }
--- a/libirccd/irccd/daemon/rule.hpp	Fri Apr 06 22:06:07 2018 +0200
+++ b/libirccd/irccd/daemon/rule.hpp	Tue Apr 10 21:20:30 2018 +0200
@@ -227,7 +227,7 @@
 /**
  * \brief Rule error.
  */
-class rule_error : public boost::system::system_error {
+class rule_error : public std::system_error {
 public:
     /**
      * \brief Rule related errors.
@@ -254,27 +254,23 @@
  *
  * \return the singleton
  */
-const boost::system::error_category& rule_category();
+const std::error_category& rule_category();
 
 /**
  * Create a boost::system::error_code from rule_error::error enum.
  *
  * \param e the error code
  */
-boost::system::error_code make_error_code(rule_error::error e);
+std::error_code make_error_code(rule_error::error e);
 
 } // !irccd
 
-namespace boost {
-
-namespace system {
+namespace std {
 
 template <>
 struct is_error_code_enum<irccd::rule_error::error> : public std::true_type {
 };
 
-} // !system
-
-} // !boost
+} // !std
 
 #endif // !IRCCD_DAEMON_RULE_HPP
--- a/libirccd/irccd/daemon/server.cpp	Fri Apr 06 22:06:07 2018 +0200
+++ b/libirccd/irccd/daemon/server.cpp	Tue Apr 10 21:20:30 2018 +0200
@@ -499,7 +499,7 @@
 
     if (flags_ & ssl) {
 #if defined(HAVE_SSL)
-        conn_ = std::make_unique<irc::tls_connection>(service_);
+        conn_ = irc::tls_connection::create(service_);
 #else
         /*
          * If SSL is not compiled in, the caller is responsible of not setting
@@ -508,7 +508,7 @@
         assert(!(flags_ & ssl));
 #endif
     } else
-        conn_ = std::make_unique<irc::ip_connection>(service_);
+        conn_ = irc::ip_connection::create(service_);
 
     state_ = state_t::connecting;
     conn_->connect(host_, std::to_string(port_), [this] (auto code) {
@@ -676,9 +676,9 @@
 {
 }
 
-const boost::system::error_category& server_category()
+const std::error_category& server_category()
 {
-    static const class category : public boost::system::error_category {
+    static const class category : public std::error_category {
     public:
         const char* name() const noexcept override
         {
@@ -735,7 +735,7 @@
     return category;
 }
 
-boost::system::error_code make_error_code(server_error::error e)
+std::error_code make_error_code(server_error::error e)
 {
     return {static_cast<int>(e), server_category()};
 }
--- a/libirccd/irccd/daemon/server.hpp	Fri Apr 06 22:06:07 2018 +0200
+++ b/libirccd/irccd/daemon/server.hpp	Tue Apr 10 21:20:30 2018 +0200
@@ -437,7 +437,7 @@
     // Misc.
     boost::asio::io_service& service_;
     boost::asio::deadline_timer timer_;
-    std::unique_ptr<irc::connection> conn_;
+    std::shared_ptr<irc::connection> conn_;
     std::int8_t recocur_{0};
     std::map<std::string, std::set<std::string>> names_map_;
     std::map<std::string, whois_info> whois_map_;
@@ -902,7 +902,7 @@
 /**
  * \brief Server error.
  */
-class server_error : public boost::system::system_error {
+class server_error : public std::system_error {
 public:
     /**
      * \brief Server related errors.
@@ -986,27 +986,23 @@
  *
  * \return the singleton
  */
-const boost::system::error_category& server_category();
+const std::error_category& server_category();
 
 /**
  * Create a boost::system::error_code from server_error::error enum.
  *
  * \param e the error code
  */
-boost::system::error_code make_error_code(server_error::error e);
+std::error_code make_error_code(server_error::error e);
 
 } // !irccd
 
-namespace boost {
-
-namespace system {
+namespace std {
 
 template <>
 struct is_error_code_enum<irccd::server_error::error> : public std::true_type {
 };
 
-} // !system
-
-} // !boost
+} // !std
 
 #endif // !IRCCD_DAEMON_SERVER_HPP
--- a/libirccd/irccd/daemon/service/transport_service.cpp	Fri Apr 06 22:06:07 2018 +0200
+++ b/libirccd/irccd/daemon/service/transport_service.cpp	Tue Apr 10 21:20:30 2018 +0200
@@ -23,7 +23,6 @@
 #include <irccd/json_util.hpp>
 
 #include <irccd/daemon/command.hpp>
-#include <irccd/daemon/ip_transport_server.hpp>
 #include <irccd/daemon/irccd.hpp>
 #include <irccd/daemon/logger.hpp>
 #include <irccd/daemon/transport_util.hpp>
@@ -48,7 +47,7 @@
         return;
     }
 
-    auto cmd = std::find_if(commands_.begin(), commands_.end(), [&] (const auto& cptr) {
+    const auto cmd = std::find_if(commands_.begin(), commands_.end(), [&] (const auto& cptr) {
         return cptr->get_name() == *name;
     });
 
@@ -57,7 +56,7 @@
     else {
         try {
             (*cmd)->exec(irccd_, *tc, object);
-        } catch (const boost::system::system_error& ex) {
+        } catch (const std::system_error& ex) {
             tc->error(ex.code(), (*cmd)->get_name());
         } catch (const std::exception& ex) {
             irccd_.get_log().warning() << "transport: unknown error not reported" << std::endl;
@@ -68,19 +67,22 @@
 
 void transport_service::do_recv(std::shared_ptr<transport_client> tc)
 {
-    tc->recv([this, tc] (auto code, auto json) {
-        switch (code.value()) {
-        case boost::system::errc::network_down:
+    tc->read([this, tc] (auto code, auto json) {
+        switch (static_cast<std::errc>(code.value())) {
+        case std::errc::not_connected:
             irccd_.get_log().info("transport: client disconnected");
             break;
-        case boost::system::errc::invalid_argument:
+        case std::errc::invalid_argument:
             tc->error(irccd_error::invalid_message);
             break;
         default:
-            handle_command(tc, json);
+            // Other error may still happen.
+            if (!code) {
+                handle_command(tc, json);
 
-            if (tc->get_state() == transport_client::state_t::ready)
-                do_recv(std::move(tc));
+                if (tc->get_state() == transport_client::state_t::ready)
+                    do_recv(std::move(tc));
+            }
 
             break;
         }
@@ -90,9 +92,7 @@
 void transport_service::do_accept(transport_server& ts)
 {
     ts.accept([this, &ts] (auto code, auto client) {
-        if (code)
-            irccd_.get_log().warning() << "transport: new client error: " << code.message() << std::endl;
-        else {
+        if (!code) {
             do_accept(ts);
             do_recv(std::move(client));
 
@@ -122,7 +122,7 @@
 
     for (const auto& servers : servers_)
         for (const auto& client : servers->get_clients())
-            client->send(json);
+            client->write(json);
 }
 
 void transport_service::load(const config& cfg) noexcept
--- a/libirccd/irccd/daemon/tls_transport_server.cpp	Fri Apr 06 22:06:07 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,54 +0,0 @@
-/*
- * tls_transport_server.cpp -- server side transports (SSL support)
- *
- * Copyright (c) 2013-2018 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 "tls_transport_server.hpp"
-
-#if defined(HAVE_SSL)
-
-namespace irccd {
-
-void tls_transport_server::do_handshake(std::shared_ptr<client_t> client, accept_handler handler)
-{
-    using boost::asio::ssl::stream_base;
-
-    client->stream().get_socket().async_handshake(stream_base::server, [client, handler] (auto code) {
-        handler(std::move(code), std::move(client));
-    });
-}
-
-tls_transport_server::tls_transport_server(boost::asio::io_service& service, acceptor_t acceptor, context_t context)
-    : ip_transport_server(service, std::move(acceptor))
-    , context_(std::move(context))
-{
-}
-
-void tls_transport_server::do_accept(accept_handler handler)
-{
-    auto client = std::make_shared<client_t>(*this, acceptor_.get_io_service(), context_);
-
-    acceptor_.async_accept(client->stream().get_socket().lowest_layer(), [this, client, handler] (auto code) {
-        if (code)
-            handler(std::move(code), nullptr);
-        else
-            do_handshake(std::move(client), std::move(handler));
-    });
-}
-
-} // !irccd
-
-#endif // !HAVE_SSL
--- a/libirccd/irccd/daemon/tls_transport_server.hpp	Fri Apr 06 22:06:07 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,73 +0,0 @@
-/*
- * tls_transport_server.hpp -- server side transports (SSL support)
- *
- * Copyright (c) 2013-2018 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_DAEMON_TLS_TRANSPORT_SERVER_HPP
-#define IRCCD_DAEMON_TLS_TRANSPORT_SERVER_HPP
-
-/**
- * \file tls_transport_server.hpp
- * \brief Server side transports (SSL support).
- */
-
-#include <irccd/sysconfig.hpp>
-
-#if defined(HAVE_SSL)
-
-#include <boost/asio/ssl.hpp>
-
-#include "ip_transport_server.hpp"
-
-namespace irccd {
-
-/**
- * \brief Secure layer implementation.
- */
-class tls_transport_server : public ip_transport_server {
-private:
-    using context_t = boost::asio::ssl::context;
-    using client_t = basic_transport_client<boost::asio::ssl::stream<socket_t>>;
-
-    context_t context_;
-
-    void do_handshake(std::shared_ptr<client_t>, accept_handler);
-
-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_handler handler) override;
-
-public:
-    /**
-     * Construct a secure layer transport server.
-     *
-     * \param service the io service
-     * \param acceptor the acceptor
-     * \param context the SSL context
-     */
-    tls_transport_server(boost::asio::io_service& service, acceptor_t acceptor, context_t context);
-};
-
-} // !irccd
-
-#endif // !HAVE_SSL
-
-#endif // !IRCCD_DAEMON_TLS_TRANSPORT_SERVER_HPP
--- a/libirccd/irccd/daemon/transport_client.cpp	Fri Apr 06 22:06:07 2018 +0200
+++ b/libirccd/irccd/daemon/transport_client.cpp	Tue Apr 10 21:20:30 2018 +0200
@@ -16,31 +16,45 @@
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
 
-#include <cassert>
-
 #include "transport_client.hpp"
 #include "transport_server.hpp"
 
 namespace irccd {
 
-void transport_client::erase()
+void transport_client::flush()
 {
+    if (queue_.empty())
+        return;
+
     const auto self = shared_from_this();
 
-    state_ = state_t::closing;
-    parent_.get_service().post([this, self] () {
-        parent_.get_clients().erase(self);
+    stream_->write(queue_.front().first, [this, self] (auto code) {
+        if (queue_.front().second)
+            queue_.front().second(code);
+
+        queue_.pop_front();
+
+        if (code)
+            erase();
+        else
+            flush();
     });
 }
 
-void transport_client::recv(network_recv_handler handler)
+void transport_client::erase()
+{
+    state_ = state_t::closing;
+    parent_.get_clients().erase(shared_from_this());
+}
+
+void transport_client::read(io::read_handler handler)
 {
     assert(handler);
 
-    const auto self = shared_from_this();
+    if (state_ != state_t::closing) {
+        const auto self = shared_from_this();
 
-    if (state_ != state_t::closing) {
-        do_recv([this, self, handler] (auto code, auto msg) {
+        stream_->read([this, self, handler] (auto code, auto msg) {
             handler(code, msg);
 
             if (code)
@@ -49,35 +63,29 @@
     }
 }
 
-void transport_client::send(nlohmann::json json, network_send_handler handler)
+void transport_client::write(nlohmann::json json, io::write_handler handler)
 {
-    const auto self = shared_from_this();
+    const auto in_progress = queue_.size() > 0;
 
-    if (state_ != state_t::closing) {
-        do_send(json, [this, self, handler] (auto code) {
-            if (handler)
-                handler(std::move(code));
-            if (code)
-                erase();
-        });
-    }
+    queue_.emplace_back(std::move(json), std::move(handler));
+
+    if (!in_progress)
+        flush();
 }
 
-void transport_client::success(const std::string& cname, network_send_handler handler)
+void transport_client::success(const std::string& cname, io::write_handler handler)
 {
     assert(!cname.empty());
 
-    send({{ "command", cname }}, std::move(handler));
+    write({{ "command", cname }}, std::move(handler));
 }
 
-void transport_client::error(boost::system::error_code code, network_send_handler handler)
+void transport_client::error(std::error_code code, io::write_handler handler)
 {
     error(std::move(code), "", std::move(handler));
 }
 
-void transport_client::error(boost::system::error_code code,
-                             std::string cname,
-                             network_send_handler handler)
+void transport_client::error(std::error_code code, std::string cname, io::write_handler handler)
 {
     assert(code);
 
@@ -90,11 +98,13 @@
     if (!cname.empty())
         json["command"] = std::move(cname);
 
-    send(std::move(json), [this, handler] (auto code) {
+    const auto self = shared_from_this();
+
+    write(std::move(json), [this, handler, self] (auto code) {
+        erase();
+
         if (handler)
             handler(code);
-
-        erase();
     });
 
     state_ = state_t::closing;
--- a/libirccd/irccd/daemon/transport_client.hpp	Fri Apr 06 22:06:07 2018 +0200
+++ b/libirccd/irccd/daemon/transport_client.hpp	Tue Apr 10 21:20:30 2018 +0200
@@ -19,7 +19,11 @@
 #ifndef IRCCD_DAEMON_TRANSPORT_CLIENT_HPP
 #define IRCCD_DAEMON_TRANSPORT_CLIENT_HPP
 
-#include <irccd/network_stream.hpp>
+#include <cassert>
+#include <deque>
+#include <memory>
+
+#include <irccd/stream.hpp>
 
 namespace irccd {
 
@@ -44,40 +48,25 @@
 private:
     state_t state_{state_t::authenticating};
     transport_server& parent_;
-
-    void erase();
+    std::shared_ptr<io::stream> stream_;
+    std::deque<std::pair<nlohmann::json, io::write_handler>> queue_;
 
-protected:
-    /**
-     * Request a receive operation.
-     *
-     * The implementation must call the handler once the operation has finished
-     * even in case of errors.
-     *
-     * \param handler the non-null handler
-     */
-    virtual void do_recv(network_recv_handler handler) = 0;
-
-    /**
-     * Request a send operation.
-     *
-     * The implementation must call the handler once the operation has finished
-     * even in case of errors.
-     *
-     * \param json the json message to send
-     * \param handler the non-null handler
-     */
-    virtual void do_send(nlohmann::json json, network_send_handler handler) = 0;
+    void flush();
+    void erase();
 
 public:
     /**
      * Constructor.
      *
+     * \pre stream != nullptr
      * \param server the parent
+     * \param stream the I/O stream
      */
-    inline transport_client(transport_server& server) noexcept
+    inline transport_client(transport_server& server, std::shared_ptr<io::stream> stream) noexcept
         : parent_(server)
+        , stream_(std::move(stream))
     {
+        assert(stream_);
     }
 
     /**
@@ -130,13 +119,15 @@
      *
      * Possible error codes:
      *
-     *   - boost::system::errc::network_down in case of errors,
-     *   - boost::system::errc::invalid_argument if the JSON message is invalid.
+     *   - std::errc::network_down in case of errors,
+     *   - std::errc::invalid_argument if the JSON message is invalid,
+     *   - std::errc::not_enough_memory in case of memory failure.
      *
      * \pre handler != nullptr
      * \param handler the handler
+     * \warning Another read operation MUST NOT be running.
      */
-    void recv(network_recv_handler handler);
+    void read(io::read_handler handler);
 
     /**
      * Start sending if not closed.
@@ -145,18 +136,21 @@
      *
      *   - boost::system::errc::network_down in case of errors,
      *
+     * \pre json.is_object()
      * \param json the json message
      * \param handler the optional handler
+     * \note If a write operation is running, it is postponed once ready.
      */
-    void send(nlohmann::json json, network_send_handler handler = nullptr);
+    void write(nlohmann::json json, io::write_handler handler = nullptr);
 
     /**
      * Convenient success message.
      *
-     * \param cname the command name
+     * \param command the command name
      * \param handler the optional handler
+     * \note If a write operation is running, it is postponed once ready.
      */
-    void success(const std::string& cname, network_send_handler handler = nullptr);
+    void success(const std::string& command, io::write_handler handler = nullptr);
 
     /**
      * Send an error code to the client.
@@ -164,20 +158,20 @@
      * \pre code is not 0
      * \param code the error code
      * \param handler the optional handler
+     * \note If a write operation is running, it is postponed once ready.
      */
-    void error(boost::system::error_code code, network_send_handler handler = nullptr);
+    void error(std::error_code code, io::write_handler handler = nullptr);
 
     /**
      * Send an error code to the client.
      *
      * \pre code is not 0
      * \param code the error code
-     * \param cname the command name
+     * \param command the command name
      * \param handler the optional handler
+     * \note If a write operation is running, it is postponed once ready.
      */
-    void error(boost::system::error_code code,
-               std::string cname,
-               network_send_handler handler = nullptr);
+    void error(std::error_code code, std::string command, io::write_handler handler = nullptr);
 };
 
 } // !irccd
--- a/libirccd/irccd/daemon/transport_server.cpp	Fri Apr 06 22:06:07 2018 +0200
+++ b/libirccd/irccd/daemon/transport_server.cpp	Tue Apr 10 21:20:30 2018 +0200
@@ -33,7 +33,7 @@
     assert(client);
     assert(handler);
 
-    client->recv([this, client, handler] (auto code, auto message) {
+    client->read([this, client, handler] (auto code, auto message) {
         if (code) {
             handler(std::move(code), std::move(client));
             return;
@@ -50,6 +50,7 @@
             client->error(irccd_error::invalid_auth);
             code = irccd_error::invalid_auth;
         } else {
+            clients_.insert(client);
             client->set_state(transport_client::state_t::ready);
             client->success("auth");
             code = irccd_error::no_error;
@@ -77,12 +78,16 @@
 #endif
     });
 
-    client->send(greetings, [this, client, handler] (auto code) {
-        if (code)
+    client->write(greetings, [this, client, handler] (auto code) {
+        if (code) {
             handler(std::move(code), std::move(client));
-        else if (!password_.empty())
+            return;
+        }
+
+        if (!password_.empty())
             do_auth(std::move(client), std::move(handler));
         else {
+            clients_.insert(client);
             client->set_state(transport_client::state_t::ready);
             handler(std::move(code), std::move(client));
         }
@@ -91,15 +96,16 @@
 
 void transport_server::accept(accept_handler handler)
 {
-    assert(handler);
+    acceptor_->accept([this, handler] (auto code, auto stream) {
+        if (code) {
+            handler(code, nullptr);
+            return;
+        }
 
-    do_accept([this, handler] (auto code, auto client) {
-        if (code)
-            handler(std::move(code), nullptr);
-        else {
-            clients_.insert(client);
-            do_greetings(std::move(client), std::move(handler));
-        }
+        do_greetings(
+            std::make_shared<transport_client>(*this, std::move(stream)),
+            std::move(handler)
+        );
     });
 }
 
@@ -108,9 +114,9 @@
 {
 }
 
-const boost::system::error_category& transport_category() noexcept
+const std::error_category& transport_category() noexcept
 {
-    static const class category : public boost::system::error_category {
+    static const class category : public std::error_category {
     public:
         const char* name() const noexcept override
         {
@@ -151,7 +157,7 @@
     return category;
 };
 
-boost::system::error_code make_error_code(transport_error::error e) noexcept
+std::error_code make_error_code(transport_error::error e) noexcept
 {
     return {static_cast<int>(e), transport_category()};
 }
--- a/libirccd/irccd/daemon/transport_server.hpp	Fri Apr 06 22:06:07 2018 +0200
+++ b/libirccd/irccd/daemon/transport_server.hpp	Tue Apr 10 21:20:30 2018 +0200
@@ -27,7 +27,7 @@
 #include <unordered_set>
 #include <type_traits>
 
-#include <boost/asio.hpp>
+#include <irccd/acceptor.hpp>
 
 #include "transport_client.hpp"
 
@@ -130,38 +130,29 @@
     using client_set = std::unordered_set<std::shared_ptr<transport_client>>;
 
     /**
-     * Callback when a new client should be accepted.
+     * Accept completion handler.
      */
-    using accept_handler = std::function<void (
-        boost::system::error_code,
-        std::shared_ptr<transport_client>
-    )>;
+    using accept_handler = std::function<void (std::error_code, std::shared_ptr<transport_client>)>;
 
 private:
-    boost::asio::io_service& service_;
     client_set clients_;
+    std::unique_ptr<io::acceptor> acceptor_;
     std::string password_;
 
     void do_auth(std::shared_ptr<transport_client>, accept_handler);
     void do_greetings(std::shared_ptr<transport_client>, accept_handler);
 
-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_handler handler) = 0;
-
 public:
     /**
-     * Default constructor.
+     * Constructor.
+     *
+     * \pre acceptor != nullptr
+     * \param acceptor the stream acceptor
      */
-    inline transport_server(boost::asio::io_service& service) noexcept
-        : service_(service)
+    inline transport_server(std::unique_ptr<io::acceptor> acceptor) noexcept
+        : acceptor_(std::move(acceptor))
     {
+        assert(acceptor_);
     }
 
     /**
@@ -170,37 +161,17 @@
     virtual ~transport_server() noexcept = default;
 
     /**
-     * Accept a new client into the transport server.
+     * Accept a client.
      *
      * Also perform greetings and authentication under the hood. On success, the
      * client is added into the server and is ready to use.
      *
-     * \pre accept != nullptr
-     * \param handler the handler
+     * \pre handler != nullptr
+     * \param handler the completion handler
      */
     void accept(accept_handler handler);
 
     /**
-     * Get the io service.
-     *
-     * \return the service
-     */
-    inline const boost::asio::io_service& get_service() const noexcept
-    {
-        return service_;
-    }
-
-    /**
-     * Overloaded function.
-     *
-     * \return the service
-     */
-    inline boost::asio::io_service& get_service() noexcept
-    {
-        return service_;
-    }
-
-    /**
      * Get the clients.
      *
      * \return the clients
@@ -244,7 +215,7 @@
 /**
  * \brief Transport error.
  */
-class transport_error : public boost::system::system_error {
+class transport_error : public std::system_error {
 public:
     /**
      * \brief Transport related errors.
@@ -300,27 +271,23 @@
  *
  * \return the singleton
  */
-const boost::system::error_category& transport_category() noexcept;
+const std::error_category& transport_category() noexcept;
 
 /**
  * Create a boost::system::error_code from server_error::error enum.
  *
  * \param e the error code
  */
-boost::system::error_code make_error_code(transport_error::error e) noexcept;
+std::error_code make_error_code(transport_error::error e) noexcept;
 
 } // !irccd
 
-namespace boost {
-
-namespace system {
+namespace std {
 
 template <>
 struct is_error_code_enum<irccd::transport_error::error> : public std::true_type {
 };
 
-} // !system
-
-} // !boost
+} // !std
 
 #endif // !IRCCD_DAEMON_TRANSPORT_SERVER_HPP
--- a/libirccd/irccd/daemon/transport_util.cpp	Fri Apr 06 22:06:07 2018 +0200
+++ b/libirccd/irccd/daemon/transport_util.cpp	Tue Apr 10 21:20:30 2018 +0200
@@ -22,18 +22,14 @@
 
 #include <irccd/ini_util.hpp>
 #include <irccd/string_util.hpp>
-
-#include <irccd/daemon/ip_transport_server.hpp>
-
-#if !defined(IRCCD_SYSTEM_WINDOWS)
-#   include <irccd/daemon/local_transport_server.hpp>
-#endif
+#include <irccd/socket_acceptor.hpp>
 
 #if defined(HAVE_SSL)
-#   include <irccd/daemon/tls_transport_server.hpp>
+#   include <irccd/tls_acceptor.hpp>
 #endif
 
 #include "transport_util.hpp"
+#include "transport_server.hpp"
 
 using namespace boost::asio;
 using namespace boost::asio::ip;
@@ -119,11 +115,13 @@
         ctx.use_private_key_file(key, boost::asio::ssl::context::pem);
         ctx.use_certificate_file(cert, boost::asio::ssl::context::pem);
 
-        return std::make_unique<tls_transport_server>(service, std::move(acceptor), std::move(ctx));
+        return std::make_unique<transport_server>(
+            std::make_unique<io::tls_acceptor<>>(std::move(ctx), std::move(acceptor)));
 #endif
     }
 
-    return std::make_unique<ip_transport_server>(service, std::move(acceptor));
+    return std::make_unique<transport_server>(
+        std::make_unique<io::ip_acceptor>(std::move(acceptor)));
 }
 
 std::unique_ptr<transport_server> from_config_load_unix(io_service& service, const ini::section& sc)
@@ -144,7 +142,8 @@
     stream_protocol::endpoint endpoint(path);
     stream_protocol::acceptor acceptor(service, std::move(endpoint));
 
-    return std::make_unique<local_transport_server>(service, std::move(acceptor));
+    return std::make_unique<transport_server>(
+        std::make_unique<io::local_acceptor>(std::move(acceptor)));
 #else
     (void)service;
     (void)sc;
--- a/libirccdctl/CMakeLists.txt	Fri Apr 06 22:06:07 2018 +0200
+++ b/libirccdctl/CMakeLists.txt	Tue Apr 10 21:20:30 2018 +0200
@@ -20,23 +20,14 @@
 
 set(
     HEADERS
-    ${libirccdctl_SOURCE_DIR}/irccd/ctl/basic_connection.hpp
     ${libirccdctl_SOURCE_DIR}/irccd/ctl/controller.hpp
-    ${libirccdctl_SOURCE_DIR}/irccd/ctl/connection.hpp
-    ${libirccdctl_SOURCE_DIR}/irccd/ctl/ip_connection.hpp
 )
 
 set(
     SOURCES
     ${libirccdctl_SOURCE_DIR}/irccd/ctl/controller.cpp
-    ${libirccdctl_SOURCE_DIR}/irccd/ctl/ip_connection.cpp
 )
 
-if (NOT IRCCD_SYSTEM_WINDOWS)
-    list(APPEND HEADERS ${libirccdctl_SOURCE_DIR}/irccd/ctl/local_connection.hpp)
-    list(APPEND SOURCES ${libirccdctl_SOURCE_DIR}/irccd/ctl/local_connection.cpp)
-endif ()
-
 irccd_define_library(
     TARGET libirccdctl
     SOURCES
--- a/libirccdctl/irccd/ctl/basic_connection.hpp	Fri Apr 06 22:06:07 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,94 +0,0 @@
-/*
- * basic_connection.hpp -- network based connection for controller
- *
- * Copyright (c) 2013-2018 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_CTL_BASIC_CONNECTION_HPP
-#define IRCCD_CTL_BASIC_CONNECTION_HPP
-
-/**
- * \file basic_connection.hpp
- * \brief Network based connection for controller.
- */
-
-#include <irccd/network_stream.hpp>
-
-#include "connection.hpp"
-
-namespace irccd {
-
-namespace ctl {
-
-/**
- * \brief Network based connection for controller.
- *
- * This class implements recv and send functions for Boost.Asio based sockets,
- * the subclasses only need to implement a connect function.
- */
-template <typename Socket>
-class basic_connection : public connection {
-protected:
-    network_stream<Socket> stream_;
-
-public:
-    /**
-     * Construct the network connection.
-     *
-     * \param args the arguments to pass to the socket
-     */
-    template <typename... Args>
-    inline basic_connection(Args&&... args)
-        : stream_(std::forward<Args>(args)...)
-    {
-    }
-
-    /**
-     * Tells if the stream has pending actions.
-     *
-     * \return true if receiving/sending
-     */
-    bool is_active() const noexcept
-    {
-        return stream_.is_active();
-    }
-
-    /**
-     * Implements connection::recv using boost::asio::async_read_until.
-     *
-     * \param handler the handler
-     */
-    void recv(network_recv_handler handler) override
-    {
-        stream_.recv(std::move(handler));
-    }
-
-    /**
-     * Implements connection::send using boost::asio::async_write.
-     *
-     * \param json the JSON message
-     * \param handler the handler
-     */
-    void send(nlohmann::json json, network_send_handler handler) override
-    {
-        stream_.send(std::move(json), std::move(handler));
-    }
-};
-
-} // !ctl
-
-} // !irccd
-
-#endif // !IRCCD_CTL_BASIC_CONNECTION_HPP
--- a/libirccdctl/irccd/ctl/connection.hpp	Fri Apr 06 22:06:07 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,79 +0,0 @@
-/*
- * connection.hpp -- abstract connection for irccdctl
- *
- * Copyright (c) 2013-2018 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_CTL_CONNECTION_HPP
-#define IRCCD_CTL_CONNECTION_HPP
-
-/**
- * \file connection.hpp
- * \brief Abstract connection for irccdctl.
- */
-
-#include <boost/system/error_code.hpp>
-
-#include <json.hpp>
-
-namespace irccd {
-
-namespace ctl {
-
-/**
- * \brief Abstract connection for irccdctl.
- */
-class connection {
-public:
-    /**
-     * Connect handler.
-     *
-     * Call the handler when the underlying protocol connection is complete.
-     */
-    using connect_t = std::function<void (boost::system::error_code)>;
-
-    /**
-     * Tells if operations are in progress.
-     */
-    virtual bool is_active() const noexcept = 0;
-
-    /**
-     * Connect to the daemon.
-     *
-     * \param handler the non-null handler
-     */
-    virtual void connect(connect_t handler) = 0;
-
-    /**
-     * Receive from irccd.
-     *
-     * \param handler the completion handler
-     */
-    virtual void recv(network_recv_handler handler) = 0;
-
-    /**
-     * Send to irccd.
-     *
-     * \param json the json object
-     * \param handler the completion handler
-     */
-    virtual void send(nlohmann::json json, network_send_handler handler) = 0;
-};
-
-} // !ctl
-
-} // !irccd
-
-#endif // !IRCCD_CTL_CONNECTION_HPP
--- a/libirccdctl/irccd/ctl/controller.cpp	Fri Apr 06 22:06:07 2018 +0200
+++ b/libirccdctl/irccd/ctl/controller.cpp	Tue Apr 10 21:20:30 2018 +0200
@@ -27,34 +27,33 @@
 #include <irccd/daemon/rule.hpp>
 
 #include "controller.hpp"
-#include "connection.hpp"
 
 namespace irccd {
 
 namespace ctl {
 
-void controller::authenticate(connect_t handler, nlohmann::json info)
+void controller::authenticate(connect_handler handler, nlohmann::json info)
 {
-    auto cmd = nlohmann::json::object({
-        { "command", "auth" },
-        { "password", password_ }
+    const auto cmd = nlohmann::json::object({
+        { "command",    "auth"      },
+        { "password",   password_   }
     });
 
-    send(std::move(cmd), [handler, info, this] (auto code) {
+    write(cmd, [handler, info, this] (auto code) {
         if (code) {
             handler(std::move(code), nullptr);
             return;
         }
 
-        recv([handler, info] (auto code, auto) {
+        read([handler, info] (auto code, auto) {
             handler(std::move(code), std::move(info));
         });
     });
 }
 
-void controller::verify(connect_t handler)
+void controller::verify(connect_handler handler)
 {
-    recv([handler, this] (auto code, auto message) {
+    read([handler, this] (auto code, auto message) {
         if (code) {
             handler(std::move(code), std::move(message));
             return;
@@ -77,25 +76,30 @@
     });
 }
 
-void controller::connect(connect_t handler)
+void controller::connect(connect_handler handler)
 {
     assert(handler);
 
-    conn_.connect([handler, this] (auto code) {
+    connector_->connect([handler, this] (auto code, auto stream) {
         if (code)
             handler(std::move(code), nullptr);
-        else
+        else {
+            stream_ = std::move(stream);
             verify(std::move(handler));
+        }
     });
 }
 
-void controller::recv(network_recv_handler handler)
+void controller::read(io::read_handler handler)
 {
     assert(handler);
+    assert(stream_);
 
-    // TODO: ensure connected.
-    conn_.recv([handler] (auto code, auto msg) {
+    auto stream = stream_;
+
+    stream_->read([this, handler, stream] (auto code, auto msg) {
         if (code) {
+            stream_ = nullptr;
             handler(std::move(code), std::move(msg));
             return;
         }
@@ -119,12 +123,19 @@
     });
 }
 
-void controller::send(nlohmann::json message, network_send_handler handler)
+void controller::write(nlohmann::json message, io::write_handler handler)
 {
     assert(message.is_object());
+    assert(stream_);
 
-    // TODO: ensure connected.
-    conn_.send(std::move(message), std::move(handler));
+    auto stream = stream_;
+
+    stream_->write(std::move(message), [this, stream, handler] (auto code) {
+        if (code)
+            stream_ = nullptr;
+        if (handler)
+            handler(std::move(code));
+    });
 }
 
 } // !ctl
--- a/libirccdctl/irccd/ctl/controller.hpp	Fri Apr 06 22:06:07 2018 +0200
+++ b/libirccdctl/irccd/ctl/controller.hpp	Tue Apr 10 21:20:30 2018 +0200
@@ -24,14 +24,15 @@
  * \brief Main irccdctl interface.
  */
 
-#include <irccd/network_stream.hpp>
+#include <cassert>
+
+#include <irccd/connector.hpp>
+#include <irccd/stream.hpp>
 
 namespace irccd {
 
 namespace ctl {
 
-class connection;
-
 /**
  * \brief Main irccdctl interface.
  *
@@ -43,50 +44,50 @@
  *
  * It is implemented in mind that connection are asynchronous even though this
  * is not necessary.
- *
- * \see connection
- * \see network_connection
- * \see local_connection
- * \see ip_connection
- * \see tls_connection
  */
 class controller {
 public:
     /**
-     * Connection handler.
+     * Connection completion handler.
      *
      * This callback is called when connection has been completed or failed. In
      * both case, the error code is set and the JSON object may contain the
      * irccd program information.
      */
-    using connect_t = std::function<void (boost::system::error_code, nlohmann::json)>;
+    using connect_handler = std::function<void (std::error_code, nlohmann::json)>;
 
 private:
-    connection& conn_;
+    std::unique_ptr<io::connector> connector_;
+    std::shared_ptr<io::stream> stream_;
     std::string password_;
 
-    void authenticate(connect_t, nlohmann::json);
-    void verify(connect_t);
+    void authenticate(connect_handler, nlohmann::json);
+    void verify(connect_handler);
 
 public:
     /**
      * Construct the controller with its connection.
      *
+     * \pre connector != nullptr
+     * \
      * \note no connect attempt is done
      */
-    inline controller(connection& conn) noexcept
-        : conn_(conn)
+    inline controller(std::unique_ptr<io::connector> connector) noexcept
+        : connector_(std::move(connector))
     {
+        assert(connector_);
     }
 
+#if 0
+
     /**
      * Access the underlying connection.
      *
      * \return the connection
      */
-    inline const connection& get_conn() const noexcept
+    inline const connection& get_stream() const noexcept
     {
-        return conn_;
+        return *stream_;
     }
 
     /**
@@ -94,10 +95,11 @@
      *
      * \return the connection
      */
-    inline connection& get_conn() noexcept
+    inline connection& get_stream() noexcept
     {
-        return conn_;
+        return *stream_;
     }
+#endif
 
     /**
      * Get the optional password set.
@@ -128,7 +130,7 @@
      * \pre handler != nullptr
      * \param handler the handler
      */
-    void connect(connect_t handler);
+    void connect(connect_handler handler);
 
     /**
      * Queue a receive operation, if receive operations are already running, it
@@ -137,7 +139,7 @@
      * \pre handler != nullptr
      * \param handler the recv handler
      */
-    void recv(network_recv_handler handler);
+    void read(io::read_handler handler);
 
     /**
      * Queue a send operation, if receive operations are already running, it is
@@ -147,7 +149,7 @@
      * \param message the JSON message
      * \param handler the optional completion handler
      */
-    void send(nlohmann::json message, network_send_handler handler = nullptr);
+    void write(nlohmann::json message, io::write_handler handler = nullptr);
 };
 
 } // !ctl
--- a/libirccdctl/irccd/ctl/ip_connection.cpp	Fri Apr 06 22:06:07 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,90 +0,0 @@
-/*
- * ip_connection.cpp -- TCP/IP and SSL connections
- *
- * Copyright (c) 2013-2018 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 "ip_connection.hpp"
-
-using resolver = boost::asio::ip::tcp::resolver;
-
-namespace irccd {
-
-namespace ctl {
-
-namespace {
-
-template <typename Socket>
-void do_connect(Socket& socket, resolver::iterator it, connection::connect_t handler)
-{
-    socket.close();
-    socket.async_connect(*it, [&socket, it, handler] (auto code) mutable {
-        if (code && ++it != resolver::iterator())
-            do_connect(socket, it, std::move(handler));
-        else
-            handler(code);
-    });
-}
-
-template <typename Socket>
-void do_resolve(const std::string& host,
-                const std::string& port,
-                Socket& socket,
-                resolver& resolver,
-                connection::connect_t handler)
-{
-    resolver::query query(host, port);
-
-    resolver.async_resolve(query, [&socket, handler] (auto code, auto it) {
-        if (code)
-            handler(code);
-        else
-            do_connect(socket, it, std::move(handler));
-    });
-}
-
-} // !namespace
-
-void ip_connection::connect(connect_t handler)
-{
-    do_resolve(host_, std::to_string(port_), stream_.get_socket(), resolver_, std::move(handler));
-}
-
-#if defined(HAVE_SSL)
-
-void tls_connection::handshake(connect_t handler)
-{
-    stream_.get_socket().async_handshake(boost::asio::ssl::stream_base::client, [handler] (auto code) {
-        handler(code);
-    });
-}
-
-void tls_connection::connect(connect_t handler)
-{
-    const auto port = std::to_string(port_);
-
-    do_resolve(host_, port, stream_.get_socket().lowest_layer(), resolver_, [handler, this] (auto code) {
-        if (code)
-            handler(code);
-        else
-            handshake(std::move(handler));
-    });
-}
-
-#endif // !HAVE_SSL
-
-} // !ctl
-
-} // !irccd
--- a/libirccdctl/irccd/ctl/ip_connection.hpp	Fri Apr 06 22:06:07 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,125 +0,0 @@
-/*
- * ip_connection.hpp -- TCP/IP and SSL connections
- *
- * Copyright (c) 2013-2018 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_CTL_IP_CONNECTION_HPP
-#define IRCCD_CTL_IP_CONNECTION_HPP
-
-/**
- * \file ip_connection.hpp
- * \brief TCP/IP and SSL connections.
- */
-
-#include <irccd/sysconfig.hpp>
-
-#include <string>
-#include <cstdint>
-
-#include "basic_connection.hpp"
-
-namespace irccd {
-
-namespace ctl {
-
-/**
- * \brief Common class for both ip and tls connections.
- */
-template <typename Socket>
-class basic_ip_connection : public basic_connection<Socket> {
-protected:
-    boost::asio::ip::tcp::resolver resolver_;
-    std::string host_;
-    std::uint16_t port_;
-
-public:
-    /**
-     * Construct the ip connection.
-     *
-     * The socket is created as invoked like this:
-     *
-     *     socket_(service_, std::forward<Args>(args)...)
-     *
-     * \param service the io service
-     * \param host the host
-     * \param port the port number
-     * \param args extra arguments (except service) to pass to the socket constructor
-     */
-    template <typename... Args>
-    inline basic_ip_connection(boost::asio::io_service& service, std::string host, std::uint16_t port, Args&&... args)
-        : basic_connection<Socket>(service, std::forward<Args>(args)...)
-        , resolver_(service)
-        , host_(std::move(host))
-        , port_(std::move(port))
-    {
-    }
-};
-
-/**
- * \brief Raw TCP/IP connection.
- */
-class ip_connection : public basic_ip_connection<boost::asio::ip::tcp::socket> {
-public:
-    /**
-     * Inherited constructor.
-     */
-    using basic_ip_connection::basic_ip_connection;
-
-    /**
-     * \copydoc connection::connect
-     */
-    void connect(connect_t handler);
-};
-
-#if defined(HAVE_SSL)
-
-/**
- * \brief Secure layer connection.
- */
-class tls_connection : public basic_ip_connection<boost::asio::ssl::stream<boost::asio::ip::tcp::socket>> {
-private:
-    void handshake(connect_t);
-
-public:
-    /**
-     * Construct the TLS connection.
-     *
-     * \param service the io service
-     * \param context the context
-     * \param host the host
-     * \param port the port number
-     */
-    inline tls_connection(boost::asio::io_service& service,
-                          boost::asio::ssl::context& context,
-                          std::string host,
-                          std::uint16_t port)
-        : basic_ip_connection(service, std::move(host), port, context)
-    {
-    }
-
-    /**
-     * \copydoc connection::connect
-     */
-    void connect(connect_t handler);
-};
-
-#endif // !HAVE_SSL
-
-} // !ctl
-
-} // !irccd
-
-#endif // !IRCCD_CTL_IP_CONNECTION_HPP
--- a/libirccdctl/irccd/ctl/local_connection.cpp	Fri Apr 06 22:06:07 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,38 +0,0 @@
-/*
- * local_connection.cpp -- unix domain connection for irccdctl
- *
- * Copyright (c) 2013-2018 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 "local_connection.hpp"
-
-#if !defined(IRCCD_SYSTEM_WINDOWS)
-
-namespace irccd {
-
-namespace ctl {
-
-void local_connection::connect(connect_t handler)
-{
-    using endpoint = boost::asio::local::stream_protocol::endpoint;
-
-    stream_.get_socket().async_connect(endpoint(path_), std::move(handler));
-}
-
-} // !ctl
-
-} // !irccd
-
-#endif // !IRCCD_SYSTEM_WINDOWS
--- a/libirccdctl/irccd/ctl/local_connection.hpp	Fri Apr 06 22:06:07 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,69 +0,0 @@
-/*
- * local_connection.hpp -- unix domain connection for irccdctl
- *
- * Copyright (c) 2013-2018 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_CTL_LOCAL_CONNECTION_HPP
-#define IRCCD_CTL_LOCAL_CONNECTION_HPP
-
-/**
- * \file local_connection.hpp
- * \brief Unix domain connection for irccdctl.
- */
-
-#include "basic_connection.hpp"
-
-#if !defined(IRCCD_SYSTEM_WINDOWS)
-
-namespace irccd {
-
-namespace ctl {
-
-/**
- * \brief Unix domain connection for irccdctl.
- */
-class local_connection : public basic_connection<boost::asio::local::stream_protocol::socket> {
-private:
-    std::string path_;
-
-public:
-    /**
-     * Construct the local connection.
-     *
-     * \param service the io_service
-     * \param path the path to the socket file
-     */
-    inline local_connection(boost::asio::io_service& service, std::string path) noexcept
-        : basic_connection(service)
-        , path_(std::move(path))
-    {
-    }
-
-    /**
-     * Connect to the socket file.
-     *
-     * \param handler the handler
-     */
-    void connect(connect_t handler) override;
-};
-
-} // !ctl
-
-} // !irccd
-
-#endif // !IRCCD_SYSTEM_WINDOWS
-
-#endif // !IRCCD_CTL_LOCAL_CONNECTION_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/data/test.crt	Tue Apr 10 21:20:30 2018 +0200
@@ -0,0 +1,21 @@
+-----BEGIN CERTIFICATE-----
+MIIDfjCCAmYCCQCZkHkS4lewTjANBgkqhkiG9w0BAQsFADCBgDELMAkGA1UEBhMC
+RlIxDzANBgNVBAgMBkFsc2FjZTEQMA4GA1UEBwwHT2Jlcm5haTESMBAGA1UECgwJ
+TWFsaWthbmlhMRUwEwYDVQQDDAxtYWxpa2FuaWEuZnIxIzAhBgkqhkiG9w0BCQEW
+FG1hcmthbmRAbWFsaWthbmlhLmZyMB4XDTE4MDQxMDEyMDgwOFoXDTI4MDEwODEy
+MDgwOFowgYAxCzAJBgNVBAYTAkZSMQ8wDQYDVQQIDAZBbHNhY2UxEDAOBgNVBAcM
+B09iZXJuYWkxEjAQBgNVBAoMCU1hbGlrYW5pYTEVMBMGA1UEAwwMbWFsaWthbmlh
+LmZyMSMwIQYJKoZIhvcNAQkBFhRtYXJrYW5kQG1hbGlrYW5pYS5mcjCCASIwDQYJ
+KoZIhvcNAQEBBQADggEPADCCAQoCggEBANdlxYhxfgoJDabzqPHYyBcNkdl0tXO2
+WOw6kO7EpQ1n0a1sMlbuFncSyYUP9ggvzGsql3vZLKYxqoOXYkcae096RBin8bqB
+ZIo6OCENxcOnIgsnkFHy9n+AtyQEJItg3/cf46BqdoMF2TW9Z2XCMCNw9IE1Bxj4
+qR2BwHK10YOUB6EQCkC3+i1aydD/Kyiiwmm+650IMQfAPjN9vW9qaQ9a7H0x98k3
+vKj5FWjiJtXVZxk920eyM42o/kbFT7jkmqFN7NPX5GjealCpq0Wg5G/XCKLy9j7Q
+DU0HRNNFGxBrfZpjEpWy2kyE2l3Iy+UPNemACBM/xcScj4yJeK9D9xUCAwEAATAN
+BgkqhkiG9w0BAQsFAAOCAQEAdOG9Jljyg6rOrm98opUxbR0fnT1IE5vbwvLsrIFP
+TRfsMSEPYsTfN0X34SlA7twyeCCqEIeY1/RBGSByaTqTfTXSGOgNKk4ZUqmAIscc
+tqlrHE44IMbMCaqphqlDBMx7dOY4C05AMHjNVXtkUGYWh/S8Fyure8DGfXh1Vop/
+1nwHThB7t/4oa3w993ODvfqVz6sI7OAJNvlGWWHKsDbovUZ6BtVgZJioeMK3Zvak
+igDPydVBG4gW2NstD9kujMrveMOAjzxSnbzJezeD/jan1u2mxHkuNrjVHA8GBHO7
+hIgdAJuLsicCm4yGu6TzZ6NQ2+EP/BdCzNfbXeYRhx1F+w==
+-----END CERTIFICATE-----
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/data/test.key	Tue Apr 10 21:20:30 2018 +0200
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpQIBAAKCAQEA12XFiHF+CgkNpvOo8djIFw2R2XS1c7ZY7DqQ7sSlDWfRrWwy
+Vu4WdxLJhQ/2CC/MayqXe9kspjGqg5diRxp7T3pEGKfxuoFkijo4IQ3Fw6ciCyeQ
+UfL2f4C3JAQki2Df9x/joGp2gwXZNb1nZcIwI3D0gTUHGPipHYHAcrXRg5QHoRAK
+QLf6LVrJ0P8rKKLCab7rnQgxB8A+M329b2ppD1rsfTH3yTe8qPkVaOIm1dVnGT3b
+R7Izjaj+RsVPuOSaoU3s09fkaN5qUKmrRaDkb9cIovL2PtANTQdE00UbEGt9mmMS
+lbLaTITaXcjL5Q816YAIEz/FxJyPjIl4r0P3FQIDAQABAoIBAQCX5I2qjRXrb7zv
+2W1utodLMmeCaDm40oLceuRPa95UmLyUxfKtuJGhAF+ZdMrztPk7LTrLvDcvL2Dm
+EO/d4j/IqRiyJmRhN/O1Qh7ouDSYpxhrs0ejcmj/Or5rKHy4yOTG+Pbk3Y6bEJm+
+usTaTljx9SqGnuVVZ6yiQSh7+9k2JfXu6MTMBwc0aWU+hCXNzuD7sWobkECJe5+D
+kPVRTz4r9GHXrQ95EjB7DxW6aKJgzxUBEf1ajk3nZVMi1rRcO7UjSWhlfVpSajCZ
+Fx5WcROoOnkVjR7r6dB5+3t2d6F1NfsZG7pnOYdxKz1EED47HLA3auOylHGdxlIa
+IsruxvYVAoGBAP9M2AJmFNTU2AWem7ULclxoZmITuRksKRbVFQWAhnYFs4zq3NLE
+Ak8PLaBEbypy3nw2n8BoQG7YjDu7ZduQaTITgDvQLZw+wyczb8oM7OktsHd4fFPo
+GY/C+KcX78lEfNdXtJjDGogaEcaIZLu/uCEE513IKOycvHCIDb6qzQtXAoGBANf8
+7R+9omQoxmz2yH83IxPZLCVsmifzc/ZjlfEz6bJPQJvd6tD/xV1O58xHX2OQrv71
+eqJiR5hOToRK5V8U0pb+5DA8knJ8eTQbSIo9aMBXJkb7qoHfXpiBf8KNTQtPPr2p
+5CkCeMbGHPXvpmaB7jK7k7k2sr2OwJB13OLT+rlzAoGBAPT9HsSehRr/7RTncXA4
+vdOjc6xmVNetIvkAHZ4cCewz6Gsv7hxrb/PCEvya7zqC5LG6EW8oG7zDHT8sw8AB
+QpGFWScMzNgE+m0h1QLFiIrzB0Z+XoB+WBk29joSpE415L8ZMPrvLwSwT6J+vHvW
+rLEy7Xt3Wp5EgihSxy3S5dUdAoGAfXj5Z88KX5UwcGyM3Rpyzj0DYFpO05aibyg7
+GvxFbsiLiADLQM7VIPeNwSZVS8npX0PMEjl1zgzvn/rCdRHrpLw+Y6dMjCWzY4nW
+AjjaeaInImVhEEFq+r2AMs1TTJakpBnl6cz9zLuaZ2TpDfO9JMvDbX1RKL2dME7I
+Gx16MfECgYEA1d9pO+4pW+iosdJJESG1VP6W3z6H7Gd7fBuVa8FJVcYbyfF+c0Vz
+2gdDJOYXBH2pEvWrrpUKoNTpNT/ei3qruxfryIVlC769Xs5EWF9/kBwzdJID+JgV
+/gNXEVnBqOs7DuJ3yXMWdS1j9W1+tFshYw00l9aCCI2YSqdd6lsfJzg=
+-----END RSA PRIVATE KEY-----
--- a/tests/src/libcommon/CMakeLists.txt	Fri Apr 06 22:06:07 2018 +0200
+++ b/tests/src/libcommon/CMakeLists.txt	Tue Apr 10 21:20:30 2018 +0200
@@ -17,5 +17,5 @@
 #
 
 add_subdirectory(fs-util)
-add_subdirectory(network-stream)
+add_subdirectory(io)
 add_subdirectory(string-util)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/src/libcommon/io/CMakeLists.txt	Tue Apr 10 21:20:30 2018 +0200
@@ -0,0 +1,23 @@
+#
+# CMakeLists.txt -- CMake build system for irccd
+#
+# Copyright (c) 2013-2018 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.
+#
+
+irccd_define_test(
+    NAME io
+    SOURCES main.cpp
+    LIBRARIES libcommon
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/src/libcommon/io/main.cpp	Tue Apr 10 21:20:30 2018 +0200
@@ -0,0 +1,223 @@
+/*
+ * main.cpp -- test io classes
+ *
+ * Copyright (c) 2013-2018 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.
+ */
+
+#define BOOST_TEST_MODULE "io"
+#include <boost/test/unit_test.hpp>
+#include <boost/mpl/list.hpp>
+
+#include <irccd/sysconfig.hpp>
+
+#include <irccd/socket_acceptor.hpp>
+#include <irccd/socket_connector.hpp>
+#include <irccd/socket_stream.hpp>
+
+#if defined(HAVE_SSL)
+#   include <irccd/tls_acceptor.hpp>
+#   include <irccd/tls_connector.hpp>
+#   include <irccd/tls_stream.hpp>
+#endif // !HAVE_SSL
+
+using boost::asio::io_service;
+using boost::asio::ip::tcp;
+
+#if defined(HAVE_SSL)
+using boost::asio::ssl::context;
+#endif
+
+#if !defined(IRCCD_SYSTEM_WINDOWS)
+using boost::asio::local::stream_protocol;
+#endif
+
+namespace irccd {
+
+class io_test {
+public:
+    io_service service_;
+
+    std::unique_ptr<io::acceptor> acceptor_;
+    std::unique_ptr<io::connector> connector_;
+
+    std::shared_ptr<io::stream> stream1_;
+    std::shared_ptr<io::stream> stream2_;
+
+    virtual std::unique_ptr<io::acceptor> create_acceptor() = 0;
+
+    virtual std::unique_ptr<io::connector> create_connector() = 0;
+
+    void init()
+    {
+        acceptor_ = create_acceptor();
+        connector_ = create_connector();
+
+        acceptor_->accept([this] (auto code, auto stream) {
+            if (code)
+                throw std::system_error(code);
+
+            stream1_ = std::move(stream);
+        });
+        connector_->connect([this] (auto code, auto stream) {
+            if (code)
+                throw std::system_error(code);
+
+            stream2_ = std::move(stream);
+        });
+
+        service_.run();
+        service_.reset();
+    }
+};
+
+class ip_io_test : public io_test {
+private:
+    tcp::endpoint endpoint_;
+
+protected:
+    /**
+     * \copydoc io_test::create_acceptor
+     */
+    std::unique_ptr<io::acceptor> create_acceptor() override
+    {
+        tcp::endpoint endpoint(tcp::v4(), 0U);
+        tcp::acceptor acceptor(service_, std::move(endpoint));
+
+        endpoint_ = acceptor.local_endpoint();
+
+        return std::make_unique<io::ip_acceptor>(std::move(acceptor));
+    }
+
+    /**
+     * \copydoc io_test::create_connector
+     */
+    std::unique_ptr<io::connector> create_connector() override
+    {
+        return std::make_unique<io::ip_connector>(service_, endpoint_);
+    }
+};
+
+#if defined(HAVE_SSL)
+
+class ssl_io_test : public io_test {
+private:
+    tcp::endpoint endpoint_;
+
+protected:
+    /**
+     * \copydoc io_test::create_acceptor
+     */
+    std::unique_ptr<io::acceptor> create_acceptor() override
+    {
+        context context(context::sslv23);
+
+        context.use_certificate_file(TESTS_SOURCE_DIR "/data/test.crt", context::pem);
+        context.use_private_key_file(TESTS_SOURCE_DIR "/data/test.key", context::pem);
+
+        tcp::endpoint endpoint(tcp::v4(), 0U);
+        tcp::acceptor acceptor(service_, std::move(endpoint));
+
+        endpoint_ = acceptor.local_endpoint();
+
+        return std::make_unique<io::tls_acceptor<>>(std::move(context), std::move(acceptor));
+    }
+
+    /**
+     * \copydoc io_test::create_connector
+     */
+    std::unique_ptr<io::connector> create_connector() override
+    {
+        return std::make_unique<io::tls_connector<>>(context(context::sslv23), service_, endpoint_);
+    }
+};
+
+#endif // !HAVE_SSL
+
+#if !defined(IRCCD_SYSTEM_WINDOWS)
+
+class local_io_test : public io_test {
+public:
+    /**
+     * \copydoc io_test::create_acceptor
+     */
+    std::unique_ptr<io::acceptor> create_acceptor() override
+    {
+        std::remove(CMAKE_BINARY_DIR "/tmp/io-test.sock");
+
+        stream_protocol::acceptor acceptor(service_, CMAKE_BINARY_DIR "/tmp/io-test.sock");
+
+        return std::make_unique<io::local_acceptor>(std::move(acceptor));
+    }
+
+    /**
+     * \copydoc io_test::create_connector
+     */
+    std::unique_ptr<io::connector> create_connector() override
+    {
+        return std::make_unique<io::local_connector>(service_, CMAKE_BINARY_DIR "/tmp/io-test.sock");
+    }
+};
+
+#endif // !IRCCD_SYSTEM_WINDOWS
+
+/**
+ * List of fixtures to tests.
+ */
+using list = boost::mpl::list<
+    ip_io_test
+#if defined(HAVE_SSL)
+    , ssl_io_test
+#endif
+#if !defined(IRCCD_SYSTEM_WINDOWS)
+    , local_io_test
+#endif
+>;
+
+BOOST_AUTO_TEST_CASE_TEMPLATE(invalid_argument, Test, list)
+{
+    Test fixture;
+
+    const nlohmann::json message{
+        { "abc", 123 },
+        { "def", 456 }
+    };
+
+    fixture.init();
+    fixture.stream1_->read([] (auto code, auto message) {
+        BOOST_TEST(!code);
+        BOOST_TEST(message.is_object());
+        BOOST_TEST(message["abc"].template get<int>() == 123);
+        BOOST_TEST(message["def"].template get<int>() == 456);
+    });
+    fixture.stream2_->write(message, [] (auto code) {
+        BOOST_TEST(!code);
+    });
+    fixture.service_.run();
+}
+
+BOOST_AUTO_TEST_CASE_TEMPLATE(network_down, Test, list)
+{
+    Test fixture;
+
+    fixture.init();
+    fixture.stream1_->read([] (auto code, auto message) {
+        BOOST_TEST(code.value() == static_cast<int>(std::errc::not_connected));
+        BOOST_TEST(message.is_null());
+    });
+    fixture.stream2_ = nullptr;
+    fixture.service_.run();
+}
+
+} // !irccd
--- a/tests/src/libcommon/network-stream/CMakeLists.txt	Fri Apr 06 22:06:07 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,23 +0,0 @@
-#
-# CMakeLists.txt -- CMake build system for irccd
-#
-# Copyright (c) 2013-2018 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.
-#
-
-irccd_define_test(
-    NAME network-stream
-    SOURCES main.cpp
-    LIBRARIES libcommon
-)
--- a/tests/src/libcommon/network-stream/main.cpp	Fri Apr 06 22:06:07 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,153 +0,0 @@
-/*
- * main.cpp -- test network_stream class
- *
- * Copyright (c) 2013-2018 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.
- */
-
-#define BOOST_TEST_MODULE "network-stream"
-#include <boost/test/unit_test.hpp>
-
-#include <irccd/network_stream.hpp>
-
-using boost::asio::io_service;
-using boost::asio::ip::tcp;
-
-namespace irccd {
-
-class network_stream_test {
-protected:
-    io_service service_;
-    tcp::socket connection_{service_};
-    ip_network_stream stream_{service_};
-
-    network_stream_test()
-    {
-        // Bind to a random port.
-        tcp::endpoint ep(tcp::v4(), 0);
-        tcp::acceptor acceptor(service_, ep);
-
-        acceptor.async_accept(connection_, [] (auto code) {
-            if (code)
-                throw boost::system::system_error(code);
-        });
-        stream_.get_socket().async_connect(acceptor.local_endpoint(), [] (auto code) {
-            if (code)
-                throw boost::system::system_error(code);
-        });
-
-        service_.run();
-        service_.reset();
-    }
-};
-
-BOOST_FIXTURE_TEST_SUITE(network_stream_test_suite, network_stream_test)
-
-BOOST_AUTO_TEST_CASE(multiple_recv)
-{
-    const auto msg1 = nlohmann::json({{ "abc", 123 }}).dump(0) + "\r\n\r\n";
-    const auto msg2 = nlohmann::json({{ "def", 456 }}).dump(0) + "\r\n\r\n";
-
-    stream_.recv([] (auto code, auto message) {
-        BOOST_TEST(!code);
-        BOOST_TEST(message["abc"].template get<int>() == 123);
-    });
-    stream_.recv([] (auto code, auto message) {
-        BOOST_TEST(!code);
-        BOOST_TEST(message["def"].template get<int>() == 456);
-    });
-
-    boost::asio::async_write(connection_, boost::asio::buffer(msg1), [] (auto code, auto) {
-        BOOST_TEST(!code);
-    });
-    boost::asio::async_write(connection_, boost::asio::buffer(msg2), [] (auto code, auto) {
-        BOOST_TEST(!code);
-    });
-
-    service_.run();
-}
-
-BOOST_AUTO_TEST_CASE(multiple_send)
-{
-    boost::asio::streambuf input;
-
-    stream_.send({{ "abc", 123 }}, [] (auto code) {
-        BOOST_TEST(!code);
-    });
-    stream_.send({{ "def", 456 }}, [] (auto code) {
-        BOOST_TEST(!code);
-    });
-
-    boost::asio::async_read_until(connection_, input, "\r\n\r\n", [&] (auto code, auto xfer) {
-        BOOST_TEST(!code);
-
-        const auto json = nlohmann::json::parse(std::string(
-            boost::asio::buffers_begin(input.data()),
-            boost::asio::buffers_begin(input.data()) + xfer - 4
-        ));
-
-        input.consume(xfer);
-
-        BOOST_TEST(json["abc"].template get<int>() == 123);
-    });
-
-    service_.run();
-    service_.reset();
-
-    boost::asio::async_read_until(connection_, input, "\r\n\r\n", [&] (auto code, auto xfer) {
-        BOOST_TEST(!code);
-
-        const auto json = nlohmann::json::parse(std::string(
-            boost::asio::buffers_begin(input.data()),
-            boost::asio::buffers_begin(input.data()) + xfer - 4
-        ));
-
-        input.consume(xfer);
-
-        BOOST_TEST(json["def"].template get<int>() == 456);
-    });
-
-    service_.run();
-}
-
-BOOST_AUTO_TEST_CASE(invalid_argument)
-{
-    const std::string msg1("not a json object\r\n\r\n");
-
-    stream_.recv([] (auto code, auto message) {
-        BOOST_TEST(code == boost::system::errc::invalid_argument);
-        BOOST_TEST(message.is_null());
-    });
-
-    boost::asio::async_write(connection_, boost::asio::buffer(msg1), [] (auto code, auto) {
-        BOOST_TEST(!code);
-    });
-
-    service_.run();
-}
-
-BOOST_AUTO_TEST_CASE(network_down)
-{
-    stream_.recv([] (auto code, auto message) {
-        BOOST_TEST(code == boost::system::errc::network_down);
-        BOOST_TEST(message.is_null());
-    });
-
-    connection_.close();
-    service_.run();
-}
-
-BOOST_AUTO_TEST_SUITE_END()
-
-} // !irccd
--- a/tests/src/libirccd/command-plugin-config/main.cpp	Fri Apr 06 22:06:07 2018 +0200
+++ b/tests/src/libirccd/command-plugin-config/main.cpp	Tue Apr 10 21:20:30 2018 +0200
@@ -55,7 +55,7 @@
 BOOST_AUTO_TEST_CASE(set)
 {
     daemon_->plugins().add(std::make_unique<custom_plugin>("test"));
-    ctl_->send({
+    ctl_->write({
         { "command",    "plugin-config" },
         { "plugin",     "test"          },
         { "variable",   "verbosy"       },
--- a/tests/src/libirccd/command-plugin-load/main.cpp	Fri Apr 06 22:06:07 2018 +0200
+++ b/tests/src/libirccd/command-plugin-load/main.cpp	Tue Apr 10 21:20:30 2018 +0200
@@ -81,7 +81,7 @@
 
 BOOST_AUTO_TEST_CASE(basic)
 {
-    ctl_->send({
+    ctl_->write({
         { "command",    "plugin-load"   },
         { "plugin",     "magic"         }
     });
--- a/tests/src/libirccd/command-plugin-reload/main.cpp	Fri Apr 06 22:06:07 2018 +0200
+++ b/tests/src/libirccd/command-plugin-reload/main.cpp	Tue Apr 10 21:20:30 2018 +0200
@@ -71,7 +71,7 @@
 
 BOOST_AUTO_TEST_CASE(basic)
 {
-    ctl_->send({
+    ctl_->write({
         { "command",    "plugin-reload" },
         { "plugin",     "test"          }
     });
--- a/tests/src/libirccd/command-plugin-unload/main.cpp	Fri Apr 06 22:06:07 2018 +0200
+++ b/tests/src/libirccd/command-plugin-unload/main.cpp	Tue Apr 10 21:20:30 2018 +0200
@@ -71,7 +71,7 @@
 
 BOOST_AUTO_TEST_CASE(basic)
 {
-    ctl_->send({
+    ctl_->write({
         { "command",    "plugin-unload" },
         { "plugin",     "test"          }
     });
--- a/tests/src/libirccd/command-rule-move/main.cpp	Fri Apr 06 22:06:07 2018 +0200
+++ b/tests/src/libirccd/command-rule-move/main.cpp	Tue Apr 10 21:20:30 2018 +0200
@@ -70,12 +70,12 @@
 {
     nlohmann::json result;
 
-    ctl_->send({
+    ctl_->write({
         { "command",    "rule-move" },
         { "from",       2           },
         { "to",         0           }
     });
-    ctl_->recv([&] (auto, auto msg) {
+    ctl_->read([&] (auto, auto msg) {
         result = msg;
     });
 
@@ -86,8 +86,8 @@
     BOOST_TEST(result.is_object());
 
     result = nullptr;
-    ctl_->send({{ "command", "rule-list" }});
-    ctl_->recv([&] (auto, auto msg) {
+    ctl_->write({{ "command", "rule-list" }});
+    ctl_->read([&] (auto, auto msg) {
         result = msg;
     });
 
@@ -144,12 +144,12 @@
 {
     nlohmann::json result;
 
-    ctl_->send({
+    ctl_->write({
         { "command",    "rule-move" },
         { "from",       0           },
         { "to",         2           }
     });
-    ctl_->recv([&] (auto, auto msg) {
+    ctl_->read([&] (auto, auto msg) {
         result = msg;
     });
 
@@ -160,8 +160,8 @@
     BOOST_TEST(result.is_object());
 
     result = nullptr;
-    ctl_->send({{ "command", "rule-list" }});
-    ctl_->recv([&] (auto, auto msg) {
+    ctl_->write({{ "command", "rule-list" }});
+    ctl_->read([&] (auto, auto msg) {
         result = msg;
     });
 
@@ -218,12 +218,12 @@
 {
     nlohmann::json result;
 
-    ctl_->send({
+    ctl_->write({
         { "command",    "rule-move" },
         { "from",       1           },
         { "to",         1           }
     });
-    ctl_->recv([&] (auto, auto msg) {
+    ctl_->read([&] (auto, auto msg) {
         result = msg;
     });
 
@@ -234,8 +234,8 @@
     BOOST_TEST(result.is_object());
 
     result = nullptr;
-    ctl_->send({{ "command", "rule-list" }});
-    ctl_->recv([&] (auto, auto msg) {
+    ctl_->write({{ "command", "rule-list" }});
+    ctl_->read([&] (auto, auto msg) {
         result = msg;
     });
 
@@ -292,12 +292,12 @@
 {
     nlohmann::json result;
 
-    ctl_->send({
+    ctl_->write({
         { "command",    "rule-move" },
         { "from",       0           },
         { "to",         123         }
     });
-    ctl_->recv([&] (auto, auto msg) {
+    ctl_->read([&] (auto, auto msg) {
         result = msg;
     });
 
@@ -308,8 +308,8 @@
     BOOST_TEST(result.is_object());
 
     result = nullptr;
-    ctl_->send({{ "command", "rule-list" }});
-    ctl_->recv([&] (auto, auto msg) {
+    ctl_->write({{ "command", "rule-list" }});
+    ctl_->read([&] (auto, auto msg) {
         result = msg;
     });
 
@@ -366,122 +366,67 @@
 
 BOOST_AUTO_TEST_CASE(invalid_index_1_from)
 {
-    boost::system::error_code result;
-    nlohmann::json message;
-
-    ctl_->send({
+    const auto result = request({
         { "command",    "rule-move" },
         { "from",       -100        },
         { "to",         0           }
     });
-    ctl_->recv([&] (auto rresult, auto rmessage) {
-        result = rresult;
-        message = rmessage;
-    });
 
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_TEST(result == rule_error::invalid_index);
-    BOOST_TEST(message["error"].template get<int>() == rule_error::invalid_index);
-    BOOST_TEST(message["errorCategory"].template get<std::string>() == "rule");
+    BOOST_TEST(result.second == rule_error::invalid_index);
+    BOOST_TEST(result.first["error"].template get<int>() == rule_error::invalid_index);
+    BOOST_TEST(result.first["errorCategory"].template get<std::string>() == "rule");
 }
 
 BOOST_AUTO_TEST_CASE(invalid_index_1_to)
 {
-    boost::system::error_code result;
-    nlohmann::json message;
-
-    ctl_->send({
+    const auto result = request({
         { "command",    "rule-move" },
         { "from",       0           },
         { "to",         -100        }
     });
-    ctl_->recv([&] (auto rresult, auto rmessage) {
-        result = rresult;
-        message = rmessage;
-    });
 
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_TEST(result == rule_error::invalid_index);
-    BOOST_TEST(message["error"].template get<int>() == rule_error::invalid_index);
-    BOOST_TEST(message["errorCategory"].template get<std::string>() == "rule");
+    BOOST_TEST(result.second == rule_error::invalid_index);
+    BOOST_TEST(result.first["error"].template get<int>() == rule_error::invalid_index);
+    BOOST_TEST(result.first["errorCategory"].template get<std::string>() == "rule");
 }
 
 BOOST_AUTO_TEST_CASE(invalid_index_2_from)
 {
-    boost::system::error_code result;
-    nlohmann::json message;
-
-    ctl_->send({
+    const auto result = request({
         { "command",    "rule-move" },
         { "from",       100         },
         { "to",         0           }
     });
-    ctl_->recv([&] (auto rresult, auto rmessage) {
-        result = rresult;
-        message = rmessage;
-    });
 
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_TEST(result == rule_error::invalid_index);
-    BOOST_TEST(message["error"].template get<int>() == rule_error::invalid_index);
-    BOOST_TEST(message["errorCategory"].template get<std::string>() == "rule");
+    BOOST_TEST(result.second == rule_error::invalid_index);
+    BOOST_TEST(result.first["error"].template get<int>() == rule_error::invalid_index);
+    BOOST_TEST(result.first["errorCategory"].template get<std::string>() == "rule");
 }
 
 BOOST_AUTO_TEST_CASE(invalid_index_3_from)
 {
-    boost::system::error_code result;
-    nlohmann::json message;
-
-    ctl_->send({
+    const auto result = request({
         { "command",    "rule-move" },
         { "from",       "notaint"   },
         { "to",         0           }
     });
-    ctl_->recv([&] (auto rresult, auto rmessage) {
-        result = rresult;
-        message = rmessage;
-    });
 
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_TEST(result == rule_error::invalid_index);
-    BOOST_TEST(message["error"].template get<int>() == rule_error::invalid_index);
-    BOOST_TEST(message["errorCategory"].template get<std::string>() == "rule");
+    BOOST_TEST(result.second == rule_error::invalid_index);
+    BOOST_TEST(result.first["error"].template get<int>() == rule_error::invalid_index);
+    BOOST_TEST(result.first["errorCategory"].template get<std::string>() == "rule");
 }
 
 BOOST_AUTO_TEST_CASE(invalid_index_3_to)
 {
-    boost::system::error_code result;
-    nlohmann::json message;
-
-    ctl_->send({
+    const auto result = request({
         { "command",    "rule-move" },
         { "from",       0           },
         { "to",         "notaint"   }
     });
-    ctl_->recv([&] (auto rresult, auto rmessage) {
-        result = rresult;
-        message = rmessage;
-    });
 
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_TEST(result == rule_error::invalid_index);
-    BOOST_TEST(message["error"].template get<int>() == rule_error::invalid_index);
-    BOOST_TEST(message["errorCategory"].template get<std::string>() == "rule");
+    BOOST_TEST(result.second == rule_error::invalid_index);
+    BOOST_TEST(result.first["error"].template get<int>() == rule_error::invalid_index);
+    BOOST_TEST(result.first["errorCategory"].template get<std::string>() == "rule");
 }
 
 BOOST_AUTO_TEST_SUITE_END()
--- a/tests/src/libirccd/command-server-invite/main.cpp	Fri Apr 06 22:06:07 2018 +0200
+++ b/tests/src/libirccd/command-server-invite/main.cpp	Tue Apr 10 21:20:30 2018 +0200
@@ -45,7 +45,7 @@
 
 BOOST_AUTO_TEST_CASE(basic)
 {
-    ctl_->send({
+    ctl_->write({
         { "command",    "server-invite"     },
         { "server",     "test"              },
         { "target",     "francis"           },
--- a/tests/src/libirccd/command-server-join/main.cpp	Fri Apr 06 22:06:07 2018 +0200
+++ b/tests/src/libirccd/command-server-join/main.cpp	Tue Apr 10 21:20:30 2018 +0200
@@ -45,7 +45,7 @@
 
 BOOST_AUTO_TEST_CASE(basic)
 {
-    ctl_->send({
+    ctl_->write({
         { "command",    "server-join"       },
         { "server",     "test"              },
         { "channel",    "#music"            },
--- a/tests/src/libirccd/command-server-kick/main.cpp	Fri Apr 06 22:06:07 2018 +0200
+++ b/tests/src/libirccd/command-server-kick/main.cpp	Tue Apr 10 21:20:30 2018 +0200
@@ -45,7 +45,7 @@
 
 BOOST_AUTO_TEST_CASE(basic)
 {
-    ctl_->send({
+    ctl_->write({
         { "command",    "server-kick"       },
         { "server",     "test"              },
         { "target",     "francis"           },
@@ -67,7 +67,7 @@
 
 BOOST_AUTO_TEST_CASE(noreason)
 {
-    ctl_->send({
+    ctl_->write({
         { "command",    "server-kick"       },
         { "server",     "test"              },
         { "target",     "francis"           },
--- a/tests/src/libirccd/command-server-mode/main.cpp	Fri Apr 06 22:06:07 2018 +0200
+++ b/tests/src/libirccd/command-server-mode/main.cpp	Tue Apr 10 21:20:30 2018 +0200
@@ -45,7 +45,7 @@
 
 BOOST_AUTO_TEST_CASE(basic)
 {
-    ctl_->send({
+    ctl_->write({
         { "command",    "server-mode"   },
         { "server",     "test"          },
         { "channel",    "#irccd"        },
--- a/tests/src/libirccd/command-server-notice/main.cpp	Fri Apr 06 22:06:07 2018 +0200
+++ b/tests/src/libirccd/command-server-notice/main.cpp	Tue Apr 10 21:20:30 2018 +0200
@@ -45,7 +45,7 @@
 
 BOOST_AUTO_TEST_CASE(basic)
 {
-    ctl_->send({
+    ctl_->write({
         { "command",    "server-notice" },
         { "server",     "test"          },
         { "target",     "#staff"        },
--- a/tests/src/libirccd/command-server-part/main.cpp	Fri Apr 06 22:06:07 2018 +0200
+++ b/tests/src/libirccd/command-server-part/main.cpp	Tue Apr 10 21:20:30 2018 +0200
@@ -45,7 +45,7 @@
 
 BOOST_AUTO_TEST_CASE(basic)
 {
-    ctl_->send({
+    ctl_->write({
         { "command",    "server-part"   },
         { "server",     "test"          },
         { "channel",    "#staff"        },
@@ -65,7 +65,7 @@
 
 BOOST_AUTO_TEST_CASE(noreason)
 {
-    ctl_->send({
+    ctl_->write({
         { "command",    "server-part"   },
         { "server",     "test"          },
         { "channel",    "#staff"        }
--- a/tests/src/libirccd/command-server-reconnect/main.cpp	Fri Apr 06 22:06:07 2018 +0200
+++ b/tests/src/libirccd/command-server-reconnect/main.cpp	Tue Apr 10 21:20:30 2018 +0200
@@ -47,7 +47,7 @@
 
 BOOST_AUTO_TEST_CASE(basic)
 {
-    ctl_->send({
+    ctl_->write({
         { "command",    "server-reconnect"  },
         { "server",     "s1"                }
     });
@@ -64,7 +64,7 @@
 
 BOOST_AUTO_TEST_CASE(all)
 {
-    ctl_->send({{"command", "server-reconnect"}});
+    ctl_->write({{"command", "server-reconnect"}});
 
     wait_for([this] () {
         return !server1_->cqueue().empty() && !server2_->cqueue().empty();
--- a/tests/src/libirccd/command-server-topic/main.cpp	Fri Apr 06 22:06:07 2018 +0200
+++ b/tests/src/libirccd/command-server-topic/main.cpp	Tue Apr 10 21:20:30 2018 +0200
@@ -45,7 +45,7 @@
 
 BOOST_AUTO_TEST_CASE(basic)
 {
-    ctl_->send({
+    ctl_->write({
         { "command",    "server-topic"  },
         { "server",     "test"          },
         { "channel",    "#staff"        },