# HG changeset patch # User David Demelier # Date 1541348765 -3600 # Node ID 560b62f6b0a7966b569d8259d0e44465f74a7606 # Parent 317c66a131be821e0d084f5c04ea87c79159cce5 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. diff -r 317c66a131be -r 560b62f6b0a7 CHANGES.md --- 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: diff -r 317c66a131be -r 560b62f6b0a7 MIGRATING.md --- 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 diff -r 317c66a131be -r 560b62f6b0a7 irccdctl/main.cpp --- 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 #include @@ -28,16 +26,12 @@ #include #include +#include #include #include -#include #include #include -#if defined(IRCCD_HAVE_SSL) -# include -#endif - #include #include @@ -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 -{ - std::vector 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 { - 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>(std::move(ctx), service, endpoints); + return std::make_unique(std::move(ctx), + service, hostname, port, ipv4, ipv6); #else throw std::runtime_error("SSL disabled"); #endif } - return std::make_unique(service, endpoints); + return std::make_unique(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(std::move(ctx), service, it->get_value()); +#else + throw std::runtime_error("SSL disabled"); +#endif + } + return std::make_unique(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 +auto parse_connect_ip(std::string_view type, const option::result& options) -> std::unique_ptr { 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(service, resolve(host, port)); + // Type (-t or --type). + const auto ipv4 = type == "ip"; + const auto ipv6 = type == "ipv6"; + + return std::make_unique(service, hostname, port, ipv4, ipv6); } /* @@ -352,7 +346,7 @@ std::unique_ptr 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("/major"); - const auto minor = doc.get("/minor"); - const auto patch = doc.get("/patch"); + const auto major = doc.get("major"); + const auto minor = doc.get("minor"); + const auto patch = doc.get("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) { diff -r 317c66a131be -r 560b62f6b0a7 libirccd-core/CMakeLists.txt --- 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 ) diff -r 317c66a131be -r 560b62f6b0a7 libirccd-core/irccd/acceptor.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 + +#include #include #include #include -namespace irccd { +#include +#include -class stream; +#if defined(IRCCD_HAVE_SSL) +# include +#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 +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 + void accept(Socket& sc, Handler handler); +}; + +template +inline basic_socket_acceptor::basic_socket_acceptor(boost::asio::io_context& service) + : service_(service) + , acceptor_(service) +{ +} + +template +inline basic_socket_acceptor::basic_socket_acceptor(boost::asio::io_context& service, Acceptor acceptor) noexcept + : service_(service) + , acceptor_(std::move(acceptor)) +{ +} + +template +inline auto basic_socket_acceptor::get_service() const noexcept -> const boost::asio::io_context& +{ + return service_; +} + +template +inline auto basic_socket_acceptor::get_service() noexcept -> boost::asio::io_context& +{ + return service_; +} + +template +inline auto basic_socket_acceptor::get_acceptor() const noexcept -> const Acceptor& +{ + return acceptor_; +} + +template +inline auto basic_socket_acceptor::get_acceptor() noexcept -> Acceptor& +{ + return acceptor_; +} + +template +template +inline void basic_socket_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 { +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(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 { +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(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 + * 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 +class tls_acceptor : public acceptor { +private: + using socket_type = typename SocketAcceptor::socket_type; + + std::shared_ptr context_; + SocketAcceptor acceptor_; + +public: + /** + * Construct a secure layer transport server. + * + * \param context the SSL context + * \param args the arguments to SocketAcceptor constructor + */ + template + tls_acceptor(boost::asio::ssl::context context, Args&&... args); + + /** + * \copydoc acceptor::accept + */ + void accept(handler handler) override; +}; + +template +template +inline tls_acceptor::tls_acceptor(boost::asio::ssl::context context, Args&&... args) + : context_(std::make_shared(std::move(context))) + , acceptor_(std::forward(args)...) +{ +} + +template +inline void tls_acceptor::accept(handler handler) +{ + auto client = std::make_shared>(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; + +#if defined(BOOST_ASIO_HAS_LOCAL_SOCKETS) + +/** + * \brief Convenient alias. + */ +using tls_local_acceptor = tls_acceptor; + +#endif // !BOOST_ASIO_HAS_LOCAL_SOCKETS + +#endif // !IRCCD_HAVE_SSL + +// }}} + } // !irccd #endif // !IRCCD_ACCEPTOR_HPP diff -r 317c66a131be -r 560b62f6b0a7 libirccd-core/irccd/connector.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 #include #include #include -namespace irccd { +#include + +#if defined(IRCCD_HAVE_SSL) +# include +#endif -class stream; +#include + +#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 + 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 + void connect(Socket& sc, Handler handler); + + /** + * \copydoc connector::connect + */ + void connect(handler handler); +}; + +template +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 +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(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 + 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 +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(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 +class tls_connector : public connector { +public: + using socket_type = typename SocketConnector::socket_type; + +private: + std::shared_ptr context_; + SocketConnector connector_; + +public: + /** + * Construct a secure layer transport server. + * + * \param context the SSL context + * \param args the arguments to SocketConnector constructor + */ + template + tls_connector(boost::asio::ssl::context context, Args&&... args); + + /** + * \copydoc socket_connector::connect + */ + void connect(handler handler) override; +}; + +template +template +inline tls_connector::tls_connector(boost::asio::ssl::context context, Args&&... args) + : context_(std::make_shared(std::move(context))) + , connector_(std::forward(args)...) +{ +} + +template +inline void tls_connector::connect(handler handler) +{ + using boost::asio::ssl::stream_base; + + assert(handler); + + auto stream = std::make_shared>(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; + +#if defined(BOOST_ASIO_HAS_LOCAL_SOCKETS) + +/** + * \brief Convenient alias. + */ +using tls_local_connector = tls_connector; + +#endif // !BOOST_ASIO_HAS_LOCAL_SOCKETS + +#endif // !IRCCD_HAVE_SSL + +// }}} + } // !irccd #endif // !IRCCD_CONNECTOR_HPP diff -r 317c66a131be -r 560b62f6b0a7 libirccd-core/irccd/socket_acceptor.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 - * - * 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 - -#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 -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 - 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 -template -void socket_acceptor::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 -socket_acceptor::socket_acceptor(acceptor acceptor) noexcept - : acceptor_(std::move(acceptor)) -{ - assert(acceptor_.is_open()); -} - -template -auto socket_acceptor::get_acceptor() const noexcept -> const acceptor& -{ - return acceptor_; -} - -template -auto socket_acceptor::get_acceptor() noexcept -> acceptor& -{ - return acceptor_; -} - -template -void socket_acceptor::accept(handler handler) -{ - assert(handler); - - const auto client = std::make_shared>(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; - -#if !BOOST_OS_WINDOWS - -/** - * Convenient Unix acceptor type. - */ -using local_acceptor = socket_acceptor; - -#endif - -} // !irccd - -#endif // !IRCCD_SOCKET_ACCEPTOR_HPP diff -r 317c66a131be -r 560b62f6b0a7 libirccd-core/irccd/socket_connector.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 - * - * 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 - -#include - -#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 -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 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 - 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 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 -template -void socket_connector::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 -socket_connector::socket_connector(boost::asio::io_service& service, endpoint endpoint) noexcept - : service_(service) - , endpoints_{std::move(endpoint)} -{ -} - -template -socket_connector::socket_connector(boost::asio::io_service& service, std::vector eps) noexcept - : service_(service) - , endpoints_(std::move(eps)) -{ -} - -template -auto socket_connector::get_io_service() const noexcept -> const boost::asio::io_service& -{ - return service_; -} - -template -auto socket_connector::get_io_service() noexcept -> boost::asio::io_service& -{ - return service_; -} - -template -void socket_connector::connect(handler handler) -{ - assert(handler); - - const auto stream = std::make_shared>(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; - -#if !BOOST_OS_WINDOWS - -/** - * Convenient Unix conncetor type. - */ -using local_connector = socket_connector; - -#endif - -} // !irccd - -#endif // !IRCCD_SOCKET_CONNECTOR_HPP diff -r 317c66a131be -r 560b62f6b0a7 libirccd-core/irccd/socket_stream.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 - * - * 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 - -#include -#include -#include -#include - -#include -#include - -#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 -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 - 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 -void socket_stream::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 -void socket_stream::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 -template -socket_stream::socket_stream(Args&&... args) - : socket_(std::forward(args)...) -{ -} - -template -auto socket_stream::get_socket() const noexcept -> const Socket& -{ - return socket_; -} - -template -auto socket_stream::get_socket() noexcept -> Socket& -{ - return socket_; -} - -template -void socket_stream::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 -void socket_stream::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; - -#if !BOOST_OS_WINDOWS - -/** - * Convenient Unix stream type. - */ -using local_stream = socket_stream; - -#endif - -} // !irccd - -#endif // !IRCCD_SOCKET_STREAM_HPP diff -r 317c66a131be -r 560b62f6b0a7 libirccd-core/irccd/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 + +#include +#include #include +#include +#include #include +#include + +#include + +#if defined(IRCCD_HAVE_SSL) +# include +#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; + using recv_handler = std::function; /** * \brief Write completion handler. */ - using write_handler = std::function; + using send_handler = std::function; /** * 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 + 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 + 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 +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 +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 +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 +inline basic_socket_stream::basic_socket_stream(boost::asio::io_context& ctx) + : socket_(ctx) +{ +} + +template +inline auto basic_socket_stream::get_socket() const noexcept -> const Socket& +{ + return socket_; +} + +template +inline auto basic_socket_stream::get_socket() noexcept -> Socket& +{ + return socket_; +} + +template +inline void basic_socket_stream::recv(recv_handler handler) +{ + socket_stream_base::recv(socket_, handler); +} + +template +inline void basic_socket_stream::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; + +// }}} + +// {{{ 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; + +#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 +class tls_stream : public socket_stream_base { +private: + boost::asio::ssl::stream socket_; + std::shared_ptr context_; + +public: + /** + * Constructor. + * + * \param service the I/O service + * \param ctx the shared context + */ + tls_stream(boost::asio::io_context& service, std::shared_ptr ctx); + + /** + * Get the SSL socket. + * + * \return the socket + */ + auto get_socket() const noexcept -> const boost::asio::ssl::stream&; + + /** + * Overloaded function. + * + * \return the socket + */ + auto get_socket() noexcept -> boost::asio::ssl::stream&; + + /** + * \copydoc stream::recv + */ + void recv(recv_handler handler) override; + + /** + * \copydoc stream::send + */ + void send(const nlohmann::json& json, send_handler handler) override; +}; + +template +inline tls_stream::tls_stream(boost::asio::io_context& service, std::shared_ptr ctx) + : socket_(service, *ctx) + , context_(std::move(ctx)) +{ +} + +template +inline auto tls_stream::get_socket() const noexcept -> const boost::asio::ssl::stream& +{ + return socket_; +} + +template +inline auto tls_stream::get_socket() noexcept -> boost::asio::ssl::stream& +{ + return socket_; +} + +template +inline void tls_stream::recv(recv_handler handler) +{ + socket_stream_base::recv(socket_, handler); +} + +template +inline void tls_stream::send(const nlohmann::json& json, send_handler handler) +{ + socket_stream_base::send(json, socket_, handler); +} + +#endif // !IRCCD_HAVE_SSL + +// }}} + } // !irccd #endif // !IRCCD_STREAM_HPP diff -r 317c66a131be -r 560b62f6b0a7 libirccd-core/irccd/tls_acceptor.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 - * - * 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 - -#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 -class tls_acceptor : public socket_acceptor { -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 - tls_acceptor(boost::asio::ssl::context context, Args&&... args); - - /** - * \copydoc acceptor::accept - */ - void accept(acceptor::handler handler) override; -}; - -template -void tls_acceptor::accept(acceptor::handler handler) -{ - assert(handler); - - auto client = std::make_shared>(this->get_acceptor().get_io_service(), this->context_); - - socket_acceptor::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 -template -tls_acceptor::tls_acceptor(boost::asio::ssl::context context, Args&&... args) - : socket_acceptor(std::forward(args)...) - , context_(std::move(context)) -{ -} - -} // !irccd - -#endif // !IRCCD_HAVE_SSL - -#endif // !IRCCD_TLS_ACCEPTOR_HPP diff -r 317c66a131be -r 560b62f6b0a7 libirccd-core/irccd/tls_connector.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 - * - * 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 - -#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 -class tls_connector : public socket_connector { -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 constructor - */ - template - tls_connector(boost::asio::ssl::context context, Args&&... args); - - /** - * \copydoc socket_connector::connect - */ - void connect(connector::handler handler) override; -}; - -template -template -tls_connector::tls_connector(boost::asio::ssl::context context, Args&&... args) - : socket_connector(std::forward(args)...) - , context_(std::move(context)) -{ -} - -template -void tls_connector::connect(connector::handler handler) -{ - using boost::asio::ssl::stream_base; - using socket = typename Protocol::socket; - - assert(handler); - - const auto stream = std::make_shared>(this->get_io_service(), context_); - - socket_connector::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 diff -r 317c66a131be -r 560b62f6b0a7 libirccd-core/irccd/tls_stream.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 - * - * 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 - -#if defined(IRCCD_HAVE_SSL) - -#include - -#include "socket_stream.hpp" - -namespace irccd { - -/** - * \brief TLS/SSL streams. - * \tparam Socket the Boost.Asio compatible socket. - */ -template -class tls_stream : public socket_stream> { -public: - /** - * Constructor. - * - * \param args the arguments to boost::asio::ssl::stream - */ - template - tls_stream(Args&&... args) - : socket_stream>(std::forward(args)...) - { - } -}; - -} // !irccd - -#endif // !IRCCD_HAVE_SSL - -#endif // !IRCCD_TLS_STREAM_HPP diff -r 317c66a131be -r 560b62f6b0a7 libirccd-ctl/irccd/ctl/controller.cpp --- 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) diff -r 317c66a131be -r 560b62f6b0a7 libirccd-ctl/irccd/ctl/controller.hpp --- 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 diff -r 317c66a131be -r 560b62f6b0a7 libirccd-test/irccd/test/cli_fixture.cpp --- 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 #include -#include +#include #include #include @@ -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(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( - std::make_unique(std::move(acceptor)))); + irccd_.transports().add(std::make_unique(std::move(acceptor))); server_->disconnect(); server_->clear(); } diff -r 317c66a131be -r 560b62f6b0a7 libirccd-test/irccd/test/command_fixture.cpp --- 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 -#include +#include +#include #include #include @@ -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(irccd_.get_service(), path); + auto connector = std::make_unique(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(std::move(connector)); + irccd_.transports().add(std::make_unique(std::move(acceptor))); - // 3. Create controller and transport server. - ctl_ = std::make_unique( - std::make_unique(ctx_, acc.local_endpoint())); - irccd_.transports().add(std::make_unique( - std::make_unique(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)); diff -r 317c66a131be -r 560b62f6b0a7 libirccd/irccd/daemon/server.cpp --- 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; diff -r 317c66a131be -r 560b62f6b0a7 libirccd/irccd/daemon/transport_client.cpp --- 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); diff -r 317c66a131be -r 560b62f6b0a7 libirccd/irccd/daemon/transport_client.hpp --- 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 parent_; std::shared_ptr stream_; - std::deque> queue_; + std::deque> 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 diff -r 317c66a131be -r 560b62f6b0a7 libirccd/irccd/daemon/transport_util.cpp --- 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 -#include - +#include #include #include -#include - -#if defined(IRCCD_HAVE_SSL) -# include -#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 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 +{ + assert(sc.get_key() == "transport"); + const auto port = ini_util::get_uint(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(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(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 +auto from_config_load_local(asio::io_service& service, const ini::section& sc) -> std::unique_ptr { 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( - std::make_unique>(std::move(ctx), std::move(acceptor))); -#endif - } - - return std::make_unique( - std::make_unique(std::move(acceptor))); -} - -auto from_config_load_unix(asio::io_service& service, const ini::section& sc) -> std::unique_ptr -{ - 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(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( - std::make_unique(std::move(acceptor))); + return std::make_unique(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; + std::unique_ptr 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(std::move(acceptor)); + transport->set_password(password); return transport; diff -r 317c66a131be -r 560b62f6b0a7 tests/src/libcommon/stream/CMakeLists.txt --- 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 ) diff -r 317c66a131be -r 560b62f6b0a7 tests/src/libcommon/stream/main.cpp --- 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 * @@ -23,15 +23,9 @@ #include -#include -#include -#include - -#if defined(IRCCD_HAVE_SSL) -# include -# include -# include -#endif // !IRCCD_HAVE_SSL +#include +#include +#include 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(std::move(acceptor)); + return std::make_unique(service_, std::move(acceptor)); } /** @@ -108,13 +98,16 @@ */ auto create_connector() -> std::unique_ptr override { - return std::make_unique(service_, endpoint_); + const auto hostname = "127.0.0.1"; + const auto port = std::to_string(endpoint_.port()); + + return std::make_unique(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 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>(std::move(context), std::move(acceptor)); + return std::make_unique>(std::move(context), service_, std::move(acceptor)); + } + + /** + * \copydoc io_fixture::create_connector + */ + auto create_connector() -> std::unique_ptr override + { + context context(context::tlsv12); + + const auto hostname = "127.0.0.1"; + const auto port = std::to_string(endpoint_.port()); + + return std::make_unique>(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 override + { + return std::make_unique(service_, path_); } /** @@ -142,26 +168,29 @@ */ auto create_connector() -> std::unique_ptr override { - return std::make_unique>(context(context::sslv23), service_, endpoint_); + return std::make_unique(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 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(std::move(acceptor)); + return std::make_unique>(std::move(context), service_, path_); } /** @@ -169,11 +198,13 @@ */ auto create_connector() -> std::unique_ptr override { - return std::make_unique(service_, CMAKE_BINARY_DIR "/tmp/io-test.sock"); + return std::make_unique>(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() == 123); BOOST_TEST(message["def"].template get() == 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(std::errc::not_connected)); + fixture.stream1_->recv([] (auto code, auto message) { + BOOST_TEST(code.value() == static_cast(std::errc::connection_reset)); BOOST_TEST(message.is_null()); }); fixture.stream2_ = nullptr;