changeset 786:be2aef228613

irccd: simplify irc class
author David Demelier <markand@malikania.fr>
date Wed, 07 Nov 2018 21:05:58 +0100
parents 7145a3df4cb7
children 86c9d58ed3ee
files libirccd/irccd/daemon/irc.cpp libirccd/irccd/daemon/irc.hpp libirccd/irccd/daemon/server.cpp
diffstat 3 files changed, 193 insertions(+), 292 deletions(-) [+]
line wrap: on
line diff
--- 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<std::string> args;
-	std::istreambuf_iterator<char> it(iss), end;
+	vector<std::string> args;
+	istreambuf_iterator<char> 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 <typename Socket>
-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 <typename Socket>
-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 <typename Socket>
-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
--- 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<void (std::error_code)>;
 
 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<boost::asio::ip::tcp::socket&> 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 <typename Socket>
-	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 <typename Socket>
-	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 <typename Socket>
-	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<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 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
--- 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<irc::tls_connection>(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<irc::ip_connection>(service_);
+	// TODO: use_ipv4, use_ipv6.
+	conn_ = std::make_unique<irc::connection>(service_);
+	conn_->use_ssl((flags_ & options::ssl) == options::ssl);
 
 	jchannels_.clear();
 	state_ = state::connecting;