# HG changeset patch # User David Demelier # Date 1471986126 -7200 # Node ID 7b54db12be5113740b4a485a5461bdb506d37626 # Parent 79d9840811a1c21005c156aef40eaf27efae48f6 Irccd: rename Connection to Client diff -r 79d9840811a1 -r 7b54db12be51 lib/irccd/CMakeSources.cmake --- 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 diff -r 79d9840811a1 -r 7b54db12be51 lib/irccd/client.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 + * + * 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 + +#include + +#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(); + 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(); + } catch (const std::exception &ex) { + cnt.m_state = std::make_unique(); + 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() != "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(); + else + cnx.m_stateNext = std::make_unique(); + + 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(); + 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(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()); + } + } +}; + +/* + * 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(); + 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(); + onDisconnect(ex.what()); + } +} + +Client::Client() + : m_state(std::make_unique()) +{ +} + +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(); + } catch (const net::WouldBlockError &) { + m_state = std::make_unique(); + } catch (const std::exception &ex) { + m_state = std::make_unique(); + 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(); + 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(); + 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(); + 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(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 diff -r 79d9840811a1 -r 7b54db12be51 lib/irccd/client.hpp --- /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 + * + * 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 +#include +#include + +#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 onConnect; + + /** + * onMessage + * --------------------------------------------------------------- + * + * A message from irccd was received. + */ + Signal onMessage; + + /** + * onDisconnect + * -------------------------------------------------------------- + * + * A fatal error occured resulting in disconnection. + */ + Signal 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 m_state; + std::unique_ptr 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 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 diff -r 79d9840811a1 -r 7b54db12be51 lib/irccd/cmd-watch.cpp --- 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"); diff -r 79d9840811a1 -r 7b54db12be51 lib/irccd/connection.cpp --- 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 - * - * 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 - -#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(); - 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(); - } catch (const std::exception &ex) { - cnt.m_state = std::make_unique(); - 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() != "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 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(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. - * ------------------------------------------------------------------ - */ - -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(); - 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(); - onDisconnect(ex.what()); - } -} - -Connection::Connection() - : m_state(std::make_unique()) -{ -} - -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(); - } catch (const net::WouldBlockError &) { - m_state = std::make_unique(); - } catch (const std::exception &ex) { - m_state = std::make_unique(); - 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(); - 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(); - 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(); - 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(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 diff -r 79d9840811a1 -r 7b54db12be51 lib/irccd/connection.hpp --- 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 - * - * 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 -#include -#include - -#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 onConnect; - - /** - * onMessage - * --------------------------------------------------------------- - * - * A message from irccd was received. - */ - Signal onMessage; - - /** - * onDisconnect - * -------------------------------------------------------------- - * - * A fatal error occured resulting in disconnection. - */ - Signal 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 m_state; - std::unique_ptr 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 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 diff -r 79d9840811a1 -r 7b54db12be51 lib/irccd/irccdctl.cpp --- 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 #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(); + m_connection = std::make_unique(); else - m_connection = std::make_unique(); + m_connection = std::make_unique(); } /* @@ -145,7 +145,7 @@ throw std::invalid_argument("missing path parameter"); m_address = net::local::create(it->value()); - m_connection = std::make_unique(); + m_connection = std::make_unique(); #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(); + m_connection = std::make_unique(); } /* @@ -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(); + m_connection = std::make_unique(); #else (void)options; diff -r 79d9840811a1 -r 7b54db12be51 lib/irccd/irccdctl.hpp --- 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 #include -#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 m_connection; + std::unique_ptr 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; }