Mercurial > code
changeset 512:0002b8da93dc
Net: rework a lot, going to stabilization soon
author | David Demelier <markand@malikania.fr> |
---|---|
date | Mon, 04 Apr 2016 17:34:01 +0200 |
parents | 5bab839611f2 |
children | ab70c638dc1d |
files | CMakeLists.txt modules/net/CMakeLists.txt modules/net/examples/blocking-accept.cpp modules/net/examples/blocking-connect.cpp modules/net/examples/elapsed-timer.cpp modules/net/examples/elapsed-timer.h modules/net/examples/non-blocking-accept.cpp modules/net/examples/non-blocking-connect.cpp modules/net/examples/test.crt modules/net/examples/test.key modules/net/net.cpp modules/net/net.h modules/net/test/main.cpp modules/net/test/test.crt modules/net/test/test.key modules/sockets/CMakeLists.txt modules/sockets/examples/blocking-accept.cpp modules/sockets/examples/blocking-connect.cpp modules/sockets/examples/elapsed-timer.cpp modules/sockets/examples/elapsed-timer.h modules/sockets/examples/non-blocking-accept.cpp modules/sockets/examples/non-blocking-connect.cpp modules/sockets/examples/test.crt modules/sockets/examples/test.key modules/sockets/test/test.crt modules/sockets/test/test.key |
diffstat | 26 files changed, 4566 insertions(+), 489 deletions(-) [+] |
line wrap: on
line diff
--- a/CMakeLists.txt Thu Feb 25 21:01:02 2016 +0100 +++ b/CMakeLists.txt Mon Apr 04 17:34:01 2016 +0200 @@ -53,5 +53,5 @@ ) endif () +add_subdirectory(modules/net) add_subdirectory(modules/options) -add_subdirectory(modules/sockets)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/net/CMakeLists.txt Mon Apr 04 17:34:01 2016 +0200 @@ -0,0 +1,46 @@ +# +# CMakeLists.txt -- sockets module +# +# 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. +# + +cmake_minimum_required(VERSION 3.0) +project(net) + +set( + SOURCES + ${net_SOURCE_DIR}/net.cpp + ${net_SOURCE_DIR}/net.h +) + +if (OPENSSL_FOUND) + if (WIN32) + set(LIBRARIES ws2_32) + endif () + + code_define_module( + NAME sockets + INCLUDES ${OPENSSL_INCLUDE_DIR} + LIBRARIES + ${LIBRARIES} + ${OPENSSL_LIBRARIES} + SOURCES + ${net_SOURCE_DIR}/net.cpp + ${net_SOURCE_DIR}/net.h + RESOURCES + ${net_SOURCE_DIR}/test/test.crt + ${net_SOURCE_DIR}/test/test.key + ) +endif ()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/net/examples/blocking-accept.cpp Mon Apr 04 17:34:01 2016 +0200 @@ -0,0 +1,52 @@ +/* + * blocking-accept.cpp -- example of blocking accept + * + * Options: + * - WITH_PORT (int), the port to use (default: 16000) + * - WITH_TIMEOUT (int), number of seconds before giving up (default: 60) + * - WITH_SSL (bool), true to test with SSL (default: false) + */ + +#include <iostream> + +#include "sockets.h" + +#if !defined(WITH_PORT) +# define WITH_PORT 16000 +#endif + +#if !defined(WITH_TIMEOUT) +# define WITH_TIMEOUT 60 +#endif + +int main() +{ +#if defined(WITH_SSL) + net::SocketTls<net::address::Ip> master; + net::SocketTls<net::address::Ip> client{net::Invalid}; +#else + net::SocketTcp<net::address::Ip> master; + net::SocketTcp<net::address::Ip> client{net::Invalid}; +#endif + + net::Listener<> listener; + + try { + master.set(net::option::SockReuseAddress{true}); + master.bind(net::address::Ip{"*", WITH_PORT}); + master.listen(); + + listener.set(master.handle(), net::Condition::Readable); + listener.wait(std::chrono::seconds(WITH_TIMEOUT)); + + master.accept(client); + } catch (const net::Error &error) { + std::cerr << "error: " << error.what() << std::endl; + std::exit(1); + } + + std::cout << "Client successfully accepted!" << std::endl; + + return 0; +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/net/examples/blocking-connect.cpp Mon Apr 04 17:34:01 2016 +0200 @@ -0,0 +1,48 @@ +/* + * blocking-connect.cpp -- example of blocking connect + * + * Options: + * - WITH_HOST (string literal), the host to try (default: "malikania.fr") + * - WITH_PORT (int), the port to use (default: 80) + * - WITH_TIMEOUT (int), number of seconds before giving up (default: 30) + * - WITH_SSL (bool), true to test with SSL (default: false) + */ + +#include <iostream> + +#if !defined(WITH_HOST) +# define WITH_HOST "malikania.fr" +#endif + +#if !defined(WITH_PORT) +# define WITH_PORT 80 +#endif + +#if !defined(WITH_TIMEOUT) +# define WITH_TIMEOUT 30 +#endif + +#include "elapsed-timer.h" +#include "sockets.h" + +int main() +{ +#if defined(WITH_SSL) + net::SocketTls<net::address::Ipv4> socket; +#else + net::SocketTcp<net::address::Ipv4> socket; +#endif + + try { + std::cout << "Trying to connect to " << WITH_HOST << ":" << WITH_PORT << std::endl; + socket.connect(net::address::Ip(WITH_HOST, WITH_PORT)); + } catch (const net::Error &error) { + std::cerr << "error: " << error.what() << std::endl; + std::exit(1); + } + + std::cout << "Successfully connected!" << std::endl; + + return 0; +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/net/examples/elapsed-timer.cpp Mon Apr 04 17:34:01 2016 +0200 @@ -0,0 +1,60 @@ +/* + * elapsed-timer.cpp -- measure elapsed time + * + * 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 "elapsed-timer.h" + +using std::chrono::duration_cast; +using std::chrono::high_resolution_clock; +using std::chrono::milliseconds; + +ElapsedTimer::ElapsedTimer() noexcept +{ + m_last = high_resolution_clock::now(); +} + +void ElapsedTimer::pause() noexcept +{ + /* + * When we put the timer on pause, do not forget to set the already + * elapsed time. + */ + (void)elapsed(); + m_paused = true; +} + +void ElapsedTimer::restart() noexcept +{ + m_paused = false; + m_last = high_resolution_clock::now(); +} + +void ElapsedTimer::reset() noexcept +{ + m_elapsed = 0; + m_last = high_resolution_clock::now(); +} + +unsigned ElapsedTimer::elapsed() noexcept +{ + if (!m_paused) { + m_elapsed += duration_cast<milliseconds>(high_resolution_clock::now() - m_last).count(); + m_last = high_resolution_clock::now(); + } + + return m_elapsed; +} \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/net/examples/elapsed-timer.h Mon Apr 04 17:34:01 2016 +0200 @@ -0,0 +1,77 @@ +/* + * elapsed-timer.h -- measure elapsed time + * + * 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. + */ + +#ifndef _ELAPSED_TIMER_H_ +#define _ELAPSED_TIMER_H_ + +/** + * @file ElapsedTimer.h + * @brief Measure elapsed time + */ + +#include <chrono> + +/** + * @class ElapsedTimer + * @brief Measure elapsed time + * + * This class provides an abstraction to measure elapsed time since the + * construction of the object. + * + * It uses std::chrono::high_resolution_clock for more precision and uses + * milliseconds only. + */ +class ElapsedTimer { +public: + using TimePoint = std::chrono::time_point<std::chrono::high_resolution_clock>; + +private: + TimePoint m_last; + bool m_paused{false}; + unsigned m_elapsed{0}; + +public: + /** + * Construct the elapsed timer, start counting. + */ + ElapsedTimer() noexcept; + + /** + * Put the timer on pause, the already elapsed time is stored. + */ + void pause() noexcept; + + /** + * Restart the timer, does not reset it. + */ + void restart() noexcept; + + /** + * Reset the timer to 0. + */ + void reset() noexcept; + + /** + * Get the number of elapsed milliseconds. + * + * @return the milliseconds + */ + unsigned elapsed() noexcept; +}; + +#endif // !_ELAPSED_TIMER_H_
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/net/examples/non-blocking-accept.cpp Mon Apr 04 17:34:01 2016 +0200 @@ -0,0 +1,89 @@ +/* + * non-blocking-accept.cpp -- example of total non-blocking accept + * + * Options: + * - WITH_PORT (int), the port to use (default: 16000) + * - WITH_TIMEOUT (int), number of milliseconds before giving up (default: 3000) + * - WITH_SSL (bool), true to test with SSL (default: false) + */ + +#include <iostream> + +#include "elapsed-timer.h" +#include "sockets.h" + +#if !defined(WITH_PORT) +# define WITH_PORT 16000 +#endif + +#if !defined(WITH_TIMEOUT) +# define WITH_TIMEOUT 3000 +#endif + +int main() +{ +#if defined(WITH_SSL) + net::SocketTls<net::address::Ip> master; + net::SocketTls<net::address::Ip> client{net::Invalid}; +#else + net::SocketTcp<net::address::Ip> master; + net::SocketTcp<net::address::Ip> client{net::Invalid}; +#endif + + net::Listener<> listener; + net::Condition cond; + + ElapsedTimer timer; + + // 1. Create the master socket for listening. + try { + master.set(net::option::SockReuseAddress{true}); + master.set(net::option::SockBlockMode{false}); + master.bind(net::address::Ip{"*", WITH_PORT}); + master.listen(); + + listener.set(master.handle(), net::Condition::Readable); + } catch (const net::Error &error) { + std::cerr << "error: " << error.what() << std::endl; + std::exit(1); + } + + while (!client.isOpen() && timer.elapsed() < WITH_TIMEOUT) { + try { + if (!client.isOpen()) { + // 2. Wait for a pre-accept process. + listener.wait(std::chrono::seconds(WITH_TIMEOUT)); + master.accept(client, nullptr, &cond); + client.set(net::option::SockBlockMode(false)); + listener.remove(master.handle()); + + std::cout << "Accepting new client" << std::endl; + + if (cond != net::Condition::None) { + std::cout << "Client accept state not complete" << std::endl; + listener.set(client.handle(), cond); + } + } else { + // 3. Wait for the accept process to complete. + std::cout << "Continuing accept for the client" << std::endl; + + listener.wait(std::chrono::seconds(WITH_TIMEOUT - timer.elapsed())); + client.accept(&cond); + listener.remove(client.handle()); + + if (cond != net::Condition::None) { + std::cout << "Client accept state not complete" << std::endl; + listener.set(client.handle(), cond); + } + } + } catch (const net::Error &error) { + std::cerr << error.function() << ": " << error.what() << std::endl; + std::exit(1); + } + } + + if (client.isOpen()) + std::cout << "Client successfully accepted!" << std::endl; + + return 0; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/net/examples/non-blocking-connect.cpp Mon Apr 04 17:34:01 2016 +0200 @@ -0,0 +1,65 @@ +/* + * non-blocking-connect.cpp -- example of non blocking connect + * + * Options: + * - WITH_HOST (string literal), the host to try (default: "malikania.fr") + * - WITH_PORT (int), the port to use (default: 80) + * - WITH_TIMEOUT (int), number of milliseconds before giving up (default: 3000) + * - WITH_SSL (bool), true to test with SSL (default: false) + */ + +#include <iostream> + +#if !defined(WITH_HOST) +# define WITH_HOST "malikania.fr" +#endif + +#if !defined(WITH_PORT) +# define WITH_PORT 80 +#endif + +#if !defined(WITH_TIMEOUT) +# define WITH_TIMEOUT 3000 +#endif + +#include "elapsed-timer.h" +#include "sockets.h" + +int main() +{ +#if defined(WITH_SSL) + net::SocketTls<net::address::Ip> socket; +#else + net::SocketTcp<net::address::Ip> socket; +#endif + + net::Listener<> listener; + net::Condition cond; + ElapsedTimer timer; + + // 1. Set to non-blocking. + socket.set(net::option::SockBlockMode(false)); + + try { + std::cout << "Trying to connect to " << WITH_HOST << ":" << WITH_PORT << std::endl; + + // 2. Initial connection process. + socket.connect(net::address::Ip(WITH_HOST, WITH_PORT), &cond); + + while (cond != net::Condition::None && timer.elapsed() < WITH_TIMEOUT) { + listener.remove(socket.handle()); + + // 2. Now complete by waiting for the appropriate condition. + listener.set(socket.handle(), cond); + listener.wait(std::chrono::milliseconds(WITH_TIMEOUT - timer.elapsed())); + socket.connect(&cond); + } + } catch (const net::Error &error) { + std::cerr << "error: " << error.what() << std::endl; + std::exit(1); + } + + std::cout << "Successfully connected!" << std::endl; + + return 0; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/net/examples/test.crt Mon Apr 04 17:34:01 2016 +0200 @@ -0,0 +1,14 @@ +-----BEGIN CERTIFICATE----- +MIICITCCAYoCCQCGm4grkVCohjANBgkqhkiG9w0BAQsFADBVMQswCQYDVQQGEwJG +UjEPMA0GA1UECAwGRnJhbmNlMSEwHwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0 +eSBMdGQxEjAQBgNVBAMMCWxvY2FsaG9zdDAeFw0xNTEwMjYyMDM0NThaFw0yNTEw +MjMyMDM0NThaMFUxCzAJBgNVBAYTAkZSMQ8wDQYDVQQIDAZGcmFuY2UxITAfBgNV +BAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDESMBAGA1UEAwwJbG9jYWxob3N0 +MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDp13OqVyOWyv5QWD4xr+Duw6SZ +gU7D5huzsAOcneSI6JUhf+7Ecu6BQ2JGkFn4srIVkMWGQuImETJ8JCpSQH7rk+xO +L9fTTK+TwhP2hW/Rf/b2gWedhJAS+gilqt4JNT7v2wFv+aTtRt/lpTXVSdtpLa/m +Pdy219f6MAPgODJ/7QIDAQABMA0GCSqGSIb3DQEBCwUAA4GBAJSnn/IBn1ZblfzP +rJO/lE1Jwpmx3B7+oR/e4fkZd6JR3s06umGYWr2H+TPl/5dj9x0gPokhoIL9zCGq +SxCPnOeaxjBkw7yh3Ks6m3xKxmK4aMpAtBHtwmbfQyIcgz71/lfCzbJ3WcKpn1ig +IZbByt5QSSPcFORRzJJa35eHBdfX +-----END CERTIFICATE-----
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/net/examples/test.key Mon Apr 04 17:34:01 2016 +0200 @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICXAIBAAKBgQDp13OqVyOWyv5QWD4xr+Duw6SZgU7D5huzsAOcneSI6JUhf+7E +cu6BQ2JGkFn4srIVkMWGQuImETJ8JCpSQH7rk+xOL9fTTK+TwhP2hW/Rf/b2gWed +hJAS+gilqt4JNT7v2wFv+aTtRt/lpTXVSdtpLa/mPdy219f6MAPgODJ/7QIDAQAB +AoGBANDt4ndQkgi56A1rOm50gVlzTg6lPPXFE/0xB5kYbcdxX0VmI7Q8KCMwTI9V +jD2rk3e3OPSjr6FpfhzyxylkXMBz2BL5NRNPowCJbiMgZOUIzlcWPKo0tgf1bZJx +YdB5U003ISGPPBjVOAjyizY7tJnaNvbpLQ0hbIAsvHPEAOnBAkEA9r3g8NQjPrvb +oIr5SMIxM8HDJ1/q+MEBSFtRFzQpmur6P64Jsu96zCyencUYTxs0L/sottrj6dPC +vjGCc6PjsQJBAPKdqK1knJv6Y95M2bnEwrymCFVdxCi7AxObStB+bg/+7mMCUqqX +j2g71bfvhYakHV7CiaYrrORChwj6vTbimv0CQGpd2IZ5LOhyW2+N+YDgFg3Vzac/ +ti+eJEto8kAqgHUELvUctZmpmypBYe9pc91GQO0ePKL3IaE/ZIhRF4d6c0ECQH9A +XiaD7PiKvjLs0A31u8ZCt4A+7BII9LYl73mntobBSbu4ji9Xyyn6qEAPa1ORZK49 +DwGPSuF2W2lESlYtSOkCQGrtczhx3IyJjk5e2Y1i/UddPKjviAysCSzcW6aVTNr9 +Y2L0sWmva2FKnkl9FDuEqxvmGr6OOkr5Ll7aWLzJri8= +-----END RSA PRIVATE KEY-----
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/net/net.cpp Mon Apr 04 17:34:01 2016 +0200 @@ -0,0 +1,181 @@ +/* + * net.cpp -- portable C++ socket wrappers + * + * 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 <cstdlib> +#include <atomic> +#include <mutex> + +#include "net.h" + +namespace net { + +/* + * Portable 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 + * ------------------------------------------------------------------ + */ + +#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 NET_WSA_NO_INIT is not set then the user + * must also call finish himself. + */ +#if !defined(NET_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 +} + +#if !defined(NET_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(NET_NO_AUTO_SSL_INIT) + atexit(finish); +#endif + } +} + +} // !ssl + +#endif // !NET_NO_SSL + +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)) +{ +} + +} // !net \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/net/net.h Mon Apr 04 17:34:01 2016 +0200 @@ -0,0 +1,3188 @@ +/* + * net.h -- portable C++ socket wrappers + * + * 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. + */ + +#ifndef NET_H +#define NET_H + +/** + * @brief Network configuration + * + * # Portable defines + * + * This file defines user options and portability stuff. + * + * ## 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 net::init function and net::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 net::ssl::init and net::ssl::finish. + * + * ### 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. + * + * ### 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 | + */ + +/* + * 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 functions + * ------------------------------------------------------------------ + */ + +#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 + +#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. + */ + +#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 + +/* + * 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 + +#include <algorithm> +#include <cassert> +#include <chrono> +#include <cstring> +#include <memory> +#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 + +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. + */ +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 + +/* + * Portable functions + * ------------------------------------------------------------------ + * + * The following free functions can be used to initialize the library or to get the last system error. + */ + +/** + * Initialize the socket library. Except if you defined NET_NO_AUTO_INIT, you don't need to call this + * function manually. + */ +void init() noexcept; + +/** + * Close the socket library. + */ +void finish() noexcept; + +/** + * 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); + +#if !defined(NET_NO_SSL) + +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. + */ +void init() noexcept; + +/** + * Close the OpenSSL library. + */ +void finish() noexcept; + +} // !ssl + +#endif // !NET_NO_SSL + +/* + * Error class + * ------------------------------------------------------------------ + * + * This is the main exception thrown on socket operations. + */ + +/** + * @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. + */ + +/** + * @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; +} + +/** + * @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::unordered_map<Handle, Condition>; + +/* + * Base Socket class + * ------------------------------------------------------------------ + * + * This base class has operations that are common to all types of sockets but you usually instanciate + * a SocketTcp or SocketUdp + */ + +/** + * @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. + * + * When using non-blocking sockets, it is important to pass the condition to functions which may block, they indicate + * the condition to wait to perform or continue the operation if they would block. + * + * For example, when trying to connect with non-blocking, user should do the following: + * + * 1. Call Socket::connect() with the condition, + * 2. Loop until condition is not set to Condition::None (or an exception is thrown), + * 3. Wait with a listener for the condition to be ready (see Listener::poll), + * 4. Call Socket::resumeConnect() with the condition again. + * + * @see protocol::Tls + * @see protocol::Tcp + * @see protocol::Udp + */ +template <typename Address, typename Protocol> +class Socket { +private: + Protocol m_proto; + +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(NET_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"); + } + + /** + * 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); + } + + /** + * Initialize connection to the given address. + * + * @pre isOpen() + * @param address the address + * @param length the address length + * @param cond the condition + * @throw net::Error on failures + */ + void connect(const sockaddr *address, socklen_t length, Condition &cond) + { + assert(m_handle != Invalid); + + cond = Condition::None; + + m_proto.connect(*this, address, length, cond); + } + + /** + * Overloaded function. + * + * @pre isOpen() + * @param address the address + * @param length the address length + * @throw net::Error on failures + */ + inline void connect(const sockaddr *address, socklen_t length) + { + Condition dummy; + + connect(address, length, dummy); + } + + /** + * Overloaded function. + * + * @pre isOpen() + * @param address the address + * @param cond the condition + * @throw net::Error on failures + */ + inline void connect(const Address &address, Condition &cond) + { + connect(address.address(), address.length(), cond); + } + + /** + * Overloaded function. + * + * @pre isOpen() + * @param address the address + * @throw net::Error on failures + */ + inline void connect(const Address &address) + { + Condition dummy; + + connect(address.address(), address.length(), dummy); + } + + void resumeConnect(Condition &cond) + { + assert(m_handle != Invalid); + + cond = Condition::None; + + m_proto.connect(*this, cond); + } + + inline void resumeConnect() + { + Condition dummy; + + connect(dummy); + } + + Socket<Address, Protocol> accept(Address &address, Condition &cond) + { + assert(m_handle != Invalid); + + sockaddr_storage storage; + socklen_t length = sizeof (storage); + + cond = Condition::None; + + Socket<Address, Protocol> client = m_proto.accept(*this, reinterpret_cast<sockaddr *>(&storage), &length, cond); + + address = Address(&storage, length); + + return client; + } + + inline Socket<Address, Protocol> accept(Address &address) + { + Condition dummy; + + return accept(address, dummy); + } + + inline Socket<Address, Protocol> accept() + { + Address da; + Condition dc; + + return accept(da, dc); + } + + inline void resumeAccept(Condition &condition) + { + assert(m_handle != Invalid); + + m_proto.resumeAccept(*this, condition); + } + + inline void resumeAccept() + { + Condition dummy; + + resumeAccept(*this, dummy); + } + + unsigned recv(void *data, unsigned length, Condition &cond) + { + assert(m_handle != Invalid); + + cond = Condition::None; + + return m_proto.recv(*this, data, length, cond); + } + + inline unsigned recv(void *data, unsigned length) + { + Condition dummy; + + return recv(data, length, dummy); + } + + std::string recv(unsigned count, Condition &cond) + { + 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; + } + + inline std::string recv(unsigned count) + { + Condition dummy; + + return recv(count, dummy); + } + + unsigned send(const void *data, unsigned length, Condition &cond) + { + assert(m_handle != Invalid); + + cond = Condition::None; + + return m_proto.send(*this, data, length, cond); + } + + inline unsigned send(const void *data, unsigned length) + { + Condition dummy; + + return send(data, length, dummy); + } + + inline unsigned send(const std::string &data, Condition &cond) + { + return send(data.c_str(), data.length(), cond); + } + + inline unsigned send(const std::string &data) + { + Condition dummy; + + return send(data.c_str(), data.length(), dummy); + } + + unsigned sendto(const void *data, unsigned length, const sockaddr *address, socklen_t addrlen, Condition &cond) + { + assert(m_handle != Invalid); + + cond = Condition::None; + + return m_proto.sendto(*this, data, length, address, addrlen, cond); + } + + inline unsigned sendto(const void *data, unsigned length, const sockaddr *address, socklen_t addrlen) + { + Condition dummy; + + return send(data, length, address, addrlen, dummy); + } + + inline unsigned sendto(const void *data, unsigned length, const Address &address, Condition &cond) + { + return sendto(data, length, address.address(), address.length(), cond); + } + + inline unsigned sendto(const void *data, unsigned length, const Address &address) + { + Condition dummy; + + return sendto(data, length, address.address(), address.length(), dummy); + } + + inline unsigned sendto(const std::string &data, const Address &address, Condition &cond) + { + return sendto(data.c_str(), data.length(), address.address(), address.length(), cond); + } + + inline unsigned sendto(const std::string &data, const Address &address) + { + Condition dummy; + + return sendto(data.c_str(), data.length(), address.address(), address.length(), dummy); + } + + unsigned recvfrom(void *data, unsigned length, sockaddr *address, socklen_t *addrlen, Condition &cond) + { + assert(m_handle != Invalid); + + cond = Condition::None; + + return m_proto.recvfrom(*this, data, length, address, addrlen, cond); + } + + inline unsigned recvfrom(void *data, unsigned length, sockaddr *address, socklen_t *addrlen) + { + Condition dummy; + + return recvfrom(data, length, address, addrlen, dummy); + } + + unsigned recvfrom(void *data, unsigned length, Address &address, Condition &cond) + { + sockaddr_storage storage; + socklen_t addrlen = sizeof (sockaddr_storage); + + auto n = recvfrom(data, length, reinterpret_cast<sockaddr *>(&storage), &addrlen, cond); + + if (n != 0) + address = Address(&storage, addrlen); + + return n; + } + + inline unsigned recvfrom(void *data, unsigned length, Address &address) + { + Condition dummy; + + return recvfrom(data, length, address, dummy); + } + + inline unsigned recvfrom(void *data, unsigned length) + { + Address da; + Condition dc; + + return recvfrom(data, length, da, dc); + } + + std::string recvfrom(unsigned count, Address &info, Condition &cond) + { + std::string result; + + result.resize(count); + auto n = recvfrom(const_cast<char *>(result.data()), count, info, cond); + result.resize(n); + + return result; + } + + inline std::string recvfrom(unsigned count, Address &info) + { + Condition dummy; + + return recvfrom(count, info, dummy); + } + + inline std::string recvfrom(unsigned count) + { + Address da; + Condition dc; + + return recvfrom(count, da, dc); + } + + /** + * 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 protocols + * ------------------------------------------------------------------ + */ + +namespace protocol { + +/** + * @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 */ + } + + /** + * Initiate connection. + * + * @param sc the socket + * @param address the address + * @param cond the condition + */ + 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 + } + } + + /** + * Resume the connection. + * + * Just check for SOL_SOCKET/SO_ERROR. + * + * @param sc the socket + */ + template <typename Address, typename Protocol> + void resumeConnect(Socket<Address, Protocol> &sc, Condition &) + { + int error = sc.template get<int>(SOL_SOCKET, SO_ERROR); + + if (error != 0) + throw Error(Error::System, "connect", error); + } + + /** + * Accept a new client. + * + * If there are no pending connection, an invalid socket is returned, condition is left to Condition::None. + * + * @param sc the socket + * @param address the address + * @param length the length + * @return the new socket + */ + template <typename Address, typename Protocol> + Socket<Address, Protocol> accept(Socket<Address, Protocol> &sc, sockaddr *address, socklen_t *length, Condition &) + { + Handle handle = ::accept(sc.handle(), address, length); + + if (handle == Invalid) + return Socket<Address, Protocol>(); + + return Socket<Address, Protocol>(handle); + } + + /** + * Resume accept process. + * + * No-op for TCP. + */ + template <typename Address, typename Protocol> + inline void accept(Socket<Address, Protocol> &, Condition &) const noexcept + { + /* no op */ + } + + /** + * Receive some data. + * + * @param sc the socket + * @param data the destination buffer + * @param length the buffer length + * @param cond the condition + */ + 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); + } + + /** + * Send some data. + * + * @param sc the socket + * @param data the data to send + * @param length the length + * @param cond the condition + */ + 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); + } +}; + +/** + * @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, typename Protocol> + inline void create(Socket<Address, Protocol> &) noexcept + { + /* No-op */ + } + + /** + * Receive some data. + * + * @param sc the socket + * @param data the data + * @param length the length + * @param address the source address + * @param addrlen the source address in/out length + * @param cond the condition + * @return the number of bytes received + */ + template <typename Address, typename Protocol> + unsigned recvfrom(Socket<Address, Protocol> &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); + } + + /** + * Send some data. + * + * @param sc the socket + * @param data the data to send + * @param length the data length + * @param address the destination address + * @param addrlen the destination address length + * @param cond the condition + */ + template <typename Address, typename Protocol> + unsigned sendto(Socket<Address, Protocol> &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); + } +}; + +#if !defined(NET_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)); + } + } + } + + template <typename Address, typename Protocol> + void doConnect(Socket<Address, Protocol> &sc, Condition &cond) + { + wrap("connect", cond, [&] () -> int { + return SSL_connect(m_ssl.get()); + }); + } + + template <typename Address, typename Protocol> + void doAccept(Socket<Address, Protocol> &sc, 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(NET_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(sc, 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(sc, 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(client, cond); + } + } + + template <typename Address, typename Protocol> + inline void accept(Socket<Address, Protocol> &sc, Condition &cond) + { + doAccept(sc, cond); + } + + + template <typename Address> + unsigned recv(Socket<Address, Tls> &sc, 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> &sc, 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 // !NET_NO_SSL + +} // !protocol + +/* + * Predefined addresses + * ------------------------------------------------------------------ + */ + +namespace address { + +class Ip { +private: + union { + sockaddr_in6 m_sin6; + sockaddr_in m_sin; + }; + + int m_domain; + +public: + inline Ip(int domain = AF_INET) noexcept + : m_domain(domain) + { + assert(domain == AF_INET || domain == AF_INET6); + + std::memset(&m_sin, 0, sizeof (sockaddr_in)); + } + + inline Ip(const std::string &host, std::uint16_t port, int domain) + : Ip(domain) + { + if (m_domain == AF_INET) + make(host, port, m_sin); + else + make(host, port, m_sin6); + } + + inline Ip(const sockaddr_storage *ss, socklen_t length) noexcept + : Ip(ss->ss_family) + { + assert(ss->ss_family == AF_INET || ss->ss_family == AF_INET6); + + if (ss->ss_family == AF_INET) + std::memcpy(&m_sin, ss, length); + else + std::memcpy(&m_sin6, ss, length); + } + + inline int domain() const noexcept + { + return AF_INET; + } + + inline const sockaddr *address() const noexcept + { + return m_domain == AF_INET ? reinterpret_cast<const sockaddr *>(&m_sin) : reinterpret_cast<const sockaddr *>(&m_sin6); + } + + inline socklen_t length() const noexcept + { + return m_domain == AF_INET ? sizeof (sockaddr_in) : sizeof (sockaddr_in6); + } + + inline std::uint16_t port() const noexcept + { + return m_domain == AF_INET ? ntohs(m_sin.sin_port) : ntohs(m_sin6.sin6_port); + } + + inline std::string ip() const + { + return m_domain == AF_INET ? ip(m_sin) : ip(m_sin6); + } + + static void make(const std::string &host, std::uint16_t port, sockaddr_in &sin) + { +#if !defined(NET_NO_AUTO_INIT) + net::init(); +#endif + + sin.sin_family = AF_INET; + sin.sin_port = htons(port); + + if (host == "*") + sin.sin_addr.s_addr = INADDR_ANY; +#if defined(NET_HAVE_INET_PTON) + else if (inet_pton(AF_INET, host.c_str(), &sin.sin_addr) <= 0) + throw Error(Error::System, "inet_pton"); +#else + else + throw Error(Error::System, "inet_pton", std::strerror(ENOSYS)); +#endif + } + + static void make(const std::string &address, std::uint16_t port, sockaddr_in6 &sin6) + { +#if !defined(NET_NO_AUTO_INIT) + net::init(); +#endif + + sin6.sin6_family = AF_INET6; + sin6.sin6_port = htons(port); + + if (address == "*") + sin6.sin6_addr = in6addr_any; +#if defined(NET_HAVE_INET_PTON) + else if (inet_pton(AF_INET6, address.c_str(), &sin6.sin6_addr) <= 0) + throw Error(Error::System, "inet_pton"); +#else + else + throw Error(Error::System, "inet_pton", std::strerror(ENOSYS)); +#endif + } + + static std::string ip(const sockaddr_in &sin) + { +#if !defined(NET_NO_AUTO_INIT) + net::init(); +#endif + +#if !defined(NET_HAVE_INET_NTOP) + throw Error(Error::System, "inet_ntop", std::strerror(ENOSYS)); +#else + char result[INET_ADDRSTRLEN + 1]; + + std::memset(result, 0, sizeof (result)); + + if (!inet_ntop(AF_INET, const_cast<in_addr *>(&sin.sin_addr), result, sizeof (result))) + throw Error(Error::System, "inet_ntop"); + + return result; +#endif + } + + static std::string ip(const sockaddr_in6 &sin6) + { +#if !defined(NET_NO_AUTO_INIT) + net::init(); +#endif + +#if !defined(NET_HAVE_INET_NTOP) + throw Error(Error::System, "inet_ntop", std::strerror(ENOSYS)); +#else + char result[INET6_ADDRSTRLEN]; + + std::memset(result, 0, sizeof (result)); + + if (!inet_ntop(AF_INET6, const_cast<in6_addr *>(&sin6.sin6_addr), result, sizeof (result))) + throw Error(Error::System, "inet_ntop"); + + return result; +#endif + } +}; + +class Ipv4 { +private: + sockaddr_in m_sin; + +public: + inline Ipv4() noexcept + { + std::memset(&m_sin, 0, sizeof (sockaddr_in)); + } + + inline Ipv4(const std::string &host, std::uint16_t port) + : Ipv4() + { + Ip::make(host, port, m_sin); + } + + inline Ipv4(const sockaddr_storage *ss, socklen_t length) noexcept + { + std::memcpy(&m_sin, ss, length); + } + + inline int domain() const noexcept + { + return AF_INET; + } + + inline const sockaddr *address() const noexcept + { + return reinterpret_cast<const sockaddr *>(&m_sin); + } + + inline socklen_t length() const noexcept + { + return sizeof (sockaddr_in); + } + + inline std::uint16_t port() const noexcept + { + return ntohs(m_sin.sin_port); + } + + inline std::string ip() const + { + return Ip::ip(m_sin); + } +}; + +class Ipv6 { +private: + sockaddr_in6 m_sin6; + +public: + inline Ipv6() noexcept + { + std::memset(&m_sin6, 0, sizeof (sockaddr_in6)); + } + + inline Ipv6(const std::string &address, std::uint16_t port) + : Ipv6() + { + Ip::make(address, port, m_sin6); + } + + inline Ipv6(const sockaddr_storage *ss, socklen_t length) noexcept + { + std::memcpy(&m_sin6, ss, length); + } + + inline int domain() const noexcept + { + return AF_INET6; + } + + inline const sockaddr *address() const noexcept + { + return reinterpret_cast<const sockaddr *>(&m_sin6); + } + + inline socklen_t length() const noexcept + { + return sizeof (sockaddr_in6); + } + + inline std::uint16_t port() const noexcept + { + return ntohs(m_sin6.sin6_port); + } + + inline std::string ip() const + { + return Ip::ip(m_sin6); + } +}; + +#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. + */ + inline Local() noexcept + { + std::memset(&m_sun, 0, sizeof (sockaddr_un)); + } + + /** + * 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 + : 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; + } + + /** + * 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 + { + assert(ss->ss_family == AF_LOCAL); + + std::memcpy(&m_sun, ss, length); + m_path = reinterpret_cast<const sockaddr_un &>(m_sun).sun_path; + } + + /** + * 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(NET_HAVE_SUN_LEN) + return SUN_LEN(&m_sun); +#else + return sizeof (m_sun); +#endif + } +}; + +#endif // !_WIN32 + +} // !address + +/* + * Listener backends + * ------------------------------------------------------------------ + */ + +namespace backend { + +/** + * @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: + inline void set(const ListenerTable &, Handle, Condition, bool) noexcept + { + } + + inline void unset(const ListenerTable &, Handle, Condition, bool) noexcept + { + } + + 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(max + 1, &readset, &writeset, nullptr, towait); + if (error == Failure) { + throw Error{Error::System, "select"}; + } + if (error == 0) { + throw Error{Error::Timeout, "select", std::strerror(ETIMEDOUT)}; + } + + 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; + } + + inline std::string name() const noexcept + { + return "select"; + } +}; + +#if defined(NET_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) + { + 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"); + } + +public: + inline Kqueue() + : m_handle(kqueue()) + { + if (m_handle < 0) + throw Error(Error::System, "kqueue"); + } + + inline Kqueue(Kqueue &&other) noexcept + : m_handle(other.m_handle) + { + other.m_handle = -1; + } + + inline ~Kqueue() + { + if (m_handle != -1) + close(m_handle); + } + + inline std::string name() const noexcept + { + return "kqueue"; + } + + void set(const ListenerTable &, Handle, 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 unset(const ListenerTable &, Handle, Condition, bool) + { + 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> wait(const ListenerTable &, int) + { + 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", std::strerror(ETIMEDOUT)); + 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; + } + + 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) + +#if defined(_WIN32) +# define poll WSAPoll +#endif + +/** + * @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 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: + inline std::string name() const noexcept + { + return "poll"; + } + + 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); + } + } + + 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)); + } + + std::vector<ListenerStatus> wait(const ListenerTable &, int ms) + { + auto result = poll(m_fds.data(), m_fds.size(), ms); + + if (result == 0) + throw Error{Error::Timeout, "select", std::strerror(ETIMEDOUT)}; + 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 // !NET_HAVE_POLL + +#if defined(NET_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; + + std::uint32_t toEpoll(Condition flags) 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 sc, 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"); + } + +public: + /** + * Construct the epoll instance. + */ + inline Epoll() + : m_handle(epoll_create1(0)) + { + if (m_handle < 0) + throw Error{Error::System, "epoll_create"}; + } + + inline Epoll(Epoll &&other) noexcept + : m_handle(other.m_handle) + { + other.m_handle = -1; + } + + inline ~Epoll() + { + if (m_handle != -1) + close(m_handle); + } + + 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 add Connection::Writable, we must + * place both. + */ + void set(const ListenerTable &, Handle, 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 unset(const ListenerTable &, Handle, 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> 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", std::strerror(ETIMEDOUT)); + 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; + } + + inline Epoll &operator=(Epoll &&other) + { + m_handle = other.m_handle; + other.m_handle = -1; + + return *this; + } +}; + +#endif // !NET_HAVE_EPOLL + +} // !backend + +/* + * Predefined options + * ------------------------------------------------------------------ + */ + +namespace option { + +/** + * @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 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); + } +}; + +/** + * @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 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)); + } +}; + +/** + * @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 + +/** + * @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 = 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; + } + + /** + * 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 // !NET_H
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/net/test/main.cpp Mon Apr 04 17:34:01 2016 +0200 @@ -0,0 +1,701 @@ +/* + * main.cpp -- test sockets + * + * Copyright (c) 2013-2015 David Demelier <markand@malikania.fr> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <chrono> +#include <iostream> +#include <sstream> +#include <string> +#include <thread> + +#include <gtest/gtest.h> + +#include <net.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)); + } catch (const net::Error &ex) { + FAIL() << ex.function() << ": " << ex.what(); + } + }); + + std::this_thread::sleep_for(250ms); + + m_tclient = std::thread([this] () { + try { + 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(); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/net/test/test.crt Mon Apr 04 17:34:01 2016 +0200 @@ -0,0 +1,14 @@ +-----BEGIN CERTIFICATE----- +MIICITCCAYoCCQCGm4grkVCohjANBgkqhkiG9w0BAQsFADBVMQswCQYDVQQGEwJG +UjEPMA0GA1UECAwGRnJhbmNlMSEwHwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0 +eSBMdGQxEjAQBgNVBAMMCWxvY2FsaG9zdDAeFw0xNTEwMjYyMDM0NThaFw0yNTEw +MjMyMDM0NThaMFUxCzAJBgNVBAYTAkZSMQ8wDQYDVQQIDAZGcmFuY2UxITAfBgNV +BAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDESMBAGA1UEAwwJbG9jYWxob3N0 +MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDp13OqVyOWyv5QWD4xr+Duw6SZ +gU7D5huzsAOcneSI6JUhf+7Ecu6BQ2JGkFn4srIVkMWGQuImETJ8JCpSQH7rk+xO +L9fTTK+TwhP2hW/Rf/b2gWedhJAS+gilqt4JNT7v2wFv+aTtRt/lpTXVSdtpLa/m +Pdy219f6MAPgODJ/7QIDAQABMA0GCSqGSIb3DQEBCwUAA4GBAJSnn/IBn1ZblfzP +rJO/lE1Jwpmx3B7+oR/e4fkZd6JR3s06umGYWr2H+TPl/5dj9x0gPokhoIL9zCGq +SxCPnOeaxjBkw7yh3Ks6m3xKxmK4aMpAtBHtwmbfQyIcgz71/lfCzbJ3WcKpn1ig +IZbByt5QSSPcFORRzJJa35eHBdfX +-----END CERTIFICATE-----
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/net/test/test.key Mon Apr 04 17:34:01 2016 +0200 @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICXAIBAAKBgQDp13OqVyOWyv5QWD4xr+Duw6SZgU7D5huzsAOcneSI6JUhf+7E +cu6BQ2JGkFn4srIVkMWGQuImETJ8JCpSQH7rk+xOL9fTTK+TwhP2hW/Rf/b2gWed +hJAS+gilqt4JNT7v2wFv+aTtRt/lpTXVSdtpLa/mPdy219f6MAPgODJ/7QIDAQAB +AoGBANDt4ndQkgi56A1rOm50gVlzTg6lPPXFE/0xB5kYbcdxX0VmI7Q8KCMwTI9V +jD2rk3e3OPSjr6FpfhzyxylkXMBz2BL5NRNPowCJbiMgZOUIzlcWPKo0tgf1bZJx +YdB5U003ISGPPBjVOAjyizY7tJnaNvbpLQ0hbIAsvHPEAOnBAkEA9r3g8NQjPrvb +oIr5SMIxM8HDJ1/q+MEBSFtRFzQpmur6P64Jsu96zCyencUYTxs0L/sottrj6dPC +vjGCc6PjsQJBAPKdqK1knJv6Y95M2bnEwrymCFVdxCi7AxObStB+bg/+7mMCUqqX +j2g71bfvhYakHV7CiaYrrORChwj6vTbimv0CQGpd2IZ5LOhyW2+N+YDgFg3Vzac/ +ti+eJEto8kAqgHUELvUctZmpmypBYe9pc91GQO0ePKL3IaE/ZIhRF4d6c0ECQH9A +XiaD7PiKvjLs0A31u8ZCt4A+7BII9LYl73mntobBSbu4ji9Xyyn6qEAPa1ORZK49 +DwGPSuF2W2lESlYtSOkCQGrtczhx3IyJjk5e2Y1i/UddPKjviAysCSzcW6aVTNr9 +Y2L0sWmva2FKnkl9FDuEqxvmGr6OOkr5Ll7aWLzJri8= +-----END RSA PRIVATE KEY-----
--- a/modules/sockets/CMakeLists.txt Thu Feb 25 21:01:02 2016 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,39 +0,0 @@ -# -# CMakeLists.txt -- sockets module -# -# 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. -# - -project(sockets) - -if (OPENSSL_FOUND) - if (WIN32) - set(LIBRARIES ws2_32) - endif () - - code_define_module( - NAME sockets - INCLUDES ${OPENSSL_INCLUDE_DIR} - LIBRARIES - ${LIBRARIES} - ${OPENSSL_LIBRARIES} - SOURCES - ${sockets_SOURCE_DIR}/sockets.cpp - ${sockets_SOURCE_DIR}/sockets.h - RESOURCES - ${sockets_SOURCE_DIR}/test/test.crt - ${sockets_SOURCE_DIR}/test/test.key - ) -endif ()
--- a/modules/sockets/examples/blocking-accept.cpp Thu Feb 25 21:01:02 2016 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,52 +0,0 @@ -/* - * blocking-accept.cpp -- example of blocking accept - * - * Options: - * - WITH_PORT (int), the port to use (default: 16000) - * - WITH_TIMEOUT (int), number of seconds before giving up (default: 60) - * - WITH_SSL (bool), true to test with SSL (default: false) - */ - -#include <iostream> - -#include "sockets.h" - -#if !defined(WITH_PORT) -# define WITH_PORT 16000 -#endif - -#if !defined(WITH_TIMEOUT) -# define WITH_TIMEOUT 60 -#endif - -int main() -{ -#if defined(WITH_SSL) - net::SocketTls<net::address::Ip> master; - net::SocketTls<net::address::Ip> client{net::Invalid}; -#else - net::SocketTcp<net::address::Ip> master; - net::SocketTcp<net::address::Ip> client{net::Invalid}; -#endif - - net::Listener<> listener; - - try { - master.set(net::option::SockReuseAddress{true}); - master.bind(net::address::Ip{"*", WITH_PORT}); - master.listen(); - - listener.set(master.handle(), net::Condition::Readable); - listener.wait(std::chrono::seconds(WITH_TIMEOUT)); - - master.accept(client); - } catch (const net::Error &error) { - std::cerr << "error: " << error.what() << std::endl; - std::exit(1); - } - - std::cout << "Client successfully accepted!" << std::endl; - - return 0; -} -
--- a/modules/sockets/examples/blocking-connect.cpp Thu Feb 25 21:01:02 2016 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,48 +0,0 @@ -/* - * blocking-connect.cpp -- example of blocking connect - * - * Options: - * - WITH_HOST (string literal), the host to try (default: "malikania.fr") - * - WITH_PORT (int), the port to use (default: 80) - * - WITH_TIMEOUT (int), number of seconds before giving up (default: 30) - * - WITH_SSL (bool), true to test with SSL (default: false) - */ - -#include <iostream> - -#if !defined(WITH_HOST) -# define WITH_HOST "malikania.fr" -#endif - -#if !defined(WITH_PORT) -# define WITH_PORT 80 -#endif - -#if !defined(WITH_TIMEOUT) -# define WITH_TIMEOUT 30 -#endif - -#include "elapsed-timer.h" -#include "sockets.h" - -int main() -{ -#if defined(WITH_SSL) - net::SocketTls<net::address::Ipv4> socket; -#else - net::SocketTcp<net::address::Ipv4> socket; -#endif - - try { - std::cout << "Trying to connect to " << WITH_HOST << ":" << WITH_PORT << std::endl; - socket.connect(net::address::Ip(WITH_HOST, WITH_PORT)); - } catch (const net::Error &error) { - std::cerr << "error: " << error.what() << std::endl; - std::exit(1); - } - - std::cout << "Successfully connected!" << std::endl; - - return 0; -} -
--- a/modules/sockets/examples/elapsed-timer.cpp Thu Feb 25 21:01:02 2016 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,60 +0,0 @@ -/* - * elapsed-timer.cpp -- measure elapsed time - * - * 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 "elapsed-timer.h" - -using std::chrono::duration_cast; -using std::chrono::high_resolution_clock; -using std::chrono::milliseconds; - -ElapsedTimer::ElapsedTimer() noexcept -{ - m_last = high_resolution_clock::now(); -} - -void ElapsedTimer::pause() noexcept -{ - /* - * When we put the timer on pause, do not forget to set the already - * elapsed time. - */ - (void)elapsed(); - m_paused = true; -} - -void ElapsedTimer::restart() noexcept -{ - m_paused = false; - m_last = high_resolution_clock::now(); -} - -void ElapsedTimer::reset() noexcept -{ - m_elapsed = 0; - m_last = high_resolution_clock::now(); -} - -unsigned ElapsedTimer::elapsed() noexcept -{ - if (!m_paused) { - m_elapsed += duration_cast<milliseconds>(high_resolution_clock::now() - m_last).count(); - m_last = high_resolution_clock::now(); - } - - return m_elapsed; -} \ No newline at end of file
--- a/modules/sockets/examples/elapsed-timer.h Thu Feb 25 21:01:02 2016 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,77 +0,0 @@ -/* - * elapsed-timer.h -- measure elapsed time - * - * 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. - */ - -#ifndef _ELAPSED_TIMER_H_ -#define _ELAPSED_TIMER_H_ - -/** - * @file ElapsedTimer.h - * @brief Measure elapsed time - */ - -#include <chrono> - -/** - * @class ElapsedTimer - * @brief Measure elapsed time - * - * This class provides an abstraction to measure elapsed time since the - * construction of the object. - * - * It uses std::chrono::high_resolution_clock for more precision and uses - * milliseconds only. - */ -class ElapsedTimer { -public: - using TimePoint = std::chrono::time_point<std::chrono::high_resolution_clock>; - -private: - TimePoint m_last; - bool m_paused{false}; - unsigned m_elapsed{0}; - -public: - /** - * Construct the elapsed timer, start counting. - */ - ElapsedTimer() noexcept; - - /** - * Put the timer on pause, the already elapsed time is stored. - */ - void pause() noexcept; - - /** - * Restart the timer, does not reset it. - */ - void restart() noexcept; - - /** - * Reset the timer to 0. - */ - void reset() noexcept; - - /** - * Get the number of elapsed milliseconds. - * - * @return the milliseconds - */ - unsigned elapsed() noexcept; -}; - -#endif // !_ELAPSED_TIMER_H_
--- a/modules/sockets/examples/non-blocking-accept.cpp Thu Feb 25 21:01:02 2016 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,89 +0,0 @@ -/* - * non-blocking-accept.cpp -- example of total non-blocking accept - * - * Options: - * - WITH_PORT (int), the port to use (default: 16000) - * - WITH_TIMEOUT (int), number of milliseconds before giving up (default: 3000) - * - WITH_SSL (bool), true to test with SSL (default: false) - */ - -#include <iostream> - -#include "elapsed-timer.h" -#include "sockets.h" - -#if !defined(WITH_PORT) -# define WITH_PORT 16000 -#endif - -#if !defined(WITH_TIMEOUT) -# define WITH_TIMEOUT 3000 -#endif - -int main() -{ -#if defined(WITH_SSL) - net::SocketTls<net::address::Ip> master; - net::SocketTls<net::address::Ip> client{net::Invalid}; -#else - net::SocketTcp<net::address::Ip> master; - net::SocketTcp<net::address::Ip> client{net::Invalid}; -#endif - - net::Listener<> listener; - net::Condition cond; - - ElapsedTimer timer; - - // 1. Create the master socket for listening. - try { - master.set(net::option::SockReuseAddress{true}); - master.set(net::option::SockBlockMode{false}); - master.bind(net::address::Ip{"*", WITH_PORT}); - master.listen(); - - listener.set(master.handle(), net::Condition::Readable); - } catch (const net::Error &error) { - std::cerr << "error: " << error.what() << std::endl; - std::exit(1); - } - - while (!client.isOpen() && timer.elapsed() < WITH_TIMEOUT) { - try { - if (!client.isOpen()) { - // 2. Wait for a pre-accept process. - listener.wait(std::chrono::seconds(WITH_TIMEOUT)); - master.accept(client, nullptr, &cond); - client.set(net::option::SockBlockMode(false)); - listener.remove(master.handle()); - - std::cout << "Accepting new client" << std::endl; - - if (cond != net::Condition::None) { - std::cout << "Client accept state not complete" << std::endl; - listener.set(client.handle(), cond); - } - } else { - // 3. Wait for the accept process to complete. - std::cout << "Continuing accept for the client" << std::endl; - - listener.wait(std::chrono::seconds(WITH_TIMEOUT - timer.elapsed())); - client.accept(&cond); - listener.remove(client.handle()); - - if (cond != net::Condition::None) { - std::cout << "Client accept state not complete" << std::endl; - listener.set(client.handle(), cond); - } - } - } catch (const net::Error &error) { - std::cerr << error.function() << ": " << error.what() << std::endl; - std::exit(1); - } - } - - if (client.isOpen()) - std::cout << "Client successfully accepted!" << std::endl; - - return 0; -}
--- a/modules/sockets/examples/non-blocking-connect.cpp Thu Feb 25 21:01:02 2016 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,65 +0,0 @@ -/* - * non-blocking-connect.cpp -- example of non blocking connect - * - * Options: - * - WITH_HOST (string literal), the host to try (default: "malikania.fr") - * - WITH_PORT (int), the port to use (default: 80) - * - WITH_TIMEOUT (int), number of milliseconds before giving up (default: 3000) - * - WITH_SSL (bool), true to test with SSL (default: false) - */ - -#include <iostream> - -#if !defined(WITH_HOST) -# define WITH_HOST "malikania.fr" -#endif - -#if !defined(WITH_PORT) -# define WITH_PORT 80 -#endif - -#if !defined(WITH_TIMEOUT) -# define WITH_TIMEOUT 3000 -#endif - -#include "elapsed-timer.h" -#include "sockets.h" - -int main() -{ -#if defined(WITH_SSL) - net::SocketTls<net::address::Ip> socket; -#else - net::SocketTcp<net::address::Ip> socket; -#endif - - net::Listener<> listener; - net::Condition cond; - ElapsedTimer timer; - - // 1. Set to non-blocking. - socket.set(net::option::SockBlockMode(false)); - - try { - std::cout << "Trying to connect to " << WITH_HOST << ":" << WITH_PORT << std::endl; - - // 2. Initial connection process. - socket.connect(net::address::Ip(WITH_HOST, WITH_PORT), &cond); - - while (cond != net::Condition::None && timer.elapsed() < WITH_TIMEOUT) { - listener.remove(socket.handle()); - - // 2. Now complete by waiting for the appropriate condition. - listener.set(socket.handle(), cond); - listener.wait(std::chrono::milliseconds(WITH_TIMEOUT - timer.elapsed())); - socket.connect(&cond); - } - } catch (const net::Error &error) { - std::cerr << "error: " << error.what() << std::endl; - std::exit(1); - } - - std::cout << "Successfully connected!" << std::endl; - - return 0; -}
--- a/modules/sockets/examples/test.crt Thu Feb 25 21:01:02 2016 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,14 +0,0 @@ ------BEGIN CERTIFICATE----- -MIICITCCAYoCCQCGm4grkVCohjANBgkqhkiG9w0BAQsFADBVMQswCQYDVQQGEwJG -UjEPMA0GA1UECAwGRnJhbmNlMSEwHwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0 -eSBMdGQxEjAQBgNVBAMMCWxvY2FsaG9zdDAeFw0xNTEwMjYyMDM0NThaFw0yNTEw -MjMyMDM0NThaMFUxCzAJBgNVBAYTAkZSMQ8wDQYDVQQIDAZGcmFuY2UxITAfBgNV -BAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDESMBAGA1UEAwwJbG9jYWxob3N0 -MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDp13OqVyOWyv5QWD4xr+Duw6SZ -gU7D5huzsAOcneSI6JUhf+7Ecu6BQ2JGkFn4srIVkMWGQuImETJ8JCpSQH7rk+xO -L9fTTK+TwhP2hW/Rf/b2gWedhJAS+gilqt4JNT7v2wFv+aTtRt/lpTXVSdtpLa/m -Pdy219f6MAPgODJ/7QIDAQABMA0GCSqGSIb3DQEBCwUAA4GBAJSnn/IBn1ZblfzP -rJO/lE1Jwpmx3B7+oR/e4fkZd6JR3s06umGYWr2H+TPl/5dj9x0gPokhoIL9zCGq -SxCPnOeaxjBkw7yh3Ks6m3xKxmK4aMpAtBHtwmbfQyIcgz71/lfCzbJ3WcKpn1ig -IZbByt5QSSPcFORRzJJa35eHBdfX ------END CERTIFICATE-----
--- a/modules/sockets/examples/test.key Thu Feb 25 21:01:02 2016 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,15 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIICXAIBAAKBgQDp13OqVyOWyv5QWD4xr+Duw6SZgU7D5huzsAOcneSI6JUhf+7E -cu6BQ2JGkFn4srIVkMWGQuImETJ8JCpSQH7rk+xOL9fTTK+TwhP2hW/Rf/b2gWed -hJAS+gilqt4JNT7v2wFv+aTtRt/lpTXVSdtpLa/mPdy219f6MAPgODJ/7QIDAQAB -AoGBANDt4ndQkgi56A1rOm50gVlzTg6lPPXFE/0xB5kYbcdxX0VmI7Q8KCMwTI9V -jD2rk3e3OPSjr6FpfhzyxylkXMBz2BL5NRNPowCJbiMgZOUIzlcWPKo0tgf1bZJx -YdB5U003ISGPPBjVOAjyizY7tJnaNvbpLQ0hbIAsvHPEAOnBAkEA9r3g8NQjPrvb -oIr5SMIxM8HDJ1/q+MEBSFtRFzQpmur6P64Jsu96zCyencUYTxs0L/sottrj6dPC -vjGCc6PjsQJBAPKdqK1knJv6Y95M2bnEwrymCFVdxCi7AxObStB+bg/+7mMCUqqX -j2g71bfvhYakHV7CiaYrrORChwj6vTbimv0CQGpd2IZ5LOhyW2+N+YDgFg3Vzac/ -ti+eJEto8kAqgHUELvUctZmpmypBYe9pc91GQO0ePKL3IaE/ZIhRF4d6c0ECQH9A -XiaD7PiKvjLs0A31u8ZCt4A+7BII9LYl73mntobBSbu4ji9Xyyn6qEAPa1ORZK49 -DwGPSuF2W2lESlYtSOkCQGrtczhx3IyJjk5e2Y1i/UddPKjviAysCSzcW6aVTNr9 -Y2L0sWmva2FKnkl9FDuEqxvmGr6OOkr5Ll7aWLzJri8= ------END RSA PRIVATE KEY-----
--- a/modules/sockets/test/test.crt Thu Feb 25 21:01:02 2016 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,14 +0,0 @@ ------BEGIN CERTIFICATE----- -MIICITCCAYoCCQCGm4grkVCohjANBgkqhkiG9w0BAQsFADBVMQswCQYDVQQGEwJG -UjEPMA0GA1UECAwGRnJhbmNlMSEwHwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0 -eSBMdGQxEjAQBgNVBAMMCWxvY2FsaG9zdDAeFw0xNTEwMjYyMDM0NThaFw0yNTEw -MjMyMDM0NThaMFUxCzAJBgNVBAYTAkZSMQ8wDQYDVQQIDAZGcmFuY2UxITAfBgNV -BAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDESMBAGA1UEAwwJbG9jYWxob3N0 -MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDp13OqVyOWyv5QWD4xr+Duw6SZ -gU7D5huzsAOcneSI6JUhf+7Ecu6BQ2JGkFn4srIVkMWGQuImETJ8JCpSQH7rk+xO -L9fTTK+TwhP2hW/Rf/b2gWedhJAS+gilqt4JNT7v2wFv+aTtRt/lpTXVSdtpLa/m -Pdy219f6MAPgODJ/7QIDAQABMA0GCSqGSIb3DQEBCwUAA4GBAJSnn/IBn1ZblfzP -rJO/lE1Jwpmx3B7+oR/e4fkZd6JR3s06umGYWr2H+TPl/5dj9x0gPokhoIL9zCGq -SxCPnOeaxjBkw7yh3Ks6m3xKxmK4aMpAtBHtwmbfQyIcgz71/lfCzbJ3WcKpn1ig -IZbByt5QSSPcFORRzJJa35eHBdfX ------END CERTIFICATE-----
--- a/modules/sockets/test/test.key Thu Feb 25 21:01:02 2016 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,15 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIICXAIBAAKBgQDp13OqVyOWyv5QWD4xr+Duw6SZgU7D5huzsAOcneSI6JUhf+7E -cu6BQ2JGkFn4srIVkMWGQuImETJ8JCpSQH7rk+xOL9fTTK+TwhP2hW/Rf/b2gWed -hJAS+gilqt4JNT7v2wFv+aTtRt/lpTXVSdtpLa/mPdy219f6MAPgODJ/7QIDAQAB -AoGBANDt4ndQkgi56A1rOm50gVlzTg6lPPXFE/0xB5kYbcdxX0VmI7Q8KCMwTI9V -jD2rk3e3OPSjr6FpfhzyxylkXMBz2BL5NRNPowCJbiMgZOUIzlcWPKo0tgf1bZJx -YdB5U003ISGPPBjVOAjyizY7tJnaNvbpLQ0hbIAsvHPEAOnBAkEA9r3g8NQjPrvb -oIr5SMIxM8HDJ1/q+MEBSFtRFzQpmur6P64Jsu96zCyencUYTxs0L/sottrj6dPC -vjGCc6PjsQJBAPKdqK1knJv6Y95M2bnEwrymCFVdxCi7AxObStB+bg/+7mMCUqqX -j2g71bfvhYakHV7CiaYrrORChwj6vTbimv0CQGpd2IZ5LOhyW2+N+YDgFg3Vzac/ -ti+eJEto8kAqgHUELvUctZmpmypBYe9pc91GQO0ePKL3IaE/ZIhRF4d6c0ECQH9A -XiaD7PiKvjLs0A31u8ZCt4A+7BII9LYl73mntobBSbu4ji9Xyyn6qEAPa1ORZK49 -DwGPSuF2W2lESlYtSOkCQGrtczhx3IyJjk5e2Y1i/UddPKjviAysCSzcW6aVTNr9 -Y2L0sWmva2FKnkl9FDuEqxvmGr6OOkr5Ll7aWLzJri8= ------END RSA PRIVATE KEY-----