changeset 545:1ef194b6b168

Common: make network_stream template
author David Demelier <markand@malikania.fr>
date Wed, 22 Nov 2017 11:23:10 +0100
parents 8d9662b2beee
children fd96de07657a
files irccdctl/cli.cpp irccdctl/main.cpp libcommon/CMakeLists.txt libcommon/irccd/network_stream.hpp libirccd/CMakeLists.txt libirccd/irccd/basic_transport_client.hpp libirccd/irccd/transport_client.cpp libirccd/irccd/transport_client.hpp libirccd/irccd/transport_server.cpp libirccd/irccd/transport_server.hpp libirccdctl/CMakeLists.txt libirccdctl/irccd/ctl/basic_connection.hpp libirccdctl/irccd/ctl/connection.hpp libirccdctl/irccd/ctl/controller.cpp libirccdctl/irccd/ctl/controller.hpp libirccdctl/irccd/ctl/ip_connection.cpp libirccdctl/irccd/ctl/ip_connection.hpp libirccdctl/irccd/ctl/local_connection.cpp libirccdctl/irccd/ctl/local_connection.hpp libirccdctl/irccd/ctl/network_connection.hpp
diffstat 20 files changed, 600 insertions(+), 578 deletions(-) [+]
line wrap: on
line diff
--- a/irccdctl/cli.cpp	Tue Nov 21 15:12:05 2017 +0100
+++ b/irccdctl/cli.cpp	Wed Nov 22 11:23:10 2017 +0100
@@ -56,7 +56,7 @@
 
 void cli::request(ctl::controller& ctl, nlohmann::json req, handler_t handler)
 {
-    ctl.send(req, [&ctl, req, handler, this] (auto code, auto) {
+    ctl.send(req, [&ctl, req, handler, this] (auto code) {
         if (code)
             throw boost::system::system_error(code);
 
--- a/irccdctl/main.cpp	Tue Nov 21 15:12:05 2017 +0100
+++ b/irccdctl/main.cpp	Wed Nov 22 11:23:10 2017 +0100
@@ -537,7 +537,7 @@
 
     exec(args);
 
-    while (ctl->has_pending())
+    while (ctl->conn().is_active())
         service.run();
 }
 
--- a/libcommon/CMakeLists.txt	Tue Nov 21 15:12:05 2017 +0100
+++ b/libcommon/CMakeLists.txt	Wed Nov 22 11:23:10 2017 +0100
@@ -28,6 +28,7 @@
     ${libcommon_SOURCE_DIR}/irccd/logger.hpp
     ${libcommon_SOURCE_DIR}/irccd/net.hpp
     ${libcommon_SOURCE_DIR}/irccd/network_errc.hpp
+    ${libcommon_SOURCE_DIR}/irccd/network_stream.hpp
     ${libcommon_SOURCE_DIR}/irccd/net_util.hpp
     ${libcommon_SOURCE_DIR}/irccd/options.hpp
     ${libcommon_SOURCE_DIR}/irccd/signals.hpp
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libcommon/irccd/network_stream.hpp	Wed Nov 22 11:23:10 2017 +0100
@@ -0,0 +1,298 @@
+/*
+ * network_stream.hpp -- base shared network stream
+ *
+ * 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_NETWORK_STREAM_HPP
+#define IRCCD_COMMON_NETWORK_STREAM_HPP
+
+/**
+ * \file network_stream.cpp
+ * \brief Base shared network stream.
+ */
+
+#include "sysconfig.hpp"
+
+#include <deque>
+#include <functional>
+#include <string>
+#include <utility>
+
+#include <boost/asio.hpp>
+
+#if defined(HAVE_SSL)
+#   include <boost/asio/ssl.hpp>
+#endif
+
+#include <json.hpp>
+
+#include "network_errc.hpp"
+
+namespace irccd {
+
+/**
+ * Read handler.
+ *
+ * Call this function when a receive operation has finished on success or
+ * failure.
+ */
+using network_recv_handler = std::function<void (boost::system::error_code, nlohmann::json)>;
+
+/**
+ * Send handler.
+ *
+ * Call this function when a send operation has finished on success or failure.
+ */
+using network_send_handler = std::function<void (boost::system::error_code)>;
+
+/**
+ * \brief Base shared network stream.
+ *
+ * This class can be used to perform I/O over a networking socket, it is
+ * implemented as asynchronous operations over Boost.Asio.
+ *
+ * All recv/send operations are placed in a queue and performed when possible.
+ */
+template <typename Socket>
+class network_stream {
+private:
+    using rbuffer_t = boost::asio::streambuf;
+    using rqueue_t = std::deque<network_recv_handler>;
+    using squeue_t = std::deque<std::pair<nlohmann::json, network_send_handler>>;
+
+    Socket socket_;
+    rbuffer_t rbuffer_;
+    rqueue_t rqueue_;
+    squeue_t squeue_;
+
+    void rflush();
+    void sflush();
+    void do_recv(network_recv_handler handler);
+    void do_send(const nlohmann::json& json, network_send_handler);
+
+public:
+    /**
+     * Construct the stream.
+     *
+     * \param args the arguments to pass to the Socket constructor
+     */
+    template <typename... Args>
+    inline network_stream(Args&&... args)
+        : socket_(std::forward<Args>(args)...)
+    {
+    }
+
+    /**
+     * Get the underlying socket.
+     *
+     * \return the socket
+     */
+    inline const Socket& socket() const noexcept
+    {
+        return socket_;
+    }
+
+    /**
+     * Overloaded function.
+     *
+     * \return the socket
+     */
+    inline Socket& socket() noexcept
+    {
+        return socket_;
+    }
+
+    /**
+     * Tells if receive operations are pending.
+     *
+     * \return true if receiving is in progress
+     */
+    inline bool is_receiving() const noexcept
+    {
+        return !rqueue_.empty();
+    }
+
+    /**
+     * Tells if send operations are pending.
+     *
+     * \return true if sending is in progress
+     */
+    inline bool is_sending() const noexcept
+    {
+        return !squeue_.empty();
+    }
+
+    /**
+     * Tells if there are any I/O pending.
+     *
+     * \return true if sending is in progress
+     */
+    inline bool is_active() const noexcept
+    {
+        return is_receiving() || is_sending();
+    }
+
+    /**
+     * Request a receive operation.
+     *
+     * \pre handler != nullptr
+     * \param handler the handler
+     */
+    void recv(network_recv_handler);
+
+    /**
+     * Request a send operation.
+     *
+     * \pre json.is_object()
+     * \param json the json message
+     * \param handler the optional handler
+     */
+    void send(nlohmann::json json, network_send_handler = nullptr);
+};
+
+template <typename Socket>
+void network_stream<Socket>::do_recv(network_recv_handler handler)
+{
+    boost::asio::async_read_until(socket_, rbuffer_, "\r\n\r\n", [this, handler] (auto code, auto xfer) {
+        if (code)
+            handler(std::move(code), nullptr);
+        else if (xfer == 0U)
+            handler(network_errc::corrupt_message, nullptr);
+        else {
+            std::string str(
+                boost::asio::buffers_begin(rbuffer_.data()),
+                boost::asio::buffers_begin(rbuffer_.data()) + xfer - 4
+            );
+
+            // Remove early in case of errors.
+            rbuffer_.consume(xfer);
+
+            // TODO: catch nlohmann::json::parse_error when 3.0.0 is released.
+            nlohmann::json message;
+
+            try {
+                message = nlohmann::json::parse(str);
+            } catch (...) {
+                handler(network_errc::invalid_message, nullptr);
+            }
+
+            if (!message.is_object())
+                handler(network_errc::invalid_message, nullptr);
+            else
+                handler(network_errc::no_error, std::move(message));
+        }
+    });
+}
+
+template <typename Socket>
+void network_stream<Socket>::do_send(const nlohmann::json& json, network_send_handler handler)
+{
+    auto str = std::make_shared<std::string>(json.dump(0) + "\r\n\r\n");
+
+    boost::asio::async_write(socket_, boost::asio::buffer(*str), [str, handler] (auto code, auto xfer) {
+        if (code)
+            handler(std::move(code));
+        else if (xfer == 0U)
+            handler(network_errc::corrupt_message);
+        else
+            handler(network_errc::no_error);
+    });
+}
+
+template <typename Socket>
+void network_stream<Socket>::rflush()
+{
+    if (rqueue_.empty())
+        return;
+
+    do_recv([this] (auto code, auto json) {
+        rqueue_.front()(code, std::move(json));
+        rqueue_.pop_front();
+
+        if (!code)
+            rflush();
+    });
+}
+
+template <typename Socket>
+void network_stream<Socket>::sflush()
+{
+    if (squeue_.empty())
+        return;
+
+    do_send(squeue_.front().first, [this] (auto code) {
+        if (squeue_.front().second)
+            squeue_.front().second(code);
+
+        squeue_.pop_front();
+
+        if (!code)
+            sflush();
+    });
+}
+
+template <typename Socket>
+void network_stream<Socket>::recv(network_recv_handler handler)
+{
+    assert(handler);
+
+    auto in_progress = !rqueue_.empty();
+
+    rqueue_.push_back(std::move(handler));
+
+    if (!in_progress)
+        rflush();
+}
+
+template <typename Socket>
+void network_stream<Socket>::send(nlohmann::json json, network_send_handler handler)
+{
+    assert(json.is_object());
+
+    auto in_progress = !squeue_.empty();
+
+    squeue_.emplace_back(std::move(json), std::move(handler));
+
+    if (!in_progress)
+        sflush();
+}
+
+/**
+ * \brief Typedef for TCP/IP socket.
+ */
+using ip_network_stream = network_stream<boost::asio::ip::tcp::socket>;
+
+#if !defined(IRCCD_SYSTEM_WINDOWS)
+
+/**
+ * \brief Typedef for Unix socket.
+ */
+using local_network_stream = network_stream<boost::asio::local::stream_protocol::socket>;
+
+#endif // !IRCCD_SYSTEM_WINDOWS
+
+#if defined(HAVE_SSL)
+
+/**
+ * \brief Typedef for SSL sockets.
+ */
+using tls_network_stream = network_stream<boost::asio::ssl::stream<boost::asio::ip::tcp::socket>>;
+
+#endif // !HAVE_SSL
+
+} // !irccd
+
+#endif // IRCCD_COMMON_NETWORK_STREAM_HPP
--- a/libirccd/CMakeLists.txt	Tue Nov 21 15:12:05 2017 +0100
+++ b/libirccd/CMakeLists.txt	Wed Nov 22 11:23:10 2017 +0100
@@ -22,6 +22,7 @@
 
 set(
     HEADERS
+    ${libirccd_SOURCE_DIR}/irccd/basic_transport_client.hpp
     ${libirccd_SOURCE_DIR}/irccd/command.hpp
     ${libirccd_SOURCE_DIR}/irccd/config.hpp
     ${libirccd_SOURCE_DIR}/irccd/dynlib_plugin.hpp
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/basic_transport_client.hpp	Wed Nov 22 11:23:10 2017 +0100
@@ -0,0 +1,91 @@
+/*
+ * basic_transport_client.hpp -- simple socket transport client
+ *
+ * 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_BASIC_TRANSPORT_CLIENT_HPP
+#define IRCCD_BASIC_TRANSPORT_CLIENT_HPP
+
+#include <irccd/network_stream.hpp>
+
+#include "transport_client.hpp"
+
+namespace irccd {
+
+/**
+ * \brief Transport client for sockets.
+ *
+ * This class implements do_recv/do_send for Boost.Asio based socket streams.
+ */
+template <typename Socket>
+class basic_transport_client : public transport_client {
+private:
+    network_stream<Socket> stream_;
+
+public:
+    /**
+     * Construct the client.
+     *
+     * \param parent the parent
+     * \param args the argument to pass to the network_stream
+     */
+    template <typename... Args>
+    inline basic_transport_client(transport_server& parent, Args&&... args)
+        : transport_client(parent)
+        , stream_(std::forward<Args>(args)...)
+    {
+    }
+
+    /**
+     * Get the underlying stream.
+     *
+     * \return the stream
+     */
+    inline const network_stream<Socket>& stream() const noexcept
+    {
+        return stream_;
+    }
+
+    /**
+     * Overloaded function.
+     *
+     * \return the stream
+     */
+    inline network_stream<Socket>& stream() noexcept
+    {
+        return stream_;
+    }
+
+    /**
+     * \copydoc transport_client::do_recv
+     */
+    void do_recv(network_recv_handler handler) override
+    {
+        stream_.recv(std::move(handler));
+    }
+
+    /**
+     * \copydoc transport_client::do_send
+     */
+    void do_send(nlohmann::json json, network_send_handler handler) override
+    {
+        stream_.send(std::move(json), std::move(handler));
+    }
+};
+
+} // !irccd
+
+#endif // !IRCCD_BASIC_TRANSPORT_CLIENT_HPP
--- a/libirccd/irccd/transport_client.cpp	Tue Nov 21 15:12:05 2017 +0100
+++ b/libirccd/irccd/transport_client.cpp	Wed Nov 22 11:23:10 2017 +0100
@@ -26,86 +26,67 @@
 void transport_client::close()
 {
     state_ = state_t::closing;
-    output_.clear();
     parent_.clients().erase(shared_from_this());
 }
 
-void transport_client::flush()
+void transport_client::recv(network_recv_handler handler)
 {
-    if (output_.empty())
-        return;
-
-    auto self = shared_from_this();
-    auto size = output_[0].first.size();
-
-    do_send(output_[0].first, [this, self, size] (auto code, auto xfer) {
-        if (output_[0].second)
-            output_[0].second(code);
-
-        output_.pop_front();
-
-        if (code || xfer != size || (output_.empty() && state_ == state_t::closing))
-            close();
-        else if (!output_.empty())
-            flush();
-    });
+    if (state_ != state_t::closing)
+        do_recv(std::move(handler));
 }
 
-void transport_client::recv(recv_t handler)
+void transport_client::send(nlohmann::json json, network_send_handler handler)
 {
-    assert(handler);
-
-    auto self = shared_from_this();
-
-    do_recv(input_, [this, self, handler] (auto code, auto xfer) {
-        if (code || xfer == 0) {
-            handler(code, nullptr);
-            close();
-            return;
-        }
-
-        std::string message(
-            boost::asio::buffers_begin(input_.data()),
-            boost::asio::buffers_begin(input_.data()) + xfer - 4
-        );
-
-        // Remove early in case of errors.
-        input_.consume(xfer);
-
-        nlohmann::json command;
-
-        try {
-            command = nlohmann::json::parse(message);
-        } catch (...) {
-            handler(network_errc::invalid_message, nullptr);
-        }
-
-        if (!command.is_object())
-            handler(network_errc::invalid_message, nullptr);
-        else
-            handler(network_errc::no_error, std::move(command));
-    });
+    if (state_ != state_t::closing)
+        do_send(std::move(json), std::move(handler));
 }
 
-void transport_client::send(const nlohmann::json& data, send_t handler)
+void transport_client::success(const std::string& cname, network_send_handler handler)
 {
-    assert(data.is_object());
-
-    if (state_ == state_t::closing)
-        return;
+    assert(!cname.empty());
 
-    auto in_progress = !output_.empty();
-
-    output_.emplace_back(data.dump() + "\r\n\r\n", std::move(handler));
-
-    if (!in_progress)
-        flush();
+    send({{ "command", cname }}, std::move(handler));
 }
 
-void transport_client::error(const nlohmann::json& data, send_t handler)
+void transport_client::error(const nlohmann::json& data, network_send_handler handler)
 {
     send(std::move(data), std::move(handler));
     set_state(state_t::closing);
 }
 
+void transport_client::error(const std::string& cname, const std::string& reason, network_send_handler handler)
+{
+    assert(!cname.empty());
+    assert(!reason.empty());
+
+    error({
+        { "command",    cname   },
+        { "error",      reason  }
+    }, std::move(handler));
+}
+
+void transport_client::error(const std::string& reason, network_send_handler handler)
+{
+    assert(!reason.empty());
+
+    error({{ "error", reason }}, std::move(handler));
+}
+
+void transport_client::error(const std::string& cname, network_errc reason, network_send_handler handler)
+{
+    assert(!cname.empty());
+
+    error({
+        { "command",    cname                       },
+        { "error",      static_cast<int>(reason)    }
+    }, std::move(handler));
+}
+
+void transport_client::error(network_errc reason, network_send_handler handler)
+{
+    assert(reason != network_errc::no_error);
+
+    error({{ "error", static_cast<int>(reason) }}, std::move(handler));
+}
+
 } // !irccd
--- a/libirccd/irccd/transport_client.hpp	Tue Nov 21 15:12:05 2017 +0100
+++ b/libirccd/irccd/transport_client.hpp	Wed Nov 22 11:23:10 2017 +0100
@@ -19,23 +19,7 @@
 #ifndef IRCCD_TRANSPORT_CLIENT_HPP
 #define IRCCD_TRANSPORT_CLIENT_HPP
 
-#include "sysconfig.hpp"
-
-#include <deque>
-#include <memory>
-#include <functional>
-#include <string>
-#include <utility>
-
-#include <boost/asio.hpp>
-
-#if defined(HAVE_SSL)
-#   include <boost/asio/ssl.hpp>
-#endif
-
-#include "json.hpp"
-
-#include "network_errc.hpp"
+#include "network_stream.hpp"
 
 namespace irccd {
 
@@ -49,16 +33,6 @@
 class transport_client : public std::enable_shared_from_this<transport_client> {
 public:
     /**
-     * Callback on receive operation.
-     */
-    using recv_t = std::function<void (boost::system::error_code, nlohmann::json)>;
-
-    /**
-     * Callback on send operation.
-     */
-    using send_t = std::function<void (boost::system::error_code)>;
-
-    /**
      * Client state.
      */
     enum class state_t {
@@ -67,64 +41,39 @@
         closing                             //!< client is closing
     };
 
-protected:
-    /**
-     * Handler for do_recv.
-     *
-     * The implementation should read until \r\n\r\n is found.
-     */
-    using do_recv_handler_t = std::function<void (boost::system::error_code, std::size_t)>;
-
-    /**
-     * Handler for do_send.
-     *
-     * The implementation must send the whole message.
-     */
-    using do_send_handler_t = std::function<void (boost::system::error_code, std::size_t)>;
-
-    /**
-     * Input buffer.
-     */
-    using input_t = boost::asio::streambuf;
-
 private:
-    using output_t = std::deque<std::pair<std::string, send_t>>;
-
     state_t state_{state_t::authenticating};
-    input_t input_;
-    output_t output_;
     transport_server& parent_;
 
     void close();
-    void flush();
 
 protected:
     /**
-     * Start a read operation, the implementation must start reading without
-     * any checks.
+     * Request a receive operation.
      *
-     * \pre handler is not null
-     * \param input the input buffer
-     * \param handler the completion handler
+     * The implementation must call the handler once the operation has finished
+     * even in case of errors.
+     *
+     * \param handler the non-null handler
      */
-    virtual void do_recv(input_t& input, do_recv_handler_t handler) = 0;
+    virtual void do_recv(network_recv_handler handler) = 0;
 
     /**
-     * Start a send operation, the implementation has no checks to perform
-     * because it is already done in transport_client functions.
+     * Request a send operation.
      *
-     * The message buffer remains valid until completion is complete.
+     * The implementation must call the handler once the operation has finished
+     * even in case of errors.
      *
-     * \pre message is not empty
-     * \pre handler is not null
-     * \param message the data to send
-     * \param handler the completion handler
+     * \param json the json message to send
+     * \param handler the non-null handler
      */
-    virtual void do_send(const std::string& message, do_send_handler_t handler) = 0;
+    virtual void do_send(nlohmann::json json, network_send_handler handler) = 0;
 
 public:
     /**
-     * Default constructor.
+     * Constructor.
+     *
+     * \param server the parent
      */
     inline transport_client(transport_server& server) noexcept
         : parent_(server)
@@ -132,11 +81,6 @@
     }
 
     /**
-     * Virtual destructor defaulted.
-     */
-    virtual ~transport_client() noexcept = default;
-
-    /**
      * Get the transport server parent.
      *
      * \return the parent
@@ -177,24 +121,20 @@
     }
 
     /**
-     * Start a receive operation.
+     * Start receiving if not closed.
      *
+     * \pre handler != nullptr
      * \param handler the handler
      */
-    void recv(recv_t handler);
+    void recv(network_recv_handler handler);
 
     /**
-     * Send or postpone some data to the client.
-     *
-     * If there are pending data, the operation will be ran once all other
-     * messages has been sent.
+     * Start sending if not closed.
      *
-     * \note if state is closing, no data is sent
-     * \pre data.is_object()
-     * \param data the message to send
-     * \param handler the optional completion handler
+     * \param json the json message
+     * \param handler the optional handler
      */
-    void send(const nlohmann::json& data, send_t handler = nullptr);
+    void send(nlohmann::json json, network_send_handler handler = nullptr);
 
     /**
      * Convenient success message.
@@ -202,12 +142,7 @@
      * \param cname the command name
      * \param handler the optional handler
      */
-    inline void success(const std::string& cname, send_t handler = nullptr)
-    {
-        assert(!cname.empty());
-
-        send({{ "command", cname }}, std::move(handler));
-    }
+    void success(const std::string& cname, network_send_handler handler = nullptr);
 
     /**
      * Send a error message, the state is set to closing.
@@ -224,7 +159,7 @@
      * \param message the error message
      * \param handler the handler
      */
-    void error(const nlohmann::json& data, send_t handler = nullptr);
+    void error(const nlohmann::json& data, network_send_handler handler = nullptr);
 
     /**
      * Convenient error overload.
@@ -234,16 +169,7 @@
      * \param reason the reason string
      * \param handler the optional handler
      */
-    inline void error(const std::string& cname, const std::string& reason, send_t handler = nullptr)
-    {
-        assert(!cname.empty());
-        assert(!reason.empty());
-
-        error({
-            { "command",    cname   },
-            { "error",      reason  }
-        }, std::move(handler));
-    }
+    void error(const std::string& cname, const std::string& reason, network_send_handler handler = nullptr);
 
     /**
      * Convenient error overload.
@@ -252,12 +178,7 @@
      * \param reason the reason string
      * \param handler the handler
      */
-    inline void error(const std::string& reason, send_t handler = nullptr)
-    {
-        assert(!reason.empty());
-
-        error({{ "error", reason }}, std::move(handler));
-    }
+    void error(const std::string& reason, network_send_handler handler = nullptr);
 
     /**
      * Convenient error overload.
@@ -266,15 +187,7 @@
      * \param reason the error code
      * \param handler the optional handler
      */
-    inline void error(const std::string& cname, network_errc reason, send_t handler = nullptr)
-    {
-        assert(!cname.empty());
-
-        error({
-            { "command",    cname                       },
-            { "error",      static_cast<int>(reason)    }
-        }, std::move(handler));
-    }
+    void error(const std::string& cname, network_errc reason, network_send_handler handler = nullptr);
 
     /**
      * Convenient error overload.
@@ -283,116 +196,9 @@
      * \param reason the reason string
      * \param handler the handler
      */
-    inline void error(network_errc reason, send_t handler = nullptr)
-    {
-        assert(reason != network_errc::no_error);
-
-        error({{ "error", static_cast<int>(reason) }}, std::move(handler));
-    }
+    void error(network_errc reason, network_send_handler handler = nullptr);
 };
 
-/**
- * \brief Basic implementation for IP/Tcp and local sockets
- *
- * This class implements an recv/send function for:
- *
- *   - boost::asio::ip::tcp
- *   - boost::asio::local::stream_protocol
- *   - boost::asio::ssl::stream
- */
-template <typename Socket>
-class basic_transport_client : public transport_client {
-protected:
-    Socket socket_;
-
-public:
-    /**
-     * Constructor.
-     *
-     * \param sock the socket
-     */
-    template <typename... Args>
-    inline basic_transport_client(transport_server& parent, Args&&... args) noexcept
-        : transport_client(parent)
-        , socket_(std::forward<Args>(args)...)
-    {
-    }
-
-    /**
-     * Get the underlying socket.
-     *
-     * \return the socket
-     */
-    inline const Socket& socket() const noexcept
-    {
-        return socket_;
-    }
-
-    /**
-     * Overloaded function.
-     *
-     * \return the socket
-     */
-    inline Socket& socket() noexcept
-    {
-        return socket_;
-    }
-
-    /**
-     * \copydoc transport_client::do_recv
-     */
-    void do_recv(input_t& input, do_recv_handler_t handler) override;
-
-    /**
-     * \copydoc transport_client::do_send
-     */
-    void do_send(const std::string& data, do_send_handler_t handler) override;
-};
-
-template <typename Socket>
-void basic_transport_client<Socket>::do_recv(input_t& input, do_recv_handler_t handler)
-{
-    auto self = shared_from_this();
-
-    boost::asio::async_read_until(socket_, input, "\r\n\r\n", [this, self, handler] (auto code, auto xfer) {
-        handler(code, xfer);
-    });
-}
-
-template <typename Socket>
-void basic_transport_client<Socket>::do_send(const std::string& data, do_send_handler_t handler)
-{
-    auto self = shared_from_this();
-
-    boost::asio::async_write(socket_, boost::asio::buffer(data), [this, self, handler] (auto code, auto xfer) {
-        handler(code, xfer);
-    });
-}
-
-#if defined(HAVE_SSL)
-
-/**
- * \brief Secure layer client.
- */
-class tls_transport_client : public basic_transport_client<boost::asio::ssl::stream<boost::asio::ip::tcp::socket>> {
-public:
-    /**
-     * Construct a secure layer client.
-     *
-     * \param parent the parent transport
-     * \param service the service to use
-     * \param context the context to reuse
-     */
-    tls_transport_client(transport_server& parent,
-                         boost::asio::io_service& service,
-                         boost::asio::ssl::context& context)
-        : basic_transport_client(parent, service, context)
-    {
-    }
-};
-
-#endif // !HAVE_SSL
-
 } // !irccd
 
 #endif // !IRCCD_TRANSPORT_CLIENT_HPP
--- a/libirccd/irccd/transport_server.cpp	Tue Nov 21 15:12:05 2017 +0100
+++ b/libirccd/irccd/transport_server.cpp	Wed Nov 22 11:23:10 2017 +0100
@@ -36,8 +36,6 @@
             return;
         }
 
-        clients_.insert(client);
-
         auto command = json_util::to_string(message["command"]);
         auto password = json_util::to_string(message["password"]);
 
@@ -49,6 +47,7 @@
             code = network_errc::invalid_auth;
         } else {
             client->set_state(transport_client::state_t::ready);
+            client->success("auth");
             code = network_errc::no_error;
         }
 
@@ -79,10 +78,8 @@
             handler(std::move(code), std::move(client));
         else if (!password_.empty())
             do_auth(std::move(client), std::move(handler));
-        else {
-            clients_.insert(client);
+        else
             handler(std::move(code), std::move(client));
-        }
     });
 }
 
@@ -93,8 +90,10 @@
     do_accept([this, handler] (auto code, auto client) {
         if (code)
             handler(std::move(code), nullptr);
-        else
+        else {
+            clients_.insert(client);
             do_greetings(std::move(client), std::move(handler));
+        }
     });
 }
 
--- a/libirccd/irccd/transport_server.hpp	Tue Nov 21 15:12:05 2017 +0100
+++ b/libirccd/irccd/transport_server.hpp	Wed Nov 22 11:23:10 2017 +0100
@@ -33,7 +33,7 @@
 #   include <boost/asio/ssl.hpp>
 #endif
 
-#include "transport_client.hpp"
+#include "basic_transport_client.hpp"
 
 namespace irccd {
 
@@ -186,15 +186,13 @@
 template <typename Protocol>
 void basic_transport_server<Protocol>::do_accept(accept_t handler)
 {
-    using client_type = basic_transport_client<socket_t>;
-
-    auto client = std::make_shared<client_type>(*this, acceptor_.get_io_service());
+    auto client = std::make_shared<basic_transport_client<socket_t>>(*this, acceptor_.get_io_service());
 
-    acceptor_.async_accept(client->socket(), [client, handler] (auto code) {
-        if (!code)
+    acceptor_.async_accept(client->stream().socket(), [this, client, handler] (auto code) {
+        if (code)
+            handler(std::move(code), nullptr);
+        else
             handler(std::move(code), std::move(client));
-        else
-            handler(std::move(code), nullptr);
     });
 }
 
--- a/libirccdctl/CMakeLists.txt	Tue Nov 21 15:12:05 2017 +0100
+++ b/libirccdctl/CMakeLists.txt	Wed Nov 22 11:23:10 2017 +0100
@@ -20,9 +20,9 @@
 
 set(
     HEADERS
+    ${libirccdctl_SOURCE_DIR}/irccd/ctl/basic_connection.hpp
     ${libirccdctl_SOURCE_DIR}/irccd/ctl/controller.hpp
     ${libirccdctl_SOURCE_DIR}/irccd/ctl/connection.hpp
-    ${libirccdctl_SOURCE_DIR}/irccd/ctl/network_connection.hpp
     ${libirccdctl_SOURCE_DIR}/irccd/ctl/ip_connection.hpp
 )
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccdctl/irccd/ctl/basic_connection.hpp	Wed Nov 22 11:23:10 2017 +0100
@@ -0,0 +1,92 @@
+/*
+ * basic_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_BASIC_CONNECTION_HPP
+#define IRCCD_CTL_BASIC_CONNECTION_HPP
+
+/**
+ * \file basic_connection.hpp
+ * \brief Network based connection for controller.
+ */
+
+#include <irccd/network_stream.hpp>
+
+#include "connection.hpp"
+
+namespace irccd {
+
+namespace ctl {
+
+/**
+ * \brief Network based connection for controller.
+ *
+ * This class implements recv and send functions for Boost.Asio based sockets,
+ * the subclasses only need to implement a connect function.
+ */
+template <typename Socket>
+class basic_connection : public connection {
+protected:
+    network_stream<Socket> stream_;
+
+public:
+    /**
+     * Construct the network connection.
+     *
+     * \param args the arguments to pass to the socket
+     */
+    template <typename... Args>
+    inline basic_connection(Args&&... args)
+        : stream_(std::forward<Args>(args)...)
+    {
+    }
+
+    /**
+     * \copydoc connection::is_active
+     */
+    bool is_active() const noexcept override
+    {
+        return stream_.is_active();
+    }
+
+    /**
+     * Implements connection::recv using boost::asio::async_read_until.
+     *
+     * \param handler the handler
+     */
+    void recv(network_recv_handler handler) override
+    {
+        stream_.recv(std::move(handler));
+    }
+
+    /**
+     * Implements connection::send using boost::asio::async_write.
+     *
+     * \param json the JSON message
+     * \param handler the handler
+     */
+    void send(nlohmann::json json, network_send_handler handler) override
+    {
+        stream_.send(std::move(json), std::move(handler));
+    }
+};
+
+} // !ctl
+
+} // !irccd
+
+#endif // !IRCCD_CTL_BASIC_CONNECTION_HPP
--- a/libirccdctl/irccd/ctl/connection.hpp	Tue Nov 21 15:12:05 2017 +0100
+++ b/libirccdctl/irccd/ctl/connection.hpp	Wed Nov 22 11:23:10 2017 +0100
@@ -45,20 +45,9 @@
     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.
+     * Tells if operations are in progress.
      */
-    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)>;
+    virtual bool is_active() const noexcept = 0;
 
     /**
      * Connect to the daemon.
@@ -67,20 +56,9 @@
      */
     virtual void connect(connect_t handler) = 0;
 
-    /**
-     * Receive a message.
-     *
-     * \param handler the non-null handler
-     */
-    virtual void recv(recv_t) = 0;
+    virtual void recv(network_recv_handler handler) = 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;
+    virtual void send(nlohmann::json json, network_send_handler handler) = 0;
 };
 
 } // !ctl
--- a/libirccdctl/irccd/ctl/controller.cpp	Tue Nov 21 15:12:05 2017 +0100
+++ b/libirccdctl/irccd/ctl/controller.cpp	Wed Nov 22 11:23:10 2017 +0100
@@ -29,50 +29,22 @@
 
 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" },
+        { "command", "auth" },
         { "password", password_ }
     });
 
-    send(std::move(cmd), [handler, info, this] (auto code, auto) {
+    send(std::move(cmd), [handler, info, this] (auto code) {
         if (code) {
             handler(std::move(code), nullptr);
             return;
         }
 
         recv([handler, info, this] (auto code, auto message) {
+            if (message["error"].is_number_integer())
+                code = static_cast<network_errc>(message["error"].template get<int>());
             if (message["error"].is_string())
                 code = network_errc::invalid_auth;
 
@@ -114,28 +86,22 @@
     });
 }
 
-void controller::recv(recv_t handler)
+void controller::recv(network_recv_handler handler)
 {
     assert(handler);
 
-    auto in_progress = !rqueue_.empty();
+    // TODO: ensure connected.
 
-    rqueue_.push_back(std::move(handler));
-
-    if (!in_progress)
-        flush_recv();
+    conn_.recv(std::move(handler));
 }
 
-void controller::send(nlohmann::json message, send_t handler)
+void controller::send(nlohmann::json message, network_send_handler handler)
 {
     assert(message.is_object());
 
-    auto in_progress = !squeue_.empty();
+    // TODO: ensure connected.
 
-    squeue_.emplace_back(std::move(message), std::move(handler));
-
-    if (!in_progress)
-        flush_send();
+    conn_.send(std::move(message), std::move(handler));
 }
 
 } // !ctl
--- a/libirccdctl/irccd/ctl/controller.hpp	Tue Nov 21 15:12:05 2017 +0100
+++ b/libirccdctl/irccd/ctl/controller.hpp	Wed Nov 22 11:23:10 2017 +0100
@@ -24,13 +24,7 @@
  * \brief Main irccdctl interface.
  */
 
-#include <boost/system/error_code.hpp>
-
-#include <deque>
-#include <functional>
-#include <string>
-
-#include <json.hpp>
+#include <irccd/network_stream.hpp>
 
 namespace irccd {
 
@@ -67,34 +61,10 @@
      */
     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);
 
@@ -109,34 +79,14 @@
     {
     }
 
-    /**
-     * Tells if receive requests are pending.
-     *
-     * \return true if receive queue is not empty
-     */
-    inline bool has_recv_pending() const noexcept
+    inline const connection& conn() const noexcept
     {
-        return !rqueue_.empty();
+        return conn_;
     }
 
-    /**
-     * Tells if send requests are pending.
-     *
-     * \return true if send queue is not empty
-     */
-    inline bool has_send_pending() const noexcept
+    inline connection& conn() 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();
+        return conn_;
     }
 
     /**
@@ -177,7 +127,7 @@
      * \pre handler != nullptr
      * \param handler the recv handler
      */
-    void recv(recv_t handler);
+    void recv(network_recv_handler handler);
 
     /**
      * Queue a send operation, if receive operations are already running, it is
@@ -187,7 +137,7 @@
      * \param message the JSON message
      * \param handler the optional completion handler
      */
-    void send(nlohmann::json message, send_t handler);
+    void send(nlohmann::json message, network_send_handler handler);
 };
 
 } // !ctl
--- a/libirccdctl/irccd/ctl/ip_connection.cpp	Tue Nov 21 15:12:05 2017 +0100
+++ b/libirccdctl/irccd/ctl/ip_connection.cpp	Wed Nov 22 11:23:10 2017 +0100
@@ -59,7 +59,7 @@
 
 void ip_connection::connect(connect_t handler)
 {
-    do_resolve(host_, std::to_string(port_), socket_, resolver_, std::move(handler));
+    do_resolve(host_, std::to_string(port_), stream_.socket(), resolver_, std::move(handler));
 }
 
 #if defined(HAVE_SSL)
--- a/libirccdctl/irccd/ctl/ip_connection.hpp	Tue Nov 21 15:12:05 2017 +0100
+++ b/libirccdctl/irccd/ctl/ip_connection.hpp	Wed Nov 22 11:23:10 2017 +0100
@@ -29,11 +29,7 @@
 #include <string>
 #include <cstdint>
 
-#if defined(HAVE_SSL)
-#   include <boost/asio/ssl.hpp>
-#endif
-
-#include "network_connection.hpp"
+#include "basic_connection.hpp"
 
 namespace irccd {
 
@@ -43,7 +39,7 @@
  * \brief Common class for both ip and tls connections.
  */
 template <typename Socket>
-class basic_ip_connection : public network_connection<Socket> {
+class basic_ip_connection : public basic_connection<Socket> {
 protected:
     boost::asio::ip::tcp::resolver resolver_;
     std::string host_;
@@ -64,7 +60,7 @@
      */
     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)...)
+        : basic_connection<Socket>(service, std::forward<Args>(args)...)
         , resolver_(service)
         , host_(std::move(host))
         , port_(std::move(port))
--- a/libirccdctl/irccd/ctl/local_connection.cpp	Tue Nov 21 15:12:05 2017 +0100
+++ b/libirccdctl/irccd/ctl/local_connection.cpp	Wed Nov 22 11:23:10 2017 +0100
@@ -28,7 +28,7 @@
 {
     using endpoint = boost::asio::local::stream_protocol::endpoint;
 
-    socket_.async_connect(endpoint(path_), std::move(handler));
+    stream_.socket().async_connect(endpoint(path_), std::move(handler));
 }
 
 } // !ctl
--- a/libirccdctl/irccd/ctl/local_connection.hpp	Tue Nov 21 15:12:05 2017 +0100
+++ b/libirccdctl/irccd/ctl/local_connection.hpp	Wed Nov 22 11:23:10 2017 +0100
@@ -24,9 +24,7 @@
  * \brief Unix domain connection for irccdctl.
  */
 
-#include <irccd/sysconfig.hpp>
-
-#include "network_connection.hpp"
+#include "basic_connection.hpp"
 
 #if !defined(IRCCD_SYSTEM_WINDOWS)
 
@@ -37,7 +35,7 @@
 /**
  * \brief Unix domain connection for irccdctl.
  */
-class local_connection : public network_connection<boost::asio::local::stream_protocol::socket> {
+class local_connection : public basic_connection<boost::asio::local::stream_protocol::socket> {
 private:
     std::string path_;
 
@@ -49,7 +47,7 @@
      * \param path the path to the socket file
      */
     inline local_connection(boost::asio::io_service& service, std::string path) noexcept
-        : network_connection(service)
+        : basic_connection(service)
         , path_(std::move(path))
     {
     }
--- a/libirccdctl/irccd/ctl/network_connection.hpp	Tue Nov 21 15:12:05 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,133 +0,0 @@
-/*
- * 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/network_errc.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);
-
-        /*
-         * Only catch parse error from JSON to avoid calling the handler twice
-         * if the handler throws from itself.
-         *
-         * TODO: in json 3.0.0, you may catch nlohmann::json::parse_error
-         * instead.
-         */
-        nlohmann::json json;
-
-        try {
-            json = nlohmann::json::parse(command);
-        } catch (...) {
-            handler(network_errc::invalid_message, nullptr);
-        }
-
-        if (!json.is_object())
-            handler(network_errc::invalid_message, nullptr);
-        else
-            handler(network_errc::no_error, std::move(json));
-    });
-}
-
-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