Mercurial > malikania
changeset 38:ecf316d52f5d
Server: add client connection states
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libcommon/malikania/net.hpp Sat Nov 05 16:29:32 2016 +0100 @@ -0,0 +1,3753 @@ +/* + * 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 MALIKANIA_NET_HPP +#define MALIKANIA_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 + +namespace malikania { + +/** + * 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); + } + + TlsSocket(TlsSocket &&) noexcept = default; + TlsSocket &operator=(TlsSocket &&) noexcept = default; + + /** + * 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 + +} // !malikania + +#endif // !MALIKANIA_NET_HPP
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libcommon/malikania/signals.hpp Sat Nov 05 16:29:32 2016 +0100 @@ -0,0 +1,190 @@ +/* + * signals.h -- synchronous observer mechanism + * + * Copyright (c) 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 MALIKANIA_SIGNALS_HPP +#define MALIKANIA_SIGNALS_HPP + +/** + * \file signals.hpp + * \brief Synchronous callbacks. + */ + +#include <algorithm> +#include <cstdint> +#include <functional> +#include <stack> +#include <unordered_map> +#include <vector> + +namespace malikania { + +/** + * \brief Stores and call registered functions. + * + * This class is intended to be use as a public field in the desired object. + * + * The user just have to call one of connect(), disconnect() or the call + * operator to use this class. + * + * It stores the callable as std::function so type-erasure is complete. + * + * The user is responsible of taking care that the object is still alive + * in case that the function takes a reference to the object. + * + * It is forbidden to connect, disconnect, clearing the signal from a callback, + * instead you should postpone this operation for example, in an event loop + * event. + * + * It is also forbidden to throw in user callbacks, to make such guarantee, the + * call operator is noexcept. + */ +template <typename... Args> +class Signal { +public: + /** + * \brief Opaque connection identifier. + * \see disconnect + */ + using Index = std::intmax_t; + +private: + using Function = std::function<void (Args...)>; + using List = std::vector<Function>; + using Stack = std::stack<Index>; + + List m_functions; + Stack m_reuseable; + +#if !defined(NDEBUG) + mutable bool m_locked{false}; +#endif + + template <typename Func> + inline Index add(Func &&function) + { + assert(!m_locked); + + std::size_t id; + + if (!m_reuseable.empty()) { + id = m_reuseable.top(); + m_functions[id] = std::forward<Func>(function); + m_reuseable.pop(); + } else { + m_functions.push_back(std::forward<Func>(function)); + id = m_functions.size() - 1; + } + + return id; + } + +public: + /** + * Register a new function to the signal. + * + * \pre must not be called from an handler + * \param function the function to copy + * \return the index for removing + * \throw exceptions on bad allocation + */ + inline Index connect(const Function &function) + { + return add(function); + } + + /** + * Overloaded function for move semantics. + * + * \pre must not be called from an handler + * \param function the function to move + * \return the connection index + * \throw exceptions on bad allocation + */ + inline Index connect(Function &&function) + { + return add(function); + } + + /** + * Disconnect an handler. + * + * \pre must not be called from an handler + * \param id the id to disconnect (will be set to 0) + */ + inline void disconnect(Index &id) + { + assert(!m_locked); + + if (id < 0 || static_cast<std::size_t>(id) >= m_functions.size()) + return; + + m_functions[id] = nullptr; + m_reuseable.push(id); + id = -1; + } + + /** + * Remove all registered functions. + * + * \pre must not be called from an handler + */ + inline void clear() noexcept + { + assert(!m_locked); + + m_functions.clear(); + + while (!m_reuseable.empty()) + m_reuseable.pop(); + } + + /** + * Get the number of signals connected. + * + * \return the number + */ + inline std::size_t size() const noexcept + { + return std::count_if(m_functions.begin(), m_functions.end(), [&] (auto fn) { + return fn != nullptr; + }); + } + + /** + * Call every functions. + * + * \pre must not be called from an handler + * \param args the arguments to pass to the signal + */ + void operator()(Args... args) const noexcept + { + assert(!m_locked); + + m_locked = true; + + for (auto &f : m_functions) + if (f) + f(args...); + + m_locked = false; + } +}; + +} // !malikania + +#endif // !MALIKANIA_SIGNALS_HPP
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libserver/malikania/connection-service.cpp Sat Nov 05 16:29:32 2016 +0100 @@ -0,0 +1,136 @@ +/* + * connection-service.cpp -- manage clients server side + * + * Copyright (c) 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. + */ + +#include <iostream> + +#include "dao-account.hpp" +#include "connection-state-ready.hpp" +#include "connection-service.hpp" +#include "server.hpp" + +namespace malikania { + +void ConnectionService::slotAuthentication(Server &server, + Connection &connection, + const std::string &login, + const std::string &password) +{ + AccountDao dao(server.database()); + + try { + if (dao.authenticate(login, password)) { + std::cout << "client successfully authenticated" << std::endl; + server.post([&connection] (auto &) { + connection.setState(std::make_unique<Connection::ReadyState>()); + }); + } else { + std::cout << "client failed to authenticate" << std::endl; + connection.error("authentication failed"); + } + } catch (const std::exception &ex) { + // FATAL ERROR?? + } +} + +void ConnectionService::slotDisconnect(Server &, Connection &) +{ + std::cout << "client disconnected" << std::endl; +} + +void ConnectionService::syncMaster(Server &server) +{ + try { + auto socket = net::TlsSocket(m_master.accept()); + + socket.setCertificate(m_settings.certificate); + socket.setPrivateKey(m_settings.key); + + auto conn = std::make_unique<Connection>(std::move(socket)); + auto &ref = *conn; + + conn->onAuthentication.connect([this, &server, &ref] (auto login, auto password) { + this->slotAuthentication(server, ref, login, password); + }); + conn->onDisconnect.connect([this, &server, &ref] () { + this->slotDisconnect(server, ref); + }); + + m_clients.push_back(std::move(conn)); + + std::cout << "new client connected" << std::endl; + } catch (const std::exception &ex) { + // LOG ERROR SOMEWHERE + } +} + +void ConnectionService::syncClients(net::Listener<> &listener, const net::ListenerStatus &status) +{ + for (auto it = m_clients.begin(); it != m_clients.end(); ) { + auto &cnt = *it; + + if (cnt->handle() == status.socket) + cnt->sync(listener, status.flags); + + if (cnt->state().id() == "disconnected") + it = m_clients.erase(it); + else + ++ it; + } +} + +ConnectionService::ConnectionService(ConnectionSettings settings) + : m_settings(std::move(settings)) + , m_master(any(m_settings.type & ConnectionSettings::Ipv6) ? AF_INET6 : AF_INET, 0) +{ + if (any(m_settings.type & ConnectionSettings::Ipv6)) { + m_master.set(net::option::Ipv6Only(m_settings.type == ConnectionSettings::Ipv6)); + m_master.set(net::option::SockBlockMode(false)); + + if (m_settings.address == "*") + m_master.bind(net::ipv6::any(m_settings.port)); + else + m_master.bind(net::ipv6::pton(m_settings.address, m_settings.port)); + } else { + if (m_settings.address == "*") + m_master.bind(net::ipv4::any(m_settings.port)); + else + m_master.bind(net::ipv4::pton(m_settings.address, m_settings.port)); + } + + m_master.listen(256); +} + +void ConnectionService::prepare(Server &, net::Listener<> &listener) +{ + listener.reset(m_master.handle(), net::Condition::Readable); + + for (auto &cnt : m_clients) + cnt->prepare(listener); +} + +void ConnectionService::sync(Server &server, + net::Listener<> &listener, + const net::ListenerStatus &status) +{ + if (status.socket == m_master.handle()) + syncMaster(server); + else + syncClients(listener, status); +} + +} // !malikania
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libserver/malikania/connection-service.hpp Sat Nov 05 16:29:32 2016 +0100 @@ -0,0 +1,133 @@ +/* + * connection-service.hpp -- manage clients server side + * + * Copyright (c) 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 MALIKANIA_CONNECTION_SERVICE_HPP +#define MALIKANIA_CONNECTION_SERVICE_HPP + +#include <cstdint> +#include <memory> +#include <vector> + +#include "connection.hpp" + +namespace malikania { + +class Server; + +/** + * \brief Connections parameters + */ +class ConnectionSettings { +public: + enum Type { + Ipv4 = (1 << 0), //!< Add IPv4 + Ipv6 = (1 << 1), //!< Add IPv6 + All = (Ipv4 | Ipv6) //!< Use both IPv4 and IPv6 on same port + } type{All}; + + std::string certificate; //!< Certificate file + std::string key; //!< Private key file + std::string address{"*"}; //!< Address to bind + std::uint16_t port{3320}; //!< Port to use +}; + +/** + * \brief Manage connections on the server + */ +class ConnectionService { +private: + ConnectionSettings m_settings; + net::TcpSocket m_master; + std::vector<std::unique_ptr<Connection>> m_clients; + + void slotAuthentication(Server &, Connection &, const std::string &, const std::string &); + void slotDisconnect(Server &, Connection &); + + void syncMaster(Server &server); + void syncClients(net::Listener<> &listener, const net::ListenerStatus &status); + +public: + ConnectionService(ConnectionSettings settings); + + inline const std::vector<std::unique_ptr<Connection>> &clients() const noexcept + { + return m_clients; + } + + inline std::vector<std::unique_ptr<Connection>> &clients() noexcept + { + return m_clients; + } + + void prepare(Server &server, net::Listener<> &listener); + + void sync(Server &server, net::Listener<> &listener, const net::ListenerStatus &status); +}; + +/** + * \cond ENUM_HIDDEN_SYMBOLS + */ + +inline ConnectionSettings::Type operator^(ConnectionSettings::Type v1, ConnectionSettings::Type v2) noexcept +{ + return static_cast<ConnectionSettings::Type>(static_cast<unsigned>(v1) ^ static_cast<unsigned>(v2)); +} + +inline ConnectionSettings::Type operator&(ConnectionSettings::Type v1, ConnectionSettings::Type v2) noexcept +{ + return static_cast<ConnectionSettings::Type>(static_cast<unsigned>(v1) & static_cast<unsigned>(v2)); +} + +inline ConnectionSettings::Type operator|(ConnectionSettings::Type v1, ConnectionSettings::Type v2) noexcept +{ + return static_cast<ConnectionSettings::Type>(static_cast<unsigned>(v1) | static_cast<unsigned>(v2)); +} + +inline ConnectionSettings::Type operator~(ConnectionSettings::Type v) noexcept +{ + return static_cast<ConnectionSettings::Type>(~static_cast<unsigned>(v)); +} + +inline ConnectionSettings::Type &operator|=(ConnectionSettings::Type &v1, ConnectionSettings::Type v2) noexcept +{ + v1 = static_cast<ConnectionSettings::Type>(static_cast<unsigned>(v1) | static_cast<unsigned>(v2)); + + return v1; +} + +inline ConnectionSettings::Type &operator&=(ConnectionSettings::Type &v1, ConnectionSettings::Type v2) noexcept +{ + v1 = static_cast<ConnectionSettings::Type>(static_cast<unsigned>(v1) & static_cast<unsigned>(v2)); + + return v1; +} + +inline ConnectionSettings::Type &operator^=(ConnectionSettings::Type &v1, ConnectionSettings::Type v2) noexcept +{ + v1 = static_cast<ConnectionSettings::Type>(static_cast<unsigned>(v1) ^ static_cast<unsigned>(v2)); + + return v1; +} + +/** + * \endcond + */ + +} // !malikania + +#endif // !MALIKANIA_CONNECTION_SERVICE_HPP
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libserver/malikania/connection-state-authenticating.cpp Sat Nov 05 16:29:32 2016 +0100 @@ -0,0 +1,50 @@ +/* + * connection-state-authenticating.cpp -- user is authenticating + * + * Copyright (c) 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. + */ + +#include "connection-state-authenticating.hpp" + +namespace malikania { + +void Connection::AuthenticatingState::prepare(Connection &conn, net::Listener<> &listener) +{ + listener.reset(conn.m_socket.handle(), net::Condition::Readable); +} + +void Connection::AuthenticatingState::sync(Connection &conn, net::Listener<> &, net::Condition) +{ + recv(conn, m_input); + + auto value = next(conn, m_input); + + if (!value.is_object()) + return; + + auto login = value.find("login"); + auto password = value.find("password"); + + if (login != value.end() && login->is_string() && + password != value.end() && password->is_string()) + conn.onAuthentication(*login, *password); +} + +std::string Connection::AuthenticatingState::id() const noexcept +{ + return "authenticating"; +} + +} // !malikania
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libserver/malikania/connection-state-authenticating.hpp Sat Nov 05 16:29:32 2016 +0100 @@ -0,0 +1,48 @@ +/* + * connection-state-authenticating.hpp -- user is authenticating + * + * Copyright (c) 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 MALIKANIA_CONNECTION_STATE_AUTHENTICATING_HPP +#define MALIKANIA_CONNECTION_STATE_AUTHENTICATING_HPP + +/** + * \file connection-state-authenticating.hpp + * \brief User is authenticating. + */ + +#include "connection-state.hpp" + +namespace malikania { + +/** + * \brief User is authenticating. + */ +class Connection::AuthenticatingState : public Connection::State { +private: + std::string m_input; + +public: + void prepare(Connection &conn, net::Listener<> &listener) override; + + void sync(Connection &conn, net::Listener<> &, net::Condition) override; + + std::string id() const noexcept override; +}; + +} // !malikania + +#endif // !MALIKANIA_CONNECTION_STATE_AUTHENTICATING_HPP
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libserver/malikania/connection-state-closing.cpp Sat Nov 05 16:29:32 2016 +0100 @@ -0,0 +1,45 @@ +/* + * connection-state-closing.hpp -- closing connection to the user + * + * Copyright (c) 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. + */ + +#include "connection-state-disconnected.hpp" +#include "connection-state-closing.hpp" + +namespace malikania { + +void Connection::ClosingState::prepare(Connection &conn, net::Listener<> &listener) +{ + if (conn.m_output.empty()) + listener.remove(conn.handle()); + else + listener.reset(conn.handle(), net::Condition::Writable); +} + +void Connection::ClosingState::sync(Connection &conn, net::Listener<> &, net::Condition) +{ + send(conn, conn.m_output); + + if (conn.m_output.empty()) + conn.m_stateNext = std::make_unique<DisconnectedState>(); +} + +std::string Connection::ClosingState::id() const noexcept +{ + return "closing"; +} + +} // !malikania
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libserver/malikania/connection-state-closing.hpp Sat Nov 05 16:29:32 2016 +0100 @@ -0,0 +1,45 @@ +/* + * connection-state-closing.hpp -- closing connection to the user + * + * Copyright (c) 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 MALIKANIA_CONNECTION_STATE_CLOSING_HPP +#define MALIKANIA_CONNECTION_STATE_CLOSING_HPP + +/** + * \file connection-state-closing.hpp + * \brief Closing connection to the user. + */ + +#include "connection-state.hpp" + +namespace malikania { + +/** + * \brief Closing connection to the user. + */ +class Connection::ClosingState : public Connection::State { +public: + void prepare(Connection &conn, net::Listener<> &listener) override; + + void sync(Connection &conn, net::Listener<> &, net::Condition) override; + + std::string id() const noexcept override; +}; + +} // !malikania + +#endif // !MALIKANIA_CONNECTION_STATE_CLOSING_HPP
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libserver/malikania/connection-state-disconnected.cpp Sat Nov 05 16:29:32 2016 +0100 @@ -0,0 +1,36 @@ +/* + * connection-state-disconnected.cpp -- client is down and will be removed + * + * Copyright (c) 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. + */ + +#include "connection-state-disconnected.hpp" + +namespace malikania { + +void Connection::DisconnectedState::prepare(Connection &, net::Listener<> &) +{ +} + +void Connection::DisconnectedState::sync(Connection &, net::Listener<> &, net::Condition) +{ +} + +std::string Connection::DisconnectedState::id() const noexcept +{ + return "disconnected"; +} + +} // !malikania
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libserver/malikania/connection-state-disconnected.hpp Sat Nov 05 16:29:32 2016 +0100 @@ -0,0 +1,45 @@ +/* + * connection-state-disconnected.hpp -- client is down and will be removed + * + * Copyright (c) 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 MALIKANIA_CONNECTION_STATE_DISCONNECTED_HPP +#define MALIKANIA_CONNECTION_STATE_DISCONNECTED_HPP + +/** + * \file connection-state-disconected.hpp + * \brief Client is down and will be removed. + */ + +#include "connection-state.hpp" + +namespace malikania { + +/** + * \brief Client is down and will be removed. + */ +class Connection::DisconnectedState : public Connection::State { +public: + void prepare(Connection &conn, net::Listener<> &listener) override; + + void sync(Connection &conn, net::Listener<> &, net::Condition) override; + + std::string id() const noexcept override; +}; + +} // !malikania + +#endif // !MALIKANIA_CONNECTION_STATE_DISCONNECTED_HPP
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libserver/malikania/connection-state-greeting.cpp Sat Nov 05 16:29:32 2016 +0100 @@ -0,0 +1,57 @@ +/* + * connection-state-greeting.cpp -- greeting the user + * + * Copyright (c) 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. + */ + +#include <json.hpp> + +#include "connection-state-authenticating.hpp" +#include "connection-state-greeting.hpp" + +namespace malikania { + +Connection::GreetingState::GreetingState() +{ + // TODO: this is just an example. + m_output += nlohmann::json::object({ + { "malikania", "0.1.0" } + }).dump(); + m_output += "\r\n\r\n"; +} + +void Connection::GreetingState::prepare(Connection &conn, net::Listener<> &listener) +{ + listener.reset(conn.m_socket.handle(), net::Condition::Writable); +} + +void Connection::GreetingState::sync(Connection &conn, net::Listener<> &listener, net::Condition condition) +{ + assert(condition == net::Condition::Writable); + + if (m_output.length() > 0) + send(conn, m_output); + else { + listener.unset(conn.handle(), net::Condition::Writable); + conn.m_stateNext = std::make_unique<AuthenticatingState>(); + } +} + +std::string Connection::GreetingState::id() const noexcept +{ + return "greeting"; +} + +} // !malikania
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libserver/malikania/connection-state-greeting.hpp Sat Nov 05 16:29:32 2016 +0100 @@ -0,0 +1,50 @@ +/* + * connection-state-greeting.hpp -- greeting the user + * + * Copyright (c) 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 MALIKANIA_CONNECTION_STATE_GREETING_HPP +#define MALIKANIA_CONNECTION_STATE_GREETING_HPP + +/** + * \file connection-state-greeting.hpp + * \brief Greeting the user. + */ + +#include "connection-state.hpp" + +namespace malikania { + +/** + * \brief Greeting the user. + */ +class Connection::GreetingState : public Connection::State { +private: + std::string m_output; + +public: + GreetingState(); + + void prepare(Connection &conn, net::Listener<> &listener) override; + + void sync(Connection &conn, net::Listener<> &listener, net::Condition condition) override; + + std::string id() const noexcept override; +}; + +} // !malikania + +#endif // !MALIKANIA_CONNECTION_STATE_GREETING_HPP
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libserver/malikania/connection-state-ready.cpp Sat Nov 05 16:29:32 2016 +0100 @@ -0,0 +1,49 @@ +/* + * connection-state-ready.cpp -- client is ready + * + * Copyright (c) 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. + */ + +#include "connection-state-ready.hpp" + +namespace malikania { + +void Connection::ReadyState::prepare(Connection &conn, net::Listener<> &listener) +{ + if (!conn.m_output.empty()) + listener.reset(conn.handle(), net::Condition::Writable); + + listener.reset(conn.handle(), net::Condition::Readable); +} + +void Connection::ReadyState::sync(Connection &conn, net::Listener<> &lst, net::Condition cond) +{ + if (bool(cond & net::Condition::Readable)) + recv(conn, conn.m_input); + + if (bool(cond & net::Condition::Writable)) { + send(conn, conn.m_output); + + if (conn.m_output.empty()) + lst.unset(conn.handle(), net::Condition::Writable); + } +} + +std::string Connection::ReadyState::id() const noexcept +{ + return "ready"; +} + +} // !malikania
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libserver/malikania/connection-state-ready.hpp Sat Nov 05 16:29:32 2016 +0100 @@ -0,0 +1,45 @@ +/* + * connection-state-ready.hpp -- client is ready + * + * Copyright (c) 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 MALIKANIA_CONNECTION_STATE_READY_HPP +#define MALIKANIA_CONNECTION_STATE_READY_HPP + +/** + * \file connection-state-ready.hpp + * \brief Client is ready. + */ + +#include "connection-state.hpp" + +namespace malikania { + +/** + * \brief Client is ready. + */ +class Connection::ReadyState : public Connection::State { +public: + void prepare(Connection &conn, net::Listener<> &listener) override; + + void sync(Connection &conn, net::Listener<> &listener, net::Condition condition) override; + + std::string id() const noexcept override; +}; + +} // !malikania + +#endif // !MALIKANIA_CONNECTION_STATE_READY_HPP
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libserver/malikania/connection-state.cpp Sat Nov 05 16:29:32 2016 +0100 @@ -0,0 +1,88 @@ +/* + * connection-state.cpp -- base class for connection state + * + * Copyright (c) 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. + */ + +#include <iostream> + +#include "connection-state-disconnected.hpp" +#include "connection-state.hpp" +#include "util.hpp" + +namespace malikania { + +void Connection::State::close(Connection &conn) noexcept +{ + conn.onDisconnect(); + conn.m_state = std::make_unique<DisconnectedState>(); +} + +void Connection::State::recv(Connection &conn, std::string &input) noexcept +{ + try { + std::string buffer; + std::size_t size = 512; + + buffer.resize(size); + buffer.resize(conn.m_socket.recv(&buffer[0], size)); + + if (buffer.size() == 0) + close(conn); + + input += buffer; + } catch (const net::WantReadError &) { + conn.m_handshake = Handshake::Reading; + } catch (const net::WantWriteError &) { + conn.m_handshake = Handshake::Writing; + } catch (const std::exception &) { + close(conn); + } +} + +void Connection::State::send(Connection &conn, std::string &output) noexcept +{ + assert(output.length() != 0); + + try { + auto nsent = conn.m_socket.send(&output[0], output.length()); + + if (nsent == 0) + close(conn); + + output.erase(0, nsent); + } catch (const net::WantReadError &) { + conn.m_handshake = Handshake::Reading; + } catch (const net::WantWriteError &) { + conn.m_handshake = Handshake::Writing; + } catch (const std::exception &) { + close(conn); + } +} + +nlohmann::json Connection::State::next(Connection &conn, std::string &input) noexcept +{ + nlohmann::json object; + + try { + object = util::json::next(input); + } catch (const std::exception &) { + close(conn); + } + + return object; +} + +} // !malikania
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libserver/malikania/connection-state.hpp Sat Nov 05 16:29:32 2016 +0100 @@ -0,0 +1,76 @@ +/* + * connection-state.hpp -- base class for connection state + * + * Copyright (c) 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 MALIKANIA_CONNECTION_STATE_HPP +#define MALIKANIA_CONNECTION_STATE_HPP + +/** + * \file connection-state.hpp + * \brief Base class for connection state. + */ + +#include <json.hpp> + +#include "connection.hpp" + +namespace malikania { + +/** + * \brief Base class for connection state. + */ +class Connection::State { +protected: + void close(Connection &conn) noexcept; + + void recv(Connection &conn, std::string &input) noexcept; + + void send(Connection &conn, std::string &output) noexcept; + + nlohmann::json next(Connection &conn, std::string &input) noexcept; + +public: + /** + * Default constructor. + */ + State() = default; + + /** + * Virtual destructor defaulted. + */ + virtual ~State() = default; + + /** + * Prepare the listener about that connection. + * + * This function should not care about handshake status because the + * connection object will never call this function if a handshake is + * taking place. + * + * \param connection the connection + * \param listener the listener + */ + virtual void prepare(Connection &connection, net::Listener<> &listener) = 0; + + virtual void sync(Connection &, net::Listener<> &, net::Condition) = 0; + + virtual std::string id() const noexcept = 0; +}; + +} // !malikania + +#endif // !MALIKANIA_CONNECTION_STATE_HPP
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libserver/malikania/connection.cpp Sat Nov 05 16:29:32 2016 +0100 @@ -0,0 +1,104 @@ +/* + * connection.cpp -- client connection + * + * Copyright (c) 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. + */ + +#include <json.hpp> + +#include "connection.hpp" +#include "connection-state-closing.hpp" +#include "connection-state-greeting.hpp" + +namespace malikania { + +void Connection::handshake() noexcept +{ + try { + m_socket.handshake(); + m_handshake = Handshake::Ready; + } catch (const net::WantReadError &) { + m_handshake = Handshake::Reading; + } catch (const net::WantWriteError &) { + m_handshake = Handshake::Writing; + } catch (const std::exception &) { + } +} + +Connection::Connection(net::TlsSocket socket) + : m_socket(std::move(socket)) + , m_state(std::make_unique<GreetingState>()) +{ + m_socket.set(net::option::SockBlockMode(false)); + + handshake(); +} + +Connection::Connection(Connection &&) = default; + +Connection::~Connection() = default; + +void Connection::prepare(net::Listener<> &listener) +{ + assert(m_state); + + switch (m_handshake) { + case Handshake::Reading: + listener.reset(m_socket.handle(), net::Condition::Readable); + break; + case Handshake::Writing: + listener.reset(m_socket.handle(), net::Condition::Writable); + break; + default: + m_state->prepare(*this, listener); + break; + } +} + +void Connection::sync(net::Listener<> &listener, net::Condition condition) +{ + assert(m_state); + + switch (m_handshake) { + case Handshake::Reading: + case Handshake::Writing: + handshake(); + break; + default: + if (m_stateNext) { + std::cout << "switching state " << m_state->id() << " -> " + << m_stateNext->id() << std::endl; + m_state.reset(m_stateNext.release()); + } + + m_state->sync(*this, listener, condition); + break; + } +} + +void Connection::setState(std::unique_ptr<State> state) noexcept +{ + m_stateNext = std::move(state); +} + +void Connection::error(std::string message) +{ + m_output += std::move(message); + m_stateNext = std::make_unique<ClosingState>(); +} + +Connection &Connection::operator=(Connection &&) = default; + +} // !malikania
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libserver/malikania/connection.hpp Sat Nov 05 16:29:32 2016 +0100 @@ -0,0 +1,105 @@ +/* + * connection.hpp -- client connection + * + * Copyright (c) 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 MALIKANIA_CONNECTION_HPP +#define MALIKANIA_CONNECTION_HPP + +/** + * \file connection.hpp + * \brief Client connection. + */ + +#include <memory> + +#include <json.hpp> + +#include "net.hpp" +#include "signals.hpp" + +namespace malikania { + +/** + * \brief Client connection. + */ +class Connection { +public: + Signal<> onDisconnect; + Signal<std::string, std::string> onAuthentication; + Signal<const nlohmann::json &> onMessage; + + class State; + class GreetingState; + class AuthenticatingState; + class ReadyState; + class ClosingState; + class DisconnectedState; + +private: + enum class Handshake { + None, + Writing, + Reading, + Ready + } m_handshake{Handshake::None}; + + net::TlsSocket m_socket; + + std::unique_ptr<State> m_state; + std::unique_ptr<State> m_stateNext; + + std::string m_input; + std::string m_output; + + void handshake() noexcept; + +public: + Connection(net::TlsSocket socket); + + Connection(Connection &&); + + virtual ~Connection(); + + inline net::Handle handle() const noexcept + { + return m_socket.handle(); + } + + virtual void prepare(net::Listener<> &listener); + + virtual void sync(net::Listener<> &listener, net::Condition condition); + + inline const State &state() const noexcept + { + return *m_state; + } + + inline State &state() noexcept + { + return *m_state; + } + + void setState(std::unique_ptr<State>) noexcept; + + void error(std::string message); + + Connection &operator=(Connection &&); +}; + +} // !malikania + +#endif // !MALIKANIA_CONNECTION_HPP
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libserver/malikania/server.cpp Sat Nov 05 16:29:32 2016 +0100 @@ -0,0 +1,51 @@ +/* + * server.cpp -- malikania basic server + * + * Copyright (c) 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. + */ + +#include <iostream> + +#include "server.hpp" + +namespace malikania { + +Server::Server(ConnectionSettings settings, DatabaseSettings dbsettings) + : m_connections(settings) + , m_database(dbsettings) +{ +} + +void Server::run() noexcept +{ + for (;;) { + m_connections.prepare(*this, m_listener); + auto status = m_listener.wait(); + m_connections.sync(*this, m_listener, status); + + // Enqueue events. + for (auto &ev : m_events) + ev(*this); + + m_events.clear(); + } +} + +void Server::post(Event ev) noexcept +{ + m_events.push_back(std::move(ev)); +} + +} // !malikania
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libserver/malikania/server.hpp Sat Nov 05 16:29:32 2016 +0100 @@ -0,0 +1,72 @@ +/* + * server.hpp -- malikania basic server + * + * Copyright (c) 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 MALIKANIA_SERVER_HPP +#define MALIKANIA_SERVER_HPP + +#include <functional> +#include <vector> + +#include "connection-service.hpp" +#include "database.hpp" +#include "net.hpp" +#include "signals.hpp" + +namespace malikania { + +class Server { +public: + using Event = std::function<void (Server &)>; + + Signal<const Connection &> onConnection; + Signal<const Connection &> onDisconnection; + +private: + std::vector<Event> m_events; + + net::Listener<> m_listener; + + ConnectionService m_connections; + Database m_database; + +public: + Server(ConnectionSettings settings, DatabaseSettings dbsettings); + + inline const ConnectionService &connections() const noexcept + { + return m_connections; + } + + inline ConnectionService &connections() noexcept + { + return m_connections; + } + + inline Database &database() + { + return m_database; + } + + void run() noexcept; + + void post(Event func) noexcept; +}; + +} // !malikania + +#endif // !MALIKANIA_SERVER_HPP