Mercurial > irccd
changeset 780:560b62f6b0a7
Core: rework socket layer, closes #939 @6h
Rework the ip_connector and ip_acceptor to be more easy to use. Also,
ip_connector will now use a resolver to find appropriate endpoints.
Bring back full support for IPv6/IPv4 with all possible combinations.
The tls_stream class now owns a shared ssl::context that is copied from the
acceptor or the connector. The tls_connector and tls_acceptor wraps basic ones
for convenience and simplicity.
Irccd and irccdctl now support local SSL sockets.
author | David Demelier <markand@malikania.fr> |
---|---|
date | Sun, 04 Nov 2018 17:26:05 +0100 |
parents | 317c66a131be |
children | baf9258e9cbb |
files | CHANGES.md MIGRATING.md irccdctl/main.cpp libirccd-core/CMakeLists.txt libirccd-core/irccd/acceptor.hpp libirccd-core/irccd/connector.hpp libirccd-core/irccd/socket_acceptor.hpp libirccd-core/irccd/socket_connector.hpp libirccd-core/irccd/socket_stream.hpp libirccd-core/irccd/stream.hpp libirccd-core/irccd/tls_acceptor.hpp libirccd-core/irccd/tls_connector.hpp libirccd-core/irccd/tls_stream.hpp libirccd-ctl/irccd/ctl/controller.cpp libirccd-ctl/irccd/ctl/controller.hpp libirccd-test/irccd/test/cli_fixture.cpp libirccd-test/irccd/test/command_fixture.cpp libirccd/irccd/daemon/server.cpp libirccd/irccd/daemon/transport_client.cpp libirccd/irccd/daemon/transport_client.hpp libirccd/irccd/daemon/transport_util.cpp tests/src/libcommon/stream/CMakeLists.txt tests/src/libcommon/stream/main.cpp |
diffstat | 23 files changed, 1348 insertions(+), 1048 deletions(-) [+] |
line wrap: on
line diff
--- a/CHANGES.md Thu Nov 01 10:34:21 2018 +0100 +++ b/CHANGES.md Sun Nov 04 17:26:05 2018 +0100 @@ -12,7 +12,8 @@ standard paths for both irccd and plugins (#611), - If Mercurial is found, the version is bundled in `irccd --version`, - Irccd no longer supports uid, gid, pid and daemon features (#846). -- Sections `[identity]` and `[server]` have been merged (#905). +- Sections `[identity]` and `[server]` have been merged (#905), +- Local transports support SSL too (#939). Irccd test:
--- a/MIGRATING.md Thu Nov 01 10:34:21 2018 +0100 +++ b/MIGRATING.md Sun Nov 04 17:26:05 2018 +0100 @@ -9,7 +9,8 @@ ### Irccdctl - The functions `server-cnotice` and `server-cmode` have been removed, use - `server-notice` and `server-mode` instead. + `server-notice` and `server-mode` instead, + - The option `connect.host` has been renamed to `connect.hostname` (#941). ### Plugins
--- a/irccdctl/main.cpp Thu Nov 01 10:34:21 2018 +0100 +++ b/irccdctl/main.cpp Sun Nov 04 17:26:05 2018 +0100 @@ -16,8 +16,6 @@ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ -// TODO: don't create a temporary list of endpoints. - #include <irccd/sysconfig.hpp> #include <iostream> @@ -28,16 +26,12 @@ #include <boost/predef/os.h> #include <irccd/config.hpp> +#include <irccd/connector.hpp> #include <irccd/json_util.hpp> #include <irccd/options.hpp> -#include <irccd/socket_connector.hpp> #include <irccd/string_util.hpp> #include <irccd/system.hpp> -#if defined(IRCCD_HAVE_SSL) -# include <irccd/tls_connector.hpp> -#endif - #include <irccd/daemon/transport_server.hpp> #include <irccd/ctl/controller.hpp> @@ -48,17 +42,7 @@ using boost::format; using boost::str; -using boost::asio::ip::tcp; - -#if !BOOST_OS_WINDOWS - -using boost::asio::local::stream_protocol; - -#endif - -namespace irccd { - -namespace ctl { +namespace irccd::ctl { namespace { @@ -86,25 +70,6 @@ } /* - * resolve - * ------------------------------------------------------------------ - * - * Block unless host/port has been resolved. - */ -auto resolve(const std::string& host, const std::string& name) -> std::vector<tcp::endpoint> -{ - std::vector<tcp::endpoint> endpoints; - - tcp::resolver resolver(service); - tcp::resolver::query query(host, name); - - for (auto it = resolver.resolve(query); it != tcp::resolver::iterator(); ++it) - endpoints.push_back(*it); - - return endpoints; -} - -/* * read_connect_ip * ------------------------------------------------------------------- * @@ -112,35 +77,49 @@ * * [connect] * type = "ip" - * host = "ip or hostname" + * hostname = "ip or hostname" * port = "port number or service" - * domain = "ipv4 or ipv6" (Optional, default: ipv4) + * family = "ipv4, ipv6" (Optional, default: ipv4) * ssl = true | false */ auto read_connect_ip(const ini::section& sc) -> std::unique_ptr<connector> { - const auto host = sc.get("host").get_value(); + const auto hostname = sc.get("hostname").get_value(); const auto port = sc.get("port").get_value(); + bool ipv4 = true; + bool ipv6 = true; - if (host.empty()) + if (auto it = sc.find("family"); it != sc.end()) { + ipv4 = ipv6 = false; + + for (auto v : *it) { + if (v == "ipv4") + ipv4 = true; + else if (v == "ipv6") + ipv6 = true; + } + } + + if (!ipv4 && !ipv6) + throw transport_error(transport_error::invalid_family); + if (hostname.empty()) throw transport_error(transport_error::invalid_hostname); if (port.empty()) throw transport_error(transport_error::invalid_port); - const auto endpoints = resolve(host, port); - if (string_util::is_boolean(sc.get("ssl").get_value())) { #if defined(IRCCD_HAVE_SSL) // TODO: support more parameters. - boost::asio::ssl::context ctx(boost::asio::ssl::context::sslv23); + boost::asio::ssl::context ctx(boost::asio::ssl::context::tlsv12); - return std::make_unique<tls_connector<>>(std::move(ctx), service, endpoints); + return std::make_unique<tls_ip_connector>(std::move(ctx), + service, hostname, port, ipv4, ipv6); #else throw std::runtime_error("SSL disabled"); #endif } - return std::make_unique<ip_connector>(service, endpoints); + return std::make_unique<ip_connector>(service, hostname, port, ipv4, ipv6); } /* @@ -163,6 +142,17 @@ if (it == sc.end()) throw std::invalid_argument("missing path parameter"); + if (string_util::is_boolean(sc.get("ssl").get_value())) { +#if defined(IRCCD_HAVE_SSL) + // TODO: support more parameters. + boost::asio::ssl::context ctx(boost::asio::ssl::context::tlsv12); + + return std::make_unique<tls_local_connector>(std::move(ctx), service, it->get_value()); +#else + throw std::runtime_error("SSL disabled"); +#endif + } + return std::make_unique<local_connector>(service, it->get_value()); #else (void)sc; @@ -291,7 +281,7 @@ * -h host or ip * -p port */ -auto parse_connect_ip(const option::result& options) -> std::unique_ptr<connector> +auto parse_connect_ip(std::string_view type, const option::result& options) -> std::unique_ptr<connector> { option::result::const_iterator it; @@ -299,7 +289,7 @@ if ((it = options.find("-h")) == options.end() && (it = options.find("--host")) == options.end()) throw transport_error(transport_error::invalid_hostname); - const auto host = it->second; + const auto hostname = it->second; // Port (-p or --port). if ((it = options.find("-p")) == options.end() && (it = options.find("--port")) == options.end()) @@ -307,7 +297,11 @@ const auto port = it->second; - return std::make_unique<ip_connector>(service, resolve(host, port)); + // Type (-t or --type). + const auto ipv4 = type == "ip"; + const auto ipv6 = type == "ipv6"; + + return std::make_unique<ip_connector>(service, hostname, port, ipv4, ipv6); } /* @@ -352,7 +346,7 @@ std::unique_ptr<connector> connector; if (it->second == "ip" || it->second == "ipv6") - connector = parse_connect_ip(options); + connector = parse_connect_ip(it->second, options); if (it->second == "unix") connector = parse_connect_local(options); else @@ -483,9 +477,9 @@ if (verbose) { const json_util::deserializer doc(info); - const auto major = doc.get<int>("/major"); - const auto minor = doc.get<int>("/minor"); - const auto patch = doc.get<int>("/patch"); + const auto major = doc.get<int>("major"); + const auto minor = doc.get<int>("minor"); + const auto patch = doc.get<int>("patch"); if (!major || !minor || !patch) std::cout << "connected to irccd (unknown version)" << std::endl; @@ -514,9 +508,7 @@ } // !namespace -} // !ctl - -} // !irccd +} // !irccd::ctl int main(int argc, char** argv) {
--- a/libirccd-core/CMakeLists.txt Thu Nov 01 10:34:21 2018 +0100 +++ b/libirccd-core/CMakeLists.txt Sun Nov 04 17:26:05 2018 +0100 @@ -30,15 +30,9 @@ ${libirccd-core_SOURCE_DIR}/irccd/ini_util.hpp ${libirccd-core_SOURCE_DIR}/irccd/json_util.hpp ${libirccd-core_SOURCE_DIR}/irccd/options.hpp - ${libirccd-core_SOURCE_DIR}/irccd/socket_acceptor.hpp - ${libirccd-core_SOURCE_DIR}/irccd/socket_connector.hpp - ${libirccd-core_SOURCE_DIR}/irccd/socket_stream.hpp ${libirccd-core_SOURCE_DIR}/irccd/stream.hpp ${libirccd-core_SOURCE_DIR}/irccd/string_util.hpp ${libirccd-core_SOURCE_DIR}/irccd/system.hpp - ${libirccd-core_SOURCE_DIR}/irccd/tls_acceptor.hpp - ${libirccd-core_SOURCE_DIR}/irccd/tls_connector.hpp - ${libirccd-core_SOURCE_DIR}/irccd/tls_stream.hpp ${libirccd-core_SOURCE_DIR}/irccd/xdg.hpp )
--- a/libirccd-core/irccd/acceptor.hpp Thu Nov 01 10:34:21 2018 +0100 +++ b/libirccd-core/irccd/acceptor.hpp Sun Nov 04 17:26:05 2018 +0100 @@ -24,13 +24,23 @@ * \brief Abstract stream acceptor interface. */ +#include <irccd/sysconfig.hpp> + +#include <cassert> #include <functional> #include <memory> #include <system_error> -namespace irccd { +#include <boost/asio.hpp> +#include <boost/filesystem/path.hpp> -class stream; +#if defined(IRCCD_HAVE_SSL) +# include <boost/asio/ssl.hpp> +#endif + +#include "stream.hpp" + +namespace irccd { /** * \brief Abstract stream acceptor interface. @@ -69,6 +79,422 @@ virtual void accept(handler handler) = 0; }; +// {{{ basic_socket_acceptor + +/** + * \brief Convenient acceptor owner. + */ +template <typename Acceptor> +class basic_socket_acceptor : public acceptor { +public: + /** + * Underlying socket type. + */ + using socket_type = typename Acceptor::protocol_type::socket; + +private: +#if !defined(NDEBUG) + bool is_accepting_{false}; +#endif + +protected: + /** + * \brief The I/O context. + */ + boost::asio::io_context& service_; + + /** + * \brief The underlying acceptor. + */ + Acceptor acceptor_; + +public: + /** + * Construct a basic_socket_acceptor. + * + * \param service the I/O context + */ + basic_socket_acceptor(boost::asio::io_context& service); + + /** + * Construct a basic_socket_acceptor with a already bound native + * acceptor. + * + * \param service the I/O context + * \param acceptor the acceptor + */ + basic_socket_acceptor(boost::asio::io_context& service, Acceptor acceptor) noexcept; + + /** + * Get the I/O context. + * + * \return the context + */ + auto get_service() const noexcept -> const boost::asio::io_context&; + + /** + * Overloaded function. + * + * \return the context + */ + auto get_service() noexcept -> boost::asio::io_context&; + + /** + * Get the underlying acceptor. + * + * \return the acceptor + */ + auto get_acceptor() const noexcept -> const Acceptor&; + + /** + * Overloaded function. + * + * \return the acceptor + */ + auto get_acceptor() noexcept -> Acceptor&; + + /** + * Accept a new client. + * + * \pre another accept call must not be running + * \param sc the socket type + * \param handler the handler + * \note implemented for SocketAcceptor concept + */ + template <typename Socket, typename Handler> + void accept(Socket& sc, Handler handler); +}; + +template <typename Acceptor> +inline basic_socket_acceptor<Acceptor>::basic_socket_acceptor(boost::asio::io_context& service) + : service_(service) + , acceptor_(service) +{ +} + +template <typename Acceptor> +inline basic_socket_acceptor<Acceptor>::basic_socket_acceptor(boost::asio::io_context& service, Acceptor acceptor) noexcept + : service_(service) + , acceptor_(std::move(acceptor)) +{ +} + +template <typename Acceptor> +inline auto basic_socket_acceptor<Acceptor>::get_service() const noexcept -> const boost::asio::io_context& +{ + return service_; +} + +template <typename Acceptor> +inline auto basic_socket_acceptor<Acceptor>::get_service() noexcept -> boost::asio::io_context& +{ + return service_; +} + +template <typename Acceptor> +inline auto basic_socket_acceptor<Acceptor>::get_acceptor() const noexcept -> const Acceptor& +{ + return acceptor_; +} + +template <typename Acceptor> +inline auto basic_socket_acceptor<Acceptor>::get_acceptor() noexcept -> Acceptor& +{ + return acceptor_; +} + +template <typename Acceptor> +template <typename Socket, typename Handler> +inline void basic_socket_acceptor<Acceptor>::accept(Socket& sc, Handler handler) +{ +#if !defined(NDEBUG) + assert(!is_accepting_); + + is_accepting_ = true; +#endif + + assert(acceptor_.is_open()); + + acceptor_.async_accept(sc, [this, handler] (auto code) { +#if !defined(NDEBUG) + is_accepting_ = false; +#endif + (void)this; + handler(std::move(code)); + }); +} + +// }}} + +// {{{ ip_acceptor + +/** + * \brief TCP/IP acceptor. + */ +class ip_acceptor : public basic_socket_acceptor<boost::asio::ip::tcp::acceptor> { +private: + void open(bool ipv4, bool ipv6); + void set(bool ipv4, bool ipv6); + void bind(const std::string& address, std::uint16_t port, bool ipv4, bool ipv6); + +public: + /** + * \pre at least ipv4 or ipv6 must be true + * \param service the I/O service + * \param address the address to bind or * for any + * \param port the port number + * \param ipv4 enable ipv4 + * \param ipv6 enable ipv6 + */ + ip_acceptor(boost::asio::io_context& service, + std::string address, + std::uint16_t port, + bool ipv4 = true, + bool ipv6 = true); + + /** + * Inherited constructors. + */ + using basic_socket_acceptor::basic_socket_acceptor; + + /** + * Inherited functions. + */ + using basic_socket_acceptor::accept; + + /** + * \copydoc acceptor::accept + */ + void accept(handler handler) override; +}; + +inline void ip_acceptor::open(bool ipv4, bool ipv6) +{ + using boost::asio::ip::tcp; + + if (ipv6) + acceptor_.open(tcp::v6()); + else + acceptor_.open(tcp::v4()); +} + +inline void ip_acceptor::set(bool ipv4, bool ipv6) +{ + using boost::asio::socket_base; + using boost::asio::ip::v6_only; + + if (ipv6) + acceptor_.set_option(v6_only(!ipv4)); + + acceptor_.set_option(socket_base::reuse_address(true)); +} + +inline void ip_acceptor::bind(const std::string& address, std::uint16_t port, bool ipv4, bool ipv6) +{ + using boost::asio::ip::make_address_v4; + using boost::asio::ip::make_address_v6; + using boost::asio::ip::tcp; + + tcp::endpoint ep; + + if (address == "*") + ep = tcp::endpoint(ipv6 ? tcp::v6() : tcp::v4(), port); + else if (ipv6) + ep = tcp::endpoint(make_address_v6(address), port); + else + ep = tcp::endpoint(make_address_v4(address), port); + + acceptor_.bind(ep); + acceptor_.listen(); +} + +inline ip_acceptor::ip_acceptor(boost::asio::io_context& service, + std::string address, + std::uint16_t port, + bool ipv4, + bool ipv6) + : basic_socket_acceptor(service) +{ + open(ipv4, ipv6); + set(ipv4, ipv6); + bind(address, port, ipv4, ipv6); +} + +inline void ip_acceptor::accept(handler handler) +{ + auto stream = std::make_shared<ip_stream>(service_); + + basic_socket_acceptor::accept(stream->get_socket(), [handler, stream] (auto code) { + if (code) + handler(std::move(code), nullptr); + else + handler(std::move(code), std::move(stream)); + }); +} + +// }}} + +// {{{ local_acceptor + +#if defined(BOOST_ASIO_HAS_LOCAL_SOCKETS) + +class local_acceptor : public basic_socket_acceptor<boost::asio::local::stream_protocol::acceptor> { +public: + /** + * Construct a local acceptor. + * + * \param service the I/O service + * \param path the unix socket file + */ + local_acceptor(boost::asio::io_context& service, + const boost::filesystem::path& path); + + /** + * Inherited constructors. + */ + using basic_socket_acceptor::basic_socket_acceptor; + + /** + * Inherited functions. + */ + using basic_socket_acceptor::accept; + + /** + * \copydoc acceptor::accept + */ + void accept(handler handler) override; +}; + +inline local_acceptor::local_acceptor(boost::asio::io_context& service, + const boost::filesystem::path& path) + : basic_socket_acceptor(service) +{ + using boost::asio::socket_base; + + std::remove(path.string().c_str()); + + acceptor_.open(); + acceptor_.set_option(socket_base::reuse_address(true)); + acceptor_.bind({ path.string() }); + acceptor_.listen(); +} + +inline void local_acceptor::accept(handler handler) +{ + auto stream = std::make_shared<local_stream>(service_); + + basic_socket_acceptor::accept(stream->get_socket(), [handler, stream] (auto code) { + if (code) + handler(std::move(code), nullptr); + else + handler(std::move(code), std::move(stream)); + }); +} + +#endif + +// }}} + +// {{{ tls_acceptor + +#if defined(IRCCD_HAVE_SSL) + +/** + * \brief TLS/SSL acceptors. + * \tparam SocketAcceptor the socket connector (e.g. ip_acceptor) + * + * Wrap a SocketAcceptor object. + * + * The SocketAcceptor object must have the following types: + * + * ```cpp + * using socket_type = implementation-defined + * ``` + * + * The following function: + * + * ```cpp + * template <typename Handler> + * void accept(socket_type& sc, Handler handler); + * + * auto get_context() -> boost::asio::io_context& + * ``` + * + * The Handler callback must have the signature + * `void f(const std::error_code&)`. + */ +template <typename SocketAcceptor> +class tls_acceptor : public acceptor { +private: + using socket_type = typename SocketAcceptor::socket_type; + + std::shared_ptr<boost::asio::ssl::context> context_; + SocketAcceptor acceptor_; + +public: + /** + * Construct a secure layer transport server. + * + * \param context the SSL context + * \param args the arguments to SocketAcceptor constructor + */ + template <typename... Args> + tls_acceptor(boost::asio::ssl::context context, Args&&... args); + + /** + * \copydoc acceptor::accept + */ + void accept(handler handler) override; +}; + +template <typename SocketAcceptor> +template <typename... Args> +inline tls_acceptor<SocketAcceptor>::tls_acceptor(boost::asio::ssl::context context, Args&&... args) + : context_(std::make_shared<boost::asio::ssl::context>(std::move(context))) + , acceptor_(std::forward<Args>(args)...) +{ +} + +template <typename SocketAcceptor> +inline void tls_acceptor<SocketAcceptor>::accept(handler handler) +{ + auto client = std::make_shared<tls_stream<socket_type>>(acceptor_.get_service(), context_); + + acceptor_.accept(client->get_socket().lowest_layer(), [handler, client] (auto code) { + using boost::asio::ssl::stream_base; + + if (code) { + handler(std::move(code), nullptr); + return; + } + + client->get_socket().async_handshake(stream_base::server, [handler, client] (auto code) { + if (code) + handler(std::move(code), nullptr); + else + handler(std::move(code), std::move(client)); + }); + }); +} + +/** + * \brief Convenient alias. + */ +using tls_ip_acceptor = tls_acceptor<ip_acceptor>; + +#if defined(BOOST_ASIO_HAS_LOCAL_SOCKETS) + +/** + * \brief Convenient alias. + */ +using tls_local_acceptor = tls_acceptor<local_acceptor>; + +#endif // !BOOST_ASIO_HAS_LOCAL_SOCKETS + +#endif // !IRCCD_HAVE_SSL + +// }}} + } // !irccd #endif // !IRCCD_ACCEPTOR_HPP
--- a/libirccd-core/irccd/connector.hpp Thu Nov 01 10:34:21 2018 +0100 +++ b/libirccd-core/irccd/connector.hpp Sun Nov 04 17:26:05 2018 +0100 @@ -24,13 +24,22 @@ * \brief Abstract connection interface. */ +#include <cassert> #include <functional> #include <memory> #include <system_error> -namespace irccd { +#include <boost/asio.hpp> + +#if defined(IRCCD_HAVE_SSL) +# include <boost/asio/ssl.hpp> +#endif -class stream; +#include <boost/filesystem/path.hpp> + +#include "stream.hpp" + +namespace irccd { /** * \brief Abstract connection interface. @@ -70,6 +79,357 @@ virtual void connect(handler handler) = 0; }; +// {{{ socket_connector_base + +/** + * \brief Provide convenient functions for connectors. + */ +class socket_connector_base : public connector { +protected: + boost::asio::io_context& service_; + +public: + /** + * Construct the connector + * + * \param service the service + */ + socket_connector_base(boost::asio::io_context& service); + + /** + * Get the I/O service. + * + * \return the service + */ + auto get_service() const noexcept -> const boost::asio::io_context&; + + /** + * Overloaded function. + * + * \return the service + */ + auto get_service() noexcept -> boost::asio::io_context&; +}; + +inline socket_connector_base::socket_connector_base(boost::asio::io_context& service) + : service_(service) +{ +} + +inline auto socket_connector_base::get_service() const noexcept -> const boost::asio::io_context& +{ + return service_; +} + +inline auto socket_connector_base::get_service() noexcept -> boost::asio::io_context& +{ + return service_; +} + +// }}} + +// {{{ ip_connector + +/** + * \brief TCP/IP connector. + */ +class ip_connector : public socket_connector_base { +public: + /** + * Underlying socket type. + */ + using socket_type = boost::asio::ip::tcp::socket; + +private: + boost::asio::ip::tcp::resolver resolver_; + + std::string hostname_; + std::string port_; + + bool ipv4_; + bool ipv6_; + +#if !defined(NDEBUG) + bool is_connecting_{false}; +#endif + + template <typename Handler> + void resolve(Handler handler); + +public: + /** + * Construct the TCP/IP connector. + * + * \pre at least ipv4 or ipv6 must be true + * \param service the I/O context + * \param hostname the hostname + * \param port the port or service name + * \param ipv4 enable IPv4 + * \param ipv6 enable IPv6 + */ + ip_connector(boost::asio::io_context& service, + std::string hostname, + std::string port, + bool ipv4 = true, + bool ipv6 = true) noexcept; + + /** + * Connect to the given socket. + * + * \param sc the socket type + * \param handler the handler + * \note implemented for SocketConnector concept + */ + template <typename Socket, typename Handler> + void connect(Socket& sc, Handler handler); + + /** + * \copydoc connector::connect + */ + void connect(handler handler); +}; + +template <typename Handler> +inline void ip_connector::resolve(Handler handler) +{ + using boost::asio::ip::tcp; + + if (ipv6_ && ipv4_) + resolver_.async_resolve(hostname_, port_, handler); + else if (ipv6_) + resolver_.async_resolve(tcp::v6(), hostname_, port_, handler); + else + resolver_.async_resolve(tcp::v4(), hostname_, port_, handler); +} + +inline ip_connector::ip_connector(boost::asio::io_context& service, + std::string host, + std::string port, + bool ipv4, + bool ipv6) noexcept + : socket_connector_base(service) + , resolver_(service) + , hostname_(std::move(host)) + , port_(std::move(port)) + , ipv4_(ipv4) + , ipv6_(ipv6) +{ + assert(!hostname_.empty()); + assert(!port_.empty()); + assert(ipv4 || ipv6); +} + +template <typename Socket, typename Handler> +inline void ip_connector::connect(Socket& sc, Handler handler) +{ +#if !defined(NDEBUG) + assert(!is_connecting_); + + is_connecting_ = true; +#endif + + resolve([this, &sc, handler] (auto code, auto res) { +#if !defined(NDEBUG) + is_connecting_ = false; +#endif + (void)this; + + if (code) { + handler(std::move(code)); + return; + } + + async_connect(sc, res, [handler] (auto code, auto) { + handler(std::move(code)); + }); + }); +} + +inline void ip_connector::connect(handler handler) +{ + auto stream = std::make_shared<ip_stream>(service_); + + connect(stream->get_socket(), [handler, stream] (auto code) { + if (code) + handler(std::move(code), nullptr); + else + handler(std::move(code), std::move(stream)); + }); +} + +// }}} + +// {{{ local_connector + +#if defined(BOOST_ASIO_HAS_LOCAL_SOCKETS) + +/** + * \brief Unix domain connector. + */ +class local_connector : public socket_connector_base { +public: + /** + * Underlying socket type. + */ + using socket_type = boost::asio::local::stream_protocol::socket; + +private: + boost::filesystem::path path_; + +#if !defined(NDEBUG) + bool is_connecting_{false}; +#endif + +public: + /** + * Construct a local connector. + * + * \param service the service + * \param path the path to the file + */ + local_connector(boost::asio::io_context& service, + boost::filesystem::path path) noexcept; + + /** + * Connect to the given socket. + * + * \param sc the socket type + * \param handler the handler + * \note implemented for SocketConnector concept + */ + template <typename Socket, typename Handler> + void connect(Socket& sc, Handler handler) noexcept; + + /** + * \copydoc connector::connect + */ + void connect(handler handler); +}; + +inline local_connector::local_connector(boost::asio::io_context& service, + boost::filesystem::path path) noexcept + : socket_connector_base(service) + , path_(std::move(path)) +{ +} + +template <typename Socket, typename Handler> +inline void local_connector::connect(Socket& sc, Handler handler) noexcept +{ +#if !defined(NDEBUG) + assert(!is_connecting_); + + is_connecting_ = true; +#endif + + sc.async_connect({ path_.string() }, [this, handler] (auto code) { +#if !defined(NDEBUG) + is_connecting_ = false; +#endif + (void)this; + handler(std::move(code)); + }); +} + +inline void local_connector::connect(handler handler) +{ + auto stream = std::make_shared<local_stream>(service_); + + connect(stream->get_socket(), [handler, stream] (auto code) { + if (code) + handler(std::move(code), nullptr); + else + handler(std::move(code), std::move(stream)); + }); +} + +#endif // !BOOST_ASIO_HAS_LOCAL_SOCKETS + +// }}} + +// {{{ tls_connector + +#if defined(IRCCD_HAVE_SSL) + +/** + * \brief TLS/SSL connectors. + * \tparam SocketConnector the socket connector (e.g. ip_connector) + */ +template <typename SocketConnector> +class tls_connector : public connector { +public: + using socket_type = typename SocketConnector::socket_type; + +private: + std::shared_ptr<boost::asio::ssl::context> context_; + SocketConnector connector_; + +public: + /** + * Construct a secure layer transport server. + * + * \param context the SSL context + * \param args the arguments to SocketConnector constructor + */ + template <typename... Args> + tls_connector(boost::asio::ssl::context context, Args&&... args); + + /** + * \copydoc socket_connector::connect + */ + void connect(handler handler) override; +}; + +template <typename SocketConnector> +template <typename... Args> +inline tls_connector<SocketConnector>::tls_connector(boost::asio::ssl::context context, Args&&... args) + : context_(std::make_shared<boost::asio::ssl::context>(std::move(context))) + , connector_(std::forward<Args>(args)...) +{ +} + +template <typename SocketConnector> +inline void tls_connector<SocketConnector>::connect(handler handler) +{ + using boost::asio::ssl::stream_base; + + assert(handler); + + auto stream = std::make_shared<tls_stream<socket_type>>(connector_.get_service(), context_); + + connector_.connect(stream->get_socket().lowest_layer(), [handler, stream] (auto code) { + if (code) { + handler(code, nullptr); + return; + } + + stream->get_socket().async_handshake(stream_base::client, [handler, stream] (auto code) { + if (code) + handler(std::move(code), nullptr); + else + handler(std::move(code), std::move(stream)); + }); + }); +} + +/** + * \brief Convenient alias. + */ +using tls_ip_connector = tls_connector<ip_connector>; + +#if defined(BOOST_ASIO_HAS_LOCAL_SOCKETS) + +/** + * \brief Convenient alias. + */ +using tls_local_connector = tls_connector<local_connector>; + +#endif // !BOOST_ASIO_HAS_LOCAL_SOCKETS + +#endif // !IRCCD_HAVE_SSL + +// }}} + } // !irccd #endif // !IRCCD_CONNECTOR_HPP
--- a/libirccd-core/irccd/socket_acceptor.hpp Thu Nov 01 10:34:21 2018 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,169 +0,0 @@ -/* - * socket_acceptor.hpp -- socket stream acceptor interface - * - * Copyright (c) 2013-2018 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_SOCKET_ACCEPTOR_HPP -#define IRCCD_SOCKET_ACCEPTOR_HPP - -/** - * \file socket_acceptor.hpp - * \brief Socket stream acceptor interface. - */ - -#include <irccd/sysconfig.hpp> - -#include "acceptor.hpp" -#include "socket_stream.hpp" - -namespace irccd { - -/** - * \brief Socket stream acceptor interface. - * \tparam Protocol a Boost.Asio compatible protocol (e.g. ip::tcp) - */ -template <typename Protocol> -class socket_acceptor : public acceptor { -public: - /** - * Convenient endpoint alias. - */ - using endpoint = typename Protocol::endpoint; - - /** - * Convenient acceptor alias. - */ - using acceptor = typename Protocol::acceptor; - - /** - * Convenient socket alias. - */ - using socket = typename Protocol::socket; - -private: - acceptor acceptor_; - -#if !defined(NDEBUG) - bool is_accepting_{false}; -#endif - -protected: - /** - * Helper to accept on the real underlying socket. - * - * \param socket the real socket - * \param handler the handler - */ - template <typename Socket, typename Handler> - void do_accept(Socket& socket, Handler handler); - -public: - /** - * Construct the socket_acceptor. - * - * \pre acceptor must be ready (is_open() returns true) - * \param acceptor the Boost.Asio acceptor - */ - socket_acceptor(acceptor acceptor) noexcept; - - /** - * Get the underlying acceptor. - * - * \return the acceptor - */ - auto get_acceptor() const noexcept -> const acceptor&; - - /** - * Overloaded function. - * - * \return the acceptor - */ - auto get_acceptor() noexcept -> acceptor&; - - /** - * \copydoc acceptor::accept - */ - - void accept(handler handler) override; -}; - -template <typename Protocol> -template <typename Socket, typename Handler> -void socket_acceptor<Protocol>::do_accept(Socket& socket, Handler handler) -{ -#if !defined(NDEBUG) - assert(!is_accepting_); - - is_accepting_ = true; -#endif - - acceptor_.async_accept(socket, [this, handler] (auto code) { -#if !defined(NDEBUG) - is_accepting_ = false; -#endif - (void)this; - handler(code); - }); -} - -template <typename Protocol> -socket_acceptor<Protocol>::socket_acceptor(acceptor acceptor) noexcept - : acceptor_(std::move(acceptor)) -{ - assert(acceptor_.is_open()); -} - -template <typename Protocol> -auto socket_acceptor<Protocol>::get_acceptor() const noexcept -> const acceptor& -{ - return acceptor_; -} - -template <typename Protocol> -auto socket_acceptor<Protocol>::get_acceptor() noexcept -> acceptor& -{ - return acceptor_; -} - -template <typename Protocol> -void socket_acceptor<Protocol>::accept(handler handler) -{ - assert(handler); - - const auto client = std::make_shared<socket_stream<socket>>(acceptor_.get_io_service()); - - do_accept(client->get_socket(), [client, handler] (auto code) { - handler(std::move(code), code ? nullptr : std::move(client)); - }); -} - -/** - * Convenient TCP/IP acceptor type. - */ -using ip_acceptor = socket_acceptor<boost::asio::ip::tcp>; - -#if !BOOST_OS_WINDOWS - -/** - * Convenient Unix acceptor type. - */ -using local_acceptor = socket_acceptor<boost::asio::local::stream_protocol>; - -#endif - -} // !irccd - -#endif // !IRCCD_SOCKET_ACCEPTOR_HPP
--- a/libirccd-core/irccd/socket_connector.hpp Thu Nov 01 10:34:21 2018 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,184 +0,0 @@ -/* - * socket_connector.hpp -- socket connection interface - * - * Copyright (c) 2013-2018 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_SOCKET_CONNECTOR_HPP -#define IRCCD_SOCKET_CONNECTOR_HPP - -/** - * \file socket_connector.hpp - * \brief Socket connection interface. - */ - -#include <irccd/sysconfig.hpp> - -#include <vector> - -#include "connector.hpp" -#include "socket_stream.hpp" - -namespace irccd { - -/** - * \brief Socket connection interface. - * \tparam Protocol a Boost.Asio compatible protocol (e.g. ip::tcp) - */ -template <typename Protocol> -class socket_connector : public connector { -public: - /** - * Convenient endpoint alias. - */ - using endpoint = typename Protocol::endpoint; - - /** - * Convenient socket alias. - */ - using socket = typename Protocol::socket; - -private: - boost::asio::io_service& service_; - std::vector<endpoint> endpoints_; - -#if !defined(NDEBUG) - bool is_connecting_{false}; -#endif - -protected: - /** - * Start trying to connect to all endpoints. - * - * \param socket the underlying socket - * \param handler handler with `void f(std::error_code)` signature - */ - template <typename Socket, typename Handler> - void do_connect(Socket& socket, Handler handler); - -public: - /** - * Construct the socket connector with only one endpoint. - * - * \param service the service - * \param endpoint the unique endpoint - */ - socket_connector(boost::asio::io_service& service, endpoint endpoint) noexcept; - - /** - * Construct the socket connection. - * - * \param service the service - * \param eps the endpoints - */ - socket_connector(boost::asio::io_service& service, std::vector<endpoint> eps) noexcept; - - /** - * Get the underlying I/O service. - * - * \return the I/O service - */ - auto get_io_service() const noexcept -> const boost::asio::io_service&; - - /** - * Overloaded function. - * - * \return the I/O service - */ - auto get_io_service() noexcept -> boost::asio::io_service&; - - /** - * \copydoc connector::connect - */ - void connect(handler handler); -}; - -template <typename Protocol> -template <typename Socket, typename Handler> -void socket_connector<Protocol>::do_connect(Socket& socket, Handler handler) -{ -#if !defined(NDEBUG) - assert(!is_connecting_); - - is_connecting_ = true; -#endif - - boost::asio::async_connect(socket, endpoints_.begin(), endpoints_.end(), [this, handler] (auto code, auto ep) { -#if !defined(NDEBUG) - is_connecting_ = false; -#endif - - if (ep == endpoints_.end()) - handler(make_error_code(std::errc::host_unreachable)); - else - handler(code); - }); -} - -template <typename Protocol> -socket_connector<Protocol>::socket_connector(boost::asio::io_service& service, endpoint endpoint) noexcept - : service_(service) - , endpoints_{std::move(endpoint)} -{ -} - -template <typename Protocol> -socket_connector<Protocol>::socket_connector(boost::asio::io_service& service, std::vector<endpoint> eps) noexcept - : service_(service) - , endpoints_(std::move(eps)) -{ -} - -template <typename Protocol> -auto socket_connector<Protocol>::get_io_service() const noexcept -> const boost::asio::io_service& -{ - return service_; -} - -template <typename Protocol> -auto socket_connector<Protocol>::get_io_service() noexcept -> boost::asio::io_service& -{ - return service_; -} - -template <typename Protocol> -void socket_connector<Protocol>::connect(handler handler) -{ - assert(handler); - - const auto stream = std::make_shared<socket_stream<socket>>(service_); - - do_connect(stream->get_socket(), [handler, stream] (auto code) { - handler(code, code ? nullptr : std::move(stream)); - }); -} - -/** - * Convenient TCP/IP connector type. - */ -using ip_connector = socket_connector<boost::asio::ip::tcp>; - -#if !BOOST_OS_WINDOWS - -/** - * Convenient Unix conncetor type. - */ -using local_connector = socket_connector<boost::asio::local::stream_protocol>; - -#endif - -} // !irccd - -#endif // !IRCCD_SOCKET_CONNECTOR_HPP
--- a/libirccd-core/irccd/socket_stream.hpp Thu Nov 01 10:34:21 2018 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,229 +0,0 @@ -/* - * socket_stream.hpp -- socket stream interface - * - * Copyright (c) 2013-2018 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_SOCKET_STREAM_HPP -#define IRCCD_SOCKET_STREAM_HPP - -/** - * \file socket_stream.hpp - * \brief Socket stream interface. - */ - -#include <irccd/sysconfig.hpp> - -#include <cstddef> -#include <cassert> -#include <string> -#include <utility> - -#include <boost/asio.hpp> -#include <boost/predef/os.h> - -#include "stream.hpp" - -namespace irccd { - -/** - * \brief Socket implementation interface. - * \tparam Socket the Boost.Asio compatible socket. - * - * This class reimplements stream for Boost.Asio sockets. - */ -template <typename Socket> -class socket_stream : public stream { -private: - Socket socket_; - boost::asio::streambuf input_; - std::string output_; - -#if !defined(NDEBUG) - bool is_receiving_{false}; - bool is_sending_{false}; -#endif - - void handle_read(std::error_code, std::size_t, stream::read_handler); - void handle_write(std::error_code, std::size_t, stream::write_handler); - -public: - /** - * Create the socket stream. - * - * \param args the Socket constructor arguments - */ - template <typename... Args> - socket_stream(Args&&... args); - - /** - * Get the underlying socket. - * - * \return the socket - */ - auto get_socket() const noexcept -> const Socket&; - - /** - * Overloaded function - * - * \return the socket - */ - auto get_socket() noexcept -> Socket&; - - /** - * \copydoc stream::read - */ - void read(read_handler handler) override; - - /** - * \copydoc stream::write - */ - void write(const nlohmann::json& json, write_handler handler) override; -}; - -template <typename Socket> -void socket_stream<Socket>::handle_read(std::error_code code, - std::size_t xfer, - stream::read_handler handler) -{ -#if !defined(NDEBUG) - is_receiving_ = false; -#endif - - if (xfer == 0U) { - handler(make_error_code(std::errc::not_connected), nullptr); - return; - } - if (code) { - handler(code, nullptr); - return; - } - - // 1. Convert the buffer safely. - std::string buffer; - - try { - buffer = std::string( - boost::asio::buffers_begin(input_.data()), - boost::asio::buffers_begin(input_.data()) + xfer - /* \r\n\r\n */ 4 - ); - - input_.consume(xfer); - } catch (const std::bad_alloc&) { - handler(make_error_code(std::errc::not_enough_memory), nullptr); - return; - } - - // 2. Convert to JSON. - nlohmann::json doc; - - try { - doc = nlohmann::json::parse(buffer); - } catch (const std::exception&) { - handler(make_error_code(std::errc::invalid_argument), nullptr); - return; - } - - if (!doc.is_object()) - handler(make_error_code(std::errc::invalid_argument), nullptr); - else - handler(std::error_code(), std::move(doc)); -} - -template <typename Socket> -void socket_stream<Socket>::handle_write(std::error_code code, - std::size_t xfer, - stream::write_handler handler) -{ -#if !defined(NDEBUG) - is_sending_ = false; -#endif - - if (xfer == 0) - handler(make_error_code(std::errc::not_connected)); - else - handler(code); -} - -template <typename Socket> -template <typename... Args> -socket_stream<Socket>::socket_stream(Args&&... args) - : socket_(std::forward<Args>(args)...) -{ -} - -template <typename Socket> -auto socket_stream<Socket>::get_socket() const noexcept -> const Socket& -{ - return socket_; -} - -template <typename Socket> -auto socket_stream<Socket>::get_socket() noexcept -> Socket& -{ - return socket_; -} - -template <typename Socket> -void socket_stream<Socket>::read(stream::read_handler handler) -{ -#if !defined(NDEBUG) - assert(!is_receiving_); - assert(handler); - - is_receiving_ = true; -#endif - - boost::asio::async_read_until(get_socket(), input_, "\r\n\r\n", [this, handler] (auto code, auto xfer) { - handle_read(code, xfer, std::move(handler)); - }); -} - -template <typename Socket> -void socket_stream<Socket>::write(const nlohmann::json& json, stream::write_handler handler) -{ -#if !defined(NDEBUG) - assert(!is_sending_); - assert(handler); - - is_sending_ = true; -#endif - - output_ = json.dump(0) + "\r\n\r\n"; - - const auto buffer = boost::asio::buffer(output_.data(), output_.size()); - - boost::asio::async_write(get_socket(), buffer, [this, handler] (auto code, auto xfer) { - handle_write(code, xfer, std::move(handler)); - }); -} - -/** - * Convenient TCP/IP stream type. - */ -using ip_stream = socket_stream<boost::asio::ip::tcp::socket>; - -#if !BOOST_OS_WINDOWS - -/** - * Convenient Unix stream type. - */ -using local_stream = socket_stream<boost::asio::local::stream_protocol::socket>; - -#endif - -} // !irccd - -#endif // !IRCCD_SOCKET_STREAM_HPP
--- a/libirccd-core/irccd/stream.hpp Thu Nov 01 10:34:21 2018 +0100 +++ b/libirccd-core/irccd/stream.hpp Sun Nov 04 17:26:05 2018 +0100 @@ -24,8 +24,21 @@ * \brief Abstract stream interface. */ +#include <irccd/sysconfig.hpp> + +#include <cassert> +#include <cstddef> #include <functional> +#include <ostream> +#include <string> #include <system_error> +#include <utility> + +#include <boost/asio.hpp> + +#if defined(IRCCD_HAVE_SSL) +# include <boost/asio/ssl.hpp> +#endif #include "json.hpp" @@ -37,19 +50,19 @@ * Abstract I/O interface that allows reading/writing from a stream in an * asynchronous manner. * - * The derived classes must implement non-blocking read and write operations. + * The derived classes must implement non-blocking recv and send operations. */ class stream { public: /** * \brief Read completion handler. */ - using read_handler = std::function<void (std::error_code, nlohmann::json)>; + using recv_handler = std::function<void (std::error_code, nlohmann::json)>; /** * \brief Write completion handler. */ - using write_handler = std::function<void (std::error_code)>; + using send_handler = std::function<void (std::error_code)>; /** * Default constructor. @@ -68,7 +81,7 @@ * \pre handler != nullptr * \param handler the handler */ - virtual void read(read_handler handler) = 0; + virtual void recv(recv_handler handler) = 0; /** * Start asynchronous write. @@ -79,9 +92,342 @@ * \param json the JSON message * \param handler the handler */ - virtual void write(const nlohmann::json& json, write_handler handler) = 0; + virtual void send(const nlohmann::json& json, send_handler handler) = 0; +}; + +// {{{ socket_stream_base + +/** + * \brief Abstract base interface for Boost.Asio sockets. + * + * This class provides convenient functions for underlying sockets. + * + * \see basic_socket_stream + */ +class socket_stream_base : public stream { +private: + boost::asio::streambuf input_{2048}; + boost::asio::streambuf output_; + +#if !defined(NDEBUG) + bool is_receiving_{false}; + bool is_sending_{false}; +#endif + + void handle_recv(boost::system::error_code, std::size_t, recv_handler); + void handle_send(boost::system::error_code, std::size_t, send_handler); + +protected: + /** + * Convenient function for receiving for the underlying socket. + * + * \param sc the socket + * \param handler the handler + */ + template <typename Socket> + void recv(Socket& sc, recv_handler handler); + + /** + * Convenient function for sending for the underlying socket. + * + * \param json the JSON object + * \param sc the socket + * \param handler the handler + */ + template <typename Socket> + void send(const nlohmann::json& json, Socket& sc, send_handler handler); }; +inline void socket_stream_base::handle_recv(boost::system::error_code code, + std::size_t xfer, + recv_handler handler) +{ +#if !defined(NDEBUG) + is_receiving_ = false; +#endif + + if (code == boost::asio::error::not_found) { + handler(make_error_code(std::errc::argument_list_too_long), nullptr); + return; + } + if (code == boost::asio::error::eof || xfer == 0) { + handler(make_error_code(std::errc::connection_reset), nullptr); + return; + } + if (code) { + handler(std::move(code), nullptr); + return; + } + + // 1. Convert the buffer safely. + std::string buffer; + + try { + buffer = std::string( + boost::asio::buffers_begin(input_.data()), + boost::asio::buffers_begin(input_.data()) + xfer - /* \r\n\r\n */ 4 + ); + + input_.consume(xfer); + } catch (const std::bad_alloc&) { + handler(make_error_code(std::errc::not_enough_memory), nullptr); + return; + } + + // 2. Convert to JSON. + nlohmann::json doc; + + try { + doc = nlohmann::json::parse(buffer); + } catch (const std::exception&) { + handler(make_error_code(std::errc::invalid_argument), nullptr); + return; + } + + if (!doc.is_object()) + handler(make_error_code(std::errc::invalid_argument), nullptr); + else + handler(std::error_code(), std::move(doc)); +} + +inline void socket_stream_base::handle_send(boost::system::error_code code, + std::size_t xfer, + send_handler handler) +{ +#if !defined(NDEBUG) + is_sending_ = false; +#endif + + if (code == boost::asio::error::eof || xfer == 0) { + handler(make_error_code(std::errc::connection_reset)); + return; + } + else + handler(std::move(code)); +} + +template <typename Socket> +inline void socket_stream_base::recv(Socket& sc, recv_handler handler) +{ +#if !defined(NDEBUG) + assert(!is_receiving_); + + is_receiving_ = true; +#endif + + assert(handler); + + async_read_until(sc, input_, "\r\n\r\n", [this, handler] (auto code, auto xfer) { + handle_recv(code, xfer, handler); + }); +} + +template <typename Socket> +inline void socket_stream_base::send(const nlohmann::json& json, Socket& sc, send_handler handler) +{ +#if !defined(NDEBUG) + assert(!is_sending_); + + is_sending_ = true; +#endif + + assert(json.is_object()); + assert(handler); + + std::ostream out(&output_); + + out << json.dump(0); + out << "\r\n\r\n"; + out << std::flush; + + async_write(sc, output_, [this, handler] (auto code, auto xfer) { + handle_send(code, xfer, handler); + }); +} + +// }}} + +// {{{ basic_socket_stream + +/** + * \brief Complete implementation for basic sockets + * \tparam Socket Boost.Asio socket (e.g. boost::asio::ip::tcp::socket) + */ +template <typename Socket> +class basic_socket_stream : public socket_stream_base { +private: + Socket socket_; + +public: + /** + * Construct a socket stream. + * + * \param service the I/O service + */ + basic_socket_stream(boost::asio::io_context& service); + + /** + * Get the underlying socket. + * + * \return the socket + */ + auto get_socket() const noexcept -> const Socket&; + + /** + * Overloaded function. + * + * \return the socket + */ + auto get_socket() noexcept -> Socket&; + + /** + * \copydoc stream::recv + */ + void recv(recv_handler handler) override; + + /** + * \copydoc stream::send + */ + void send(const nlohmann::json& json, send_handler handler) override; +}; + +template <typename Socket> +inline basic_socket_stream<Socket>::basic_socket_stream(boost::asio::io_context& ctx) + : socket_(ctx) +{ +} + +template <typename Socket> +inline auto basic_socket_stream<Socket>::get_socket() const noexcept -> const Socket& +{ + return socket_; +} + +template <typename Socket> +inline auto basic_socket_stream<Socket>::get_socket() noexcept -> Socket& +{ + return socket_; +} + +template <typename Socket> +inline void basic_socket_stream<Socket>::recv(recv_handler handler) +{ + socket_stream_base::recv(socket_, handler); +} + +template <typename Socket> +inline void basic_socket_stream<Socket>::send(const nlohmann::json& json, send_handler handler) +{ + socket_stream_base::send(json, socket_, handler); +} + +// }}} + +// {{{ ip_stream + +/** + * \brief Convenient alias for boost::asio::ip::tcp::socket + */ +using ip_stream = basic_socket_stream<boost::asio::ip::tcp::socket>; + +// }}} + +// {{{ local_stream + +#if defined(BOOST_ASIO_HAS_LOCAL_SOCKETS) + +/** + * \brief Convenient alias for boost::asio::local::stream_protocol::socket + */ +using local_stream = basic_socket_stream<boost::asio::local::stream_protocol::socket>; + +#endif // !BOOST_ASIO_HAS_LOCAL_SOCKETS + +// }}} + +// {{{ tls_stream + +#if defined(IRCCD_HAVE_SSL) + +/** + * \brief TLS/SSL streams. + * \tparam Socket the Boost.Asio compatible socket. + */ +template <typename Socket> +class tls_stream : public socket_stream_base { +private: + boost::asio::ssl::stream<Socket> socket_; + std::shared_ptr<boost::asio::ssl::context> context_; + +public: + /** + * Constructor. + * + * \param service the I/O service + * \param ctx the shared context + */ + tls_stream(boost::asio::io_context& service, std::shared_ptr<boost::asio::ssl::context> ctx); + + /** + * Get the SSL socket. + * + * \return the socket + */ + auto get_socket() const noexcept -> const boost::asio::ssl::stream<Socket>&; + + /** + * Overloaded function. + * + * \return the socket + */ + auto get_socket() noexcept -> boost::asio::ssl::stream<Socket>&; + + /** + * \copydoc stream::recv + */ + void recv(recv_handler handler) override; + + /** + * \copydoc stream::send + */ + void send(const nlohmann::json& json, send_handler handler) override; +}; + +template <typename Socket> +inline tls_stream<Socket>::tls_stream(boost::asio::io_context& service, std::shared_ptr<boost::asio::ssl::context> ctx) + : socket_(service, *ctx) + , context_(std::move(ctx)) +{ +} + +template <typename Socket> +inline auto tls_stream<Socket>::get_socket() const noexcept -> const boost::asio::ssl::stream<Socket>& +{ + return socket_; +} + +template <typename Socket> +inline auto tls_stream<Socket>::get_socket() noexcept -> boost::asio::ssl::stream<Socket>& +{ + return socket_; +} + +template <typename Socket> +inline void tls_stream<Socket>::recv(recv_handler handler) +{ + socket_stream_base::recv(socket_, handler); +} + +template <typename Socket> +inline void tls_stream<Socket>::send(const nlohmann::json& json, send_handler handler) +{ + socket_stream_base::send(json, socket_, handler); +} + +#endif // !IRCCD_HAVE_SSL + +// }}} + } // !irccd #endif // !IRCCD_STREAM_HPP
--- a/libirccd-core/irccd/tls_acceptor.hpp Thu Nov 01 10:34:21 2018 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,96 +0,0 @@ -/* - * tls_acceptor.hpp -- TLS/SSL acceptors - * - * Copyright (c) 2013-2018 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_TLS_ACCEPTOR_HPP -#define IRCCD_TLS_ACCEPTOR_HPP - -/** - * \file tls_acceptor.hpp - * \brief TLS/SSL acceptors. - */ - -#include <irccd/sysconfig.hpp> - -#if defined(IRCCD_HAVE_SSL) - -#include "socket_acceptor.hpp" -#include "tls_stream.hpp" - -namespace irccd { - -/** - * \brief TLS/SSL acceptors. - * \tparam Protocol a Boost.Asio compatible protocol (e.g. ip::tcp) - */ -template <typename Protocol = boost::asio::ip::tcp> -class tls_acceptor : public socket_acceptor<Protocol> { -private: - using socket = typename Protocol::socket; - - boost::asio::ssl::context context_; - -public: - /** - * Construct a secure layer transport server. - * - * \param context the SSL context - * \param args the socket_acceptor arguments - */ - template <typename... Args> - tls_acceptor(boost::asio::ssl::context context, Args&&... args); - - /** - * \copydoc acceptor::accept - */ - void accept(acceptor::handler handler) override; -}; - -template <typename Protocol> -void tls_acceptor<Protocol>::accept(acceptor::handler handler) -{ - assert(handler); - - auto client = std::make_shared<tls_stream<socket>>(this->get_acceptor().get_io_service(), this->context_); - - socket_acceptor<Protocol>::do_accept(client->get_socket().lowest_layer(), [handler, client] (auto code) { - using boost::asio::ssl::stream_base; - - if (code) { - handler(code, nullptr); - return; - } - - client->get_socket().async_handshake(stream_base::server, [handler, client] (auto code) { - handler(code, code ? nullptr : std::move(client)); - }); - }); -} - -template <typename Protocol> -template <typename... Args> -tls_acceptor<Protocol>::tls_acceptor(boost::asio::ssl::context context, Args&&... args) - : socket_acceptor<Protocol>(std::forward<Args>(args)...) - , context_(std::move(context)) -{ -} - -} // !irccd - -#endif // !IRCCD_HAVE_SSL - -#endif // !IRCCD_TLS_ACCEPTOR_HPP
--- a/libirccd-core/irccd/tls_connector.hpp Thu Nov 01 10:34:21 2018 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,95 +0,0 @@ -/* - * tls_connector.hpp -- TLS/SSL connectors - * - * Copyright (c) 2013-2018 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_TLS_CONNECTOR_HPP -#define IRCCD_TLS_CONNECTOR_HPP - -/** - * \file tls_connector.hpp - * \brief TLS/SSL connectors. - */ - -#include <irccd/sysconfig.hpp> - -#if defined(IRCCD_HAVE_SSL) - -#include "socket_connector.hpp" -#include "tls_stream.hpp" - -namespace irccd { - -/** - * \brief TLS/SSL connectors. - * \tparam Protocol a Boost.Asio compatible protocol (e.g. ip::tcp) - */ -template <typename Protocol = boost::asio::ip::tcp> -class tls_connector : public socket_connector<Protocol> { -private: - boost::asio::ssl::context context_; - -public: - /** - * Construct a secure layer transport server. - * - * \param context the SSL context - * \param args the arguments to socket_connector<Socket> constructor - */ - template <typename... Args> - tls_connector(boost::asio::ssl::context context, Args&&... args); - - /** - * \copydoc socket_connector::connect - */ - void connect(connector::handler handler) override; -}; - -template <typename Protocol> -template <typename... Args> -tls_connector<Protocol>::tls_connector(boost::asio::ssl::context context, Args&&... args) - : socket_connector<Protocol>(std::forward<Args>(args)...) - , context_(std::move(context)) -{ -} - -template <typename Protocol> -void tls_connector<Protocol>::connect(connector::handler handler) -{ - using boost::asio::ssl::stream_base; - using socket = typename Protocol::socket; - - assert(handler); - - const auto stream = std::make_shared<tls_stream<socket>>(this->get_io_service(), context_); - - socket_connector<Protocol>::do_connect(stream->get_socket().lowest_layer(), [handler, stream] (auto code) { - if (code) { - handler(code, nullptr); - return; - } - - stream->get_socket().async_handshake(stream_base::client, [handler, stream] (auto code) { - handler(code, code ? nullptr : std::move(stream)); - }); - }); -} - -} // !irccd - -#endif // !IRCCD_HAVE_SSL - -#endif // !IRCCD_TLS_CONNECTOR_HPP
--- a/libirccd-core/irccd/tls_stream.hpp Thu Nov 01 10:34:21 2018 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,60 +0,0 @@ -/* - * tls_stream.hpp -- TLS/SSL streams - * - * Copyright (c) 2013-2018 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_TLS_STREAM_HPP -#define IRCCD_TLS_STREAM_HPP - -/** - * \file tls_stream.hpp - * \brief TLS/SSL streams. - */ - -#include <irccd/sysconfig.hpp> - -#if defined(IRCCD_HAVE_SSL) - -#include <boost/asio/ssl.hpp> - -#include "socket_stream.hpp" - -namespace irccd { - -/** - * \brief TLS/SSL streams. - * \tparam Socket the Boost.Asio compatible socket. - */ -template <typename Socket = boost::asio::ip::tcp::socket> -class tls_stream : public socket_stream<boost::asio::ssl::stream<Socket>> { -public: - /** - * Constructor. - * - * \param args the arguments to boost::asio::ssl::stream<Socket> - */ - template <typename... Args> - tls_stream(Args&&... args) - : socket_stream<boost::asio::ssl::stream<Socket>>(std::forward<Args>(args)...) - { - } -}; - -} // !irccd - -#endif // !IRCCD_HAVE_SSL - -#endif // !IRCCD_TLS_STREAM_HPP
--- a/libirccd-ctl/irccd/ctl/controller.cpp Thu Nov 01 10:34:21 2018 +0100 +++ b/libirccd-ctl/irccd/ctl/controller.cpp Sun Nov 04 17:26:05 2018 +0100 @@ -106,14 +106,14 @@ }); } -void controller::read(stream::read_handler handler) +void controller::read(stream::recv_handler handler) { assert(handler); assert(stream_); auto stream = stream_; - stream_->read([this, handler, stream] (auto code, auto msg) { + stream_->recv([this, handler, stream] (auto code, auto msg) { if (code) { stream_ = nullptr; handler(std::move(code), std::move(msg)); @@ -139,14 +139,14 @@ }); } -void controller::write(nlohmann::json message, stream::write_handler handler) +void controller::write(nlohmann::json message, stream::send_handler handler) { assert(message.is_object()); assert(stream_); auto stream = stream_; - stream_->write(std::move(message), [this, stream, handler] (auto code) { + stream_->send(std::move(message), [this, stream, handler] (auto code) { if (code) stream_ = nullptr; if (handler)
--- a/libirccd-ctl/irccd/ctl/controller.hpp Thu Nov 01 10:34:21 2018 +0100 +++ b/libirccd-ctl/irccd/ctl/controller.hpp Sun Nov 04 17:26:05 2018 +0100 @@ -104,7 +104,7 @@ * \pre handler != nullptr * \param handler the recv handler */ - void read(stream::read_handler handler); + void read(stream::recv_handler handler); /** * Queue a send operation, if receive operations are already running, it @@ -114,7 +114,7 @@ * \param message the JSON message * \param handler the optional completion handler */ - void write(nlohmann::json message, stream::write_handler handler = nullptr); + void write(nlohmann::json message, stream::send_handler handler = nullptr); }; } // !irccd::ctl
--- a/libirccd-test/irccd/test/cli_fixture.cpp Thu Nov 01 10:34:21 2018 +0100 +++ b/libirccd-test/irccd/test/cli_fixture.cpp Sun Nov 04 17:26:05 2018 +0100 @@ -22,7 +22,7 @@ #include <boost/process.hpp> #include <irccd/string_util.hpp> -#include <irccd/socket_acceptor.hpp> +#include <irccd/acceptor.hpp> #include <irccd/daemon/command.hpp> #include <irccd/daemon/transport_service.hpp> @@ -37,17 +37,13 @@ cli_fixture::cli_fixture() : server_(new mock_server(irccd_.get_service(), "test", "localhost")) { - std::remove(CMAKE_BINARY_DIR "/tmp/irccd.sock"); - - local_acceptor::endpoint endpoint(CMAKE_BINARY_DIR "/tmp/irccd.sock"); - local_acceptor::acceptor acceptor(service_, std::move(endpoint)); + auto acceptor = std::make_unique<local_acceptor>(irccd_.get_service(), CMAKE_BINARY_DIR "/tmp/irccd.sock"); for (const auto& f : command::registry) irccd_.transports().get_commands().push_back(f()); irccd_.servers().add(server_); - irccd_.transports().add(std::make_unique<transport_server>( - std::make_unique<local_acceptor>(std::move(acceptor)))); + irccd_.transports().add(std::make_unique<transport_server>(std::move(acceptor))); server_->disconnect(); server_->clear(); }
--- a/libirccd-test/irccd/test/command_fixture.cpp Thu Nov 01 10:34:21 2018 +0100 +++ b/libirccd-test/irccd/test/command_fixture.cpp Sun Nov 04 17:26:05 2018 +0100 @@ -16,8 +16,8 @@ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ -#include <irccd/socket_acceptor.hpp> -#include <irccd/socket_connector.hpp> +#include <irccd/acceptor.hpp> +#include <irccd/connector.hpp> #include <irccd/daemon/command.hpp> #include <irccd/daemon/transport_server.hpp> @@ -56,23 +56,20 @@ : server_(new mock_server(ctx_, "test", "localhost")) , plugin_(new mock_plugin("test")) { - using boost::asio::ip::tcp; + const auto path = CMAKE_BINARY_DIR "/tmp/irccd.sock"; + + auto acceptor = std::make_unique<local_acceptor>(irccd_.get_service(), path); + auto connector = std::make_unique<local_connector>(irccd_.get_service(), path); // 1. Add all commands. for (const auto& f : command::registry) irccd_.transports().get_commands().push_back(f()); - // 2. Bind to a random port. - tcp::endpoint ep(tcp::v4(), 0); - tcp::acceptor acc(ctx_, ep); + // 2. Create controller and transport server. + ctl_ = std::make_unique<ctl::controller>(std::move(connector)); + irccd_.transports().add(std::make_unique<transport_server>(std::move(acceptor))); - // 3. Create controller and transport server. - ctl_ = std::make_unique<ctl::controller>( - std::make_unique<ip_connector>(ctx_, acc.local_endpoint())); - irccd_.transports().add(std::make_unique<transport_server>( - std::make_unique<ip_acceptor>(std::move(acc)))); - - // Wait for controller to connect. + // 3. Wait for controller to connect. boost::asio::deadline_timer timer(ctx_); timer.expires_from_now(boost::posix_time::seconds(10));
--- a/libirccd/irccd/daemon/server.cpp Thu Nov 01 10:34:21 2018 +0100 +++ b/libirccd/irccd/daemon/server.cpp Sun Nov 04 17:26:05 2018 +0100 @@ -561,7 +561,7 @@ void server::set_options(options flags) noexcept { #if !defined(IRCCD_HAVE_SSL) - assert(!(flags & ssl)); + assert((flags & options::ssl) != options::ssl); #endif flags_ = flags;
--- a/libirccd/irccd/daemon/transport_client.cpp Thu Nov 01 10:34:21 2018 +0100 +++ b/libirccd/irccd/daemon/transport_client.cpp Sun Nov 04 17:26:05 2018 +0100 @@ -30,7 +30,7 @@ const auto self = shared_from_this(); - stream_->write(queue_.front().first, [this, self] (auto code) { + stream_->send(queue_.front().first, [this, self] (auto code) { if (queue_.front().second) queue_.front().second(code); @@ -69,14 +69,14 @@ state_ = state; } -void transport_client::read(stream::read_handler handler) +void transport_client::read(stream::recv_handler handler) { assert(handler); if (state_ != state::closing) { const auto self = shared_from_this(); - stream_->read([this, self, handler] (auto code, auto msg) { + stream_->recv([this, self, handler] (auto code, auto msg) { handler(code, msg); if (code) @@ -85,7 +85,7 @@ } } -void transport_client::write(nlohmann::json json, stream::write_handler handler) +void transport_client::write(nlohmann::json json, stream::send_handler handler) { const auto in_progress = queue_.size() > 0; @@ -95,19 +95,19 @@ flush(); } -void transport_client::success(const std::string& cname, stream::write_handler handler) +void transport_client::success(const std::string& cname, stream::send_handler handler) { assert(!cname.empty()); write({{ "command", cname }}, std::move(handler)); } -void transport_client::error(std::error_code code, stream::write_handler handler) +void transport_client::error(std::error_code code, stream::send_handler handler) { error(std::move(code), "", std::move(handler)); } -void transport_client::error(std::error_code code, std::string_view cname, stream::write_handler handler) +void transport_client::error(std::error_code code, std::string_view cname, stream::send_handler handler) { assert(code);
--- a/libirccd/irccd/daemon/transport_client.hpp Thu Nov 01 10:34:21 2018 +0100 +++ b/libirccd/irccd/daemon/transport_client.hpp Sun Nov 04 17:26:05 2018 +0100 @@ -49,7 +49,7 @@ state state_{state::authenticating}; std::weak_ptr<transport_server> parent_; std::shared_ptr<stream> stream_; - std::deque<std::pair<nlohmann::json, stream::write_handler>> queue_; + std::deque<std::pair<nlohmann::json, stream::send_handler>> queue_; void flush(); void erase(); @@ -92,7 +92,7 @@ * \param handler the handler * \warning Another read operation MUST NOT be running. */ - void read(stream::read_handler handler); + void read(stream::recv_handler handler); /** * Start sending if not closed. @@ -106,7 +106,7 @@ * \param handler the optional handler * \note If a write operation is running, it is postponed once ready. */ - void write(nlohmann::json json, stream::write_handler handler = nullptr); + void write(nlohmann::json json, stream::send_handler handler = nullptr); /** * Convenient success message. @@ -115,7 +115,7 @@ * \param handler the optional handler * \note If a write operation is running, it is postponed once ready. */ - void success(const std::string& command, stream::write_handler handler = nullptr); + void success(const std::string& command, stream::send_handler handler = nullptr); /** * Send an error code to the client. @@ -125,7 +125,7 @@ * \param handler the optional handler * \note If a write operation is running, it is postponed once ready. */ - void error(std::error_code code, stream::write_handler handler = nullptr); + void error(std::error_code code, stream::send_handler handler = nullptr); /** * Send an error code to the client. @@ -136,7 +136,7 @@ * \param handler the optional handler * \note If a write operation is running, it is postponed once ready. */ - void error(std::error_code code, std::string_view command, stream::write_handler handler = nullptr); + void error(std::error_code code, std::string_view command, stream::send_handler handler = nullptr); }; } // !irccd
--- a/libirccd/irccd/daemon/transport_util.cpp Thu Nov 01 10:34:21 2018 +0100 +++ b/libirccd/irccd/daemon/transport_util.cpp Sun Nov 04 17:26:05 2018 +0100 @@ -20,15 +20,9 @@ #include <cassert> -#include <boost/predef/os.h> - +#include <irccd/acceptor.hpp> #include <irccd/ini_util.hpp> #include <irccd/string_util.hpp> -#include <irccd/socket_acceptor.hpp> - -#if defined(IRCCD_HAVE_SSL) -# include <irccd/tls_acceptor.hpp> -#endif #include "transport_util.hpp" #include "transport_server.hpp" @@ -39,7 +33,7 @@ namespace { -auto from_config_load_ip_protocol(const ini::section& sc) -> asio::ip::tcp +auto from_config_load_ip_protocols(const ini::section& sc) -> std::pair<bool, bool> { bool ipv4 = true, ipv6 = false; @@ -66,89 +60,79 @@ if (!ipv4 && !ipv6) throw transport_error(transport_error::invalid_family); - return ipv4 ? asio::ip::tcp::v4() : asio::ip::tcp::v6(); + return { ipv4, ipv6 }; } -auto from_config_load_ip_endpoint(const ini::section& sc) -> asio::ip::tcp::endpoint +#if defined(IRCCD_HAVE_SSL) + +auto from_config_load_ssl(const ini::section& sc) -> boost::asio::ssl::context { + const auto key = sc.get("key").get_value(); + const auto cert = sc.get("certificate").get_value(); + + if (key.empty()) + throw transport_error(transport_error::invalid_private_key); + if (cert.empty()) + throw transport_error(transport_error::invalid_certificate); + + boost::asio::ssl::context ctx(boost::asio::ssl::context::tlsv12); + + ctx.use_private_key_file(key, boost::asio::ssl::context::pem); + ctx.use_certificate_file(cert, boost::asio::ssl::context::pem); + + return ctx; +} + +#endif // !IRCCD_HAVE_SSL + +auto from_config_load_ip(asio::io_service& service, const ini::section& sc) -> std::unique_ptr<acceptor> +{ + assert(sc.get_key() == "transport"); + const auto port = ini_util::get_uint<std::uint16_t>(sc, "port"); const auto address = ini_util::optional_string(sc, "address", "*"); + const auto [ ipv4, ipv6 ] = from_config_load_ip_protocols(sc); if (!port) throw transport_error(transport_error::invalid_port); if (address.empty()) throw transport_error(transport_error::invalid_address); - const auto protocol = from_config_load_ip_protocol(sc); + if (string_util::is_boolean(sc.get("ssl").get_value())) +#if !defined(IRCCD_HAVE_SSL) + throw transport_error(transport_error::ssl_disabled); +#else + return std::make_unique<tls_ip_acceptor>(from_config_load_ssl(sc), + service, address, *port, ipv4, ipv6); +#endif // !IRCCD_HAVE_SSL - return address == "*" - ? asio::ip::tcp::endpoint(protocol, *port) - : asio::ip::tcp::endpoint(asio::ip::address::from_string(address), *port); + return std::make_unique<ip_acceptor>(service, address, *port, ipv4, ipv6); } -auto from_config_load_ip_acceptor(asio::io_service& service, const ini::section& sc) -> asio::ip::tcp::acceptor -{ - return asio::ip::tcp::acceptor(service, from_config_load_ip_endpoint(sc), true); -} - -auto from_config_load_ip(asio::io_service& service, const ini::section& sc) -> std::unique_ptr<transport_server> +auto from_config_load_local(asio::io_service& service, const ini::section& sc) -> std::unique_ptr<acceptor> { assert(sc.get_key() == "transport"); - auto acceptor = from_config_load_ip_acceptor(service, sc); - - if (string_util::is_boolean(sc.get("ssl").get_value())) { -#if !defined(IRCCD_HAVE_SSL) - throw transport_error(transport_error::ssl_disabled); -#else - const auto key = sc.get("key").get_value(); - const auto cert = sc.get("certificate").get_value(); - - if (key.empty()) - throw transport_error(transport_error::invalid_private_key); - if (cert.empty()) - throw transport_error(transport_error::invalid_certificate); - - boost::asio::ssl::context ctx(boost::asio::ssl::context::sslv23); - - ctx.use_private_key_file(key, boost::asio::ssl::context::pem); - ctx.use_certificate_file(cert, boost::asio::ssl::context::pem); - - return std::make_unique<transport_server>( - std::make_unique<tls_acceptor<>>(std::move(ctx), std::move(acceptor))); -#endif - } - - return std::make_unique<transport_server>( - std::make_unique<ip_acceptor>(std::move(acceptor))); -} - -auto from_config_load_unix(asio::io_service& service, const ini::section& sc) -> std::unique_ptr<transport_server> -{ - assert(sc.get_key() == "transport"); - -#if !BOOST_OS_WINDOWS - using boost::asio::local::stream_protocol; - +#if defined(BOOST_ASIO_HAS_LOCAL_SOCKETS) const auto path = sc.get("path").get_value(); if (path.empty()) throw transport_error(transport_error::invalid_path); - // Remove the file first. - std::remove(path.c_str()); + if (string_util::is_boolean(sc.get("ssl").get_value())) +#if !defined(IRCCD_HAVE_SSL) + throw transport_error(transport_error::ssl_disabled); +#else + return std::make_unique<tls_local_acceptor>(from_config_load_ssl(sc), service, path); +#endif // !IRCCD_HAVE_SSL - stream_protocol::endpoint endpoint(path); - stream_protocol::acceptor acceptor(service, std::move(endpoint)); - - return std::make_unique<transport_server>( - std::make_unique<local_acceptor>(std::move(acceptor))); + return std::make_unique<local_acceptor>(service, path); #else (void)service; (void)sc; throw transport_error(transport_error::not_supported); -#endif +#endif // !BOOST_ASIO_HAS_LOCAL_SOCKETS } } // !namespace @@ -163,15 +147,17 @@ if (type.empty()) throw transport_error(transport_error::not_supported); - std::unique_ptr<transport_server> transport; + std::unique_ptr<acceptor> acceptor; if (type == "ip") - transport = from_config_load_ip(service, sc); + acceptor = from_config_load_ip(service, sc); else if (type == "unix") - transport = from_config_load_unix(service, sc); + acceptor = from_config_load_local(service, sc); else throw transport_error(transport_error::not_supported); + auto transport = std::make_unique<transport_server>(std::move(acceptor)); + transport->set_password(password); return transport;
--- a/tests/src/libcommon/stream/CMakeLists.txt Thu Nov 01 10:34:21 2018 +0100 +++ b/tests/src/libcommon/stream/CMakeLists.txt Sun Nov 04 17:26:05 2018 +0100 @@ -17,7 +17,7 @@ # irccd_define_test( - NAME io + NAME stream SOURCES main.cpp LIBRARIES libirccd-core )
--- a/tests/src/libcommon/stream/main.cpp Thu Nov 01 10:34:21 2018 +0100 +++ b/tests/src/libcommon/stream/main.cpp Sun Nov 04 17:26:05 2018 +0100 @@ -1,5 +1,5 @@ /* - * main.cpp -- test io classes + * main.cpp -- test network classes * * Copyright (c) 2013-2018 David Demelier <markand@malikania.fr> * @@ -23,15 +23,9 @@ #include <irccd/sysconfig.hpp> -#include <irccd/socket_acceptor.hpp> -#include <irccd/socket_connector.hpp> -#include <irccd/socket_stream.hpp> - -#if defined(IRCCD_HAVE_SSL) -# include <irccd/tls_acceptor.hpp> -# include <irccd/tls_connector.hpp> -# include <irccd/tls_stream.hpp> -#endif // !IRCCD_HAVE_SSL +#include <irccd/acceptor.hpp> +#include <irccd/connector.hpp> +#include <irccd/stream.hpp> using boost::asio::io_service; using boost::asio::ip::tcp; @@ -40,10 +34,6 @@ using boost::asio::ssl::context; #endif -#if !BOOST_OS_WINDOWS -using boost::asio::local::stream_protocol; -#endif - namespace irccd { namespace { @@ -100,7 +90,7 @@ endpoint_ = acceptor.local_endpoint(); - return std::make_unique<ip_acceptor>(std::move(acceptor)); + return std::make_unique<ip_acceptor>(service_, std::move(acceptor)); } /** @@ -108,13 +98,16 @@ */ auto create_connector() -> std::unique_ptr<connector> override { - return std::make_unique<ip_connector>(service_, endpoint_); + const auto hostname = "127.0.0.1"; + const auto port = std::to_string(endpoint_.port()); + + return std::make_unique<ip_connector>(service_, hostname, port, true, false); } }; #if defined(IRCCD_HAVE_SSL) -class ssl_stream_fixture : public stream_fixture { +class tls_ip_stream_fixture : public stream_fixture { private: tcp::endpoint endpoint_; @@ -124,7 +117,7 @@ */ auto create_acceptor() -> std::unique_ptr<acceptor> override { - context context(context::sslv23); + context context(context::tlsv12); context.use_certificate_file(TESTS_SOURCE_DIR "/data/test.crt", context::pem); context.use_private_key_file(TESTS_SOURCE_DIR "/data/test.key", context::pem); @@ -134,7 +127,40 @@ endpoint_ = acceptor.local_endpoint(); - return std::make_unique<tls_acceptor<>>(std::move(context), std::move(acceptor)); + return std::make_unique<tls_acceptor<ip_acceptor>>(std::move(context), service_, std::move(acceptor)); + } + + /** + * \copydoc io_fixture::create_connector + */ + auto create_connector() -> std::unique_ptr<connector> override + { + context context(context::tlsv12); + + const auto hostname = "127.0.0.1"; + const auto port = std::to_string(endpoint_.port()); + + return std::make_unique<tls_connector<ip_connector>>(std::move(context), + service_, hostname, port, true, false); + } +}; + +#endif // !IRCCD_HAVE_SSL + +#if defined(BOOST_ASIO_HAS_LOCAL_SOCKETS) + +class local_stream_fixture : public stream_fixture { +private: + const std::string path_{CMAKE_BINARY_DIR "/tmp/stream-test.sock"}; + +public: + + /** + * \copydoc io_fixture::create_acceptor + */ + auto create_acceptor() -> std::unique_ptr<acceptor> override + { + return std::make_unique<local_acceptor>(service_, path_); } /** @@ -142,26 +168,29 @@ */ auto create_connector() -> std::unique_ptr<connector> override { - return std::make_unique<tls_connector<>>(context(context::sslv23), service_, endpoint_); + return std::make_unique<local_connector>(service_, path_); } }; -#endif // !IRCCD_HAVE_SSL +#if defined(IRCCD_HAVE_SSL) -#if !BOOST_OS_WINDOWS +class tls_local_stream_fixture : public stream_fixture { +private: + const std::string path_{CMAKE_BINARY_DIR "/tmp/stream-test.sock"}; -class local_stream_fixture : public stream_fixture { public: + /** * \copydoc io_fixture::create_acceptor */ auto create_acceptor() -> std::unique_ptr<acceptor> override { - std::remove(CMAKE_BINARY_DIR "/tmp/io-test.sock"); + context context(context::tlsv12); - stream_protocol::acceptor acceptor(service_, CMAKE_BINARY_DIR "/tmp/io-test.sock"); + context.use_certificate_file(TESTS_SOURCE_DIR "/data/test.crt", context::pem); + context.use_private_key_file(TESTS_SOURCE_DIR "/data/test.key", context::pem); - return std::make_unique<local_acceptor>(std::move(acceptor)); + return std::make_unique<tls_acceptor<local_acceptor>>(std::move(context), service_, path_); } /** @@ -169,11 +198,13 @@ */ auto create_connector() -> std::unique_ptr<connector> override { - return std::make_unique<local_connector>(service_, CMAKE_BINARY_DIR "/tmp/io-test.sock"); + return std::make_unique<tls_connector<local_connector>>(context(context::tlsv12), service_, path_); } }; -#endif // !BOOST_OS_WINDOWS +#endif // !IRCCD_HAVE_SSL + +#endif // !BOOST_ASIO_HAS_LOCAL_SOCKETS /** * List of fixtures to tests. @@ -181,10 +212,13 @@ using list = boost::mpl::list< ip_stream_fixture #if defined(IRCCD_HAVE_SSL) - , ssl_stream_fixture + , tls_ip_stream_fixture #endif -#if !BOOST_OS_WINDOWS +#if defined(BOOST_ASIO_HAS_LOCAL_SOCKETS) , local_stream_fixture +# if defined(IRCCD_HAVE_SSL) + , tls_local_stream_fixture +# endif #endif >; @@ -198,25 +232,25 @@ }; fixture.init(); - fixture.stream1_->read([] (auto code, auto message) { + fixture.stream1_->recv([] (auto code, auto message) { BOOST_TEST(!code); BOOST_TEST(message.is_object()); BOOST_TEST(message["abc"].template get<int>() == 123); BOOST_TEST(message["def"].template get<int>() == 456); }); - fixture.stream2_->write(message, [] (auto code) { + fixture.stream2_->send(message, [] (auto code) { BOOST_TEST(!code); }); fixture.service_.run(); } -BOOST_AUTO_TEST_CASE_TEMPLATE(network_down, Test, list) +BOOST_AUTO_TEST_CASE_TEMPLATE(connection_reset, Test, list) { Test fixture; fixture.init(); - fixture.stream1_->read([] (auto code, auto message) { - BOOST_TEST(code.value() == static_cast<int>(std::errc::not_connected)); + fixture.stream1_->recv([] (auto code, auto message) { + BOOST_TEST(code.value() == static_cast<int>(std::errc::connection_reset)); BOOST_TEST(message.is_null()); }); fixture.stream2_ = nullptr;