# HG changeset patch # User David Demelier # Date 1541621158 -3600 # Node ID be2aef228613b3444529b4e750411692554e6347 # Parent 7145a3df4cb79e12069c912631d093e787b7fbe7 irccd: simplify irc class diff -r 7145a3df4cb7 -r be2aef228613 libirccd/irccd/daemon/irc.cpp --- a/libirccd/irccd/daemon/irc.cpp Wed Nov 07 12:55:00 2018 +0100 +++ b/libirccd/irccd/daemon/irc.cpp Wed Nov 07 21:05:58 2018 +0100 @@ -22,15 +22,34 @@ #include "irc.hpp" +using std::errc; +using std::flush; +using std::isspace; +using std::istreambuf_iterator; +using std::istringstream; +using std::move; +using std::ostream; +using std::string; +using std::string_view; +using std::vector; + using boost::asio::async_connect; using boost::asio::async_read_until; using boost::asio::async_write; +using boost::asio::io_service; +using boost::asio::ip::tcp; + +#if defined(IRCCD_HAVE_SSL) + +using boost::asio::ssl::stream_base; + +#endif namespace irccd::irc { -auto message::get(unsigned short index) const noexcept -> const std::string& +auto message::get(unsigned short index) const noexcept -> const string& { - static const std::string dummy; + static const string dummy; return (index >= args.size()) ? dummy : args[index]; } @@ -45,17 +64,17 @@ return a.front() == 0x01 && a.back() == 0x01; } -auto message::ctcp(unsigned short index) const -> std::string +auto message::ctcp(unsigned short index) const -> string { assert(is_ctcp(index)); return args[index].substr(1, args[index].size() - 1); } -auto message::parse(const std::string& line) -> message +auto message::parse(const string& line) -> message { - std::istringstream iss(line); - std::string prefix; + istringstream iss(line); + string prefix; if (line.empty()) return {}; @@ -68,19 +87,19 @@ } // Command. - std::string command; + string command; iss >> command; iss.ignore(1); // Arguments. - std::vector args; - std::istreambuf_iterator it(iss), end; + vector args; + istreambuf_iterator it(iss), end; while (it != end) { std::string arg; if (*it == ':') - arg = std::string(++it, end); + arg = string(++it, end); else { while (!isspace(*it) && it != end) arg.push_back(*it++); @@ -90,193 +109,192 @@ ++it; } - args.push_back(std::move(arg)); + args.push_back(move(arg)); } - return { std::move(prefix), std::move(command), std::move(args) }; + return { move(prefix), move(command), move(args) }; } -auto user::parse(std::string_view line) -> user +auto user::parse(string_view line) -> user { if (line.empty()) return { "", "" }; const auto pos = line.find("!"); - if (pos == std::string::npos) - return { std::string(line), "" }; + if (pos == string::npos) + return { string(line), "" }; - return { - std::string(line.substr(0, pos)), - std::string(line.substr(pos + 1)) - }; + return { string(line.substr(0, pos)), string(line.substr(pos + 1)) }; } -template -void connection::wrap_connect(Socket& socket, - const std::string& host, - const std::string& service, - const connect_handler& handler) noexcept +void connection::handshake(const connect_handler& handler) { - using boost::asio::ip::tcp; + if (!ssl_) { + handler({}); + return; + } - tcp::resolver::query query(host, service); +#if defined(IRCCD_HAVE_SSL) + ssl_socket_.async_handshake(stream_base::client, [handler] (auto code) { + handler(std::move(code)); + }); +#endif +} - resolver_.async_resolve(query, [&socket, handler] (auto code, auto it) { +void connection::connect(const tcp::resolver::results_type& endpoints, const connect_handler& handler) +{ + async_connect(socket_, endpoints, [this, handler] (auto code, auto) { if (code) { - handler(code); - } else { - async_connect(socket, it, [handler] (auto code, auto) { - handler(code); - }); + handler(move(code)); + return; } + + handshake(handler); }); } -template -void connection::wrap_recv(Socket& socket, const recv_handler& handler) noexcept +void connection::resolve(string_view hostname, string_view port, const connect_handler& handler) { - async_read_until(socket, input_, "\r\n", [this, handler] (auto code, auto xfer) { - if (xfer == 0U) - return handler(make_error_code(boost::asio::error::eof), message()); - else if (code) - return handler(std::move(code), message()); - - std::string data; + auto chain = [this, handler] (auto code, auto eps) { + if (code) + handler(std::move(code)); + else + connect(eps, std::move(handler)); + }; - try { - data = std::string( - boost::asio::buffers_begin(input_.data()), - boost::asio::buffers_begin(input_.data()) + xfer - 2 - ); - - input_.consume(xfer); - } catch (...) { - code = make_error_code(boost::system::errc::not_enough_memory); - } - - handler(code, code ? message() : message::parse(data)); - }); + if (ipv6_ && ipv4_) + resolver_.async_resolve(hostname, port, move(chain)); + else if (ipv6_) + resolver_.async_resolve(tcp::v6(), hostname, port, move(chain)); + else + resolver_.async_resolve(tcp::v4(), hostname, port, move(chain)); } -template -void connection::wrap_send(Socket& socket, const send_handler& handler) noexcept +connection::connection(io_service& service) + : service_(service) + , resolver_(service) { - boost::asio::async_write(socket, output_, [handler] (auto code, auto xfer) { - if (xfer == 0U) - return handler(make_error_code(boost::asio::error::eof)); +} - handler(code); - }); +void connection::use_ipv4(bool enable) noexcept +{ + ipv4_ = enable; } -void connection::connect(const std::string& host, - const std::string& service, - const connect_handler& handler) noexcept +void connection::use_ipv6(bool enable) noexcept +{ + ipv6_ = enable; +} + +void connection::use_ssl(bool enable) noexcept { + ssl_ = enable; +} + +void connection::connect(string_view hostname, string_view service, connect_handler handler) +{ +#if !defined(IRCCD_HAVE_SSL) + assert(!ssl_); +#endif #if !defined(NDEBUG) - assert(handler); assert(!is_connecting_); is_connecting_ = true; #endif + assert(handler); + assert(ipv4_ || ipv6_); - do_connect(host, service, [this, handler] (auto code) { + auto chain = [this, handler] (auto code) { #if !defined(NDEBUG) is_connecting_ = false; #endif (void)this; - handler(std::move(code)); - }); + + handler(move(code)); + }; + + resolve(hostname, service, move(chain)); } -void connection::recv(const recv_handler& handler) noexcept +void connection::recv(recv_handler handler) { #if !defined(NDEBUG) - assert(handler); assert(!is_receiving_); is_receiving_ = true; #endif - do_recv([this, handler] (auto code, auto message) { + auto chain = [this, handler] (auto code, auto xfer) { #if !defined(NDEBUG) is_receiving_ = false; #endif (void)this; - handler(std::move(code), std::move(message)); - }); + + if (code == boost::asio::error::not_found) + return handler(make_error_code(errc::argument_list_too_long), message()); + if (code == boost::asio::error::eof || xfer == 0) + return handler(make_error_code(errc::connection_reset), message()); + else if (code) + return handler(move(code), message()); + + string data; + + // 1. Convert the buffer safely. + try { + data = string( + buffers_begin(input_.data()), + buffers_begin(input_.data()) + xfer - 2 + ); + + input_.consume(xfer); + } catch (...) { + return handler(make_error_code(errc::not_enough_memory), message()); + } + + handler(move(code), message::parse(data)); + }; + +#if defined(IRCCD_HAVE_SSL) + if (ssl_) + async_read_until(ssl_socket_, input_, "\r\n", move(chain)); + else +#endif + async_read_until(socket_, input_, "\r\n", move(chain)); } -void connection::send(std::string message, const send_handler& handler) +void connection::send(string_view message, send_handler handler) { #if !defined(NDEBUG) - assert(handler); assert(!is_sending_); is_sending_ = true; #endif - std::ostream out(&output_); - - out << std::move(message); - out << "\r\n"; - - do_send([this, handler] (auto code) { + auto chain = [this, handler] (auto code, auto xfer) { #if !defined(NDEBUG) is_sending_ = false; #endif (void)this; - handler(std::move(code)); - }); -} + + if (code == boost::asio::error::eof || xfer == 0) + return handler(make_error_code(errc::connection_reset)); -void ip_connection::do_connect(const std::string& host, - const std::string& service, - const connect_handler& handler) noexcept -{ - wrap_connect(socket_, host, service, handler); -} + handler(move(code)); + }; -void ip_connection::do_recv(const recv_handler& handler) noexcept -{ - wrap_recv(socket_, handler); -} + ostream out(&output_); -void ip_connection::do_send(const send_handler& handler) noexcept -{ - wrap_send(socket_, handler); -} + out << message; + out << "\r\n"; + out << flush; #if defined(IRCCD_HAVE_SSL) - -void tls_connection::do_connect(const std::string& host, - const std::string& service, - const connect_handler& handler) noexcept -{ - using boost::asio::ssl::stream_base; - - wrap_connect(socket_.lowest_layer(), host, service, [this, handler] (auto code) { - if (code) { - handler(code); - } else { - socket_.async_handshake(stream_base::client, [handler] (auto code) { - handler(code); - }); - } - }); + if (ssl_) + async_write(ssl_socket_, output_, move(chain)); + else +#endif + async_write(socket_, output_, move(chain)); } -void tls_connection::do_recv(const recv_handler& handler) noexcept -{ - wrap_recv(socket_, handler); -} - -void tls_connection::do_send(const send_handler& handler) noexcept -{ - wrap_send(socket_, handler); -} - -#endif // !IRCCD_HAVE_SSL - } // !irccd::irc diff -r 7145a3df4cb7 -r be2aef228613 libirccd/irccd/daemon/irc.hpp --- a/libirccd/irccd/daemon/irc.hpp Wed Nov 07 12:55:00 2018 +0100 +++ b/libirccd/irccd/daemon/irc.hpp Wed Nov 07 21:05:58 2018 +0100 @@ -1286,85 +1286,38 @@ using send_handler = std::function; private: - boost::asio::ip::tcp::resolver resolver_; - boost::asio::streambuf input_; + boost::asio::io_context& service_; + boost::asio::ip::tcp::socket socket_{service_}; + boost::asio::ip::tcp::resolver resolver_{service_}; + boost::asio::streambuf input_{1024}; boost::asio::streambuf output_; + bool ipv4_{true}; + bool ipv6_{true}; + bool ssl_{false}; + +#if defined(IRCCD_HAVE_SSL) + boost::asio::ssl::context context_{boost::asio::ssl::context::tlsv12}; + boost::asio::ssl::stream ssl_socket_{socket_, context_}; +#endif + #if !defined(NDEBUG) bool is_connecting_{false}; bool is_receiving_{false}; bool is_sending_{false}; #endif -protected: - /** - * Use boost::asio::async_resolve and boost::asio::async_connect on the - * given real socket type. - * - * \param socket the socket - * \param host the hostname - * \param service the service or port number - * \param handler the non-null handler - */ - template - void wrap_connect(Socket& socket, - const std::string& host, - const std::string& service, - const connect_handler& handler) noexcept; - - /** - * Use boost::asio::asynd_read_until on the given real socket type. - * - * \param socket the socket - * \param handler the non-null handler - */ - template - void wrap_recv(Socket& socket, const recv_handler& handler) noexcept; - - /** - * Use boost::asio::asynd_write on the given real socket type. - * - * \param socket the socket - * \param handler the non-null handler - */ - template - void wrap_send(Socket& socket, const send_handler& handler) noexcept; - - /** - * Do the connection. - * - * Derived implementation may call wrap_connect on its underlying socket. - * - * \param host the hostname - * \param service the service or port number - * \param handler the non-null handler - */ - virtual void do_connect(const std::string& host, - const std::string& service, - const connect_handler& handler) noexcept = 0; - - /** - * Receive some data. - * - * \param handler the non-null handler - */ - virtual void do_recv(const recv_handler& handler) noexcept = 0; - - /** - * Send data. - * - * \param handler the non-null handler - */ - virtual void do_send(const send_handler& handler) noexcept = 0; + void handshake(const connect_handler&); + void connect(const boost::asio::ip::tcp::resolver::results_type&, const connect_handler&); + void resolve(std::string_view, std::string_view, const connect_handler&); public: /** * Default constructor. + * + * \param service the I/O service */ - inline connection(boost::asio::io_service& service) - : resolver_(service) - { - } + connection(boost::asio::io_service& service); /** * Virtual destructor defaulted. @@ -1372,17 +1325,38 @@ virtual ~connection() = default; /** + * Enable IPv4 + * + * \param enable true to enable + */ + void use_ipv4(bool enable = true) noexcept; + + /** + * Enable IPv6 + * + * \param enable true to enable + */ + void use_ipv6(bool enable = true) noexcept; + + /** + * Enable TLS. + * + * \pre IRCCD_HAVE_SSL must be defined + * \param enable true to enable + */ + void use_ssl(bool enable = true) noexcept; + + /** * Connect to the host. * * \pre handler the handler * \pre another connect operation must not be running - * \param host the host + * \pre ipv4 or ipv6 must be set + * \param hostname the hostname * \param service the service or port number * \param handler the non-null handler */ - void connect(const std::string& host, - const std::string& service, - const connect_handler& handler) noexcept; + void connect(std::string_view hostname, std::string_view service, connect_handler handler); /** * Start receiving data. @@ -1394,7 +1368,7 @@ * \pre handler != nullptr * \param handler the handler to call */ - void recv(const recv_handler& handler) noexcept; + void recv(recv_handler handler); /** * Start sending data. @@ -1407,91 +1381,9 @@ * \param message the raw message * \param handler the handler to call */ - void send(std::string message, const send_handler& handler); -}; - -/** - * \brief Clear TCP connection - */ -class ip_connection : public connection { -private: - boost::asio::ip::tcp::socket socket_; - -protected: - /** - * \copydoc connection::do_connect - */ - void do_connect(const std::string& host, - const std::string& service, - const connect_handler& handler) noexcept override; - - /** - * \copydoc connection::do_recv - */ - void do_recv(const recv_handler& handler) noexcept override; - - /** - * \copydoc connection::do_send - */ - void do_send(const send_handler& handler) noexcept override; - -public: - /** - * Constructor. - * - * \param service the io service - */ - inline ip_connection(boost::asio::io_service& service) - : connection(service) - , socket_(service) - { - } + void send(std::string_view message, send_handler handler); }; -#if defined(IRCCD_HAVE_SSL) - -/** - * \brief SSL connection - */ -class tls_connection : public connection { -private: - boost::asio::ssl::context context_; - boost::asio::ssl::stream socket_; - -protected: - /** - * \copydoc connection::do_connect - */ - void do_connect(const std::string& host, - const std::string& service, - const connect_handler& handler) noexcept override; - - /** - * \copydoc connection::do_recv - */ - void do_recv(const recv_handler& handler) noexcept override; - - /** - * \copydoc connection::do_send - */ - void do_send(const send_handler& handler) noexcept override; - -public: - /** - * Constructor. - * - * \param service the io service - */ - inline tls_connection(boost::asio::io_service& service) - : connection(service) - , context_(boost::asio::ssl::context::sslv23) - , socket_(service, context_) - { - } -}; - -#endif // !IRCCD_HAVE_SSL - } // !irccd::irc #endif // !IRCCD_IRC_HPP diff -r 7145a3df4cb7 -r be2aef228613 libirccd/irccd/daemon/server.cpp --- a/libirccd/irccd/daemon/server.cpp Wed Nov 07 12:55:00 2018 +0100 +++ b/libirccd/irccd/daemon/server.cpp Wed Nov 07 21:05:58 2018 +0100 @@ -664,18 +664,9 @@ (void)res_init(); #endif - if ((flags_ & options::ssl) == options::ssl) { -#if defined(IRCCD_HAVE_SSL) - conn_ = std::make_unique(service_); -#else - /* - * If SSL is not compiled in, the caller is responsible of not setting - * the flag. - */ - assert((flags_ & options::ssl) != options::ssl); -#endif - } else - conn_ = std::make_unique(service_); + // TODO: use_ipv4, use_ipv6. + conn_ = std::make_unique(service_); + conn_->use_ssl((flags_ & options::ssl) == options::ssl); jchannels_.clear(); state_ = state::connecting;