Mercurial > code
changeset 514:d89a5e9e5fa7
Remove sockets directory
author | David Demelier <markand@malikania.fr> |
---|---|
date | Wed, 25 May 2016 14:17:55 +0200 |
parents | ab70c638dc1d |
children | 409cf1aa4af9 |
files | modules/sockets/sockets.cpp modules/sockets/sockets.h modules/sockets/test/main.cpp |
diffstat | 3 files changed, 0 insertions(+), 4205 deletions(-) [+] |
line wrap: on
line diff
--- a/modules/sockets/sockets.cpp Tue Apr 05 15:18:41 2016 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,718 +0,0 @@ -/* - * sockets.cpp -- portable C++ socket wrappers - * - * 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. - */ - -#define TIMEOUT_MSG "operation timeout" - -#include <algorithm> -#include <atomic> -#include <cstring> -#include <mutex> - -#include "sockets.h" - -namespace net { - -/* - * Portable constants - * ------------------------------------------------------------------ - */ - -/* {{{ Constants */ - -#if defined(_WIN32) - -const Handle Invalid{INVALID_SOCKET}; -const int Failure{SOCKET_ERROR}; - -#else - -const Handle Invalid{-1}; -const int Failure{-1}; - -#endif - -/* }}} */ - -/* - * Portable functions - * ------------------------------------------------------------------ - */ - -/* {{{ Functions */ - -#if defined(_WIN32) - -namespace { - -static std::mutex s_mutex; -static std::atomic<bool> s_initialized{false}; - -} // !namespace - -#endif // !_WIN32 - -void init() noexcept -{ -#if defined(_WIN32) - std::lock_guard<std::mutex> lock(s_mutex); - - if (!s_initialized) { - s_initialized = true; - - WSADATA wsa; - WSAStartup(MAKEWORD(2, 2), &wsa); - - /* - * If SOCKET_WSA_NO_INIT is not set then the user - * must also call finish himself. - */ -#if !defined(SOCKET_NO_AUTO_INIT) - atexit(finish); -#endif - } -#endif -} - -void finish() noexcept -{ -#if defined(_WIN32) - WSACleanup(); -#endif -} - -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 -} - -std::string error() -{ -#if defined(_WIN32) - return error(WSAGetLastError()); -#else - return error(errno); -#endif -} - -/* }}} */ - -/* - * SSL stuff - * ------------------------------------------------------------------ - */ - -/* {{{ SSL initialization */ - -#if !defined(SOCKET_NO_SSL) - -namespace ssl { - -namespace { - -std::mutex mutex; -std::atomic<bool> initialized{false}; - -} // !namespace - -void finish() noexcept -{ - ERR_free_strings(); -} - -void init() noexcept -{ - std::lock_guard<std::mutex> lock{mutex}; - - if (!initialized) { - initialized = true; - - SSL_library_init(); - SSL_load_error_strings(); - OpenSSL_add_all_algorithms(); - -#if !defined(SOCKET_NO_AUTO_SSL_INIT) - atexit(finish); -#endif // SOCKET_NO_AUTO_SSL_INIT - } -} - -} // !ssl - -#endif // SOCKET_NO_SSL - -/* }}} */ - -/* - * Error class - * ------------------------------------------------------------------ - */ - -/* {{{ Error */ - -Error::Error(Code code, std::string function) - : m_code(code) - , m_function(std::move(function)) - , m_error(error()) -{ -} - -Error::Error(Code code, std::string function, int n) - : m_code(code) - , m_function(std::move(function)) - , m_error(error(n)) -{ -} - -Error::Error(Code code, std::string function, std::string error) - : m_code(code) - , m_function(std::move(function)) - , m_error(std::move(error)) -{ -} - -/* }}} */ - -/* - * Predefined addressed to be used - * ------------------------------------------------------------------ - */ - -/* {{{ Addresses */ - -namespace address { - -namespace { - -void resolv(sockaddr *destination, const std::string host, std::uint16_t port, int domain) -{ - assert(domain == AF_INET || domain == AF_INET6); - - addrinfo hints, *res; - - std::memset(&hints, 0, sizeof (addrinfo)); - hints.ai_family = domain; - - auto error = getaddrinfo(host.c_str(), std::to_string(port).c_str(), &hints, &res); - if (error != 0) - throw Error(Error::System, "getaddrinfo", gai_strerror(error)); - - if (domain == AF_INET6) - std::memcpy(destination, res->ai_addr, res->ai_addrlen); - else - std::memcpy(destination, res->ai_addr, res->ai_addrlen); - - freeaddrinfo(res); -} - -} // !namespace - -/* {{{ Ipv4 */ - -Ipv4::Ipv4() noexcept -{ - std::memset(&m_sin, 0, sizeof (sockaddr_in)); -} - -Ipv4::Ipv4(const std::string &host, std::uint16_t port) - : Ipv4() -{ - if (host == "*") { - m_sin.sin_addr.s_addr = INADDR_ANY; - m_sin.sin_family = AF_INET; - m_sin.sin_port = htons(port); - } else { - resolv(reinterpret_cast<sockaddr *>(&m_sin), host, port, AF_INET); - } -} - -Ipv4::Ipv4(const sockaddr_storage *ss, socklen_t length) noexcept -{ - std::memcpy(&m_sin, ss, length); -} - -/* }}} */ - -/* {{{ Ipv6 */ - -Ipv6::Ipv6() noexcept -{ - std::memset(&m_sin6, 0, sizeof (sockaddr_in6)); -} - -Ipv6::Ipv6(const std::string &host, std::uint16_t port) - : Ipv6() -{ - if (host == "*") { - m_sin6.sin6_addr = in6addr_any; - m_sin6.sin6_family = AF_INET6; - m_sin6.sin6_port = htons(port); - } else { - resolv(reinterpret_cast<sockaddr *>(&m_sin6), host, port, AF_INET6); - } -} - -Ipv6::Ipv6(const sockaddr_storage *ss, socklen_t length) noexcept -{ - std::memcpy(&m_sin6, ss, length); -} - -/* }}} */ - -/* {{{ Local */ - -#if !defined(_WIN32) - -Local::Local() noexcept -{ - std::memset(&m_sun, 0, sizeof (sockaddr_un)); -} - -Local::Local(std::string path, bool rm) noexcept - : m_path(std::move(path)) -{ - /* Silently remove the file even if it fails */ - if (rm) - ::remove(m_path.c_str()); - - /* Copy the path */ - std::memset(m_sun.sun_path, 0, sizeof (m_sun.sun_path)); - std::strncpy(m_sun.sun_path, m_path.c_str(), sizeof (m_sun.sun_path) - 1); - - /* Set the parameters */ - m_sun.sun_family = AF_LOCAL; -} - -Local::Local(const sockaddr_storage *ss, socklen_t length) noexcept -{ - assert(ss->ss_family == AF_LOCAL); - - if (ss->ss_family == AF_LOCAL) { - std::memcpy(&m_sun, ss, length); - m_path = reinterpret_cast<const sockaddr_un &>(m_sun).sun_path; - } -} - -#endif // !_WIN32 - -/* }}} */ - -} // !address - -/* }}} */ - -/* - * Listener backends - * ------------------------------------------------------------------ - */ - -/* {{{ Backends */ -/* - * Select - * ------------------------------------------------------------------ - */ - -/* {{{ Select */ - -std::vector<ListenerStatus> Select::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(max + 1, &readset, &writeset, nullptr, towait); - if (error == Failure) { - throw Error{Error::System, "select"}; - } - if (error == 0) { - throw Error{Error::Timeout, "select", TIMEOUT_MSG}; - } - - 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; -} - -/* }}} */ - -/* - * Poll - * ------------------------------------------------------------------ - */ - -/* {{{ Poll */ - -/* - * Poll implementation - * ------------------------------------------------------------------ - */ - -#if defined(SOCKET_HAVE_POLL) - -#if defined(_WIN32) -# define poll WSAPoll -#endif - -short Poll::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 Poll::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; -} - -void Poll::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); - } -} - -void Poll::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)); - } -} - -std::vector<ListenerStatus> Poll::wait(const ListenerTable &, int ms) -{ - auto result = poll(m_fds.data(), m_fds.size(), ms); - if (result == 0) { - throw Error{Error::Timeout, "select", TIMEOUT_MSG}; - } - if (result < 0) { - throw Error{Error::System, "poll"}; - } - - 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 // !SOCKET_HAVE_POLL - -/* }}} */ - -/* - * Epoll implementation - * ------------------------------------------------------------------ - */ - -/* {{{ Epoll */ - -#if defined(SOCKET_HAVE_EPOLL) - -uint32_t Epoll::toEpoll(Condition condition) const noexcept -{ - uint32_t events = 0; - - if ((condition & Condition::Readable) == Condition::Readable) - events |= EPOLLIN; - if ((condition & Condition::Writable) == Condition::Writable) - events |= EPOLLOUT; - - return events; -} - -Condition Epoll::toCondition(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 Epoll::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(Error::System, "epoll_ctl"); -} - -Epoll::Epoll() - : m_handle(epoll_create1(0)) -{ - if (m_handle < 0) - throw Error{Error::System, "epoll_create"}; -} - -Epoll::~Epoll() -{ - if (m_handle != -1) - close(m_handle); -} - -/* - * For set and unset, we need to apply the whole flags required, so if the socket - * was set to Connection::Readable and user add Connection::Writable, we must - * place both. - */ -void Epoll::set(const ListenerTable &table, Handle sc, Condition condition, bool add) -{ - if (add) { - update(sc, EPOLL_CTL_ADD, toEpoll(condition)); - m_events.resize(m_events.size() + 1); - } else { - update(sc, EPOLL_CTL_MOD, toEpoll(table.at(sc) | 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. - */ -void Epoll::unset(const ListenerTable &table, Handle sc, Condition condition, bool remove) -{ - if (remove) { - update(sc, EPOLL_CTL_DEL, 0); - m_events.resize(m_events.size() - 1); - } else { - update(sc, EPOLL_CTL_MOD, toEpoll(table.at(sc) & ~(condition))); - } -} - -std::vector<ListenerStatus> Epoll::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 Error(Error::Timeout, "epoll_wait", TIMEOUT_MSG); - if (ret < 0) - throw Error(Error::System, "epoll_wait"); - - for (int i = 0; i < ret; ++i) - result.push_back(ListenerStatus{m_events[i].data.fd, toCondition(m_events[i].events)}); - - return result; -} - -#endif // !SOCKET_HAVE_EPOLL - -/* }}} */ - -/* - * Kqueue implementation - * ------------------------------------------------------------------ - */ - -/* {{{ Kqueue */ - -#if defined(SOCKET_HAVE_KQUEUE) - -Kqueue::Kqueue() - : m_handle(kqueue()) -{ - if (m_handle < 0) - throw Error(Error::System, "kqueue"); -} - -Kqueue::~Kqueue() -{ - if (m_handle != -1) - close(m_handle); -} - -void Kqueue::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{Error::System, "kevent"}; - } -} - -void Kqueue::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); - } -} - -void Kqueue::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); - } -} - -std::vector<ListenerStatus> Kqueue::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 Error{Error::Timeout, "kevent", TIMEOUT_MSG}; - } - if (nevents < 0) { - throw Error{Error::System, "kevent"}; - } - - 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; -} - -#endif // !SOCKET_HAVE_KQUEUE - -/* }}} */ - -/* }}} */ - -} // !net
--- a/modules/sockets/sockets.h Tue Apr 05 15:18:41 2016 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,2783 +0,0 @@ -/* - * sockets.h -- portable C++ socket wrappers - * - * 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 _SOCKETS_H_ -#define _SOCKETS_H_ - -/** - * @file sockets.h - * @brief Portable socket abstraction - * - * # Introduction - * - * This file is a portable networking library. - * - * ## Options - * - * The user may set the following variables before compiling these files: - * - * ### General options - * - * - **SOCKET_NO_AUTO_INIT**: (bool) Set to 0 if you don't want Socket class to - * automatically calls net::init function and net::finish functions. - * - **SOCKET_NO_SSL**: (bool) Set to 0 if you don't have access to OpenSSL library. - * - **SOCKET_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 net::ssl::init and net::ssl::finish. - * - * ### 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. - * - * - **SOCKET_HAVE_POLL**: Defined on all BSD, Linux. Also defined on Windows - * if _WIN32_WINNT is set to 0x0600 or greater. - * - **SOCKET_HAVE_KQUEUE**: Defined on all BSD and Apple. - * - **SOCKET_HAVE_EPOLL**: Defined on Linux only. - * - **SOCKET_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 | - */ - -#if defined(_WIN32) -# if _WIN32_WINNT >= 0x0600 && !defined(SOCKET_HAVE_POLL) -# define SOCKET_HAVE_POLL -# endif -#elif defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__) -# if !defined(SOCKET_HAVE_KQUEUE) -# define SOCKET_HAVE_KQUEUE -# endif -# if !defined(SOCKET_HAVE_POLL) -# define SOCKET_HAVE_POLL -# endif -#elif defined(__linux__) -# if !defined(SOCKET_HAVE_EPOLL) -# define SOCKET_HAVE_EPOLL -# endif -# if !defined(SOCKET_HAVE_POLL) -# define SOCKET_HAVE_POLL -# endif -#endif - -/* - * Define SOCKET_DEFAULT_BACKEND - * ------------------------------------------------------------------ - */ - -/** - * Defines the default Listener backend to use. - * - * @note Do not rely on the value shown in doxygen. - */ -#if defined(_WIN32) -# if !defined(SOCKET_DEFAULT_BACKEND) -# if defined(SOCKET_HAVE_POLL) -# define SOCKET_DEFAULT_BACKEND Poll -# else -# define SOCKET_DEFAULT_BACKEND Select -# endif -# endif -#elif defined(__linux__) -# include <sys/epoll.h> - -# if !defined(SOCKET_DEFAULT_BACKEND) -# define SOCKET_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(SOCKET_DEFAULT_BACKEND) -# define SOCKET_DEFAULT_BACKEND Kqueue -# endif -#else -# if !defined(SOCKET_DEFAULT_BACKEND) -# define SOCKET_DEFAULT_BACKEND Select -# endif -#endif - -#if defined(SOCKET_HAVE_POLL) && !defined(_WIN32) -# include <poll.h> -#endif - -/* - * Headers to include - * ------------------------------------------------------------------ - */ - -#if defined(_WIN32) -# include <cstdlib> - -# include <WinSock2.h> -# include <WS2tcpip.h> -#else -# include <cerrno> - -# 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 - -#if !defined(SOCKET_NO_SSL) -# include <openssl/err.h> -# include <openssl/evp.h> -# include <openssl/ssl.h> -#endif - -#include <cassert> -#include <chrono> -#include <cstdint> -#include <cstdlib> -#include <cstring> -#include <exception> -#include <functional> -#include <map> -#include <memory> -#include <string> -#include <vector> - -/** - * General network namespace. - */ -namespace net { - -/* - * Portables types - * ------------------------------------------------------------------ - * - * The following types are defined differently between Unix and Windows. - */ - -/* {{{ Protocols */ - -#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. - */ - -/* {{{ Constants */ - -/* - * The following constants are defined differently from Unix - * to Windows. - */ -#if defined(_WIN32) - -/** - * Socket creation failure or invalidation. - */ -extern const Handle Invalid; - -/** - * Socket operation failure. - */ -extern const int Failure; - -#else - -/** - * Socket creation failure or invalidation. - */ -extern const int Invalid; - -/** - * Socket operation failure. - */ -extern const int Failure; - -#endif - -#if !defined(SOCKET_NO_SSL) - -namespace ssl { - -/** - * @enum Method - * @brief Which OpenSSL method to use. - */ -enum Method { - Tlsv1, //!< TLS v1.2 (recommended) - Sslv3 //!< SSLv3 -}; - -} // !ssl - -#endif - -/* }}} */ - -/* - * Portable functions - * ------------------------------------------------------------------ - * - * The following free functions can be used to initialize the library or to get the last system error. - */ - -/* {{{ Functions */ - -/** - * Initialize the socket library. Except if you defined SOCKET_NO_AUTO_INIT, you don't need to call this - * function manually. - */ -void init() noexcept; - -/** - * Close the socket library. - */ -void finish() noexcept; - -#if !defined(SOCKET_NO_SSL) - -/** - * OpenSSL namespace. - */ -namespace ssl { - -/** - * Initialize the OpenSSL library. Except if you defined SOCKET_NO_AUTO_SSL_INIT, you don't need to call this function - * manually. - */ -void init() noexcept; - -/** - * Close the OpenSSL library. - */ -void finish() noexcept; - -} // !ssl - -#endif // SOCKET_NO_SSL - -/** - * Get the last socket system error. The error is set from errno or from - * WSAGetLastError on Windows. - * - * @return a string message - */ -std::string error(); - -/** - * Get the last system error. - * - * @param errn the error number (errno or WSAGetLastError) - * @return the error - */ -std::string error(int errn); - -/* }}} */ - -/* - * Error class - * ------------------------------------------------------------------ - * - * This is the main exception thrown on socket operations. - */ - -/* {{{ Error */ - -/** - * @class Error - * @brief Base class for sockets error - */ -class Error : public std::exception { -public: - /** - * @enum Code - * @brief Which kind of error - */ - enum Code { - Timeout, ///!< The action did timeout - System, ///!< There is a system error - Other ///!< Other custom error - }; - -private: - Code m_code; - std::string m_function; - std::string m_error; - -public: - /** - * Constructor that use the last system error. - * - * @param code which kind of error - * @param function the function name - */ - Error(Code code, std::string function); - - /** - * Constructor that use the system error set by the user. - * - * @param code which kind of error - * @param function the function name - * @param error the error - */ - Error(Code code, std::string function, int error); - - /** - * Constructor that set the error specified by the user. - * - * @param code which kind of error - * @param function the function name - * @param error the error - */ - Error(Code code, std::string function, std::string error); - - /** - * Get which function has triggered the error. - * - * @return the function name (e.g connect) - */ - inline const std::string &function() const noexcept - { - return m_function; - } - - /** - * The error code. - * - * @return the code - */ - inline Code code() const noexcept - { - return m_code; - } - - /** - * Get the error (only the error content). - * - * @return the error - */ - const char *what() const noexcept - { - return m_error.c_str(); - } -}; - -/* }}} */ - -/* - * Condition enum - * ------------------------------------------------------------------ - * - * Defines if we must wait for reading or writing. - */ - -/* {{{ Condition */ - -/** - * @enum Condition - * @brief Define the required condition for the socket. - * - * As explained in Action enumeration, some operations required to be called several times, before calling these - * operations, the user must wait the socket to be readable or writable. This can be checked with Socket::condition. - */ -enum class Condition { - None, //!< No condition is required - Readable = (1 << 0), //!< The socket must be readable - Writable = (1 << 1) //!< The socket must be writable -}; - -/** - * Apply bitwise XOR. - * - * @param v1 the first value - * @param v2 the second value - * @return the new value - */ -constexpr 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 - */ -constexpr 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 - */ -constexpr 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 - */ -constexpr 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; -} - -/* }}} */ - -/* - * Base Socket class - * ------------------------------------------------------------------ - * - * This base class has operations that are common to all types of sockets but you usually instanciate - * a SocketTcp or SocketUdp - */ - -/* {{{ Socket */ - -/** - * @class Socket - * @brief Base socket class for socket operations. - * - * **Important:** When using non-blocking sockets, some considerations must be taken. See the implementation of the - * underlying protocol for more details. - * - * @see protocol::Tls - * @see protocol::Tcp - * @see protocol::Udp - */ -template <typename Address, typename Protocol> -class Socket { -private: - Protocol m_proto; - - inline void reset(Condition *condition) const noexcept - { - if (condition) - *condition = Condition::None; - } - -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 - * @param iface the implementation - * @throw net::Error on errors - */ - Socket(int domain, int type, int protocol, Protocol iface = {}) - : m_proto(std::move(iface)) - { -#if !defined(SOCKET_NO_AUTO_INIT) - init(); -#endif - m_handle = ::socket(domain, type, protocol); - - if (m_handle == Invalid) - throw Error(Error::System, "socket"); - - m_proto.create(*this); - } - - /** - * This tries to create a socket. - * - * Domain and type are determined by the Address and Protocol object. - * - * @param protocol the protocol - * @param address which type of address - * @throw net::Error on errors - */ - explicit inline Socket(Protocol protocol = {}, const Address &address = {}) - : Socket(address.domain(), protocol.type(), 0, std::move(protocol)) - { - } - - explicit inline Socket(Handle handle, Protocol protocol = {}) noexcept - : m_proto(std::move(protocol)) - , 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_proto(std::move(other.m_proto)) - , m_handle(other.m_handle) - { - /* Invalidate other */ - other.m_handle = Invalid; - } - - /** - * Default destructor. - */ - virtual ~Socket() - { - close(); - } - - /** - * Access the implementation. - * - * @return the implementation - * @warning use this function with care - */ - inline const Protocol &protocol() const noexcept - { - return m_proto; - } - - /** - * Overloaded function. - * - * @return the implementation - */ - inline Protocol &protocol() noexcept - { - return m_proto; - } - - /** - * 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 net::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(Error::System, "set"); - } - - /** - * Object-oriented option setter. - * - * The object must have `set(Socket<Address, Protocol> &) const`. - * - * @pre isOpen() - * @param option the option - * @throw net::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 - * @throw net::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(Error::System, "get"); - - std::memcpy(&result, &desired, size); - - return result; - } - - /** - * Object-oriented option getter. - * - * The object must have `T get(Socket<Address, Protocol> &) 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 net::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 net::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(Error::System, "bind"); - } - - /** - * Overload that takes an address. - * - * @pre isOpen() - * @param address the address - * @throw net::Error on errors - */ - inline void bind(const Address &address) - { - assert(m_handle != Invalid); - - if (::bind(m_handle, address.address(), address.length()) == Failure) - throw Error(Error::System, "bind"); - } - - /** - * Overload that creates an address on the fly. - * - * @pre isOpen() - * @param args the arguments to Address constructor - * @throw net::Error on errors - */ - template <typename... Args> - inline void bind(Args&&... args) - { - assert(m_handle != Invalid); - - Address address{std::forward<Args>(args)...}; - - if (::bind(m_handle, address.address(), address.length()) == Failure) - throw Error(Error::System, "bind"); - } - - /** - * Listen for pending connection. - * - * @pre isOpen() - * @param max the maximum number - * @throw net::Error on errors - */ - inline void listen(int max = 128) - { - assert(m_handle != Invalid); - - if (::listen(this->m_handle, max) == Failure) - throw Error(Error::System, "listen"); - } - - /** - * Get the local name. This is a wrapper of getsockname(). - * - * @pre isOpen() - * @return the address - * @throw Error on failures - * @pre state() must not be State::Closed - */ - Address address() 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(Error::System, "getsockname"); - - return Address(&ss, length); - } - - void connect(const sockaddr *address, socklen_t length, Condition *cond = nullptr) - { - assert(m_handle != Invalid); - - Condition dummy = Condition::None; - - reset(cond); - m_proto.connect(*this, address, length, cond ? *cond : dummy); - } - - inline void connect(const Address &address, Condition *cond = nullptr) - { - connect(address.address(), address.length(), cond); - } - - void connect(Condition *cond = nullptr) - { - assert(m_handle != Invalid); - - Condition dummy = Condition::None; - - reset(cond); - m_proto.connect(*this, cond ? *cond : dummy); - } - - void accept(Socket<Address, Protocol> &client, Address *address = nullptr, Condition *cond = nullptr) - { - assert(m_handle != Invalid); - - reset(cond); - - Condition dummy = Condition::None; - sockaddr_storage storage; - socklen_t length = sizeof (storage); - - reset(cond); - m_proto.accept(*this, client, reinterpret_cast<sockaddr *>(&storage), &length, cond ? *cond : dummy); - - if (address) - *address = Address(&storage, length); - } - - void accept(Condition *cond = nullptr) - { - assert(m_handle != Invalid); - - Condition dummy = Condition::None; - - reset(cond); - m_proto.accept(*this, cond ? *cond : dummy); - } - - unsigned recv(void *data, unsigned length, Condition *cond = nullptr) - { - assert(m_handle != Invalid); - - Condition dummy = Condition::None; - - reset(cond); - - return m_proto.recv(*this, data, length, cond ? *cond : dummy); - } - - std::string recv(unsigned count, Condition *cond = nullptr) - { - assert(m_handle != Invalid); - - std::string result; - - result.resize(count); - auto n = recv(const_cast<char *>(result.data()), count, cond); - result.resize(n); - - return result; - } - - unsigned send(const void *data, unsigned length, Condition *cond = nullptr) - { - assert(m_handle != Invalid); - - Condition dummy = Condition::None; - - reset(cond); - - return m_proto.send(*this, data, length, cond ? *cond : dummy); - } - - inline unsigned send(const std::string &data, Condition *cond = nullptr) - { - return send(data.c_str(), data.length(), cond); - } - - unsigned sendto(const void *data, unsigned length, const sockaddr *address, socklen_t addrlen, Condition *cond = nullptr) - { - assert(m_handle != Invalid); - - Condition dummy = Condition::None; - - reset(cond); - - return m_proto.sendto(*this, data, length, address, addrlen, cond ? *cond : dummy); - } - - inline unsigned sendto(const void *data, unsigned length, const Address &address, Condition *cond = nullptr) - { - return sendto(data, length, address.address(), address.length(), cond); - } - - inline unsigned sendto(const std::string &data, const Address &address, Condition *cond = nullptr) - { - return sendto(data.c_str(), data.length(), address.address(), address.length(), cond); - } - - unsigned recvfrom(void *data, unsigned length, sockaddr *address, socklen_t *addrlen, Condition *cond = nullptr) - { - assert(m_handle != Invalid); - - Condition dummy = Condition::None; - - reset(cond); - - return m_proto.recvfrom(*this, data, length, address, addrlen, cond ? *cond : dummy); - } - - unsigned recvfrom(void *data, unsigned length, Address *address = nullptr, Condition *cond = nullptr) - { - sockaddr_storage storage; - socklen_t addrlen = sizeof (sockaddr_storage); - - auto n = recvfrom(data, length, reinterpret_cast<sockaddr *>(&storage), &addrlen, cond); - - if (address && n != 0) - *address = Address(&storage, addrlen); - - return n; - } - - std::string recvfrom(unsigned count, Address *info = nullptr, Condition *cond = nullptr) - { - std::string result; - - result.resize(count); - auto n = recvfrom(const_cast<char *>(result.data()), count, info, cond); - result.resize(n); - - return result; - } - - /** - * 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; - m_proto = std::move(other.m_proto); - - /* Invalidate other */ - other.m_handle = Invalid; - - return *this; - } -}; - -/** - * Compare two sockets. - * - * @param s1 the first socket - * @param s2 the second socket - * @return true if they equals - */ -template <typename Address, typename Protocol> -bool operator==(const Socket<Address, Protocol> &s1, const Socket<Address, Protocol> &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 - */ -template <typename Address, typename Protocol> -bool operator!=(const Socket<Address, Protocol> &s1, const Socket<Address, Protocol> &s2) -{ - return s1.handle() != s2.handle(); -} - -/** - * Compare two sockets. - * - * @param s1 the first socket - * @param s2 the second socket - * @return true if s1 < s2 - */ -template <typename Address, typename Protocol> -bool operator<(const Socket<Address, Protocol> &s1, const Socket<Address, Protocol> &s2) -{ - return s1.handle() < s2.handle(); -} - -/** - * Compare two sockets. - * - * @param s1 the first socket - * @param s2 the second socket - * @return true if s1 > s2 - */ -template <typename Address, typename Protocol> -bool operator>(const Socket<Address, Protocol> &s1, const Socket<Address, Protocol> &s2) -{ - return s1.handle() > s2.handle(); -} - -/** - * Compare two sockets. - * - * @param s1 the first socket - * @param s2 the second socket - * @return true if s1 <= s2 - */ -template <typename Address, typename Protocol> -bool operator<=(const Socket<Address, Protocol> &s1, const Socket<Address, Protocol> &s2) -{ - return s1.handle() <= s2.handle(); -} - -/** - * Compare two sockets. - * - * @param s1 the first socket - * @param s2 the second socket - * @return true if s1 >= s2 - */ -template <typename Address, typename Protocol> -bool operator>=(const Socket<Address, Protocol> &s1, const Socket<Address, Protocol> &s2) -{ - return s1.handle() >= s2.handle(); -} - -/* }}} */ - -/* - * Predefined options - * ------------------------------------------------------------------ - */ - -/* {{{ Options */ - -/** - * Namespace of predefined options. - */ -namespace option { - -/* - * Options for socket - * ------------------------------------------------------------------ - */ - -/* {{{ Options for socket */ - -/** - * @class SockBlockMode - * @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: - inline SockBlockMode(bool value = true) noexcept - : m_value(value) - { - } - - /** - * Set the option. - * - * @param sc the socket - * @throw Error on errors - */ - template <typename Address, typename Protocol> - void set(Socket<Address, Protocol> &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(Error::System, "fcntl"); -#else - unsigned long flags = (m_value) ? 0 : 1; - - if (ioctlsocket(sc.handle(), FIONBIO, &flags) == Failure) - throw Error(Error::System, "fcntl"); -#endif - } - - /** - * Get the option. - * - * @return the value - * @throw Error on errors - */ - template <typename Address, typename Protocol> - bool get(Socket<Address, Protocol> &sc) const - { -#if defined(O_NONBLOCK) && !defined(_WIN32) - int flags = fcntl(sc.handle(), F_GETFL, 0); - - if (flags < 0) - throw Error(Error::System, "fcntl"); - - return !(flags & O_NONBLOCK); -#else - throw Error(Error::Other, "get", std::strerror(ENOSYS)); -#endif - } -}; - -/** - * @class SockReuseAddress - * @brief Reuse address, must be used before calling Socket::bind - */ -class SockReuseAddress { -private: - bool m_value; - -public: - inline SockReuseAddress(bool value = true) noexcept - : m_value(value) - { - } - - /** - * Set the option. - * - * @param sc the socket - * @throw Error on errors - */ - template <typename Address, typename Protocol> - inline void set(Socket<Address, Protocol> &sc) const - { - sc.set(SOL_SOCKET, SO_REUSEADDR, m_value ? 1 : 0); - } - - /** - * Get the option. - * - * @return the value - * @throw Error on errors - */ - template <typename Address, typename Protocol> - inline bool get(Socket<Address, Protocol> &sc) const - { - return static_cast<bool>(sc.template get<int>(SOL_SOCKET, SO_REUSEADDR)); - } -}; - -/** - * @class SockSendBuffer - * @brief Set or get the output buffer. - */ -class SockSendBuffer { -public: - /** - * Set to the buffer size. - */ - int value{2048}; - - /** - * Set the option. - * - * @param sc the socket - * @throw Error on errors - */ - template <typename Address, typename Protocol> - inline void set(Socket<Address, Protocol> &sc) const - { - sc.set(SOL_SOCKET, SO_SNDBUF, value); - } - - /** - * Get the option. - * - * @return the value - * @throw Error on errors - */ - template <typename Address, typename Protocol> - inline int get(Socket<Address, Protocol> &sc) const - { - return sc.template get<int>(SOL_SOCKET, SO_SNDBUF); - } -}; - -/** - * @class SockReceiveBuffer - * @brief Set or get the input buffer. - */ -class SockReceiveBuffer { -public: - /** - * Set to the buffer size. - */ - int value{2048}; - - /** - * Set the option. - * - * @param sc the socket - * @throw Error on errors - */ - template <typename Address, typename Protocol> - inline void set(Socket<Address, Protocol> &sc) const - { - sc.set(SOL_SOCKET, SO_RCVBUF, value); - } - - /** - * Get the option. - * - * @return the value - * @throw Error on errors - */ - template <typename Address, typename Protocol> - inline int get(Socket<Address, Protocol> &sc) const - { - return sc.template get<int>(SOL_SOCKET, SO_RCVBUF); - } -}; - -/* }}} */ - -/* {{{ Options for TCP */ - -/** - * @class TcpNoDelay - * @brief Set this option if you want to disable nagle's algorithm. - */ -class TcpNoDelay { -private: - bool m_value; - -public: - inline TcpNoDelay(bool value = true) noexcept - : m_value(value) - { - } - - /** - * Set the option. - * - * @param sc the socket - * @throw Error on errors - */ - template <typename Address, typename Protocol> - inline void set(Socket<Address, Protocol> &sc) const - { - sc.set(IPPROTO_TCP, TCP_NODELAY, m_value ? 1 : 0); - } - - /** - * Get the option. - * - * @return the value - * @throw Error on errors - */ - template <typename Address, typename Protocol> - inline bool get(Socket<Address, Protocol> &sc) const - { - return static_cast<bool>(sc.template get<int>(IPPROTO_TCP, TCP_NODELAY)); - } -}; - -/* }}} */ - -/* {{{ Options for IPv6 */ - -/** - * @class Ipv6Only - * @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: - inline Ipv6Only(bool value = true) noexcept - : m_value(value) - { - } - - /** - * Set the option. - * - * @param sc the socket - * @throw Error on errors - */ - template <typename Address, typename Protocol> - inline void set(Socket<Address, Protocol> &sc) const - { - sc.set(IPPROTO_IPV6, IPV6_V6ONLY, m_value ? 1 : 0); - } - - /** - * Get the option. - * - * @return the value - * @throw Error on errors - */ - template <typename Address, typename Protocol> - inline bool get(Socket<Address, Protocol> &sc) const - { - return static_cast<bool>(sc.template get<int>(IPPROTO_IPV6, IPV6_V6ONLY)); - } -}; - -/* }}} */ - -} // !option - -/* }}} */ - -/* - * Predefined addressed to be used - * ------------------------------------------------------------------ - * - * - Ipv4, - * - Ipv6, - * - Local. - */ - -/* {{{ Addresses */ - -/** - * Set of predefined addresses. - */ -namespace address { - -class Ipv4 { -public: -private: - sockaddr_in m_sin; - -public: - Ipv4() noexcept; - Ipv4(const std::string &host, std::uint16_t port); - Ipv4(const sockaddr_storage *ss, socklen_t length) noexcept; - - inline int domain() const noexcept - { - return AF_INET; - } - - inline const sockaddr *address() const noexcept - { - return reinterpret_cast<const sockaddr *>(&m_sin); - } - - /** - * Return the underlying address length. - * - * @return the length - */ - inline socklen_t length() const noexcept - { - return sizeof (sockaddr_in); - } - - /** - * Get the port. - * - * @return the port - */ - inline std::uint16_t port() const noexcept - { - return ntohs(m_sin.sin_port); - } - - /** - * Get the IP address in textual form. - * - * @return the address - * @throw Error on errors - */ - std::string ip() const - { - char result[128]; - - std::memset(result, 0, sizeof (result)); - - if (!inet_ntop(AF_INET, const_cast<in_addr *>(&m_sin.sin_addr), result, sizeof (result))) - throw Error(Error::System, "inet_ntop"); - - return result; - } -}; - -class Ipv6 { -private: - sockaddr_in6 m_sin6; - -public: - Ipv6() noexcept; - Ipv6(const std::string &host, std::uint16_t port); - Ipv6(const sockaddr_storage *ss, socklen_t length) noexcept; - - inline int domain() const noexcept - { - return AF_INET6; - } - - inline const sockaddr *address() const noexcept - { - return reinterpret_cast<const sockaddr *>(&m_sin6); - } - - /** - * Return the underlying address length. - * - * @return the length - */ - inline socklen_t length() const noexcept - { - return sizeof (sockaddr_in6); - } - - /** - * Get the port. - * - * @return the port - */ - inline std::uint16_t port() const noexcept - { - return ntohs(m_sin6.sin6_port); - } - - /** - * Get the IP address in textual form. - * - * @return the address - * @throw Error on errors - */ - std::string ip() const - { - char result[128]; - - std::memset(result, 0, sizeof (result)); - - if (!inet_ntop(AF_INET6, const_cast<in6_addr *>(&m_sin6.sin6_addr), result, sizeof (result))) - throw Error(Error::System, "inet_ntop"); - - return result; - } -}; - -using Ip = Ipv4; - -#if !defined(_WIN32) - -/** - * @class Local - * @brief unix family sockets - * - * Create an address to a specific path. Only available on Unix. - */ -class Local { -private: - sockaddr_un m_sun; - std::string m_path; - -public: - /** - * Get the domain AF_LOCAL. - * - * @return AF_LOCAL - */ - inline int domain() const noexcept - { - return AF_LOCAL; - } - - /** - * Default constructor. - */ - Local() noexcept; - - /** - * Construct an address to a path. - * - * @param path the path - * @param rm remove the file before (default: false) - */ - Local(std::string path, bool rm = false) noexcept; - - /** - * Construct an unix address from a storage address. - * - * @pre storage's domain must be AF_LOCAL - * @param ss the storage - * @param length the length - */ - Local(const sockaddr_storage *ss, socklen_t length) noexcept; - - /** - * Get the sockaddr_un. - * - * @return the address - */ - inline const sockaddr *address() const noexcept - { - return reinterpret_cast<const sockaddr *>(&m_sun); - } - - /** - * Get the address length. - * - * @return the length - */ - inline socklen_t length() const noexcept - { -#if defined(SOCKET_HAVE_SUN_LEN) - return SUN_LEN(&m_sun); -#else - return sizeof (m_sun); -#endif - } -}; - -#endif // !_WIN32 - -} // !address - -/* }}} */ - -/* - * Predefined protocols - * ------------------------------------------------------------------ - * - * - Tcp, for standard stream connections, - * - Udp, for standard datagram connections, - * - Tls, for secure stream connections. - */ - -/* {{{ Protocols */ - -/** - * Set of predefined protocols. - */ -namespace protocol { - -/* {{{ Tcp */ - -/** - * @class Tcp - * @brief Clear TCP implementation. - * - * This is the basic TCP protocol that implements recv, send, connect and accept as wrappers of the usual - * C functions. - */ -class Tcp { -public: - /** - * Socket type. - * - * @return SOCK_STREAM - */ - inline int type() const noexcept - { - return SOCK_STREAM; - } - - /** - * Do nothing. - * - * This function is just present for compatibility, it should never be called. - */ - template <typename Address> - inline void create(Socket<Address, Tcp> &) const noexcept - { - /* No-op */ - } - - template <typename Address, typename Protocol> - void connect(Socket<Address, Protocol> &sc, const sockaddr *address, socklen_t length, Condition &cond) - { - if (::connect(sc.handle(), address, length) == Failure) { - /* - * Determine if the error comes from a non-blocking connect that cannot be - * accomplished yet. - */ -#if defined(_WIN32) - int error = WSAGetLastError(); - - if (error == WSAEWOULDBLOCK) - cond = Condition::Writable; - else - throw Error(Error::System, "connect", error); -#else - if (errno == EINPROGRESS) - cond = Condition::Writable; - else - throw Error(Error::System, "connect"); -#endif - } - } - - template <typename Address, typename Protocol> - void connect(Socket<Address, Protocol> &sc, Condition &) - { - int error = sc.template get<int>(SOL_SOCKET, SO_ERROR); - - if (error != 0) - throw Error(Error::System, "connect", error); - } - - template <typename Address, typename Protocol> - void accept(Socket<Address, Protocol> &sc, Socket<Address, Protocol> &client, sockaddr *address, socklen_t *length, Condition &) - { - Handle handle = ::accept(sc.handle(), address, length); - - if (handle == Invalid) - client = Socket<Address, Protocol>(); - - client = Socket<Address, Protocol>(handle); - } - - template <typename Address, typename Protocol> - inline void accept(Socket<Address, Protocol> &, Condition &) const noexcept - { - /* no op */ - } - - template <typename Address> - unsigned recv(Socket<Address, Tcp> &sc, void *data, unsigned length, Condition &cond) - { - int nbread = ::recv(sc.handle(), (Arg)data, length, 0); - - if (nbread == Failure) { -#if defined(_WIN32) - int error = WSAGetLastError(); - - if (error == WSAEWOULDBLOCK) { - nbread = 0; - cond = Condition::Readable; - } else { - throw Error(Error::System, "recv", error); - } -#else - if (errno == EAGAIN || errno == EWOULDBLOCK) { - nbread = 0; - cond = Condition::Readable; - } else { - throw Error(Error::System, "recv"); - } -#endif - } - - return static_cast<unsigned>(nbread); - } - - template <typename Address> - unsigned send(Socket<Address, Tcp> &sc, const void *data, unsigned length, Condition &cond) - { - int nbsent = ::send(sc.handle(), (ConstArg)data, length, 0); - - if (nbsent == Failure) { -#if defined(_WIN32) - int error = WSAGetLastError(); - - if (error == WSAEWOULDBLOCK) { - nbsent = 0; - cond = Condition::Writable; - } else { - throw Error(Error::System, "send", error); - } -#else - if (errno == EAGAIN || errno == EWOULDBLOCK) { - nbsent = 0; - cond = Condition::Writable; - } else { - throw Error(Error::System, "send"); - } -#endif - } - - return static_cast<unsigned>(nbsent); - } -}; - -/* }}} */ - -/* {{{ Udp */ - -/** - * @class Udp - * @brief Clear UDP type. - * - * This class is the basic implementation of UDP sockets. - */ -class Udp { -public: - /** - * Socket type. - * - * @return SOCK_DGRAM - */ - inline int type() const noexcept - { - return SOCK_DGRAM; - } - - /** - * Do nothing. - */ - template <typename Address> - inline void create(Socket<Address, Udp> &) noexcept - { - /* No-op */ - } - - template <typename Address> - unsigned recvfrom(Socket<Address, Udp> &sc, void *data, unsigned length, sockaddr *address, socklen_t *addrlen, Condition &cond) - { - int nbread; - - nbread = ::recvfrom(sc.handle(), (Arg)data, length, 0, address, addrlen); - - if (nbread == Failure) { -#if defined(_WIN32) - int error = WSAGetLastError(); - - if (error == WSAEWOULDBLOCK) { - nbread = 0; - cond = Condition::Writable; - } else { - throw Error(Error::System, "recvfrom"); - } -#else - if (errno == EAGAIN || errno == EWOULDBLOCK) { - nbread = 0; - cond = Condition::Writable; - } else { - throw Error(Error::System, "recvfrom"); - } -#endif - } - - return static_cast<unsigned>(nbread); - } - - template <typename Address> - unsigned sendto(Socket<Address, Udp> &sc, const void *data, unsigned length, const sockaddr *address, socklen_t addrlen, Condition &cond) - { - int nbsent; - - nbsent = ::sendto(sc.handle(), (ConstArg)data, length, 0, address, addrlen); - if (nbsent == Failure) { -#if defined(_WIN32) - int error = WSAGetLastError(); - - if (error == WSAEWOULDBLOCK) { - nbsent = 0; - cond = Condition::Writable; - } else { - throw Error(Error::System, "sendto", error); - } -#else - if (errno == EAGAIN || errno == EWOULDBLOCK) { - nbsent = 0; - cond = Condition::Writable; - } else { - throw Error(Error::System, "sendto"); - } -#endif - } - - return static_cast<unsigned>(nbsent); - } -}; - -/* }}} */ - -/* {{{ Tls */ - -#if !defined(SOCKET_NO_SSL) - -class Tls : private Tcp { -private: - using Context = std::shared_ptr<SSL_CTX>; - using Ssl = std::unique_ptr<SSL, void (*)(SSL *)>; - - /* OpenSSL objects */ - Context m_context; - Ssl m_ssl{nullptr, nullptr}; - - /* Status */ - bool m_tcpconnected{false}; - - /* - * User definable parameters - */ - ssl::Method m_method{ssl::Tlsv1}; - std::string m_key; - std::string m_certificate; - bool m_verify{false}; - - /* - * Construct with a context and ssl, for Tls::accept. - */ - Tls(Context context, Ssl ssl) - : m_context(std::move(context)) - , m_ssl(std::move(ssl)) - { - } - - /* - * Get the OpenSSL error message. - */ - inline std::string error(int error) - { - auto msg = ERR_reason_error_string(error); - - return msg == nullptr ? "" : msg; - } - - template <typename Function> - void wrap(const std::string &func, Condition &cond, Function &&function) - { - auto ret = function(); - - if (ret <= 0) { - int no = SSL_get_error(m_ssl.get(), ret); - - switch (no) { - case SSL_ERROR_WANT_READ: - cond = Condition::Readable; - break; - case SSL_ERROR_WANT_WRITE: - cond = Condition::Writable; - break; - default: - throw Error(Error::System, func, error(no)); - } - } - } - - void doConnect(Condition &cond) - { - wrap("connect", cond, [&] () -> int { - return SSL_connect(m_ssl.get()); - }); - } - - void doAccept(Condition &cond) - { - wrap("accept", cond, [&] () -> int { - return SSL_accept(m_ssl.get()); - }); - } - -public: - /** - * @copydoc Tcp::type - */ - inline int type() const noexcept - { - return SOCK_STREAM; - } - - /** - * Empty TLS constructor. - */ - Tls() - { -#if !defined(SOCKET_NO_SSL_AUTO_INIT) - ::net::ssl::init(); -#endif - } - - /** - * Set the method. - * - * @param method the method - * @pre the socket must not be already created - */ - inline void setMethod(ssl::Method method) noexcept - { - assert(!m_context); - assert(!m_ssl); - - m_method = method; - } - - /** - * Use the specified private key file. - * - * @param file the path to the private key - */ - inline void setPrivateKey(std::string file) noexcept - { - m_key = std::move(file); - } - - /** - * Use the specified certificate file. - * - * @param file the path to the file - */ - inline void setCertificate(std::string file) noexcept - { - m_certificate = std::move(file); - } - - /** - * Set to true if we must verify the certificate and private key. - * - * @param verify the mode - */ - inline void setVerify(bool verify = true) noexcept - { - m_verify = verify; - } - - /** - * Initialize the SSL objects after have created. - * - * @param sc the socket - * @throw net::Error on errors - */ - template <typename Address> - inline void create(Socket<Address, Tls> &sc) - { - auto method = (m_method == ssl::Tlsv1) ? TLSv1_method() : SSLv3_method(); - - 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(), sc.handle()); - - /* Load certificates */ - if (m_certificate.size() > 0) - SSL_CTX_use_certificate_file(m_context.get(), m_certificate.c_str(), SSL_FILETYPE_PEM); - if (m_key.size() > 0) - SSL_CTX_use_PrivateKey_file(m_context.get(), m_key.c_str(), SSL_FILETYPE_PEM); - if (m_verify && !SSL_CTX_check_private_key(m_context.get())) - throw Error(Error::System, "(openssl)", "unable to verify key"); - } - - template <typename Address, typename Protocol> - void connect(Socket<Address, Protocol> &sc, const sockaddr *address, socklen_t length, Condition &cond) - { - /* 1. Connect using raw TCP */ - Tcp::connect(sc, address, length, cond); - - /* 2. If the connection is complete (e.g. non-blocking), try handshake */ - if (cond == Condition::None) { - m_tcpconnected = true; - doConnect(cond); - } - } - - template <typename Address, typename Protocol> - void connect(Socket<Address, Protocol> &sc, Condition &cond) - { - /* 1. Be sure to complete standard connect before */ - if (!m_tcpconnected) { - Tcp::connect(sc, cond); - m_tcpconnected = cond = Condition::None; - } - - if (m_tcpconnected) - doConnect(cond); - } - - template <typename Address> - void accept(Socket<Address, Tls> &sc, Socket<Address, Tls> &client, sockaddr *address, socklen_t *length, Condition &cond) - { - /* TCP sets empty client if no pending connection is available */ - Tcp::accept(sc, client, address, length, cond); - - if (client.isOpen()) { - Tls &proto = client.protocol(); - - /* 1. Share the context */ - proto.m_context = m_context; - - /* 2. Create new SSL instance */ - proto.m_ssl = Ssl(SSL_new(m_context.get()), SSL_free); - - SSL_set_fd(proto.m_ssl.get(), client.handle()); - - /* 3. Try accept process on the **new** client */ - proto.doAccept(cond); - } - } - - template <typename Address, typename Protocol> - inline void accept(Socket<Address, Protocol> &sc, Condition &cond) - { - doAccept(cond); - } - - template <typename Address> - unsigned recv(Socket<Address, Tls> &, void *data, unsigned len, Condition &cond) - { - int nbread = 0; - - wrap("recv", cond, [&] () -> int { - return (nbread = SSL_read(m_ssl.get(), data, len)); - }); - - return static_cast<unsigned>(nbread < 0 ? 0 : nbread); - } - - template <typename Address> - unsigned send(Socket<Address, Tls> &, const void *data, unsigned len, Condition &cond) - { - int nbsent = 0; - - wrap("send", cond, [&] () -> int { - return (nbsent = SSL_write(m_ssl.get(), data, len)); - }); - - return static_cast<unsigned>(nbsent < 0 ? 0 : nbsent); - } -}; - -#endif // !SOCKET_NO_SSL - -/* }}} */ - -} // !protocol - -/* }}} */ - -/* - * Convenient helpers - * ------------------------------------------------------------------ - * - * - SocketTcp<Address>, for TCP sockets, - * - SocketUdp<Address>, for UDP sockets, - * - SocketTls<Address>, for secure TCP sockets. - */ - -/* {{{ Helpers */ - -/** - * Helper to create TCP sockets. - */ -template <typename Address> -using SocketTcp = Socket<Address, protocol::Tcp>; - -/** - * Helper to create TCP/IPv4 sockets. - */ -using SocketTcpIp = Socket<address::Ipv4, protocol::Tcp>; - -/** - * Helper to create TCP/IPv6 sockets. - */ -using SocketTcpIpv6 = Socket<address::Ipv6, protocol::Tcp>; - -#if !defined(_WIN32) - -/** - * Helper to create TCP/Local sockets. - */ -using SocketTcpLocal = Socket<address::Local, protocol::Tcp>; - -#endif - -/** - * Helper to create UDP sockets. - */ -template <typename Address> -using SocketUdp = Socket<Address, protocol::Udp>; - -/** - * Helper to create UDP/IPv4 sockets. - */ -using SocketUdpIp = Socket<address::Ipv4, protocol::Udp>; - -/** - * Helper to create UDP/IPv6 sockets. - */ -using SocketUdpIpv6 = Socket<address::Ipv6, protocol::Udp>; - -#if !defined(SOCKET_NO_SSL) - -/** - * Helper to create OpenSSL TCP sockets. - */ -template <typename Address> -using SocketTls = Socket<Address, protocol::Tls>; - -/** - * Helper to create OpenSSL TCP/Ip sockets. - */ -using SocketTlsIp = Socket<address::Ipv4, protocol::Tls>; - -#endif // !SOCKET_NO_SSL - -/* }}} */ - -/* - * Listener - * ------------------------------------------------------------------ - * - * Wrapper for select(2) and other various implementations. - */ - -/* {{{ Listener stuff */ - -/** - * @class ListenerStatus - * @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::map<Handle, Condition>; - -/* {{{ Backends */ - -/* {{{ Select */ - -/** - * @class Select - * @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: - /** - * No-op, uses the ListenerTable directly. - */ - inline void set(const ListenerTable &, Handle, Condition, bool) noexcept {} - - /** - * No-op, uses the ListenerTable directly. - */ - inline void unset(const ListenerTable &, Handle, Condition, bool) noexcept {} - - /** - * Return the sockets - */ - std::vector<ListenerStatus> wait(const ListenerTable &table, int ms); - - /** - * Backend identifier - */ - inline const char *name() const noexcept - { - return "select"; - } -}; - -/* }}} */ - -/* {{{ Poll */ - -#if defined(SOCKET_HAVE_POLL) - -/** - * @class Poll - * @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 flags) const noexcept; - Condition toCondition(short &event) const noexcept; - -public: - /** - * Set the handle. - */ - void set(const ListenerTable &, Handle, Condition, bool); - - /** - * Unset the handle. - */ - void unset(const ListenerTable &, Handle, Condition, bool); - - /** - * Wait for events. - */ - std::vector<ListenerStatus> wait(const ListenerTable &, int ms); - - /** - * Backend identifier - */ - inline const char *name() const noexcept - { - return "poll"; - } -}; - -#endif - -/* }}} */ - -/* {{{ Epoll */ - -#if defined(SOCKET_HAVE_EPOLL) - -/** - * @class Epoll - * @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; - - uint32_t toEpoll(Condition flags) const noexcept; - Condition toCondition(uint32_t events) const noexcept; - void update(Handle sc, int op, int eflags); - -public: - /** - * Construct the epoll instance. - */ - Epoll(); - - inline Epoll(Epoll &&other) noexcept - : m_handle(other.m_handle) - { - other.m_handle = -1; - } - - Epoll &operator=(Epoll &&other) - { - m_handle = other.m_handle; - other.m_handle = -1; - - return *this; - } - - /** - * Close the epoll instance. - */ - ~Epoll(); - - /** - * Set the handle. - */ - void set(const ListenerTable &, Handle, Condition, bool); - - /** - * Unset the handle. - */ - void unset(const ListenerTable &, Handle, Condition, bool); - - /** - * Wait for events. - */ - std::vector<ListenerStatus> wait(const ListenerTable &, int); - - /** - * Backend identifier - */ - inline const char *name() const noexcept - { - return "epoll"; - } -}; - -#endif - -/* }}} */ - -/* {{{ Kqueue */ - -#if defined(SOCKET_HAVE_KQUEUE) - -/** - * @class Kqueue - * @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 sc, int filter, int kflags); - -public: - /** - * Construct the kqueue instance. - */ - Kqueue(); - - inline Kqueue(Kqueue &&other) noexcept - : m_handle(other.m_handle) - { - other.m_handle = -1; - } - - inline Kqueue &operator=(Kqueue &&other) noexcept - { - m_handle = other.m_handle; - other.m_handle = -1; - - return *this; - } - - /** - * Destroy the kqueue instance. - */ - ~Kqueue(); - - /** - * Set the handle. - */ - void set(const ListenerTable &, Handle, Condition, bool); - - /** - * Unset the handle. - */ - void unset(const ListenerTable &, Handle, Condition, bool); - - /** - * Wait for events. - */ - std::vector<ListenerStatus> wait(const ListenerTable &, int); - - /** - * Backend identifier - */ - inline const char *name() const noexcept - { - return "kqueue"; - } -}; - -#endif - -/* }}} */ - -/* }}} */ - -/* {{{ Listener */ - -/** - * @class Listener - * @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. - * - * To implement the backend, the following functions must be available: - * - * ### Set - * - * @code - * void set(const ListenerTable &, Handle sc, Condition condition, bool add); - * @endcode - * - * This function, takes the socket to be added and the flags. The condition is - * always guaranteed to be correct and the function will never be called twice - * even if the user tries to set the same flag again. - * - * An optional add argument is added for backends which needs to do different - * operation depending if the socket was already set before or if it is the - * first time (e.g EPOLL_CTL_ADD vs EPOLL_CTL_MOD for epoll(7). - * - * ### Unset - * - * @code - * void unset(const ListenerTable &, Handle sc, Condition condition, bool remove); - * @endcode - * - * Like set, this function is only called if the condition is actually set and will - * not be called multiple times. - * - * Also like set, an optional remove argument is set if the socket is being - * completely removed (e.g no more flags are set for this socket). - * - * ### Wait - * - * @code - * std::vector<ListenerStatus> wait(const ListenerTable &, int ms); - * @endcode - * - * Wait for the sockets to be ready with the specified milliseconds. Must return a list of ListenerStatus, - * may throw any exceptions. - * - * ### Name - * - * @code - * inline const char *name() const noexcept - * @endcode - * - * Returns the backend name. Usually the class in lower case. - */ -template <typename Backend = SOCKET_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; - } - - /** - * 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. - * - * @param sc the socket - * @param condition the condition (may be OR'ed) - * @throw Error if the backend failed to set - */ - void set(Handle sc, Condition condition) - { - /* Invalid or useless flags */ - 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 { - /* Remove flag if already present */ - if ((condition & Condition::Readable) == Condition::Readable && - (it->second & Condition::Readable) == Condition::Readable) - condition &= ~(Condition::Readable); - if ((condition & Condition::Writable) == Condition::Writable && - (it->second & Condition::Writable) == Condition::Writable) - condition &= ~(Condition::Writable); - - /* Still need a call? */ - if (condition != Condition::None) { - m_backend.set(m_table, sc, condition, false); - it->second |= condition; - } - } - } - - /** - * Unset a socket from the listener, only the flags is removed - * unless the two flagss are requested. - * - * For example, if you added a socket for both reading and writing, - * unsetting the write flags will keep the socket for reading. - * - * @param sc the socket - * @param condition the condition (may be OR'ed) - * @see remove - */ - void unset(Handle sc, Condition condition) - { - auto it = m_table.find(sc); - - /* Invalid or useless flags */ - if (condition == Condition::None || static_cast<int>(condition) > 0x3 || it == m_table.end()) - return; - - /* - * Like set, do not update if the socket is already at the appropriate - * state. - */ - if ((condition & Condition::Readable) == Condition::Readable && - (it->second & Condition::Readable) != Condition::Readable) - condition &= ~(Condition::Readable); - if ((condition & Condition::Writable) == Condition::Writable && - (it->second & Condition::Writable) != Condition::Writable) - condition &= ~(Condition::Writable); - - if (condition != Condition::None) { - /* Determine if it's a complete removal */ - 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. - */ - 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); - - return m_backend.wait(m_table, cvt.count())[0]; - } - - /** - * Overload with milliseconds. - * - * @param timeout the optional timeout in milliseconds - * @return the socket ready - */ - inline ListenerStatus wait(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. - * - * @return the socket ready - */ - inline std::vector<ListenerStatus> waitMultiple(int timeout = -1) - { - return waitMultiple(std::chrono::milliseconds(timeout)); - } -}; - -/* }}} */ - -/* }}} */ - -} // !net - -#endif // !_SOCKETS_H_
--- a/modules/sockets/test/main.cpp Tue Apr 05 15:18:41 2016 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,704 +0,0 @@ -/* - * main.cpp -- test sockets - * - * Copyright (c) 2013-2015 David Demelier <markand@malikania.fr> - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include <chrono> -#include <iostream> -#include <sstream> -#include <string> -#include <thread> - -#include <gtest/gtest.h> - -#include <sockets.h> - -using namespace net; -using namespace net::address; -using namespace net::option; -using namespace net::protocol; - -using namespace std::literals::chrono_literals; - -/* - * Options - * ------------------------------------------------------------------ - */ - -TEST(Options, reuse) -{ - SocketTcpIp s; - - try { - s.set(option::SockReuseAddress(true)); - ASSERT_TRUE(s.get<option::SockReuseAddress>()); - - s.set(option::SockReuseAddress(false)); - ASSERT_FALSE(s.get<option::SockReuseAddress>()); - } catch (const std::exception &ex) { - FAIL() << ex.what(); - } -} - -TEST(Options, nodelay) -{ - SocketTcpIp s; - - try { - s.set(option::TcpNoDelay(true)); - ASSERT_TRUE(s.get<option::TcpNoDelay>()); - - s.set(option::TcpNoDelay(false)); - ASSERT_FALSE(s.get<option::TcpNoDelay>()); - } catch (const std::exception &ex) { - FAIL() << ex.what(); - } -} - -TEST(Options, v6only) -{ - SocketTcpIpv6 s; - - try { - s.set(option::Ipv6Only(true)); - ASSERT_TRUE(s.get<option::Ipv6Only>()); - - s.set(option::Ipv6Only(false)); - ASSERT_FALSE(s.get<option::Ipv6Only>()); - } catch (const std::exception &ex) { - FAIL() << ex.what(); - } -} - -/* -------------------------------------------------------- - * TCP tests - * -------------------------------------------------------- */ - -class TcpServerTest : public testing::Test { -protected: - SocketTcp<Ipv4> m_server; - SocketTcp<Ipv4> m_client; - - std::thread m_tserver; - std::thread m_tclient; - -public: - TcpServerTest() - { - m_server.set(SockReuseAddress()); - } - - ~TcpServerTest() - { - if (m_tserver.joinable()) - m_tserver.join(); - if (m_tclient.joinable()) - m_tclient.join(); - } -}; - -TEST_F(TcpServerTest, connect) -{ - m_tserver = std::thread([this] () { - SocketTcp<Ipv4> sc; - - m_server.bind(Ipv4("*", 16000)); - m_server.listen(); - m_server.accept(sc); - m_server.close(); - }); - - std::this_thread::sleep_for(100ms); - - m_tclient = std::thread([this] () { - m_client.connect(Ipv4("127.0.0.1", 16000)); - m_client.close(); - }); -} - -TEST_F(TcpServerTest, io) -{ - m_tserver = std::thread([this] () { - m_server.bind(Ipv4("*", 16000)); - m_server.listen(); - - SocketTcp<Ipv4> client; - - m_server.accept(client); - - auto msg = client.recv(512); - - ASSERT_EQ("hello world", msg); - - client.send(msg); - }); - - std::this_thread::sleep_for(100ms); - - m_tclient = std::thread([this] () { - m_client.connect(Ipv4("127.0.0.1", 16000)); - m_client.send("hello world"); - - ASSERT_EQ("hello world", m_client.recv(512)); - }); -} - -/* -------------------------------------------------------- - * UDP tests - * -------------------------------------------------------- */ - -class UdpServerTest : public testing::Test { -protected: - SocketUdp<Ipv4> m_server; - SocketUdp<Ipv4> m_client; - - std::thread m_tserver; - std::thread m_tclient; - -public: - UdpServerTest() - { - m_server.set(SockBlockMode()); - } - - ~UdpServerTest() - { - if (m_tserver.joinable()) - m_tserver.join(); - if (m_tclient.joinable()) - m_tclient.join(); - } -}; - -TEST_F(UdpServerTest, io) -{ - m_tserver = std::thread([this] () { - Ipv4 client; - Ipv4 info("*", 16000); - - m_server.bind(info); - auto msg = m_server.recvfrom(512, &client); - - ASSERT_EQ("hello world", msg); - - m_server.sendto(msg, client); - m_server.close(); - }); - - std::this_thread::sleep_for(100ms); - - m_tclient = std::thread([this] () { - Ipv4 info("127.0.0.1", 16000); - - m_client.sendto("hello world", info); - - ASSERT_EQ("hello world", m_client.recvfrom(512, &info)); - - m_client.close(); - }); -} - -/* -------------------------------------------------------- - * Listener: set function - * -------------------------------------------------------- */ - -class TestBackendSet { -public: - int m_callcount{0}; - bool m_added{false}; - Condition m_flags{Condition::None}; - - inline void set(const ListenerTable &, Handle, Condition flags, bool add) noexcept - { - m_callcount ++; - m_added = add; - m_flags |= flags; - } - - inline void unset(const ListenerTable &, Handle, Condition, bool) noexcept - { - } - - std::vector<ListenerStatus> wait(const ListenerTable &, int) - { - return {}; - } -}; - -class TestBackendSetFail { -public: - inline void set(const ListenerTable &, Handle, Condition, bool) - { - throw "fail"; - } - - inline void unset(const ListenerTable &, Handle, Condition, bool) noexcept - { - } - - std::vector<ListenerStatus> wait(const ListenerTable &, int) - { - return {}; - } -}; - -TEST(ListenerSet, initialAdd) -{ - Listener<TestBackendSet> listener; - Handle s = 0; - - listener.set(s, Condition::Readable); - - ASSERT_EQ(1U, listener.size()); - ASSERT_EQ(1, listener.backend().m_callcount); - ASSERT_TRUE(listener.backend().m_added); - ASSERT_TRUE(listener.backend().m_flags == Condition::Readable); -} - -TEST(ListenerSet, readThenWrite) -{ - Listener<TestBackendSet> listener; - Handle s = 0; - - listener.set(s, Condition::Readable); - listener.set(s, Condition::Writable); - - ASSERT_EQ(1U, listener.size()); - ASSERT_EQ(2, listener.backend().m_callcount); - ASSERT_FALSE(listener.backend().m_added); - ASSERT_TRUE(static_cast<int>(listener.backend().m_flags) == 0x3); -} - -TEST(ListenerSet, allOneShot) -{ - Listener<TestBackendSet> listener; - Handle s = 0; - - listener.set(s, Condition::Readable | Condition::Writable); - - ASSERT_EQ(1U, listener.size()); - ASSERT_EQ(1, listener.backend().m_callcount); - ASSERT_TRUE(listener.backend().m_added); - ASSERT_TRUE(static_cast<int>(listener.backend().m_flags) == 0x3); -} - -TEST(ListenerSet, readTwice) -{ - Listener<TestBackendSet> listener; - Handle s = 0; - - listener.set(s, Condition::Readable); - listener.set(s, Condition::Readable); - - ASSERT_EQ(1U, listener.size()); - ASSERT_EQ(1, listener.backend().m_callcount); - ASSERT_TRUE(listener.backend().m_added); - ASSERT_TRUE(listener.backend().m_flags == Condition::Readable); -} - -TEST(ListenerSet, failure) -{ - Listener<TestBackendSetFail> listener; - Handle s = 0; - - try { - listener.set(s, Condition::Readable); - FAIL() << "exception expected"; - } catch (...) { - } - - ASSERT_EQ(0U, listener.size()); -} - -/* -------------------------------------------------------- - * Listener: unset / remove functions - * -------------------------------------------------------- */ - -class TestBackendUnset { -public: - bool m_isset{false}; - bool m_isunset{false}; - Condition m_flags{Condition::None}; - bool m_removal{false}; - - inline void set(const ListenerTable &, Handle &, Condition flags, bool) noexcept - { - m_isset = true; - m_flags |= flags; - } - - inline void unset(const ListenerTable &, Handle &, Condition flags, bool remove) noexcept - { - m_isunset = true; - m_flags &= ~(flags); - m_removal = remove; - } - - std::vector<ListenerStatus> wait(const ListenerTable &, int) noexcept - { - return {}; - } -}; - -class TestBackendUnsetFail { -public: - inline void set(const ListenerTable &, Handle &, Condition, bool) noexcept - { - } - - inline void unset(const ListenerTable &, Handle &, Condition, bool) - { - throw "fail"; - } - - std::vector<ListenerStatus> wait(const ListenerTable &, int) - { - return {}; - } -}; - -TEST(ListenerUnsetRemove, unset) -{ - Listener<TestBackendUnset> listener; - Handle s = 0; - - listener.set(s, Condition::Readable); - listener.unset(s, Condition::Readable); - - ASSERT_EQ(0U, listener.size()); - ASSERT_TRUE(listener.backend().m_isset); - ASSERT_TRUE(listener.backend().m_isunset); - ASSERT_TRUE(listener.backend().m_flags == Condition::None); - ASSERT_TRUE(listener.backend().m_removal); -} - -TEST(ListenerUnsetRemove, unsetOne) -{ - Listener<TestBackendUnset> listener; - Handle s = 0; - - listener.set(s, Condition::Readable | Condition::Writable); - listener.unset(s, Condition::Readable); - - ASSERT_EQ(1U, listener.size()); - ASSERT_TRUE(listener.backend().m_isset); - ASSERT_TRUE(listener.backend().m_isunset); - ASSERT_TRUE(listener.backend().m_flags == Condition::Writable); - ASSERT_FALSE(listener.backend().m_removal); -} - -TEST(ListenerUnsetRemove, unsetAll) -{ - Listener<TestBackendUnset> listener; - Handle s = 0; - - listener.set(s, Condition::Readable | Condition::Writable); - listener.unset(s, Condition::Readable); - listener.unset(s, Condition::Writable); - - ASSERT_EQ(0U, listener.size()); - ASSERT_TRUE(listener.backend().m_isset); - ASSERT_TRUE(listener.backend().m_isunset); - ASSERT_TRUE(listener.backend().m_flags == Condition::None); - ASSERT_TRUE(listener.backend().m_removal); -} - -TEST(ListenerUnsetRemove, remove) -{ - Listener<TestBackendUnset> listener; - Handle s = 0; - - listener.set(s, Condition::Readable | Condition::Writable); - listener.remove(s); - - ASSERT_EQ(0U, listener.size()); - ASSERT_TRUE(listener.backend().m_isset); - ASSERT_TRUE(listener.backend().m_isunset); - ASSERT_TRUE(listener.backend().m_flags == Condition::None); - ASSERT_TRUE(listener.backend().m_removal); -} - -TEST(ListenerUnsetRemove, failure) -{ - Listener<TestBackendUnsetFail> listener; - Handle s = 0; - - listener.set(s, Condition::Readable | Condition::Writable); - - try { - listener.remove(s); - FAIL() << "exception expected"; - } catch (...) { - } - - /* If fail, kept into the table */ - ASSERT_EQ(1U, listener.size()); -} - -/* -------------------------------------------------------- - * Listener: system - * -------------------------------------------------------- */ - -class ListenerTest : public testing::Test { -protected: - Listener<Select> m_listener; - SocketTcp<Ipv4> m_masterTcp; - SocketTcp<Ipv4> m_clientTcp; - - std::thread m_tserver; - std::thread m_tclient; - -public: - ListenerTest() - { - m_masterTcp.set(SockReuseAddress()); - m_masterTcp.bind(Ipv4("*", 16000)); - m_masterTcp.listen(); - } - - ~ListenerTest() - { - if (m_tserver.joinable()) - m_tserver.join(); - if (m_tclient.joinable()) - m_tclient.join(); - } -}; - -TEST_F(ListenerTest, accept) -{ - m_tserver = std::thread([this] () { - try { - m_listener.set(m_masterTcp.handle(), Condition::Readable); - m_listener.wait(); - m_masterTcp.accept(nullptr); - m_masterTcp.close(); - } catch (const std::exception &ex) { - FAIL() << ex.what(); - } - }); - - std::this_thread::sleep_for(100ms); - - m_tclient = std::thread([this] () { - m_clientTcp.connect(Ipv4("127.0.0.1", 16000)); - }); -} - -TEST_F(ListenerTest, recv) -{ - m_tserver = std::thread([this] () { - try { - m_listener.set(m_masterTcp.handle(), Condition::Readable); - m_listener.wait(); - - SocketTcp<Ipv4> sc; - - m_masterTcp.accept(sc); - - ASSERT_EQ("hello", sc.recv(512)); - } catch (const std::exception &ex) { - FAIL() << ex.what(); - } - }); - - std::this_thread::sleep_for(100ms); - - m_tclient = std::thread([this] () { - m_clientTcp.connect(Ipv4("127.0.0.1", 16000)); - m_clientTcp.send("hello"); - }); -} - -/* -------------------------------------------------------- - * Non-blocking connect - * -------------------------------------------------------- */ - -class NonBlockingConnectTest : public testing::Test { -protected: - SocketTcp<Ipv4> m_server; - SocketTcp<Ipv4> m_client; - - std::thread m_tserver; - std::thread m_tclient; - -public: - NonBlockingConnectTest() - { - m_client.set(SockBlockMode(false)); - } - - ~NonBlockingConnectTest() - { - if (m_tserver.joinable()) - m_tserver.join(); - if (m_tclient.joinable()) - m_tclient.join(); - } -}; - -/* -------------------------------------------------------- - * TCP accept - * -------------------------------------------------------- */ - -class TcpAcceptTest : public testing::Test { -protected: - SocketTcp<Ipv4> m_server; - SocketTcp<Ipv4> m_client; - - std::thread m_tserver; - std::thread m_tclient; - -public: - TcpAcceptTest() - { - m_server.set(SockReuseAddress()); - m_server.bind(Ipv4("*", 16000)); - m_server.listen(); - } - - ~TcpAcceptTest() - { - if (m_tserver.joinable()) - m_tserver.join(); - if (m_tclient.joinable()) - m_tclient.join(); - } -}; - -/* -------------------------------------------------------- - * TCP recv - * -------------------------------------------------------- */ - -class TcpRecvTest : public testing::Test { -protected: - SocketTcp<Ipv4> m_server; - SocketTcp<Ipv4> m_client; - - std::thread m_tserver; - std::thread m_tclient; - -public: - TcpRecvTest() - { - m_server.set(SockReuseAddress()); - m_server.bind(Ipv4("*", 16000)); - m_server.listen(); - } - - ~TcpRecvTest() - { - if (m_tserver.joinable()) - m_tserver.join(); - if (m_tclient.joinable()) - m_tclient.join(); - } -}; - -TEST_F(TcpRecvTest, blockingSuccess) -{ - m_tserver = std::thread([this] () { - SocketTcp<Ipv4> client; - - m_server.accept(client); - - ASSERT_EQ("hello", client.recv(32)); - }); - - std::this_thread::sleep_for(100ms); - - m_tclient = std::thread([this] () { - m_client.connect(Ipv4("127.0.0.1", 16000)); - m_client.send("hello"); - m_client.close(); - }); -} - -/* -------------------------------------------------------- - * Socket SSL - * -------------------------------------------------------- */ - -#if !defined(SOCKET_NO_SSL) - -class TlsRecvTest : public testing::Test { -protected: - SocketTls<Ipv4> m_server{nullptr}; - SocketTls<Ipv4> m_client; - - std::thread m_tserver; - std::thread m_tclient; - -public: - TlsRecvTest() - { - Tls protocol; - - protocol.setCertificate("sockets/test.crt"); - protocol.setPrivateKey("sockets/test.key"); - protocol.setVerify(false); - - m_server = SocketTls<Ipv4>(std::move(protocol)); - m_server.set(SockReuseAddress()); - m_server.bind(Ipv4("*", 16000)); - m_server.listen(); - } - - ~TlsRecvTest() - { - if (m_tserver.joinable()) - m_tserver.join(); - if (m_tclient.joinable()) - m_tclient.join(); - } -}; - -TEST_F(TlsRecvTest, blockingSuccess) -{ - m_tserver = std::thread([this] () { - try { - SocketTls<Ipv4> client; - - m_server.accept(client); - - ASSERT_EQ("hello", client.recv(32)); - - std::this_thread::sleep_for(500ms); - } catch (const net::Error &ex) { - FAIL() << ex.function() << ": " << ex.what(); - } - }); - - std::this_thread::sleep_for(500ms); - - m_tclient = std::thread([this] () { - try { - puts("OLOL"); - m_client.connect(Ipv4("127.0.0.1", 16000)); - m_client.send("hello"); - } catch (const net::Error &ex) { - FAIL() << ex.function() << ": " << ex.what(); - } - }); -} - -#endif - -int main(int argc, char **argv) -{ - testing::InitGoogleTest(&argc, argv); - - return RUN_ALL_TESTS(); -}