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