# HG changeset patch # User David Demelier # Date 1471445725 -7200 # Node ID b25176b3bb8058f45a2211d2dbd6fc039678bdf0 # Parent 554a542f0a8206f6a4cf627a6bfc5317c4133ac7 Irccd: unify connection and the states in connection.cpp diff -r 554a542f0a82 -r b25176b3bb80 lib/irccd/CMakeSources.cmake --- 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 diff -r 554a542f0a82 -r b25176b3bb80 lib/irccd/conn-state-auth.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 - * - * 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(); - 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(); - } catch (const std::exception &ex) { - cnt.m_state = std::make_unique(); - 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 diff -r 554a542f0a82 -r b25176b3bb80 lib/irccd/conn-state-auth.hpp --- 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 - * - * 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 diff -r 554a542f0a82 -r b25176b3bb80 lib/irccd/conn-state-checking.cpp --- 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 - * - * 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 - -#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() != "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(); - else - cnx.m_stateNext = std::make_unique(); - - 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(); - 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 diff -r 554a542f0a82 -r b25176b3bb80 lib/irccd/conn-state-checking.hpp --- 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 - * - * 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 diff -r 554a542f0a82 -r b25176b3bb80 lib/irccd/conn-state-connecting.cpp --- 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 - * - * 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(SOL_SOCKET, SO_ERROR); - - if (errc != 0) { - cnx.m_stateNext = std::make_unique(); - cnx.onDisconnect(net::error(errc)); - } else - cnx.m_stateNext = std::make_unique(); - } catch (const std::exception &ex) { - cnx.m_stateNext = std::make_unique(); - cnx.onDisconnect(ex.what()); - } -} - -} // !irccd diff -r 554a542f0a82 -r b25176b3bb80 lib/irccd/conn-state-connecting.hpp --- 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 - * - * 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 diff -r 554a542f0a82 -r b25176b3bb80 lib/irccd/conn-state-disconnected.cpp --- 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 - * - * 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 diff -r 554a542f0a82 -r b25176b3bb80 lib/irccd/conn-state-disconnected.hpp --- 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 - * - * 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 diff -r 554a542f0a82 -r b25176b3bb80 lib/irccd/conn-state-ready.cpp --- 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 - * - * 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 diff -r 554a542f0a82 -r b25176b3bb80 lib/irccd/conn-state-ready.hpp --- 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 - * - * 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 diff -r 554a542f0a82 -r b25176b3bb80 lib/irccd/conn-state.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 - * - * 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 diff -r 554a542f0a82 -r b25176b3bb80 lib/irccd/connection.cpp --- 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 +#include + #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(); + 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(); + } catch (const std::exception &ex) { + cnt.m_state = std::make_unique(); + 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() != "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(); + else + cnx.m_stateNext = std::make_unique(); + + 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(); + 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(SOL_SOCKET, SO_ERROR); + + if (errc != 0) { + cnx.m_stateNext = std::make_unique(); + cnx.onDisconnect(net::error(errc)); + } else + cnx.m_stateNext = std::make_unique(); + } catch (const std::exception &ex) { + cnx.m_stateNext = std::make_unique(); + cnx.onDisconnect(ex.what()); + } + } +}; + +/* * Connection. * ------------------------------------------------------------------ */