Mercurial > code
changeset 603:05f830e0539b
Net: use Boost.Asio instead
author | David Demelier <markand@malikania.fr> |
---|---|
date | Fri, 02 Dec 2016 22:22:17 +0100 |
parents | f322e5fcc099 |
children | ff11ca6b0d55 |
files | CMakeLists.txt modules/net/CMakeLists.txt modules/net/doc/mainpage.cpp modules/net/net.hpp modules/net/test/main.cpp modules/net/test/test.crt modules/net/test/test.key |
diffstat | 7 files changed, 0 insertions(+), 4641 deletions(-) [+] |
line wrap: on
line diff
--- a/CMakeLists.txt Fri Dec 02 22:21:52 2016 +0100 +++ b/CMakeLists.txt Fri Dec 02 22:22:17 2016 +0100 @@ -50,7 +50,6 @@ endif () add_subdirectory(modules/js) -add_subdirectory(modules/net) add_subdirectory(modules/unicode) add_subdirectory(modules/xdg)
--- a/modules/net/CMakeLists.txt Fri Dec 02 22:21:52 2016 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,29 +0,0 @@ -# -# CMakeLists.txt -- code building for common code -# -# Copyright (c) 2013-2016 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. -# - -find_package(OpenSSL) - -if (OPENSSL_FOUND) - code_define_module( - NAME net - SOURCES net.hpp - LIBRARIES $<$<BOOL:${WIN32}>:ws2_32> ${OPENSSL_LIBRARIES} - INCLUDES ${OPENSSL_INCLUDE_DIR} - FLAGS DIRECTORY=\"${CMAKE_CURRENT_SOURCE_DIR}/test/\" - ) -endif ()
--- a/modules/net/doc/mainpage.cpp Fri Dec 02 22:21:52 2016 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,57 +0,0 @@ -/** - * @mainpage - * - * Welcome to the net library. - * - * ## Introduction - * - * Portable and convenient library for networking in C++14. - * - * ## What it is? - * - * A clean and portable set of classes and function wrapping the BSD sockets family. It provides an API as close as - * possible to the original BSD sockets functions. - * - * It is implemented using templates to make sockets as type safe as possible. - * - * This module also adds the following: - * - * - Generic synchronous multiplexer based on select, poll, epoll and kqueue, - * - Convenient object-oriented option system, - * - Convenient object-oriented address system, - * - Experimental [OpenSSL][] support. - * - * ## What it is not? - * - * This module is not a high-level network library. It does not add asynchronous stuff like [Boost.Asio][asio] does. It is - * targeted to people who like BSD sockets and want low level facilities. It only depends on C++11 standard library and - * optionally [OpenSSL][]. - * - * ## Requirements - * - * - C++11. - * - OpenSSL, optional. - * - * ## Installation - * - * Just copy the file net.hpp and add it to your project. - * - * ## Overview - * - * ```` - * #include "net.h" - * - * try { - * net::SocketTcpIpv4 sc; - * - * sc.bind(net::address::Ipv4("*", 8080)); - * sc.listen(64); - * } catch (const std::exception &ex) { - * std::cerr << ex.what() << std::endl; - * std::exit(1); - * } - * ```` - * - * [asio]: http://www.boost.org/doc/libs/1_60_0/doc/html/boost_asio.html - * [OpenSSL]: http://openssl.org - */
--- a/modules/net/net.hpp Fri Dec 02 22:21:52 2016 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,3746 +0,0 @@ -/* - * net.hpp -- portable C++ socket wrapper - * - * Copyright (c) 2013-2016 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 NET_HPP -#define NET_HPP - -/** - * \file net.hpp - * \brief Networking - * \author David Demelier <markand@malikania.fr> - */ - -/** - * \defgroup net-module-tcp Network TCP support - * \brief TCP support in the networking module. - */ - -/** - * \defgroup net-module-udp Network UDP support - * \brief UDP support in networking module. - */ - -/** - * \defgroup net-module-tls Network TLS support - * \brief TLS support in networking module. - */ - -/** - * \defgroup net-module-addresses Network predefined addresses - * \brief Predefined addresses for sockets - */ - -/** - * \defgroup net-module-options Network predefined options - * \brief Predefined options for sockets - */ - -/** - * \defgroup net-module-backends Network predefined backends - * \brief Predefined backends for Listener - */ - -/** - * \defgroup net-module-resolv Network resolver - * \brief Resolv functions - */ - -/** - * \page Networking Networking - * - * - \subpage net-options - * - \subpage net-concepts - */ - -/** - * \page net-options User options - * - * The user may set the following variables before compiling these files: - * - * # General options - * - * - **NET_NO_AUTO_INIT**: (bool) Set to 0 if you don't want Socket class to - * automatically calls init function and finish functions. - * - * - **NET_NO_SSL**: (bool) Set to 0 if you don't have access to OpenSSL - * library. - * - * - **NET_NO_AUTO_SSL_INIT**: (bool) Set to 0 if you don't want Socket class - * with Tls to automatically init the OpenSSL library. You will need to call - * ssl::init and ssl::finish. - * - * # General compatibility options. - * - * The following options are auto detected but you can override them if you - * want. - * - * - **NET_HAVE_INET_PTON**: (bool) Set to 1 if you have inet_pton function. - * True for all platforms and Windows - * if _WIN32_WINNT is greater or equal to 0x0600. - * - * - **NET_HAVE_INET_NTOP**: (bool) Same as above. - * - * **Note:** On Windows, it is highly encouraged to set _WIN32_WINNT to at least - * 0x0600 on MinGW. - * - * # Options for Listener class - * - * Feature detection, multiple implementations may be avaible, for example, - * Linux has poll, select and epoll. - * - * We assume that `select(2)` is always available. - * - * Of course, you can set the variables yourself if you test it with your build - * system. - * - * - **NET_HAVE_POLL**: Defined on all BSD, Linux. Also defined on Windows - * if _WIN32_WINNT is set to 0x0600 or greater. - * - **NET_HAVE_KQUEUE**: Defined on all BSD and Apple. - * - **NET_HAVE_EPOLL**: Defined on Linux only. - * - **NET_DEFAULT_BACKEND**: Which backend to use (e.g. `Select`). - * - * The preference priority is ordered from left to right. - * - * | System | Backend | Class name | - * |---------------|-------------------------|--------------| - * | Linux | epoll(7) | Epoll | - * | *BSD | kqueue(2) | Kqueue | - * | Windows | poll(2), select(2) | Poll, Select | - * | Mac OS X | kqueue(2) | Kqueue | - */ - -/** - * \page net-concepts Concepts - * - * - \subpage net-concept-backend - * - \subpage net-concept-option - * - \subpage net-concept-stream - * - \subpage net-concept-datagram - */ - -/** - * \page net-concept-backend Backend (Concept) - * - * A backend is an interface for the Listener class. It is primarily designed to - * be the most suitable for the host environment. - * - * The backend must be default constructible, it is highly encouraged to be move - * constructible. - * - * This concepts requires the following functions: - * - * # name - * - * Get the backend name, informational only. - * - * ## Synopsis - * - * ```` - * std::string name() const; - * ```` - * - * ## Returns - * - * The backend name. - * - * # set - * - * Set one or more condition for the given handle. - * - * This erase previous flags if any. - * - * ## Synopsis - * - * ```` - * void set(const ListenerTable &table, Handle handle, Condition condition, - * bool add); - * ```` - * - * ## Arguments - * - * - **table**: the current table of sockets, - * - **handle**: the handle to set, - * - **condition**: the condition to add (may be OR'ed), - * - **add**: hint set to true if the handle is not currently registered. - * - * # unset - * - * Unset one or more condition for the given handle. - * - * ## Synopsis - * - * ```` - * void unset(const ListenerTable &table, Handle handle, Condition condition, - * bool remove); - * ```` - * - * ## Arguments - * - * - **table**: the current table of sockets, - * - **handle**: the handle to update, - * - **condition**: the condition to remove (may be OR'ed), - * - **remove**: hint set to true if the handle will be completely removed. - * - * # wait - * - * Wait for multiple sockets to be ready. - * - * ## Synopsis - * - * ```` - * std::vector<ListenerStatus> wait(const ListenerTable &table, int ms); - * ```` - * - * ## Arguments - * - * - **table**: the current table, - * - **ms**: the number to wait in milliseconds, negative means forever. - * - * ## Returns - * - * The list of sockets ready paired to their condition. - */ - -/** - * \page net-concept-option Option (Concept) - * - * An option can be set or get from a socket. - * - * If an operation is not available, provides the function but throws an - * exception with ENOSYS message. - * - * This concepts requires the following functions: - * - * # Option (constructor) - * - * At least one default constructor must be present. - * - * ## Synopsis - * - * ```` - * Option() noexcept; - * ```` - * - * # set - * - * Set the option. - * - * ## Synopsis - * - * ```` - * template <typename Address> - * void set(Socket &sc) const; - * ```` - * - * ## Arguments - * - * - **sc**: the socket. - * - * # get - * - * Get an option, T can be any type. - * - * ## Synopsis - * - * ```` - * template <typename Address> - * T get(Socket &sc) const; - * ```` - * - * ## Arguments - * - * - **sc**: the socket. - * - * ## Returns - * - * The value. - */ - -/** - * \page net-concept-stream Stream (Concept) - * - * This concepts requires the following functions: - * - * # type - * - * Return the type of socket, usually `SOCK_STREAM`. - * - * ## Synopsis - * - * ```` - * int type() const noexcept; - * ```` - * - * ## Returns - * - * The type of socket. - * - * # connect - * - * Connect to the given address. - * - * ## Synopsis - * - * ```` - * void connect(const sockaddr *address, socklen_t length); // (0) - * void connect(const Address &address); // 1 (Optional) - * ```` - * - * ## Arguments - * - * - **address**: the address, - * - **length**: the address length. - * - * ## Throws - * - * - net::WouldBlockError: if the operation would block, - * - net::Error: on other error. - * - * # accept - * - * Accept a new client. - * - * If no pending connection is available and operation would block, the - * implementation must throw WouldBlockError. Any other error can be thrown - * otherwise a valid socket must be returned. - * - * ## Synopsis - * - * ```` - * Socket accept(); - * ```` - * - * ## Returns - * - * The new socket. - * - * ## Throws - * - * - net::WouldBlockError: if the operation would block, - * - net::Error: on other error. - * - * # recv - * - * Receive data. - * - * ## Synopsis - * - * ```` - * unsigned recv(void *data, unsigned length); - * ```` - * - * ## Arguments - * - * - **data**: the destination buffer, - * - **length**: the destination buffer length. - * - * ## Returns - * - * The number of bytes sent. - * - * ## Throws - * - * - net::WouldBlockError: if the operation would block, - * - net::Error: on other error. - * - * # send - * - * Send data. - * - * ## Synopsis - * - * ```` - * unsigned send(const void *data, unsigned length); - * ```` - * - * ## Arguments - * - * - **data**: the data to send, - * - **length**: the data length. - * - * ## Returns - * - * The number of bytes sent. - * - * ## Throws - * - * - net::WouldBlockError: if the operation would block, - * - net::Error: on other error. - */ - -/** - * \page net-concept-datagram Datagram (Concept) - * - * This concepts requires the following functions: - * - * # type - * - * Return the type of socket, usually `SOCK_DGRAM`. - * - * ## Synopsis - * - * ```` - * int type() const noexcept; - * ```` - * - * ## Returns - * - * The type of socket. - * - * # recvfrom - * - * Receive data. - * - * ## Synopsis - * - * ```` - * unsigned recvfrom(void *data, unsigned length, sockaddr *address, - * socklen_t *addrlen); - * unsigned recvfrom(void *data, unsigned length, Address *source) - * ```` - * - * ## Arguments - * - * - **data**: the data, - * - **length**: the length, - * - **address**: the source address, - * - **addrlen**: the source address in/out length. - * - * ## Returns - * - * The number of bytes received. - * - * ## Throws - * - * - net::WouldBlockError: if the operation would block, - * - net::Error: on other error. - * - * # sendto - * - * ```` - * unsigned sendto(const void *data, unsigned length, const sockaddr *address, - * socklen_t addrlen); - * unsigned sendto(const void *data, unsigned length, const Address &address); - * ```` - * - * ## Arguments - * - * - **data**: the data to send, - * - **length**: the data length, - * - **address**: the destination address, - * - **addrlen**: the destination address length. - * - * ## Returns - * - * The number of bytes sent. - * - * ## Throws - * - * - net::WouldBlockError: if the operation would block, - * - net::Error: on other error. - */ - -/* - * Headers to include. - * ------------------------------------------------------------------ - */ - -/* - * Include Windows headers before because it brings _WIN32_WINNT if not - * specified by the user. - */ -#if defined(_WIN32) -# include <WinSock2.h> -# include <WS2tcpip.h> -#else -# include <sys/ioctl.h> -# include <sys/types.h> -# include <sys/socket.h> -# include <sys/un.h> - -# include <arpa/inet.h> - -# include <netinet/in.h> -# include <netinet/tcp.h> - -# include <fcntl.h> -# include <netdb.h> -# include <unistd.h> -#endif - -#include <algorithm> -#include <atomic> -#include <cassert> -#include <cerrno> -#include <chrono> -#include <climits> -#include <cstddef> -#include <cstdlib> -#include <cstring> -#include <iterator> -#include <memory> -#include <mutex> -#include <string> -#include <unordered_map> -#include <vector> - -#if !defined(NET_NO_SSL) - -#include <openssl/err.h> -#include <openssl/evp.h> -#include <openssl/ssl.h> - -#endif // !NET_NO_SSL - -/* - * Determine which I/O multiplexing implementations are available. - * ------------------------------------------------------------------ - * - * May define the following: - * - * - NET_HAVE_EPOLL - * - NET_HAVE_KQUEUE - * - NET_HAVE_POLL - */ - -#if defined(_WIN32) -# if _WIN32_WINNT >= 0x0600 && !defined(NET_HAVE_POLL) -# define NET_HAVE_POLL -# endif -#elif defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__) -# if !defined(NET_HAVE_KQUEUE) -# define NET_HAVE_KQUEUE -# endif -# if !defined(NET_HAVE_POLL) -# define NET_HAVE_POLL -# endif -#elif defined(__linux__) -# if !defined(NET_HAVE_EPOLL) -# define NET_HAVE_EPOLL -# endif -# if !defined(NET_HAVE_POLL) -# define NET_HAVE_POLL -# endif -#endif - -/* - * Compatibility macros. - * ------------------------------------------------------------------ - */ - -/** - * \brief Tells if inet_pton is available - */ -#if !defined(NET_HAVE_INET_PTON) -# if defined(_WIN32) -# if _WIN32_WINNT >= 0x0600 -# define NET_HAVE_INET_PTON -# endif -# else -# define NET_HAVE_INET_PTON -# endif -#endif - -/** - * \brief Tells if inet_ntop is available - */ -#if !defined(NET_HAVE_INET_NTOP) -# if defined(_WIN32) -# if _WIN32_WINNT >= 0x0600 -# define NET_HAVE_INET_NTOP -# endif -# else -# define NET_HAVE_INET_NTOP -# endif -#endif - -/* - * Define NET_DEFAULT_BACKEND. - * ------------------------------------------------------------------ - * - * Define the default I/O multiplexing implementation to use if not specified. - */ - -/** - * \brief Defines the default backend - */ -#if defined(_WIN32) -# if !defined(NET_DEFAULT_BACKEND) -# if defined(NET_HAVE_POLL) -# define NET_DEFAULT_BACKEND Poll -# else -# define NET_DEFAULT_BACKEND Select -# endif -# endif -#elif defined(__linux__) -# include <sys/epoll.h> - -# if !defined(NET_DEFAULT_BACKEND) -# define NET_DEFAULT_BACKEND Epoll -# endif -#elif defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__DragonFly__) || defined(__APPLE__) -# include <sys/types.h> -# include <sys/event.h> -# include <sys/time.h> - -# if !defined(NET_DEFAULT_BACKEND) -# define NET_DEFAULT_BACKEND Kqueue -# endif -#else -# if !defined(NET_DEFAULT_BACKEND) -# define NET_DEFAULT_BACKEND Select -# endif -#endif - -#if defined(NET_HAVE_POLL) && !defined(_WIN32) -# include <poll.h> -#endif - -/** - * The network namespace. - */ -namespace net { - -/* - * Portables types. - * ------------------------------------------------------------------ - * - * The following types are defined differently between Unix and Windows. - */ - -#if defined(_WIN32) - -/** - * Socket type, SOCKET. - */ -using Handle = SOCKET; - -/** - * Argument to pass to set. - */ -using ConstArg = const char *; - -/** - * Argument to pass to get. - */ -using Arg = char *; - -#else - -/** - * Socket type, int. - */ -using Handle = int; - -/** - * Argument to pass to set. - */ -using ConstArg = const void *; - -/** - * Argument to pass to get. - */ -using Arg = void *; - -#endif - -/* - * Portable constants. - * ------------------------------------------------------------------ - * - * These constants are needed to check functions return codes, they are rarely - * needed in end user code. - */ - -#if defined(_WIN32) - -/** - * Socket creation failure or invalidation. - */ -const Handle Invalid{INVALID_SOCKET}; - -/** - * Socket operation failure. - */ -const int Failure{SOCKET_ERROR}; - -#else - -/** - * Socket creation failure or invalidation. - */ -const Handle Invalid{-1}; - -/** - * Socket operation failure. - */ -const int Failure{-1}; - -#endif - -/** - * Close the socket library. - */ -inline void finish() noexcept -{ -#if defined(_WIN32) - WSACleanup(); -#endif -} - -/** - * Initialize the socket library. Except if you defined NET_NO_AUTO_INIT, you - * don't need to call this - * function manually. - */ -inline void init() noexcept -{ -#if defined(_WIN32) - static std::atomic<bool> initialized; - static std::mutex mutex; - - std::lock_guard<std::mutex> lock(mutex); - - if (!initialized) { - initialized = true; - - WSADATA wsa; - WSAStartup(MAKEWORD(2, 2), &wsa); - - /* - * If NET_NO_AUTO_INIT is not set then the user must also call finish - * himself. - */ -#if !defined(NET_NO_AUTO_INIT) - atexit(finish); -#endif - } -#endif -} - -/** - * Get the last system error. - * - * \param errn the error number (errno or WSAGetLastError) - * \return the error - */ -inline std::string error(int errn) -{ -#if defined(_WIN32) - LPSTR str = nullptr; - std::string errmsg = "Unknown error"; - - FormatMessageA( - FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, - NULL, - errn, - MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), - (LPSTR)&str, 0, NULL); - - - if (str) { - errmsg = std::string(str); - LocalFree(str); - } - - return errmsg; -#else - return strerror(errn); -#endif -} - -/** - * Get the last socket system error. The error is set from errno or from - * WSAGetLastError on Windows. - * - * \return a string message - */ -inline std::string error() -{ -#if defined(_WIN32) - return error(WSAGetLastError()); -#else - return error(errno); -#endif -} - -#if !defined(NET_NO_SSL) - -/** - * \brief SSL namespace - */ -namespace ssl { - -/** - * \enum Method - * \brief Which OpenSSL method to use. - */ -enum Method { - Tlsv1, //!< TLS v1.2 (recommended) - Sslv3 //!< SSLv3 -}; - -/** - * Initialize the OpenSSL library. Except if you defined NET_NO_AUTO_SSL_INIT, - * you don't need to call this function manually. - */ -inline void init() noexcept -{ - static std::atomic<bool> initialized; - static std::mutex mutex; - - std::lock_guard<std::mutex> lock(mutex); - - if (!initialized) { - initialized = true; - - SSL_library_init(); - SSL_load_error_strings(); - OpenSSL_add_all_algorithms(); - -#if !defined(NET_NO_AUTO_SSL_INIT) - atexit(finish); -#endif - } -} - -/** - * Close the OpenSSL library. - */ -inline void finish() noexcept -{ - ERR_free_strings(); -} - -} // !ssl - -#endif // !NET_NO_SSL - -/* - * Error class. - * ------------------------------------------------------------------ - * - * This is the main exception thrown on socket operations. - */ - -/** - * \brief Base class for sockets error. - */ -class Error : public std::exception { -private: - std::string m_message; - -public: - /** - * Construct the error using the specified error from the system. - * - * \param code the error code - * \warning the code must be a Winsock error or errno on Unix - */ - inline Error(int code) noexcept - : m_message(error(code)) - { - } - - /** - * Construct the error using the custom message. - * - * \param message the message - */ - inline Error(std::string message) noexcept - : m_message(std::move(message)) - { - } - - /** - * Construct the error using the last message from the system. - */ - inline Error() noexcept -#if defined(_WIN32) - : Error(WSAGetLastError()) -#else - : Error(errno) -#endif - { - } - - /** - * Get the error (only the error content). - * - * \return the error - */ - const char *what() const noexcept override - { - return m_message.c_str(); - } -}; - -/** - * \brief Timeout occured. - */ -class TimeoutError : public std::exception { -public: - /** - * Get the error message. - * - * \return the message - */ - const char *what() const noexcept override - { - return std::strerror(ETIMEDOUT); - } -}; - -/** - * \brief Operation would block. - */ -class WouldBlockError : public std::exception { -public: - /** - * Get the error message. - * - * \return the message - */ - const char *what() const noexcept override - { - return std::strerror(EWOULDBLOCK); - } -}; - -/** - * \brief Operation requires sending data to complete. - */ -class WantWriteError : public std::exception { -public: - /** - * Get the error message. - * - * \return the message - */ - const char *what() const noexcept override - { - return "operation requires writing to complete"; - } -}; - -/** - * \brief Operation requires reading data to complete. - */ -class WantReadError : public std::exception { -public: - /** - * Get the error message. - * - * \return the message - */ - const char *what() const noexcept override - { - return "operation requires read to complete"; - } -}; - -/* - * Condition enum - * ------------------------------------------------------------------ - * - * Defines if we must wait for reading or writing. - */ - -/** - * \enum Condition - * \brief Define the required condition for the socket. - */ -enum class Condition { - None, //!< No condition is required - Readable = (1 << 0), //!< The socket must be readable - Writable = (1 << 1), //!< The socket must be writable - All = 0x3 //! Both are requested -}; - -/** - * Apply bitwise XOR. - * - * \param v1 the first value - * \param v2 the second value - * \return the new value - */ -inline Condition operator^(Condition v1, Condition v2) noexcept -{ - return static_cast<Condition>(static_cast<int>(v1) ^ static_cast<int>(v2)); -} - -/** - * Apply bitwise AND. - * - * \param v1 the first value - * \param v2 the second value - * \return the new value - */ -inline Condition operator&(Condition v1, Condition v2) noexcept -{ - return static_cast<Condition>(static_cast<int>(v1) & static_cast<int>(v2)); -} - -/** - * Apply bitwise OR. - * - * \param v1 the first value - * \param v2 the second value - * \return the new value - */ -inline Condition operator|(Condition v1, Condition v2) noexcept -{ - return static_cast<Condition>(static_cast<int>(v1) | static_cast<int>(v2)); -} - -/** - * Apply bitwise NOT. - * - * \param v the value - * \return the complement - */ -inline Condition operator~(Condition v) noexcept -{ - return static_cast<Condition>(~static_cast<int>(v)); -} - -/** - * Assign bitwise OR. - * - * \param v1 the first value - * \param v2 the second value - * \return the new value - */ -inline Condition &operator|=(Condition &v1, Condition v2) noexcept -{ - v1 = static_cast<Condition>(static_cast<int>(v1) | static_cast<int>(v2)); - - return v1; -} - -/** - * Assign bitwise AND. - * - * \param v1 the first value - * \param v2 the second value - * \return the new value - */ -inline Condition &operator&=(Condition &v1, Condition v2) noexcept -{ - v1 = static_cast<Condition>(static_cast<int>(v1) & static_cast<int>(v2)); - - return v1; -} - -/** - * Assign bitwise XOR. - * - * \param v1 the first value - * \param v2 the second value - * \return the new value - */ -inline Condition &operator^=(Condition &v1, Condition v2) noexcept -{ - v1 = static_cast<Condition>(static_cast<int>(v1) ^ static_cast<int>(v2)); - - return v1; -} - -/** - * \brief Generic socket address storage. - * \ingroup net-module-addresses - */ -class Address { -private: - sockaddr_storage m_storage; - socklen_t m_length; - -public: - /** - * Construct empty address. - */ - inline Address() noexcept - : m_storage{} - , m_length(0) - { - } - - /** - * Construct address from existing one. - * - * \pre address != nullptr - * \param address the address - * \param length the address length - */ - inline Address(const sockaddr *address, socklen_t length) noexcept - : m_length(length) - { - assert(address); - - std::memcpy(&m_storage, address, length); - } - - /** - * Get the underlying address. - * - * \return the address - */ - inline const sockaddr *get() const noexcept - { - return reinterpret_cast<const sockaddr *>(&m_storage); - } - - /** - * Overloaded function - * - * \return the address - */ - inline sockaddr *get() noexcept - { - return reinterpret_cast<sockaddr *>(&m_storage); - } - - /** - * Get the underlying address as the given type (e.g sockaddr_in). - * - * \return the address reference - */ - template <typename T> - inline const T &as() const noexcept - { - return reinterpret_cast<const T &>(m_storage); - } - - /** - * Overloaded function - * - * \return the address reference - */ - template <typename T> - inline T &as() noexcept - { - return reinterpret_cast<T &>(m_storage); - } - - /** - * Get the underlying address length. - * - * \return the length - */ - inline socklen_t length() const noexcept - { - return m_length; - } - - /** - * Get the address domain. - * - * \return the domain - */ - inline int domain() const noexcept - { - return m_storage.ss_family; - } -}; - -/** - * \brief Address iterator. - * \ingroup net-module-addresses - * \see resolve - * - * This iterator can be used to try to connect to an host. - * - * When you use resolve with unspecified domain or socket type, the function may - * retrieve several different addresses that you can iterate over to try to - * connect to. - * - * Example: - * - * ````cpp - * SocketTcp sc; - * AddressIterator end, it = resolve("hostname.test", "80"); - * - * while (!connected_condition && it != end) - * sc.connect(it->address(), it->length()); - * ```` - * - * When an iterator equals to a default constructed iterator, it is considered - * not dereferenceable. - */ -class AddressIterator : public std::iterator<std::forward_iterator_tag, Address> { -private: - std::vector<Address> m_addresses; - std::size_t m_index{0}; - -public: - /** - * Construct a null iterator. - * - * The default constructed iterator is not dereferenceable. - */ - inline AddressIterator() noexcept = default; - - /** - * Construct an address iterator with a set of addresses. - * - * \pre index < m_addresses.size() - * \param addresses the addresses - * \param index the first index - */ - inline AddressIterator(std::vector<Address> addresses, std::size_t index = 0) noexcept - : m_addresses(std::move(addresses)) - , m_index(index) - { - assert(index < m_addresses.size()); - } - - /** - * Get the generic address. - * - * \pre this is dereferenceable - * \return the generic address - */ - inline const Address &operator*() const noexcept - { - assert(m_index <= m_addresses.size()); - - return m_addresses[m_index]; - } - - /** - * Overloaded function. - * - * \pre this is dereferenceable - * \return the generic address - */ - inline Address &operator*() noexcept - { - assert(m_index <= m_addresses.size()); - - return m_addresses[m_index]; - } - - /** - * Get the generic address. - * - * \pre this is dereferenceable - * \return the generic address - */ - inline const Address *operator->() const noexcept - { - assert(m_index <= m_addresses.size()); - - return &m_addresses[m_index]; - } - - /** - * Overloaded function. - * - * \pre this is dereferenceable - * \return the generic address - */ - inline Address *operator->() noexcept - { - assert(m_index <= m_addresses.size()); - - return &m_addresses[m_index]; - } - - /** - * Pre-increment the iterator. - * - * \return this - */ - inline AddressIterator &operator++(int) noexcept - { - if (m_index + 1 >= m_addresses.size()) { - m_addresses.clear(); - m_index = 0; - } else - m_index ++; - - return *this; - } - - /** - * Post-increment the iterator. - * - * \return copy of this - */ - inline AddressIterator operator++() noexcept - { - AddressIterator save = *this; - - if (m_index + 1 >= m_addresses.size()) { - m_addresses.clear(); - m_index = 0; - } else - m_index ++; - - return save; - } - - friend bool operator==(const AddressIterator &, const AddressIterator &) noexcept; - friend bool operator!=(const AddressIterator &, const AddressIterator &) noexcept; -}; - -/** - * Compare two address iterators. - * - * \param i1 the first iterator - * \param i2 the second iterator - * \return true if they equal - */ -inline bool operator==(const AddressIterator &i1, const AddressIterator &i2) noexcept -{ - return i1.m_addresses == i2.m_addresses && i1.m_index == i2.m_index; -} - -/** - * Compare two address iterators. - * - * \param i1 the first iterator - * \param i2 the second iterator - * \return false if they equal - */ -inline bool operator!=(const AddressIterator &i1, const AddressIterator &i2) noexcept -{ - return !(i1 == i2); -} - -/** - * Compare two generic addresses. - * - * \param a1 the first address - * \param a2 the second address - * \return true if they equal - */ -inline bool operator==(const Address &a1, const Address &a2) noexcept -{ - return a1.length() == a2.length() && std::memcmp(a1.get(), a2.get(), a1.length()) == 0; -} - -/** - * Compare two generic addresses. - * - * \param a1 the first address - * \param a2 the second address - * \return false if they equal - */ -inline bool operator!=(const Address &a1, const Address &a2) noexcept -{ - return !(a1 == a2); -} - -/** - * \brief Base socket class. - */ -class Socket { -protected: - /** - * The native handle. - */ - Handle m_handle{Invalid}; - -public: - /** - * Create a socket handle. - * - * This is the primary function and the only one that creates the socket - * handle, all other constructors are just overloaded functions. - * - * \param domain the domain AF_* - * \param type the type SOCK_* - * \param protocol the protocol - * \throw Error on errors - */ - Socket(int domain, int type, int protocol) - { -#if !defined(NET_NO_AUTO_INIT) - init(); -#endif - m_handle = ::socket(domain, type, protocol); - - if (m_handle == Invalid) - throw Error(); - } - - /** - * Create the socket with an already defined handle and its protocol. - * - * \param handle the handle - */ - explicit inline Socket(Handle handle) noexcept - : m_handle(handle) - { - } - - /** - * Create an invalid socket. Can be used when you cannot instanciate the - * socket immediately. - */ - explicit inline Socket(std::nullptr_t) noexcept - : m_handle(Invalid) - { - } - - /** - * Copy constructor deleted. - */ - Socket(const Socket &) = delete; - - /** - * Transfer ownership from other to this. - * - * \param other the other socket - */ - inline Socket(Socket &&other) noexcept - : m_handle(other.m_handle) - { - other.m_handle = Invalid; - } - - /** - * Default destructor. - */ - virtual ~Socket() - { - close(); - } - - /** - * Tells if the socket is not invalid. - * - * \return true if not invalid - */ - inline bool isOpen() const noexcept - { - return m_handle != Invalid; - } - - /** - * Set an option for the socket. Wrapper of setsockopt(2). - * - * \pre isOpen() - * \param level the setting level - * \param name the name - * \param arg the value - * \throw Error on errors - */ - template <typename Argument> - inline void set(int level, int name, const Argument &arg) - { - assert(m_handle != Invalid); - - if (::setsockopt(m_handle, level, name, (ConstArg)&arg, sizeof (arg)) == Failure) - throw Error(); - } - - /** - * Object-oriented option setter. - * - * The object must have `set(Socket &) const`. - * - * \pre isOpen() - * \param option the option - * \throw Error on errors - */ - template <typename Option> - inline void set(const Option &option) - { - assert(m_handle != Invalid); - - option.set(*this); - } - - /** - * Get an option for the socket. Wrapper of getsockopt(2). - * - * \pre isOpen() - * \param level the setting level - * \param name the name - * \return the value - * \throw Error on errors - */ - template <typename Argument> - Argument get(int level, int name) - { - assert(m_handle != Invalid); - - Argument desired, result{}; - socklen_t size = sizeof (result); - - if (::getsockopt(m_handle, level, name, (Arg)&desired, &size) == Failure) - throw Error(); - - std::memcpy(&result, &desired, size); - - return result; - } - - /** - * Object-oriented option getter. - * - * The object must have `T get(Socket &) const`, T can be any type and it is - * the value returned from this function. - * - * \pre isOpen() - * \return the same value as get() in the option - * \throw Error on errors - */ - template <typename Option> - inline auto get() -> decltype(std::declval<Option>().get(*this)) - { - assert(m_handle != Invalid); - - return Option().get(*this); - } - - /** - * Get the native handle. - * - * \return the handle - * \warning Not portable - */ - inline Handle handle() const noexcept - { - return m_handle; - } - - /** - * Bind using a native address. - * - * \pre isOpen() - * \param address the address - * \param length the size - * \throw Error on errors - */ - inline void bind(const sockaddr *address, socklen_t length) - { - assert(m_handle != Invalid); - - if (::bind(m_handle, address, length) == Failure) - throw Error(); - } - - /** - * Overload that takes an address. - * - * \pre isOpen() - * \param address the address - * \throw Error on errors - */ - inline void bind(const Address &address) - { - assert(m_handle != Invalid); - - bind(address.get(), address.length()); - } - - /** - * Listen for pending connection. - * - * \pre isOpen() - * \param max the maximum number - * \throw Error on errors - */ - inline void listen(int max = 128) - { - assert(m_handle != Invalid); - - if (::listen(m_handle, max) == Failure) - throw Error(); - } - - /** - * Get the local name. This is a wrapper of getsockname(). - * - * \pre isOpen() - * \return the address - * \throw Error on failures - */ - Address getsockname() const - { - assert(m_handle != Invalid); - - sockaddr_storage ss; - socklen_t length = sizeof (sockaddr_storage); - - if (::getsockname(m_handle, reinterpret_cast<sockaddr *>(&ss), &length) == Failure) - throw Error(); - - return Address(reinterpret_cast<sockaddr *>(&ss), length); - } - - /** - * Get connected address. This is a wrapper for getpeername(). - * - * \pre isOpen() - * \return the address - * \throw Error on failures - */ - Address getpeername() const - { - assert(m_handle != Invalid); - - sockaddr_storage ss; - socklen_t length = sizeof (sockaddr_storage); - - if (::getpeername(m_handle, reinterpret_cast<sockaddr *>(&ss), &length) == Failure) - throw Error(); - - return Address(reinterpret_cast<sockaddr *>(&ss), length); - } - - /** - * Close the socket. - * - * Automatically called from the destructor. - */ - void close() - { - if (m_handle != Invalid) { -#if defined(_WIN32) - ::closesocket(m_handle); -#else - ::close(m_handle); -#endif - m_handle = Invalid; - } - } - - /** - * Assignment operator forbidden. - * - * \return *this - */ - Socket &operator=(const Socket &) = delete; - - /** - * Transfer ownership from other to this. The other socket is left - * invalid and will not be closed. - * - * \param other the other socket - * \return this - */ - Socket &operator=(Socket &&other) noexcept - { - m_handle = other.m_handle; - other.m_handle = Invalid; - - return *this; - } -}; - -/** - * Compare two sockets. - * - * \param s1 the first socket - * \param s2 the second socket - * \return true if they equals - */ -inline bool operator==(const Socket &s1, const Socket &s2) -{ - return s1.handle() == s2.handle(); -} - -/** - * Compare two sockets. - * - * \param s1 the first socket - * \param s2 the second socket - * \return true if they are different - */ -inline bool operator!=(const Socket &s1, const Socket &s2) -{ - return s1.handle() != s2.handle(); -} - -/** - * Compare two sockets. - * - * \param s1 the first socket - * \param s2 the second socket - * \return true if s1 < s2 - */ -inline bool operator<(const Socket &s1, const Socket &s2) -{ - return s1.handle() < s2.handle(); -} - -/** - * Compare two sockets. - * - * \param s1 the first socket - * \param s2 the second socket - * \return true if s1 > s2 - */ -inline bool operator>(const Socket &s1, const Socket &s2) -{ - return s1.handle() > s2.handle(); -} - -/** - * Compare two sockets. - * - * \param s1 the first socket - * \param s2 the second socket - * \return true if s1 <= s2 - */ -inline bool operator<=(const Socket &s1, const Socket &s2) -{ - return s1.handle() <= s2.handle(); -} - -/** - * Compare two sockets. - * - * \param s1 the first socket - * \param s2 the second socket - * \return true if s1 >= s2 - */ -inline bool operator>=(const Socket &s1, const Socket &s2) -{ - return s1.handle() >= s2.handle(); -} - -/** - * \brief Clear TCP implementation. - * \ingroup net-module-tcp - * - * This is the basic TCP protocol that implements recv, send, connect and accept - * as wrappers of the usual C functions. - */ -class TcpSocket : public Socket { -public: - /** - * Inherited constructors. - */ - using Socket::Socket; - - /** - * Construct a TCP socket. - * - * \param domain the domain - * \param protocol the protocol - * \throw Error on errors - */ - inline TcpSocket(int domain, int protocol) - : Socket(domain, SOCK_STREAM, protocol) - { - } - - /** - * Get the type of the socket. - * - * \return the type - */ - inline int type() const noexcept - { - return SOCK_STREAM; - } - - /** - * Initiate connection. - * - * \param address the address - * \param length the address length - * \throw WouldBlockError if the socket is marked non-blocking and - * connection cannot be established immediately - * \throw Error on other errors - */ - void connect(const sockaddr *address, socklen_t length) - { - if (::connect(this->m_handle, address, length) == Failure) { -#if defined(_WIN32) - int error = WSAGetLastError(); - - if (error == WSAEWOULDBLOCK) - throw WouldBlockError(); - else - throw Error(error); -#else - if (errno == EINPROGRESS) - throw WouldBlockError(); - else - throw Error(); -#endif - } - } - - /** - * Overloaded function. - * - * \param address the address - * \throw WouldBlockError if the socket is marked non-blocking and - * connection cannot be established immediately - * \throw Error on other errors - */ - void connect(const Address &address) - { - connect(address.get(), address.length()); - } - - /** - * Accept a new client. - * - * If there are no pending connection, an invalid socket is returned. - * - * \return the new socket - * \throw WouldBlockError if the socket is marked non-blocking and no - * connection are available - * \throw Error on other errors - */ - TcpSocket accept() - { - Handle handle = ::accept(this->m_handle, nullptr, 0); - - if (handle == Invalid) { -#if defined(_WIN32) - int error = WSAGetLastError(); - - if (error == WSAEWOULDBLOCK) - throw WouldBlockError(); - else - throw Error(error); -#else - if (errno == EWOULDBLOCK || errno == EAGAIN) - throw WouldBlockError(); - else - throw Error(); -#endif - } - - return TcpSocket(handle); - } - - /** - * Receive some data. - * - * \param data the destination buffer - * \param length the buffer length - * \return the number of bytes received - */ - unsigned recv(void *data, unsigned length) - { - int max = length > INT_MAX ? INT_MAX : static_cast<int>(length); - int nbread = ::recv(this->m_handle, (Arg)data, max, 0); - - if (nbread == Failure) { -#if defined(_WIN32) - int error = WSAGetLastError(); - - if (error == WSAEWOULDBLOCK) - throw WouldBlockError(); - else - throw Error(error); -#else - if (errno == EAGAIN || errno == EWOULDBLOCK) - throw WouldBlockError(); - else - throw Error(); -#endif - } - - return static_cast<unsigned>(nbread); - } - - /** - * Send some data. - * - * \param data the data to send - * \param length the length - * \return the number of bytes sent - */ - unsigned send(const void *data, unsigned length) - { - int max = length > INT_MAX ? INT_MAX : static_cast<int>(length); - int nbsent = ::send(this->m_handle, (ConstArg)data, max, 0); - - if (nbsent == Failure) { -#if defined(_WIN32) - int error = WSAGetLastError(); - - if (error == WSAEWOULDBLOCK) - throw WouldBlockError(); - else - throw Error(); -#else - if (errno == EAGAIN || errno == EWOULDBLOCK) - throw WouldBlockError(); - else - throw Error(); -#endif - } - - return static_cast<unsigned>(nbsent); - } -}; - -/** - * \brief Clear UDP type. - * - * This class is the basic implementation of UDP sockets. - */ -class UdpSocket : public Socket { -public: - /** - * Inherited constructors. - */ - using Socket::Socket; - - /** - * Construct a TCP socket. - * - * \param domain the domain - * \param protocol the protocol - * \throw Error on errors - */ - inline UdpSocket(int domain, int protocol) - : Socket(domain, SOCK_DGRAM, protocol) - { - } - - /** - * Get the type of the socket. - * - * \return the type - */ - inline int type() const noexcept - { - return SOCK_DGRAM; - } - - /** - * Receive some data. - * - * \param data the data - * \param length the length - * \param address the source address - * \param addrlen the source address in/out length - * \return the number of bytes received - * \throw WouldBlockError if the socket is marked non-blocking and the - * operation would block - * \throw Error on other errors - */ - unsigned recvfrom(void *data, unsigned length, sockaddr *address, socklen_t *addrlen) - { - int max = length > INT_MAX ? INT_MAX : static_cast<int>(length); - int nbread = ::recvfrom(this->m_handle, (Arg)data, max, 0, address, addrlen); - - if (nbread == Failure) { -#if defined(_WIN32) - int error = WSAGetLastError(); - - if (error == EWOULDBLOCK) - throw WouldBlockError(); - else - throw Error(error); -#else - if (errno == EAGAIN || errno == EWOULDBLOCK) - throw WouldBlockError(); - else - throw Error(); -#endif - } - - return static_cast<unsigned>(nbread); - } - - /** - * Overloaded function. - * - * \param data the data - * \param length the length - * \param source the source information (optional) - * \throw WouldBlockError if the socket is marked non-blocking and the - * operation would block - * \throw Error on other errors - */ - inline unsigned recvfrom(void *data, unsigned length, Address *source = nullptr) - { - sockaddr_storage st; - socklen_t socklen = sizeof (sockaddr_storage); - - auto nr = recvfrom(data, length, reinterpret_cast<sockaddr *>(&st), &socklen); - - if (source) - *source = Address(reinterpret_cast<const sockaddr *>(&st), socklen); - - return nr; - } - - /** - * Send some data. - * - * \param data the data to send - * \param length the data length - * \param address the destination address - * \param addrlen the destination address length - * \return the number of bytes sent - * \throw WouldBlockError if the socket is marked non-blocking and the - * operation would block - * \throw Error on other errors - */ - unsigned sendto(const void *data, unsigned length, const sockaddr *address, socklen_t addrlen) - { - int max = length > INT_MAX ? INT_MAX : static_cast<int>(length); - int nbsent = ::sendto(this->m_handle, (ConstArg)data, max, 0, address, addrlen); - - if (nbsent == Failure) { -#if defined(_WIN32) - int error = WSAGetLastError(); - - if (error == EWOULDBLOCK) - throw WouldBlockError(); - else - throw Error(error); -#else - if (errno == EAGAIN || errno == EWOULDBLOCK) - throw WouldBlockError(); - else - throw Error(); -#endif - } - - return static_cast<unsigned>(nbsent); - } - - /** - * Overloaded function - * - * \param data the data to send - * \param length the data length - * \param address the destination address - * \return the number of bytes sent - * \throw WouldBlockError if the socket is marked non-blocking and the - * operation would block - * \throw Error on other errors - */ - inline unsigned sendto(const void *data, unsigned length, const Address &address) - { - return sendto(data, length, address.get(), address.length()); - } -}; - -#if !defined(NET_NO_SSL) - -/** - * \brief Experimental TLS support. - * \ingroup net-module-tls - * \warning This class is highly experimental. - */ -class TlsSocket : public Socket { -public: - /** - * \brief SSL connection mode. - */ - enum Mode { - Server, //!< Use Server when you accept a socket server side, - Client //!< Use Client when you connect to a server. - }; - -private: - using Context = std::shared_ptr<SSL_CTX>; - using Ssl = std::unique_ptr<SSL, void (*)(SSL *)>; - - // Determine if we created a TlsSocket from a temporary or a lvalue. - bool m_mustclose{false}; - - Context m_context; - Ssl m_ssl{nullptr, nullptr}; - - inline std::string error() - { - BIO *bio = BIO_new(BIO_s_mem()); - char *buf = nullptr; - - ERR_print_errors(bio); - - std::size_t length = BIO_get_mem_data (bio, &buf); - std::string result(buf, length); - - BIO_free(bio); - - return result; - } - - template <typename Function> - void wrap(Function &&function) - { - auto ret = function(); - - if (ret <= 0) { - int no = SSL_get_error(m_ssl.get(), ret); - - switch (no) { - case SSL_ERROR_WANT_READ: - throw WantReadError(); - case SSL_ERROR_WANT_WRITE: - throw WantWriteError(); - default: - throw Error(error()); - } - } - } - - void create(Mode mode, const SSL_METHOD *method) - { -#if !defined(NET_NO_SSL_AUTO_INIT) - ssl::init(); -#endif - m_context = Context(SSL_CTX_new(method), SSL_CTX_free); - m_ssl = Ssl(SSL_new(m_context.get()), SSL_free); - - SSL_set_fd(m_ssl.get(), this->m_handle); - - if (mode == Server) - SSL_set_accept_state(m_ssl.get()); - else - SSL_set_connect_state(m_ssl.get()); - } - -public: - /** - * Create a socket around an existing one. - * - * The original socket is moved to this instance and must not be used - * anymore. - * - * \param sock the TCP socket - * \param mode the mode - * \param method the method - */ - TlsSocket(TcpSocket &&sock, Mode mode = Server, const SSL_METHOD *method = TLSv1_method()) - : Socket(std::move(sock)) - , m_mustclose(true) - { - create(mode, method); - } - - /** - * Wrap a socket around an existing one without taking ownership. - * - * The original socket must still exist until this TlsSocket is closed. - * - * \param sock the TCP socket - * \param mode the mode - * \param method the method - */ - TlsSocket(TcpSocket &sock, Mode mode = Server, const SSL_METHOD *method = TLSv1_method()) - : Socket(sock.handle()) - { - create(mode, method); - } - - /** - * Destroy the socket if owned. - */ - ~TlsSocket() - { - /** - * If the socket has been created from a rvalue this class owns the - * socket and will close it in the parent destructor. - * - * Otherwise, when created from a lvalue, mark this socket as invalid - * to avoid double close'ing it as two sockets points to the same - * descriptor. - */ - if (!m_mustclose) - m_handle = Invalid; - } - - /** - * Get the type of socket. - * - * \return the type - */ - inline int type() const noexcept - { - return SOCK_STREAM; - } - - /** - * Use the specified private key file. - * - * \param file the path to the private key - * \param type the type of file - */ - inline void setPrivateKey(std::string file, int type = SSL_FILETYPE_PEM) - { - if (SSL_use_PrivateKey_file(m_ssl.get(), file.c_str(), type) != 1) - throw Error(error()); - } - - /** - * Use the specified certificate file. - * - * \param file the path to the file - * \param type the type of file - */ - inline void setCertificate(std::string file, int type = SSL_FILETYPE_PEM) - { - if (SSL_use_certificate_file(m_ssl.get(), file.c_str(), type) != 1) - throw Error(error()); - } - - /** - * Do handshake, needed in some case when you have non blocking sockets. - */ - void handshake() - { - wrap([&] () -> int { - return SSL_do_handshake(m_ssl.get()); - }); - } - - /** - * Receive some data. - * - * \param data the destination buffer - * \param length the buffer length - * \return the number of bytes received - */ - unsigned recv(void *data, unsigned length) - { - int max = length > INT_MAX ? INT_MAX : static_cast<int>(length); - int nbread = 0; - - wrap([&] () -> int { - return (nbread = SSL_read(m_ssl.get(), data, max)); - }); - - return static_cast<unsigned>(nbread < 0 ? 0 : nbread); - } - - /** - * Send some data. - * - * \param data the data to send - * \param length the length - * \return the number of bytes sent - */ - unsigned send(const void *data, unsigned length) - { - int max = length > INT_MAX ? INT_MAX : static_cast<int>(length); - int nbsent = 0; - - wrap([&] () -> int { - return (nbsent = SSL_write(m_ssl.get(), data, max)); - }); - - return static_cast<unsigned>(nbsent < 0 ? 0 : nbsent); - } -}; - -#endif // !NET_NO_SSL - -/** - * \brief IPv4 functions. - */ -namespace ipv4 { - -/** - * Create an address to bind on any. - * - * \param port the port - * \return the address - */ -inline Address any(std::uint16_t port) -{ - sockaddr_in sin; - socklen_t length = sizeof (sockaddr_in); - - std::memset(&sin, 0, sizeof (sockaddr_in)); - sin.sin_family = AF_INET; - sin.sin_addr.s_addr = INADDR_ANY; - sin.sin_port = htons(port); - - return Address(reinterpret_cast<const sockaddr *>(&sin), length); -} - -/** - * Create an address from a IPv4 string. - * - * \param ip the ip address - * \param port the port - * \return the address - * \throw Error if inet_pton is unavailable - */ -inline Address pton(const std::string &ip, std::uint16_t port) -{ -#if defined(NET_HAVE_INET_PTON) -#if !defined(NET_NO_AUTO_INIT) - init(); -#endif - - sockaddr_in sin; - socklen_t length = sizeof (sockaddr_in); - - std::memset(&sin, 0, sizeof (sockaddr_in)); - sin.sin_family = AF_INET; - sin.sin_port = htons(port); - - if (inet_pton(AF_INET, ip.c_str(), &sin.sin_addr) <= 0) - throw Error(); - - return Address(reinterpret_cast<const sockaddr *>(&sin), length); -#else - (void)ip; - (void)port; - - throw Error(std::strerror(ENOSYS)); -#endif -} - -/** - * Get the underlying ip from the given address. - * - * \pre address.domain() == AF_INET - * \param address the IPv6 address - * \return the ip address - * \throw Error if inet_ntop is unavailable - */ -inline std::string ntop(const Address &address) -{ - assert(address.domain() == AF_INET); - -#if !defined(NET_NO_AUTO_INIT) - init(); -#endif - -#if defined(NET_HAVE_INET_NTOP) - char result[INET_ADDRSTRLEN + 1]; - - std::memset(result, 0, sizeof (result)); - - if (!inet_ntop(AF_INET, const_cast<in_addr *>(&address.as<sockaddr_in>().sin_addr), result, sizeof (result))) - throw Error(); - - return result; -#else - (void)address; - - throw Error(std::strerror(ENOSYS)); -#endif -} - -/** - * Get the port from the IPv4 address. - * - * \pre address.domain() == AF_INET4 - * \param address the address - * \return the port - */ -inline std::uint16_t port(const Address &address) noexcept -{ - assert(address.domain() == AF_INET); - - return ntohs(address.as<sockaddr_in>().sin_port); -} - -} // !ipv4 - -/** - * \brief IPv6 functions. - */ -namespace ipv6 { - -/** - * Create an address to bind on any. - * - * \param port the port - * \return the address - */ -inline Address any(std::uint16_t port) -{ - sockaddr_in6 sin6; - socklen_t length = sizeof (sockaddr_in6); - - std::memset(&sin6, 0, sizeof (sockaddr_in6)); - sin6.sin6_family = AF_INET6; - sin6.sin6_addr = in6addr_any; - sin6.sin6_port = htons(port); - - return Address(reinterpret_cast<const sockaddr *>(&sin6), length); -} - -/** - * Create an address from a IPv4 string. - * - * \param ip the ip address - * \param port the port - * \return the address - * \throw Error if inet_pton is unavailable - */ -inline Address pton(const std::string &ip, std::uint16_t port) -{ -#if defined(NET_HAVE_INET_PTON) -#if !defined(NET_NO_AUTO_INIT) - init(); -#endif - - sockaddr_in6 sin6; - socklen_t length = sizeof (sockaddr_in6); - - std::memset(&sin6, 0, sizeof (sockaddr_in6)); - sin6.sin6_family = AF_INET6; - sin6.sin6_port = htons(port); - - if (inet_pton(AF_INET6, ip.c_str(), &sin6.sin6_addr) <= 0) - throw Error(); - - return Address(reinterpret_cast<const sockaddr *>(&sin6), length); -#else - (void)ip; - (void)port; - - throw Error(std::strerror(ENOSYS)); -#endif -} - -/** - * Get the underlying ip from the given address. - * - * \pre address.domain() == AF_INET6 - * \param address the IPv6 address - * \return the ip address - * \throw Error if inet_ntop is unavailable - */ -inline std::string ntop(const Address &address) -{ - assert(address.domain() == AF_INET6); - -#if defined(NET_HAVE_INET_NTOP) -#if !defined(NET_NO_AUTO_INIT) - init(); -#endif - - char ret[INET6_ADDRSTRLEN]; - - std::memset(ret, 0, sizeof (ret)); - - if (!inet_ntop(AF_INET6, const_cast<in6_addr *>(&address.as<sockaddr_in6>().sin6_addr), ret, sizeof (ret))) - throw Error(); - - return ret; -#else - (void)address; - - throw Error(std::strerror(ENOSYS)); -#endif -} - -/** - * Get the port from the IPv6 address. - * - * \pre address.domain() == AF_INET6 - * \param address the address - * \return the port - */ -inline std::uint16_t port(const Address &address) noexcept -{ - assert(address.domain() == AF_INET6); - - return ntohs(address.as<sockaddr_in6>().sin6_port); -} - -} // !ipv6 - -#if !defined(_WIN32) - -/** - * \brief Unix domain functions. - */ -namespace local { - -/** - * Construct an address to a path. - * - * \pre !path.empty() - * \param path the path - * \param rm remove the file before (default: false) - */ -inline Address create(const std::string &path, bool rm = false) noexcept -{ - assert(!path.empty()); - - // Silently remove the file even if it fails. - if (rm) - remove(path.c_str()); - - sockaddr_un sun; - socklen_t length; - - std::memset(sun.sun_path, 0, sizeof (sun.sun_path)); - std::strncpy(sun.sun_path, path.c_str(), sizeof (sun.sun_path) - 1); - - sun.sun_family = AF_LOCAL; - -#if defined(NET_HAVE_SUN_LEN) - length = SUN_LEN(&sun); -#else - length = sizeof (sun); -#endif - - return Address(reinterpret_cast<const sockaddr *>(&sun), length); -} - -/** - * Get the path from the address. - * - * \pre address.domain() == AF_LOCAL - * \param address the local address - * \return the path to the socket file - */ -inline std::string path(const Address &address) -{ - assert(address.domain() == AF_LOCAL); - - return reinterpret_cast<const sockaddr_un *>(address.get())->sun_path; -} - -} // !local - -#endif // !_WIN32 - -/** - * \brief Predefined options. - */ -namespace option { - -/** - * \ingroup net-module-options - * \brief Set or get the blocking-mode for a socket. - * \warning On Windows, it's not possible to check if the socket is blocking or - * not. - */ -class SockBlockMode { -private: - bool m_value; - -public: - /** - * Create the option. - * - * By default the blocking mode is set to true. - * - * \param value set to true to make blocking sockets - */ - inline SockBlockMode(bool value = true) noexcept - : m_value(value) - { - } - - /** - * Set the option. - * - * \param sc the socket - * \throw Error on errors - */ - void set(Socket &sc) const - { -#if defined(O_NONBLOCK) && !defined(_WIN32) - int flags; - - if ((flags = fcntl(sc.handle(), F_GETFL, 0)) < 0) - flags = 0; - - if (m_value) - flags &= ~(O_NONBLOCK); - else - flags |= O_NONBLOCK; - - if (fcntl(sc.handle(), F_SETFL, flags) < 0) - throw Error(); -#else - unsigned long flags = (m_value) ? 0 : 1; - - if (ioctlsocket(sc.handle(), FIONBIO, &flags) == Failure) - throw Error(); -#endif - } - - /** - * Get the option. - * - * \param sc the socket - * \return the value - * \throw Error on errors - */ - bool get(Socket &sc) const - { -#if defined(O_NONBLOCK) && !defined(_WIN32) - int flags = fcntl(sc.handle(), F_GETFL, 0); - - if (flags < 0) - throw Error(); - - return !(flags & O_NONBLOCK); -#else - (void)sc; - - throw Error(std::strerror(ENOSYS)); -#endif - } -}; - -/** - * \ingroup net-module-options - * \brief Set or get the input buffer. - */ -class SockReceiveBuffer { -private: - int m_value; - -public: - /** - * Create the option. - * - * \param size the buffer size - */ - inline SockReceiveBuffer(int size = 2048) noexcept - : m_value(size) - { - } - - /** - * Set the option. - * - * \param sc the socket - * \throw Error on errors - */ - inline void set(Socket &sc) const - { - sc.set(SOL_SOCKET, SO_RCVBUF, m_value); - } - - /** - * Get the option. - * - * \param sc the socket - * \return the value - * \throw Error on errors - */ - inline int get(Socket &sc) const - { - return sc.get<int>(SOL_SOCKET, SO_RCVBUF); - } -}; - -/** - * \ingroup net-module-options - * \brief Reuse address, must be used before calling Socket::bind - */ -class SockReuseAddress { -private: - bool m_value; - -public: - /** - * Create the option. - * - * By default the option reuses the address. - * - * \param value set to true to reuse the address - */ - inline SockReuseAddress(bool value = true) noexcept - : m_value(value) - { - } - - /** - * Set the option. - * - * \param sc the socket - * \throw Error on errors - */ - inline void set(Socket &sc) const - { - sc.set(SOL_SOCKET, SO_REUSEADDR, m_value ? 1 : 0); - } - - /** - * Get the option. - * - * \param sc the socket - * \return the value - * \throw Error on errors - */ - inline bool get(Socket &sc) const - { - return sc.get<int>(SOL_SOCKET, SO_REUSEADDR) != 0; - } -}; - -/** - * \ingroup net-module-options - * \brief Set or get the output buffer. - */ -class SockSendBuffer { -private: - int m_value; - -public: - /** - * Create the option. - * - * \param size the buffer size - */ - inline SockSendBuffer(int size = 2048) noexcept - : m_value(size) - { - } - - /** - * Set the option. - * - * \param sc the socket - * \throw Error on errors - */ - inline void set(Socket &sc) const - { - sc.set(SOL_SOCKET, SO_SNDBUF, m_value); - } - - /** - * Get the option. - * - * \param sc the socket - * \return the value - * \throw Error on errors - */ - inline int get(Socket &sc) const - { - return sc.get<int>(SOL_SOCKET, SO_SNDBUF); - } -}; - -/** - * \ingroup net-module-options - * \brief Set this option if you want to disable nagle's algorithm. - */ -class TcpNoDelay { -private: - bool m_value; - -public: - /** - * Create the option. - * - * By default disable TCP delay. - * - * \param value set to true to disable TCP delay - */ - inline TcpNoDelay(bool value = true) noexcept - : m_value(value) - { - } - - /** - * Set the option. - * - * \param sc the socket - * \throw Error on errors - */ - inline void set(Socket &sc) const - { - sc.set(IPPROTO_TCP, TCP_NODELAY, m_value ? 1 : 0); - } - - /** - * Get the option. - * - * \param sc the socket - * \return the value - * \throw Error on errors - */ - inline bool get(Socket &sc) const - { - return sc.get<int>(IPPROTO_TCP, TCP_NODELAY) != 0; - } -}; - -/** - * \ingroup net-module-options - * \brief Control IPPROTO_IPV6/IPV6_V6ONLY - * - * Note: some systems may or not set this option by default so it's a good idea - * to set it in any case to either - * false or true if portability is a concern. - */ -class Ipv6Only { -private: - bool m_value; - -public: - /** - * Create the option. - * - * By default with want IPv6 only. - * - * \param value set to true to use IPv6 only - */ - inline Ipv6Only(bool value = true) noexcept - : m_value(value) - { - } - - /** - * Set the option. - * - * \param sc the socket - * \throw Error on errors - */ - inline void set(Socket &sc) const - { - sc.set(IPPROTO_IPV6, IPV6_V6ONLY, m_value ? 1 : 0); - } - - /** - * Get the option. - * - * \param sc the socket - * \return the value - * \throw Error on errors - */ - inline bool get(Socket &sc) const - { - return sc.get<int>(IPPROTO_IPV6, IPV6_V6ONLY) != 0; - } -}; - -} // !option - -/** - * \brief Result of polling - * - * Result of a select call, returns the first ready socket found with its - * flags. - */ -class ListenerStatus { -public: - Handle socket; //!< which socket is ready - Condition flags; //!< the flags -}; - -/** - * Table used in the socket listener to store which sockets have been - * set in which directions. - */ -using ListenerTable = std::unordered_map<Handle, Condition>; - -/** - * \brief Predefined backends for Listener. - */ -namespace backend { - -#if defined(NET_HAVE_EPOLL) - -/** - * \ingroup net-module-backends - * \brief Linux's epoll. - */ -class Epoll { -private: - int m_handle{-1}; - std::vector<epoll_event> m_events; - - Epoll(const Epoll &) = delete; - Epoll &operator=(const Epoll &) = delete; - - std::uint32_t toEpoll(Condition condition) const noexcept - { - std::uint32_t events = 0; - - if ((condition & Condition::Readable) == Condition::Readable) - events |= EPOLLIN; - if ((condition & Condition::Writable) == Condition::Writable) - events |= EPOLLOUT; - - return events; - } - - Condition toCondition(std::uint32_t events) const noexcept - { - Condition condition = Condition::None; - - if ((events & EPOLLIN) || (events & EPOLLHUP)) - condition |= Condition::Readable; - if (events & EPOLLOUT) - condition |= Condition::Writable; - - return condition; - } - - void update(Handle h, int op, int eflags) - { - epoll_event ev; - - std::memset(&ev, 0, sizeof (epoll_event)); - - ev.events = eflags; - ev.data.fd = h; - - if (epoll_ctl(m_handle, op, h, &ev) < 0) - throw Error(); - } - -public: - /** - * Create epoll. - * - * \throw Error on failures - */ - inline Epoll() - : m_handle(epoll_create1(0)) - { - if (m_handle < 0) - throw Error(); - } - - /** - * Move constructor. - * - * \param other the other backend - */ - inline Epoll(Epoll &&other) noexcept - : m_handle(other.m_handle) - { - other.m_handle = -1; - } - - /** - * Close the kqueue descriptor. - */ - inline ~Epoll() - { - if (m_handle != -1) - close(m_handle); - } - - /** - * Get the backend name. - * - * \return kqueue - */ - inline std::string name() const noexcept - { - return "epoll"; - } - - /** - * For set and unset, we need to apply the whole flags required, so if the - * socket was set to Connection::Readable and user **adds** - * Connection::Writable, we must set both. - * - * \param table the listener table - * \param h the handle - * \param condition the condition - * \param add set to true if the socket is new to the backend - * \throw Error on failures - */ - void set(const ListenerTable &table, Handle h, Condition condition, bool add) - { - if (add) { - update(h, EPOLL_CTL_ADD, toEpoll(condition)); - m_events.resize(m_events.size() + 1); - } else - update(h, EPOLL_CTL_MOD, toEpoll(table.at(h) | condition)); - } - - /** - * Unset is a bit complicated case because Listener tells us which - * flag to remove but to update epoll descriptor we need to pass - * the effective flags that we want to be applied. - * - * So we put the same flags that are currently effective and remove the - * requested one. - * - * \param table the listener table - * \param h the handle - * \param condition the condition - * \param add set to true if the socket is new to the backend - * \throw Error on failures - */ - void unset(const ListenerTable &table, Handle h, Condition condition, bool remove) - { - if (remove) { - update(h, EPOLL_CTL_DEL, 0); - m_events.resize(m_events.size() - 1); - } else - update(h, EPOLL_CTL_MOD, toEpoll(table.at(h) & ~(condition))); - } - - /** - * Wait for sockets to be ready. - * - * \param ms the milliseconds timeout - * \return the sockets ready - * \throw Error on failures - */ - std::vector<ListenerStatus> wait(const ListenerTable &, int ms) - { - int ret = epoll_wait(m_handle, m_events.data(), m_events.size(), ms); - std::vector<ListenerStatus> result; - - if (ret == 0) - throw TimeoutError(); - if (ret < 0) - throw Error(); - - for (int i = 0; i < ret; ++i) - result.push_back(ListenerStatus{m_events[i].data.fd, toCondition(m_events[i].events)}); - - return result; - } - - /** - * Move operator. - * - * \param other the other - * \return this - */ - inline Epoll &operator=(Epoll &&other) - { - m_handle = other.m_handle; - other.m_handle = -1; - - return *this; - } -}; - -#endif // !NET_HAVE_EPOLL - -#if defined(NET_HAVE_KQUEUE) - -/** - * \ingroup net-module-backends - * \brief Implements kqueue(2). - * - * This implementation is available on all BSD and Mac OS X. It is better than - * poll(2) because it's O(1), however it's a bit more memory consuming. - */ -class Kqueue { -private: - std::vector<struct kevent> m_result; - int m_handle; - - Kqueue(const Kqueue &) = delete; - Kqueue &operator=(const Kqueue &) = delete; - - void update(Handle h, int filter, int kflags) - { - struct kevent ev; - - EV_SET(&ev, h, filter, kflags, 0, 0, nullptr); - - if (kevent(m_handle, &ev, 1, nullptr, 0, nullptr) < 0) - throw Error(); - } - -public: - /** - * Create kqueue. - * - * \throw Error on failures - */ - inline Kqueue() - : m_handle(kqueue()) - { - if (m_handle < 0) - throw Error(); - } - - /** - * Move constructor. - * - * \param other the other backend - */ - inline Kqueue(Kqueue &&other) noexcept - : m_handle(other.m_handle) - { - other.m_handle = -1; - } - - /** - * Close the kqueue descriptor. - */ - inline ~Kqueue() - { - if (m_handle != -1) - close(m_handle); - } - - /** - * Get the backend name. - * - * \return kqueue - */ - inline std::string name() const noexcept - { - return "kqueue"; - } - - /** - * Set socket. - * - * \param h the handle - * \param condition the condition - * \param add set to true if the socket is new to the backend - * \throw Error on failures - */ - void set(const ListenerTable &, Handle h, Condition condition, bool add) - { - if ((condition & Condition::Readable) == Condition::Readable) - update(h, EVFILT_READ, EV_ADD | EV_ENABLE); - if ((condition & Condition::Writable) == Condition::Writable) - update(h, EVFILT_WRITE, EV_ADD | EV_ENABLE); - if (add) - m_result.resize(m_result.size() + 1); - } - - /** - * Unset socket. - * - * \param h the handle - * \param condition the condition - * \param remove set to true if the socket is completely removed - * \throw Error on failures - */ - void unset(const ListenerTable &, Handle h, Condition condition, bool remove) - { - if ((condition & Condition::Readable) == Condition::Readable) - update(h, EVFILT_READ, EV_DELETE); - if ((condition & Condition::Writable) == Condition::Writable) - update(h, EVFILT_WRITE, EV_DELETE); - if (remove) - m_result.resize(m_result.size() - 1); - } - - /** - * Wait for sockets to be ready. - * - * \param ms the milliseconds timeout - * \return the sockets ready - * \throw Error on failures - */ - std::vector<ListenerStatus> wait(const ListenerTable &, int ms) - { - std::vector<ListenerStatus> sockets; - timespec ts = { 0, 0 }; - timespec *pts = (ms <= 0) ? nullptr : &ts; - - ts.tv_sec = ms / 1000; - ts.tv_nsec = (ms % 1000) * 1000000; - - int nevents = kevent(m_handle, nullptr, 0, &m_result[0], m_result.capacity(), pts); - - if (nevents == 0) - throw TimeoutError(); - if (nevents < 0) - throw Error(); - - for (int i = 0; i < nevents; ++i) { - sockets.push_back(ListenerStatus{ - static_cast<Handle>(m_result[i].ident), - m_result[i].filter == EVFILT_READ ? Condition::Readable : Condition::Writable - }); - } - - return sockets; - } - - /** - * Move operator. - * - * \param other the other - * \return this - */ - inline Kqueue &operator=(Kqueue &&other) noexcept - { - m_handle = other.m_handle; - other.m_handle = -1; - - return *this; - } -}; - -#endif // !NET_HAVE_KQUEUE - -#if defined(NET_HAVE_POLL) - -/** - * \ingroup net-module-backends - * \brief Implements poll(2). - * - * Poll is widely supported and is better than select(2). It is still not the - * best option as selecting the sockets is O(n). - */ -class Poll { -private: - std::vector<pollfd> m_fds; - - short toPoll(Condition condition) const noexcept - { - short result = 0; - - if ((condition & Condition::Readable) == Condition::Readable) - result |= POLLIN; - if ((condition & Condition::Writable) == Condition::Writable) - result |= POLLOUT; - - return result; - } - - Condition toCondition(short &event) const noexcept - { - Condition condition = Condition::None; - - /* - * Poll implementations mark the socket differently regarding the - * disconnection of a socket. - * - * At least, even if POLLHUP or POLLIN is set, recv() always return 0 so - * we mark the socket as readable. - */ - if ((event & POLLIN) || (event & POLLHUP)) - condition |= Condition::Readable; - if (event & POLLOUT) - condition |= Condition::Writable; - - // Reset event for safety. - event = 0; - - return condition; - } - -public: - /** - * Get the backend name. - * - * \return kqueue - */ - inline std::string name() const noexcept - { - return "poll"; - } - - /** - * Set socket. - * - * \param h the handle - * \param condition the condition - * \param add set to true if the socket is new to the backend - * \throw Error on failures - */ - void set(const ListenerTable &, Handle h, Condition condition, bool add) - { - if (add) - m_fds.push_back(pollfd{h, toPoll(condition), 0}); - else { - auto it = std::find_if(m_fds.begin(), m_fds.end(), [&] (const pollfd &pfd) { - return pfd.fd == h; - }); - - it->events |= toPoll(condition); - } - } - - /** - * Unset socket. - * - * \param h the handle - * \param condition the condition - * \param remove set to true if the socket is completely removed - * \throw Error on failures - */ - void unset(const ListenerTable &, Handle h, Condition condition, bool remove) - { - auto it = std::find_if(m_fds.begin(), m_fds.end(), [&] (const pollfd &pfd) { - return pfd.fd == h; - }); - - if (remove) - m_fds.erase(it); - else - it->events &= ~(toPoll(condition)); - } - - /** - * Wait for sockets to be ready. - * - * \param ms the milliseconds timeout - * \return the sockets ready - * \throw Error on failures - */ - std::vector<ListenerStatus> wait(const ListenerTable &, int ms) - { -#if defined(_WIN32) - auto result = WSAPoll(m_fds.data(), (ULONG)m_fds.size(), ms); -#else - auto result = poll(m_fds.data(), m_fds.size(), ms); -#endif - - if (result == 0) - throw TimeoutError(); - if (result < 0) - throw Error(); - - std::vector<ListenerStatus> sockets; - - for (auto &fd : m_fds) - if (fd.revents != 0) - sockets.push_back(ListenerStatus{fd.fd, toCondition(fd.revents)}); - - return sockets; - } -}; - -#endif // !NET_HAVE_POLL - -/** - * \ingroup net-module-backends - * \brief Implements select(2) - * - * This class is the fallback of any other method, it is not preferred at all - * for many reasons. - */ -class Select { -public: - /** - * Get the backend name. - * - * \return select - */ - inline std::string name() const - { - return "select"; - } - - /** - * No-op. - */ - inline void set(const ListenerTable &, Handle, Condition, bool) noexcept - { - } - - /** - * No-op. - */ - inline void unset(const ListenerTable &, Handle, Condition, bool) noexcept - { - } - - /** - * Wait for sockets to be ready. - * - * \param table the listener table - * \param ms the milliseconds timeout - * \return the sockets ready - * \throw Error on failures - */ - std::vector<ListenerStatus> wait(const ListenerTable &table, int ms) - { - timeval maxwait, *towait; - fd_set readset; - fd_set writeset; - - FD_ZERO(&readset); - FD_ZERO(&writeset); - - Handle max = 0; - - for (const auto &pair : table) { - if ((pair.second & Condition::Readable) == Condition::Readable) - FD_SET(pair.first, &readset); - if ((pair.second & Condition::Writable) == Condition::Writable) - FD_SET(pair.first, &writeset); - if (pair.first > max) - max = pair.first; - } - - maxwait.tv_sec = 0; - maxwait.tv_usec = ms * 1000; - - // Set to nullptr for infinite timeout. - towait = (ms < 0) ? nullptr : &maxwait; - - auto error = ::select(static_cast<int>(max + 1), &readset, &writeset, nullptr, towait); - - if (error == Failure) - throw Error(); - if (error == 0) - throw TimeoutError(); - - std::vector<ListenerStatus> sockets; - - for (const auto &pair : table) { - if (FD_ISSET(pair.first, &readset)) - sockets.push_back(ListenerStatus{pair.first, Condition::Readable}); - if (FD_ISSET(pair.first, &writeset)) - sockets.push_back(ListenerStatus{pair.first, Condition::Writable}); - } - - return sockets; - } -}; - -} // !backend - -/** - * \brief Synchronous multiplexing - * - * Convenient wrapper around the select() system call. - * - * This class is implemented using a bridge pattern to allow different uses - * of listener implementation. - * - * You should not reinstanciate a new Listener at each iteartion of your - * main loop as it can be extremely costly. Instead use the same listener that - * you can safely modify on the fly. - * - * Currently, poll, epoll, select and kqueue are available. - */ -template <typename Backend = backend :: NET_DEFAULT_BACKEND> -class Listener { -private: - Backend m_backend; - ListenerTable m_table; - -public: - /** - * Construct an empty listener. - */ - Listener() = default; - - /** - * Get the backend. - * - * \return the backend - */ - inline const Backend &backend() const noexcept - { - return m_backend; - } - - /** - * Overloaded function. - * - * \return the backend - */ - inline Backend &backend() noexcept - { - return m_backend; - } - - /** - * Get the non-modifiable table. - * - * \return the table - */ - inline const ListenerTable &table() const noexcept - { - return m_table; - } - - /** - * Overloaded function. - * - * \return the iterator - */ - inline ListenerTable::const_iterator begin() const noexcept - { - return m_table.begin(); - } - - /** - * Overloaded function. - * - * \return the iterator - */ - inline ListenerTable::const_iterator cbegin() const noexcept - { - return m_table.cbegin(); - } - - /** - * Overloaded function. - * - * \return the iterator - */ - inline ListenerTable::const_iterator end() const noexcept - { - return m_table.end(); - } - - /** - * Overloaded function. - * - * \return the iterator - */ - inline ListenerTable::const_iterator cend() const noexcept - { - return m_table.cend(); - } - - /** - * Add or update a socket to the listener. - * - * If the socket is already placed with the appropriate flags, the - * function is a no-op. - * - * If incorrect flags are passed, the function does nothing. - * - * Previous flags are discarded. - * - * \param sc the socket - * \param condition the condition (may be OR'ed) - * \throw Error if the backend failed to set - */ - void reset(Handle sc, Condition condition) - { - if (condition == Condition::None || static_cast<int>(condition) > 0x3) - return; - - auto it = m_table.find(sc); - - // Do not update the table if the backend failed to add or update. - if (it == m_table.end()) { - m_backend.set(m_table, sc, condition, true); - m_table.emplace(sc, condition); - } else { - /* - * In this scenario, the socket exist in the registry with at least - * one flag, the user may call set with read, write or both flags. - * - * If one of Readable or Writable is requested, we remove the - * opposite one if present. - * - * If both are requested, we add the missing one. - */ - auto remove = it->second & ~(condition); - - if (remove != Condition::None) { - m_backend.unset(m_table, sc, remove, false); - it->second &= ~(remove); - } - - auto apply = it->second ^ condition; - - if (apply != Condition::None) { - m_backend.set(m_table, sc, it->second ^ condition, false); - it->second |= (it->second ^ condition); - } - } - } - - /** - * Remove one or more flags from the listener for the given socket. - * - * It's perfectly safe to try removing a flag that is not currently set, the - * listener simply ignores them. - * - * If the socket is not in the registry, this function is a no-op. - * - * \param sc the socket - * \param condition the condition (may be OR'ed) - * \see remove - */ - void unset(Handle sc, Condition condition) - { - if (condition == Condition::None || static_cast<int>(condition) > 0x3) - return; - - auto it = m_table.find(sc); - - if (it == m_table.end()) - return; - - // Remove only present flags. - condition = it->second & condition; - - if (condition == Condition::None) - return; - - /* - * Determine if we should completely remove the socket from the table if - * condition once remove is none. - */ - bool removal = (it->second & ~(condition)) == Condition::None; - - m_backend.unset(m_table, sc, condition, removal); - - if (removal) - m_table.erase(it); - else - it->second &= ~(condition); - } - - /** - * Remove completely the socket from the listener. - * - * It is a shorthand for unset(sc, Condition::Readable | - * Condition::Writable); - * - * \param sc the socket - */ - inline void remove(Handle sc) - { - unset(sc, Condition::Readable | Condition::Writable); - } - - /** - * Remove all sockets. - */ - inline void clear() - { - while (!m_table.empty()) - remove(m_table.begin()->first); - } - - /** - * Get the number of sockets in the listener. - * - * \return the number of sockets - */ - inline ListenerTable::size_type size() const noexcept - { - return m_table.size(); - } - - /** - * Select a socket. Waits for a specific amount of time specified as the - * duration. - * - * \param duration the duration - * \return the socket ready - */ - template <typename Rep, typename Ratio> - inline ListenerStatus wait(const std::chrono::duration<Rep, Ratio> &duration) - { - auto cvt = std::chrono::duration_cast<std::chrono::milliseconds>(duration); - auto max = cvt.count() > INT_MAX ? INT_MAX : static_cast<int>(cvt.count()); - - return m_backend.wait(m_table, max)[0]; - } - - /** - * Overload with milliseconds. - * - * \param timeout the optional timeout in milliseconds - * \return the socket ready - */ - inline ListenerStatus wait(long long int timeout = -1) - { - return wait(std::chrono::milliseconds(timeout)); - } - - /** - * Select multiple sockets. - * - * \param duration the duration - * \return the socket ready - */ - template <typename Rep, typename Ratio> - inline std::vector<ListenerStatus> waitMultiple(const std::chrono::duration<Rep, Ratio> &duration) - { - auto cvt = std::chrono::duration_cast<std::chrono::milliseconds>(duration); - - return m_backend.wait(m_table, cvt.count()); - } - - /** - * Overload with milliseconds. - * - * \param timeout the optional timeout in milliseconds - * \return the socket ready - */ - inline std::vector<ListenerStatus> waitMultiple(int timeout = -1) - { - return waitMultiple(std::chrono::milliseconds(timeout)); - } -}; - -/** - * \ingroup net-module-resolv - * - * Resolve an hostname immediately. - * - * \param host the hostname - * \param service the service (e.g. http or port number) - * \param domain the domain (e.g. AF_INET) - * \param type the type (e.g. SOCK_STREAM) - * \return the address iterator - * \throw Error on failures - */ -inline AddressIterator resolve(const std::string &host, - const std::string &service, - int domain = AF_UNSPEC, - int type = 0) -{ -#if !defined(NET_NO_AUTO_INIT) - init(); -#endif - - struct addrinfo hints, *res, *p; - - std::memset(&hints, 0, sizeof (hints)); - hints.ai_family = domain; - hints.ai_socktype = type; - - int e = getaddrinfo(host.c_str(), service.c_str(), &hints, &res); - - if (e != 0) - throw Error(gai_strerror(e)); - - std::vector<Address> addresses; - - for (p = res; p != nullptr; p = p->ai_next) - addresses.push_back(Address(p->ai_addr, p->ai_addrlen)); - - return AddressIterator(addresses, 0); -} - -/** - * \ingroup net-module-resolv - * - * Resolve the first address. - * - * \param host the hostname - * \param service the service name - * \param domain the domain (e.g. AF_INET) - * \param type the type (e.g. SOCK_STREAM) - * \return the first generic address available - * \throw Error on failures - * \note do not use AF_UNSPEC and 0 as type for this function - */ -inline Address resolveOne(const std::string &host, const std::string &service, int domain, int type) -{ - AddressIterator it = resolve(host, service, domain, type); - AddressIterator end; - - if (it == end) - throw Error("no address available"); - - return *it; -} - -} // !net - -#endif // !NET_HPP
--- a/modules/net/test/main.cpp Fri Dec 02 22:21:52 2016 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,779 +0,0 @@ -/* - * main.cpp -- test sockets - * - * Copyright (c) 2013-2015 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. - */ - -#include <chrono> -#include <iostream> -#include <sstream> -#include <string> -#include <thread> - -#include <gtest/gtest.h> - -#include <net.hpp> - -using namespace net; -using namespace net::option; - -using namespace std::literals::chrono_literals; - -/* - * Options. - * ------------------------------------------------------------------ - */ - -TEST(Options, reuse) -{ - TcpSocket s(AF_INET, 0); - - try { - s.set(option::SockReuseAddress(true)); - ASSERT_TRUE(s.get<option::SockReuseAddress>()); - - s.set(option::SockReuseAddress(false)); - ASSERT_FALSE(s.get<option::SockReuseAddress>()); - } catch (const std::exception &ex) { - FAIL() << ex.what(); - } -} - -TEST(Options, nodelay) -{ - TcpSocket s(AF_INET, 0); - - try { - s.set(option::TcpNoDelay(true)); - ASSERT_TRUE(s.get<option::TcpNoDelay>()); - - s.set(option::TcpNoDelay(false)); - ASSERT_FALSE(s.get<option::TcpNoDelay>()); - } catch (const std::exception &ex) { - FAIL() << ex.what(); - } -} - -TEST(Options, v6only) -{ - TcpSocket s(AF_INET6, 0); - - try { - s.set(option::Ipv6Only(true)); - ASSERT_TRUE(s.get<option::Ipv6Only>()); - - s.set(option::Ipv6Only(false)); - ASSERT_FALSE(s.get<option::Ipv6Only>()); - } catch (const std::exception &ex) { - FAIL() << ex.what(); - } -} - -/* - * TCP tests. - * ------------------------------------------------------------------ - */ - -class TcpServerTest : public testing::Test { -protected: - TcpSocket m_server{AF_INET, 0}; - TcpSocket m_client{AF_INET, 0}; - - std::thread m_tserver; - std::thread m_tclient; - -public: - TcpServerTest() - { - m_server.set(SockReuseAddress()); - } - - ~TcpServerTest() - { - if (m_tserver.joinable()) - m_tserver.join(); - if (m_tclient.joinable()) - m_tclient.join(); - } -}; - -TEST_F(TcpServerTest, connect) -{ - m_tserver = std::thread([this] () { - try { - TcpSocket sc(AF_INET, 0); - - m_server.bind(net::ipv4::any(16000)); - m_server.listen(); - m_server.accept(); - m_server.close(); - } catch (const std::exception &ex) { - FAIL() << ex.what(); - } - }); - - std::this_thread::sleep_for(100ms); - - m_tclient = std::thread([this] () { - try { - m_client.connect(net::ipv4::pton("127.0.0.1", 16000)); - m_client.close(); - } catch (const std::exception &ex) { - FAIL() << ex.what(); - } - }); -} - -TEST_F(TcpServerTest, io) -{ - m_tserver = std::thread([this] () { - m_server.bind(net::ipv4::any(16000)); - m_server.listen(); - - TcpSocket client = m_server.accept(); - - std::string msg; - - msg.resize(512); - msg.resize(client.recv(&msg[0], 512)); - - ASSERT_EQ("hello world", msg); - - client.send(msg.data(), msg.length()); - }); - - std::this_thread::sleep_for(100ms); - - m_tclient = std::thread([this] () { - std::string msg = "hello world"; - - m_client.connect(net::ipv4::pton("127.0.0.1", 16000)); - m_client.send(msg.c_str(), msg.length()); - - msg.resize(512); - msg.resize(m_client.recv(&msg[0], 512)); - - ASSERT_EQ("hello world", msg); - }); -} - -/* - * UDP tests. - * ------------------------------------------------------------------ - */ - -class UdpServerTest : public testing::Test { -protected: - UdpSocket m_server{AF_INET, 0}; - UdpSocket m_client{AF_INET, 0}; - - std::thread m_tserver; - std::thread m_tclient; - -public: - UdpServerTest() - { - m_server.set(SockBlockMode()); - } - - ~UdpServerTest() - { - if (m_tserver.joinable()) - m_tserver.join(); - if (m_tclient.joinable()) - m_tclient.join(); - } -}; - -TEST_F(UdpServerTest, io) -{ - m_tserver = std::thread([this] () { - try { - Address client; - Address info = net::ipv4::any(16000); - - m_server.bind(info); - - std::string msg; - - msg.resize(512); - msg.resize(m_server.recvfrom(&msg[0], 512, &client)); - - ASSERT_EQ("hello world", msg); - - m_server.sendto(msg.c_str(), msg.length(), client); - m_server.close(); - } catch (const net::Error &ex) { - FAIL() << ex.what(); - } - }); - - std::this_thread::sleep_for(100ms); - - m_tclient = std::thread([this] () { - try { - Address info = net::ipv4::pton("127.0.0.1", 16000); - std::string msg = "hello world"; - - m_client.sendto(msg.c_str(), msg.length(), info); - - msg.resize(512); - msg.resize(m_client.recvfrom(&msg[0], 512, &info)); - - ASSERT_EQ("hello world", msg); - - m_client.close(); - } catch (const net::Error &ex) { - FAIL() << ex.what(); - } - }); -} - -/* - * Listener: set function. - * ------------------------------------------------------------------ - */ - -class TestBackendSet { -public: - std::function<void (Condition, bool)> onSet; - std::function<void (Condition, bool)> onUnset; - - inline void set(const ListenerTable &, Handle, Condition flags, bool addition) noexcept - { - if (onSet) - onSet(flags, addition); - } - - inline void unset(const ListenerTable &, Handle, Condition flags, bool removal) noexcept - { - if (onUnset) - onUnset(flags, removal); - } - - std::vector<ListenerStatus> wait(const ListenerTable &, int) - { - return {}; - } -}; - -class TestBackendSetFail { -public: - inline void set(const ListenerTable &, Handle, Condition, bool) - { - throw "fail"; - } - - inline void unset(const ListenerTable &, Handle, Condition, bool) noexcept - { - } - - std::vector<ListenerStatus> wait(const ListenerTable &, int) - { - return {}; - } -}; - -TEST(ListenerSet, none) -{ - Listener<TestBackendSet> listener; - Handle s = 0; - - listener.backend().onSet = [&] (auto, auto) { - FAIL() << "unexpected set call"; - }; - listener.backend().onUnset = [&] (auto, auto) { - FAIL() << "unexpected unset call"; - }; - listener.reset(s, static_cast<Condition>(0U)); - - ASSERT_EQ(0U, listener.size()); -} - -TEST(ListenerSet, invalid) -{ - Listener<TestBackendSet> listener; - Handle s = 0; - - listener.backend().onSet = [&] (auto, auto) { - FAIL() << "unexpected set call"; - }; - listener.backend().onUnset = [&] (auto, auto) { - FAIL() << "unexpected unset call"; - }; - listener.reset(s, static_cast<Condition>(123U)); - - ASSERT_EQ(0U, listener.size()); -} - -TEST(ListenerSet, initialAdd) -{ - Listener<TestBackendSet> listener; - Handle s = 0; - - listener.backend().onSet = [&] (auto flags, auto addition) { - ASSERT_TRUE(addition); - ASSERT_EQ(Condition::Readable, flags); - }; - listener.backend().onUnset = [&] (auto, auto) { - FAIL() << "unexpected unset call"; - }; - listener.reset(s, Condition::Readable); - - ASSERT_EQ(1U, listener.size()); -} - -TEST(ListenerSet, readThenWrite) -{ - Listener<TestBackendSet> listener; - Handle s = 0; - - // Add Readable. - listener.backend().onSet = [&] (auto flags, auto addition) { - ASSERT_TRUE(addition); - ASSERT_EQ(Condition::Readable, flags); - }; - listener.backend().onUnset = [&] (auto, auto) { - FAIL() << "unexpected unset call"; - }; - listener.reset(s, Condition::Readable); - - // Replace Readable with Writable. - listener.backend().onSet = [&] (auto flags, auto addition) { - ASSERT_FALSE(addition); - ASSERT_EQ(Condition::Writable, flags); - }; - listener.backend().onUnset = [&] (auto flags, auto removal) { - ASSERT_FALSE(removal); - ASSERT_EQ(Condition::Readable, flags); - }; - listener.reset(s, Condition::Writable); - - ASSERT_EQ(1U, listener.size()); -} - -TEST(ListenerSet, allOneShot) -{ - Listener<TestBackendSet> listener; - Handle s = 0; - - listener.backend().onSet = [&] (auto flags, auto addition) { - ASSERT_TRUE(addition); - ASSERT_EQ(Condition::All, flags); - }; - listener.backend().onUnset = [&] (auto, auto) { - FAIL() << "unexpected unset call"; - }; - listener.reset(s, Condition::Readable | Condition::Writable); - - ASSERT_EQ(1U, listener.size()); -} - -TEST(ListenerSet, readTwice) -{ - Listener<TestBackendSet> listener; - Handle s = 0; - - listener.backend().onSet = [&] (auto flags, auto addition) { - ASSERT_TRUE(addition); - ASSERT_EQ(Condition::Readable, flags); - }; - listener.backend().onUnset = [&] (auto, auto) { - FAIL() << "unexpected unset call"; - }; - listener.reset(s, Condition::Readable); - listener.backend().onSet = [&] (auto, auto) { - FAIL() << "double set call unexpected"; - }; - listener.reset(s, Condition::Readable); - - ASSERT_EQ(1U, listener.size()); -} - -TEST(ListenerSet, writeTwice) -{ - Listener<TestBackendSet> listener; - Handle s = 0; - - listener.backend().onSet = [&] (auto flags, auto addition) { - ASSERT_TRUE(addition); - ASSERT_EQ(Condition::Writable, flags); - }; - listener.backend().onUnset = [&] (auto, auto) { - FAIL() << "unexpected unset call"; - }; - listener.reset(s, Condition::Writable); - listener.backend().onSet = [&] (auto, auto) { - FAIL() << "double set call unexpected"; - }; - listener.reset(s, Condition::Writable); - - ASSERT_EQ(1U, listener.size()); -} - -TEST(ListenerSet, allTwice) -{ - Listener<TestBackendSet> listener; - Handle s = 0; - - listener.backend().onSet = [&] (auto flags, auto addition) { - ASSERT_TRUE(addition); - ASSERT_EQ(Condition::All, flags); - }; - listener.backend().onUnset = [&] (auto, auto) { - FAIL() << "unexpected unset call"; - }; - listener.reset(s, Condition::All); - listener.backend().onSet = [&] (auto, auto) { - FAIL() << "double set call unexpected"; - }; - listener.reset(s, Condition::All); - - ASSERT_EQ(1U, listener.size()); -} - -TEST(ListenerSet, bothThenReadable) -{ - Listener<TestBackendSet> listener; - Handle s = 0; - - listener.backend().onSet = [&] (auto flags, auto addition) { - ASSERT_TRUE(addition); - ASSERT_EQ(Condition::All, flags); - }; - listener.backend().onUnset = [&] (auto, auto) { - FAIL() << "unexpected unset call"; - }; - listener.reset(s, Condition::All); - listener.backend().onSet = [&] (auto flags, auto addition) { - ASSERT_TRUE(addition); - ASSERT_EQ(Condition::Readable, flags); - }; - listener.backend().onUnset = [&] (auto flags, auto removal) { - ASSERT_FALSE(removal); - ASSERT_EQ(Condition::Writable, flags); - }; - listener.reset(s, Condition::Readable); -} - -TEST(ListenerSet, failure) -{ - Listener<TestBackendSetFail> listener; - Handle s = 0; - - ASSERT_ANY_THROW(listener.reset(s, Condition::Readable)); - ASSERT_EQ(0U, listener.size()); -} - -/* - * Listener: unset / remove functions. - * ------------------------------------------------------------------ - */ - -class TestBackendUnsetFail { -public: - inline void set(const ListenerTable &, Handle &, Condition, bool) noexcept - { - } - - inline void unset(const ListenerTable &, Handle &, Condition, bool) - { - throw "fail"; - } - - std::vector<ListenerStatus> wait(const ListenerTable &, int) - { - return {}; - } -}; - -TEST(ListenerUnsetRemove, unset) -{ - Listener<TestBackendSet> listener; - Handle s = 0; - - listener.reset(s, Condition::Readable); - listener.backend().onUnset = [&] (auto flags, auto removal) { - ASSERT_EQ(Condition::Readable, flags); - ASSERT_TRUE(removal); - }; - listener.unset(s, Condition::Readable); - - ASSERT_EQ(0U, listener.size()); -} - -TEST(ListenerUnsetRemove, unsetOne) -{ - Listener<TestBackendSet> listener; - Handle s = 0; - - listener.reset(s, Condition::Readable | Condition::Writable); - listener.backend().onUnset = [&] (auto flags, auto removal) { - ASSERT_EQ(Condition::Readable, flags); - ASSERT_FALSE(removal); - }; - listener.unset(s, Condition::Readable); - - ASSERT_EQ(1U, listener.size()); -} - -TEST(ListenerUnsetRemove, unsetAll) -{ - Listener<TestBackendSet> listener; - Handle s = 0; - - listener.reset(s, Condition::Readable | Condition::Writable); - listener.backend().onUnset = [&] (auto flags, auto removal) { - ASSERT_EQ(Condition::Readable, flags); - ASSERT_FALSE(removal); - }; - listener.unset(s, Condition::Readable); - listener.backend().onUnset = [&] (auto flags, auto removal) { - ASSERT_EQ(Condition::Writable, flags); - ASSERT_TRUE(removal); - }; - listener.unset(s, Condition::Writable); - - ASSERT_EQ(0U, listener.size()); -} - -TEST(ListenerUnsetRemove, remove) -{ - Listener<TestBackendSet> listener; - Handle s = 0; - - listener.reset(s, Condition::Readable | Condition::Writable); - listener.backend().onUnset = [&] (auto flags, auto removal) { - ASSERT_EQ(Condition::All, flags); - ASSERT_TRUE(removal); - }; - listener.remove(s); - - ASSERT_EQ(0U, listener.size()); -} - -TEST(ListenerUnsetRemove, failure) -{ - Listener<TestBackendUnsetFail> listener; - Handle s = 0; - - listener.reset(s, Condition::Readable | Condition::Writable); - - ASSERT_ANY_THROW(listener.remove(s)); - - // If fail, kept into the table. - ASSERT_EQ(1U, listener.size()); -} - -/* - * Listener: system. - * ------------------------------------------------------------------ - */ - -class ListenerTest : public testing::Test { -protected: - Listener<backend::Select> m_listener; - TcpSocket m_masterTcp{AF_INET, 0}; - TcpSocket m_clientTcp{AF_INET, 0}; - - std::thread m_tserver; - std::thread m_tclient; - -public: - ListenerTest() - { - m_masterTcp.set(SockReuseAddress()); - m_masterTcp.bind(net::ipv4::any(16000)); - m_masterTcp.listen(); - } - - ~ListenerTest() - { - if (m_tserver.joinable()) { - m_tserver.join(); - } - if (m_tclient.joinable()) { - m_tclient.join(); - } - } -}; - -TEST_F(ListenerTest, accept) -{ - m_tserver = std::thread([this] () { - try { - m_listener.reset(m_masterTcp.handle(), Condition::Readable); - m_listener.wait(); - m_masterTcp.accept(); - m_masterTcp.close(); - } catch (const std::exception &ex) { - FAIL() << ex.what(); - } - }); - - std::this_thread::sleep_for(100ms); - - m_tclient = std::thread([this] () { - m_clientTcp.connect(net::ipv4::pton("127.0.0.1", 16000)); - }); -} - -TEST_F(ListenerTest, recv) -{ - m_tserver = std::thread([this] () { - try { - m_listener.reset(m_masterTcp.handle(), Condition::Readable); - m_listener.wait(); - - TcpSocket sc = m_masterTcp.accept(); - - std::string msg; - - msg.resize(512); - msg.resize(sc.recv(&msg[0], 512)); - - ASSERT_EQ("hello", msg); - } catch (const std::exception &ex) { - FAIL() << ex.what(); - } - }); - - std::this_thread::sleep_for(100ms); - - m_tclient = std::thread([this] () { - std::string msg = "hello"; - - m_clientTcp.connect(net::ipv4::pton("127.0.0.1", 16000)); - m_clientTcp.send(msg.c_str(), msg.length()); - }); -} - -/* - * Non-blocking connect - * ------------------------------------------------------------------ - */ - -class NonBlockingConnectTest : public testing::Test { -protected: - TcpSocket m_server{AF_INET, 0}; - TcpSocket m_client{AF_INET, 0}; - - std::thread m_tserver; - std::thread m_tclient; - -public: - NonBlockingConnectTest() - { - m_client.set(SockBlockMode(false)); - } - - ~NonBlockingConnectTest() - { - if (m_tserver.joinable()) - m_tserver.join(); - if (m_tclient.joinable()) - m_tclient.join(); - } -}; - -/* - * TCP accept. - * ------------------------------------------------------------------ - */ - -class TcpAcceptTest : public testing::Test { -protected: - TcpSocket m_server{AF_INET, 0}; - TcpSocket m_client{AF_INET, 0}; - - std::thread m_tserver; - std::thread m_tclient; - -public: - TcpAcceptTest() - { - m_server.set(SockReuseAddress()); - m_server.bind(net::ipv4::any(16000)); - m_server.listen(); - } - - ~TcpAcceptTest() - { - if (m_tserver.joinable()) - m_tserver.join(); - if (m_tclient.joinable()) - m_tclient.join(); - } -}; - -/* - * TCP recv. - * ------------------------------------------------------------------ - */ - -class TcpRecvTest : public testing::Test { -protected: - TcpSocket m_server{AF_INET, 0}; - TcpSocket m_client{AF_INET, 0}; - - std::thread m_tserver; - std::thread m_tclient; - -public: - TcpRecvTest() - { - m_server.set(SockReuseAddress()); - m_server.bind(net::ipv4::any(16000)); - m_server.listen(); - } - - ~TcpRecvTest() - { - if (m_tserver.joinable()) - m_tserver.join(); - if (m_tclient.joinable()) - m_tclient.join(); - } -}; - -TEST_F(TcpRecvTest, blockingSuccess) -{ - m_tserver = std::thread([this] () { - TcpSocket client = m_server.accept(); - std::string msg; - - msg.resize(512); - msg.resize(client.recv(&msg[0], 512)); - - ASSERT_EQ("hello", msg); - }); - - std::this_thread::sleep_for(100ms); - - m_tclient = std::thread([this] () { - std::string msg = "hello"; - - m_client.connect(net::ipv4::pton("127.0.0.1", 16000)); - m_client.send(msg.c_str(), msg.length()); - m_client.close(); - }); -} - -int main(int argc, char **argv) -{ - testing::InitGoogleTest(&argc, argv); - - return RUN_ALL_TESTS(); -}
--- a/modules/net/test/test.crt Fri Dec 02 22:21:52 2016 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,14 +0,0 @@ ------BEGIN CERTIFICATE----- -MIICITCCAYoCCQCGm4grkVCohjANBgkqhkiG9w0BAQsFADBVMQswCQYDVQQGEwJG -UjEPMA0GA1UECAwGRnJhbmNlMSEwHwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0 -eSBMdGQxEjAQBgNVBAMMCWxvY2FsaG9zdDAeFw0xNTEwMjYyMDM0NThaFw0yNTEw -MjMyMDM0NThaMFUxCzAJBgNVBAYTAkZSMQ8wDQYDVQQIDAZGcmFuY2UxITAfBgNV -BAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDESMBAGA1UEAwwJbG9jYWxob3N0 -MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDp13OqVyOWyv5QWD4xr+Duw6SZ -gU7D5huzsAOcneSI6JUhf+7Ecu6BQ2JGkFn4srIVkMWGQuImETJ8JCpSQH7rk+xO -L9fTTK+TwhP2hW/Rf/b2gWedhJAS+gilqt4JNT7v2wFv+aTtRt/lpTXVSdtpLa/m -Pdy219f6MAPgODJ/7QIDAQABMA0GCSqGSIb3DQEBCwUAA4GBAJSnn/IBn1ZblfzP -rJO/lE1Jwpmx3B7+oR/e4fkZd6JR3s06umGYWr2H+TPl/5dj9x0gPokhoIL9zCGq -SxCPnOeaxjBkw7yh3Ks6m3xKxmK4aMpAtBHtwmbfQyIcgz71/lfCzbJ3WcKpn1ig -IZbByt5QSSPcFORRzJJa35eHBdfX ------END CERTIFICATE-----
--- a/modules/net/test/test.key Fri Dec 02 22:21:52 2016 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,15 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIICXAIBAAKBgQDp13OqVyOWyv5QWD4xr+Duw6SZgU7D5huzsAOcneSI6JUhf+7E -cu6BQ2JGkFn4srIVkMWGQuImETJ8JCpSQH7rk+xOL9fTTK+TwhP2hW/Rf/b2gWed -hJAS+gilqt4JNT7v2wFv+aTtRt/lpTXVSdtpLa/mPdy219f6MAPgODJ/7QIDAQAB -AoGBANDt4ndQkgi56A1rOm50gVlzTg6lPPXFE/0xB5kYbcdxX0VmI7Q8KCMwTI9V -jD2rk3e3OPSjr6FpfhzyxylkXMBz2BL5NRNPowCJbiMgZOUIzlcWPKo0tgf1bZJx -YdB5U003ISGPPBjVOAjyizY7tJnaNvbpLQ0hbIAsvHPEAOnBAkEA9r3g8NQjPrvb -oIr5SMIxM8HDJ1/q+MEBSFtRFzQpmur6P64Jsu96zCyencUYTxs0L/sottrj6dPC -vjGCc6PjsQJBAPKdqK1knJv6Y95M2bnEwrymCFVdxCi7AxObStB+bg/+7mMCUqqX -j2g71bfvhYakHV7CiaYrrORChwj6vTbimv0CQGpd2IZ5LOhyW2+N+YDgFg3Vzac/ -ti+eJEto8kAqgHUELvUctZmpmypBYe9pc91GQO0ePKL3IaE/ZIhRF4d6c0ECQH9A -XiaD7PiKvjLs0A31u8ZCt4A+7BII9LYl73mntobBSbu4ji9Xyyn6qEAPa1ORZK49 -DwGPSuF2W2lESlYtSOkCQGrtczhx3IyJjk5e2Y1i/UddPKjviAysCSzcW6aVTNr9 -Y2L0sWmva2FKnkl9FDuEqxvmGr6OOkr5Ll7aWLzJri8= ------END RSA PRIVATE KEY-----