changeset 251:7b54db12be51

Irccd: rename Connection to Client
author David Demelier <markand@malikania.fr>
date Tue, 23 Aug 2016 23:02:06 +0200
parents 79d9840811a1
children e0f58cfbd45a
files lib/irccd/CMakeSources.cmake lib/irccd/client.cpp lib/irccd/client.hpp lib/irccd/cmd-watch.cpp lib/irccd/connection.cpp lib/irccd/connection.hpp lib/irccd/irccdctl.cpp lib/irccd/irccdctl.hpp
diffstat 8 files changed, 882 insertions(+), 872 deletions(-) [+]
line wrap: on
line diff
--- a/lib/irccd/CMakeSources.cmake	Thu Aug 18 16:42:10 2016 +0200
+++ b/lib/irccd/CMakeSources.cmake	Tue Aug 23 23:02:06 2016 +0200
@@ -1,7 +1,7 @@
 set(
     HEADERS
     ${CMAKE_CURRENT_LIST_DIR}/alias.hpp
-    ${CMAKE_CURRENT_LIST_DIR}/connection.hpp
+    ${CMAKE_CURRENT_LIST_DIR}/client.hpp
     ${CMAKE_CURRENT_LIST_DIR}/cmd-help.hpp
     ${CMAKE_CURRENT_LIST_DIR}/cmd-plugin-config.hpp
     ${CMAKE_CURRENT_LIST_DIR}/cmd-plugin-info.hpp
@@ -73,7 +73,7 @@
 set(
     SOURCES
     ${CMAKE_CURRENT_LIST_DIR}/alias.cpp
-    ${CMAKE_CURRENT_LIST_DIR}/connection.cpp
+    ${CMAKE_CURRENT_LIST_DIR}/client.cpp
     ${CMAKE_CURRENT_LIST_DIR}/config.cpp
     ${CMAKE_CURRENT_LIST_DIR}/cmd-help.cpp
     ${CMAKE_CURRENT_LIST_DIR}/cmd-plugin-config.cpp
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lib/irccd/client.cpp	Tue Aug 23 23:02:06 2016 +0200
@@ -0,0 +1,547 @@
+/*
+ * client.cpp -- value wrapper for connecting to irccd
+ *
+ * Copyright (c) 2013-2016 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 <format.h>
+
+#include "client.hpp"
+#include "util.hpp"
+
+using namespace fmt::literals;
+
+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 &cnx, const std::string &message)
+    {
+        try {
+            auto json = nlohmann::json::parse(message);
+
+            if (!json.is_object())
+                return;
+
+            cnx.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 = util::nextNetwork(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 = util::nextNetwork(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("server version too recent {}.{}.{} vs {}.{}.{}"_format(
+                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 = util::nextNetwork(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 &, fd_set &) override
+    {
+        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.
+ * ------------------------------------------------------------------
+ */
+
+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);
+}
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lib/irccd/client.hpp	Tue Aug 23 23:02:06 2016 +0200
@@ -0,0 +1,311 @@
+/*
+ * client.hpp -- value wrapper for connecting to irccd
+ *
+ * Copyright (c) 2013-2016 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 "net.hpp"
+#include "signals.hpp"
+#include "pollable.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 Pollable {
+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;
+
+    /**
+     * 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
+     */
+    void prepare(fd_set &in, fd_set &out, net::Handle &max) override;
+
+    /**
+     * Do some I/O using the protected recv and send functions.
+     *
+     * \param in the input set
+     * \param out the output set
+     */
+    void sync(fd_set &in, fd_set &out) override;
+};
+
+/**
+ * \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
+     */
+    virtual unsigned recv(char *buffer, unsigned length);
+
+    /**
+     * \copydoc Client::send
+     */
+    virtual unsigned send(const char *buffer, unsigned length);
+
+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;
+};
+
+} // !irccd
+
+#endif // !IRCCD_CLIENT_HPP
--- a/lib/irccd/cmd-watch.cpp	Thu Aug 18 16:42:10 2016 +0200
+++ b/lib/irccd/cmd-watch.cpp	Tue Aug 23 23:02:06 2016 +0200
@@ -215,7 +215,7 @@
     if (format != "native" && format != "json")
         throw std::invalid_argument("invalid format given: " + format);
 
-    while (ctl.connection().isConnected()) {
+    while (ctl.client().isConnected()) {
         try {
             auto object = ctl.next();
             auto event = object.find("event");
--- a/lib/irccd/connection.cpp	Thu Aug 18 16:42:10 2016 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,547 +0,0 @@
-/*
- * connection.cpp -- value wrapper for connecting to irccd
- *
- * Copyright (c) 2013-2016 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 <stdexcept>
-
-#include <format.h>
-
-#include "connection.hpp"
-#include "util.hpp"
-
-using namespace fmt::literals;
-
-namespace irccd {
-
-/*
- * Connection::State.
- * ------------------------------------------------------------------
- */
-
-class Connection::State {
-public:
-    State() = default;
-    virtual ~State() = default;
-    virtual Status status() const noexcept = 0;
-    virtual void prepare(Connection &cnt, fd_set &in, fd_set &out) = 0;
-    virtual void sync(Connection &cnt, fd_set &in, fd_set &out) = 0;
-};
-
-/*
- * Connection::DisconnectedState.
- * ------------------------------------------------------------------
- */
-
-class Connection::DisconnectedState : public Connection::State {
-public:
-    Connection::Status status() const noexcept override
-    {
-        return Disconnected;
-    }
-
-    void prepare(Connection &, fd_set &, fd_set &) override {}
-    void sync(Connection &, fd_set &, fd_set &) override {}
-};
-
-/*
- * Connection::DisconnectedState.
- * ------------------------------------------------------------------
- */
-
-class Connection::ReadyState : public Connection::State {
-private:
-    void parse(Connection &cnx, const std::string &message)
-    {
-        try {
-            auto json = nlohmann::json::parse(message);
-
-            if (!json.is_object())
-                return;
-
-            cnx.onMessage(json);
-        } catch (const std::exception &) {
-        }
-    }
-public:
-    Connection::Status status() const noexcept override
-    {
-        return Ready;
-    }
-
-    void prepare(Connection &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(Connection &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 = util::nextNetwork(cnx.m_input);
-
-            if (!msg.empty())
-                parse(cnx, msg);
-        } while (!msg.empty());
-    }
-};
-
-/*
- * Connection::AuthState.
- * ------------------------------------------------------------------
- */
-
-class Connection::AuthState : public Connection::State {
-private:
-    enum {
-        Created,
-        Sending,
-        Checking
-    } m_auth{Created};
-
-    std::string m_output;
-
-    void send(Connection &cnt) noexcept
-    {
-        try {
-            auto n = cnt.send(m_output.data(), m_output.size());
-
-            if (n == 0) {
-                m_output.clear();
-                throw std::runtime_error("connection 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(Connection &cnt) noexcept
-    {
-        cnt.recv();
-
-        auto msg = util::nextNetwork(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:
-    Connection::Status status() const noexcept override
-    {
-        return Authenticating;
-    }
-
-    void prepare(Connection &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(Connection &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;
-        }
-    }
-};
-
-/*
- * Connection::CheckingState.
- * ------------------------------------------------------------------
- */
-
-class Connection::CheckingState : public Connection::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(Connection &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("server version too recent {}.{}.{} vs {}.{}.{}"_format(
-                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(Connection &cnx) const
-    {
-        auto msg = util::nextNetwork(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:
-    Connection::Status status() const noexcept override
-    {
-        return Checking;
-    }
-
-    void prepare(Connection &cnx, fd_set &in, fd_set &) override
-    {
-        FD_SET(cnx.m_socket.handle(), &in);
-    }
-
-    void sync(Connection &cnx, fd_set &, fd_set &) override
-    {
-        cnx.recv();
-
-        verify(cnx);
-    }
-};
-
-/*
- * Connection::ConnectingState.
- * ------------------------------------------------------------------
- */
-
-class Connection::ConnectingState : public Connection::State {
-public:
-    Connection::Status status() const noexcept override
-    {
-        return Connecting;
-    }
-
-    void prepare(Connection &cnx, fd_set &, fd_set &out) override
-    {
-        FD_SET(cnx.m_socket.handle(), &out);
-    }
-
-    void sync(Connection &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());
-        }
-    }
-};
-
-/*
- * Connection.
- * ------------------------------------------------------------------
- */
-
-unsigned Connection::recv(char *buffer, unsigned length)
-{
-    return m_socket.recv(buffer, length);
-}
-
-unsigned Connection::send(const char *buffer, unsigned length)
-{
-    return m_socket.send(buffer, length);
-}
-
-void Connection::recv()
-{
-    try {
-        std::string buffer;
-
-        buffer.resize(512);
-        buffer.resize(recv(&buffer[0], buffer.size()));
-
-        if (buffer.empty())
-            throw std::runtime_error("connection lost");
-
-        m_input += std::move(buffer);
-    } catch (const std::exception &ex) {
-        m_stateNext = std::make_unique<DisconnectedState>();
-        onDisconnect(ex.what());
-    }
-}
-
-void Connection::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());
-    }
-}
-
-Connection::Connection()
-    : m_state(std::make_unique<DisconnectedState>())
-{
-}
-
-Connection::~Connection() = default;
-
-Connection::Status Connection::status() const noexcept
-{
-    return m_state->status();
-}
-
-void Connection::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 Connection::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 Connection::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());
-    }
-}
-
-/*
- * TlsConnection.
- * ------------------------------------------------------------------
- */
-
-void TlsConnection::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 TlsConnection::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 TlsConnection::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 TlsConnection::connect(const net::Address &address)
-{
-    Connection::connect(address);
-
-    m_ssl = std::make_unique<net::TlsSocket>(m_socket, net::TlsSocket::Client);
-}
-
-void TlsConnection::prepare(fd_set &in, fd_set &out, net::Handle &max)
-{
-    if (m_state->status() == Connecting)
-        Connection::prepare(in, out, max);
-    else {
-        if (m_socket.handle() > max)
-            max = m_socket.handle();
-
-        /*
-         * Attempt an immediate handshake immediately if connection 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:
-            Connection::prepare(in, out, max);
-        }
-    }
-}
-
-void TlsConnection::sync(fd_set &in, fd_set &out)
-{
-    if (m_state->status() == Connecting)
-        Connection::sync(in, out);
-    else if (m_handshake != HandshakeReady)
-        handshake();
-    else
-        Connection::sync(in, out);
-}
-
-} // !irccd
--- a/lib/irccd/connection.hpp	Thu Aug 18 16:42:10 2016 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,311 +0,0 @@
-/*
- * connection.hpp -- value wrapper for connecting to irccd
- *
- * Copyright (c) 2013-2016 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_CONNECTION_HPP
-#define IRCCD_CONNECTION_HPP
-
-/**
- * \file connection.hpp
- * \brief Connection to irccd instance.
- */
-
-#include <cassert>
-#include <memory>
-#include <string>
-
-#include "net.hpp"
-#include "signals.hpp"
-#include "pollable.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 Connection : public Pollable {
-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;
-
-    /**
-     * 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.
-     */
-    Connection();
-
-    /**
-     * Default destructor.
-     */
-    virtual ~Connection();
-
-    /**
-     * 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
-     */
-    void prepare(fd_set &in, fd_set &out, net::Handle &max) override;
-
-    /**
-     * Do some I/O using the protected recv and send functions.
-     *
-     * \param in the input set
-     * \param out the output set
-     */
-    void sync(fd_set &in, fd_set &out) override;
-};
-
-/**
- * \brief TLS over IP connection.
- */
-class TlsConnection : public Connection {
-private:
-    enum {
-        HandshakeUndone,
-        HandshakeRead,
-        HandshakeWrite,
-        HandshakeReady
-    } m_handshake{HandshakeUndone};
-
-private:
-    std::unique_ptr<net::TlsSocket> m_ssl;
-
-    void handshake();
-
-protected:
-    /**
-     * \copydoc Connection::recv
-     */
-    virtual unsigned recv(char *buffer, unsigned length);
-
-    /**
-     * \copydoc Connection::send
-     */
-    virtual unsigned send(const char *buffer, unsigned length);
-
-public:
-    /**
-     * \copydoc Connection::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;
-};
-
-} // !irccd
-
-#endif // !IRCCD_CONNECTION_HPP
--- a/lib/irccd/irccdctl.cpp	Thu Aug 18 16:42:10 2016 +0200
+++ b/lib/irccd/irccdctl.cpp	Tue Aug 23 23:02:06 2016 +0200
@@ -19,7 +19,7 @@
 #include <format.h>
 
 #include "command.hpp"
-#include "connection.hpp"
+#include "client.hpp"
 #include "elapsed-timer.hpp"
 #include "fs.hpp"
 #include "ini.hpp"
@@ -121,9 +121,9 @@
     m_address = net::resolveOne(host, port, domain, SOCK_STREAM);
 
     if ((it = sc.find("ssl")) != sc.end() && util::isBoolean(it->value()))
-        m_connection = std::make_unique<TlsConnection>();
+        m_connection = std::make_unique<TlsClient>();
     else
-        m_connection = std::make_unique<Connection>();
+        m_connection = std::make_unique<Client>();
 }
 
 /*
@@ -145,7 +145,7 @@
         throw std::invalid_argument("missing path parameter");
 
     m_address = net::local::create(it->value());
-    m_connection = std::make_unique<Connection>();
+    m_connection = std::make_unique<Client>();
 #else
     (void)sc;
 
@@ -291,7 +291,7 @@
         domain = it->second == "ipv6" ? AF_INET6: AF_INET;
 
     m_address = net::resolveOne(host, port, domain, SOCK_STREAM);
-    m_connection = std::make_unique<Connection>();
+    m_connection = std::make_unique<Client>();
 }
 
 /*
@@ -311,7 +311,7 @@
         throw std::invalid_argument("missing path parameter (-P or --path)");
 
     m_address = net::local::create(it->second, false);
-    m_connection = std::make_unique<Connection>();
+    m_connection = std::make_unique<Client>();
 #else
     (void)options;
 
--- a/lib/irccd/irccdctl.hpp	Thu Aug 18 16:42:10 2016 +0200
+++ b/lib/irccd/irccdctl.hpp	Tue Aug 23 23:02:06 2016 +0200
@@ -28,7 +28,7 @@
 #include <memory>
 #include <string>
 
-#include "connection.hpp"
+#include "client.hpp"
 #include "alias.hpp"
 #include "options.hpp"
 #include "service-command.hpp"
@@ -37,7 +37,7 @@
 
 namespace irccd {
 
-class Connection;
+class Client;
 
 namespace ini {
 
@@ -55,7 +55,7 @@
     CommandService m_commandService;
 
     // Connection handler.
-    std::unique_ptr<Connection> m_connection;
+    std::unique_ptr<Client> m_connection;
     std::uint32_t m_timeout{30000};
     net::Address m_address;
 
@@ -93,12 +93,22 @@
         return m_commandService;
     }
 
-    inline const Connection &connection() const noexcept
+    /**
+     * Get the client connection to irccd.
+     *
+     * \return the connection
+     */
+    inline const Client &client() const noexcept
     {
         return *m_connection;
     }
 
-    inline Connection &connection() noexcept
+    /**
+     * Get the client connection to irccd.
+     *
+     * \return the connection
+     */
+    inline Client &client() noexcept
     {
         return *m_connection;
     }