changeset 240:b25176b3bb80

Irccd: unify connection and the states in connection.cpp
author David Demelier <markand@malikania.fr>
date Wed, 17 Aug 2016 16:55:25 +0200
parents 554a542f0a82
children d46111dcc510
files lib/irccd/CMakeSources.cmake lib/irccd/conn-state-auth.cpp lib/irccd/conn-state-auth.hpp lib/irccd/conn-state-checking.cpp lib/irccd/conn-state-checking.hpp lib/irccd/conn-state-connecting.cpp lib/irccd/conn-state-connecting.hpp lib/irccd/conn-state-disconnected.cpp lib/irccd/conn-state-disconnected.hpp lib/irccd/conn-state-ready.cpp lib/irccd/conn-state-ready.hpp lib/irccd/conn-state.hpp lib/irccd/connection.cpp
diffstat 13 files changed, 321 insertions(+), 788 deletions(-) [+]
line wrap: on
line diff
--- a/lib/irccd/CMakeSources.cmake	Wed Aug 17 15:20:50 2016 +0200
+++ b/lib/irccd/CMakeSources.cmake	Wed Aug 17 16:55:25 2016 +0200
@@ -2,11 +2,6 @@
     HEADERS
     ${CMAKE_CURRENT_LIST_DIR}/alias.hpp
     ${CMAKE_CURRENT_LIST_DIR}/connection.hpp
-    ${CMAKE_CURRENT_LIST_DIR}/conn-state-auth.hpp
-    ${CMAKE_CURRENT_LIST_DIR}/conn-state-checking.hpp
-    ${CMAKE_CURRENT_LIST_DIR}/conn-state-connecting.hpp
-    ${CMAKE_CURRENT_LIST_DIR}/conn-state-disconnected.hpp
-    ${CMAKE_CURRENT_LIST_DIR}/conn-state-ready.hpp
     ${CMAKE_CURRENT_LIST_DIR}/cmd-help.hpp
     ${CMAKE_CURRENT_LIST_DIR}/cmd-plugin-config.hpp
     ${CMAKE_CURRENT_LIST_DIR}/cmd-plugin-info.hpp
@@ -84,11 +79,6 @@
     SOURCES
     ${CMAKE_CURRENT_LIST_DIR}/alias.cpp
     ${CMAKE_CURRENT_LIST_DIR}/connection.cpp
-    ${CMAKE_CURRENT_LIST_DIR}/conn-state-auth.cpp
-    ${CMAKE_CURRENT_LIST_DIR}/conn-state-checking.cpp
-    ${CMAKE_CURRENT_LIST_DIR}/conn-state-connecting.cpp
-    ${CMAKE_CURRENT_LIST_DIR}/conn-state-disconnected.cpp
-    ${CMAKE_CURRENT_LIST_DIR}/conn-state-ready.cpp
     ${CMAKE_CURRENT_LIST_DIR}/config.cpp
     ${CMAKE_CURRENT_LIST_DIR}/cmd-help.cpp
     ${CMAKE_CURRENT_LIST_DIR}/cmd-plugin-config.cpp
--- a/lib/irccd/conn-state-auth.cpp	Wed Aug 17 15:20:50 2016 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,124 +0,0 @@
-/*
- * conn-state-auth.cpp -- connection is authenticating
- *
- * 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 "conn-state-auth.hpp"
-#include "conn-state-disconnected.hpp"
-#include "conn-state-ready.hpp"
-
-namespace irccd {
-
-void Connection::AuthState::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 Connection::AuthState::check(Connection &cnt) noexcept
-{
-    cnt.syncInput();
-
-    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());
-    }
-}
-
-Connection::Status Connection::AuthState::status() const noexcept
-{
-    return Authenticating;
-}
-
-void Connection::AuthState::prepare(Connection &cnt, fd_set &in, fd_set &out)
-{
-    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 Connection::AuthState::sync(Connection &cnt, fd_set &in, fd_set &out)
-{
-    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;
-    }
-}
-
-} // !irccd
--- a/lib/irccd/conn-state-auth.hpp	Wed Aug 17 15:20:50 2016 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,70 +0,0 @@
-/*
- * conn-state-auth.hpp -- connection is authenticating
- *
- * 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_CONN_STATE_AUTH_HPP
-#define IRCCD_CONN_STATE_AUTH_HPP
-
-/**
- * \file conn-state-auth.hpp
- * \brief Connection is authenticating.
- */
-
-#include "conn-state.hpp"
-
-namespace irccd {
-
-/**
- * \brief Authentication in progress.
- *
- * This state emit the authentication command and receives the response to see
- * if authentication succeeded.
- */
-class Connection::AuthState : public Connection::State {
-private:
-    enum {
-        Created,
-        Sending,
-        Checking
-    } m_auth{Created};
-
-    std::string m_output;
-
-    void send(Connection &cnt) noexcept;
-    void check(Connection &cnt) noexcept;
-
-public:
-
-    /**
-     * \copydoc State::status
-     */
-    Status status() const noexcept override;
-
-    /**
-     * \copydoc State::prepare
-     */
-    void prepare(Connection &cnt, fd_set &in, fd_set &out) override;
-
-    /**
-     * \copydoc State::sync
-     */
-    void sync(Connection &cnt, fd_set &in, fd_set &out) override;
-};
-
-} // !irccd
-
-#endif // !IRCCD_CONN_STATE_AUTH_HPP
--- a/lib/irccd/conn-state-checking.cpp	Wed Aug 17 15:20:50 2016 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,106 +0,0 @@
-/*
- * conn-state-checking.cpp -- verify irccd instance
- *
- * 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 <format.h>
-
-#include "conn-state-auth.hpp"
-#include "conn-state-checking.hpp"
-#include "conn-state-disconnected.hpp"
-#include "conn-state-ready.hpp"
-#include "sysconfig.hpp"
-
-using namespace fmt::literals;
-
-namespace irccd {
-
-void Connection::CheckingState::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 Connection::CheckingState::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 Connection::CheckingState::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());
-    }
-}
-
-Connection::Status Connection::CheckingState::status() const noexcept
-{
-    return Checking;
-}
-
-void Connection::CheckingState::prepare(Connection &cnx, fd_set &in, fd_set &)
-{
-    FD_SET(cnx.m_socket.handle(), &in);
-}
-
-void Connection::CheckingState::sync(Connection &cnx, fd_set &, fd_set &)
-{
-    cnx.syncInput();
-
-    verify(cnx);
-}
-
-} // !irccd
--- a/lib/irccd/conn-state-checking.hpp	Wed Aug 17 15:20:50 2016 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,65 +0,0 @@
-/*
- * conn-state-checking.hpp -- verify irccd instance
- *
- * 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_CONN_STATE_CHECKING_HPP
-#define IRCCD_CONN_STATE_CHECKING_HPP
-
-/**
- * \file conn-state-checking.hpp
- * \brief Verify irccd instance and version
- */
-
-#include "conn-state.hpp"
-
-namespace irccd {
-
-/**
- * \brief State for veryfing connection.
- *
- * This state is used when socket connection is complete but we have not
- * verified that the endpoint is an irccd instance.
- *
- * This state also verifies that the irccd daemon is compatible with
- * our library.
- */
-class Connection::CheckingState : public Connection::State {
-private:
-    void verifyProgram(const nlohmann::json &json) const;
-    void verifyVersion(Connection &cnx, const nlohmann::json &json) const;
-    void verify(Connection &cnx) const;
-
-public:
-    /**
-     * \copydoc State::status
-     */
-    Status status() const noexcept override;
-
-    /**
-     * \copydoc State::prepare
-     */
-    void prepare(Connection &cnt, fd_set &in, fd_set &out) override;
-
-    /**
-     * \copydoc State::sync
-     */
-    void sync(Connection &cnt, fd_set &in, fd_set &out) override;
-};
-
-} // !irccd
-
-#endif // !IRCCD_CONN_STATE_CHECKING_HPP
--- a/lib/irccd/conn-state-connecting.cpp	Wed Aug 17 15:20:50 2016 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,54 +0,0 @@
-/*
- * conn-state-connecting.cpp -- connection is in progress
- *
- * 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 "conn-state-checking.hpp"
-#include "conn-state-connecting.hpp"
-#include "conn-state-disconnected.hpp"
-
-namespace irccd {
-
-Connection::Status Connection::ConnectingState::status() const noexcept
-{
-    return Connecting;
-}
-
-void Connection::ConnectingState::prepare(Connection &cnx, fd_set &, fd_set &out)
-{
-    FD_SET(cnx.m_socket.handle(), &out);
-}
-
-void Connection::ConnectingState::sync(Connection &cnx, fd_set &, fd_set &out)
-{
-    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());
-    }
-}
-
-} // !irccd
--- a/lib/irccd/conn-state-connecting.hpp	Wed Aug 17 15:20:50 2016 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,57 +0,0 @@
-/*
- * conn-state-connecting.hpp -- connection is in progress
- *
- * 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_CONN_STATE_CONNECTING_HPP
-#define IRCCD_CONN_STATE_CONNECTING_HPP
-
-/**
- * \file conn-state-connecting.hpp
- * \brief Connection is in progress.
- */
-
-#include "conn-state.hpp"
-
-namespace irccd {
-
-/**
- * \brief State to complete socket connection.
- *
- * This state is used to complete the socket connection if it has not been
- * completed immediately.
- */
-class Connection::ConnectingState : public Connection::State {
-public:
-    /**
-     * \copydoc State::status
-     */
-    Status status() const noexcept override;
-
-    /**
-     * \copydoc State::prepare
-     */
-    void prepare(Connection &cnt, fd_set &in, fd_set &out) override;
-
-    /**
-     * \copydoc State::sync
-     */
-    void sync(Connection &cnt, fd_set &in, fd_set &out) override;
-};
-
-} // !irccd
-
-#endif // !IRCCD_CONN_STATE_CONNECTING_HPP
--- a/lib/irccd/conn-state-disconnected.cpp	Wed Aug 17 15:20:50 2016 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,36 +0,0 @@
-/*
- * conn-state-disconnected.cpp -- disconnected state
- *
- * 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 "conn-state-disconnected.hpp"
-
-namespace irccd {
-
-Connection::Status Connection::DisconnectedState::status() const noexcept
-{
-    return Disconnected;
-}
-
-void Connection::DisconnectedState::prepare(Connection &, fd_set &, fd_set &)
-{
-}
-
-void Connection::DisconnectedState::sync(Connection &, fd_set &, fd_set &)
-{
-}
-
-} // !irccd
--- a/lib/irccd/conn-state-disconnected.hpp	Wed Aug 17 15:20:50 2016 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,57 +0,0 @@
-/*
- * conn-state-disconnected.hpp -- disconnected state
- *
- * 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_CONN_STATE_DISCONNECTED_HPP
-#define IRCCD_CONN_STATE_DISCONNECTED_HPP
-
-/**
- * \file conn-state-disconnected.hpp
- * \brief Disconnected.
- */
-#include "conn-state.hpp"
-
-namespace irccd {
-
-/**
- * \brief Disconnected state.
- *
- * No-op state.
- *
- * This state does nothing, it is the default one and the last one.
- */
-class Connection::DisconnectedState : public Connection::State {
-public:
-    /**
-     * \copydoc State::status
-     */
-    Status status() const noexcept override;
-
-    /**
-     * \copydoc State::prepare
-     */
-    void prepare(Connection &cnt, fd_set &in, fd_set &out) override;
-
-    /**
-     * \copydoc State::sync
-     */
-    void sync(Connection &cnt, fd_set &in, fd_set &out) override;
-};
-
-} // !irccd
-
-#endif // !IRCCD_CONN_STATE_DISCONNECTED_HPP
--- a/lib/irccd/conn-state-ready.cpp	Wed Aug 17 15:20:50 2016 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,71 +0,0 @@
-/*
- * conn-state-ready.cpp -- connection is ready for I/O
- *
- * 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 "conn-state-ready.hpp"
-
-namespace irccd {
-
-namespace {
-
-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 &) {
-    }
-}
-
-} // !namespace
-
-Connection::Status Connection::ReadyState::status() const noexcept
-{
-    return Ready;
-}
-
-void Connection::ReadyState::prepare(Connection &cnx, fd_set &in, fd_set &out)
-{
-    FD_SET(cnx.m_socket.handle(), &in);
-
-    if (!cnx.m_output.empty())
-        FD_SET(cnx.m_socket.handle(), &out);
-}
-
-void Connection::ReadyState::sync(Connection &cnx, fd_set &in, fd_set &out)
-{
-    if (FD_ISSET(cnx.m_socket.handle(), &out))
-        cnx.syncOutput();
-
-    if (FD_ISSET(cnx.m_socket.handle(), &in))
-        cnx.syncInput();
-
-    std::string msg;
-
-    do {
-        msg = util::nextNetwork(cnx.m_input);
-
-        if (!msg.empty())
-            parse(cnx, msg);
-    } while (!msg.empty());
-}
-
-} // !irccd
--- a/lib/irccd/conn-state-ready.hpp	Wed Aug 17 15:20:50 2016 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,60 +0,0 @@
-/*
- * conn-state-ready.hpp -- connection is ready for I/O
- *
- * 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_CONN_STATE_READY_HPP
-#define IRCCD_CONN_STATE_READY_HPP
-
-/**
- * \file conn-state-ready.hpp
- * \brief Connection is ready.
- */
-
-#include "conn-state.hpp"
-
-namespace irccd {
-
-/**
- * \brief Ready state.
- *
- * This state is used when the connection to irccd is complete, including
- * irccd daemon verification and optional handshaking.
- *
- * It's the only state that may trigger onEvent and onResponse signals
- * from the Connection.
- */
-class Connection::ReadyState : public Connection::State {
-public:
-    /**
-     * \copydoc State::status
-     */
-    Status status() const noexcept override;
-
-    /**
-     * \copydoc State::prepare
-     */
-    void prepare(Connection &cnt, fd_set &in, fd_set &out) override;
-
-    /**
-     * \copydoc State::sync
-     */
-    void sync(Connection &cnt, fd_set &in, fd_set &out) override;
-};
-
-} // !irccd
-
-#endif // !IRCCD_CONN_STATE_READY_HPP
--- a/lib/irccd/conn-state.hpp	Wed Aug 17 15:20:50 2016 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,75 +0,0 @@
-/*
- * conn-state.hpp -- abstract state for Connection object
- *
- * 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_CONN_STATE_HPP
-#define IRCCD_CONN_STATE_HPP
-
-/**
- * \file conn-state.hpp
- * \brief State for Connection.
- */
-
-#include "connection.hpp"
-
-namespace irccd {
-
-/**
- * \brief Abstract state interface for Connection
- *
- * Abstract state interface for Connection.
- *
- * The Connection is event based, you should not throw exceptions from the
- * prepare or sync functions, instead you should change the Connection state
- * and emit the onDisconnect signal.
- */
-class Connection::State {
-public:
-    /**
-     * Return the state.
-     *
-     * \return the state
-     */
-    virtual Status status() const noexcept = 0;
-
-    /**
-     * Prepare the input and output sets.
-     *
-     * You should not change the connection state in this function.
-     *
-     * \param cnt the connection object
-     * \param in the input set
-     * \param out the output set
-     */
-    virtual void prepare(Connection &cnt, fd_set &in, fd_set &out) = 0;
-
-    /**
-     * Synchronize network I/O in the implementation.
-     *
-     * You should change the connection state using cnx.m_nextState
-     * if needed.
-     *
-     * \param cnt the connection object
-     * \param in the input set
-     * \param out the output set
-     */
-    virtual void sync(Connection &cnt, fd_set &in, fd_set &out) = 0;
-};
-
-} // !irccd
-
-#endif // !IRCCD_CONN_STATE_HPP
--- a/lib/irccd/connection.cpp	Wed Aug 17 15:20:50 2016 +0200
+++ b/lib/irccd/connection.cpp	Wed Aug 17 16:55:25 2016 +0200
@@ -18,15 +18,333 @@
 
 #include <stdexcept>
 
+#include <format.h>
+
 #include "connection.hpp"
-#include "conn-state-connecting.hpp"
-#include "conn-state-checking.hpp"
-#include "conn-state-disconnected.hpp"
 #include "util.hpp"
 
+using namespace fmt::literals;
+
 namespace irccd {
 
 /*
+ * Connection::State.
+ * ------------------------------------------------------------------
+ */
+
+class Connection::State {
+public:
+    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
+    {
+        return Disconnected;
+    }
+
+    void prepare(Connection &, fd_set &, fd_set &) {}
+    void sync(Connection &, fd_set &, fd_set &){}
+};
+
+/*
+ * 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
+    {
+        return Ready;
+    }
+
+    void prepare(Connection &cnx, fd_set &in, fd_set &out)
+    {
+        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)
+    {
+        if (FD_ISSET(cnx.m_socket.handle(), &out))
+            cnx.syncOutput();
+
+        if (FD_ISSET(cnx.m_socket.handle(), &in))
+            cnx.syncInput();
+
+        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.syncInput();
+
+        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
+    {
+        return Authenticating;
+    }
+
+    void prepare(Connection &cnt, fd_set &in, fd_set &out)
+    {
+        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)
+    {
+        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
+    {
+        return Checking;
+    }
+
+    void prepare(Connection &cnx, fd_set &in, fd_set &)
+    {
+        FD_SET(cnx.m_socket.handle(), &in);
+    }
+
+    void sync(Connection &cnx, fd_set &, fd_set &)
+    {
+        cnx.syncInput();
+
+        verify(cnx);
+    }
+};
+
+/*
+ * Connection::ConnectingState.
+ * ------------------------------------------------------------------
+ */
+
+class Connection::ConnectingState : public Connection::State {
+public:
+    Connection::Status status() const noexcept
+    {
+        return Connecting;
+    }
+
+    void prepare(Connection &cnx, fd_set &, fd_set &out)
+    {
+        FD_SET(cnx.m_socket.handle(), &out);
+    }
+
+    void sync(Connection &cnx, fd_set &, fd_set &out)
+    {
+        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.
  * ------------------------------------------------------------------
  */