changeset 527:a88796ed040a

Irccdctl: switch to Boost.Asio, closes #697
author David Demelier <markand@malikania.fr>
date Thu, 16 Nov 2017 22:45:12 +0100
parents 5df38b4fbc55
children 9daccaeedcce
files irccdctl/alias.cpp irccdctl/alias.hpp irccdctl/cli.cpp irccdctl/cli.hpp irccdctl/main.cpp libcommon/CMakeLists.txt libcommon/irccd/errors.cpp libcommon/irccd/errors.hpp libirccd-test/CMakeLists.txt libirccd-test/irccd/command-tester.cpp libirccd-test/irccd/command-tester.hpp libirccdctl/CMakeLists.txt libirccdctl/irccd/client.cpp libirccdctl/irccd/client.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 libirccdctl/irccd/ctl/network_connection.hpp libirccdctl/irccd/irccdctl.hpp tests/CMakeLists.txt
diffstat 24 files changed, 1786 insertions(+), 2298 deletions(-) [+]
line wrap: on
line diff
--- a/irccdctl/alias.cpp	Tue Nov 14 20:25:30 2017 +0100
+++ b/irccdctl/alias.cpp	Thu Nov 16 22:45:12 2017 +0100
@@ -23,38 +23,42 @@
 
 namespace irccd {
 
-AliasArg::AliasArg(std::string value)
+namespace ctl {
+
+alias_arg::alias_arg(std::string value)
 {
     assert(!value.empty());
 
-    if ((m_isPlaceholder = std::regex_match(value, std::regex("^%\\d+$"))))
-        m_value = value.substr(1);
+    if ((is_placeholder_ = std::regex_match(value, std::regex("^%\\d+$"))))
+        value_ = value.substr(1);
     else
-        m_value = std::move(value);
+        value_ = std::move(value);
 }
 
-unsigned AliasArg::index() const noexcept
+unsigned alias_arg::index() const noexcept
 {
-    assert(isPlaceholder());
+    assert(is_placeholder_);
 
-    return std::stoi(m_value);
+    return std::stoi(value_);
 }
 
-const std::string &AliasArg::value() const noexcept
+const std::string& alias_arg::value() const noexcept
 {
-    assert(!isPlaceholder());
+    assert(!is_placeholder_);
 
-    return m_value;
+    return value_;
 }
 
-std::ostream &operator<<(std::ostream &out, const AliasArg &arg)
+std::ostream& operator<<(std::ostream& out, const alias_arg& arg)
 {
-    if (arg.m_isPlaceholder)
-        out << "%" << arg.m_value;
+    if (arg.is_placeholder_)
+        out << "%" << arg.value_;
     else
-        out << arg.m_value;
+        out << arg.value_;
 
     return out;
 }
 
+} // !ctl
+
 } // !irccd
--- a/irccdctl/alias.hpp	Tue Nov 14 20:25:30 2017 +0100
+++ b/irccdctl/alias.hpp	Thu Nov 16 22:45:12 2017 +0100
@@ -32,6 +32,8 @@
 
 namespace irccd {
 
+namespace ctl {
+
 /**
  * \brief Describe an alias argument.
  *
@@ -40,10 +42,10 @@
  *
  * Placeholders are placed using %n where n is an integer starting from 0.
  */
-class AliasArg {
+class alias_arg {
 private:
-    std::string m_value;
-    bool m_isPlaceholder;
+    std::string value_;
+    bool is_placeholder_;
 
 public:
     /**
@@ -52,33 +54,33 @@
      * \pre value must not be empty
      * \param value the value
      */
-    IRCCD_EXPORT AliasArg(std::string value);
+    alias_arg(std::string value);
 
     /**
      * Check if the argument is a placeholder.
      *
      * \return true if the argument is a placeholder
      */
-    inline bool isPlaceholder() const noexcept
+    inline bool is_placeholder() const noexcept
     {
-        return m_isPlaceholder;
+        return is_placeholder_;
     }
 
     /**
      * Get the placeholder index (e.g %0 returns 0)
      *
-     * \pre isPlaceholder() must return true
+     * \pre is_placeholder() must return true
      * \return the position
      */
-    IRCCD_EXPORT unsigned index() const noexcept;
+    unsigned index() const noexcept;
 
     /**
      * Get the real value.
      *
-     * \pre isPlaceholder() must return false
+     * \pre is_placeholder() must return false
      * \return the value
      */
-    IRCCD_EXPORT const std::string &value() const noexcept;
+    const std::string& value() const noexcept;
 
     /**
      * Output the alias to the stream.
@@ -86,7 +88,7 @@
      * \param out the output stream
      * \return out
      */
-    IRCCD_EXPORT friend std::ostream &operator<<(std::ostream &out, const AliasArg &);
+    friend std::ostream& operator<<(std::ostream& out, const alias_arg&);
 };
 
 /**
@@ -95,10 +97,10 @@
  * An alias command is just a command with a set of applied or placeholders
  * arguments.
  */
-class AliasCommand {
+class alias_command {
 private:
-    std::string m_command;
-    std::vector<AliasArg> m_args;
+    std::string command_;
+    std::vector<alias_arg> args_;
 
 public:
     /**
@@ -107,9 +109,9 @@
      * \param command the command
      * \param args the arguments
      */
-    inline AliasCommand(std::string command, std::vector<AliasArg> args = {}) noexcept
-        : m_command(std::move(command))
-        , m_args(std::move(args))
+    inline alias_command(std::string command, std::vector<alias_arg> args = {}) noexcept
+        : command_(std::move(command))
+        , args_(std::move(args))
     {
     }
 
@@ -118,9 +120,9 @@
      *
      * \return the command name
      */
-    inline const std::string &command() const noexcept
+    inline const std::string& command() const noexcept
     {
-        return m_command;
+        return command_;
     }
 
     /**
@@ -128,21 +130,22 @@
      *
      * \return the arguments
      */
-    inline const std::vector<AliasArg> &args() const noexcept
+    inline const std::vector<alias_arg>& args() const noexcept
     {
-        return m_args;
+        return args_;
     }
 };
 
 /**
  * \brief A set of commands to execute with their arguments.
  *
- * An alias is a composition of AliasCommand, typically, the user is able to set
- * an alias that execute a list of specified commands in order they are defined.
+ * An alias is a composition of alias_command, typically, the user is able to
+ * set an alias that execute a list of specified commands in order they are
+ * defined.
  */
-class Alias : public std::vector<AliasCommand> {
+class alias : public std::vector<alias_command> {
 private:
-    std::string m_name;
+    std::string name_;
 
 public:
     /**
@@ -150,8 +153,8 @@
      *
      * \param name the alias name
      */
-    inline Alias(std::string name) noexcept
-        : m_name(std::move(name))
+    inline alias(std::string name) noexcept
+        : name_(std::move(name))
     {
     }
 
@@ -160,12 +163,14 @@
      *
      * \return the name
      */
-    inline const std::string &name() const noexcept
+    inline const std::string& name() const noexcept
     {
-        return m_name;
+        return name_;
     }
 };
 
+} // !ctl
+
 } // !irccd
 
 #endif // !IRCCD_ALIAS_HPP
--- a/irccdctl/cli.cpp	Tue Nov 14 20:25:30 2017 +0100
+++ b/irccdctl/cli.cpp	Thu Nov 16 22:45:12 2017 +0100
@@ -16,150 +16,107 @@
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
 
-#include <cassert>
-#include <iostream>
-#include <sstream>
+#include <boost/system/system_error.hpp>
 
-#include <boost/timer/timer.hpp>
+#include <irccd/errors.hpp>
+#include <irccd/json_util.hpp>
+#include <irccd/options.hpp>
+#include <irccd/string_util.hpp>
 
-#include <json.hpp>
+#include <irccd/ctl/controller.hpp>
 
 #include "cli.hpp"
-#include "irccdctl.hpp"
-#include "json_util.hpp"
-#include "logger.hpp"
-#include "options.hpp"
-#include "net_util.hpp"
-#include "string_util.hpp"
-
-using namespace std::string_literals;
 
 namespace irccd {
 
+namespace ctl {
+
+void cli::recv_response(ctl::controller& ctl, nlohmann::json req, handler_t handler)
+{
+    ctl.recv([&ctl, req, handler, this] (auto code, auto message) {
+        if (code)
+            throw boost::system::system_error(code);
+
+        auto c = json_util::to_string(message["command"]);
+
+        if (c != req["command"].get<std::string>()) {
+            recv_response(ctl, std::move(req), std::move(handler));
+            return;
+        }
+
+        if (message["error"].is_number_integer())
+            throw boost::system::system_error(static_cast<network_error>(message["error"].template get<int>()));
+        if (message["error"].is_string())
+            throw std::runtime_error(message["error"].template get<std::string>());
+
+        if (handler)
+            handler(std::move(message));
+    });
+}
+
+void cli::request(ctl::controller& ctl, nlohmann::json req, handler_t handler)
+{
+    ctl.send(req, [&ctl, req, handler, this] (auto code, auto) {
+        if (code)
+            throw boost::system::system_error(code);
+
+        recv_response(ctl, std::move(req), std::move(handler));
+    });
+}
+
 /*
- * Cli.
+ * plugin_info_cli.
  * ------------------------------------------------------------------
  */
 
-void Cli::check(const nlohmann::json &response)
-{
-    if (!json_util::get_bool(response, "status", false)) {
-        auto error = json_util::get_string(response, "error");
-
-        if (error.empty())
-            throw std::runtime_error("command failed with an unknown error");
-
-        throw std::runtime_error(error);
-    }
-}
-
-nlohmann::json Cli::request(Irccdctl &irccdctl, nlohmann::json args)
+void plugin_config_cli::set(ctl::controller& ctl, const std::vector<std::string>&args)
 {
-    auto msg = nlohmann::json();
-
-    if (!args.is_object())
-        args = nlohmann::json::object();
-
-    args.push_back({"command", m_name});
-    irccdctl.client().request(args);
-
-    auto id = irccdctl.client().onMessage.connect([&] (auto input) {
-        msg = std::move(input);
-    });
-
-    try {
-        boost::timer::cpu_timer timer;
-
-        while (irccdctl.client().isConnected() && !msg.is_object() && timer.elapsed().wall / 1000000LL < 3000)
-            net_util::poll(3000 - timer.elapsed().wall / 1000000LL, irccdctl);
-    } catch (const std::exception &) {
-        irccdctl.client().onMessage.disconnect(id);
-        throw;
-    }
-
-    irccdctl.client().onMessage.disconnect(id);
-
-    if (!msg.is_object())
-        throw std::runtime_error("no response received");
-    if (json_util::get_string(msg, "command") != m_name)
-        throw std::runtime_error("unexpected command result received");
-
-    check(msg);
-
-    return msg;
-}
-
-void Cli::call(Irccdctl &irccdctl, nlohmann::json args)
-{
-    check(request(irccdctl, args));
-}
-
-namespace cli {
-
-/*
- * PluginConfigCli.
- * ------------------------------------------------------------------
- */
-
-void PluginConfigCli::set(Irccdctl &irccdctl, const std::vector<std::string> &args)
-{
-    check(request(irccdctl, nlohmann::json::object({
+    request(ctl, {
         { "plugin", args[0] },
         { "variable", args[1] },
         { "value", args[2] }
-    })));
+    });
 }
 
-void PluginConfigCli::get(Irccdctl &irccdctl, const std::vector<std::string> &args)
+void plugin_config_cli::get(ctl::controller& ctl, const std::vector<std::string>& args)
 {
-    auto result = request(irccdctl, nlohmann::json::object({
+    auto json = nlohmann::json::object({
         { "plugin", args[0] },
         { "variable", args[1] }
-    }));
+    });
 
-    check(result);
-
-    if (result["variables"].is_object())
-        std::cout << json_util::pretty(result["variables"][args[1]]) << std::endl;
+    request(ctl, std::move(json), [args] (auto result) {
+        if (result["variables"].is_object())
+            std::cout << json_util::pretty(result["variables"][args[1]]) << std::endl;
+    });
 }
 
-void PluginConfigCli::getall(Irccdctl &irccdctl, const std::vector<std::string> &args)
+void plugin_config_cli::getall(ctl::controller& ctl, const std::vector<std::string> &args)
 {
-    auto result = request(irccdctl, nlohmann::json::object({{ "plugin", args[0] }}));
+    request(ctl, {{ "plugin", args[0] }}, [] (auto result) {
+        auto variables = result["variables"];
 
-    check(result);
-
-    auto variables = result["variables"];
-
-    for (auto v = variables.begin(); v != variables.end(); ++v)
-        std::cout << std::setw(16) << std::left << v.key() << " : " << json_util::pretty(v.value()) << std::endl;
+        for (auto v = variables.begin(); v != variables.end(); ++v)
+            std::cout << std::setw(16) << std::left << v.key() << " : " << json_util::pretty(v.value()) << std::endl;
+    });
 }
 
-PluginConfigCli::PluginConfigCli()
-    : Cli("plugin-config",
-          "configure a plugin",
-          "plugin-config plugin [variable] [value]",
-          "Get or set a plugin configuration variable.\n\n"
-          "If both variable and value are provided, sets the plugin configuration "
-          "to the\nrespective variable name and value.\n\n"
-          "If only variable is specified, shows its current value. Otherwise, list "
-          "all\nvariables and their values.\n\n"
-          "Examples:\n"
-          "\tirccdctl plugin-config ask")
+std::string plugin_config_cli::name() const
 {
+    return "plugin-config";
 }
 
-void PluginConfigCli::exec(Irccdctl &irccdctl, const std::vector<std::string> &args)
+void plugin_config_cli::exec(ctl::controller& ctl, const std::vector<std::string> &args)
 {
     switch (args.size()) {
     case 3:
-        set(irccdctl, args);
+        set(ctl, args);
         break;
     case 2:
-        get(irccdctl, args);
+        get(ctl, args);
         break;
     case 1:
-        getall(irccdctl, args);
+        getall(ctl, args);
         break;
     default:
         throw std::invalid_argument("plugin-config requires at least 1 argument");
@@ -167,188 +124,150 @@
 }
 
 /*
- * PluginInfoCli.
+ * plugin_info_cli.
  * ------------------------------------------------------------------
  */
 
-PluginInfoCli::PluginInfoCli()
-    : Cli("plugin-info",
-          "get plugin information",
-          "plugin-info plugin",
-          "Get plugin information.\n\n"
-          "Example:\n"
-          "\tirccdctl plugin-info ask"
-    )
+std::string plugin_info_cli::name() const
 {
+    return "plugin-info";
 }
 
-void PluginInfoCli::exec(Irccdctl &irccdctl, const std::vector<std::string> &args)
+void plugin_info_cli::exec(ctl::controller& ctl, const std::vector<std::string>& args)
 {
     if (args.size() < 1)
         throw std::invalid_argument("plugin-info requires 1 argument");
 
-    auto result = request(irccdctl, {{ "plugin", args[0] }});
-
-    std::cout << std::boolalpha;
-    std::cout << "Author         : " << json_util::get_string(result, "author") << std::endl;
-    std::cout << "License        : " << json_util::get_string(result, "license") << std::endl;
-    std::cout << "Summary        : " << json_util::get_string(result, "summary") << std::endl;
-    std::cout << "Version        : " << json_util::get_string(result, "version") << std::endl;
+    request(ctl, {{ "plugin", args[0] }}, [] (auto result) {
+        std::cout << std::boolalpha;
+        std::cout << "Author         : " << json_util::get_string(result, "author") << std::endl;
+        std::cout << "License        : " << json_util::get_string(result, "license") << std::endl;
+        std::cout << "Summary        : " << json_util::get_string(result, "summary") << std::endl;
+        std::cout << "Version        : " << json_util::get_string(result, "version") << std::endl;
+    });
 }
 
 /*
- * PluginListCli.
+ * plugin_list_cli.
  * ------------------------------------------------------------------
  */
 
-PluginListCli::PluginListCli()
-    : Cli("plugin-list",
-          "list loaded plugins",
-          "plugin-list",
-          "Get the list of all loaded plugins.\n\n"
-          "Example:\n"
-          "\tirccdctl plugin-list")
+std::string plugin_list_cli::name() const
 {
+    return "plugin-list";
 }
 
-void PluginListCli::exec(Irccdctl &irccdctl, const std::vector<std::string> &)
+void plugin_list_cli::exec(ctl::controller& ctl, const std::vector<std::string>&)
 {
-    auto result = request(irccdctl);
-
-    for (const auto &value : result["list"])
-        if (value.is_string())
-            std::cout << value.get<std::string>() << std::endl;
+    request(ctl, {{ "command", "plugin-list" }}, [] (auto result) {
+        for (const auto &value : result["list"])
+            if (value.is_string())
+                std::cout << value.template get<std::string>() << std::endl;
+    });
 }
 
 /*
- * PluginLoadCli.
+ * plugin_load_cli.
  * ------------------------------------------------------------------
  */
 
-PluginLoadCli::PluginLoadCli()
-    : Cli("plugin-load",
-          "load a plugin",
-          "plugin-load logger",
-          "Load a plugin into the irccd instance.\n\n"
-          "Example:\n"
-          "\tirccdctl plugin-load logger")
+std::string plugin_load_cli::name() const
 {
+    return "plugin-load";
 }
 
-void PluginLoadCli::exec(Irccdctl &irccdctl, const std::vector<std::string> &args)
+void plugin_load_cli::exec(ctl::controller& ctl, const std::vector<std::string> &args)
 {
     if (args.size() < 1)
         throw std::invalid_argument("plugin-load requires 1 argument");
 
-    check(request(irccdctl, {{"plugin", args[0]}}));
+    request(ctl, {{ "plugin", args[0] }});
 }
 
 /*
- * PluginReloadCli.
+ * plugin_reload_cli.
  * ------------------------------------------------------------------
  */
 
-PluginReloadCli::PluginReloadCli()
-    : Cli("plugin-reload",
-          "reload a plugin",
-          "plugin-reload plugin",
-          "Reload a plugin by calling the appropriate onReload event, the plugin is not\n"
-          "unloaded and must be already loaded.\n\n"
-          "Example:\n"
-          "\tirccdctl plugin-reload logger")
+std::string plugin_reload_cli::name() const
 {
+    return "plugin-reload";
 }
 
-void PluginReloadCli::exec(Irccdctl &irccdctl, const std::vector<std::string> &args)
+void plugin_reload_cli::exec(ctl::controller& ctl, const std::vector<std::string>& args)
 {
     if (args.size() < 1)
         throw std::invalid_argument("plugin-reload requires 1 argument");
 
-    check(request(irccdctl, {{ "plugin", args[0] }}));
+    request(ctl, {{ "plugin", args[0] }});
 }
 
 /*
- * PluginUnloadCli.
+ * plugin_unload_cli.
  * ------------------------------------------------------------------
  */
 
-PluginUnloadCli::PluginUnloadCli()
-    : Cli("plugin-unload",
-          "unload a plugin",
-          "plugin-unload plugin",
-          "Unload a loaded plugin from the irccd instance.\n\n"
-          "Example:\n"
-          "\tirccdctl plugin-unload logger")
+std::string plugin_unload_cli::name() const
 {
+    return "plugin-unload";
 }
 
-void PluginUnloadCli::exec(Irccdctl &irccdctl, const std::vector<std::string> &args)
+void plugin_unload_cli::exec(ctl::controller& ctl, const std::vector<std::string>& args)
 {
     if (args.size() < 1)
         throw std::invalid_argument("plugin-unload requires 1 argument");
 
-    check(request(irccdctl, {{ "plugin", args[0] }}));
+    request(ctl, {{ "plugin", args[0] }});
 }
 
 /*
- * ServerChannelCli.
+ * server_channel_cli.
  * ------------------------------------------------------------------
  */
 
-ServerChannelMode::ServerChannelMode()
-    : Cli("server-cmode",
-          "change channel mode",
-          "server-cmode server channel mode",
-          "Change the mode of the specified channel.\n\n"
-          "Example:\n"
-          "\tirccdctl server-cmode freenode #staff +t")
+std::string server_channel_mode_cli::name() const
 {
+    return "server-cmode";
 }
 
-void ServerChannelMode::exec(Irccdctl &irccdctl, const std::vector<std::string> &args)
+void server_channel_mode_cli::exec(ctl::controller& ctl, const std::vector<std::string> &args)
 {
     if (args.size() < 3)
         throw std::invalid_argument("server-cmode requires 3 arguments");
 
-    check(request(irccdctl, {
+    request(ctl, {
         { "command",    "server-cmode"  },
         { "server",     args[0]         },
         { "channel",    args[1]         },
         { "mode",       args[2]         }
-    }));
+    });
 }
 
 /*
- * ServerChannelNoticeCli.
+ * server_channel_notice_cli.
  * ------------------------------------------------------------------
  */
 
-ServerChannelNoticeCli::ServerChannelNoticeCli()
-    : Cli("server-cnotice",
-          "send a channel notice",
-          "server-cnotice server channel message",
-          "Send a notice to a public channel. This is a notice that everyone on the channel\n"
-          "will receive.\n\n"
-          "Example:\n"
-          "\tirccdctl server-cnotice freenode #staff \"Don't flood!\"")
+std::string server_channel_notice_cli::name() const
 {
+    return "server-cnotice";
 }
 
-void ServerChannelNoticeCli::exec(Irccdctl &irccdctl, const std::vector<std::string> &args)
+void server_channel_notice_cli::exec(ctl::controller& ctl, const std::vector<std::string>& args)
 {
     if (args.size() < 3)
         throw std::invalid_argument("server-cnotice requires 3 arguments");
 
-    check(request(irccdctl, {
+    request(ctl, {
         { "command",    "server-cnotice"    },
         { "server",     args[0]             },
         { "channel",    args[1]             },
         { "message",    args[2]             }
-    }));
+    });
 }
 
 /*
- * ServerConnectCli.
+ * server_connect_cli.
  * ------------------------------------------------------------------
  */
 
@@ -376,25 +295,12 @@
 
 } // !namespace
 
-ServerConnectCli::ServerConnectCli()
-    : Cli("server-connect",
-          "add a server",
-          "server-connect [options] id host [port]",
-          "Connect to a new IRC server.\n\n"
-          "Available options:\n"
-          "  -c, --command\t\tspecify the command char\n"
-          "  -n, --nickname\tspecify a nickname\n"
-          "  -r, --realname\tspecify a real name\n"
-          "  -S, --ssl-verify\tverify SSL\n"
-          "  -s, --ssl\t\tconnect using SSL\n"
-          "  -u, --username\tspecify a user name\n\n"
-          "Example:\n"
-          "\tirccdctl server-connect -n jean example irc.example.org\n"
-          "\tirccdctl server-connect --ssl example irc.example.org 6697")
+std::string server_connect_cli::name() const
 {
+    return "server-connect";
 }
 
-void ServerConnectCli::exec(Irccdctl &irccdctl, const std::vector<std::string> &args)
+void server_connect_cli::exec(ctl::controller& ctl, const std::vector<std::string>& args)
 {
     std::vector<std::string> copy(args);
 
@@ -427,26 +333,20 @@
     if ((it = result.find("-u")) != result.end() || (it = result.find("--username")) != result.end())
         object["username"] = it->second;
 
-    check(request(irccdctl, object));
+    request(ctl, object);
 }
 
 /*
- * ServerDisconnectCli.
+ * server_disconnect_cli.
  * ------------------------------------------------------------------
  */
 
-ServerDisconnectCli::ServerDisconnectCli()
-    : Cli("server-disconnect",
-          "disconnect server",
-          "server-disconnect [server]",
-          "Disconnect from a server.\n\n"
-          "If server is not specified, irccd disconnects all servers.\n\n"
-          "Example:\n"
-          "\tirccdctl server-disconnect localhost")
+std::string server_disconnect_cli::name() const
 {
+    return "server-disconnect";
 }
 
-void ServerDisconnectCli::exec(Irccdctl &irccdctl, const std::vector<std::string> &args)
+void server_disconnect_cli::exec(ctl::controller& ctl, const std::vector<std::string>& args)
 {
     auto object = nlohmann::json::object({
         { "command", "server-disconnect" }
@@ -455,101 +355,85 @@
     if (args.size() > 0)
         object["server"] = args[0];
 
-    check(request(irccdctl, object));
+    request(ctl, object);
 }
 
 /*
- * ServerInfoCli.
+ * server_info_cli.
  * ------------------------------------------------------------------
  */
 
-ServerInfoCli::ServerInfoCli()
-    : Cli("server-info",
-          "get server information",
-          "server-info server",
-          "Get information about a server.\n\n"
-          "Example:\n"
-          "\tirccdctl server-info freenode")
+std::string server_info_cli::name() const
 {
+    return "server-info";
 }
 
-void ServerInfoCli::exec(Irccdctl &irccdctl, const std::vector<std::string> &args)
+void server_info_cli::exec(ctl::controller& ctl, const std::vector<std::string>& args)
 {
     if (args.size() < 1)
         throw std::invalid_argument("server-info requires 1 argument");
 
-    auto result = request(irccdctl, {
+    auto json = nlohmann::json::object({
         { "command",    "server-info"   },
         { "server",     args[0]         }
     });
 
-    check(result);
+    request(ctl, std::move(json), [] (auto result) {
+        std::cout << std::boolalpha;
+        std::cout << "Name           : " << json_util::pretty(result["name"]) << std::endl;
+        std::cout << "Host           : " << json_util::pretty(result["host"]) << std::endl;
+        std::cout << "Port           : " << json_util::pretty(result["port"]) << std::endl;
+        std::cout << "Ipv6           : " << json_util::pretty(result["ipv6"]) << std::endl;
+        std::cout << "SSL            : " << json_util::pretty(result["ssl"]) << std::endl;
+        std::cout << "SSL verified   : " << json_util::pretty(result["sslVerify"]) << std::endl;
+        std::cout << "Channels       : ";
 
-    std::cout << std::boolalpha;
-    std::cout << "Name           : " << json_util::pretty(result["name"]) << std::endl;
-    std::cout << "Host           : " << json_util::pretty(result["host"]) << std::endl;
-    std::cout << "Port           : " << json_util::pretty(result["port"]) << std::endl;
-    std::cout << "Ipv6           : " << json_util::pretty(result["ipv6"]) << std::endl;
-    std::cout << "SSL            : " << json_util::pretty(result["ssl"]) << std::endl;
-    std::cout << "SSL verified   : " << json_util::pretty(result["sslVerify"]) << std::endl;
-    std::cout << "Channels       : ";
+        for (const auto &v : result["channels"])
+            if (v.is_string())
+                std::cout << v.template get<std::string>() << " ";
 
-    for (const auto &v : result["channels"])
-        if (v.is_string())
-            std::cout << v.get<std::string>() << " ";
+        std::cout << std::endl;
 
-    std::cout << std::endl;
-
-    std::cout << "Nickname       : " << json_util::pretty(result["nickname"]) << std::endl;
-    std::cout << "User name      : " << json_util::pretty(result["username"]) << std::endl;
-    std::cout << "Real name      : " << json_util::pretty(result["realname"]) << std::endl;
+        std::cout << "Nickname       : " << json_util::pretty(result["nickname"]) << std::endl;
+        std::cout << "User name      : " << json_util::pretty(result["username"]) << std::endl;
+        std::cout << "Real name      : " << json_util::pretty(result["realname"]) << std::endl;
+    });
 }
 
 /*
- * ServerInviteCli.
+ * server_invite_cli.
  * ------------------------------------------------------------------
  */
 
-ServerInviteCli::ServerInviteCli()
-    : Cli("server-invite",
-          "invite someone",
-          "server-invite server nickname channel",
-          "Invite the specified target on the channel.\n\n"
-          "Example:\n"
-          "\tirccdctl server-invite freenode xorg62 #staff")
+std::string server_invite_cli::name() const
 {
+    return "server-invite";
 }
 
-void ServerInviteCli::exec(Irccdctl &irccdctl, const std::vector<std::string> &args)
+void server_invite_cli::exec(ctl::controller& ctl, const std::vector<std::string>& args)
 {
     if (args.size() < 3)
         throw std::invalid_argument("server-invite requires 3 arguments");
 
-    check(request(irccdctl, {
+    request(ctl, {
         { "command",    "server-invite" },
         { "server",     args[0]         },
         { "target",     args[1]         },
         { "channel",    args[2]         }
-    }));
+    });
 }
 
 /*
- * ServerJoinCli.
+ * server_join_cli.
  * ------------------------------------------------------------------
  */
 
-ServerJoinCli::ServerJoinCli()
-    : Cli("server-join",
-          "join a channel",
-          "server-join server channel [password]",
-          "Join the specified channel, the password is optional.\n\n"
-          "Example:\n"
-          "\tirccdctl server-join freenode #test\n"
-          "\tirccdctl server-join freenode #private-club secret")
+std::string server_join_cli::name() const
 {
+    return "server-join";
 }
 
-void ServerJoinCli::exec(Irccdctl &irccdctl, const std::vector<std::string> &args)
+void server_join_cli::exec(ctl::controller& ctl, const std::vector<std::string>& args)
 {
     if (args.size() < 2)
         throw std::invalid_argument("server-join requires at least 2 arguments");
@@ -562,25 +446,20 @@
     if (args.size() == 3)
         object["password"] = args[2];
 
-    check(request(irccdctl, object));
+    request(ctl, object);
 }
 
 /*
- * ServerKickCli.
+ * server_kick_cli.
  * ------------------------------------------------------------------
  */
 
-ServerKickCli::ServerKickCli()
-    : Cli("server-kick",
-          "kick someone from a channel",
-          "server-kick server target channel [reason]",
-          "Kick the specified target from the channel, the reason is optional.\n\n"
-          "Example:\n"
-          "\tirccdctl server-kick freenode jean #staff \"Stop flooding\"")
+std::string server_kick_cli::name() const
 {
+    return "server-kick";
 }
 
-void ServerKickCli::exec(Irccdctl &irccdctl, const std::vector<std::string> &args)
+void server_kick_cli::exec(ctl::controller& ctl, const std::vector<std::string>& args)
 {
     if (args.size() < 3)
         throw std::invalid_argument("server-kick requires at least 3 arguments ");
@@ -594,187 +473,147 @@
     if (args.size() == 4)
         object["reason"] = args[3];
 
-    check(request(irccdctl, object));
+    request(ctl, object);
 }
 
 /*
- * ServerListCli.
+ * server_list_cli.
  * ------------------------------------------------------------------
  */
 
-ServerListCli::ServerListCli()
-    : Cli("server-list",
-          "get list of servers",
-          "server-list",
-          "Get the list of all connected servers.\n\n"
-          "Example:\n"
-          "\tirccdctl server-list")
+std::string server_list_cli::name() const
 {
+    return "server-list";
 }
 
-void ServerListCli::exec(Irccdctl &irccdctl, const std::vector<std::string> &)
+void server_list_cli::exec(ctl::controller& ctl, const std::vector<std::string>&)
 {
-    auto response = request(irccdctl);
-
-    check(response);
-
-    for (const auto &n : response["list"])
-        if (n.is_string())
-            std::cout << n.get<std::string>() << std::endl;
+    request(ctl, {{ "command", "server-list" }}, [] (auto result) {
+        for (const auto &n : result["list"])
+            if (n.is_string())
+                std::cout << n.template get<std::string>() << std::endl;
+    });
 }
 
 /*
- * ServerMeCli.
+ * server_me_cli.
  * ------------------------------------------------------------------
  */
 
-ServerMeCli::ServerMeCli()
-    : Cli("server-me",
-          "send an action emote",
-          "server-me server target message",
-          "Send an action emote.\n\n"
-          "Example:\n"
-          "\tirccdctl server-me freenode #staff \"going back soon\"")
+std::string server_me_cli::name() const
 {
+    return "server-me";
 }
 
-void ServerMeCli::exec(Irccdctl &irccdctl, const std::vector<std::string> &args)
+void server_me_cli::exec(ctl::controller& ctl, const std::vector<std::string>& args)
 {
     if (args.size() < 3)
         throw std::runtime_error("server-me requires 3 arguments");
 
-    check(request(irccdctl, {
+    request(ctl, {
         { "server",     args[0]     },
         { "target",     args[1]     },
         { "message",    args[2]     }
-    }));
+    });
 }
 
 /*
- * ServerMessageCli.
+ * server_message_cli.
  * ------------------------------------------------------------------
  */
 
-ServerMessageCli::ServerMessageCli()
-    : Cli("server-message",
-          "send a message",
-          "server-message server target message",
-          "Send a message to the specified target or channel.\n\n"
-          "Example:\n"
-          "\tirccdctl server-message freenode #staff \"Hello from irccd\"")
+std::string server_message_cli::name() const
 {
+    return "server-message";
 }
 
-void ServerMessageCli::exec(Irccdctl &irccdctl, const std::vector<std::string> &args)
+void server_message_cli::exec(ctl::controller& ctl, const std::vector<std::string>& args)
 {
     if (args.size() < 3)
         throw std::invalid_argument("server-message requires 3 arguments");
 
-    check(request(irccdctl, {
+    request(ctl, {
         { "server",     args[0] },
         { "target",     args[1] },
         { "message",    args[2] }
-    }));
+    });
 }
 
 /*
- * ServerModeCli.
+ * server_mode_cli.
  * ------------------------------------------------------------------
  */
 
-ServerModeCli::ServerModeCli()
-    : Cli("server-mode",
-          "the the user mode",
-          "server-mode server mode",
-          "Set the irccd's user mode.\n\n"
-          "Example:\n"
-          "\tirccdctl server-mode +i")
+std::string server_mode_cli::name() const
 {
+    return "server-mode";
 }
 
-void ServerModeCli::exec(Irccdctl &irccdctl, const std::vector<std::string> &args)
+void server_mode_cli::exec(ctl::controller& ctl, const std::vector<std::string>& args)
 {
     if (args.size() < 2)
         throw std::invalid_argument("server-mode requires 2 arguments");
 
-    check(request(irccdctl, {
+    request(ctl, {
         { "server", args[0] },
         { "mode",   args[1] }
-    }));
+    });
 }
 
 /*
- * ServerNickCli.
+ * server_nick_cli.
  * ------------------------------------------------------------------
  */
 
-ServerNickCli::ServerNickCli()
-    : Cli("server-nick",
-          "change your nickname",
-          "server-nick server nickname",
-          "Change irccd's nickname.\n\n"
-          "Example:\n"
-          "\tirccdctl server-nick freenode david")
+std::string server_nick_cli::name() const
 {
+    return "server-nick";
 }
 
-void ServerNickCli::exec(Irccdctl &irccdctl, const std::vector<std::string> &args)
+void server_nick_cli::exec(ctl::controller& ctl, const std::vector<std::string>& args)
 {
     if (args.size() < 2)
         throw std::invalid_argument("server-nick requires 2 arguments");
 
-    check(request(irccdctl, {
+    request(ctl, {
         { "server",     args[0] },
         { "nickname",   args[1] }
-    }));
+    });
 }
 
 /*
- * ServerNoticeCli.
+ * server_notice_cli.
  * ------------------------------------------------------------------
  */
 
-ServerNoticeCli::ServerNoticeCli()
-    : Cli("server-notice",
-          "send a private notice",
-          "server-notice server target message",
-          "Send a private notice to the specified target.\n\n"
-          "Example:\n"
-          "\tirccdctl server-notice freenode jean \"I know you are here.\"")
+std::string server_notice_cli::name() const
 {
+    return "server-notice";
 }
 
-void ServerNoticeCli::exec(Irccdctl &irccdctl, const std::vector<std::string> &args)
+void server_notice_cli::exec(ctl::controller& ctl, const std::vector<std::string>& args)
 {
     if (args.size() < 3)
         throw std::invalid_argument("server-notice requires 3 arguments");
 
-    check(request(irccdctl, {
+    request(ctl, {
         { "server",     args[0] },
         { "target",     args[1] },
         { "message",    args[2] }
-    }));
+    });
 }
 
 /*
- * ServerPartCli.
+ * server_part_cli.
  * ------------------------------------------------------------------
  */
 
-ServerPartCli::ServerPartCli()
-    : Cli("server-part",
-          "leave a channel",
-          "server-part server channel [reason]",
-          "Leave the specified channel, the reason is optional.\n\n"
-          "Not all IRC servers support giving a reason to leave a channel, do not specify\n"
-          "it if this is a concern.\n\n"
-          "Example:\n"
-          "\tirccdctl server-part freenode #staff\n"
-          "\tirccdctl server-part freenode #botwar \"too noisy\"")
+std::string server_part_cli::name() const
 {
+    return "server-part";
 }
 
-void ServerPartCli::exec(Irccdctl &irccdctl, const std::vector<std::string> &args)
+void server_part_cli::exec(ctl::controller& ctl, const std::vector<std::string>& args)
 {
     if (args.size() < 2)
         throw std::invalid_argument("server-part requires at least 2 arguments");
@@ -787,27 +626,20 @@
     if (args.size() >= 3)
         object["reason"] = args[2];
 
-    check(request(irccdctl, object));
+    request(ctl, object);
 }
 
 /*
- * ServerReconnectCli.
+ * server_reconnect_cli.
  * ------------------------------------------------------------------
  */
 
-ServerReconnectCli::ServerReconnectCli()
-    : Cli("server-reconnect",
-          "force reconnection of a server",
-          "server-reconnect [server]",
-          "Force reconnection of one or all servers.\n\n"
-          "If server is not specified, all servers will try to reconnect.\n\n"
-          "Example:\n"
-          "\tirccdctl server-reconnect\n"
-          "\tirccdctl server-reconnect wanadoo")
+std::string server_reconnect_cli::name() const
 {
+    return "server-reconnect";
 }
 
-void ServerReconnectCli::exec(Irccdctl &irccdctl, const std::vector<std::string> &args)
+void server_reconnect_cli::exec(ctl::controller& ctl, const std::vector<std::string>& args)
 {
     auto object = nlohmann::json::object({
         { "command", "server-reconnect" }
@@ -816,60 +648,42 @@
     if (args.size() >= 1)
         object["server"] = args[0];
 
-    check(request(irccdctl, object));
+    request(ctl, object);
 }
 
 /*
- * ServerTopicCli.
+ * server_topic_cli.
  * ------------------------------------------------------------------
  */
 
-ServerTopicCli::ServerTopicCli()
-    : Cli("server-topic",
-          "change channel topic",
-          "server-topic server channel topic",
-          "Change the topic of the specified channel.\n\n"
-          "Example:\n"
-          "\tirccdctl server-topic freenode #wmfs \"This is the best channel\"")
+std::string server_topic_cli::name() const
 {
+    return "server-topic";
 }
 
-void ServerTopicCli::exec(Irccdctl &irccdctl, const std::vector<std::string> &args)
+void server_topic_cli::exec(ctl::controller& ctl, const std::vector<std::string>& args)
 {
     if (args.size() < 3)
         throw std::invalid_argument("server-topic requires 3 arguments");
 
-    check(request(irccdctl, {
+    request(ctl, {
         { "server",     args[0] },
         { "channel",    args[1] },
         { "topic",      args[2] }
-    }));
+    });
 }
 
 /*
- * RuleAddCli.
+ * rule_add_cli.
  * ------------------------------------------------------------------
  */
 
-RuleAddCli::RuleAddCli()
-    : Cli("rule-add",
-          "add a new rule",
-          "rule-add [options] accept|drop",
-          "Add a new rule to irccd.\n\n"
-          "If no index is specified, the rule is added to the end.\n\n"
-          "Available options:\n"
-          "  -c, --add-channel\t\tmatch a channel\n"
-          "  -e, --add-event\t\tmatch an event\n"
-          "  -i, --index\t\t\trule position\n"
-          "  -p, --add-plugin\t\tmatch a plugin\n"
-          "  -s, --add-server\t\tmatch a server\n\n"
-          "Example:\n"
-          "\tirccdctl rule-add -p hangman drop\n"
-          "\tirccdctl rule-add -s localhost -c #games -p hangman accept")
+std::string rule_add_cli::name() const
 {
+    return "rule-add";
 }
 
-void RuleAddCli::exec(Irccdctl &irccdctl, const std::vector<std::string> &args)
+void rule_add_cli::exec(ctl::controller& ctl, const std::vector<std::string>& args)
 {
     static const option::options options{
         { "-c",             true },
@@ -918,41 +732,24 @@
 
     // And action.
     if (copy[0] != "accept" && copy[0] != "drop")
-        throw std::runtime_error("invalid action '"s + copy[0] + "'");
+        throw std::runtime_error(string_util::sprintf("invalid action '%s'", copy[0]));
 
     json["action"] = copy[0];
 
-    check(request(irccdctl, json));
+    request(ctl, json);
 }
 
 /*
- * RuleEditCli.
+ * rule_edit_cli.
  * ------------------------------------------------------------------
  */
 
-RuleEditCli::RuleEditCli()
-    : Cli("rule-edit",
-          "edit an existing rule",
-          "rule-edit [options] index",
-          "Edit an existing rule in irccd.\n\n"
-          "All options can be specified multiple times.\n\n"
-          "Available options:\n"
-          "  -a, --action\t\t\tset action\n"
-          "  -c, --add-channel\t\tmatch a channel\n"
-          "  -C, --remove-channel\t\tremove a channel\n"
-          "  -e, --add-event\t\tmatch an event\n"
-          "  -E, --remove-event\t\tremove an event\n"
-          "  -p, --add-plugin\t\tmatch a plugin\n"
-          "  -P, --add-plugin\t\tremove a plugin\n"
-          "  -s, --add-server\t\tmatch a server\n"
-          "  -S, --remove-server\t\tremove a server\n\n"
-          "Example:\n"
-          "\tirccdctl rule-edit -p hangman 0\n"
-          "\tirccdctl rule-edit -S localhost -c #games -p hangman 1")
+std::string rule_edit_cli::name() const
 {
+    return "rule-edit";
 }
 
-void RuleEditCli::exec(Irccdctl &irccdctl, const std::vector<std::string> &args)
+void rule_edit_cli::exec(ctl::controller& ctl, const std::vector<std::string>& args)
 {
     static const option::options options{
         { "-a",                 true },
@@ -1018,17 +815,17 @@
     // Index.
     json["index"] = string_util::to_number<unsigned>(copy[0]);
 
-    check(request(irccdctl, json));
+    request(ctl, json);
 }
 
 /*
- * RuleListCli.
+ * rule_list_cli.
  * ------------------------------------------------------------------
  */
 
 namespace {
 
-void showRule(const nlohmann::json& json, int index)
+void show_rule(const nlohmann::json& json, int index)
 {
     assert(json.is_object());
 
@@ -1062,48 +859,34 @@
 
 } // !namespace
 
-RuleListCli::RuleListCli()
-    : Cli("rule-list",
-          "list all rules",
-          "rule-list",
-          "List all rules.\n\n"
-          "Example:\n"
-          "\tirccdctl rule-list")
+std::string rule_list_cli::name() const
 {
+    return "rule-list";
 }
 
-void RuleListCli::exec(Irccdctl &irccdctl, const std::vector<std::string> &)
+void rule_list_cli::exec(ctl::controller& ctl, const std::vector<std::string>&)
 {
-    auto response = request(irccdctl);
-    auto pos = 0;
-
-    check(response);
+    request(ctl, {{ "command", "rule-list" }}, [] (auto result) {
+        auto pos = 0;
 
-    for (const auto &obj : response["list"]) {
-        if (!obj.is_object())
-            continue;
-
-        showRule(obj, pos++);
-    }
+        for (const auto& obj : result["list"]) {
+            if (obj.is_object())
+                show_rule(obj, pos++);
+        }
+    });
 }
 
 /*
- * RuleInfoCli.
+ * rule_info_cli.
  * ------------------------------------------------------------------
  */
 
-RuleInfoCli::RuleInfoCli()
-    : Cli("rule-info",
-          "show a rule",
-          "rule-info index",
-          "Show a rule.\n\n"
-          "Example:\n"
-          "\tirccdctl rule-info 0\n"
-          "\tirccdctl rule-info 1")
+std::string rule_info_cli::name() const
 {
+    return "rule-info";
 }
 
-void RuleInfoCli::exec(Irccdctl &irccdctl, const std::vector<std::string> &args)
+void rule_info_cli::exec(ctl::controller& ctl, const std::vector<std::string>& args)
 {
     if (args.size() < 1)
         throw std::invalid_argument("rule-info requires 1 argument");
@@ -1116,32 +899,27 @@
         throw std::invalid_argument("invalid number '" + args[0] + "'");
     }
 
-    auto result = request(irccdctl, {
+    auto json = nlohmann::json::object({
         { "command",    "rule-info" },
         { "index",      index       }
     });
 
-    check(result);
-    showRule(result, 0);
+    request(ctl, std::move(json), [] (auto result) {
+        show_rule(result, 0);
+    });
 }
 
 /*
- * RuleRemoveCli.
+ * rule_remove_cli.
  * ------------------------------------------------------------------
  */
 
-RuleRemoveCli::RuleRemoveCli()
-    : Cli("rule-remove",
-          "remove a rule",
-          "rule-remove index",
-          "Remove an existing rule.\n\n"
-          "Example:\n"
-          "\tirccdctl rule-remove 0\n"
-          "\tirccdctl rule-remove 1")
+std::string rule_remove_cli::name() const
 {
+    return "rule-remove";
 }
 
-void RuleRemoveCli::exec(Irccdctl &irccdctl, const std::vector<std::string> &args)
+void rule_remove_cli::exec(ctl::controller& ctl, const std::vector<std::string>& args)
 {
     if (args.size() < 1)
         throw std::invalid_argument("rule-remove requires 1 argument");
@@ -1154,34 +932,23 @@
         throw std::invalid_argument("invalid number '" + args[0] + "'");
     }
 
-    auto result = request(irccdctl, {
+    request(ctl, {
         { "command",    "rule-remove"   },
         { "index",      index           }
     });
-
-    check(result);
 }
 
 /*
- * RuleMoveCli.
+ * rule_move_cli.
  * ------------------------------------------------------------------
  */
 
-RuleMoveCli::RuleMoveCli()
-    : Cli("rule-move",
-          "move a rule to a new position",
-          "rule-move source destination",
-          "Move a rule from the given source at the specified destination index.\n\n"
-          "The rule will replace the existing one at the given destination moving\ndown every "
-          "other rules. If destination is greater or equal the number of rules,\nthe rule "
-          "is moved to the end.\n\n"
-          "Example:\n"
-          "\tirccdctl rule-move 0 5\n"
-          "\tirccdctl rule-move 4 3")
+std::string rule_move_cli::name() const
 {
+    return "rule-move";
 }
 
-void RuleMoveCli::exec(Irccdctl &irccdctl, const std::vector<std::string> &args)
+void rule_move_cli::exec(ctl::controller& ctl, const std::vector<std::string>& args)
 {
     if (args.size() < 2)
         throw std::invalid_argument("rule-move requires 2 arguments");
@@ -1196,11 +963,11 @@
         throw std::invalid_argument("invalid number");
     }
 
-    check(request(irccdctl, {
+    request(ctl, {
         { "command",    "rule-move" },
         { "from",       from        },
         { "to",         to          }
-    }));
+    });
 }
 
 /*
@@ -1361,7 +1128,7 @@
     std::cout << "realname:    " << json_util::pretty(v, "realname") << "\n";
 }
 
-const std::unordered_map<std::string, std::function<void (const nlohmann::json &)>> events{
+const std::unordered_map<std::string, std::function<void (const nlohmann::json&)>> events{
     { "onChannelMode",      onChannelMode   },
     { "onChannelNotice",    onChannelNotice },
     { "onConnect",          onConnect       },
@@ -1380,60 +1147,42 @@
     { "onWhois",            onWhois         }
 };
 
+void get_event(ctl::controller& ctl, std::string fmt)
+{
+    ctl.recv([&ctl, fmt] (auto code, auto message) {
+        if (code)
+            throw boost::system::system_error(code);
+
+        auto it = events.find(json_util::to_string(message["event"]));
+
+        if (it != events.end()) {
+            if (fmt == "json")
+                std::cout << message.dump(4) << std::endl;
+            else {
+                it->second(message);
+                std::cout << std::endl;
+            }
+        }
+
+        get_event(ctl, std::move(fmt));
+    });
+}
+
 } // !namespace
 
-WatchCli::WatchCli()
-    : Cli("watch",
-          "watch irccd events",
-          "watch [-f|--format json|native]",
-          "Start watching irccd events.\n\n"
-          "You can use different output formats, native is human readable format, json is\n"
-          "pretty formatted json.\n\n"
-          "Example:\n"
-          "\tirccdctl watch\n"
-          "\tirccdctl watch -f json")
+std::string watch_cli::name() const
 {
+    return "watch";
 }
 
-void WatchCli::exec(Irccdctl& client, const std::vector<std::string>& args)
+void watch_cli::exec(ctl::controller& ctl, const std::vector<std::string>& args)
 {
-    std::string fmt = format(args);
+    auto fmt = format(args);
 
     if (fmt != "native" && fmt != "json")
         throw std::invalid_argument("invalid format given: " + fmt);
 
-    auto id = client.client().onEvent.connect([&] (auto ev) {
-        try {
-            auto name = ev.find("event");
-
-            if (name == ev.end() || !name->is_string())
-                return;
-
-            auto it = events.find(*name);
-
-            // Silently ignore to avoid breaking user output.
-            if (it == events.end())
-                return;
-
-            if (fmt == "json")
-                std::cout << ev.dump(4) << std::endl;
-            else {
-                it->second(ev);
-                std::cout << std::endl;
-            }
-        } catch (...) {
-        }
-    });
-
-    try {
-        while (client.client().isConnected()) {
-            net_util::poll(500, client);
-        }
-    } catch (const std::exception &ex) {
-        log::warning() << ex.what() << std::endl;
-    }
-
-    client.client().onEvent.disconnect(id);
+    get_event(ctl, fmt);
 }
 
 } // !cli
--- a/irccdctl/cli.hpp	Tue Nov 14 20:25:30 2017 +0100
+++ b/irccdctl/cli.hpp	Thu Nov 16 22:45:12 2017 +0100
@@ -26,713 +26,541 @@
 
 namespace irccd {
 
-class Irccdctl;
+namespace ctl {
+
+class controller;
 
 /*
- * Cli.
+ * cli.
  * ------------------------------------------------------------------
  */
 
 /**
  * \brief Abstract CLI class.
  */
-class Cli {
-protected:
-    std::string m_name;
-    std::string m_summary;
-    std::string m_usage;
-    std::string m_help;
+class cli {
+public:
+    using handler_t = std::function<void (nlohmann::json)>;
 
-    /**
-     * Check the message result and print an error if any.
-     *
-     * \param result the result
-     * \throw an error if any
-     */
-    void check(const nlohmann::json &result);
+private:
+    void recv_response(ctl::controller&, nlohmann::json, handler_t);
 
+protected:
     /**
-     * Send a request and wait for the response.
+     * Convenient request helper.
+     *
+     * This function send and receive the response for the given request. It
+     * checks for an error code or string in the command result and throws it if
+     * any.
      *
-     * \param irccdctl the client
-     * \param args the optional arguments
+     * If handler is not null, it will be called once the command result has
+     * been received.
+     *
+     * This function may executes successive read calls until we get the
+     * response.
+     *
+     * \param ctl the controller
+     * \param json the json object
+     * \param handler the optional handler
      */
-    nlohmann::json request(Irccdctl &irccdctl, nlohmann::json args = nullptr);
-
-    /**
-     * Call a command and wait for success/error response.
-     *
-     * \param irccdctl the client
-     * \param args the extra arguments (may be null)
-     */
-    void call(Irccdctl &irccdctl, nlohmann::json args);
+    void request(ctl::controller& ctl, nlohmann::json json, handler_t handler = nullptr);
 
 public:
-    inline Cli(std::string name, std::string summary, std::string usage, std::string help) noexcept
-        : m_name(std::move(name))
-        , m_summary(std::move(summary))
-        , m_usage(std::move(usage))
-        , m_help(std::move(help))
-    {
-    }
-
-    virtual ~Cli() = default;
+    cli() noexcept = default;
 
-    inline const std::string &name() const noexcept
-    {
-        return m_name;
-    }
-
-    inline const std::string &summary() const noexcept
-    {
-        return m_summary;
-    }
+    virtual ~cli() noexcept = default;
 
-    inline const std::string &usage() const noexcept
-    {
-        return m_usage;
-    }
-
-    inline const std::string &help() const noexcept
-    {
-        return m_help;
-    }
+    virtual std::string name() const = 0;
 
-    virtual void exec(Irccdctl &client, const std::vector<std::string> &args) = 0;
+    virtual void exec(ctl::controller& ctl, const std::vector<std::string>& args) = 0;
 };
 
-namespace cli {
-
-/*
- * PluginConfigCli.
- * ------------------------------------------------------------------
- */
-
 /**
  * \brief Implementation of irccdctl plugin-config.
  */
-class PluginConfigCli : public Cli {
+class plugin_config_cli : public cli {
 private:
-    void set(Irccdctl &, const std::vector<std::string> &);
-    void get(Irccdctl &, const std::vector<std::string> &);
-    void getall(Irccdctl &, const std::vector<std::string> &);
+    void set(ctl::controller&, const std::vector<std::string>&);
+    void get(ctl::controller&, const std::vector<std::string>&);
+    void getall(ctl::controller&, const std::vector<std::string>&);
 
 public:
     /**
-     * Default constructor.
+     * \copydoc cli::name
      */
-    PluginConfigCli();
+    std::string name() const override;
 
     /**
-     * \copydoc Cli::exec
+     * \copydoc cli::exec
      */
-    void exec(Irccdctl &irccdctl, const std::vector<std::string> &args) override;
+    void exec(ctl::controller& irccdctl, const std::vector<std::string>& args) override;
 };
 
-/*
- * PluginInfoCli.
- * ------------------------------------------------------------------
- */
-
 /**
  * \brief Implementation of irccdctl plugin-info.
  */
-class PluginInfoCli : public Cli {
+class plugin_info_cli : public cli {
 public:
     /**
-     * Default constructor.
+     * \copydoc cli::name
      */
-    PluginInfoCli();
+    std::string name() const override;
 
     /**
-     * \copydoc Cli::exec
+     * \copydoc cli::exec
      */
-    void exec(Irccdctl &irccdctl, const std::vector<std::string> &args) override;
+    void exec(ctl::controller& irccdctl, const std::vector<std::string>& args) override;
 };
 
-/*
- * PluginListCli.
- * ------------------------------------------------------------------
- */
-
 /**
  * \brief Implementation of irccdctl plugin-list.
  */
-class PluginListCli : public Cli {
+class plugin_list_cli : public cli {
 public:
     /**
-     * Default constructor.
+     * \copydoc cli::name
      */
-    PluginListCli();
+    std::string name() const override;
 
     /**
-     * \copydoc Cli::exec
+     * \copydoc cli::exec
      */
-    void exec(Irccdctl &irccdctl, const std::vector<std::string> &args) override;
+    void exec(ctl::controller& irccdctl, const std::vector<std::string>& args) override;
 };
 
-/*
- * PluginLoadCli.
- * ------------------------------------------------------------------
- */
-
 /**
  * \brief Implementation of irccdctl plugin-load.
  */
-class PluginLoadCli : public Cli {
+class plugin_load_cli : public cli {
 public:
     /**
-     * Default constructor.
+     * \copydoc cli::name
      */
-    PluginLoadCli();
+    std::string name() const override;
 
     /**
-     * \copydoc Cli::exec
+     * \copydoc cli::exec
      */
-    void exec(Irccdctl &irccdctl, const std::vector<std::string> &args) override;
+    void exec(ctl::controller& irccdctl, const std::vector<std::string>& args) override;
 };
 
-/*
- * PluginReloadCli.
- * ------------------------------------------------------------------
- */
-
 /**
  * \brief Implementation of irccdctl plugin-reload.
  */
-class PluginReloadCli : public Cli {
+class plugin_reload_cli : public cli {
 public:
-    PluginReloadCli();
+    /**
+     * \copydoc cli::name
+     */
+    std::string name() const override;
 
     /**
-     * \copydoc Command::exec
+     * \copydoc cli::exec
      */
-    void exec(Irccdctl &irccdctl, const std::vector<std::string> &args) override;
+    void exec(ctl::controller& irccdctl, const std::vector<std::string>& args) override;
 };
 
-/*
- * PluginUnloadCli.
- * ------------------------------------------------------------------
- */
-
 /**
  * \brief Implementation of irccdctl plugin-unload.
  */
-class PluginUnloadCli : public Cli {
+class plugin_unload_cli : public cli {
 public:
-    PluginUnloadCli();
+    /**
+     * \copydoc cli::name
+     */
+    std::string name() const override;
 
     /**
-     * \copydoc Cli::exec
+     * \copydoc cli::exec
      */
-    void exec(Irccdctl &irccdctl, const std::vector<std::string> &args) override;
+    void exec(ctl::controller& irccdctl, const std::vector<std::string>& args) override;
 };
 
-/*
- * ServerChannelCli.
- * ------------------------------------------------------------------
- */
-
 /**
  * \brief Implementation of irccdctl server-cmode.
  */
-class ServerChannelMode : public Cli {
+class server_channel_mode_cli : public cli {
 public:
     /**
-     * Default constructor.
+     * \copydoc cli::name
      */
-    ServerChannelMode();
+    std::string name() const override;
 
     /**
-     * \copydoc Cli::exec
+     * \copydoc cli::exec
      */
-    void exec(Irccdctl &irccdctl, const std::vector<std::string> &args) override;
+    void exec(ctl::controller& irccdctl, const std::vector<std::string>& args) override;
 };
 
-/*
- * ServerChannelNoticeCli.
- * ------------------------------------------------------------------
- */
-
 /**
  * \brief Implementation of irccdctl server-cnotice.
  */
-class ServerChannelNoticeCli : public Cli {
+class server_channel_notice_cli : public cli {
 public:
     /**
-     * Default constructor.
+     * \copydoc cli::name
      */
-    ServerChannelNoticeCli();
+    std::string name() const override;
 
     /**
-     * \copydoc Cli::exec
+     * \copydoc cli::exec
      */
-    void exec(Irccdctl &irccdctl, const std::vector<std::string> &args) override;
+    void exec(ctl::controller& irccdctl, const std::vector<std::string>& args) override;
 };
 
-/*
- * ServerConnectCli.
- * ------------------------------------------------------------------
- */
-
 /**
  * \brief Implementation of irccdctl server-connect.
  */
-class ServerConnectCli : public Cli {
+class server_connect_cli : public cli {
 public:
     /**
-     * Default constructor.
+     * \copydoc cli::name
      */
-    ServerConnectCli();
+    std::string name() const override;
 
     /**
-     * \copydoc Cli::exec
+     * \copydoc cli::exec
      */
-    void exec(Irccdctl &irccdctl, const std::vector<std::string> &args) override;
+    void exec(ctl::controller& irccdctl, const std::vector<std::string>& args) override;
 };
 
-/*
- * ServerDisconnectCli.
- * ------------------------------------------------------------------
- */
-
 /**
  * \brief Implementation of irccdctl server-disconnect.
  */
-class ServerDisconnectCli : public Cli {
+class server_disconnect_cli : public cli {
 public:
     /**
-     * Default constructor.
+     * \copydoc cli::name
      */
-    ServerDisconnectCli();
+    std::string name() const override;
 
     /**
-     * \copydoc Cli::exec
+     * \copydoc cli::exec
      */
-    void exec(Irccdctl &irccdctl, const std::vector<std::string> &args) override;
+    void exec(ctl::controller& irccdctl, const std::vector<std::string>& args) override;
 };
 
-/*
- * ServerInfoCli.
- * ------------------------------------------------------------------
- */
-
 /**
  * \brief Implementation of irccdctl server-info.
  */
-class ServerInfoCli : public Cli {
+class server_info_cli : public cli {
 public:
     /**
-     * Default constructor.
+     * \copydoc cli::name
      */
-    ServerInfoCli();
+    std::string name() const override;
 
     /**
-     * \copydoc Cli::exec
+     * \copydoc cli::exec
      */
-    void exec(Irccdctl &irccdctl, const std::vector<std::string> &args) override;
+    void exec(ctl::controller& irccdctl, const std::vector<std::string>& args) override;
 };
 
-/*
- * ServerInviteCli.
- * ------------------------------------------------------------------
- */
-
 /**
  * \brief Implementation of irccdctl server-invite.
  */
-class ServerInviteCli : public Cli {
+class server_invite_cli : public cli {
 public:
     /**
-     * Default constructor.
+     * \copydoc cli::name
      */
-    ServerInviteCli();
+    std::string name() const override;
 
     /**
-     * \copydoc Cli::exec
+     * \copydoc cli::exec
      */
-    void exec(Irccdctl &irccdctl, const std::vector<std::string> &args) override;
+    void exec(ctl::controller& irccdctl, const std::vector<std::string>& args) override;
 };
 
-/*
- * ServerJoinCli.
- * ------------------------------------------------------------------
- */
-
 /**
  * \brief Implementation of irccdctl server-join.
  */
-class ServerJoinCli : public Cli {
+class server_join_cli : public cli {
 public:
     /**
-     * Default constructor.
+     * \copydoc cli::name
      */
-    ServerJoinCli();
+    std::string name() const override;
 
     /**
-     * \copydoc Cli::exec
+     * \copydoc cli::exec
      */
-    void exec(Irccdctl &irccdctl, const std::vector<std::string> &args) override;
+    void exec(ctl::controller& irccdctl, const std::vector<std::string>& args) override;
 };
 
-/*
- * ServerKickCli.
- * ------------------------------------------------------------------
- */
-
 /**
  * \brief Implementation of irccdctl server-kick.
  */
-class ServerKickCli : public Cli {
+class server_kick_cli : public cli {
 public:
     /**
-     * Default constructor.
+     * \copydoc cli::name
      */
-    ServerKickCli();
+    std::string name() const override;
 
     /**
-     * \copydoc Cli::exec
+     * \copydoc cli::exec
      */
-    void exec(Irccdctl &irccdctl, const std::vector<std::string> &args) override;
+    void exec(ctl::controller& irccdctl, const std::vector<std::string>& args) override;
 };
 
-/*
- * ServerListCli.
- * ------------------------------------------------------------------
- */
-
 /**
  * \brief Implementation of irccdctl server-list.
  */
-class ServerListCli : public Cli {
+class server_list_cli : public cli {
 public:
     /**
-     * Default constructor.
+     * \copydoc cli::name
      */
-    ServerListCli();
+    std::string name() const override;
 
     /**
-     * \copydoc Cli::exec
+     * \copydoc cli::exec
      */
-    void exec(Irccdctl &irccdctl, const std::vector<std::string> &args) override;
+    void exec(ctl::controller& irccdctl, const std::vector<std::string>& args) override;
 };
 
-/*
- * ServerMeCli.
- * ------------------------------------------------------------------
- */
-
 /**
  * \brief Implementation of irccdctl server-me.
  */
-class ServerMeCli : public Cli {
+class server_me_cli : public cli {
 public:
     /**
-     * Default constructor.
+     * \copydoc cli::name
      */
-    ServerMeCli();
+    std::string name() const override;
 
     /**
-     * \copydoc Command::exec
+     * \copydoc cli::exec
      */
-    void exec(Irccdctl &irccdctl, const std::vector<std::string> &args) override;
+    void exec(ctl::controller& irccdctl, const std::vector<std::string>& args) override;
 };
 
-/*
- * ServerMessageCli.
- * ------------------------------------------------------------------
- */
-
 /**
  * \brief Implementation of irccdctl server-message.
  */
-class ServerMessageCli : public Cli {
+class server_message_cli : public cli {
 public:
     /**
-     * Default constructor.
+     * \copydoc cli::name
      */
-    ServerMessageCli();
+    std::string name() const override;
 
     /**
-     * \copydoc Cli::exec
+     * \copydoc cli::exec
      */
-    void exec(Irccdctl &irccdctl, const std::vector<std::string> &args) override;
+    void exec(ctl::controller& irccdctl, const std::vector<std::string>& args) override;
 };
 
-/*
- * ServerModeCli.
- * ------------------------------------------------------------------
- */
-
 /**
  * \brief Implementation of irccdctl server-mode.
  */
-class ServerModeCli : public Cli {
+class server_mode_cli : public cli {
 public:
     /**
-     * Default constructor.
+     * \copydoc cli::name
      */
-    ServerModeCli();
+    std::string name() const override;
 
     /**
-     * \copydoc Cli::exec
+     * \copydoc cli::exec
      */
-    void exec(Irccdctl &irccdctl, const std::vector<std::string> &args) override;
+    void exec(ctl::controller& irccdctl, const std::vector<std::string>& args) override;
 };
 
-/*
- * ServerNickCli.
- * ------------------------------------------------------------------
- */
-
 /**
  * \brief Implementation of irccdctl server-nick.
  */
-class ServerNickCli : public Cli {
+class server_nick_cli : public cli {
 public:
     /**
-     * Default constructor.
+     * \copydoc cli::name
      */
-    ServerNickCli();
+    std::string name() const override;
 
     /**
-     * \copydoc Cli::exec
+     * \copydoc cli::exec
      */
-    void exec(Irccdctl &irccdctl, const std::vector<std::string> &args) override;
+    void exec(ctl::controller& irccdctl, const std::vector<std::string>& args) override;
 };
 
-/*
- * ServerNoticeCli.
- * ------------------------------------------------------------------
- */
-
 /**
  * \brief Implementation of irccdctl server-notice.
  */
-class ServerNoticeCli : public Cli {
+class server_notice_cli : public cli {
 public:
     /**
-     * Default constructor.
+     * \copydoc cli::name
      */
-    ServerNoticeCli();
+    std::string name() const override;
 
     /**
-     * \copydoc Cli::exec
+     * \copydoc cli::exec
      */
-    void exec(Irccdctl &irccdctl, const std::vector<std::string> &args) override;
+    void exec(ctl::controller& irccdctl, const std::vector<std::string>& args) override;
 };
 
-/*
- * ServerPartCli.
- * ------------------------------------------------------------------
- */
-
 /**
  * \brief Implementation of irccdctl server-part.
  */
-class ServerPartCli : public Cli {
+class server_part_cli : public cli {
 public:
     /**
-     * Default constructor.
+     * \copydoc cli::name
      */
-    ServerPartCli();
+    std::string name() const override;
 
     /**
-     * \copydoc Cli::exec
+     * \copydoc cli::exec
      */
-    void exec(Irccdctl &irccdctl, const std::vector<std::string> &args) override;
+    void exec(ctl::controller& irccdctl, const std::vector<std::string>& args) override;
 };
 
-/*
- * ServerReconnectCli.
- * ------------------------------------------------------------------
- */
-
 /**
  * \brief Implementation of irccdctl server-reconnect.
  */
-class ServerReconnectCli : public Cli {
+class server_reconnect_cli : public cli {
 public:
     /**
-     * Default constructor.
+     * \copydoc cli::name
      */
-    ServerReconnectCli();
+    std::string name() const override;
 
     /**
-     * \copydoc Cli::exec
+     * \copydoc cli::exec
      */
-    void exec(Irccdctl &irccdctl, const std::vector<std::string> &args) override;
+    void exec(ctl::controller& irccdctl, const std::vector<std::string>& args) override;
 };
 
-/*
- * ServerTopicCli.
- * ------------------------------------------------------------------
- */
-
 /**
  * \brief Implementation of irccdctl server-topic.
  */
-class ServerTopicCli : public Cli {
+class server_topic_cli : public cli {
 public:
     /**
-     * Default constructor.
+     * \copydoc cli::name
      */
-    ServerTopicCli();
+    std::string name() const override;
 
     /**
-     * \copydoc Cli::exec
+     * \copydoc cli::exec
      */
-    void exec(Irccdctl &client, const std::vector<std::string> &args) override;
+    void exec(ctl::controller& irccdctl, const std::vector<std::string>& args) override;
 };
 
-/*
- * RuleAddCli.
- * ------------------------------------------------------------------
- */
-
 /**
  * \brief Implementation of irccdctl rule-add.
  */
-class RuleAddCli : public Cli {
+class rule_add_cli : public cli {
 public:
     /**
-     * Default constructor.
+     * \copydoc cli::name
      */
-    RuleAddCli();
+    std::string name() const override;
 
     /**
-     * \copydoc Cli::exec
+     * \copydoc cli::exec
      */
-    void exec(Irccdctl &client, const std::vector<std::string> &args) override;
+    void exec(ctl::controller& irccdctl, const std::vector<std::string>& args) override;
 };
 
-/*
- * RuleEditCli.
- * ------------------------------------------------------------------
- */
-
 /**
  * \brief Implementation of irccdctl rule-edit.
  */
-class RuleEditCli : public Cli {
+class rule_edit_cli : public cli {
 public:
     /**
-     * Default constructor.
+     * \copydoc cli::name
      */
-    RuleEditCli();
+    std::string name() const override;
 
     /**
-     * \copydoc Cli::exec
+     * \copydoc cli::exec
      */
-    void exec(Irccdctl &client, const std::vector<std::string> &args) override;
+    void exec(ctl::controller& irccdctl, const std::vector<std::string>& args) override;
 };
 
-/*
- * RuleListCli.
- * ------------------------------------------------------------------
- */
-
 /**
  * \brief Implementation of irccdctl rule-list.
  */
-class RuleListCli : public Cli {
+class rule_list_cli : public cli {
 public:
     /**
-     * Default constructor.
+     * \copydoc cli::name
      */
-    RuleListCli();
+    std::string name() const override;
 
     /**
-     * \copydoc Cli::exec
+     * \copydoc cli::exec
      */
-    void exec(Irccdctl &client, const std::vector<std::string> &args) override;
+    void exec(ctl::controller& irccdctl, const std::vector<std::string>& args) override;
 };
 
-/*
- * RuleInfoCli
- * ------------------------------------------------------------------
- */
-
 /**
  * \brief Implementation of irccdctl rule-info.
  */
-class RuleInfoCli : public Cli {
+class rule_info_cli : public cli {
 public:
     /**
-     * Default constructor.
+     * \copydoc cli::name
      */
-    RuleInfoCli();
+    std::string name() const override;
 
     /**
-     * \copydoc Cli::exec
+     * \copydoc cli::exec
      */
-    void exec(Irccdctl &client, const std::vector<std::string> &args) override;
+    void exec(ctl::controller& irccdctl, const std::vector<std::string>& args) override;
 };
 
-/*
- * RuleRemoveCli
- * ------------------------------------------------------------------
- */
-
 /**
  * \brief Implementation of irccdctl rule-remove.
  */
-class RuleRemoveCli : public Cli {
+class rule_remove_cli : public cli {
 public:
     /**
-     * Default constructor.
+     * \copydoc cli::name
      */
-    RuleRemoveCli();
+    std::string name() const override;
 
     /**
-     * \copydoc Cli::exec
+     * \copydoc cli::exec
      */
-    void exec(Irccdctl &client, const std::vector<std::string> &args) override;
+    void exec(ctl::controller& irccdctl, const std::vector<std::string>& args) override;
 };
 
-/*
- * RuleMoveCli
- * ------------------------------------------------------------------
- */
-
 /**
  * \brief Implementation of irccdctl rule-move.
  */
-class RuleMoveCli : public Cli {
+class rule_move_cli : public cli {
 public:
     /**
-     * Default constructor.
+     * \copydoc cli::name
      */
-    RuleMoveCli();
+    std::string name() const override;
 
     /**
-     * \copydoc Cli::exec
+     * \copydoc cli::exec
      */
-    void exec(Irccdctl &client, const std::vector<std::string> &args) override;
+    void exec(ctl::controller& irccdctl, const std::vector<std::string>& args) override;
 };
 
-/*
- * WatchCli.
- * ------------------------------------------------------------------
- */
-
 /**
  * \brief Implementation of irccdctl watch.
  */
-class WatchCli : public Cli {
+class watch_cli : public cli {
 public:
     /**
-     * Default constructor.
+     * \copydoc cli::name
      */
-    WatchCli();
+    std::string name() const override;
 
     /**
-     * \copydoc Cli::exec
+     * \copydoc cli::exec
      */
-    void exec(Irccdctl &client, const std::vector<std::string> &args) override;
+    void exec(ctl::controller& irccdctl, const std::vector<std::string>& args) override;
 };
 
-} // !cli
+} // !ctl
 
 } // !irccd
 
--- a/irccdctl/main.cpp	Tue Nov 14 20:25:30 2017 +0100
+++ b/irccdctl/main.cpp	Thu Nov 16 22:45:12 2017 +0100
@@ -16,92 +16,67 @@
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
 
+#include <irccd/sysconfig.hpp>
+
 #include <unordered_map>
 
 #include <boost/filesystem.hpp>
 #include <boost/timer/timer.hpp>
 
+#include <irccd/ini.hpp>
+#include <irccd/json_util.hpp>
+#include <irccd/logger.hpp>
+#include <irccd/options.hpp>
+#include <irccd/string_util.hpp>
+#include <irccd/system.hpp>
+#include <irccd/util.hpp>
+
+#include <irccd/ctl/controller.hpp>
+#include <irccd/ctl/ip_connection.hpp>
+
+#if !defined(IRCCD_SYSTEM_WINDOWS)
+#   include <irccd/ctl/local_connection.hpp>
+#endif
+
 #include "alias.hpp"
 #include "cli.hpp"
-#include "client.hpp"
-#include "ini.hpp"
-#include "irccdctl.hpp"
-#include "logger.hpp"
-#include "options.hpp"
-#include "system.hpp"
-#include "string_util.hpp"
-#include "util.hpp"
 
-using namespace std::string_literals;
-using namespace irccd;
+namespace irccd {
+
+namespace ctl {
 
 namespace {
 
-std::vector<std::unique_ptr<Cli>> commands;
-std::unordered_map<std::string, Alias> aliases;
-std::unique_ptr<Client> client;
-std::unique_ptr<Irccdctl> irccdctl;
-net::Address address;
+// Main service;
+boost::asio::io_service service;
 
-void usage()
-{
-    bool first = true;
+#if defined(HAVE_SSL)
 
-    for (const auto &cmd : commands) {
-        log::warning() << (first ? "usage: " : "       ") << sys::program_name() << " "
-                       << cmd->usage() << std::endl;
-        first = false;
-    }
-
-    std::exit(1);
-}
+// For tls_connection.
+boost::asio::ssl::context ctx(boost::asio::ssl::context::sslv23);
 
-void help()
-{
-    log::warning() << "usage: " << sys::program_name() << " [options...] <command> [command-options...] [command-args...]\n\n";
-    log::warning() << "General options:\n";
-    log::warning() << "\t-c, --config file\tspecify the configuration file\n";
-    log::warning() << "\t    --help\t\tshow this help\n";
-    log::warning() << "\t-t, --type type\t\tspecify connection type\n";
-    log::warning() << "\t-v, --verbose\t\tbe verbose\n\n";
-    log::warning() << "Available options for type ip and ipv6 (-t, --type):\n";
-    log::warning() << "\t-h, --host address\tconnect to the specified address\n";
-    log::warning() << "\t-p, --port port\t\tuse the specified port number\n\n";
-    log::warning() << "Available options for type unix (-t, --type):\n";
-    log::warning() << "\t-P, --path file\t\tconnect to the specified socket file\n\n";
-    log::warning() << "Available commands:\n";
-
-    for (const auto &cmd : commands)
-        log::warning() << "\t" << std::left << std::setw(32)
-                       << cmd->name() << cmd->summary() << std::endl;
+#endif
 
-    log::warning() << "\nFor more information on a command, type " << sys::program_name() << " help <command>" << std::endl;
-    std::exit(1);
-}
-
-void help(const std::string &command)
-{
-    auto it = std::find_if(commands.begin(), commands.end(), [&] (const auto &c) {
-        return c->name() == command;
-    });
+// Connection to instance.
+std::unique_ptr<connection> conn;
+std::unique_ptr<controller> ctl;
 
-    if (it == commands.end()) {
-        log::warning() << "no command named " << command << std::endl;
-    } else {
-        log::warning() << "usage: " << sys::program_name() << " " << (*it)->usage() << "\n" << std::endl;
-        log::warning() << (*it)->help() << std::endl;
-    }
-
-    std::exit(1);
-}
+// List of all commands and alias.
+std::unordered_map<std::string, alias> aliases;
+std::unordered_map<std::string, std::unique_ptr<cli>> commands;
 
 /*
  * Configuration file parsing.
  * -------------------------------------------------------------------
  */
 
+void usage()
+{
+    std::exit(1);
+}
+
 /*
- * readConnectIp
+ * read_connect_ip
  * -------------------------------------------------------------------
  *
  * Extract IP connection information from the config file.
@@ -113,10 +88,12 @@
  * domain = "ipv4 or ipv6" (Optional, default: ipv4)
  * ssl = true | false
  */
-void readConnectIp(const ini::section &sc)
+std::unique_ptr<connection> read_connect_ip(const ini::section& sc)
 {
+    std::unique_ptr<connection> conn;
+    std::string host;
+    std::uint16_t port;
     ini::section::const_iterator it;
-    std::string host, port;
 
     if ((it = sc.find("host")) == sc.end())
         throw std::invalid_argument("missing host parameter");
@@ -126,34 +103,22 @@
     if ((it = sc.find("port")) == sc.end())
         throw std::invalid_argument("missing port parameter");
 
-    port = it->value();
-
-    int domain = AF_INET;
-
-    if ((it = sc.find("domain")) != sc.end() || (it = sc.find("family")) != sc.end()) {
-        if (it->value() == "ipv6") {
-            domain = AF_INET6;
-        } else if (it->value() == "ipv4") {
-            domain = AF_INET;
-        } else {
-            throw std::invalid_argument("invalid family: " + it->value());
-        }
-    }
-
-    address = net::resolveOne(host, port, domain, SOCK_STREAM);
+    port = string_util::to_number<std::uint16_t>(it->value());
 
     if ((it = sc.find("ssl")) != sc.end() && string_util::is_boolean(it->value()))
 #if defined(HAVE_SSL)
-        client = std::make_unique<TlsClient>();
+        conn = std::make_unique<tls_connection>(service, ctx, host, port);
 #else
         throw std::runtime_error("SSL disabled");
 #endif
     else
-        client = std::make_unique<Client>();
+        conn = std::make_unique<ip_connection>(service, host, port);
+
+    return conn;
 }
 
 /*
- * readConnectLocal
+ * read_connect_local
  * -------------------------------------------------------------------
  *
  * Extract local connection for Unix.
@@ -162,7 +127,7 @@
  * type = "unix"
  * path = "path to socket file"
  */
-void readConnectLocal(const ini::section &sc)
+std::unique_ptr<connection> read_connect_local(const ini::section& sc)
 {
 #if !defined(IRCCD_SYSTEM_WINDOWS)
     auto it = sc.find("path");
@@ -170,8 +135,7 @@
     if (it == sc.end())
         throw std::invalid_argument("missing path parameter");
 
-    address = net::local::create(it->value());
-    client = std::make_unique<Client>();
+    return std::make_unique<local_connection>(service, it->value());
 #else
     (void)sc;
 
@@ -180,34 +144,37 @@
 }
 
 /*
- * readConnect
+ * read_connect
  * -------------------------------------------------------------------
  *
  * Generic function for reading the [connect] section.
  */
-void readConnect(const ini::section &sc)
+void read_connect(const ini::section& sc)
 {
     auto it = sc.find("type");
 
     if (it == sc.end())
         throw std::invalid_argument("missing type parameter");
 
-    if (it->value() == "ip") {
-        readConnectIp(sc);
-    } else if (it->value() == "unix") {
-        readConnectLocal(sc);
-    } else {
-        throw std::invalid_argument("invalid type given: " + it->value());
+    if (it->value() == "ip")
+        conn = read_connect_ip(sc);
+    else if (it->value() == "unix")
+        conn = 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);
+
+        auto password = sc.find("password");
+
+        if (password != sc.end())
+            ctl->set_password(password->value());
     }
-
-    auto password = sc.find("password");
-
-    if (password != sc.end())
-        client->setPassword(password->value());
 }
 
 /*
- * readGeneral
+ * read_general
  * -------------------------------------------------------------------
  *
  * Read the general section.
@@ -215,7 +182,7 @@
  * [general]
  * verbose = true
  */
-void readGeneral(const ini::section &sc)
+void read_general(const ini::section& sc)
 {
     auto verbose = sc.find("verbose");
 
@@ -224,7 +191,7 @@
 }
 
 /*
- * readAlias
+ * read_alias
  * -------------------------------------------------------------------
  *
  * Read aliases for irccdctl.
@@ -233,15 +200,15 @@
  * cmd1 = ( "command", "arg1, "...", "argn" )
  * cmd2 = ( "command", "arg1, "...", "argn" )
  */
-Alias readAlias(const ini::section &sc, const std::string &name)
+alias read_alias(const ini::section& sc, const std::string& name)
 {
-    Alias alias(name);
+    alias alias(name);
 
     /*
      * Each defined option is a command that the user can call. The name is
      * unused and serves as documentation purpose.
      */
-    for (const auto &option : sc) {
+    for (const auto& option : sc) {
         /*
          * Iterate over the arguments which are usually a list and the first
          * argument is a command name.
@@ -250,7 +217,7 @@
             throw std::runtime_error(string_util::sprintf("alias %s: missing command name in '%s'", name, option.key()));
 
         std::string command = option[0];
-        std::vector<AliasArg> args(option.begin() + 1, option.end());
+        std::vector<alias_arg> args(option.begin() + 1, option.end());
 
         alias.emplace_back(std::move(command), std::move(args));
     }
@@ -258,22 +225,22 @@
     return alias;
 }
 
-void read(const std::string &path)
+void read(const std::string& path)
 {
     try {
         ini::document doc = ini::read_file(path);
         ini::document::const_iterator it;
 
-        if (!client && (it = doc.find("connect")) != doc.end())
-            readConnect(*it);
+        if (!ctl && (it = doc.find("connect")) != doc.end())
+            read_connect(*it);
         if ((it = doc.find("general")) != doc.end())
-            readGeneral(*it);
+            read_general(*it);
 
         // [alias.*] sections.
         for (const auto& sc : doc) {
             if (sc.key().compare(0, 6, "alias.") == 0) {
                 auto name = sc.key().substr(6);
-                auto alias = readAlias(sc, name);
+                auto alias = read_alias(sc, name);
 
                 aliases.emplace(std::move(name), std::move(alias));
             }
@@ -289,7 +256,7 @@
  */
 
 /*
- * parseConnectIp
+ * parse_connect_ip
  * ------------------------------------------------------------------
  *
  * Parse internet connection from command line.
@@ -298,50 +265,35 @@
  * -h host or ip
  * -p port
  */
-void parseConnectIp(const option::result &options)
+std::unique_ptr<connection> parse_connect_ip(const option::result& options)
 {
     option::result::const_iterator it;
 
     // Host (-h or --host).
-    std::string host;
-
-    if ((it = options.find("-h")) == options.end() && (it = options.find("--host")) == options.end()) {
+    if ((it = options.find("-h")) == options.end() && (it = options.find("--host")) == options.end())
         throw std::invalid_argument("missing host argument (-h or --host)");
-    }
 
-    host = it->second;
+    auto host = it->second;
 
     // Port (-p or --port).
-    std::string port;
-
-    if ((it = options.find("-p")) == options.end() && (it = options.find("--port")) == options.end()) {
+    if ((it = options.find("-p")) == options.end() && (it = options.find("--port")) == options.end())
         throw std::invalid_argument("missing port argument (-p or --port)");
-    }
-
-    port = it->second;
 
-    // Domain
-    int domain = AF_INET;
+    auto port = string_util::to_number<std::uint16_t>(it->second);
 
-    if ((it = options.find("-t")) != options.end()) {
-        domain = it->second == "ipv6" ? AF_INET6 : AF_INET;
-    } else if ((it = options.find("--type")) != options.end()) {
-        domain = it->second == "ipv6" ? AF_INET6: AF_INET;
-    }
 
-    address = net::resolveOne(host, port, domain, SOCK_STREAM);
-    client  = std::make_unique<Client>();
+    return std::make_unique<ip_connection>(service, host, port);
 }
 
 /*
- * parseConnectLocal
+ * parse_connect_local
  * ------------------------------------------------------------------
  *
  * Parse local connection.
  *
  * -P file
  */
-void parseConnectLocal(const option::result &options)
+std::unique_ptr<connection> parse_connect_local(const option::result& options)
 {
 #if !defined(IRCCD_SYSTEM_WINDOWS)
     option::result::const_iterator it;
@@ -349,8 +301,7 @@
     if ((it = options.find("-P")) == options.end() && (it = options.find("--path")) == options.end())
         throw std::invalid_argument("missing path parameter (-P or --path)");
 
-    address = net::local::create(it->second, false);
-    client = std::make_unique<Client>();
+    return std::make_unique<local_connection>(service, it->second);
 #else
     (void)options;
 
@@ -359,12 +310,12 @@
 }
 
 /*
- * parseConnect
+ * parse_connect
  * ------------------------------------------------------------------
  *
  * Generic parsing of command line option for connection.
  */
-void parseConnect(const option::result &options)
+void parse_connect(const option::result& options)
 {
     assert(options.count("-t") > 0 || options.count("--type") > 0);
 
@@ -372,15 +323,19 @@
 
     if (it == options.end())
         it = options.find("--type");
+
     if (it->second == "ip" || it->second == "ipv6")
-        return parseConnectIp(options);
+        conn = parse_connect_ip(options);
     if (it->second == "unix")
-        return parseConnectLocal(options);
+        conn = parse_connect_local(options);
+    else
+        throw std::invalid_argument(string_util::sprintf("invalid type given: %s", it->second));
 
-    throw std::invalid_argument("invalid type given: " + it->second);
+    if (conn)
+        ctl = std::make_unique<controller>(*conn);
 }
 
-option::result parse(int &argc, char **&argv)
+option::result parse(int& argc, char**& argv)
 {
     // 1. Parse command line options.
     option::options def{
@@ -404,15 +359,13 @@
     try {
         result = option::read(argc, argv, def);
 
-        if (result.count("--help") != 0) {
+        if (result.count("--help") > 0 || result.count("-h") > 0)
             usage();
             // NOTREACHED
-        }
 
-        if (result.count("-v") != 0 || result.count("--verbose") != 0) {
+        if (result.count("-v") != 0 || result.count("--verbose") != 0)
             log::set_verbose(true);
-        }
-    } catch (const std::exception &ex) {
+    } catch (const std::exception& ex) {
         log::warning() << "irccdctl: " << ex.what() << std::endl;
         usage();
     }
@@ -422,31 +375,27 @@
 
 void exec(std::vector<std::string>);
 
-void exec(const Alias &alias, std::vector<std::string> argsCopy)
+void exec(const alias& alias, std::vector<std::string> args_copy)
 {
-    std::vector<nlohmann::json> values;
-
-    for (const AliasCommand &cmd : alias) {
-        std::vector<std::string> args(argsCopy);
-        std::vector<std::string> cmdArgs;
+    for (const auto& cmd : alias) {
+        std::vector<std::string> args(args_copy);
+        std::vector<std::string> cmd_args;
         std::vector<std::string>::size_type toremove = 0;
 
         // 1. Append command name before.
-        cmdArgs.push_back(cmd.command());
+        cmd_args.push_back(cmd.command());
 
-        for (const auto &arg : cmd.args()) {
-            if (arg.isPlaceholder()) {
+        for (const auto& arg : cmd.args()) {
+            if (arg.is_placeholder()) {
                 if (args.size() < arg.index() + 1)
                     throw std::invalid_argument(string_util::sprintf("missing argument for placeholder %d", arg.index()));
 
-                cmdArgs.push_back(args[arg.index()]);
+                cmd_args.push_back(args[arg.index()]);
 
-                if (arg.index() + 1 > toremove) {
+                if (arg.index() + 1 > toremove)
                     toremove = arg.index() + 1;
-                }
-            } else {
-                cmdArgs.push_back(arg.value());
-            }
+            } else
+                cmd_args.push_back(arg.value());
         }
 
         assert(toremove <= args.size());
@@ -455,10 +404,10 @@
         args.erase(args.begin(), args.begin() + toremove);
 
         // 3. Now append the rest of arguments.
-        std::copy(args.begin(), args.end(), std::back_inserter(cmdArgs));
+        std::copy(args.begin(), args.end(), std::back_inserter(cmd_args));
 
         // 4. Finally try to execute.
-        exec(cmdArgs);
+        exec(cmd_args);
     }
 }
 
@@ -472,69 +421,108 @@
     // Remove name.
     args.erase(args.begin());
 
-    if (alias != aliases.end()) {
+    if (alias != aliases.end())
         exec(alias->second, args);
-    } else {
-        auto cmd = std::find_if(commands.begin(), commands.end(), [&] (auto &it) {
-            return it->name() == name;
-        });
+    else {
+        auto cmd = commands.find(name);
 
-        if (cmd != commands.end()) {
-            (*cmd)->exec(*irccdctl, args);
-        } else {
+        if (cmd != commands.end())
+            cmd->second->exec(*ctl, args);
+        else
             throw std::invalid_argument("no alias or command named " + name);
-        }
     }
 }
 
 void init(int &argc, char **&argv)
 {
     sys::set_program_name("irccdctl");
-    net::init();
+
+    -- argc;
+    ++ argv;
 
-    --argc;
-    ++argv;
+    auto add = [] (auto c) {
+        commands.emplace(c->name(), std::move(c));
+    };
 
-    commands.push_back(std::make_unique<cli::PluginConfigCli>());
-    commands.push_back(std::make_unique<cli::PluginInfoCli>());
-    commands.push_back(std::make_unique<cli::PluginListCli>());
-    commands.push_back(std::make_unique<cli::PluginLoadCli>());
-    commands.push_back(std::make_unique<cli::PluginReloadCli>());
-    commands.push_back(std::make_unique<cli::PluginUnloadCli>());
-    commands.push_back(std::make_unique<cli::ServerChannelMode>());
-    commands.push_back(std::make_unique<cli::ServerChannelNoticeCli>());
-    commands.push_back(std::make_unique<cli::ServerConnectCli>());
-    commands.push_back(std::make_unique<cli::ServerDisconnectCli>());
-    commands.push_back(std::make_unique<cli::ServerInfoCli>());
-    commands.push_back(std::make_unique<cli::ServerInviteCli>());
-    commands.push_back(std::make_unique<cli::ServerJoinCli>());
-    commands.push_back(std::make_unique<cli::ServerKickCli>());
-    commands.push_back(std::make_unique<cli::ServerListCli>());
-    commands.push_back(std::make_unique<cli::ServerMeCli>());
-    commands.push_back(std::make_unique<cli::ServerMessageCli>());
-    commands.push_back(std::make_unique<cli::ServerModeCli>());
-    commands.push_back(std::make_unique<cli::ServerNickCli>());
-    commands.push_back(std::make_unique<cli::ServerNoticeCli>());
-    commands.push_back(std::make_unique<cli::ServerPartCli>());
-    commands.push_back(std::make_unique<cli::ServerReconnectCli>());
-    commands.push_back(std::make_unique<cli::ServerTopicCli>());
-    commands.push_back(std::make_unique<cli::RuleAddCli>());
-    commands.push_back(std::make_unique<cli::RuleEditCli>());
-    commands.push_back(std::make_unique<cli::RuleListCli>());
-    commands.push_back(std::make_unique<cli::RuleInfoCli>());
-    commands.push_back(std::make_unique<cli::RuleMoveCli>());
-    commands.push_back(std::make_unique<cli::RuleRemoveCli>());
-    commands.push_back(std::make_unique<cli::WatchCli>());
+    add(std::make_unique<plugin_config_cli>());
+    add(std::make_unique<plugin_info_cli>());
+    add(std::make_unique<plugin_list_cli>());
+    add(std::make_unique<plugin_load_cli>());
+    add(std::make_unique<plugin_reload_cli>());
+    add(std::make_unique<plugin_unload_cli>());
+    add(std::make_unique<server_channel_mode_cli>());
+    add(std::make_unique<server_channel_notice_cli>());
+    add(std::make_unique<server_connect_cli>());
+    add(std::make_unique<server_disconnect_cli>());
+    add(std::make_unique<server_info_cli>());
+    add(std::make_unique<server_invite_cli>());
+    add(std::make_unique<server_join_cli>());
+    add(std::make_unique<server_kick_cli>());
+    add(std::make_unique<server_list_cli>());
+    add(std::make_unique<server_me_cli>());
+    add(std::make_unique<server_message_cli>());
+    add(std::make_unique<server_mode_cli>());
+    add(std::make_unique<server_nick_cli>());
+    add(std::make_unique<server_notice_cli>());
+    add(std::make_unique<server_part_cli>());
+    add(std::make_unique<server_reconnect_cli>());
+    add(std::make_unique<server_topic_cli>());
+    add(std::make_unique<rule_add_cli>());
+    add(std::make_unique<rule_edit_cli>());
+    add(std::make_unique<rule_list_cli>());
+    add(std::make_unique<rule_info_cli>());
+    add(std::make_unique<rule_move_cli>());
+    add(std::make_unique<rule_remove_cli>());
+    add(std::make_unique<watch_cli>());
+}
+
+void do_connect()
+{
+    bool connected = false;
+
+    ctl->connect([&] (auto code, auto info) {
+        if (code)
+            throw boost::system::system_error(code);
+
+        log::info(string_util::sprintf("connected to irccd %d.%d.%d",
+            json_util::to_int(info["major"]),
+            json_util::to_int(info["minor"]),
+            json_util::to_int(info["patch"])));
+
+        connected = true;
+    });
+
+    while (!connected)
+        service.run();
+
+    service.reset();
+}
+
+void do_exec(int argc, char** argv)
+{
+    std::vector<std::string> args;
+
+    for (int i = 0; i < argc; ++i)
+        args.push_back(argv[i]);
+
+    exec(args);
+
+    while (ctl->has_pending())
+        service.run();
 }
 
 } // !namespace
 
-int main(int argc, char **argv)
+} // !ctl
+
+} // !irccd
+
+int main(int argc, char** argv)
 {
-    init(argc, argv);
+    irccd::ctl::init(argc, argv);
 
     // 1. Read command line arguments.
-    auto result = parse(argc, argv);
+    auto result = irccd::ctl::parse(argc, argv);
 
     /*
      * 2. Open optional config by command line or by searching it
@@ -547,65 +535,42 @@
      */
     try {
         if (result.count("-t") > 0 || result.count("--type") > 0)
-            parseConnect(result);
+            irccd::ctl::parse_connect(result);
 
         auto it = result.find("-c");
 
         if (it != result.end() || (it = result.find("--config")) != result.end())
-            read(it->second);
+            irccd::ctl::read(it->second);
         else {
-            for (const auto& path : sys::config_filenames("irccdctl.conf")) {
+            for (const auto& path : irccd::sys::config_filenames("irccdctl.conf")) {
                 boost::system::error_code ec;
 
                 if (boost::filesystem::exists(path, ec) && !ec) {
-                    read(path);
+                    irccd::ctl::read(path);
                     break;
                 }
             }
         }
-    } catch (const std::exception &ex) {
-        log::warning() << sys::program_name() << ": " << ex.what() << std::endl;
-        std::exit(1);
-    }
-
-    if (argc <= 0) {
-        usage();
-        // NOTREACHED
-    }
-    if (std::strcmp(argv[0], "help") == 0) {
-        if (argc >= 2)
-            help(argv[1]);
-        else
-            help();
-        // NOTREACHED
-    }
-
-    if (!client) {
-        log::warning("irccdctl: no connection specified");
-        std::exit(1);
+    } catch (const std::exception& ex) {
+        irccd::log::warning() << "abort: " << ex.what() << std::endl;
+        return 1;
     }
 
-    irccdctl = std::make_unique<Irccdctl>(std::move(client));
-    irccdctl->client().onDisconnect.connect([&] (auto reason) {
-        log::warning() << "connection lost to irccd: " << reason << std::endl;
-    });
-    irccdctl->client().onConnect.connect([&] (auto info) {
-        log::info() << "connected to irccd "
-                    << info.major << "."
-                    << info.minor << "."
-                    << info.patch << std::endl;
-    });
-    irccdctl->client().connect(address);
+    if (argc <= 0)
+        irccd::ctl::usage();
+        // NOTREACHED
 
-    // Build a vector of arguments.
-    std::vector<std::string> args;
-
-    for (int i = 0; i < argc; ++i)
-        args.push_back(argv[i]);
+    if (!irccd::ctl::ctl) {
+        irccd::log::warning("abort: no connection specified");
+        return 1;
+    }
 
     try {
-        exec(args);
-    } catch (const std::exception &ex) {
-        std::cerr << sys::program_name() << ": unrecoverable error: " << ex.what() << std::endl;
+        irccd::ctl::do_connect();
+        irccd::ctl::do_exec(argc, argv);
+    } catch (const boost::system::system_error& ex) {
+        std::cerr << "abort: " << ex.code().message() << std::endl;
+    } catch (const std::exception& ex) {
+        std::cerr << "abort: " << ex.what() << std::endl;
     }
 }
--- a/libcommon/CMakeLists.txt	Tue Nov 14 20:25:30 2017 +0100
+++ b/libcommon/CMakeLists.txt	Thu Nov 16 22:45:12 2017 +0100
@@ -22,6 +22,7 @@
 
 set(
     HEADERS
+    ${libcommon_SOURCE_DIR}/irccd/errors.hpp
     ${libcommon_SOURCE_DIR}/irccd/fs_util.hpp
     ${libcommon_SOURCE_DIR}/irccd/ini.hpp
     ${libcommon_SOURCE_DIR}/irccd/json_util.hpp
@@ -38,6 +39,7 @@
 
 set(
     SOURCES
+    ${libcommon_SOURCE_DIR}/irccd/errors.cpp
     ${libcommon_SOURCE_DIR}/irccd/ini.cpp
     ${libcommon_SOURCE_DIR}/irccd/json_util.cpp
     ${libcommon_SOURCE_DIR}/irccd/logger.cpp
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libcommon/irccd/errors.cpp	Thu Nov 16 22:45:12 2017 +0100
@@ -0,0 +1,57 @@
+/*
+ * errors.cpp -- describe some error codes
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "errors.hpp"
+
+namespace irccd {
+
+const boost::system::error_category& network_category() noexcept
+{
+    static const class network_category : public boost::system::error_category {
+    public:
+        const char* name() const noexcept override
+        {
+            return "network_category";
+        }
+
+        std::string message(int code) const override
+        {
+            switch (static_cast<network_error>(code)) {
+            case network_error::invalid_program:
+                return "invalid program";
+            case network_error::invalid_version:
+                return "invalid version";
+            case network_error::invalid_auth:
+                return "invalid authentication";
+            case network_error::invalid_message:
+                return "invalid message";
+            default:
+                return "unknown error";
+            }
+        }
+    } category;
+
+    return category;
+}
+
+boost::system::error_code make_error_code(network_error errc) noexcept
+{
+    return {static_cast<int>(errc), network_category()};
+}
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libcommon/irccd/errors.hpp	Thu Nov 16 22:45:12 2017 +0100
@@ -0,0 +1,71 @@
+/*
+ * errors.hpp -- describe some error codes
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef IRCCD_COMMON_ERRORS_HPP
+#define IRCCD_COMMON_ERRORS_HPP
+
+/**
+ * \file errors.hpp
+ * \brief Describe some error codes.
+ */
+
+#include <boost/system/error_code.hpp>
+
+#include <type_traits>
+
+namespace irccd {
+
+/**
+ * \brief Error code for transport/irccdctl
+ */
+enum class network_error {
+    invalid_program = 1,    //!< connected daemon is not irccd
+    invalid_version,        //!< irccd daemon is incompatible
+    invalid_auth,           //!< invalid credentials in auth command
+    invalid_message         //!< the message was not JSON
+};
+
+/**
+ * Get the network category singleton.
+ *
+ * \return the category for network_error enum
+ */
+const boost::system::error_category& network_category() noexcept;
+
+/**
+ * Construct an error_code from network_error enum.
+ *
+ * \return the error code
+ */
+boost::system::error_code make_error_code(network_error errc) noexcept;
+
+} // !irccd
+
+namespace boost {
+
+namespace system {
+
+template <>
+struct is_error_code_enum<irccd::network_error> : public std::true_type {
+};
+
+} // !system
+
+} // !boost
+
+#endif // !IRCCD_COMMON_ERRORS_HPP
--- a/libirccd-test/CMakeLists.txt	Tue Nov 14 20:25:30 2017 +0100
+++ b/libirccd-test/CMakeLists.txt	Thu Nov 16 22:45:12 2017 +0100
@@ -21,8 +21,6 @@
 irccd_define_library(
     TARGET libirccd-test
     SOURCES
-        ${libirccd-test_SOURCE_DIR}/irccd/command-tester.cpp
-        ${libirccd-test_SOURCE_DIR}/irccd/command-tester.hpp
         ${libirccd-test_SOURCE_DIR}/irccd/journal_server.cpp
         ${libirccd-test_SOURCE_DIR}/irccd/journal_server.hpp
         $<$<BOOL:${HAVE_JS}>:${libirccd-test_SOURCE_DIR}/irccd/plugin_test.cpp>
--- a/libirccd-test/irccd/command-tester.cpp	Tue Nov 14 20:25:30 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,47 +0,0 @@
-/*
- * command-tester.cpp -- test fixture helper for remote commands
- *
- * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#include "command-tester.hpp"
-#include "client.hpp"
-#include "logger.hpp"
-#include "service.hpp"
-#include "transport.hpp"
-
-namespace irccd {
-
-CommandTester::CommandTester(std::unique_ptr<command> cmd,
-                             std::unique_ptr<server> server)
-    : m_irccdctl(std::make_unique<Client>())
-{
-    // Be silent.
-    log::set_logger(std::make_unique<log::silent_logger>());
-    log::set_verbose(false);
-
-    auto tpt = std::make_unique<transport_server_ip>("*", 0);
-    auto port = tpt->port();
-
-    m_irccd.transports().add(std::move(tpt));
-    m_irccdctl.client().connect(net::ipv4::pton("127.0.0.1", port));
-
-    if (cmd)
-        m_irccd.commands().add(std::move(cmd));
-    if (server)
-        m_irccd.servers().add(std::move(server));
-}
-
-} // !irccd
--- a/libirccd-test/irccd/command-tester.hpp	Tue Nov 14 20:25:30 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,56 +0,0 @@
-/*
- * command-tester.hpp -- test fixture helper for remote commands
- *
- * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#ifndef IRCCD_COMMAND_TESTER_HPP
-#define IRCCD_COMMAND_TESTER_HPP
-
-#include <boost/timer/timer.hpp>
-
-#include <gtest/gtest.h>
-
-#include "irccd.hpp"
-#include "irccdctl.hpp"
-#include "net_util.hpp"
-
-namespace irccd {
-
-class command;
-class server;
-
-class CommandTester : public testing::Test {
-protected:
-    irccd m_irccd;
-    Irccdctl m_irccdctl;
-
-public:
-    CommandTester(std::unique_ptr<command> cmd = nullptr,
-                  std::unique_ptr<server> server = nullptr);
-
-    template <typename Predicate>
-    void poll(Predicate &&predicate)
-    {
-        boost::timer::cpu_timer timer;
-
-        while (!predicate() && timer.elapsed().wall / 1000000LL < 30000)
-            net_util::poll(250, m_irccd, m_irccdctl);
-    }
-};
-
-} // !irccd
-
-#endif // !IRCCD_COMMAND_TESTER_HPP
--- a/libirccdctl/CMakeLists.txt	Tue Nov 14 20:25:30 2017 +0100
+++ b/libirccdctl/CMakeLists.txt	Thu Nov 16 22:45:12 2017 +0100
@@ -20,21 +20,33 @@
 
 set(
     HEADERS
-    ${libirccdctl_SOURCE_DIR}/irccd/client.hpp
-    ${libirccdctl_SOURCE_DIR}/irccd/irccdctl.hpp
+    ${libirccdctl_SOURCE_DIR}/irccd/ctl/controller.hpp
+    ${libirccdctl_SOURCE_DIR}/irccd/ctl/connection.hpp
+    ${libirccdctl_SOURCE_DIR}/irccd/ctl/network_connection.hpp
+    ${libirccdctl_SOURCE_DIR}/irccd/ctl/ip_connection.hpp
 )
 
 set(
     SOURCES
-    ${libirccdctl_SOURCE_DIR}/irccd/client.cpp
+    ${libirccdctl_SOURCE_DIR}/irccd/ctl/controller.cpp
+    ${libirccdctl_SOURCE_DIR}/irccd/ctl/ip_connection.cpp
 )
 
+if (NOT IRCCD_SYSTEM_WINDOWS)
+    list(APPEND LIBRARIES -pthread)
+    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
         ${libirccdctl_SOURCE_DIR}/CMakeLists.txt
         ${HEADERS}
         ${SOURCES}
-    LIBRARIES libcommon
-    PUBLIC_INCLUDES ${libirccdctl_SOURCE_DIR}/irccd
+    LIBRARIES
+        ${LIBRARIES}
+        libcommon
+    PUBLIC_INCLUDES
+        $<BUILD_INTERFACE:${libirccdctl_SOURCE_DIR}>
 )
--- a/libirccdctl/irccd/client.cpp	Tue Nov 14 20:25:30 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,553 +0,0 @@
-/*
- * client.cpp -- value wrapper for connecting to irccd
- *
- * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN Client WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#include <stdexcept>
-
-#include "client.hpp"
-#include "net_util.hpp"
-#include "string_util.hpp"
-
-namespace irccd {
-
-/*
- * Client::State.
- * ------------------------------------------------------------------
- */
-
-class Client::State {
-public:
-    State() = default;
-    virtual ~State() = default;
-    virtual Status status() const noexcept = 0;
-    virtual void prepare(Client &cnt, fd_set &in, fd_set &out) = 0;
-    virtual void sync(Client &cnt, fd_set &in, fd_set &out) = 0;
-};
-
-/*
- * Client::DisconnectedState.
- * ------------------------------------------------------------------
- */
-
-class Client::DisconnectedState : public Client::State {
-public:
-    Client::Status status() const noexcept override
-    {
-        return Disconnected;
-    }
-
-    void prepare(Client &, fd_set &, fd_set &) override {}
-    void sync(Client &, fd_set &, fd_set &) override {}
-};
-
-/*
- * Client::DisconnectedState.
- * ------------------------------------------------------------------
- */
-
-class Client::ReadyState : public Client::State {
-private:
-    void parse(Client &client, const std::string &message)
-    {
-        try {
-            auto json = nlohmann::json::parse(message);
-
-            if (!json.is_object())
-                return;
-
-            if (json.count("event") > 0)
-                client.onEvent(json);
-            else
-                client.onMessage(json);
-        } catch (const std::exception &) {
-        }
-    }
-public:
-    Client::Status status() const noexcept override
-    {
-        return Ready;
-    }
-
-    void prepare(Client &cnx, fd_set &in, fd_set &out) override
-    {
-        FD_SET(cnx.m_socket.handle(), &in);
-
-        if (!cnx.m_output.empty())
-            FD_SET(cnx.m_socket.handle(), &out);
-    }
-
-    void sync(Client &cnx, fd_set &in, fd_set &out) override
-    {
-        if (FD_ISSET(cnx.m_socket.handle(), &out))
-            cnx.send();
-
-        if (FD_ISSET(cnx.m_socket.handle(), &in))
-            cnx.recv();
-
-        std::string msg;
-
-        do {
-            msg = net_util::next_network(cnx.m_input);
-
-            if (!msg.empty())
-                parse(cnx, msg);
-        } while (!msg.empty());
-    }
-};
-
-/*
- * Client::AuthState.
- * ------------------------------------------------------------------
- */
-
-class Client::AuthState : public Client::State {
-private:
-    enum {
-        Created,
-        Sending,
-        Checking
-    } m_auth{Created};
-
-    std::string m_output;
-
-    void send(Client &cnt) noexcept
-    {
-        try {
-            auto n = cnt.send(m_output.data(), m_output.size());
-
-            if (n == 0) {
-                m_output.clear();
-                throw std::runtime_error("Client lost");
-            }
-
-            m_output.erase(0, n);
-
-            if (m_output.empty())
-                m_auth = Checking;
-        } catch (const std::exception &ex) {
-            cnt.m_state = std::make_unique<DisconnectedState>();
-            cnt.onDisconnect(ex.what());
-        }
-    }
-
-    void check(Client &cnt) noexcept
-    {
-        cnt.recv();
-
-        auto msg = net_util::next_network(cnt.m_input);
-
-        if (msg.empty())
-            return;
-
-        try {
-            auto doc = nlohmann::json::parse(msg);
-
-            if (!doc.is_object())
-                throw std::invalid_argument("invalid argument");
-
-            auto cmd = doc.find("response");
-
-            if (cmd == doc.end() || !cmd->is_string() || *cmd != "auth")
-                throw std::invalid_argument("authentication result expected");
-
-            auto result = doc.find("result");
-
-            if (result == doc.end() || !result->is_boolean())
-                throw std::invalid_argument("bad protocol");
-
-            if (!*result)
-                throw std::runtime_error("authentication failed");
-
-            cnt.m_state = std::make_unique<ReadyState>();
-        } catch (const std::exception &ex) {
-            cnt.m_state = std::make_unique<DisconnectedState>();
-            cnt.onDisconnect(ex.what());
-        }
-    }
-
-public:
-    Client::Status status() const noexcept override
-    {
-        return Authenticating;
-    }
-
-    void prepare(Client &cnt, fd_set &in, fd_set &out) override
-    {
-        switch (m_auth) {
-        case Created:
-            m_auth = Sending;
-            m_output += nlohmann::json({
-                { "command", "auth" },
-                { "password", cnt.m_password }
-            }).dump();
-            m_output += "\r\n\r\n";
-
-            // FALLTHROUGH
-        case Sending:
-            FD_SET(cnt.m_socket.handle(), &out);
-            break;
-        case Checking:
-            FD_SET(cnt.m_socket.handle(), &in);
-            break;
-        default:
-            break;
-        }
-    }
-
-    void sync(Client &cnt, fd_set &in, fd_set &out) override
-    {
-        switch (m_auth) {
-        case Sending:
-            if (FD_ISSET(cnt.m_socket.handle(), &out))
-                send(cnt);
-            break;
-        case Checking:
-            if (FD_ISSET(cnt.m_socket.handle(), &in))
-                check(cnt);
-            break;
-        default:
-            break;
-        }
-    }
-};
-
-/*
- * Client::CheckingState.
- * ------------------------------------------------------------------
- */
-
-class Client::CheckingState : public Client::State {
-private:
-    void verifyProgram(const nlohmann::json &json) const
-    {
-        auto prog = json.find("program");
-
-        if (prog == json.end() || !prog->is_string() || prog->get<std::string>() != "irccd")
-            throw std::runtime_error("not an irccd instance");
-    }
-
-    void verifyVersion(Client &cnx, const nlohmann::json &json) const
-    {
-        auto getVersionVar = [&] (auto key) {
-            auto it = json.find(key);
-
-            if (it == json.end() || !it->is_number_unsigned())
-                throw std::runtime_error("invalid irccd instance");
-
-            return *it;
-        };
-
-        Info info{
-            getVersionVar("major"),
-            getVersionVar("minor"),
-            getVersionVar("patch")
-        };
-
-        // Ensure compatibility.
-        if (info.major != IRCCD_VERSION_MAJOR || info.minor > IRCCD_VERSION_MINOR)
-            throw std::runtime_error(string_util::sprintf("server version too recent %d.%d.%d vs %d.%d.%d",
-                info.major, info.minor, info.patch,
-                IRCCD_VERSION_MAJOR, IRCCD_VERSION_MINOR, IRCCD_VERSION_PATCH
-            ));
-
-        // Successfully connected.
-        if (cnx.m_password.empty())
-            cnx.m_stateNext = std::make_unique<ReadyState>();
-        else
-            cnx.m_stateNext = std::make_unique<AuthState>();
-
-        cnx.onConnect(info);
-    }
-
-    void verify(Client &cnx) const
-    {
-        auto msg = net_util::next_network(cnx.m_input);
-
-        if (msg.empty())
-            return;
-
-        try {
-            auto json = nlohmann::json::parse(msg);
-
-            verifyProgram(json);
-            verifyVersion(cnx, json);
-        } catch (const std::exception &ex) {
-            cnx.m_stateNext = std::make_unique<DisconnectedState>();
-            cnx.onDisconnect(ex.what());
-        }
-    }
-
-public:
-    Client::Status status() const noexcept override
-    {
-        return Checking;
-    }
-
-    void prepare(Client &cnx, fd_set &in, fd_set &) override
-    {
-        FD_SET(cnx.m_socket.handle(), &in);
-    }
-
-    void sync(Client &cnx, fd_set &in, fd_set &) override
-    {
-        if (FD_ISSET(cnx.m_socket.handle(), &in)) {
-            cnx.recv();
-            verify(cnx);
-        }
-    }
-};
-
-/*
- * Client::ConnectingState.
- * ------------------------------------------------------------------
- */
-
-class Client::ConnectingState : public Client::State {
-public:
-    Client::Status status() const noexcept override
-    {
-        return Connecting;
-    }
-
-    void prepare(Client &cnx, fd_set &, fd_set &out) override
-    {
-        FD_SET(cnx.m_socket.handle(), &out);
-    }
-
-    void sync(Client &cnx, fd_set &, fd_set &out) override
-    {
-        if (!FD_ISSET(cnx.m_socket.handle(), &out))
-            return;
-
-        try {
-            auto errc = cnx.m_socket.get<int>(SOL_SOCKET, SO_ERROR);
-
-            if (errc != 0) {
-                cnx.m_stateNext = std::make_unique<DisconnectedState>();
-                cnx.onDisconnect(net::error(errc));
-            } else
-                cnx.m_stateNext = std::make_unique<CheckingState>();
-        } catch (const std::exception &ex) {
-            cnx.m_stateNext = std::make_unique<DisconnectedState>();
-            cnx.onDisconnect(ex.what());
-        }
-    }
-};
-
-/*
- * Client.
- * ------------------------------------------------------------------
- */
-
-unsigned Client::recv(char *buffer, unsigned length)
-{
-    return m_socket.recv(buffer, length);
-}
-
-unsigned Client::send(const char *buffer, unsigned length)
-{
-    return m_socket.send(buffer, length);
-}
-
-void Client::recv()
-{
-    try {
-        std::string buffer;
-
-        buffer.resize(512);
-        buffer.resize(recv(&buffer[0], buffer.size()));
-
-        if (buffer.empty())
-            throw std::runtime_error("Client lost");
-
-        m_input += std::move(buffer);
-    } catch (const std::exception &ex) {
-        m_stateNext = std::make_unique<DisconnectedState>();
-        onDisconnect(ex.what());
-    }
-}
-
-void Client::send()
-{
-    try {
-        auto ns = send(m_output.data(), m_output.length());
-
-        if (ns > 0)
-            m_output.erase(0, ns);
-    } catch (const std::exception &ex) {
-        m_stateNext = std::make_unique<DisconnectedState>();
-        onDisconnect(ex.what());
-    }
-}
-
-Client::Client()
-    : m_state(std::make_unique<DisconnectedState>())
-{
-}
-
-Client::~Client() = default;
-
-Client::Status Client::status() const noexcept
-{
-    return m_state->status();
-}
-
-void Client::connect(const net::Address &address)
-{
-    assert(status() == Disconnected);
-
-    try {
-        m_socket = net::TcpSocket(address.domain(), 0);
-        m_socket.set(net::option::SockBlockMode(false));
-        m_socket.connect(address);
-        m_state = std::make_unique<CheckingState>();
-    } catch (const net::WouldBlockError &) {
-        m_state = std::make_unique<ConnectingState>();
-    } catch (const std::exception &ex) {
-        m_state = std::make_unique<DisconnectedState>();
-        onDisconnect(ex.what());
-    }
-}
-
-void Client::prepare(fd_set &in, fd_set &out, net::Handle &max)
-{
-    try {
-        m_state->prepare(*this, in, out);
-
-        if (m_socket.handle() > max)
-            max = m_socket.handle();
-    } catch (const std::exception &ex) {
-        m_state = std::make_unique<DisconnectedState>();
-        onDisconnect(ex.what());
-    }
-}
-
-void Client::sync(fd_set &in, fd_set &out)
-{
-    try {
-        m_state->sync(*this, in, out);
-
-        if (m_stateNext) {
-            m_state = std::move(m_stateNext);
-            m_stateNext = nullptr;
-        }
-    } catch (const std::exception &ex) {
-        m_state = std::make_unique<DisconnectedState>();
-        onDisconnect(ex.what());
-    }
-}
-
-/*
- * TlsClient.
- * ------------------------------------------------------------------
- */
-
-#if defined(HAVE_SSL)
-
-void TlsClient::handshake()
-{
-    try {
-        m_ssl->handshake();
-        m_handshake = HandshakeReady;
-    } catch (const net::WantReadError &) {
-        m_handshake = HandshakeRead;
-    } catch (const net::WantWriteError &) {
-        m_handshake = HandshakeWrite;
-    } catch (const std::exception &ex) {
-        m_state = std::make_unique<DisconnectedState>();
-        onDisconnect(ex.what());
-    }
-}
-
-unsigned TlsClient::recv(char *buffer, unsigned length)
-{
-    unsigned nread = 0;
-
-    try {
-        nread = m_ssl->recv(buffer, length);
-    } catch (const net::WantReadError &) {
-        m_handshake = HandshakeRead;
-    } catch (const net::WantWriteError &) {
-        m_handshake = HandshakeWrite;
-    }
-
-    return nread;
-}
-
-unsigned TlsClient::send(const char *buffer, unsigned length)
-{
-    unsigned nsent = 0;
-
-    try {
-        nsent = m_ssl->send(buffer, length);
-    } catch (const net::WantReadError &) {
-        m_handshake = HandshakeRead;
-    } catch (const net::WantWriteError &) {
-        m_handshake = HandshakeWrite;
-    }
-
-    return nsent;
-}
-
-void TlsClient::connect(const net::Address &address)
-{
-    Client::connect(address);
-
-    m_ssl = std::make_unique<net::TlsSocket>(m_socket, net::TlsSocket::Client);
-}
-
-void TlsClient::prepare(fd_set &in, fd_set &out, net::Handle &max)
-{
-    if (m_state->status() == Connecting)
-        Client::prepare(in, out, max);
-    else {
-        if (m_socket.handle() > max)
-            max = m_socket.handle();
-
-        /*
-         * Attempt an immediate handshake immediately if Client succeeded
-         * in last iteration.
-         */
-        if (m_handshake == HandshakeUndone)
-            handshake();
-
-        switch (m_handshake) {
-        case HandshakeRead:
-            FD_SET(m_socket.handle(), &in);
-            break;
-        case HandshakeWrite:
-            FD_SET(m_socket.handle(), &out);
-            break;
-        default:
-            Client::prepare(in, out, max);
-        }
-    }
-}
-
-void TlsClient::sync(fd_set &in, fd_set &out)
-{
-    if (m_state->status() == Connecting)
-        Client::sync(in, out);
-    else if (m_handshake != HandshakeReady)
-        handshake();
-    else
-        Client::sync(in, out);
-}
-
-#endif // !HAVE_SSL
-
-} // !irccd
--- a/libirccdctl/irccd/client.hpp	Tue Nov 14 20:25:30 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,325 +0,0 @@
-/*
- * client.hpp -- value wrapper for connecting to irccd
- *
- * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#ifndef IRCCD_CLIENT_HPP
-#define IRCCD_CLIENT_HPP
-
-/**
- * \file client.hpp
- * \brief Connection to irccd instance.
- */
-
-#include <cassert>
-#include <memory>
-#include <string>
-
-#include <json.hpp>
-
-#include "net.hpp"
-#include "signals.hpp"
-#include "sysconfig.hpp"
-
-namespace irccd {
-
-/**
- * \brief Low level connection to irccd instance.
- *
- * This class is an event-based connection to an irccd instance. You can use
- * it directly if you want to issue commands to irccd in an asynchronous way.
- *
- * Being asynchronous makes mixing the event loop with this connection easier.
- *
- * It is implemented as a finite state machine as it may requires several
- * roundtrips between the controller and irccd.
- *
- * Be aware that there are no namespaces for commands, if you plan to use
- * Irccdctl class and you also connect the onMessage signal, irccdctl will also
- * use it. Do not use Irccdctl directly if this is a concern.
- *
- * The state may change and is currently implementing as following:
- *
- *   [o]
- *    |       +----------------------------+
- *    v       v                            |
- * +--------------+   +----------+     +----------------+
- * | Disconnected |-->| Checking |---->| Authenticating |
- * +--------------+   +----------+     +----------------+
- *     ^       |            ^              |
- *     |       |            |              v
- *     |       |      +------------+   +-------+
- *     |       +----->| Connecting |<--| Ready |
- *     |              +------------+   +-------+
- *     |                                   |
- *     ------------------------------------+
- */
-class Client {
-public:
-    /**
-     * \brief The current connection state.
-     */
-    enum Status {
-        Disconnected,           //!< Socket is closed
-        Connecting,             //!< Connection is in progress
-        Checking,               //!< Connection is checking irccd daemon
-        Authenticating,         //!< Connection is authenticating
-        Ready                   //!< Socket is ready for I/O
-    };
-
-    /**
-     * \brief Irccd information.
-     */
-    class Info {
-    public:
-        unsigned short major;   //!< Major version number
-        unsigned short minor;   //!< Minor version number
-        unsigned short patch;   //!< Patch version
-    };
-
-    /**
-     * onConnect
-     * --------------------------------------------------------------
-     *
-     * Connection was successful.
-     */
-    Signal<const Info &> onConnect;
-
-    /**
-     * onEvent
-     * --------------------------------------------------------------
-     *
-     * An event has been received.
-     */
-    Signal<const nlohmann::json &> onEvent;
-
-    /**
-     * onMessage
-     * ---------------------------------------------------------------
-     *
-     * A message from irccd was received.
-     */
-    Signal<const nlohmann::json &> onMessage;
-
-    /**
-     * onDisconnect
-     * --------------------------------------------------------------
-     *
-     * A fatal error occured resulting in disconnection.
-     */
-    Signal<const std::string &> onDisconnect;
-
-private:
-    std::string m_input;
-    std::string m_output;
-    std::string m_password;
-
-public:
-    class State;
-    class AuthState;
-    class DisconnectedState;
-    class ConnectingState;
-    class CheckingState;
-    class ReadyState;
-
-protected:
-    std::unique_ptr<State> m_state;
-    std::unique_ptr<State> m_stateNext;
-    net::TcpSocket m_socket{net::Invalid};
-
-    /**
-     * Try to receive some data into the given buffer.
-     *
-     * \param buffer the destination buffer
-     * \param length the buffer length
-     * \return the number of bytes received
-     */
-    virtual unsigned recv(char *buffer, unsigned length);
-
-    /**
-     * Try to send some data into the given buffer.
-     *
-     * \param buffer the source buffer
-     * \param length the buffer length
-     * \return the number of bytes sent
-     */
-    virtual unsigned send(const char *buffer, unsigned length);
-
-    /**
-     * Convenient wrapper around recv().
-     *
-     * Must be used in sync() function.
-     */
-    void recv();
-
-    /**
-     * Convenient wrapper around send().
-     *
-     * Must be used in sync() function.
-     */
-    void send();
-
-public:
-    /**
-     * Default constructor.
-     */
-    Client();
-
-    /**
-     * Default destructor.
-     */
-    virtual ~Client();
-
-    /**
-     * Get the optional password.
-     *
-     * \return the password
-     */
-    inline const std::string &password() const noexcept
-    {
-        return m_password;
-    }
-
-    /**
-     * Set the optional password
-     *
-     * \param password the password
-     */
-    inline void setPassword(std::string password) noexcept
-    {
-        m_password = std::move(password);
-    }
-
-    /**
-     * Send an asynchronous request to irccd.
-     *
-     * \pre json.is_object
-     * \param json the JSON object
-     */
-    inline void request(const nlohmann::json &json)
-    {
-        assert(json.is_object());
-
-        m_output += json.dump();
-        m_output += "\r\n\r\n";
-    }
-
-    /**
-     * Get the underlying socket handle.
-     *
-     * \return the handle
-     */
-    inline net::Handle handle() const noexcept
-    {
-        return m_socket.handle();
-    }
-
-    /**
-     * Shorthand for state() != Disconnected.
-     *
-     * \return true if state() != Disconnected
-     */
-    inline bool isConnected() const noexcept
-    {
-        return status() != Disconnected;
-    }
-
-    /**
-     * Get the current state.
-     *
-     * \return the state
-     */
-    Status status() const noexcept;
-
-    /**
-     * Initiate connection to irccd.
-     *
-     * \pre state() == Disconnected
-     * \param address the address
-     */
-    virtual void connect(const net::Address &address);
-
-    /**
-     * Prepare the input and output set according to the current connection
-     * state.
-     *
-     * \param in the input set
-     * \param out the output set
-     * \param max the maximum file descriptor
-     */
-    virtual void prepare(fd_set &in, fd_set &out, net::Handle &max);
-
-    /**
-     * Do some I/O using the protected recv and send functions.
-     *
-     * \param in the input set
-     * \param out the output set
-     */
-    virtual void sync(fd_set &in, fd_set &out);
-};
-
-#if defined(HAVE_SSL)
-
-/**
- * \brief TLS over IP connection.
- */
-class TlsClient : public Client {
-private:
-    enum {
-        HandshakeUndone,
-        HandshakeRead,
-        HandshakeWrite,
-        HandshakeReady
-    } m_handshake{HandshakeUndone};
-
-private:
-    std::unique_ptr<net::TlsSocket> m_ssl;
-
-    void handshake();
-
-protected:
-    /**
-     * \copydoc Client::recv
-     */
-    unsigned recv(char *buffer, unsigned length) override;
-
-    /**
-     * \copydoc Client::send
-     */
-    unsigned send(const char *buffer, unsigned length) override;
-
-public:
-    /**
-     * \copydoc Client::connect
-     */
-    void connect(const net::Address &address) override;
-
-    /**
-     * \copydoc Service::prepare
-     */
-    void prepare(fd_set &in, fd_set &out, net::Handle &max) override;
-
-    /**
-     * \copydoc Service::sync
-     */
-    void sync(fd_set &in, fd_set &out) override;
-};
-
-#endif // !HAVE_SSL
-
-} // !irccd
-
-#endif // !IRCCD_CLIENT_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccdctl/irccd/ctl/connection.hpp	Thu Nov 16 22:45:12 2017 +0100
@@ -0,0 +1,90 @@
+/*
+ * connection.hpp -- abstract connection for irccdctl
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef IRCCD_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)>;
+
+    /**
+     * Receive handler.
+     *
+     * Call the handler when you have read a JSON message from the underlying
+     * protocol.
+     */
+    using recv_t = std::function<void (boost::system::error_code, nlohmann::json)>;
+
+    /**
+     * Send handler.
+     *
+     * Call the handler when you have sent a JSON message to the underlying
+     * protocol.
+     */
+    using send_t = std::function<void (boost::system::error_code)>;
+
+    /**
+     * Connect to the daemon.
+     *
+     * \param handler the non-null handler
+     */
+    virtual void connect(connect_t handler) = 0;
+
+    /**
+     * Receive a message.
+     *
+     * \param handler the non-null handler
+     */
+    virtual void recv(recv_t) = 0;
+
+    /**
+     * Send a message.
+     *
+     * \param message the JSON message object
+     * \param handler the non-null handler
+     */
+    virtual void send(const nlohmann::json& json, send_t) = 0;
+};
+
+} // !ctl
+
+} // !irccd
+
+#endif // !IRCCD_CTL_CONNECTION_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccdctl/irccd/ctl/controller.cpp	Thu Nov 16 22:45:12 2017 +0100
@@ -0,0 +1,143 @@
+/*
+ * controller.cpp -- main irccdctl interface
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <cassert>
+
+#include <irccd/errors.hpp>
+#include <irccd/sysconfig.hpp>
+#include <irccd/json_util.hpp>
+
+#include "controller.hpp"
+#include "connection.hpp"
+
+namespace irccd {
+
+namespace ctl {
+
+void controller::flush_recv()
+{
+    if (rqueue_.empty())
+        return;
+
+    conn_.recv([this] (auto code, auto json) {
+        rqueue_.front()(code, std::move(json));
+        rqueue_.pop_front();
+
+        if (!code)
+            flush_recv();
+    });
+}
+
+void controller::flush_send()
+{
+    if (squeue_.empty())
+        return;
+
+    conn_.send(squeue_.front().first, [this] (auto code) {
+        if (squeue_.front().second)
+            squeue_.front().second(code, squeue_.front().first);
+
+        squeue_.pop_front();
+
+        if (!code)
+            flush_send();
+    });
+}
+
+void controller::authenticate(connect_t handler, nlohmann::json info)
+{
+    auto cmd = nlohmann::json::object({
+        { "command", "authenticate" },
+        { "password", password_ }
+    });
+
+    send(std::move(cmd), [handler, info, this] (auto code, auto) {
+        if (code) {
+            handler(std::move(code), nullptr);
+            return;
+        }
+
+        recv([handler, info, this] (auto code, auto message) {
+            if (message["error"].is_string())
+                code = network_error::invalid_auth;
+
+            handler(std::move(code), std::move(info));
+        });
+    });
+}
+
+void controller::verify(connect_t handler)
+{
+    recv([handler, this] (auto code, auto message) {
+        if (code) {
+            handler(std::move(code), std::move(message));
+            return;
+        }
+
+        if (json_util::to_string(message["program"]) != "irccd")
+            handler(network_error::invalid_program, std::move(message));
+        else if (json_util::to_int(message["major"]) != IRCCD_VERSION_MAJOR)
+            handler(network_error::invalid_version, std::move(message));
+        else {
+            if (!password_.empty())
+                authenticate(std::move(handler), message);
+            else
+                handler(code, std::move(message));
+        }
+    });
+}
+
+void controller::connect(connect_t handler)
+{
+    assert(handler);
+
+    conn_.connect([handler, this] (auto code) {
+        if (code)
+            handler(std::move(code), nullptr);
+        else
+            verify(std::move(handler));
+    });
+}
+
+void controller::recv(recv_t handler)
+{
+    assert(handler);
+
+    auto in_progress = !rqueue_.empty();
+
+    rqueue_.push_back(std::move(handler));
+
+    if (!in_progress)
+        flush_recv();
+}
+
+void controller::send(nlohmann::json message, send_t handler)
+{
+    assert(message.is_object());
+
+    auto in_progress = !squeue_.empty();
+
+    squeue_.emplace_back(std::move(message), std::move(handler));
+
+    if (!in_progress)
+        flush_send();
+}
+
+} // !ctl
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccdctl/irccd/ctl/controller.hpp	Thu Nov 16 22:45:12 2017 +0100
@@ -0,0 +1,197 @@
+/*
+ * controller.hpp -- main irccdctl interface
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef IRCCD_CTL_CONTROLLER_HPP
+#define IRCCD_CTL_CONTROLLER_HPP
+
+/**
+ * \file controller.hpp
+ * \brief Main irccdctl interface.
+ */
+
+#include <boost/system/error_code.hpp>
+
+#include <deque>
+#include <functional>
+#include <string>
+
+#include <json.hpp>
+
+namespace irccd {
+
+namespace ctl {
+
+class connection;
+
+/**
+ * \brief Main irccdctl interface.
+ *
+ * This class is an easy frontend to issue commands to irccd, it uses an
+ * independant connection to perform the requests.
+ *
+ * This class is responsible of doing initial connection, performing checks and
+ * optional authentication.
+ *
+ * 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.
+     *
+     * 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)>;
+
+    /**
+     * Receive handler.
+     *
+     * This callback is called when a message has been received. If an error
+     * occured the error_code is set and the JSON object is null, otherwise it
+     * contains the received message.
+     */
+    using recv_t = std::function<void (boost::system::error_code, nlohmann::json)>;
+
+    /**
+     * Send handler.
+     *
+     * This callback is optional and is called when a message has been sent, it
+     * is also called if an error occured.
+     */
+    using send_t = std::function<void (boost::system::error_code, nlohmann::json)>;
+
+private:
+    using recv_queue_t = std::deque<recv_t>;
+    using send_queue_t = std::deque<std::pair<nlohmann::json, send_t>>;
+
+    connection& conn_;
+    recv_queue_t rqueue_;
+    send_queue_t squeue_;
+    std::string password_;
+
+    void flush_recv();
+    void flush_send();
+    void authenticate(connect_t, nlohmann::json);
+    void verify(connect_t);
+
+public:
+    /**
+     * Construct the controller with its connection.
+     *
+     * \note no connect attempt is done
+     */
+    inline controller(connection& conn) noexcept
+        : conn_(conn)
+    {
+    }
+
+    /**
+     * Tells if receive requests are pending.
+     *
+     * \return true if receive queue is not empty
+     */
+    inline bool has_recv_pending() const noexcept
+    {
+        return !rqueue_.empty();
+    }
+
+    /**
+     * Tells if send requests are pending.
+     *
+     * \return true if send queue is not empty
+     */
+    inline bool has_send_pending() const noexcept
+    {
+        return !squeue_.empty();
+    }
+
+    /**
+     * Tells if receive or send requests are pending.
+     *
+     * \return true if one of receive/send queue is not empty
+     */
+    inline bool has_pending() const noexcept
+    {
+        return has_recv_pending() || has_send_pending();
+    }
+
+    /**
+     * Get the optional password set.
+     *
+     * \return the password
+     */
+    inline const std::string& password() const noexcept
+    {
+        return password_;
+    }
+
+    /**
+     * Set an optional password.
+     *
+     * An empty password means no authentication (default).
+     *
+     * \param password the password
+     * \note this must be called before connect
+     */
+    inline void set_password(std::string password) noexcept
+    {
+        password_ = std::move(password);
+    }
+
+    /**
+     * Attempt to connect to the irccd daemon.
+     *
+     * \pre handler != nullptr
+     * \param handler the handler
+     */
+    void connect(connect_t handler);
+
+    /**
+     * Queue a receive operation, if receive operations are already running, it
+     * is queued and ran once ready.
+     *
+     * \pre handler != nullptr
+     * \param handler the recv handler
+     */
+    void recv(recv_t handler);
+
+    /**
+     * Queue a send operation, if receive operations are already running, it is
+     * queued and ran once ready.
+     *
+     * \pre message.is_object()
+     * \param message the JSON message
+     * \param handler the optional completion handler
+     */
+    void send(nlohmann::json message, send_t handler);
+};
+
+} // !ctl
+
+} // !irccd
+
+#endif // !IRCCD_CTL_CONTROLLER_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccdctl/irccd/ctl/ip_connection.cpp	Thu Nov 16 22:45:12 2017 +0100
@@ -0,0 +1,88 @@
+/*
+ * ip_connection.cpp -- TCP/IP and SSL connections
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "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_), socket_, resolver_, std::move(handler));
+}
+
+#if defined(HAVE_SSL)
+
+void tls_connection::handshake(connect_t handler)
+{
+    socket_.async_handshake(boost::asio::ssl::stream_base::client, [handler] (auto code) {
+        handler(code);
+    });
+}
+
+void tls_connection::connect(connect_t handler)
+{
+    do_resolve(host_, std::to_string(port_), socket_.lowest_layer(), resolver_, [handler, this] (auto code) {
+        if (code)
+            handler(code);
+        else
+            handshake(std::move(handler));
+    });
+}
+
+#endif // !HAVE_SSL
+
+} // !ctl
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccdctl/irccd/ctl/ip_connection.hpp	Thu Nov 16 22:45:12 2017 +0100
@@ -0,0 +1,129 @@
+/*
+ * ip_connection.hpp -- TCP/IP and SSL connections
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef IRCCD_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>
+
+#if defined(HAVE_SSL)
+#   include <boost/asio/ssl.hpp>
+#endif
+
+#include "network_connection.hpp"
+
+namespace irccd {
+
+namespace ctl {
+
+/**
+ * \brief Common class for both ip and tls connections.
+ */
+template <typename Socket>
+class basic_ip_connection : public network_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 extra args (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)
+        : network_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
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccdctl/irccd/ctl/local_connection.cpp	Thu Nov 16 22:45:12 2017 +0100
@@ -0,0 +1,38 @@
+/*
+ * local_connection.cpp -- unix domain connection for irccdctl
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "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;
+
+    socket_.async_connect(endpoint(path_), std::move(handler));
+}
+
+} // !ctl
+
+} // !irccd
+
+#endif // !IRCCD_SYSTEM_WINDOWS
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccdctl/irccd/ctl/local_connection.hpp	Thu Nov 16 22:45:12 2017 +0100
@@ -0,0 +1,71 @@
+/*
+ * local_connection.hpp -- unix domain connection for irccdctl
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef IRCCD_CTL_LOCAL_CONNECTION_HPP
+#define IRCCD_CTL_LOCAL_CONNECTION_HPP
+
+/**
+ * \file local_connection.hpp
+ * \brief Unix domain connection for irccdctl.
+ */
+
+#include <irccd/sysconfig.hpp>
+
+#include "network_connection.hpp"
+
+#if !defined(IRCCD_SYSTEM_WINDOWS)
+
+namespace irccd {
+
+namespace ctl {
+
+/**
+ * \brief Unix domain connection for irccdctl.
+ */
+class local_connection : public network_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
+        : network_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/libirccdctl/irccd/ctl/network_connection.hpp	Thu Nov 16 22:45:12 2017 +0100
@@ -0,0 +1,119 @@
+/*
+ * network_connection.hpp -- network based connection for controller
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef IRCCD_CTL_NETWORK_CONNECTION_HPP
+#define IRCCD_CTL_NETWORK_CONNECTION_HPP
+
+/**
+ * \file network_connection.hpp
+ * \brief Network based connection for controller.
+ */
+
+#include <boost/asio.hpp>
+
+#include <irccd/errors.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 network_connection : public connection {
+private:
+    boost::asio::streambuf input_;
+
+protected:
+    /**
+     * The underlying socket.
+     */
+    Socket socket_;
+
+public:
+    /**
+     * Construct the network connection.
+     *
+     * \param args the arguments to pass to the socket
+     */
+    template <typename... Args>
+    inline network_connection(Args&&... args)
+        : socket_(std::forward<Args>(args)...)
+    {
+    }
+
+    /**
+     * Implements connection::recv using boost::asio::async_read_until.
+     *
+     * \param handler the handler
+     */
+    void recv(recv_t handler) override;
+
+    /**
+     * Implements connection::send using boost::asio::async_write.
+     *
+     * \param handler the handler
+     */
+    void send(const nlohmann::json& json, send_t) override;
+};
+
+template <typename Socket>
+void network_connection<Socket>::recv(recv_t handler)
+{
+    boost::asio::async_read_until(socket_, input_, "\r\n\r\n", [handler, this] (auto code, auto xfer) {
+        if (code || xfer == 0) {
+            handler(code, nullptr);
+            return;
+        }
+
+        std::string command{
+            boost::asio::buffers_begin(input_.data()),
+            boost::asio::buffers_begin(input_.data()) + xfer - 4
+        };
+
+        input_.consume(xfer);
+
+        try {
+            handler(code, nlohmann::json::parse(command));
+        } catch (...) {
+            handler(network_error::invalid_message, nullptr);
+        }
+    });
+}
+
+template <typename Socket>
+void network_connection<Socket>::send(const nlohmann::json& message, send_t handler)
+{
+    auto data = std::make_shared<std::string>(message.dump(0) + "\r\n\r\n");
+
+    boost::asio::async_write(socket_, boost::asio::buffer(*data), [handler, data, this] (auto code, auto) {
+        handler(code);
+    });
+}
+
+} // !ctl
+
+} // !irccd
+
+#endif // !IRCCD_CTL_NETWORK_CONNECTION_HPP
--- a/libirccdctl/irccd/irccdctl.hpp	Tue Nov 14 20:25:30 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,97 +0,0 @@
-/*
- * irccdctl.hpp -- main irccdctl class
- *
- * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#ifndef IRCCD_IRCCDCTL_HPP
-#define IRCCD_IRCCDCTL_HPP
-
-/**
- * \file irccdctl.hpp
- * \brief Main irccdctl class
- */
-
-#include <memory>
-
-#include "client.hpp"
-
-namespace irccd {
-
-/**
- * \brief Transient class for connecting to irccd
- */
-class Irccdctl {
-private:
-    std::unique_ptr<Client> m_client;
-
-public:
-    /**
-     * Create the irccdctl instance with the specified client.
-     *
-     * \param client the client
-     */
-    inline Irccdctl(std::unique_ptr<Client> client) noexcept
-        : m_client(std::move(client))
-    {
-    }
-
-    /**
-     * Get the client.
-     *
-     * \return the client reference
-     */
-    inline Client &client() noexcept
-    {
-        return *m_client;
-    }
-
-    /**
-     * Overloaded function.
-     *
-     * \return the client
-     */
-    inline const Client &client() const noexcept
-    {
-        return *m_client;
-    }
-
-    /**
-     * Pollable prepare function.
-     *
-     * \param in the input set
-     * \param out the output set
-     * \param max the maximum handle
-     */
-    inline void prepare(fd_set &in, fd_set &out, net::Handle &max)
-    {
-        m_client->prepare(in, out, max);
-    }
-
-    /**
-     * Pollable sync function.
-     *
-     * \param in the input set
-     * \param out the output set
-     */
-    inline void sync(fd_set &in, fd_set &out)
-    {
-        m_client->sync(in, out);
-    }
-};
-
-} // !irccd
-
-#endif // !IRCCD_IRCCDCTL_HPP
--- a/tests/CMakeLists.txt	Tue Nov 14 20:25:30 2017 +0100
+++ b/tests/CMakeLists.txt	Thu Nov 16 22:45:12 2017 +0100
@@ -27,35 +27,35 @@
     file(WRITE ${tests_BINARY_DIR}/root/file-1.txt "\n")
     file(WRITE ${tests_BINARY_DIR}/root/level-a/level-1/file-2.txt)
 
-    add_subdirectory(cmd-plugin-config)
+#    add_subdirectory(cmd-plugin-config)
 #    add_subdirectory(cmd-plugin-info)
 #    add_subdirectory(cmd-plugin-list)
 #    add_subdirectory(cmd-plugin-load)
 #    add_subdirectory(cmd-plugin-reload)
 #    add_subdirectory(cmd-plugin-unload)
-    add_subdirectory(cmd-rule-add)
-    add_subdirectory(cmd-rule-edit)
-    add_subdirectory(cmd-rule-info)
-    add_subdirectory(cmd-rule-list)
-    add_subdirectory(cmd-rule-move)
-    add_subdirectory(cmd-rule-remove)
-    add_subdirectory(cmd-server-cmode)
-    add_subdirectory(cmd-server-cnotice)
-    add_subdirectory(cmd-server-connect)
-    add_subdirectory(cmd-server-disconnect)
-    add_subdirectory(cmd-server-info)
-    add_subdirectory(cmd-server-invite)
-    add_subdirectory(cmd-server-join)
-    add_subdirectory(cmd-server-kick)
-    add_subdirectory(cmd-server-list)
-    add_subdirectory(cmd-server-me)
-    add_subdirectory(cmd-server-message)
-    add_subdirectory(cmd-server-mode)
-    add_subdirectory(cmd-server-nick)
-    add_subdirectory(cmd-server-notice)
-    add_subdirectory(cmd-server-part)
-    add_subdirectory(cmd-server-reconnect)
-    add_subdirectory(cmd-server-topic)
+#    add_subdirectory(cmd-rule-add)
+#    add_subdirectory(cmd-rule-edit)
+#    add_subdirectory(cmd-rule-info)
+#    add_subdirectory(cmd-rule-list)
+#    add_subdirectory(cmd-rule-move)
+#    add_subdirectory(cmd-rule-remove)
+#    add_subdirectory(cmd-server-cmode)
+#    add_subdirectory(cmd-server-cnotice)
+#    add_subdirectory(cmd-server-connect)
+#    add_subdirectory(cmd-server-disconnect)
+#    add_subdirectory(cmd-server-info)
+#    add_subdirectory(cmd-server-invite)
+#    add_subdirectory(cmd-server-join)
+#    add_subdirectory(cmd-server-kick)
+#    add_subdirectory(cmd-server-list)
+#    add_subdirectory(cmd-server-me)
+#    add_subdirectory(cmd-server-message)
+#    add_subdirectory(cmd-server-mode)
+#    add_subdirectory(cmd-server-nick)
+#    add_subdirectory(cmd-server-notice)
+#    add_subdirectory(cmd-server-part)
+#    add_subdirectory(cmd-server-reconnect)
+#    add_subdirectory(cmd-server-topic)
 #    add_subdirectory(dynlib_plugin)
 
     # Misc