Mercurial > irccd
changeset 551:a5e1c91abb8e
Irccd: split services, closes #730
While here:
- delete interrupt_service, no longer needed,
- delete net.hpp, net_util.hpp, no longer needed.
line wrap: on
line diff
--- a/irccd/main.cpp Fri Nov 24 20:05:15 2017 +0100 +++ b/irccd/main.cpp Fri Nov 24 20:09:45 2017 +0100 @@ -33,15 +33,17 @@ #include <csignal> #include <iostream> -#include "command.hpp" -#include "logger.hpp" -#include "options.hpp" -#include "service.hpp" -#include "string_util.hpp" -#include "system.hpp" -#include "transport_service.hpp" -#include "config.hpp" -#include "irccd.hpp" +#include <irccd/command_service.hpp> +#include <irccd/config.hpp> +#include <irccd/irccd.hpp> +#include <irccd/logger.hpp> +#include <irccd/options.hpp> +#include <irccd/plugin_service.hpp> +#include <irccd/rule_service.hpp> +#include <irccd/server_service.hpp> +#include <irccd/string_util.hpp> +#include <irccd/system.hpp> +#include <irccd/transport_service.hpp> #if defined(HAVE_JS) # include <irccd/js/directory_jsapi.hpp> @@ -62,6 +64,7 @@ namespace { +std::atomic<bool> running{true}; std::unique_ptr<irccd> instance; void usage() @@ -101,7 +104,7 @@ void stop(int) { - instance->stop(); + running = false; } void init(int& argc, char**& argv) @@ -362,6 +365,8 @@ * associated objects before any other static global values such as * loggers. */ - instance->run(); + while (running) + service.run(); + instance = nullptr; }
--- a/libcommon/CMakeLists.txt Fri Nov 24 20:05:15 2017 +0100 +++ b/libcommon/CMakeLists.txt Fri Nov 24 20:09:45 2017 +0100 @@ -26,10 +26,8 @@ ${libcommon_SOURCE_DIR}/irccd/ini.hpp ${libcommon_SOURCE_DIR}/irccd/json_util.hpp ${libcommon_SOURCE_DIR}/irccd/logger.hpp - ${libcommon_SOURCE_DIR}/irccd/net.hpp ${libcommon_SOURCE_DIR}/irccd/network_errc.hpp ${libcommon_SOURCE_DIR}/irccd/network_stream.hpp - ${libcommon_SOURCE_DIR}/irccd/net_util.hpp ${libcommon_SOURCE_DIR}/irccd/options.hpp ${libcommon_SOURCE_DIR}/irccd/signals.hpp ${libcommon_SOURCE_DIR}/irccd/string_util.hpp
--- a/libcommon/irccd/net.hpp Fri Nov 24 20:05:15 2017 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,3734 +0,0 @@ -/* - * net.hpp -- portable C++ socket wrapper - * - * Copyright (c) 2013-2017 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 IRCCD_NET_HPP -#define IRCCD_NET_HPP - -/** - * \file net.hpp - * \brief Networking - * \author David Demelier <markand@malikania.fr> - */ - -/** - * \defgroup net-module-tcp Network TCP support - * \brief TCP support in the networking module. - */ - -/** - * \defgroup net-module-udp Network UDP support - * \brief UDP support in networking module. - */ - -/** - * \defgroup net-module-tls Network TLS support - * \brief TLS support in networking module. - */ - -/** - * \defgroup net-module-addresses Network predefined addresses - * \brief Predefined addresses for sockets - */ - -/** - * \defgroup net-module-options Network predefined options - * \brief Predefined options for sockets - */ - -/** - * \defgroup net-module-backends Network predefined backends - * \brief Predefined backends for Listener - */ - -/** - * \defgroup net-module-resolv Network resolver - * \brief Resolv functions - */ - -/** - * \page Networking Networking - * - * - \subpage net-options - * - \subpage net-concepts - */ - -/** - * \page net-options User options - * - * The user may set the following variables before compiling these files: - * - * # General options - * - * - **NET_NO_AUTO_INIT**: (bool) Set to 0 if you don't want Socket class to - * automatically calls init function and finish functions. - * - * - **NET_NO_SSL**: (bool) Set to 0 if you don't have access to OpenSSL - * library. - * - * - **NET_NO_AUTO_SSL_INIT**: (bool) Set to 0 if you don't want Socket class - * with Tls to automatically init the OpenSSL library. You will need to call - * ssl::init and ssl::finish. - * - * # General compatibility options. - * - * The following options are auto detected but you can override them if you - * want. - * - * - **NET_HAVE_INET_PTON**: (bool) Set to 1 if you have inet_pton function. - * True for all platforms and Windows - * if _WIN32_WINNT is greater or equal to 0x0600. - * - * - **NET_HAVE_INET_NTOP**: (bool) Same as above. - * - * **Note:** On Windows, it is highly encouraged to set _WIN32_WINNT to at least - * 0x0600 on MinGW. - * - * # Options for Listener class - * - * Feature detection, multiple implementations may be avaible, for example, - * Linux has poll, select and epoll. - * - * We assume that `select(2)` is always available. - * - * Of course, you can set the variables yourself if you test it with your build - * system. - * - * - **NET_HAVE_POLL**: Defined on all BSD, Linux. Also defined on Windows - * if _WIN32_WINNT is set to 0x0600 or greater. - * - **NET_HAVE_KQUEUE**: Defined on all BSD and Apple. - * - **NET_HAVE_EPOLL**: Defined on Linux only. - * - **NET_DEFAULT_BACKEND**: Which backend to use (e.g. `Select`). - * - * The preference priority is ordered from left to right. - * - * | System | Backend | Class name | - * |---------------|-------------------------|--------------| - * | Linux | epoll(7) | Epoll | - * | *BSD | kqueue(2) | Kqueue | - * | Windows | poll(2), select(2) | Poll, Select | - * | Mac OS X | kqueue(2) | Kqueue | - */ - -/** - * \page net-concepts Concepts - * - * - \subpage net-concept-backend - * - \subpage net-concept-option - * - \subpage net-concept-stream - * - \subpage net-concept-datagram - */ - -/** - * \page net-concept-backend Backend (Concept) - * - * A backend is an interface for the Listener class. It is primarily designed to - * be the most suitable for the host environment. - * - * The backend must be default constructible, it is highly encouraged to be move - * constructible. - * - * This concepts requires the following functions: - * - * # name - * - * Get the backend name, informational only. - * - * ## Synopsis - * - * ```` - * std::string name() const; - * ```` - * - * ## Returns - * - * The backend name. - * - * # set - * - * Set one or more condition for the given handle. - * - * ## Synopsis - * - * ```` - * void set(const ListenerTable &table, Handle handle, Condition condition, - * bool add); - * ```` - * - * ## Arguments - * - * - **table**: the current table of sockets, - * - **handle**: the handle to set, - * - **condition**: the condition to add (may be OR'ed), - * - **add**: hint set to true if the handle is not currently registered. - * - * # unset - * - * Unset one or more condition for the given handle. - * - * ## Synopsis - * - * ```` - * void unset(const ListenerTable &table, Handle handle, Condition condition, - * bool remove); - * ```` - * - * ## Arguments - * - * - **table**: the current table of sockets, - * - **handle**: the handle to update, - * - **condition**: the condition to remove (may be OR'ed), - * - **remove**: hint set to true if the handle will be completely removed. - * - * # wait - * - * Wait for multiple sockets to be ready. - * - * ## Synopsis - * - * ```` - * std::vector<ListenerStatus> wait(const ListenerTable &table, int ms); - * ```` - * - * ## Arguments - * - * - **table**: the current table, - * - **ms**: the number to wait in milliseconds, negative means forever. - * - * ## Returns - * - * The list of sockets ready paired to their condition. - */ - -/** - * \page net-concept-option Option (Concept) - * - * An option can be set or get from a socket. - * - * If an operation is not available, provides the function but throws an - * exception with ENOSYS message. - * - * This concepts requires the following functions: - * - * # Option (constructor) - * - * At least one default constructor must be present. - * - * ## Synopsis - * - * ```` - * Option() noexcept; - * ```` - * - * # set - * - * Set the option. - * - * ## Synopsis - * - * ```` - * template <typename Address> - * void set(Socket &sc) const; - * ```` - * - * ## Arguments - * - * - **sc**: the socket. - * - * # get - * - * Get an option, T can be any type. - * - * ## Synopsis - * - * ```` - * template <typename Address> - * T get(Socket &sc) const; - * ```` - * - * ## Arguments - * - * - **sc**: the socket. - * - * ## Returns - * - * The value. - */ - -/** - * \page net-concept-stream Stream (Concept) - * - * This concepts requires the following functions: - * - * # type - * - * Return the type of socket, usually `SOCK_STREAM`. - * - * ## Synopsis - * - * ```` - * int type() const noexcept; - * ```` - * - * ## Returns - * - * The type of socket. - * - * # connect - * - * Connect to the given address. - * - * ## Synopsis - * - * ```` - * void connect(const sockaddr *address, socklen_t length); // (0) - * void connect(const Address &address); // 1 (Optional) - * ```` - * - * ## Arguments - * - * - **address**: the address, - * - **length**: the address length. - * - * ## Throws - * - * - net::WouldBlockError: if the operation would block, - * - net::Error: on other error. - * - * # accept - * - * Accept a new client. - * - * If no pending connection is available and operation would block, the - * implementation must throw WouldBlockError. Any other error can be thrown - * otherwise a valid socket must be returned. - * - * ## Synopsis - * - * ```` - * Socket accept(); - * ```` - * - * ## Returns - * - * The new socket. - * - * ## Throws - * - * - net::WouldBlockError: if the operation would block, - * - net::Error: on other error. - * - * # recv - * - * Receive data. - * - * ## Synopsis - * - * ```` - * unsigned recv(void *data, unsigned length); - * ```` - * - * ## Arguments - * - * - **data**: the destination buffer, - * - **length**: the destination buffer length. - * - * ## Returns - * - * The number of bytes sent. - * - * ## Throws - * - * - net::WouldBlockError: if the operation would block, - * - net::Error: on other error. - * - * # send - * - * Send data. - * - * ## Synopsis - * - * ```` - * unsigned send(const void *data, unsigned length); - * ```` - * - * ## Arguments - * - * - **data**: the data to send, - * - **length**: the data length. - * - * ## Returns - * - * The number of bytes sent. - * - * ## Throws - * - * - net::WouldBlockError: if the operation would block, - * - net::Error: on other error. - */ - -/** - * \page net-concept-datagram Datagram (Concept) - * - * This concepts requires the following functions: - * - * # type - * - * Return the type of socket, usually `SOCK_DGRAM`. - * - * ## Synopsis - * - * ```` - * int type() const noexcept; - * ```` - * - * ## Returns - * - * The type of socket. - * - * # recvfrom - * - * Receive data. - * - * ## Synopsis - * - * ```` - * unsigned recvfrom(void *data, unsigned length, sockaddr *address, - * socklen_t *addrlen); - * unsigned recvfrom(void *data, unsigned length, Address *source) - * ```` - * - * ## Arguments - * - * - **data**: the data, - * - **length**: the length, - * - **address**: the source address, - * - **addrlen**: the source address in/out length. - * - * ## Returns - * - * The number of bytes received. - * - * ## Throws - * - * - net::WouldBlockError: if the operation would block, - * - net::Error: on other error. - * - * # sendto - * - * ```` - * unsigned sendto(const void *data, unsigned length, const sockaddr *address, - * socklen_t addrlen); - * unsigned sendto(const void *data, unsigned length, const Address &address); - * ```` - * - * ## Arguments - * - * - **data**: the data to send, - * - **length**: the data length, - * - **address**: the destination address, - * - **addrlen**: the destination address length. - * - * ## Returns - * - * The number of bytes sent. - * - * ## Throws - * - * - net::WouldBlockError: if the operation would block, - * - net::Error: on other error. - */ - -/* - * Headers to include. - * ------------------------------------------------------------------ - */ - -/* - * Include Windows headers before because it brings _WIN32_WINNT if not - * specified by the user. - */ -#if defined(_WIN32) -# include <winsock2.h> -# include <ws2tcpip.h> -#else -# include <sys/ioctl.h> -# include <sys/types.h> -# include <sys/socket.h> -# include <sys/un.h> - -# include <arpa/inet.h> - -# include <netinet/in.h> -# include <netinet/tcp.h> - -# include <fcntl.h> -# include <netdb.h> -# include <unistd.h> -#endif - -#include <algorithm> -#include <atomic> -#include <cassert> -#include <cerrno> -#include <chrono> -#include <climits> -#include <cstddef> -#include <cstdlib> -#include <cstring> -#include <iterator> -#include <memory> -#include <mutex> -#include <string> -#include <unordered_map> -#include <vector> - -#if !defined(NET_NO_SSL) - -#include <openssl/err.h> -#include <openssl/evp.h> -#include <openssl/ssl.h> -#include <openssl/opensslv.h> - -#endif // !NET_NO_SSL - -/* - * Determine which I/O multiplexing implementations are available. - * ------------------------------------------------------------------ - * - * May define the following: - * - * - NET_HAVE_EPOLL - * - NET_HAVE_KQUEUE - * - NET_HAVE_POLL - */ - -#if defined(_WIN32) -# if _WIN32_WINNT >= 0x0600 && !defined(NET_HAVE_POLL) -# define NET_HAVE_POLL -# endif -#elif defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__) -# if !defined(NET_HAVE_KQUEUE) -# define NET_HAVE_KQUEUE -# endif -# if !defined(NET_HAVE_POLL) -# define NET_HAVE_POLL -# endif -#elif defined(__linux__) -# if !defined(NET_HAVE_EPOLL) -# define NET_HAVE_EPOLL -# endif -# if !defined(NET_HAVE_POLL) -# define NET_HAVE_POLL -# endif -#endif - -/* - * Compatibility macros. - * ------------------------------------------------------------------ - */ - -/** - * \brief Tells if inet_pton is available - */ -#if !defined(NET_HAVE_INET_PTON) -# if defined(_WIN32) -# if _WIN32_WINNT >= 0x0600 -# define NET_HAVE_INET_PTON -# endif -# else -# define NET_HAVE_INET_PTON -# endif -#endif - -/** - * \brief Tells if inet_ntop is available - */ -#if !defined(NET_HAVE_INET_NTOP) -# if defined(_WIN32) -# if _WIN32_WINNT >= 0x0600 -# define NET_HAVE_INET_NTOP -# endif -# else -# define NET_HAVE_INET_NTOP -# endif -#endif - -/* - * Define NET_DEFAULT_BACKEND. - * ------------------------------------------------------------------ - * - * Define the default I/O multiplexing implementation to use if not specified. - */ - -/** - * \brief Defines the default backend - */ -#if defined(_WIN32) -# if !defined(NET_DEFAULT_BACKEND) -# if defined(NET_HAVE_POLL) -# define NET_DEFAULT_BACKEND Poll -# else -# define NET_DEFAULT_BACKEND Select -# endif -# endif -#elif defined(__linux__) -# include <sys/epoll.h> - -# if !defined(NET_DEFAULT_BACKEND) -# define NET_DEFAULT_BACKEND Epoll -# endif -#elif defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__DragonFly__) || defined(__APPLE__) -# include <sys/types.h> -# include <sys/event.h> -# include <sys/time.h> - -# if !defined(NET_DEFAULT_BACKEND) -# define NET_DEFAULT_BACKEND Kqueue -# endif -#else -# if !defined(NET_DEFAULT_BACKEND) -# define NET_DEFAULT_BACKEND Select -# endif -#endif - -#if defined(NET_HAVE_POLL) && !defined(_WIN32) -# include <poll.h> -#endif - -#if !defined(NET_NO_SSL) -# if OPENSSL_VERSION_NUMBER >= 0x10100000L -# define NET_SSL_DEFAULT_METHOD TLS_method -# else -# define NET_SSL_DEFAULT_METHOD TLSv1_method -# endif -#endif - -namespace irccd { - -/** - * The network namespace. - */ -namespace net { - -/* - * Portables types. - * ------------------------------------------------------------------ - * - * The following types are defined differently between Unix and Windows. - */ - -#if defined(_WIN32) - -/** - * Socket type, SOCKET. - */ -using Handle = SOCKET; - -/** - * Argument to pass to set. - */ -using ConstArg = const char *; - -/** - * Argument to pass to get. - */ -using Arg = char *; - -#else - -/** - * Socket type, int. - */ -using Handle = int; - -/** - * Argument to pass to set. - */ -using ConstArg = const void *; - -/** - * Argument to pass to get. - */ -using Arg = void *; - -#endif - -/* - * Portable constants. - * ------------------------------------------------------------------ - * - * These constants are needed to check functions return codes, they are rarely - * needed in end user code. - */ - -#if defined(_WIN32) - -/** - * Socket creation failure or invalidation. - */ -const Handle Invalid{INVALID_SOCKET}; - -/** - * Socket operation failure. - */ -const int Failure{SOCKET_ERROR}; - -#else - -/** - * Socket creation failure or invalidation. - */ -const Handle Invalid{-1}; - -/** - * Socket operation failure. - */ -const int Failure{-1}; - -#endif - -/** - * Close the socket library. - */ -inline void finish() noexcept -{ -#if defined(_WIN32) - WSACleanup(); -#endif -} - -/** - * Initialize the socket library. Except if you defined NET_NO_AUTO_INIT, you - * don't need to call this - * function manually. - */ -inline void init() noexcept -{ -#if defined(_WIN32) - static std::atomic<bool> initialized; - static std::mutex mutex; - - std::lock_guard<std::mutex> lock(mutex); - - if (!initialized) { - initialized = true; - - WSADATA wsa; - WSAStartup(MAKEWORD(2, 2), &wsa); - - /* - * If NET_NO_AUTO_INIT is not set then the user must also call finish - * himself. - */ -#if !defined(NET_NO_AUTO_INIT) - atexit(finish); -#endif - } -#endif -} - -/** - * Get the last system error. - * - * \param errn the error number (errno or WSAGetLastError) - * \return the error - */ -inline std::string error(int errn) -{ -#if defined(_WIN32) - LPSTR str = nullptr; - std::string errmsg = "Unknown error"; - - FormatMessageA( - FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, - NULL, - errn, - MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), - (LPSTR)&str, 0, NULL); - - - if (str) { - errmsg = std::string(str); - LocalFree(str); - } - - return errmsg; -#else - return strerror(errn); -#endif -} - -/** - * Get the last socket system error. The error is set from errno or from - * WSAGetLastError on Windows. - * - * \return a string message - */ -inline std::string error() -{ -#if defined(_WIN32) - return error(WSAGetLastError()); -#else - return error(errno); -#endif -} - -#if !defined(NET_NO_SSL) - -/** - * \brief SSL namespace - */ -namespace ssl { - -/** - * \enum Method - * \brief Which OpenSSL method to use. - */ -enum Method { - Tlsv1, //!< TLS v1.2 (recommended) - Sslv3 //!< SSLv3 -}; - -/** - * Initialize the OpenSSL library. Except if you defined NET_NO_AUTO_SSL_INIT, - * you don't need to call this function manually. - */ -inline void init() noexcept -{ - static std::atomic<bool> initialized; - static std::mutex mutex; - - std::lock_guard<std::mutex> lock(mutex); - - if (!initialized) { - initialized = true; - - SSL_library_init(); - SSL_load_error_strings(); - OpenSSL_add_all_algorithms(); - -#if !defined(NET_NO_AUTO_SSL_INIT) - atexit(finish); -#endif - } -} - -/** - * Close the OpenSSL library. - */ -inline void finish() noexcept -{ - ERR_free_strings(); -} - -} // !ssl - -#endif // !NET_NO_SSL - -/* - * Error class. - * ------------------------------------------------------------------ - * - * This is the main exception thrown on socket operations. - */ - -/** - * \brief Base class for sockets error. - */ -class Error : public std::exception { -private: - std::string m_message; - -public: - /** - * Construct the error using the specified error from the system. - * - * \param code the error code - * \warning the code must be a Winsock error or errno on Unix - */ - inline Error(int code) noexcept - : m_message(error(code)) - { - } - - /** - * Construct the error using the custom message. - * - * \param message the message - */ - inline Error(std::string message) noexcept - : m_message(std::move(message)) - { - } - - /** - * Construct the error using the last message from the system. - */ - inline Error() noexcept -#if defined(_WIN32) - : Error(WSAGetLastError()) -#else - : Error(errno) -#endif - { - } - - /** - * Get the error (only the error content). - * - * \return the error - */ - const char *what() const noexcept override - { - return m_message.c_str(); - } -}; - -/** - * \brief Timeout occured. - */ -class TimeoutError : public std::exception { -public: - /** - * Get the error message. - * - * \return the message - */ - const char *what() const noexcept override - { - return std::strerror(ETIMEDOUT); - } -}; - -/** - * \brief Operation would block. - */ -class WouldBlockError : public std::exception { -public: - /** - * Get the error message. - * - * \return the message - */ - const char *what() const noexcept override - { - return std::strerror(EWOULDBLOCK); - } -}; - -/** - * \brief Operation requires sending data to complete. - */ -class WantWriteError : public std::exception { -public: - /** - * Get the error message. - * - * \return the message - */ - const char *what() const noexcept override - { - return "operation requires writing to complete"; - } -}; - -/** - * \brief Operation requires reading data to complete. - */ -class WantReadError : public std::exception { -public: - /** - * Get the error message. - * - * \return the message - */ - const char *what() const noexcept override - { - return "operation requires read to complete"; - } -}; - -/* - * Condition enum - * ------------------------------------------------------------------ - * - * Defines if we must wait for reading or writing. - */ - -/** - * \enum Condition - * \brief Define the required condition for the socket. - */ -enum class Condition { - None, //!< No condition is required - Readable = (1 << 0), //!< The socket must be readable - Writable = (1 << 1) //!< The socket must be writable -}; - -/** - * Apply bitwise XOR. - * - * \param v1 the first value - * \param v2 the second value - * \return the new value - */ -inline Condition operator^(Condition v1, Condition v2) noexcept -{ - return static_cast<Condition>(static_cast<int>(v1) ^ static_cast<int>(v2)); -} - -/** - * Apply bitwise AND. - * - * \param v1 the first value - * \param v2 the second value - * \return the new value - */ -inline Condition operator&(Condition v1, Condition v2) noexcept -{ - return static_cast<Condition>(static_cast<int>(v1) & static_cast<int>(v2)); -} - -/** - * Apply bitwise OR. - * - * \param v1 the first value - * \param v2 the second value - * \return the new value - */ -inline Condition operator|(Condition v1, Condition v2) noexcept -{ - return static_cast<Condition>(static_cast<int>(v1) | static_cast<int>(v2)); -} - -/** - * Apply bitwise NOT. - * - * \param v the value - * \return the complement - */ -inline Condition operator~(Condition v) noexcept -{ - return static_cast<Condition>(~static_cast<int>(v)); -} - -/** - * Assign bitwise OR. - * - * \param v1 the first value - * \param v2 the second value - * \return the new value - */ -inline Condition &operator|=(Condition &v1, Condition v2) noexcept -{ - v1 = static_cast<Condition>(static_cast<int>(v1) | static_cast<int>(v2)); - - return v1; -} - -/** - * Assign bitwise AND. - * - * \param v1 the first value - * \param v2 the second value - * \return the new value - */ -inline Condition &operator&=(Condition &v1, Condition v2) noexcept -{ - v1 = static_cast<Condition>(static_cast<int>(v1) & static_cast<int>(v2)); - - return v1; -} - -/** - * Assign bitwise XOR. - * - * \param v1 the first value - * \param v2 the second value - * \return the new value - */ -inline Condition &operator^=(Condition &v1, Condition v2) noexcept -{ - v1 = static_cast<Condition>(static_cast<int>(v1) ^ static_cast<int>(v2)); - - return v1; -} - -/** - * \brief Generic socket address storage. - * \ingroup net-module-addresses - */ -class Address { -private: - sockaddr_storage m_storage; - socklen_t m_length; - -public: - /** - * Construct empty address. - */ - inline Address() noexcept - : m_storage{} - , m_length(0) - { - } - - /** - * Construct address from existing one. - * - * \pre address != nullptr - * \param address the address - * \param length the address length - */ - inline Address(const sockaddr *address, socklen_t length) noexcept - : m_length(length) - { - assert(address); - - std::memcpy(&m_storage, address, length); - } - - /** - * Get the underlying address. - * - * \return the address - */ - inline const sockaddr *get() const noexcept - { - return reinterpret_cast<const sockaddr *>(&m_storage); - } - - /** - * Overloaded function - * - * \return the address - */ - inline sockaddr *get() noexcept - { - return reinterpret_cast<sockaddr *>(&m_storage); - } - - /** - * Get the underlying address as the given type (e.g sockaddr_in). - * - * \return the address reference - */ - template <typename T> - inline const T &as() const noexcept - { - return reinterpret_cast<const T &>(m_storage); - } - - /** - * Overloaded function - * - * \return the address reference - */ - template <typename T> - inline T &as() noexcept - { - return reinterpret_cast<T &>(m_storage); - } - - /** - * Get the underlying address length. - * - * \return the length - */ - inline socklen_t length() const noexcept - { - return m_length; - } - - /** - * Get the address domain. - * - * \return the domain - */ - inline int domain() const noexcept - { - return m_storage.ss_family; - } -}; - -/** - * \brief Address iterator. - * \ingroup net-module-addresses - * \see resolve - * - * This iterator can be used to try to connect to an host. - * - * When you use resolve with unspecified domain or socket type, the function may - * retrieve several different addresses that you can iterate over to try to - * connect to. - * - * Example: - * - * ````cpp - * SocketTcp sc; - * AddressIterator end, it = resolve("hostname.test", "80"); - * - * while (!connected_condition && it != end) - * sc.connect(it->address(), it->length()); - * ```` - * - * When an iterator equals to a default constructed iterator, it is considered - * not dereferenceable. - */ -class AddressIterator : public std::iterator<std::forward_iterator_tag, Address> { -private: - std::vector<Address> m_addresses; - std::size_t m_index{0}; - -public: - /** - * Construct a null iterator. - * - * The default constructed iterator is not dereferenceable. - */ - inline AddressIterator() noexcept = default; - - /** - * Construct an address iterator with a set of addresses. - * - * \pre index < m_addresses.size() - * \param addresses the addresses - * \param index the first index - */ - inline AddressIterator(std::vector<Address> addresses, std::size_t index = 0) noexcept - : m_addresses(std::move(addresses)) - , m_index(index) - { - assert(index < m_addresses.size()); - } - - /** - * Get the generic address. - * - * \pre this is dereferenceable - * \return the generic address - */ - inline const Address &operator*() const noexcept - { - assert(m_index <= m_addresses.size()); - - return m_addresses[m_index]; - } - - /** - * Overloaded function. - * - * \pre this is dereferenceable - * \return the generic address - */ - inline Address &operator*() noexcept - { - assert(m_index <= m_addresses.size()); - - return m_addresses[m_index]; - } - - /** - * Get the generic address. - * - * \pre this is dereferenceable - * \return the generic address - */ - inline const Address *operator->() const noexcept - { - assert(m_index <= m_addresses.size()); - - return &m_addresses[m_index]; - } - - /** - * Overloaded function. - * - * \pre this is dereferenceable - * \return the generic address - */ - inline Address *operator->() noexcept - { - assert(m_index <= m_addresses.size()); - - return &m_addresses[m_index]; - } - - /** - * Pre-increment the iterator. - * - * \return this - */ - inline AddressIterator &operator++(int) noexcept - { - if (m_index + 1 >= m_addresses.size()) { - m_addresses.clear(); - m_index = 0; - } else - m_index ++; - - return *this; - } - - /** - * Post-increment the iterator. - * - * \return copy of this - */ - inline AddressIterator operator++() noexcept - { - AddressIterator save = *this; - - if (m_index + 1 >= m_addresses.size()) { - m_addresses.clear(); - m_index = 0; - } else - m_index ++; - - return save; - } - - friend bool operator==(const AddressIterator &, const AddressIterator &) noexcept; - friend bool operator!=(const AddressIterator &, const AddressIterator &) noexcept; -}; - -/** - * Compare two address iterators. - * - * \param i1 the first iterator - * \param i2 the second iterator - * \return true if they equal - */ -inline bool operator==(const AddressIterator &i1, const AddressIterator &i2) noexcept -{ - return i1.m_addresses == i2.m_addresses && i1.m_index == i2.m_index; -} - -/** - * Compare two address iterators. - * - * \param i1 the first iterator - * \param i2 the second iterator - * \return false if they equal - */ -inline bool operator!=(const AddressIterator &i1, const AddressIterator &i2) noexcept -{ - return !(i1 == i2); -} - -/** - * Compare two generic addresses. - * - * \param a1 the first address - * \param a2 the second address - * \return true if they equal - */ -inline bool operator==(const Address &a1, const Address &a2) noexcept -{ - return a1.length() == a2.length() && std::memcmp(a1.get(), a2.get(), a1.length()) == 0; -} - -/** - * Compare two generic addresses. - * - * \param a1 the first address - * \param a2 the second address - * \return false if they equal - */ -inline bool operator!=(const Address &a1, const Address &a2) noexcept -{ - return !(a1 == a2); -} - -/** - * \brief Base socket class. - */ -class Socket { -protected: - /** - * The native handle. - */ - Handle m_handle{Invalid}; - -public: - /** - * Create a socket handle. - * - * This is the primary function and the only one that creates the socket - * handle, all other constructors are just overloaded functions. - * - * \param domain the domain AF_* - * \param type the type SOCK_* - * \param protocol the protocol - * \throw Error on errors - */ - Socket(int domain, int type, int protocol) - { -#if !defined(NET_NO_AUTO_INIT) - init(); -#endif - m_handle = ::socket(domain, type, protocol); - - if (m_handle == Invalid) - throw Error(); - } - - /** - * Create the socket with an already defined handle and its protocol. - * - * \param handle the handle - */ - explicit inline Socket(Handle handle) noexcept - : m_handle(handle) - { - } - - /** - * Create an invalid socket. Can be used when you cannot instanciate the - * socket immediately. - */ - explicit inline Socket(std::nullptr_t) noexcept - : m_handle(Invalid) - { - } - - /** - * Copy constructor deleted. - */ - Socket(const Socket &) = delete; - - /** - * Transfer ownership from other to this. - * - * \param other the other socket - */ - inline Socket(Socket &&other) noexcept - : m_handle(other.m_handle) - { - other.m_handle = Invalid; - } - - /** - * Default destructor. - */ - virtual ~Socket() - { - close(); - } - - /** - * Tells if the socket is not invalid. - * - * \return true if not invalid - */ - inline bool isOpen() const noexcept - { - return m_handle != Invalid; - } - - /** - * Set an option for the socket. Wrapper of setsockopt(2). - * - * \pre isOpen() - * \param level the setting level - * \param name the name - * \param arg the value - * \throw Error on errors - */ - template <typename Argument> - inline void set(int level, int name, const Argument &arg) - { - assert(m_handle != Invalid); - - if (::setsockopt(m_handle, level, name, (ConstArg)&arg, sizeof (arg)) == Failure) - throw Error(); - } - - /** - * Object-oriented option setter. - * - * The object must have `set(Socket &) const`. - * - * \pre isOpen() - * \param option the option - * \throw Error on errors - */ - template <typename Option> - inline void set(const Option &option) - { - assert(m_handle != Invalid); - - option.set(*this); - } - - /** - * Get an option for the socket. Wrapper of getsockopt(2). - * - * \pre isOpen() - * \param level the setting level - * \param name the name - * \return the value - * \throw Error on errors - */ - template <typename Argument> - Argument get(int level, int name) - { - assert(m_handle != Invalid); - - Argument desired, result{}; - socklen_t size = sizeof (result); - - if (::getsockopt(m_handle, level, name, (Arg)&desired, &size) == Failure) - throw Error(); - - std::memcpy(&result, &desired, size); - - return result; - } - - /** - * Object-oriented option getter. - * - * The object must have `T get(Socket &) const`, T can be any type and it is - * the value returned from this function. - * - * \pre isOpen() - * \return the same value as get() in the option - * \throw Error on errors - */ - template <typename Option> - inline auto get() -> decltype(std::declval<Option>().get(*this)) - { - assert(m_handle != Invalid); - - return Option().get(*this); - } - - /** - * Get the native handle. - * - * \return the handle - * \warning Not portable - */ - inline Handle handle() const noexcept - { - return m_handle; - } - - /** - * Bind using a native address. - * - * \pre isOpen() - * \param address the address - * \param length the size - * \throw Error on errors - */ - inline void bind(const sockaddr *address, socklen_t length) - { - assert(m_handle != Invalid); - - if (::bind(m_handle, address, length) == Failure) - throw Error(); - } - - /** - * Overload that takes an address. - * - * \pre isOpen() - * \param address the address - * \throw Error on errors - */ - inline void bind(const Address &address) - { - assert(m_handle != Invalid); - - bind(address.get(), address.length()); - } - - /** - * Listen for pending connection. - * - * \pre isOpen() - * \param max the maximum number - * \throw Error on errors - */ - inline void listen(int max = 128) - { - assert(m_handle != Invalid); - - if (::listen(m_handle, max) == Failure) - throw Error(); - } - - /** - * Get the local name. This is a wrapper of getsockname(). - * - * \pre isOpen() - * \return the address - * \throw Error on failures - */ - Address getsockname() const - { - assert(m_handle != Invalid); - - sockaddr_storage ss; - socklen_t length = sizeof (sockaddr_storage); - - if (::getsockname(m_handle, reinterpret_cast<sockaddr *>(&ss), &length) == Failure) - throw Error(); - - return Address(reinterpret_cast<sockaddr *>(&ss), length); - } - - /** - * Get connected address. This is a wrapper for getpeername(). - * - * \pre isOpen() - * \return the address - * \throw Error on failures - */ - Address getpeername() const - { - assert(m_handle != Invalid); - - sockaddr_storage ss; - socklen_t length = sizeof (sockaddr_storage); - - if (::getpeername(m_handle, reinterpret_cast<sockaddr *>(&ss), &length) == Failure) - throw Error(); - - return Address(reinterpret_cast<sockaddr *>(&ss), length); - } - - /** - * Close the socket. - * - * Automatically called from the destructor. - */ - void close() - { - if (m_handle != Invalid) { -#if defined(_WIN32) - ::closesocket(m_handle); -#else - ::close(m_handle); -#endif - m_handle = Invalid; - } - } - - /** - * Assignment operator forbidden. - * - * \return *this - */ - Socket &operator=(const Socket &) = delete; - - /** - * Transfer ownership from other to this. The other socket is left - * invalid and will not be closed. - * - * \param other the other socket - * \return this - */ - Socket &operator=(Socket &&other) noexcept - { - m_handle = other.m_handle; - other.m_handle = Invalid; - - return *this; - } -}; - -/** - * Compare two sockets. - * - * \param s1 the first socket - * \param s2 the second socket - * \return true if they equals - */ -inline bool operator==(const Socket &s1, const Socket &s2) -{ - return s1.handle() == s2.handle(); -} - -/** - * Compare two sockets. - * - * \param s1 the first socket - * \param s2 the second socket - * \return true if they are different - */ -inline bool operator!=(const Socket &s1, const Socket &s2) -{ - return s1.handle() != s2.handle(); -} - -/** - * Compare two sockets. - * - * \param s1 the first socket - * \param s2 the second socket - * \return true if s1 < s2 - */ -inline bool operator<(const Socket &s1, const Socket &s2) -{ - return s1.handle() < s2.handle(); -} - -/** - * Compare two sockets. - * - * \param s1 the first socket - * \param s2 the second socket - * \return true if s1 > s2 - */ -inline bool operator>(const Socket &s1, const Socket &s2) -{ - return s1.handle() > s2.handle(); -} - -/** - * Compare two sockets. - * - * \param s1 the first socket - * \param s2 the second socket - * \return true if s1 <= s2 - */ -inline bool operator<=(const Socket &s1, const Socket &s2) -{ - return s1.handle() <= s2.handle(); -} - -/** - * Compare two sockets. - * - * \param s1 the first socket - * \param s2 the second socket - * \return true if s1 >= s2 - */ -inline bool operator>=(const Socket &s1, const Socket &s2) -{ - return s1.handle() >= s2.handle(); -} - -/** - * \brief Clear TCP implementation. - * \ingroup net-module-tcp - * - * This is the basic TCP protocol that implements recv, send, connect and accept - * as wrappers of the usual C functions. - */ -class TcpSocket : public Socket { -public: - /** - * Inherited constructors. - */ - using Socket::Socket; - - /** - * Construct a TCP socket. - * - * \param domain the domain - * \param protocol the protocol - * \throw Error on errors - */ - inline TcpSocket(int domain, int protocol) - : Socket(domain, SOCK_STREAM, protocol) - { - } - - /** - * Get the type of the socket. - * - * \return the type - */ - inline int type() const noexcept - { - return SOCK_STREAM; - } - - /** - * Initiate connection. - * - * \param address the address - * \param length the address length - * \throw WouldBlockError if the socket is marked non-blocking and - * connection cannot be established immediately - * \throw Error on other errors - */ - void connect(const sockaddr *address, socklen_t length) - { - if (::connect(this->m_handle, address, length) == Failure) { -#if defined(_WIN32) - int error = WSAGetLastError(); - - if (error == WSAEWOULDBLOCK) - throw WouldBlockError(); - else - throw Error(error); -#else - if (errno == EINPROGRESS) - throw WouldBlockError(); - else - throw Error(); -#endif - } - } - - /** - * Overloaded function. - * - * \param address the address - * \throw WouldBlockError if the socket is marked non-blocking and - * connection cannot be established immediately - * \throw Error on other errors - */ - void connect(const Address &address) - { - connect(address.get(), address.length()); - } - - /** - * Accept a new client. - * - * If there are no pending connection, an invalid socket is returned. - * - * \return the new socket - * \throw WouldBlockError if the socket is marked non-blocking and no - * connection are available - * \throw Error on other errors - */ - TcpSocket accept() - { - Handle handle = ::accept(this->m_handle, nullptr, 0); - - if (handle == Invalid) { -#if defined(_WIN32) - int error = WSAGetLastError(); - - if (error == WSAEWOULDBLOCK) - throw WouldBlockError(); - else - throw Error(error); -#else - if (errno == EWOULDBLOCK || errno == EAGAIN) - throw WouldBlockError(); - else - throw Error(); -#endif - } - - return TcpSocket(handle); - } - - /** - * Receive some data. - * - * \param data the destination buffer - * \param length the buffer length - * \return the number of bytes received - */ - unsigned recv(void *data, unsigned length) - { - int max = length > INT_MAX ? INT_MAX : static_cast<int>(length); - int nbread = ::recv(this->m_handle, (Arg)data, max, 0); - - if (nbread == Failure) { -#if defined(_WIN32) - int error = WSAGetLastError(); - - if (error == WSAEWOULDBLOCK) - throw WouldBlockError(); - else - throw Error(error); -#else - if (errno == EAGAIN || errno == EWOULDBLOCK) - throw WouldBlockError(); - else - throw Error(); -#endif - } - - return static_cast<unsigned>(nbread); - } - - /** - * Send some data. - * - * \param data the data to send - * \param length the length - * \return the number of bytes sent - */ - unsigned send(const void *data, unsigned length) - { - int max = length > INT_MAX ? INT_MAX : static_cast<int>(length); - int nbsent = ::send(this->m_handle, (ConstArg)data, max, 0); - - if (nbsent == Failure) { -#if defined(_WIN32) - int error = WSAGetLastError(); - - if (error == WSAEWOULDBLOCK) - throw WouldBlockError(); - else - throw Error(); -#else - if (errno == EAGAIN || errno == EWOULDBLOCK) - throw WouldBlockError(); - else - throw Error(); -#endif - } - - return static_cast<unsigned>(nbsent); - } -}; - -/** - * \brief Clear UDP type. - * - * This class is the basic implementation of UDP sockets. - */ -class UdpSocket : public Socket { -public: - /** - * Inherited constructors. - */ - using Socket::Socket; - - /** - * Construct a TCP socket. - * - * \param domain the domain - * \param protocol the protocol - * \throw Error on errors - */ - inline UdpSocket(int domain, int protocol) - : Socket(domain, SOCK_DGRAM, protocol) - { - } - - /** - * Get the type of the socket. - * - * \return the type - */ - inline int type() const noexcept - { - return SOCK_DGRAM; - } - - /** - * Receive some data. - * - * \param data the data - * \param length the length - * \param address the source address - * \param addrlen the source address in/out length - * \return the number of bytes received - * \throw WouldBlockError if the socket is marked non-blocking and the - * operation would block - * \throw Error on other errors - */ - unsigned recvfrom(void *data, unsigned length, sockaddr *address, socklen_t *addrlen) - { - int max = length > INT_MAX ? INT_MAX : static_cast<int>(length); - int nbread = ::recvfrom(this->m_handle, (Arg)data, max, 0, address, addrlen); - - if (nbread == Failure) { -#if defined(_WIN32) - int error = WSAGetLastError(); - - if (error == EWOULDBLOCK) - throw WouldBlockError(); - else - throw Error(error); -#else - if (errno == EAGAIN || errno == EWOULDBLOCK) - throw WouldBlockError(); - else - throw Error(); -#endif - } - - return static_cast<unsigned>(nbread); - } - - /** - * Overloaded function. - * - * \param data the data - * \param length the length - * \param source the source information (optional) - * \throw WouldBlockError if the socket is marked non-blocking and the - * operation would block - * \throw Error on other errors - */ - inline unsigned recvfrom(void *data, unsigned length, Address *source = nullptr) - { - sockaddr_storage st; - socklen_t socklen = sizeof (sockaddr_storage); - - auto nr = recvfrom(data, length, reinterpret_cast<sockaddr *>(&st), &socklen); - - if (source) - *source = Address(reinterpret_cast<const sockaddr *>(&st), socklen); - - return nr; - } - - /** - * Send some data. - * - * \param data the data to send - * \param length the data length - * \param address the destination address - * \param addrlen the destination address length - * \return the number of bytes sent - * \throw WouldBlockError if the socket is marked non-blocking and the - * operation would block - * \throw Error on other errors - */ - unsigned sendto(const void *data, unsigned length, const sockaddr *address, socklen_t addrlen) - { - int max = length > INT_MAX ? INT_MAX : static_cast<int>(length); - int nbsent = ::sendto(this->m_handle, (ConstArg)data, max, 0, address, addrlen); - - if (nbsent == Failure) { -#if defined(_WIN32) - int error = WSAGetLastError(); - - if (error == EWOULDBLOCK) - throw WouldBlockError(); - else - throw Error(error); -#else - if (errno == EAGAIN || errno == EWOULDBLOCK) - throw WouldBlockError(); - else - throw Error(); -#endif - } - - return static_cast<unsigned>(nbsent); - } - - /** - * Overloaded function - * - * \param data the data to send - * \param length the data length - * \param address the destination address - * \return the number of bytes sent - * \throw WouldBlockError if the socket is marked non-blocking and the - * operation would block - * \throw Error on other errors - */ - inline unsigned sendto(const void *data, unsigned length, const Address &address) - { - return sendto(data, length, address.get(), address.length()); - } -}; - -#if !defined(NET_NO_SSL) - -/** - * \brief Experimental TLS support. - * \ingroup net-module-tls - * \warning This class is highly experimental. - */ -class TlsSocket : public Socket { -public: - /** - * \brief SSL connection mode. - */ - enum Mode { - Server, //!< Use Server when you accept a socket server side, - Client //!< Use Client when you connect to a server. - }; - -private: - using Context = std::shared_ptr<SSL_CTX>; - using Ssl = std::unique_ptr<SSL, void (*)(SSL *)>; - - // Determine if we created a TlsSocket from a temporary or a lvalue. - bool m_mustclose{false}; - - Context m_context; - Ssl m_ssl{nullptr, nullptr}; - - inline std::string error() - { - BIO *bio = BIO_new(BIO_s_mem()); - char *buf = nullptr; - - ERR_print_errors(bio); - - std::size_t length = BIO_get_mem_data (bio, &buf); - std::string result(buf, length); - - BIO_free(bio); - - return result; - } - - template <typename Function> - void wrap(Function &&function) - { - auto ret = function(); - - if (ret <= 0) { - int no = SSL_get_error(m_ssl.get(), ret); - - switch (no) { - case SSL_ERROR_WANT_READ: - throw WantReadError(); - case SSL_ERROR_WANT_WRITE: - throw WantWriteError(); - default: - throw Error(error()); - } - } - } - - void create(Mode mode, const SSL_METHOD *method) - { -#if !defined(NET_NO_SSL_AUTO_INIT) - ssl::init(); -#endif - m_context = Context(SSL_CTX_new(method), SSL_CTX_free); - m_ssl = Ssl(SSL_new(m_context.get()), SSL_free); - - SSL_set_fd(m_ssl.get(), this->m_handle); - - if (mode == Server) - SSL_set_accept_state(m_ssl.get()); - else - SSL_set_connect_state(m_ssl.get()); - } - -public: - /** - * Create a socket around an existing one. - * - * The original socket is moved to this instance and must not be used - * anymore. - * - * \param sock the TCP socket - * \param mode the mode - * \param method the method - */ - TlsSocket(TcpSocket &&sock, Mode mode = Server, const SSL_METHOD *method = NET_SSL_DEFAULT_METHOD()) - : Socket(std::move(sock)) - , m_mustclose(true) - { - create(mode, method); - } - - /** - * Wrap a socket around an existing one without taking ownership. - * - * The original socket must still exist until this TlsSocket is closed. - * - * \param sock the TCP socket - * \param mode the mode - * \param method the method - */ - TlsSocket(TcpSocket &sock, Mode mode = Server, const SSL_METHOD *method = NET_SSL_DEFAULT_METHOD()) - : Socket(sock.handle()) - { - create(mode, method); - } - - /** - * Destroy the socket if owned. - */ - ~TlsSocket() - { - /** - * If the socket has been created from a rvalue this class owns the - * socket and will close it in the parent destructor. - * - * Otherwise, when created from a lvalue, mark this socket as invalid - * to avoid double close'ing it as two sockets points to the same - * descriptor. - */ - if (!m_mustclose) - m_handle = Invalid; - } - - /** - * Get the type of socket. - * - * \return the type - */ - inline int type() const noexcept - { - return SOCK_STREAM; - } - - /** - * Use the specified private key file. - * - * \param file the path to the private key - * \param type the type of file - */ - inline void setPrivateKey(std::string file, int type = SSL_FILETYPE_PEM) - { - if (SSL_use_PrivateKey_file(m_ssl.get(), file.c_str(), type) != 1) - throw Error(error()); - } - - /** - * Use the specified certificate file. - * - * \param file the path to the file - * \param type the type of file - */ - inline void setCertificate(std::string file, int type = SSL_FILETYPE_PEM) - { - if (SSL_use_certificate_file(m_ssl.get(), file.c_str(), type) != 1) - throw Error(error()); - } - - /** - * Do handshake, needed in some case when you have non blocking sockets. - */ - void handshake() - { - wrap([this] () -> int { - return SSL_do_handshake(m_ssl.get()); - }); - } - - /** - * Receive some data. - * - * \param data the destination buffer - * \param length the buffer length - * \return the number of bytes received - */ - unsigned recv(void *data, unsigned length) - { - int max = length > INT_MAX ? INT_MAX : static_cast<int>(length); - int nbread = 0; - - wrap([&] () -> int { - return nbread = SSL_read(m_ssl.get(), data, max); - }); - - return static_cast<unsigned>(nbread < 0 ? 0 : nbread); - } - - /** - * Send some data. - * - * \param data the data to send - * \param length the length - * \return the number of bytes sent - */ - unsigned send(const void *data, unsigned length) - { - int max = length > INT_MAX ? INT_MAX : static_cast<int>(length); - int nbsent = 0; - - wrap([&] () -> int { - return nbsent = SSL_write(m_ssl.get(), data, max); - }); - - return static_cast<unsigned>(nbsent < 0 ? 0 : nbsent); - } -}; - -#endif // !NET_NO_SSL - -/** - * \brief IPv4 functions. - */ -namespace ipv4 { - -/** - * Create an address to bind on any. - * - * \param port the port - * \return the address - */ -inline Address any(std::uint16_t port) -{ - sockaddr_in sin; - socklen_t length = sizeof (sockaddr_in); - - std::memset(&sin, 0, sizeof (sockaddr_in)); - sin.sin_family = AF_INET; - sin.sin_addr.s_addr = INADDR_ANY; - sin.sin_port = htons(port); - - return Address(reinterpret_cast<const sockaddr *>(&sin), length); -} - -/** - * Create an address from a IPv4 string. - * - * \param ip the ip address - * \param port the port - * \return the address - * \throw Error if inet_pton is unavailable - */ -inline Address pton(const std::string &ip, std::uint16_t port) -{ -#if defined(NET_HAVE_INET_PTON) -#if !defined(NET_NO_AUTO_INIT) - init(); -#endif - - sockaddr_in sin; - socklen_t length = sizeof (sockaddr_in); - - std::memset(&sin, 0, sizeof (sockaddr_in)); - sin.sin_family = AF_INET; - sin.sin_port = htons(port); - - if (inet_pton(AF_INET, ip.c_str(), &sin.sin_addr) <= 0) - throw Error(); - - return Address(reinterpret_cast<const sockaddr *>(&sin), length); -#else - (void)ip; - (void)port; - - throw Error(std::strerror(ENOSYS)); -#endif -} - -/** - * Get the underlying ip from the given address. - * - * \pre address.domain() == AF_INET - * \param address the IPv6 address - * \return the ip address - * \throw Error if inet_ntop is unavailable - */ -inline std::string ntop(const Address &address) -{ - assert(address.domain() == AF_INET); - -#if !defined(NET_NO_AUTO_INIT) - init(); -#endif - -#if defined(NET_HAVE_INET_NTOP) - char result[INET_ADDRSTRLEN + 1]; - - std::memset(result, 0, sizeof (result)); - - if (!inet_ntop(AF_INET, const_cast<in_addr *>(&address.as<sockaddr_in>().sin_addr), result, sizeof (result))) - throw Error(); - - return result; -#else - (void)address; - - throw Error(std::strerror(ENOSYS)); -#endif -} - -/** - * Get the port from the IPv4 address. - * - * \pre address.domain() == AF_INET4 - * \param address the address - * \return the port - */ -inline std::uint16_t port(const Address &address) noexcept -{ - assert(address.domain() == AF_INET); - - return ntohs(address.as<sockaddr_in>().sin_port); -} - -} // !ipv4 - -/** - * \brief IPv6 functions. - */ -namespace ipv6 { - -/** - * Create an address to bind on any. - * - * \param port the port - * \return the address - */ -inline Address any(std::uint16_t port) -{ - sockaddr_in6 sin6; - socklen_t length = sizeof (sockaddr_in6); - - std::memset(&sin6, 0, sizeof (sockaddr_in6)); - sin6.sin6_family = AF_INET6; - sin6.sin6_addr = in6addr_any; - sin6.sin6_port = htons(port); - - return Address(reinterpret_cast<const sockaddr *>(&sin6), length); -} - -/** - * Create an address from a IPv4 string. - * - * \param ip the ip address - * \param port the port - * \return the address - * \throw Error if inet_pton is unavailable - */ -inline Address pton(const std::string &ip, std::uint16_t port) -{ -#if defined(NET_HAVE_INET_PTON) -#if !defined(NET_NO_AUTO_INIT) - init(); -#endif - - sockaddr_in6 sin6; - socklen_t length = sizeof (sockaddr_in6); - - std::memset(&sin6, 0, sizeof (sockaddr_in6)); - sin6.sin6_family = AF_INET6; - sin6.sin6_port = htons(port); - - if (inet_pton(AF_INET6, ip.c_str(), &sin6.sin6_addr) <= 0) - throw Error(); - - return Address(reinterpret_cast<const sockaddr *>(&sin6), length); -#else - (void)ip; - (void)port; - - throw Error(std::strerror(ENOSYS)); -#endif -} - -/** - * Get the underlying ip from the given address. - * - * \pre address.domain() == AF_INET6 - * \param address the IPv6 address - * \return the ip address - * \throw Error if inet_ntop is unavailable - */ -inline std::string ntop(const Address &address) -{ - assert(address.domain() == AF_INET6); - -#if defined(NET_HAVE_INET_NTOP) -#if !defined(NET_NO_AUTO_INIT) - init(); -#endif - - char ret[INET6_ADDRSTRLEN]; - - std::memset(ret, 0, sizeof (ret)); - - if (!inet_ntop(AF_INET6, const_cast<in6_addr *>(&address.as<sockaddr_in6>().sin6_addr), ret, sizeof (ret))) - throw Error(); - - return ret; -#else - (void)address; - - throw Error(std::strerror(ENOSYS)); -#endif -} - -/** - * Get the port from the IPv6 address. - * - * \pre address.domain() == AF_INET6 - * \param address the address - * \return the port - */ -inline std::uint16_t port(const Address &address) noexcept -{ - assert(address.domain() == AF_INET6); - - return ntohs(address.as<sockaddr_in6>().sin6_port); -} - -} // !ipv6 - -#if !defined(_WIN32) - -/** - * \brief Unix domain functions. - */ -namespace local { - -/** - * Construct an address to a path. - * - * \pre !path.empty() - * \param path the path - * \param rm remove the file before (default: false) - */ -inline Address create(const std::string &path, bool rm = false) noexcept -{ - assert(!path.empty()); - - // Silently remove the file even if it fails. - if (rm) - remove(path.c_str()); - - sockaddr_un sun; - socklen_t length; - - std::memset(sun.sun_path, 0, sizeof (sun.sun_path)); - std::strncpy(sun.sun_path, path.c_str(), sizeof (sun.sun_path) - 1); - - sun.sun_family = AF_LOCAL; - -#if defined(NET_HAVE_SUN_LEN) - length = SUN_LEN(&sun); -#else - length = sizeof (sun); -#endif - - return Address(reinterpret_cast<const sockaddr *>(&sun), length); -} - -/** - * Get the path from the address. - * - * \pre address.domain() == AF_LOCAL - * \param address the local address - * \return the path to the socket file - */ -inline std::string path(const Address &address) -{ - assert(address.domain() == AF_LOCAL); - - return reinterpret_cast<const sockaddr_un *>(address.get())->sun_path; -} - -} // !local - -#endif // !_WIN32 - -/** - * \brief Predefined options. - */ -namespace option { - -/** - * \ingroup net-module-options - * \brief Set or get the blocking-mode for a socket. - * \warning On Windows, it's not possible to check if the socket is blocking or - * not. - */ -class SockBlockMode { -private: - bool m_value; - -public: - /** - * Create the option. - * - * By default the blocking mode is set to true. - * - * \param value set to true to make blocking sockets - */ - inline SockBlockMode(bool value = true) noexcept - : m_value(value) - { - } - - /** - * Set the option. - * - * \param sc the socket - * \throw Error on errors - */ - void set(Socket &sc) const - { -#if defined(O_NONBLOCK) && !defined(_WIN32) - int flags; - - if ((flags = fcntl(sc.handle(), F_GETFL, 0)) < 0) - flags = 0; - - if (m_value) - flags &= ~(O_NONBLOCK); - else - flags |= O_NONBLOCK; - - if (fcntl(sc.handle(), F_SETFL, flags) < 0) - throw Error(); -#else - unsigned long flags = (m_value) ? 0 : 1; - - if (ioctlsocket(sc.handle(), FIONBIO, &flags) == Failure) - throw Error(); -#endif - } - - /** - * Get the option. - * - * \param sc the socket - * \return the value - * \throw Error on errors - */ - bool get(Socket &sc) const - { -#if defined(O_NONBLOCK) && !defined(_WIN32) - int flags = fcntl(sc.handle(), F_GETFL, 0); - - if (flags < 0) - throw Error(); - - return !(flags & O_NONBLOCK); -#else - (void)sc; - - throw Error(std::strerror(ENOSYS)); -#endif - } -}; - -/** - * \ingroup net-module-options - * \brief Set or get the input buffer. - */ -class SockReceiveBuffer { -private: - int m_value; - -public: - /** - * Create the option. - * - * \param size the buffer size - */ - inline SockReceiveBuffer(int size = 2048) noexcept - : m_value(size) - { - } - - /** - * Set the option. - * - * \param sc the socket - * \throw Error on errors - */ - inline void set(Socket &sc) const - { - sc.set(SOL_SOCKET, SO_RCVBUF, m_value); - } - - /** - * Get the option. - * - * \param sc the socket - * \return the value - * \throw Error on errors - */ - inline int get(Socket &sc) const - { - return sc.get<int>(SOL_SOCKET, SO_RCVBUF); - } -}; - -/** - * \ingroup net-module-options - * \brief Reuse address, must be used before calling Socket::bind - */ -class SockReuseAddress { -private: - bool m_value; - -public: - /** - * Create the option. - * - * By default the option reuses the address. - * - * \param value set to true to reuse the address - */ - inline SockReuseAddress(bool value = true) noexcept - : m_value(value) - { - } - - /** - * Set the option. - * - * \param sc the socket - * \throw Error on errors - */ - inline void set(Socket &sc) const - { - sc.set(SOL_SOCKET, SO_REUSEADDR, m_value ? 1 : 0); - } - - /** - * Get the option. - * - * \param sc the socket - * \return the value - * \throw Error on errors - */ - inline bool get(Socket &sc) const - { - return sc.get<int>(SOL_SOCKET, SO_REUSEADDR) != 0; - } -}; - -/** - * \ingroup net-module-options - * \brief Set or get the output buffer. - */ -class SockSendBuffer { -private: - int m_value; - -public: - /** - * Create the option. - * - * \param size the buffer size - */ - inline SockSendBuffer(int size = 2048) noexcept - : m_value(size) - { - } - - /** - * Set the option. - * - * \param sc the socket - * \throw Error on errors - */ - inline void set(Socket &sc) const - { - sc.set(SOL_SOCKET, SO_SNDBUF, m_value); - } - - /** - * Get the option. - * - * \param sc the socket - * \return the value - * \throw Error on errors - */ - inline int get(Socket &sc) const - { - return sc.get<int>(SOL_SOCKET, SO_SNDBUF); - } -}; - -/** - * \ingroup net-module-options - * \brief Set this option if you want to disable nagle's algorithm. - */ -class TcpNoDelay { -private: - bool m_value; - -public: - /** - * Create the option. - * - * By default disable TCP delay. - * - * \param value set to true to disable TCP delay - */ - inline TcpNoDelay(bool value = true) noexcept - : m_value(value) - { - } - - /** - * Set the option. - * - * \param sc the socket - * \throw Error on errors - */ - inline void set(Socket &sc) const - { - sc.set(IPPROTO_TCP, TCP_NODELAY, m_value ? 1 : 0); - } - - /** - * Get the option. - * - * \param sc the socket - * \return the value - * \throw Error on errors - */ - inline bool get(Socket &sc) const - { - return sc.get<int>(IPPROTO_TCP, TCP_NODELAY) != 0; - } -}; - -/** - * \ingroup net-module-options - * \brief Control IPPROTO_IPV6/IPV6_V6ONLY - * - * Note: some systems may or not set this option by default so it's a good idea - * to set it in any case to either - * false or true if portability is a concern. - */ -class Ipv6Only { -private: - bool m_value; - -public: - /** - * Create the option. - * - * By default with want IPv6 only. - * - * \param value set to true to use IPv6 only - */ - inline Ipv6Only(bool value = true) noexcept - : m_value(value) - { - } - - /** - * Set the option. - * - * \param sc the socket - * \throw Error on errors - */ - inline void set(Socket &sc) const - { - sc.set(IPPROTO_IPV6, IPV6_V6ONLY, m_value ? 1 : 0); - } - - /** - * Get the option. - * - * \param sc the socket - * \return the value - * \throw Error on errors - */ - inline bool get(Socket &sc) const - { - return sc.get<int>(IPPROTO_IPV6, IPV6_V6ONLY) != 0; - } -}; - -} // !option - -/** - * \brief Result of polling - * - * Result of a select call, returns the first ready socket found with its - * flags. - */ -class ListenerStatus { -public: - Handle socket; //!< which socket is ready - Condition flags; //!< the flags -}; - -/** - * Table used in the socket listener to store which sockets have been - * set in which directions. - */ -using ListenerTable = std::unordered_map<Handle, Condition>; - -/** - * \brief Predefined backends for Listener. - */ -namespace backend { - -#if defined(NET_HAVE_EPOLL) - -/** - * \ingroup net-module-backends - * \brief Linux's epoll. - */ -class Epoll { -private: - int m_handle{-1}; - std::vector<epoll_event> m_events; - - Epoll(const Epoll &) = delete; - Epoll &operator=(const Epoll &) = delete; - - std::uint32_t toEpoll(Condition condition) const noexcept - { - std::uint32_t events = 0; - - if ((condition & Condition::Readable) == Condition::Readable) - events |= EPOLLIN; - if ((condition & Condition::Writable) == Condition::Writable) - events |= EPOLLOUT; - - return events; - } - - Condition toCondition(std::uint32_t events) const noexcept - { - Condition condition = Condition::None; - - if ((events & EPOLLIN) || (events & EPOLLHUP)) - condition |= Condition::Readable; - if (events & EPOLLOUT) - condition |= Condition::Writable; - - return condition; - } - - void update(Handle h, int op, int eflags) - { - epoll_event ev; - - std::memset(&ev, 0, sizeof (epoll_event)); - - ev.events = eflags; - ev.data.fd = h; - - if (epoll_ctl(m_handle, op, h, &ev) < 0) - throw Error(); - } - -public: - /** - * Create epoll. - * - * \throw Error on failures - */ - inline Epoll() - : m_handle(epoll_create1(0)) - { - if (m_handle < 0) - throw Error(); - } - - /** - * Move constructor. - * - * \param other the other backend - */ - inline Epoll(Epoll &&other) noexcept - : m_handle(other.m_handle) - { - other.m_handle = -1; - } - - /** - * Close the kqueue descriptor. - */ - inline ~Epoll() - { - if (m_handle != -1) - close(m_handle); - } - - /** - * Get the backend name. - * - * \return kqueue - */ - inline std::string name() const noexcept - { - return "epoll"; - } - - /** - * For set and unset, we need to apply the whole flags required, so if the - * socket was set to Connection::Readable and user **adds** - * Connection::Writable, we must set both. - * - * \param table the listener table - * \param h the handle - * \param condition the condition - * \param add set to true if the socket is new to the backend - * \throw Error on failures - */ - void set(const ListenerTable &table, Handle h, Condition condition, bool add) - { - if (add) { - update(h, EPOLL_CTL_ADD, toEpoll(condition)); - m_events.resize(m_events.size() + 1); - } else - update(h, EPOLL_CTL_MOD, toEpoll(table.at(h) | condition)); - } - - /** - * Unset is a bit complicated case because Listener tells us which - * flag to remove but to update epoll descriptor we need to pass - * the effective flags that we want to be applied. - * - * So we put the same flags that are currently effective and remove the - * requested one. - * - * \param table the listener table - * \param h the handle - * \param condition the condition - * \param add set to true if the socket is new to the backend - * \throw Error on failures - */ - void unset(const ListenerTable &table, Handle h, Condition condition, bool remove) - { - if (remove) { - update(h, EPOLL_CTL_DEL, 0); - m_events.resize(m_events.size() - 1); - } else - update(h, EPOLL_CTL_MOD, toEpoll(table.at(h) & ~(condition))); - } - - /** - * Wait for sockets to be ready. - * - * \param ms the milliseconds timeout - * \return the sockets ready - * \throw Error on failures - */ - std::vector<ListenerStatus> wait(const ListenerTable &, int ms) - { - int ret = epoll_wait(m_handle, m_events.data(), m_events.size(), ms); - std::vector<ListenerStatus> result; - - if (ret == 0) - throw TimeoutError(); - if (ret < 0) - throw Error(); - - for (int i = 0; i < ret; ++i) - result.push_back(ListenerStatus{m_events[i].data.fd, toCondition(m_events[i].events)}); - - return result; - } - - /** - * Move operator. - * - * \param other the other - * \return this - */ - inline Epoll &operator=(Epoll &&other) - { - m_handle = other.m_handle; - other.m_handle = -1; - - return *this; - } -}; - -#endif // !NET_HAVE_EPOLL - -#if defined(NET_HAVE_KQUEUE) - -/** - * \ingroup net-module-backends - * \brief Implements kqueue(2). - * - * This implementation is available on all BSD and Mac OS X. It is better than - * poll(2) because it's O(1), however it's a bit more memory consuming. - */ -class Kqueue { -private: - std::vector<struct kevent> m_result; - int m_handle; - - Kqueue(const Kqueue &) = delete; - Kqueue &operator=(const Kqueue &) = delete; - - void update(Handle h, int filter, int kflags) - { - struct kevent ev; - - EV_SET(&ev, h, filter, kflags, 0, 0, nullptr); - - if (kevent(m_handle, &ev, 1, nullptr, 0, nullptr) < 0) - throw Error(); - } - -public: - /** - * Create kqueue. - * - * \throw Error on failures - */ - inline Kqueue() - : m_handle(kqueue()) - { - if (m_handle < 0) - throw Error(); - } - - /** - * Move constructor. - * - * \param other the other backend - */ - inline Kqueue(Kqueue &&other) noexcept - : m_handle(other.m_handle) - { - other.m_handle = -1; - } - - /** - * Close the kqueue descriptor. - */ - inline ~Kqueue() - { - if (m_handle != -1) - close(m_handle); - } - - /** - * Get the backend name. - * - * \return kqueue - */ - inline std::string name() const noexcept - { - return "kqueue"; - } - - /** - * Set socket. - * - * \param h the handle - * \param condition the condition - * \param add set to true if the socket is new to the backend - * \throw Error on failures - */ - void set(const ListenerTable &, Handle h, Condition condition, bool add) - { - if ((condition & Condition::Readable) == Condition::Readable) - update(h, EVFILT_READ, EV_ADD | EV_ENABLE); - if ((condition & Condition::Writable) == Condition::Writable) - update(h, EVFILT_WRITE, EV_ADD | EV_ENABLE); - if (add) - m_result.resize(m_result.size() + 1); - } - - /** - * Unset socket. - * - * \param h the handle - * \param condition the condition - * \param remove set to true if the socket is completely removed - * \throw Error on failures - */ - void unset(const ListenerTable &, Handle h, Condition condition, bool remove) - { - if ((condition & Condition::Readable) == Condition::Readable) - update(h, EVFILT_READ, EV_DELETE); - if ((condition & Condition::Writable) == Condition::Writable) - update(h, EVFILT_WRITE, EV_DELETE); - if (remove) - m_result.resize(m_result.size() - 1); - } - - /** - * Wait for sockets to be ready. - * - * \param ms the milliseconds timeout - * \return the sockets ready - * \throw Error on failures - */ - std::vector<ListenerStatus> wait(const ListenerTable &, int ms) - { - std::vector<ListenerStatus> sockets; - timespec ts = { 0, 0 }; - timespec *pts = (ms <= 0) ? nullptr : &ts; - - ts.tv_sec = ms / 1000; - ts.tv_nsec = (ms % 1000) * 1000000; - - int nevents = kevent(m_handle, nullptr, 0, &m_result[0], m_result.capacity(), pts); - - if (nevents == 0) - throw TimeoutError(); - if (nevents < 0) - throw Error(); - - for (int i = 0; i < nevents; ++i) { - sockets.push_back(ListenerStatus{ - static_cast<Handle>(m_result[i].ident), - m_result[i].filter == EVFILT_READ ? Condition::Readable : Condition::Writable - }); - } - - return sockets; - } - - /** - * Move operator. - * - * \param other the other - * \return this - */ - inline Kqueue &operator=(Kqueue &&other) noexcept - { - m_handle = other.m_handle; - other.m_handle = -1; - - return *this; - } -}; - -#endif // !NET_HAVE_KQUEUE - -#if defined(NET_HAVE_POLL) - -/** - * \ingroup net-module-backends - * \brief Implements poll(2). - * - * Poll is widely supported and is better than select(2). It is still not the - * best option as selecting the sockets is O(n). - */ -class Poll { -private: - std::vector<pollfd> m_fds; - - short toPoll(Condition condition) const noexcept - { - short result = 0; - - if ((condition & Condition::Readable) == Condition::Readable) - result |= POLLIN; - if ((condition & Condition::Writable) == Condition::Writable) - result |= POLLOUT; - - return result; - } - - Condition toCondition(short &event) const noexcept - { - Condition condition = Condition::None; - - /* - * Poll implementations mark the socket differently regarding the - * disconnection of a socket. - * - * At least, even if POLLHUP or POLLIN is set, recv() always return 0 so - * we mark the socket as readable. - */ - if ((event & POLLIN) || (event & POLLHUP)) - condition |= Condition::Readable; - if (event & POLLOUT) - condition |= Condition::Writable; - - // Reset event for safety. - event = 0; - - return condition; - } - -public: - /** - * Get the backend name. - * - * \return kqueue - */ - inline std::string name() const noexcept - { - return "poll"; - } - - /** - * Set socket. - * - * \param h the handle - * \param condition the condition - * \param add set to true if the socket is new to the backend - * \throw Error on failures - */ - void set(const ListenerTable &, Handle h, Condition condition, bool add) - { - if (add) - m_fds.push_back(pollfd{h, toPoll(condition), 0}); - else { - auto it = std::find_if(m_fds.begin(), m_fds.end(), [&] (const pollfd &pfd) { - return pfd.fd == h; - }); - - it->events |= toPoll(condition); - } - } - - /** - * Unset socket. - * - * \param h the handle - * \param condition the condition - * \param remove set to true if the socket is completely removed - * \throw Error on failures - */ - void unset(const ListenerTable &, Handle h, Condition condition, bool remove) - { - auto it = std::find_if(m_fds.begin(), m_fds.end(), [&] (const pollfd &pfd) { - return pfd.fd == h; - }); - - if (remove) - m_fds.erase(it); - else - it->events &= ~(toPoll(condition)); - } - - /** - * Wait for sockets to be ready. - * - * \param ms the milliseconds timeout - * \return the sockets ready - * \throw Error on failures - */ - std::vector<ListenerStatus> wait(const ListenerTable &, int ms) - { -#if defined(_WIN32) - auto result = WSAPoll(m_fds.data(), (ULONG)m_fds.size(), ms); -#else - auto result = poll(m_fds.data(), m_fds.size(), ms); -#endif - - if (result == 0) - throw TimeoutError(); - if (result < 0) - throw Error(); - - std::vector<ListenerStatus> sockets; - - for (auto &fd : m_fds) - if (fd.revents != 0) - sockets.push_back(ListenerStatus{fd.fd, toCondition(fd.revents)}); - - return sockets; - } -}; - -#endif // !NET_HAVE_POLL - -/** - * \ingroup net-module-backends - * \brief Implements select(2) - * - * This class is the fallback of any other method, it is not preferred at all - * for many reasons. - */ -class Select { -public: - /** - * Get the backend name. - * - * \return select - */ - inline std::string name() const - { - return "select"; - } - - /** - * No-op. - */ - inline void set(const ListenerTable &, Handle, Condition, bool) noexcept - { - } - - /** - * No-op. - */ - inline void unset(const ListenerTable &, Handle, Condition, bool) noexcept - { - } - - /** - * Wait for sockets to be ready. - * - * \param table the listener table - * \param ms the milliseconds timeout - * \return the sockets ready - * \throw Error on failures - */ - std::vector<ListenerStatus> wait(const ListenerTable &table, int ms) - { - timeval maxwait, *towait; - fd_set readset; - fd_set writeset; - - FD_ZERO(&readset); - FD_ZERO(&writeset); - - Handle max = 0; - - for (const auto &pair : table) { - if ((pair.second & Condition::Readable) == Condition::Readable) - FD_SET(pair.first, &readset); - if ((pair.second & Condition::Writable) == Condition::Writable) - FD_SET(pair.first, &writeset); - if (pair.first > max) - max = pair.first; - } - - maxwait.tv_sec = 0; - maxwait.tv_usec = ms * 1000; - - // Set to nullptr for infinite timeout. - towait = (ms < 0) ? nullptr : &maxwait; - - auto error = ::select(static_cast<int>(max + 1), &readset, &writeset, nullptr, towait); - - if (error == Failure) - throw Error(); - if (error == 0) - throw TimeoutError(); - - std::vector<ListenerStatus> sockets; - - for (const auto &pair : table) { - if (FD_ISSET(pair.first, &readset)) - sockets.push_back(ListenerStatus{pair.first, Condition::Readable}); - if (FD_ISSET(pair.first, &writeset)) - sockets.push_back(ListenerStatus{pair.first, Condition::Writable}); - } - - return sockets; - } -}; - -} // !backend - -/** - * \brief Synchronous multiplexing - * - * Convenient wrapper around the select() system call. - * - * This class is implemented using a bridge pattern to allow different uses - * of listener implementation. - * - * You should not reinstanciate a new Listener at each iteartion of your - * main loop as it can be extremely costly. Instead use the same listener that - * you can safely modify on the fly. - * - * Currently, poll, epoll, select and kqueue are available. - */ -template <typename Backend = backend :: NET_DEFAULT_BACKEND> -class Listener { -private: - Backend m_backend; - ListenerTable m_table; - -public: - /** - * Construct an empty listener. - */ - Listener() = default; - - /** - * Get the backend. - * - * \return the backend - */ - inline const Backend &backend() const noexcept - { - return m_backend; - } - - /** - * 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. - * - * \return the number of sockets - */ - inline ListenerTable::size_type size() const noexcept - { - return m_table.size(); - } - - /** - * Select a socket. Waits for a specific amount of time specified as the - * duration. - * - * \param duration the duration - * \return the socket ready - */ - template <typename Rep, typename Ratio> - inline ListenerStatus wait(const std::chrono::duration<Rep, Ratio> &duration) - { - auto cvt = std::chrono::duration_cast<std::chrono::milliseconds>(duration); - auto max = cvt.count() > INT_MAX ? INT_MAX : static_cast<int>(cvt.count()); - - return m_backend.wait(m_table, max)[0]; - } - - /** - * Overload with milliseconds. - * - * \param timeout the optional timeout in milliseconds - * \return the socket ready - */ - inline ListenerStatus wait(long long int timeout = -1) - { - return wait(std::chrono::milliseconds(timeout)); - } - - /** - * Select multiple sockets. - * - * \param duration the duration - * \return the socket ready - */ - template <typename Rep, typename Ratio> - inline std::vector<ListenerStatus> waitMultiple(const std::chrono::duration<Rep, Ratio> &duration) - { - auto cvt = std::chrono::duration_cast<std::chrono::milliseconds>(duration); - - return m_backend.wait(m_table, cvt.count()); - } - - /** - * Overload with milliseconds. - * - * \param timeout the optional timeout in milliseconds - * \return the socket ready - */ - inline std::vector<ListenerStatus> waitMultiple(int timeout = -1) - { - return waitMultiple(std::chrono::milliseconds(timeout)); - } -}; - -/** - * \ingroup net-module-resolv - * - * Resolve an hostname immediately. - * - * \param host the hostname - * \param service the service (e.g. http or port number) - * \param domain the domain (e.g. AF_INET) - * \param type the type (e.g. SOCK_STREAM) - * \return the address iterator - * \throw Error on failures - */ -inline AddressIterator resolve(const std::string &host, - const std::string &service, - int domain = AF_UNSPEC, - int type = 0) -{ -#if !defined(NET_NO_AUTO_INIT) - init(); -#endif - - struct addrinfo hints, *res, *p; - - std::memset(&hints, 0, sizeof (hints)); - hints.ai_family = domain; - hints.ai_socktype = type; - - int e = getaddrinfo(host.c_str(), service.c_str(), &hints, &res); - - if (e != 0) - throw Error(gai_strerror(e)); - - std::vector<Address> addresses; - - for (p = res; p != nullptr; p = p->ai_next) - addresses.push_back(Address(p->ai_addr, p->ai_addrlen)); - - return AddressIterator(addresses, 0); -} - -/** - * \ingroup net-module-resolv - * - * Resolve the first address. - * - * \param host the hostname - * \param service the service name - * \param domain the domain (e.g. AF_INET) - * \param type the type (e.g. SOCK_STREAM) - * \return the first generic address available - * \throw Error on failures - * \note do not use AF_UNSPEC and 0 as type for this function - */ -inline Address resolveOne(const std::string &host, const std::string &service, int domain, int type) -{ - AddressIterator it = resolve(host, service, domain, type); - AddressIterator end; - - if (it == end) - throw Error("no address available"); - - return *it; -} - -} // !net - -} // !irccd - -#endif // !IRCCD_NET_HPP
--- a/libcommon/irccd/net_util.hpp Fri Nov 24 20:05:15 2017 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,141 +0,0 @@ -/* - * net_util.hpp -- network utilities for pollable objects - * - * Copyright (c) 2013-2017 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 IRCCD_COMMON_NET_UTIL_HPP -#define IRCCD_COMMON_NET_UTIL_HPP - -/** - * \file net_util.hpp - * \brief Network utilities for pollable objects. - */ - -#include "net.hpp" - -namespace irccd { - -/** - * \brief Miscellaneous utilities for Pollable objects - */ -namespace net_util { - -/** - * \cond HIDDEN_SYMBOLS - */ - -inline void prepare(fd_set &, fd_set &, net::Handle &) noexcept -{ -} - -/** - * \endcond - */ - -/** - * Call prepare function for every Pollable objects. - * - * \param in the input set - * \param out the output set - * \param max the maximum handle - * \param first the first Pollable object - * \param rest the additional Pollable objects - */ -template <typename Pollable, typename... Rest> -inline void prepare(fd_set &in, fd_set &out, net::Handle &max, Pollable &first, Rest&... rest) -{ - first.prepare(in, out, max); - prepare(in, out, max, rest...); -} - -/** - * \cond HIDDEN_SYMBOLS - */ - -inline void sync(fd_set &, fd_set &) noexcept -{ -} - -/** - * \endcond - */ - -/** - * Call sync function for every Pollable objects. - * - * \param in the input set - * \param out the output set - * \param first the first Pollable object - * \param rest the additional Pollable objects - */ -template <typename Pollable, typename... Rest> -inline void sync(fd_set &in, fd_set &out, Pollable &first, Rest&... rest) -{ - first.sync(in, out); - sync(in, out, rest...); -} - -/** - * Prepare and sync Pollable objects. - * - * \param timeout the timeout in milliseconds (< 0 means forever) - * \param first the the first Pollable object - * \param rest the additional Pollable objects - */ -template <typename Pollable, typename... Rest> -void poll(int timeout, Pollable &first, Rest&... rest) -{ - fd_set in, out; - timeval tv = { timeout / 1000, (timeout % 1000) * 1000 }; - - FD_ZERO(&in); - FD_ZERO(&out); - - net::Handle max = 0; - - prepare(in, out, max, first, rest...); - - if (select(max + 1, &in, &out, nullptr, timeout < 0 ? nullptr : &tv) < 0 && errno != EINTR) { - throw std::runtime_error(std::strerror(errno)); - } else { - sync(in, out, first, rest...); - } -} - -/** - * Parse a network message from an input buffer and remove it from it. - * - * \param input the buffer, will be updated - * \return the message or empty string if there is nothing - */ -inline std::string next_network(std::string& input) -{ - std::string result; - std::string::size_type pos = input.find("\r\n\r\n"); - - if ((pos = input.find("\r\n\r\n")) != std::string::npos) { - result = input.substr(0, pos); - input.erase(input.begin(), input.begin() + pos + 4); - } - - return result; -} - -} // !net_util - -} // !irccd - -#endif // !IRCCD_COMMON_NET_UTIL_HPP
--- a/libirccd-js/irccd/js/js_plugin.cpp Fri Nov 24 20:05:15 2017 +0100 +++ b/libirccd-js/irccd/js/js_plugin.cpp Fri Nov 24 20:09:45 2017 +0100 @@ -1,5 +1,5 @@ /* - * plugin-js.cpp -- Javascript plugins for irccd + * js_plugin.cpp -- Javascript plugins for irccd * * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr> * @@ -24,12 +24,8 @@ #include "irccd.hpp" #include "logger.hpp" -//#include "plugin_module.hpp" -//#include "server_module.hpp" #include "js_plugin.hpp" -#include "service.hpp" #include "server_jsapi.hpp" -//#include "timer.hpp" namespace irccd {
--- a/libirccd-js/irccd/js/plugin_jsapi.cpp Fri Nov 24 20:05:15 2017 +0100 +++ b/libirccd-js/irccd/js/plugin_jsapi.cpp Fri Nov 24 20:09:45 2017 +0100 @@ -17,7 +17,7 @@ */ #include <irccd/irccd.hpp> -#include <irccd/service.hpp> +#include <irccd/plugin_service.hpp> #include "irccd_jsapi.hpp" #include "js_plugin.hpp"
--- a/libirccd-js/irccd/js/server_jsapi.cpp Fri Nov 24 20:05:15 2017 +0100 +++ b/libirccd-js/irccd/js/server_jsapi.cpp Fri Nov 24 20:09:45 2017 +0100 @@ -21,8 +21,7 @@ #include <unordered_map> #include <irccd.hpp> -#include <irccd/server.hpp> -#include <irccd/service.hpp> +#include <irccd/server_service.hpp> #include "irccd_jsapi.hpp" #include "js_plugin.hpp"
--- a/libirccd-test/irccd/plugin_test.cpp Fri Nov 24 20:05:15 2017 +0100 +++ b/libirccd-test/irccd/plugin_test.cpp Fri Nov 24 20:09:45 2017 +0100 @@ -19,7 +19,8 @@ #include <cassert> #include <irccd/logger.hpp> -#include <irccd/service.hpp> +#include <irccd/plugin_service.hpp> +#include <irccd/server_service.hpp> #include <irccd/js/directory_jsapi.hpp> #include <irccd/js/elapsed_timer_jsapi.hpp>
--- a/libirccd/CMakeLists.txt Fri Nov 24 20:05:15 2017 +0100 +++ b/libirccd/CMakeLists.txt Fri Nov 24 20:09:45 2017 +0100 @@ -24,14 +24,17 @@ HEADERS ${libirccd_SOURCE_DIR}/irccd/basic_transport_client.hpp ${libirccd_SOURCE_DIR}/irccd/command.hpp + ${libirccd_SOURCE_DIR}/irccd/command_service.hpp ${libirccd_SOURCE_DIR}/irccd/config.hpp ${libirccd_SOURCE_DIR}/irccd/dynlib_plugin.hpp ${libirccd_SOURCE_DIR}/irccd/irc.hpp ${libirccd_SOURCE_DIR}/irccd/irccd.hpp ${libirccd_SOURCE_DIR}/irccd/plugin.hpp + ${libirccd_SOURCE_DIR}/irccd/plugin_service.hpp ${libirccd_SOURCE_DIR}/irccd/rule.hpp + ${libirccd_SOURCE_DIR}/irccd/rule_service.hpp ${libirccd_SOURCE_DIR}/irccd/server.hpp - ${libirccd_SOURCE_DIR}/irccd/service.hpp + ${libirccd_SOURCE_DIR}/irccd/server_service.hpp ${libirccd_SOURCE_DIR}/irccd/transport_client.hpp ${libirccd_SOURCE_DIR}/irccd/transport_server.hpp ${libirccd_SOURCE_DIR}/irccd/transport_service.hpp @@ -40,14 +43,17 @@ set( SOURCES ${libirccd_SOURCE_DIR}/irccd/command.cpp + ${libirccd_SOURCE_DIR}/irccd/command_service.cpp ${libirccd_SOURCE_DIR}/irccd/config.cpp ${libirccd_SOURCE_DIR}/irccd/dynlib_plugin.cpp ${libirccd_SOURCE_DIR}/irccd/irc.cpp ${libirccd_SOURCE_DIR}/irccd/irccd.cpp ${libirccd_SOURCE_DIR}/irccd/plugin.cpp + ${libirccd_SOURCE_DIR}/irccd/plugin_service.cpp ${libirccd_SOURCE_DIR}/irccd/rule.cpp + ${libirccd_SOURCE_DIR}/irccd/rule_service.cpp ${libirccd_SOURCE_DIR}/irccd/server.cpp - ${libirccd_SOURCE_DIR}/irccd/service.cpp + ${libirccd_SOURCE_DIR}/irccd/server_service.cpp ${libirccd_SOURCE_DIR}/irccd/transport_client.cpp ${libirccd_SOURCE_DIR}/irccd/transport_server.cpp ${libirccd_SOURCE_DIR}/irccd/transport_service.cpp
--- a/libirccd/irccd/command.cpp Fri Nov 24 20:05:15 2017 +0100 +++ b/libirccd/irccd/command.cpp Fri Nov 24 20:09:45 2017 +0100 @@ -19,7 +19,9 @@ #include "command.hpp" #include "irccd.hpp" #include "json_util.hpp" -#include "service.hpp" +#include "rule_service.hpp" +#include "plugin_service.hpp" +#include "server_service.hpp" #include "transport_client.hpp" #include "util.hpp"
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libirccd/irccd/command_service.cpp Fri Nov 24 20:09:45 2017 +0100 @@ -0,0 +1,49 @@ +/* + * command_service.cpp -- command service + * + * Copyright (c) 2013-2017 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 "command_service.hpp" + +namespace irccd { + +bool command_service::contains(const std::string& name) const noexcept +{ + return find(name) != nullptr; +} + +std::shared_ptr<command> command_service::find(const std::string& name) const noexcept +{ + auto it = std::find_if(commands_.begin(), commands_.end(), [&] (const auto& cmd) { + return cmd->name() == name; + }); + + return it == commands_.end() ? nullptr : *it; +} + +void command_service::add(std::shared_ptr<command> command) +{ + auto it = std::find_if(commands_.begin(), commands_.end(), [&] (const auto& cmd) { + return cmd->name() == command->name(); + }); + + if (it != commands_.end()) + *it = std::move(command); + else + commands_.push_back(std::move(command)); +} + +} // !irccd
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libirccd/irccd/command_service.hpp Fri Nov 24 20:09:45 2017 +0100 @@ -0,0 +1,80 @@ +/* + * command_service.hpp -- command service + * + * Copyright (c) 2013-2017 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 IRCCD_COMMAND_SERVICE_HPP +#define IRCCD_COMMAND_SERVICE_HPP + +/** + * \file command_service.hpp + * \brief Command service. + */ + +#include <memory> +#include <vector> + +#include "command.hpp" + +namespace irccd { + +/** + * \brief Store remote commands. + * \ingroup services + */ +class command_service { +private: + std::vector<std::shared_ptr<command>> commands_; + +public: + /** + * Get all commands. + * + * \return the list of commands. + */ + inline const std::vector<std::shared_ptr<command>>& commands() const noexcept + { + return commands_; + } + + /** + * Tells if a command exists. + * + * \param name the command name + * \return true if the command exists + */ + bool contains(const std::string& name) const noexcept; + + /** + * Find a command by name. + * + * \param name the command name + * \return the command or empty one if not found + */ + std::shared_ptr<command> find(const std::string& name) const noexcept; + + /** + * Add a command or replace existing one. + * + * \pre command != nullptr + * \param command the command name + */ + void add(std::shared_ptr<command> command); +}; + +} // !irccd + +#endif // !IRCCD_COMMAND_SERVICE_HPP
--- a/libirccd/irccd/config.cpp Fri Nov 24 20:05:15 2017 +0100 +++ b/libirccd/irccd/config.cpp Fri Nov 24 20:09:45 2017 +0100 @@ -23,10 +23,9 @@ #include "config.hpp" #include "irccd.hpp" #include "logger.hpp" -#include "plugin.hpp" +#include "plugin_service.hpp" #include "rule.hpp" #include "server.hpp" -#include "service.hpp" #include "string_util.hpp" #include "sysconfig.hpp" #include "system.hpp"
--- a/libirccd/irccd/irccd.cpp Fri Nov 24 20:05:15 2017 +0100 +++ b/libirccd/irccd/irccd.cpp Fri Nov 24 20:09:45 2017 +0100 @@ -16,12 +16,12 @@ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ +#include "command_service.hpp" #include "irccd.hpp" -#include "logger.hpp" -#include "net_util.hpp" +#include "plugin_service.hpp" +#include "rule_service.hpp" +#include "server_service.hpp" #include "transport_service.hpp" -#include "service.hpp" -#include "util.hpp" namespace irccd { @@ -29,7 +29,6 @@ : config_(std::move(config)) , service_(service) , command_service_(std::make_unique<command_service>()) - , itr_service_(std::make_unique<interrupt_service>()) , server_service_(std::make_unique<server_service>(*this)) , tpt_service_(std::make_unique<transport_service>(*this)) , rule_service_(std::make_unique<rule_service>()) @@ -39,70 +38,4 @@ irccd::~irccd() = default; -void irccd::post(std::function<void (irccd&)> ev) noexcept -{ - std::lock_guard<std::mutex> lock(mutex_); - - events_.push_back(std::move(ev)); - itr_service_->interrupt(); -} - -void irccd::run() -{ - for (;;) - service_.run(); - -#if 0 - while (running_) { - net_util::poll(100, *this); - service_.poll(); - } -#endif -} - -void irccd::prepare(fd_set&, fd_set&, net::Handle&) -{ -} - -void irccd::sync(fd_set& , fd_set& ) -{ -#if 0 - if (!running_) - return; - - net_util::sync(in, out, *itr_service_, *server_service_); - - if (!running_) - return; - - /* - * Make a copy because the events can add other events while we are - * iterating it. Also lock because the timers may alter these events too. - */ - std::vector<std::function<void (irccd&)>> copy; - - { - std::lock_guard<std::mutex> lock(mutex_); - - copy = std::move(events_); - events_.clear(); - } - - if (copy.size() > 0) - log::debug() << "irccd: dispatching " << copy.size() << " event" - << (copy.size() > 1 ? "s" : "") << std::endl; - - for (auto& ev : copy) - ev(*this); -#endif -} - -void irccd::stop() -{ - log::debug() << "irccd: requesting to stop now" << std::endl; - - running_ = false; - itr_service_->interrupt(); -} - } // !irccd
--- a/libirccd/irccd/irccd.hpp Fri Nov 24 20:05:15 2017 +0100 +++ b/libirccd/irccd/irccd.hpp Fri Nov 24 20:09:45 2017 +0100 @@ -24,17 +24,13 @@ * \brief Base class for irccd front end. */ -#include <atomic> -#include <functional> +#include "sysconfig.hpp" + #include <memory> -#include <mutex> -#include <vector> #include <boost/asio.hpp> #include "config.hpp" -#include "net.hpp" -#include "sysconfig.hpp" /** * \brief Main irccd namespace @@ -42,7 +38,6 @@ namespace irccd { class command_service; -class interrupt_service; class plugin_service; class rule_service; class server_service; @@ -59,14 +54,8 @@ // Main io service. boost::asio::io_service& service_; - // Main loop stuff. - std::atomic<bool> running_{true}; - std::mutex mutex_; - std::vector<std::function<void (irccd&)>> events_; - // Services. std::shared_ptr<command_service> command_service_; - std::shared_ptr<interrupt_service> itr_service_; std::shared_ptr<server_service> server_service_; std::shared_ptr<transport_service> tpt_service_; std::shared_ptr<rule_service> rule_service_; @@ -184,40 +173,18 @@ } /** - * Prepare the services for selection. + * Transient function for posting handlers. * - * \param in the input set - * \param out the output set - * \param max the maximum handle - */ - void prepare(fd_set& in, fd_set& out, net::Handle& max); - - /** - * Synchronize the services. - * - * \param in the input set - * \param out the output set + * \param h the handler + * \deprecated do not use */ - void sync(fd_set& in, fd_set& out); - - /** - * Add an event to the queue. This will immediately signals the event loop - * to interrupt itself to dispatch the pending events. - * - * \param ev the event - * \note Thread-safe - */ - void post(std::function<void (irccd&)> ev) noexcept; - - /** - * Loop forever by calling prepare and sync indefinitely. - */ - void run(); - - /** - * Request to stop, usually from a signal. - */ - void stop(); + template <typename Handler> + inline void post(Handler h) + { + service_.post([this, h] () { + h(*this); + }); + } }; } // !irccd
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libirccd/irccd/plugin_service.cpp Fri Nov 24 20:09:45 2017 +0100 @@ -0,0 +1,198 @@ +/* + * plugin_service.cpp -- plugin service + * + * Copyright (c) 2013-2017 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 "config.hpp" +#include "irccd.hpp" +#include "logger.hpp" +#include "plugin_service.hpp" +#include "string_util.hpp" +#include "system.hpp" + +namespace irccd { + +namespace { + +template <typename Map> +Map to_map(const config& conf, const std::string& section) +{ + Map ret; + + for (const auto& opt : conf.doc().get(section)) + ret.emplace(opt.key(), opt.value()); + + return ret; +} + +} // !namespace + +plugin_service::plugin_service(irccd& irccd) noexcept + : irccd_(irccd) +{ +} + +plugin_service::~plugin_service() +{ + for (const auto& plugin : plugins_) + plugin->on_unload(irccd_); +} + +bool plugin_service::has(const std::string& name) const noexcept +{ + return std::count_if(plugins_.cbegin(), plugins_.cend(), [&] (const auto& plugin) { + return plugin->name() == name; + }) > 0; +} + +std::shared_ptr<plugin> plugin_service::get(const std::string& name) const noexcept +{ + auto it = std::find_if(plugins_.begin(), plugins_.end(), [&] (const auto& plugin) { + return plugin->name() == name; + }); + + if (it == plugins_.end()) + return nullptr; + + return *it; +} + +std::shared_ptr<plugin> plugin_service::require(const std::string& name) const +{ + auto plugin = get(name); + + if (!plugin) + throw std::invalid_argument(string_util::sprintf("plugin %s not found", name)); + + return plugin; +} + +void plugin_service::add(std::shared_ptr<plugin> plugin) +{ + plugins_.push_back(std::move(plugin)); +} + +void plugin_service::add_loader(std::unique_ptr<plugin_loader> loader) +{ + loaders_.push_back(std::move(loader)); +} + +plugin_config plugin_service::config(const std::string& id) +{ + return to_map<plugin_config>(irccd_.config(), string_util::sprintf("plugin.%s", id)); +} + +plugin_formats plugin_service::formats(const std::string& id) +{ + return to_map<plugin_formats>(irccd_.config(), string_util::sprintf("format.%s", id)); +} + +plugin_paths plugin_service::paths(const std::string& id) +{ + auto defaults = to_map<plugin_paths>(irccd_.config(), "paths"); + auto paths = to_map<plugin_paths>(irccd_.config(), string_util::sprintf("paths.%s", id)); + + // Fill defaults paths. + if (!defaults.count("cache")) + defaults.emplace("cache", sys::cachedir() + "/plugin/" + id); + if (!defaults.count("data")) + paths.emplace("data", sys::datadir() + "/plugin/" + id); + if (!defaults.count("config")) + paths.emplace("config", sys::sysconfigdir() + "/plugin/" + id); + + // Now fill missing fields. + if (!paths.count("cache")) + paths.emplace("cache", defaults["cache"]); + if (!paths.count("data")) + paths.emplace("data", defaults["data"]); + if (!paths.count("config")) + paths.emplace("config", defaults["config"]); + + return paths; +} + +std::shared_ptr<plugin> plugin_service::open(const std::string& id, + const std::string& path) +{ + for (const auto& loader : loaders_) { + auto plugin = loader->open(id, path); + + if (plugin) + return plugin; + } + + return nullptr; +} + +std::shared_ptr<plugin> plugin_service::find(const std::string& id) +{ + for (const auto& loader : loaders_) { + auto plugin = loader->find(id); + + if (plugin) + return plugin; + } + + return nullptr; +} + +void plugin_service::load(std::string name, std::string path) +{ + if (has(name)) + return; + + try { + std::shared_ptr<plugin> plugin; + + if (path.empty()) + plugin = find(name); + else + plugin = open(name, std::move(path)); + + if (plugin) { + plugin->set_config(config(name)); + plugin->set_formats(formats(name)); + plugin->set_paths(paths(name)); + plugin->on_load(irccd_); + + add(std::move(plugin)); + } + } catch (const std::exception& ex) { + log::warning(string_util::sprintf("plugin %s: %s", name, ex.what())); + } +} + +void plugin_service::reload(const std::string& name) +{ + auto plugin = get(name); + + if (plugin) + plugin->on_reload(irccd_); +} + +void plugin_service::unload(const std::string& name) +{ + auto it = std::find_if(plugins_.begin(), plugins_.end(), [&] (const auto& plugin) { + return plugin->name() == name; + }); + + if (it != plugins_.end()) { + (*it)->on_unload(irccd_); + plugins_.erase(it); + } +} + +} // !irccd
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libirccd/irccd/plugin_service.hpp Fri Nov 24 20:09:45 2017 +0100 @@ -0,0 +1,184 @@ +/* + * plugin_service.hpp -- plugin service + * + * Copyright (c) 2013-2017 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 IRCCD_PLUGIN_SERVICE_HPP +#define IRCCD_PLUGIN_SERVICE_HPP + +/** + * \file plugin_service.hpp + * \brief Plugin service. + */ + +#include <memory> +#include <string> +#include <vector> + +#include "plugin.hpp" + +namespace irccd { + +class irccd; + +/** + * \brief Manage plugins. + * \ingroup services + */ +class plugin_service { +private: + irccd& irccd_; + std::vector<std::shared_ptr<plugin>> plugins_; + std::vector<std::unique_ptr<plugin_loader>> loaders_; + +public: + /** + * Create the plugin service. + * + * \param irccd the irccd instance + */ + plugin_service(irccd& irccd) noexcept; + + /** + * Destroy plugins. + */ + ~plugin_service(); + + /** + * Get the list of plugins. + * + * \return the list of plugins + */ + inline const std::vector<std::shared_ptr<plugin>>& list() const noexcept + { + return plugins_; + } + + /** + * Check if a plugin is loaded. + * + * \param name the plugin id + * \return true if has plugin + */ + bool has(const std::string& name) const noexcept; + + /** + * Get a loaded plugin or null if not found. + * + * \param name the plugin id + * \return the plugin or empty one if not found + */ + std::shared_ptr<plugin> get(const std::string& name) const noexcept; + + /** + * Find a loaded plugin. + * + * \param name the plugin id + * \return the plugin + * \throws std::out_of_range if not found + */ + std::shared_ptr<plugin> require(const std::string& name) const; + + /** + * Add the specified plugin to the registry. + * + * \pre plugin != nullptr + * \param plugin the plugin + * \note the plugin is only added to the list, no action is performed on it + */ + void add(std::shared_ptr<plugin> plugin); + + /** + * Add a loader. + * + * \param loader the loader + */ + void add_loader(std::unique_ptr<plugin_loader> loader); + + /** + * Get the configuration for the specified plugin. + * + * \return the configuration + */ + plugin_config config(const std::string& id); + + /** + * Get the formats for the specified plugin. + * + * \return the formats + */ + plugin_formats formats(const std::string& id); + + /** + * Get the paths for the specified plugin. + * + * If none is defined, return the default ones. + * + * \return the paths + */ + plugin_paths paths(const std::string& id); + + /** + * Generic function for opening the plugin at the given path. + * + * This function will search for every pluginLoader and call open() on it, + * the first one that success will be returned. + * + * \param id the plugin id + * \param path the path to the file + * \return the plugin or nullptr on failures + */ + std::shared_ptr<plugin> open(const std::string& id, + const std::string& path); + + /** + * Generic function for finding a plugin. + * + * \param id the plugin id + * \return the plugin or nullptr on failures + */ + std::shared_ptr<plugin> find(const std::string& id); + + /** + * Convenient wrapper that loads a plugin, call onLoad and add it to the + * registry. + * + * Any errors are printed using logger. + * + * \param name the name + * \param path the optional path (searched if empty) + */ + void load(std::string name, std::string path = ""); + + /** + * Unload a plugin and remove it. + * + * \param name the plugin id + */ + void unload(const std::string& name); + + /** + * Reload a plugin by calling onReload. + * + * \param name the plugin name + * \throw std::exception on failures + */ + void reload(const std::string& name); +}; + +} // !irccd + +#endif // !IRCCD_PLUGIN_SERVICE_HPP
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libirccd/irccd/rule_service.cpp Fri Nov 24 20:09:45 2017 +0100 @@ -0,0 +1,88 @@ +/* + * rule_service.cpp -- rule service + * + * Copyright (c) 2013-2017 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 "logger.hpp" +#include "rule_service.hpp" +#include "string_util.hpp" + +namespace irccd { + +void rule_service::add(rule rule) +{ + rules_.push_back(std::move(rule)); +} + +void rule_service::insert(rule rule, unsigned position) +{ + assert(position <= rules_.size()); + + rules_.insert(rules_.begin() + position, std::move(rule)); +} + +void rule_service::remove(unsigned position) +{ + assert(position < rules_.size()); + + rules_.erase(rules_.begin() + position); +} + +const rule &rule_service::require(unsigned position) const +{ + if (position >= rules_.size()) + throw std::out_of_range("rule " + std::to_string(position) + " does not exist"); + + return rules_[position]; +} + +rule &rule_service::require(unsigned position) +{ + if (position >= rules_.size()) + throw std::out_of_range("rule " + std::to_string(position) + " does not exist"); + + return rules_[position]; +} + +bool rule_service::solve(const std::string& server, + const std::string& channel, + const std::string& origin, + const std::string& plugin, + const std::string& event) noexcept +{ + bool result = true; + + log::debug(string_util::sprintf("rule: solving for server=%s, channel=%s, origin=%s, plugin=%s, event=%s", + server, channel, origin, plugin, event)); + + int i = 0; + for (const auto& rule : rules_) { + log::debug() << " candidate " << i++ << ":\n" + << " servers: " << string_util::join(rule.servers().begin(), rule.servers().end()) << "\n" + << " channels: " << string_util::join(rule.channels().begin(), rule.channels().end()) << "\n" + << " origins: " << string_util::join(rule.origins().begin(), rule.origins().end()) << "\n" + << " plugins: " << string_util::join(rule.plugins().begin(), rule.plugins().end()) << "\n" + << " events: " << string_util::join(rule.events().begin(), rule.events().end()) << "\n" + << " action: " << ((rule.action() == rule::action_type::accept) ? "accept" : "drop") << std::endl; + + if (rule.match(server, channel, origin, plugin, event)) + result = rule.action() == rule::action_type::accept; + } + + return result; +} + +} // !irccd
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libirccd/irccd/rule_service.hpp Fri Nov 24 20:09:45 2017 +0100 @@ -0,0 +1,120 @@ +/* + * rule_service.hpp -- rule service + * + * Copyright (c) 2013-2017 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 IRCCD_RULE_SERVICE_HPP +#define IRCCD_RULE_SERVICE_HPP + +/** + * \file rule_service.hpp + * \brief Rule service. + */ + +#include <vector> + +#include "rule.hpp" + +namespace irccd { + +/** + * \brief Store and solve rules. + * \ingroup services + */ +class rule_service { +private: + std::vector<rule> rules_; + +public: + /** + * Get the list of rules. + * + * \return the list of rules + */ + inline const std::vector<rule>& list() const noexcept + { + return rules_; + } + + /** + * Get the number of rules. + * + * \return the number of rules + */ + inline std::size_t length() const noexcept + { + return rules_.size(); + } + + /** + * Append a rule. + * + * \param rule the rule to append + */ + void add(rule rule); + + /** + * Insert a new rule at the specified position. + * + * \param rule the rule + * \param position the position + */ + void insert(rule rule, unsigned position); + + /** + * Remove a new rule from the specified position. + * + * \pre position must be valid + * \param position the position + */ + void remove(unsigned position); + + /** + * Get a rule at the specified index or throw an exception if not found. + * + * \param position the position + * \return the rule + * \throw std::out_of_range if position is invalid + */ + const rule& require(unsigned position) const; + + /** + * Overloaded function. + * + * \copydoc require + */ + rule& require(unsigned position); + + /** + * Resolve the action to execute with the specified list of rules. + * + * \param server the server name + * \param channel the channel name + * \param origin the origin + * \param plugin the plugin name + * \param event the event name (e.g onKick) + * \return true if the plugin must be called + */ + bool solve(const std::string& server, + const std::string& channel, + const std::string& origin, + const std::string& plugin, + const std::string& event) noexcept; +}; + +} // !irccd + +#endif // !IRCCD_RULE_SERVICE_HPP
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libirccd/irccd/server_service.cpp Fri Nov 24 20:09:45 2017 +0100 @@ -0,0 +1,568 @@ +/* + * server_service.hpp -- server service + * + * Copyright (c) 2013-2017 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 "irccd.hpp" +#include "logger.hpp" +#include "plugin_service.hpp" +#include "rule_service.hpp" +#include "server_service.hpp" +#include "string_util.hpp" +#include "transport_service.hpp" + +namespace irccd { + +class event_handler { +public: + std::string server; + std::string origin; + std::string target; + std::function<std::string (plugin &)> function_name; + std::function<void (plugin &)> function_exec; + + void operator()(irccd& irccd) const + { + for (auto& plugin : irccd.plugins().list()) { + auto eventname = function_name(*plugin); + auto allowed = irccd.rules().solve(server, target, origin, plugin->name(), eventname); + + if (!allowed) { + log::debug() << "rule: event skipped on match" << std::endl; + continue; + } + + log::debug() << "rule: event allowed" << std::endl; + + // TODO: this is the responsability of plugin_service. + try { + function_exec(*plugin); + } catch (const std::exception& ex) { + log::warning() << "plugin " << plugin->name() << ": error: " << ex.what() << std::endl; + } + } + } +}; + +void server_service::handle_channel_mode(const channel_mode_event& ev) +{ + log::debug() << "server " << ev.server->name() << ": event onChannelMode:\n"; + log::debug() << " origin: " << ev.origin << "\n"; + log::debug() << " channel: " << ev.channel << "\n"; + log::debug() << " mode: " << ev.mode << "\n"; + log::debug() << " argument: " << ev.argument << std::endl; + + irccd_.transports().broadcast(nlohmann::json::object({ + { "event", "onChannelMode" }, + { "server", ev.server->name() }, + { "origin", ev.origin }, + { "channel", ev.channel }, + { "mode", ev.mode }, + { "argument", ev.argument } + })); + + irccd_.post(event_handler{ev.server->name(), ev.origin, ev.channel, + [=] (plugin&) -> std::string { + return "onChannelMode"; + }, + [=] (plugin& plugin) { + plugin.on_channel_mode(irccd_, ev); + } + }); +} + +void server_service::handle_channel_notice(const channel_notice_event& ev) +{ + log::debug() << "server " << ev.server->name() << ": event onChannelNotice:\n"; + log::debug() << " origin: " << ev.origin << "\n"; + log::debug() << " channel: " << ev.channel << "\n"; + log::debug() << " message: " << ev.message << std::endl; + + irccd_.transports().broadcast(nlohmann::json::object({ + { "event", "onChannelNotice" }, + { "server", ev.server->name() }, + { "origin", ev.origin }, + { "channel", ev.channel }, + { "message", ev.message } + })); + + irccd_.post(event_handler{ev.server->name(), ev.origin, ev.channel, + [=] (plugin&) -> std::string { + return "onChannelNotice"; + }, + [=] (plugin& plugin) { + plugin.on_channel_notice(irccd_, ev); + } + }); +} + +void server_service::handle_connect(const connect_event& ev) +{ + log::debug() << "server " << ev.server->name() << ": event onConnect" << std::endl; + + irccd_.transports().broadcast(nlohmann::json::object({ + { "event", "onConnect" }, + { "server", ev.server->name() } + })); + + irccd_.post(event_handler{ev.server->name(), /* origin */ "", /* channel */ "", + [=] (plugin&) -> std::string { + return "onConnect"; + }, + [=] (plugin& plugin) { + plugin.on_connect(irccd_, ev); + } + }); +} + +void server_service::handle_invite(const invite_event& ev) +{ + log::debug() << "server " << ev.server->name() << ": event onInvite:\n"; + log::debug() << " origin: " << ev.origin << "\n"; + log::debug() << " channel: " << ev.channel << "\n"; + log::debug() << " target: " << ev.nickname << std::endl; + + irccd_.transports().broadcast(nlohmann::json::object({ + { "event", "onInvite" }, + { "server", ev.server->name() }, + { "origin", ev.origin }, + { "channel", ev.channel } + })); + + irccd_.post(event_handler{ev.server->name(), ev.origin, ev.channel, + [=] (plugin&) -> std::string { + return "onInvite"; + }, + [=] (plugin& plugin) { + plugin.on_invite(irccd_, ev); + } + }); +} + +void server_service::handle_join(const join_event& ev) +{ + log::debug() << "server " << ev.server->name() << ": event onJoin:\n"; + log::debug() << " origin: " << ev.origin << "\n"; + log::debug() << " channel: " << ev.channel << std::endl; + + irccd_.transports().broadcast(nlohmann::json::object({ + { "event", "onJoin" }, + { "server", ev.server->name() }, + { "origin", ev.origin }, + { "channel", ev.channel } + })); + + irccd_.post(event_handler{ev.server->name(), ev.origin, ev.channel, + [=] (plugin&) -> std::string { + return "onJoin"; + }, + [=] (plugin& plugin) { + plugin.on_join(irccd_, ev); + } + }); +} + +void server_service::handle_kick(const kick_event& ev) +{ + log::debug() << "server " << ev.server->name() << ": event onKick:\n"; + log::debug() << " origin: " << ev.origin << "\n"; + log::debug() << " channel: " << ev.channel << "\n"; + log::debug() << " target: " << ev.target << "\n"; + log::debug() << " reason: " << ev.reason << std::endl; + + irccd_.transports().broadcast(nlohmann::json::object({ + { "event", "onKick" }, + { "server", ev.server->name() }, + { "origin", ev.origin }, + { "channel", ev.channel }, + { "target", ev.target }, + { "reason", ev.reason } + })); + + irccd_.post(event_handler{ev.server->name(), ev.origin, ev.channel, + [=] (plugin&) -> std::string { + return "onKick"; + }, + [=] (plugin& plugin) { + plugin.on_kick(irccd_, ev); + } + }); +} + +void server_service::handle_message(const message_event& ev) +{ + log::debug() << "server " << ev.server->name() << ": event onMessage:\n"; + log::debug() << " origin: " << ev.origin << "\n"; + log::debug() << " channel: " << ev.channel << "\n"; + log::debug() << " message: " << ev.message << std::endl; + + irccd_.transports().broadcast(nlohmann::json::object({ + { "event", "onMessage" }, + { "server", ev.server->name() }, + { "origin", ev.origin }, + { "channel", ev.channel }, + { "message", ev.message } + })); + + irccd_.post(event_handler{ev.server->name(), ev.origin, ev.channel, + [=] (plugin& plugin) -> std::string { + return string_util::parse_message( + ev.message, + ev.server->command_char(), + plugin.name() + ).type == string_util::message_pack::type::command ? "onCommand" : "onMessage"; + }, + [=] (plugin& plugin) mutable { + auto copy = ev; + auto pack = string_util::parse_message(copy.message, copy.server->command_char(), plugin.name()); + + copy.message = pack.message; + + if (pack.type == string_util::message_pack::type::command) + plugin.on_command(irccd_, copy); + else + plugin.on_message(irccd_, copy); + } + }); +} + +void server_service::handle_me(const me_event& ev) +{ + log::debug() << "server " << ev.server->name() << ": event onMe:\n"; + log::debug() << " origin: " << ev.origin << "\n"; + log::debug() << " target: " << ev.channel << "\n"; + log::debug() << " message: " << ev.message << std::endl; + + irccd_.transports().broadcast(nlohmann::json::object({ + { "event", "onMe" }, + { "server", ev.server->name() }, + { "origin", ev.origin }, + { "target", ev.channel }, + { "message", ev.message } + })); + + irccd_.post(event_handler{ev.server->name(), ev.origin, ev.channel, + [=] (plugin&) -> std::string { + return "onMe"; + }, + [=] (plugin& plugin) { + plugin.on_me(irccd_, ev); + } + }); +} + +void server_service::handle_mode(const mode_event& ev) +{ + log::debug() << "server " << ev.server->name() << ": event onMode\n"; + log::debug() << " origin: " << ev.origin << "\n"; + log::debug() << " mode: " << ev.mode << std::endl; + + irccd_.transports().broadcast(nlohmann::json::object({ + { "event", "onMode" }, + { "server", ev.server->name() }, + { "origin", ev.origin }, + { "mode", ev.mode } + })); + + irccd_.post(event_handler{ev.server->name(), ev.origin, /* channel */ "", + [=] (plugin &) -> std::string { + return "onMode"; + }, + [=] (plugin &plugin) { + plugin.on_mode(irccd_, ev); + } + }); +} + +void server_service::handle_names(const names_event& ev) +{ + log::debug() << "server " << ev.server->name() << ": event onNames:\n"; + log::debug() << " channel: " << ev.channel << "\n"; + log::debug() << " names: " << string_util::join(ev.names.begin(), ev.names.end(), ", ") << std::endl; + + auto names = nlohmann::json::array(); + + for (const auto& v : ev.names) + names.push_back(v); + + irccd_.transports().broadcast(nlohmann::json::object({ + { "event", "onNames" }, + { "server", ev.server->name() }, + { "channel", ev.channel }, + { "names", std::move(names) } + })); + + irccd_.post(event_handler{ev.server->name(), /* origin */ "", ev.channel, + [=] (plugin&) -> std::string { + return "onNames"; + }, + [=] (plugin& plugin) { + plugin.on_names(irccd_, ev); + } + }); +} + +void server_service::handle_nick(const nick_event& ev) +{ + log::debug() << "server " << ev.server->name() << ": event onNick:\n"; + log::debug() << " origin: " << ev.origin << "\n"; + log::debug() << " nickname: " << ev.nickname << std::endl; + + irccd_.transports().broadcast(nlohmann::json::object({ + { "event", "onNick" }, + { "server", ev.server->name() }, + { "origin", ev.origin }, + { "nickname", ev.nickname } + })); + + irccd_.post(event_handler{ev.server->name(), ev.origin, /* channel */ "", + [=] (plugin&) -> std::string { + return "onNick"; + }, + [=] (plugin& plugin) { + plugin.on_nick(irccd_, ev); + } + }); +} + +void server_service::handle_notice(const notice_event& ev) +{ + log::debug() << "server " << ev.server->name() << ": event onNotice:\n"; + log::debug() << " origin: " << ev.origin << "\n"; + log::debug() << " message: " << ev.message << std::endl; + + irccd_.transports().broadcast(nlohmann::json::object({ + { "event", "onNotice" }, + { "server", ev.server->name() }, + { "origin", ev.origin }, + { "message", ev.message } + })); + + irccd_.post(event_handler{ev.server->name(), ev.origin, /* channel */ "", + [=] (plugin&) -> std::string { + return "onNotice"; + }, + [=] (plugin& plugin) { + plugin.on_notice(irccd_, ev); + } + }); +} + +void server_service::handle_part(const part_event& ev) +{ + log::debug() << "server " << ev.server->name() << ": event onPart:\n"; + log::debug() << " origin: " << ev.origin << "\n"; + log::debug() << " channel: " << ev.channel << "\n"; + log::debug() << " reason: " << ev.reason << std::endl; + + irccd_.transports().broadcast(nlohmann::json::object({ + { "event", "onPart" }, + { "server", ev.server->name() }, + { "origin", ev.origin }, + { "channel", ev.channel }, + { "reason", ev.reason } + })); + + irccd_.post(event_handler{ev.server->name(), ev.origin, ev.channel, + [=] (plugin&) -> std::string { + return "onPart"; + }, + [=] (plugin& plugin) { + plugin.on_part(irccd_, ev); + } + }); +} + +void server_service::handle_query(const query_event& ev) +{ + log::debug() << "server " << ev.server->name() << ": event onQuery:\n"; + log::debug() << " origin: " << ev.origin << "\n"; + log::debug() << " message: " << ev.message << std::endl; + + irccd_.transports().broadcast(nlohmann::json::object({ + { "event", "onQuery" }, + { "server", ev.server->name() }, + { "origin", ev.origin }, + { "message", ev.message } + })); + + irccd_.post(event_handler{ev.server->name(), ev.origin, /* channel */ "", + [=] (plugin& plugin) -> std::string { + return string_util::parse_message( + ev.message, + ev.server->command_char(), + plugin.name() + ).type == string_util::message_pack::type::command ? "onQueryCommand" : "onQuery"; + }, + [=] (plugin& plugin) mutable { + auto copy = ev; + auto pack = string_util::parse_message(copy.message, copy.server->command_char(), plugin.name()); + + copy.message = pack.message; + + if (pack.type == string_util::message_pack::type::command) + plugin.on_query_command(irccd_, copy); + else + plugin.on_query(irccd_, copy); + } + }); +} + +void server_service::handle_topic(const topic_event& ev) +{ + log::debug() << "server " << ev.server->name() << ": event onTopic:\n"; + log::debug() << " origin: " << ev.origin << "\n"; + log::debug() << " channel: " << ev.channel << "\n"; + log::debug() << " topic: " << ev.topic << std::endl; + + irccd_.transports().broadcast(nlohmann::json::object({ + { "event", "onTopic" }, + { "server", ev.server->name() }, + { "origin", ev.origin }, + { "channel", ev.channel }, + { "topic", ev.topic } + })); + + irccd_.post(event_handler{ev.server->name(), ev.origin, ev.channel, + [=] (plugin&) -> std::string { + return "onTopic"; + }, + [=] (plugin& plugin) { + plugin.on_topic(irccd_, ev); + } + }); +} + +void server_service::handle_whois(const whois_event& ev) +{ + log::debug() << "server " << ev.server->name() << ": event onWhois\n"; + log::debug() << " nickname: " << ev.whois.nick << "\n"; + log::debug() << " username: " << ev.whois.user << "\n"; + log::debug() << " host: " << ev.whois.host << "\n"; + log::debug() << " realname: " << ev.whois.realname << "\n"; + log::debug() << " channels: " << string_util::join(ev.whois.channels.begin(), ev.whois.channels.end(), ", ") << std::endl; + + irccd_.transports().broadcast(nlohmann::json::object({ + { "event", "onWhois" }, + { "server", ev.server->name() }, + { "nickname", ev.whois.nick }, + { "username", ev.whois.user }, + { "host", ev.whois.host }, + { "realname", ev.whois.realname } + })); + + irccd_.post(event_handler{ev.server->name(), /* origin */ "", /* channel */ "", + [=] (plugin&) -> std::string { + return "onWhois"; + }, + [=] (plugin& plugin) { + plugin.on_whois(irccd_, ev); + } + }); +} + +server_service::server_service(irccd &irccd) + : irccd_(irccd) +{ +} + +bool server_service::has(const std::string& name) const noexcept +{ + return std::count_if(servers_.cbegin(), servers_.end(), [&] (const auto& server) { + return server->name() == name; + }) > 0; +} + +void server_service::add(std::shared_ptr<server> server) +{ + assert(!has(server->name())); + + std::weak_ptr<class server> ptr(server); + + server->on_channel_mode.connect(boost::bind(&server_service::handle_channel_mode, this, _1)); + server->on_channel_notice.connect(boost::bind(&server_service::handle_channel_notice, this, _1)); + server->on_connect.connect(boost::bind(&server_service::handle_connect, this, _1)); + server->on_invite.connect(boost::bind(&server_service::handle_invite, this, _1)); + server->on_join.connect(boost::bind(&server_service::handle_join, this, _1)); + server->on_kick.connect(boost::bind(&server_service::handle_kick, this, _1)); + server->on_message.connect(boost::bind(&server_service::handle_message, this, _1)); + server->on_me.connect(boost::bind(&server_service::handle_me, this, _1)); + server->on_mode.connect(boost::bind(&server_service::handle_mode, this, _1)); + server->on_names.connect(boost::bind(&server_service::handle_names, this, _1)); + server->on_nick.connect(boost::bind(&server_service::handle_nick, this, _1)); + server->on_notice.connect(boost::bind(&server_service::handle_notice, this, _1)); + server->on_part.connect(boost::bind(&server_service::handle_part, this, _1)); + server->on_query.connect(boost::bind(&server_service::handle_query, this, _1)); + server->on_topic.connect(boost::bind(&server_service::handle_topic, this, _1)); + server->on_whois.connect(boost::bind(&server_service::handle_whois, this, _1)); + server->on_die.connect([this, ptr] () { + irccd_.post([=] (irccd&) { + auto server = ptr.lock(); + + if (server) { + log::info(string_util::sprintf("server %s: removed", server->name())); + servers_.erase(std::find(servers_.begin(), servers_.end(), server)); + } + }); + }); + + server->connect(); + servers_.push_back(std::move(server)); +} + +std::shared_ptr<server> server_service::get(const std::string& name) const noexcept +{ + auto it = std::find_if(servers_.begin(), servers_.end(), [&] (const auto& server) { + return server->name() == name; + }); + + if (it == servers_.end()) + return nullptr; + + return *it; +} + +std::shared_ptr<server> server_service::require(const std::string& name) const +{ + auto server = get(name); + + if (!server) + throw std::invalid_argument(string_util::sprintf("server %s not found", name)); + + return server; +} + +void server_service::remove(const std::string& name) +{ + auto it = std::find_if(servers_.begin(), servers_.end(), [&] (const auto& server) { + return server->name() == name; + }); + + if (it != servers_.end()) { + (*it)->disconnect(); + servers_.erase(it); + } +} + +void server_service::clear() noexcept +{ + for (auto &server : servers_) + server->disconnect(); + + servers_.clear(); +} + +} // !irccd
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libirccd/irccd/server_service.hpp Fri Nov 24 20:09:45 2017 +0100 @@ -0,0 +1,130 @@ +/* + * server_service.hpp -- server service + * + * Copyright (c) 2013-2017 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 IRCCD_SERVER_SERVICE_HPP +#define IRCCD_SERVER_SERVICE_HPP + +/** + * \file server_service.hpp + * \brief Server service. + */ + +#include <memory> +#include <vector> + +#include "server.hpp" + +namespace irccd { + +class irccd; + +/** + * \brief Manage IRC servers. + * \ingroup services + */ +class server_service { +private: + irccd& irccd_; + std::vector<std::shared_ptr<server>> servers_; + + void handle_channel_mode(const channel_mode_event&); + void handle_channel_notice(const channel_notice_event&); + void handle_connect(const connect_event&); + void handle_invite(const invite_event&); + void handle_join(const join_event&); + void handle_kick(const kick_event&); + void handle_message(const message_event&); + void handle_me(const me_event&); + void handle_mode(const mode_event&); + void handle_names(const names_event&); + void handle_nick(const nick_event&); + void handle_notice(const notice_event&); + void handle_part(const part_event&); + void handle_query(const query_event&); + void handle_topic(const topic_event&); + void handle_whois(const whois_event&); + +public: + /** + * Create the server service. + */ + server_service(irccd& instance); + + /** + * Get the list of servers + * + * \return the servers + */ + inline const std::vector<std::shared_ptr<server>>& servers() const noexcept + { + return servers_; + } + + /** + * Check if a server exists. + * + * \param name the name + * \return true if exists + */ + bool has(const std::string& name) const noexcept; + + /** + * Add a new server to the application. + * + * \pre hasServer must return false + * \param sv the server + */ + void add(std::shared_ptr<server> sv); + + /** + * Get a server or empty one if not found + * + * \param name the server name + * \return the server or empty one if not found + */ + std::shared_ptr<server> get(const std::string& name) const noexcept; + + /** + * Find a server by name. + * + * \param name the server name + * \return the server + * \throw std::out_of_range if the server does not exist + */ + std::shared_ptr<server> require(const std::string& name) const; + + /** + * Remove a server from the irccd instance. + * + * The server if any, will be disconnected. + * + * \param name the server name + */ + void remove(const std::string& name); + + /** + * Remove all servers. + * + * All servers will be disconnected. + */ + void clear() noexcept; +}; + +} // !irccd + +#endif // !IRCCD_SERVER_SERVICE_HPP
--- a/libirccd/irccd/service.cpp Fri Nov 24 20:05:15 2017 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,908 +0,0 @@ -/* - * service.cpp -- irccd services - * - * Copyright (c) 2013-2017 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 <algorithm> -#include <array> -#include <functional> -#include <stdexcept> - -#include "config.hpp" -#include "irccd.hpp" -#include "logger.hpp" -#include "service.hpp" -#include "string_util.hpp" -#include "system.hpp" -#include "transport_service.hpp" - -using namespace std::string_literals; - -namespace irccd { - -/* - * command_service. - * ------------------------------------------------------------------ - */ - -bool command_service::contains(const std::string& name) const noexcept -{ - return find(name) != nullptr; -} - -std::shared_ptr<command> command_service::find(const std::string& name) const noexcept -{ - auto it = std::find_if(commands_.begin(), commands_.end(), [&] (const auto& cmd) { - return cmd->name() == name; - }); - - return it == commands_.end() ? nullptr : *it; -} - -void command_service::add(std::shared_ptr<command> command) -{ - auto it = std::find_if(commands_.begin(), commands_.end(), [&] (const auto& cmd) { - return cmd->name() == command->name(); - }); - - if (it != commands_.end()) - *it = std::move(command); - else - commands_.push_back(std::move(command)); -} - -/* - * interrupt_service. - * ------------------------------------------------------------------ - */ - -interrupt_service::interrupt_service() - : in_(AF_INET, 0) - , out_(AF_INET, 0) -{ - // Bind a socket to any port. - in_.set(net::option::SockReuseAddress(true)); - in_.bind(net::ipv4::any(0)); - in_.listen(1); - - // Do the socket pair. - out_.connect(net::ipv4::pton("127.0.0.1", net::ipv4::port(in_.getsockname()))); - in_ = in_.accept(); - out_.set(net::option::SockBlockMode(false)); -} - -void interrupt_service::prepare(fd_set& in, fd_set&, net::Handle& max) -{ - FD_SET(in_.handle(), &in); - - if (in_.handle() > max) - max = in_.handle(); -} - -void interrupt_service::sync(fd_set& in, fd_set&) -{ - if (FD_ISSET(in_.handle(), &in)) { - static std::array<char, 32> tmp; - - try { - log::debug("irccd: interrupt service recv"); - in_.recv(tmp.data(), 32); - } catch (const std::exception& ex) { - log::warning() << "irccd: interrupt service error: " << ex.what() << std::endl; - } - } -} - -void interrupt_service::interrupt() noexcept -{ - try { - static char byte; - - log::debug("irccd: interrupt service send"); - out_.send(&byte, 1); - } catch (const std::exception& ex) { - log::warning() << "irccd: interrupt service error: " << ex.what() << std::endl; - } -} - -/* - * plugin_service. - * ------------------------------------------------------------------ - */ - -plugin_service::plugin_service(irccd& irccd) noexcept - : irccd_(irccd) -{ -} - -plugin_service::~plugin_service() -{ - for (const auto& plugin : plugins_) - plugin->on_unload(irccd_); -} - -bool plugin_service::has(const std::string& name) const noexcept -{ - return std::count_if(plugins_.cbegin(), plugins_.cend(), [&] (const auto& plugin) { - return plugin->name() == name; - }) > 0; -} - -std::shared_ptr<plugin> plugin_service::get(const std::string& name) const noexcept -{ - auto it = std::find_if(plugins_.begin(), plugins_.end(), [&] (const auto& plugin) { - return plugin->name() == name; - }); - - if (it == plugins_.end()) - return nullptr; - - return *it; -} - -std::shared_ptr<plugin> plugin_service::require(const std::string& name) const -{ - auto plugin = get(name); - - if (!plugin) - throw std::invalid_argument(string_util::sprintf("plugin %s not found", name)); - - return plugin; -} - -void plugin_service::add(std::shared_ptr<plugin> plugin) -{ - plugins_.push_back(std::move(plugin)); -} - -void plugin_service::add_loader(std::unique_ptr<plugin_loader> loader) -{ - loaders_.push_back(std::move(loader)); -} - -namespace { - -template <typename Map> -Map to_map(const config& conf, const std::string& section) -{ - Map ret; - - for (const auto& opt : conf.doc().get(section)) - ret.emplace(opt.key(), opt.value()); - - return ret; -} - -} // !namespace - -plugin_config plugin_service::config(const std::string& id) -{ - return to_map<plugin_config>(irccd_.config(), string_util::sprintf("plugin.%s", id)); -} - -plugin_formats plugin_service::formats(const std::string& id) -{ - return to_map<plugin_formats>(irccd_.config(), string_util::sprintf("format.%s", id)); -} - -plugin_paths plugin_service::paths(const std::string& id) -{ - auto defaults = to_map<plugin_paths>(irccd_.config(), "paths"); - auto paths = to_map<plugin_paths>(irccd_.config(), string_util::sprintf("paths.%s", id)); - - // Fill defaults paths. - if (!defaults.count("cache")) - defaults.emplace("cache", sys::cachedir() + "/plugin/" + id); - if (!defaults.count("data")) - paths.emplace("data", sys::datadir() + "/plugin/" + id); - if (!defaults.count("config")) - paths.emplace("config", sys::sysconfigdir() + "/plugin/" + id); - - // Now fill missing fields. - if (!paths.count("cache")) - paths.emplace("cache", defaults["cache"]); - if (!paths.count("data")) - paths.emplace("data", defaults["data"]); - if (!paths.count("config")) - paths.emplace("config", defaults["config"]); - - return paths; -} - -std::shared_ptr<plugin> plugin_service::open(const std::string& id, - const std::string& path) -{ - for (const auto& loader : loaders_) { - auto plugin = loader->open(id, path); - - if (plugin) - return plugin; - } - - return nullptr; -} - -std::shared_ptr<plugin> plugin_service::find(const std::string& id) -{ - for (const auto& loader : loaders_) { - auto plugin = loader->find(id); - - if (plugin) - return plugin; - } - - return nullptr; -} - -void plugin_service::load(std::string name, std::string path) -{ - if (has(name)) - return; - - try { - std::shared_ptr<plugin> plugin; - - if (path.empty()) - plugin = find(name); - else - plugin = open(name, std::move(path)); - - if (plugin) { - plugin->set_config(config(name)); - plugin->set_formats(formats(name)); - plugin->set_paths(paths(name)); - plugin->on_load(irccd_); - - add(std::move(plugin)); - } - } catch (const std::exception& ex) { - log::warning(string_util::sprintf("plugin %s: %s", name, ex.what())); - } -} - -void plugin_service::reload(const std::string& name) -{ - auto plugin = get(name); - - if (plugin) - plugin->on_reload(irccd_); -} - -void plugin_service::unload(const std::string& name) -{ - auto it = std::find_if(plugins_.begin(), plugins_.end(), [&] (const auto& plugin) { - return plugin->name() == name; - }); - - if (it != plugins_.end()) { - (*it)->on_unload(irccd_); - plugins_.erase(it); - } -} - -/* - * rule_service. - * ------------------------------------------------------------------ - */ - -void rule_service::add(rule rule) -{ - rules_.push_back(std::move(rule)); -} - -void rule_service::insert(rule rule, unsigned position) -{ - assert(position <= rules_.size()); - - rules_.insert(rules_.begin() + position, std::move(rule)); -} - -void rule_service::remove(unsigned position) -{ - assert(position < rules_.size()); - - rules_.erase(rules_.begin() + position); -} - -const rule &rule_service::require(unsigned position) const -{ - if (position >= rules_.size()) - throw std::out_of_range("rule " + std::to_string(position) + " does not exist"); - - return rules_[position]; -} - -rule &rule_service::require(unsigned position) -{ - if (position >= rules_.size()) - throw std::out_of_range("rule " + std::to_string(position) + " does not exist"); - - return rules_[position]; -} - -bool rule_service::solve(const std::string& server, - const std::string& channel, - const std::string& origin, - const std::string& plugin, - const std::string& event) noexcept -{ - bool result = true; - - log::debug(string_util::sprintf("rule: solving for server=%s, channel=%s, origin=%s, plugin=%s, event=%s", - server, channel, origin, plugin, event)); - - int i = 0; - for (const auto& rule : rules_) { - log::debug() << " candidate " << i++ << ":\n" - << " servers: " << string_util::join(rule.servers().begin(), rule.servers().end()) << "\n" - << " channels: " << string_util::join(rule.channels().begin(), rule.channels().end()) << "\n" - << " origins: " << string_util::join(rule.origins().begin(), rule.origins().end()) << "\n" - << " plugins: " << string_util::join(rule.plugins().begin(), rule.plugins().end()) << "\n" - << " events: " << string_util::join(rule.events().begin(), rule.events().end()) << "\n" - << " action: " << ((rule.action() == rule::action_type::accept) ? "accept" : "drop") << std::endl; - - if (rule.match(server, channel, origin, plugin, event)) - result = rule.action() == rule::action_type::accept; - } - - return result; -} - -/* - * server_service. - * ------------------------------------------------------------------ - */ - -class event_handler { -public: - std::string server; - std::string origin; - std::string target; - std::function<std::string (plugin &)> function_name; - std::function<void (plugin &)> function_exec; - - void operator()(irccd& irccd) const - { - for (auto& plugin : irccd.plugins().list()) { - auto eventname = function_name(*plugin); - auto allowed = irccd.rules().solve(server, target, origin, plugin->name(), eventname); - - if (!allowed) { - log::debug() << "rule: event skipped on match" << std::endl; - continue; - } - - log::debug() << "rule: event allowed" << std::endl; - - // TODO: this is the responsability of plugin_service. - try { - function_exec(*plugin); - } catch (const std::exception& ex) { - log::warning() << "plugin " << plugin->name() << ": error: " << ex.what() << std::endl; - } - } - } -}; - -void server_service::handle_channel_mode(const channel_mode_event& ev) -{ - log::debug() << "server " << ev.server->name() << ": event onChannelMode:\n"; - log::debug() << " origin: " << ev.origin << "\n"; - log::debug() << " channel: " << ev.channel << "\n"; - log::debug() << " mode: " << ev.mode << "\n"; - log::debug() << " argument: " << ev.argument << std::endl; - - irccd_.transports().broadcast(nlohmann::json::object({ - { "event", "onChannelMode" }, - { "server", ev.server->name() }, - { "origin", ev.origin }, - { "channel", ev.channel }, - { "mode", ev.mode }, - { "argument", ev.argument } - })); - - irccd_.post(event_handler{ev.server->name(), ev.origin, ev.channel, - [=] (plugin&) -> std::string { - return "onChannelMode"; - }, - [=] (plugin& plugin) { - plugin.on_channel_mode(irccd_, ev); - } - }); -} - -void server_service::handle_channel_notice(const channel_notice_event& ev) -{ - log::debug() << "server " << ev.server->name() << ": event onChannelNotice:\n"; - log::debug() << " origin: " << ev.origin << "\n"; - log::debug() << " channel: " << ev.channel << "\n"; - log::debug() << " message: " << ev.message << std::endl; - - irccd_.transports().broadcast(nlohmann::json::object({ - { "event", "onChannelNotice" }, - { "server", ev.server->name() }, - { "origin", ev.origin }, - { "channel", ev.channel }, - { "message", ev.message } - })); - - irccd_.post(event_handler{ev.server->name(), ev.origin, ev.channel, - [=] (plugin&) -> std::string { - return "onChannelNotice"; - }, - [=] (plugin& plugin) { - plugin.on_channel_notice(irccd_, ev); - } - }); -} - -void server_service::handle_connect(const connect_event& ev) -{ - log::debug() << "server " << ev.server->name() << ": event onConnect" << std::endl; - - irccd_.transports().broadcast(nlohmann::json::object({ - { "event", "onConnect" }, - { "server", ev.server->name() } - })); - - irccd_.post(event_handler{ev.server->name(), /* origin */ "", /* channel */ "", - [=] (plugin&) -> std::string { - return "onConnect"; - }, - [=] (plugin& plugin) { - plugin.on_connect(irccd_, ev); - } - }); -} - -void server_service::handle_invite(const invite_event& ev) -{ - log::debug() << "server " << ev.server->name() << ": event onInvite:\n"; - log::debug() << " origin: " << ev.origin << "\n"; - log::debug() << " channel: " << ev.channel << "\n"; - log::debug() << " target: " << ev.nickname << std::endl; - - irccd_.transports().broadcast(nlohmann::json::object({ - { "event", "onInvite" }, - { "server", ev.server->name() }, - { "origin", ev.origin }, - { "channel", ev.channel } - })); - - irccd_.post(event_handler{ev.server->name(), ev.origin, ev.channel, - [=] (plugin&) -> std::string { - return "onInvite"; - }, - [=] (plugin& plugin) { - plugin.on_invite(irccd_, ev); - } - }); -} - -void server_service::handle_join(const join_event& ev) -{ - log::debug() << "server " << ev.server->name() << ": event onJoin:\n"; - log::debug() << " origin: " << ev.origin << "\n"; - log::debug() << " channel: " << ev.channel << std::endl; - - irccd_.transports().broadcast(nlohmann::json::object({ - { "event", "onJoin" }, - { "server", ev.server->name() }, - { "origin", ev.origin }, - { "channel", ev.channel } - })); - - irccd_.post(event_handler{ev.server->name(), ev.origin, ev.channel, - [=] (plugin&) -> std::string { - return "onJoin"; - }, - [=] (plugin& plugin) { - plugin.on_join(irccd_, ev); - } - }); -} - -void server_service::handle_kick(const kick_event& ev) -{ - log::debug() << "server " << ev.server->name() << ": event onKick:\n"; - log::debug() << " origin: " << ev.origin << "\n"; - log::debug() << " channel: " << ev.channel << "\n"; - log::debug() << " target: " << ev.target << "\n"; - log::debug() << " reason: " << ev.reason << std::endl; - - irccd_.transports().broadcast(nlohmann::json::object({ - { "event", "onKick" }, - { "server", ev.server->name() }, - { "origin", ev.origin }, - { "channel", ev.channel }, - { "target", ev.target }, - { "reason", ev.reason } - })); - - irccd_.post(event_handler{ev.server->name(), ev.origin, ev.channel, - [=] (plugin&) -> std::string { - return "onKick"; - }, - [=] (plugin& plugin) { - plugin.on_kick(irccd_, ev); - } - }); -} - -void server_service::handle_message(const message_event& ev) -{ - log::debug() << "server " << ev.server->name() << ": event onMessage:\n"; - log::debug() << " origin: " << ev.origin << "\n"; - log::debug() << " channel: " << ev.channel << "\n"; - log::debug() << " message: " << ev.message << std::endl; - - irccd_.transports().broadcast(nlohmann::json::object({ - { "event", "onMessage" }, - { "server", ev.server->name() }, - { "origin", ev.origin }, - { "channel", ev.channel }, - { "message", ev.message } - })); - - irccd_.post(event_handler{ev.server->name(), ev.origin, ev.channel, - [=] (plugin& plugin) -> std::string { - return string_util::parse_message( - ev.message, - ev.server->command_char(), - plugin.name() - ).type == string_util::message_pack::type::command ? "onCommand" : "onMessage"; - }, - [=] (plugin& plugin) mutable { - auto copy = ev; - auto pack = string_util::parse_message(copy.message, copy.server->command_char(), plugin.name()); - - copy.message = pack.message; - - if (pack.type == string_util::message_pack::type::command) - plugin.on_command(irccd_, copy); - else - plugin.on_message(irccd_, copy); - } - }); -} - -void server_service::handle_me(const me_event& ev) -{ - log::debug() << "server " << ev.server->name() << ": event onMe:\n"; - log::debug() << " origin: " << ev.origin << "\n"; - log::debug() << " target: " << ev.channel << "\n"; - log::debug() << " message: " << ev.message << std::endl; - - irccd_.transports().broadcast(nlohmann::json::object({ - { "event", "onMe" }, - { "server", ev.server->name() }, - { "origin", ev.origin }, - { "target", ev.channel }, - { "message", ev.message } - })); - - irccd_.post(event_handler{ev.server->name(), ev.origin, ev.channel, - [=] (plugin&) -> std::string { - return "onMe"; - }, - [=] (plugin& plugin) { - plugin.on_me(irccd_, ev); - } - }); -} - -void server_service::handle_mode(const mode_event& ev) -{ - log::debug() << "server " << ev.server->name() << ": event onMode\n"; - log::debug() << " origin: " << ev.origin << "\n"; - log::debug() << " mode: " << ev.mode << std::endl; - - irccd_.transports().broadcast(nlohmann::json::object({ - { "event", "onMode" }, - { "server", ev.server->name() }, - { "origin", ev.origin }, - { "mode", ev.mode } - })); - - irccd_.post(event_handler{ev.server->name(), ev.origin, /* channel */ "", - [=] (plugin &) -> std::string { - return "onMode"; - }, - [=] (plugin &plugin) { - plugin.on_mode(irccd_, ev); - } - }); -} - -void server_service::handle_names(const names_event& ev) -{ - log::debug() << "server " << ev.server->name() << ": event onNames:\n"; - log::debug() << " channel: " << ev.channel << "\n"; - log::debug() << " names: " << string_util::join(ev.names.begin(), ev.names.end(), ", ") << std::endl; - - auto names = nlohmann::json::array(); - - for (const auto& v : ev.names) - names.push_back(v); - - irccd_.transports().broadcast(nlohmann::json::object({ - { "event", "onNames" }, - { "server", ev.server->name() }, - { "channel", ev.channel }, - { "names", std::move(names) } - })); - - irccd_.post(event_handler{ev.server->name(), /* origin */ "", ev.channel, - [=] (plugin&) -> std::string { - return "onNames"; - }, - [=] (plugin& plugin) { - plugin.on_names(irccd_, ev); - } - }); -} - -void server_service::handle_nick(const nick_event& ev) -{ - log::debug() << "server " << ev.server->name() << ": event onNick:\n"; - log::debug() << " origin: " << ev.origin << "\n"; - log::debug() << " nickname: " << ev.nickname << std::endl; - - irccd_.transports().broadcast(nlohmann::json::object({ - { "event", "onNick" }, - { "server", ev.server->name() }, - { "origin", ev.origin }, - { "nickname", ev.nickname } - })); - - irccd_.post(event_handler{ev.server->name(), ev.origin, /* channel */ "", - [=] (plugin&) -> std::string { - return "onNick"; - }, - [=] (plugin& plugin) { - plugin.on_nick(irccd_, ev); - } - }); -} - -void server_service::handle_notice(const notice_event& ev) -{ - log::debug() << "server " << ev.server->name() << ": event onNotice:\n"; - log::debug() << " origin: " << ev.origin << "\n"; - log::debug() << " message: " << ev.message << std::endl; - - irccd_.transports().broadcast(nlohmann::json::object({ - { "event", "onNotice" }, - { "server", ev.server->name() }, - { "origin", ev.origin }, - { "message", ev.message } - })); - - irccd_.post(event_handler{ev.server->name(), ev.origin, /* channel */ "", - [=] (plugin&) -> std::string { - return "onNotice"; - }, - [=] (plugin& plugin) { - plugin.on_notice(irccd_, ev); - } - }); -} - -void server_service::handle_part(const part_event& ev) -{ - log::debug() << "server " << ev.server->name() << ": event onPart:\n"; - log::debug() << " origin: " << ev.origin << "\n"; - log::debug() << " channel: " << ev.channel << "\n"; - log::debug() << " reason: " << ev.reason << std::endl; - - irccd_.transports().broadcast(nlohmann::json::object({ - { "event", "onPart" }, - { "server", ev.server->name() }, - { "origin", ev.origin }, - { "channel", ev.channel }, - { "reason", ev.reason } - })); - - irccd_.post(event_handler{ev.server->name(), ev.origin, ev.channel, - [=] (plugin&) -> std::string { - return "onPart"; - }, - [=] (plugin& plugin) { - plugin.on_part(irccd_, ev); - } - }); -} - -void server_service::handle_query(const query_event& ev) -{ - log::debug() << "server " << ev.server->name() << ": event onQuery:\n"; - log::debug() << " origin: " << ev.origin << "\n"; - log::debug() << " message: " << ev.message << std::endl; - - irccd_.transports().broadcast(nlohmann::json::object({ - { "event", "onQuery" }, - { "server", ev.server->name() }, - { "origin", ev.origin }, - { "message", ev.message } - })); - - irccd_.post(event_handler{ev.server->name(), ev.origin, /* channel */ "", - [=] (plugin& plugin) -> std::string { - return string_util::parse_message( - ev.message, - ev.server->command_char(), - plugin.name() - ).type == string_util::message_pack::type::command ? "onQueryCommand" : "onQuery"; - }, - [=] (plugin& plugin) mutable { - auto copy = ev; - auto pack = string_util::parse_message(copy.message, copy.server->command_char(), plugin.name()); - - copy.message = pack.message; - - if (pack.type == string_util::message_pack::type::command) - plugin.on_query_command(irccd_, copy); - else - plugin.on_query(irccd_, copy); - } - }); -} - -void server_service::handle_topic(const topic_event& ev) -{ - log::debug() << "server " << ev.server->name() << ": event onTopic:\n"; - log::debug() << " origin: " << ev.origin << "\n"; - log::debug() << " channel: " << ev.channel << "\n"; - log::debug() << " topic: " << ev.topic << std::endl; - - irccd_.transports().broadcast(nlohmann::json::object({ - { "event", "onTopic" }, - { "server", ev.server->name() }, - { "origin", ev.origin }, - { "channel", ev.channel }, - { "topic", ev.topic } - })); - - irccd_.post(event_handler{ev.server->name(), ev.origin, ev.channel, - [=] (plugin&) -> std::string { - return "onTopic"; - }, - [=] (plugin& plugin) { - plugin.on_topic(irccd_, ev); - } - }); -} - -void server_service::handle_whois(const whois_event& ev) -{ - log::debug() << "server " << ev.server->name() << ": event onWhois\n"; - log::debug() << " nickname: " << ev.whois.nick << "\n"; - log::debug() << " username: " << ev.whois.user << "\n"; - log::debug() << " host: " << ev.whois.host << "\n"; - log::debug() << " realname: " << ev.whois.realname << "\n"; - log::debug() << " channels: " << string_util::join(ev.whois.channels.begin(), ev.whois.channels.end(), ", ") << std::endl; - - irccd_.transports().broadcast(nlohmann::json::object({ - { "event", "onWhois" }, - { "server", ev.server->name() }, - { "nickname", ev.whois.nick }, - { "username", ev.whois.user }, - { "host", ev.whois.host }, - { "realname", ev.whois.realname } - })); - - irccd_.post(event_handler{ev.server->name(), /* origin */ "", /* channel */ "", - [=] (plugin&) -> std::string { - return "onWhois"; - }, - [=] (plugin& plugin) { - plugin.on_whois(irccd_, ev); - } - }); -} - -server_service::server_service(irccd &irccd) - : irccd_(irccd) -{ -} - -bool server_service::has(const std::string& name) const noexcept -{ - return std::count_if(servers_.cbegin(), servers_.end(), [&] (const auto& server) { - return server->name() == name; - }) > 0; -} - -void server_service::add(std::shared_ptr<server> server) -{ - assert(!has(server->name())); - - std::weak_ptr<class server> ptr(server); - - server->on_channel_mode.connect(boost::bind(&server_service::handle_channel_mode, this, _1)); - server->on_channel_notice.connect(boost::bind(&server_service::handle_channel_notice, this, _1)); - server->on_connect.connect(boost::bind(&server_service::handle_connect, this, _1)); - server->on_invite.connect(boost::bind(&server_service::handle_invite, this, _1)); - server->on_join.connect(boost::bind(&server_service::handle_join, this, _1)); - server->on_kick.connect(boost::bind(&server_service::handle_kick, this, _1)); - server->on_message.connect(boost::bind(&server_service::handle_message, this, _1)); - server->on_me.connect(boost::bind(&server_service::handle_me, this, _1)); - server->on_mode.connect(boost::bind(&server_service::handle_mode, this, _1)); - server->on_names.connect(boost::bind(&server_service::handle_names, this, _1)); - server->on_nick.connect(boost::bind(&server_service::handle_nick, this, _1)); - server->on_notice.connect(boost::bind(&server_service::handle_notice, this, _1)); - server->on_part.connect(boost::bind(&server_service::handle_part, this, _1)); - server->on_query.connect(boost::bind(&server_service::handle_query, this, _1)); - server->on_topic.connect(boost::bind(&server_service::handle_topic, this, _1)); - server->on_whois.connect(boost::bind(&server_service::handle_whois, this, _1)); - server->on_die.connect([this, ptr] () { - irccd_.post([=] (irccd&) { - auto server = ptr.lock(); - - if (server) { - log::info(string_util::sprintf("server %s: removed", server->name())); - servers_.erase(std::find(servers_.begin(), servers_.end(), server)); - } - }); - }); - - server->connect(); - servers_.push_back(std::move(server)); -} - -std::shared_ptr<server> server_service::get(const std::string& name) const noexcept -{ - auto it = std::find_if(servers_.begin(), servers_.end(), [&] (const auto& server) { - return server->name() == name; - }); - - if (it == servers_.end()) - return nullptr; - - return *it; -} - -std::shared_ptr<server> server_service::require(const std::string& name) const -{ - auto server = get(name); - - if (!server) - throw std::invalid_argument(string_util::sprintf("server %s not found", name)); - - return server; -} - -void server_service::remove(const std::string& name) -{ - auto it = std::find_if(servers_.begin(), servers_.end(), [&] (const auto& server) { - return server->name() == name; - }); - - if (it != servers_.end()) { - (*it)->disconnect(); - servers_.erase(it); - } -} - -void server_service::clear() noexcept -{ - for (auto &server : servers_) - server->disconnect(); - - servers_.clear(); -} - -} // !irccd
--- a/libirccd/irccd/service.hpp Fri Nov 24 20:05:15 2017 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,468 +0,0 @@ -/* - * service.hpp -- irccd services - * - * Copyright (c) 2013-2017 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 IRCCD_SERVICE_HPP -#define IRCCD_SERVICE_HPP - -/** - * \file service.hpp - * \brief irccd services. - */ - -#include <memory> -#include <unordered_map> -#include <vector> - -#include <json.hpp> - -#include "command.hpp" -#include "net.hpp" -#include "plugin.hpp" -#include "rule.hpp" -#include "server.hpp" -#include "sysconfig.hpp" - -namespace irccd { - -/* - * command_service. - * ------------------------------------------------------------------ - */ - -/** - * \brief Store remote commands. - * \ingroup services - */ -class command_service { -private: - std::vector<std::shared_ptr<command>> commands_; - -public: - /** - * Get all commands. - * - * \return the list of commands. - */ - inline const std::vector<std::shared_ptr<command>>& commands() const noexcept - { - return commands_; - } - - /** - * Tells if a command exists. - * - * \param name the command name - * \return true if the command exists - */ - bool contains(const std::string& name) const noexcept; - - /** - * Find a command by name. - * - * \param name the command name - * \return the command or empty one if not found - */ - std::shared_ptr<command> find(const std::string& name) const noexcept; - - /** - * Add a command or replace existing one. - * - * \pre command != nullptr - * \param command the command name - */ - void add(std::shared_ptr<command> command); -}; - -/* - * interrupt_service. - * ------------------------------------------------------------------ - */ - -/** - * \brief Interrupt irccd event loop. - * \ingroup services - */ -class interrupt_service { -private: - net::TcpSocket in_; - net::TcpSocket out_; - -public: - /** - * Prepare the socket pair. - * - * \throw std::runtime_error on errors - */ - interrupt_service(); - - /** - * \copydoc Service::prepare - */ - void prepare(fd_set& in, fd_set& out, net::Handle& max); - - /** - * \copydoc Service::sync - */ - void sync(fd_set& in, fd_set& out); - - /** - * Request interruption. - */ - void interrupt() noexcept; -}; - -/* - * plugin_service. - * ------------------------------------------------------------------ - */ - -/** - * \brief Manage plugins. - * \ingroup services - */ -class plugin_service { -private: - irccd& irccd_; - std::vector<std::shared_ptr<plugin>> plugins_; - std::vector<std::unique_ptr<plugin_loader>> loaders_; - -public: - /** - * Create the plugin service. - * - * \param irccd the irccd instance - */ - plugin_service(irccd& irccd) noexcept; - - /** - * Destroy plugins. - */ - ~plugin_service(); - - /** - * Get the list of plugins. - * - * \return the list of plugins - */ - inline const std::vector<std::shared_ptr<plugin>>& list() const noexcept - { - return plugins_; - } - - /** - * Check if a plugin is loaded. - * - * \param name the plugin id - * \return true if has plugin - */ - bool has(const std::string& name) const noexcept; - - /** - * Get a loaded plugin or null if not found. - * - * \param name the plugin id - * \return the plugin or empty one if not found - */ - std::shared_ptr<plugin> get(const std::string& name) const noexcept; - - /** - * Find a loaded plugin. - * - * \param name the plugin id - * \return the plugin - * \throws std::out_of_range if not found - */ - std::shared_ptr<plugin> require(const std::string& name) const; - - /** - * Add the specified plugin to the registry. - * - * \pre plugin != nullptr - * \param plugin the plugin - * \note the plugin is only added to the list, no action is performed on it - */ - void add(std::shared_ptr<plugin> plugin); - - /** - * Add a loader. - * - * \param loader the loader - */ - void add_loader(std::unique_ptr<plugin_loader> loader); - - /** - * Get the configuration for the specified plugin. - * - * \return the configuration - */ - plugin_config config(const std::string& id); - - /** - * Get the formats for the specified plugin. - * - * \return the formats - */ - plugin_formats formats(const std::string& id); - - /** - * Get the paths for the specified plugin. - * - * If none is defined, return the default ones. - * - * \return the paths - */ - plugin_paths paths(const std::string& id); - - /** - * Generic function for opening the plugin at the given path. - * - * This function will search for every pluginLoader and call open() on it, - * the first one that success will be returned. - * - * \param id the plugin id - * \param path the path to the file - * \return the plugin or nullptr on failures - */ - std::shared_ptr<plugin> open(const std::string& id, - const std::string& path); - - /** - * Generic function for finding a plugin. - * - * \param id the plugin id - * \return the plugin or nullptr on failures - */ - std::shared_ptr<plugin> find(const std::string& id); - - /** - * Convenient wrapper that loads a plugin, call onLoad and add it to the - * registry. - * - * Any errors are printed using logger. - * - * \param name the name - * \param path the optional path (searched if empty) - */ - void load(std::string name, std::string path = ""); - - /** - * Unload a plugin and remove it. - * - * \param name the plugin id - */ - void unload(const std::string& name); - - /** - * Reload a plugin by calling onReload. - * - * \param name the plugin name - * \throw std::exception on failures - */ - void reload(const std::string& name); -}; - -/* - * rule_service. - * ------------------------------------------------------------------ - */ - -/** - * \brief Store and solve rules. - * \ingroup services - */ -class rule_service { -private: - std::vector<rule> rules_; - -public: - /** - * Get the list of rules. - * - * \return the list of rules - */ - inline const std::vector<rule>& list() const noexcept - { - return rules_; - } - - /** - * Get the number of rules. - * - * \return the number of rules - */ - inline std::size_t length() const noexcept - { - return rules_.size(); - } - - /** - * Append a rule. - * - * \param rule the rule to append - */ - void add(rule rule); - - /** - * Insert a new rule at the specified position. - * - * \param rule the rule - * \param position the position - */ - void insert(rule rule, unsigned position); - - /** - * Remove a new rule from the specified position. - * - * \pre position must be valid - * \param position the position - */ - void remove(unsigned position); - - /** - * Get a rule at the specified index or throw an exception if not found. - * - * \param position the position - * \return the rule - * \throw std::out_of_range if position is invalid - */ - const rule& require(unsigned position) const; - - /** - * Overloaded function. - * - * \copydoc require - */ - rule& require(unsigned position); - - /** - * Resolve the action to execute with the specified list of rules. - * - * \param server the server name - * \param channel the channel name - * \param origin the origin - * \param plugin the plugin name - * \param event the event name (e.g onKick) - * \return true if the plugin must be called - */ - bool solve(const std::string& server, - const std::string& channel, - const std::string& origin, - const std::string& plugin, - const std::string& event) noexcept; -}; - -/* - * server_service. - * ------------------------------------------------------------------ - */ - -/** - * \brief Manage IRC servers. - * \ingroup services - */ -class server_service { -private: - irccd& irccd_; - std::vector<std::shared_ptr<server>> servers_; - - void handle_channel_mode(const channel_mode_event&); - void handle_channel_notice(const channel_notice_event&); - void handle_connect(const connect_event&); - void handle_invite(const invite_event&); - void handle_join(const join_event&); - void handle_kick(const kick_event&); - void handle_message(const message_event&); - void handle_me(const me_event&); - void handle_mode(const mode_event&); - void handle_names(const names_event&); - void handle_nick(const nick_event&); - void handle_notice(const notice_event&); - void handle_part(const part_event&); - void handle_query(const query_event&); - void handle_topic(const topic_event&); - void handle_whois(const whois_event&); - -public: - /** - * Create the server service. - */ - server_service(irccd& instance); - - /** - * Get the list of servers - * - * \return the servers - */ - inline const std::vector<std::shared_ptr<server>>& servers() const noexcept - { - return servers_; - } - - /** - * Check if a server exists. - * - * \param name the name - * \return true if exists - */ - bool has(const std::string& name) const noexcept; - - /** - * Add a new server to the application. - * - * \pre hasServer must return false - * \param sv the server - */ - void add(std::shared_ptr<server> sv); - - /** - * Get a server or empty one if not found - * - * \param name the server name - * \return the server or empty one if not found - */ - std::shared_ptr<server> get(const std::string& name) const noexcept; - - /** - * Find a server by name. - * - * \param name the server name - * \return the server - * \throw std::out_of_range if the server does not exist - */ - std::shared_ptr<server> require(const std::string& name) const; - - /** - * Remove a server from the irccd instance. - * - * The server if any, will be disconnected. - * - * \param name the server name - */ - void remove(const std::string& name); - - /** - * Remove all servers. - * - * All servers will be disconnected. - */ - void clear() noexcept; -}; - -} // !irccd - -#endif // !IRCCD_SERVICE_HPP
--- a/libirccd/irccd/transport_service.cpp Fri Nov 24 20:05:15 2017 +0100 +++ b/libirccd/irccd/transport_service.cpp Fri Nov 24 20:09:45 2017 +0100 @@ -18,10 +18,9 @@ #include <cassert> -#include "command.hpp" +#include "command_service.hpp" #include "irccd.hpp" #include "logger.hpp" -#include "service.hpp" #include "transport_client.hpp" #include "transport_server.hpp" #include "transport_service.hpp"
--- a/tests/js-plugin/main.cpp Fri Nov 24 20:05:15 2017 +0100 +++ b/tests/js-plugin/main.cpp Fri Nov 24 20:09:45 2017 +0100 @@ -21,7 +21,7 @@ #include <boost/test/unit_test.hpp> #include <irccd/irccd.hpp> -#include <irccd/service.hpp> +#include <irccd/plugin_service.hpp> #include <irccd/js/irccd_jsapi.hpp> #include <irccd/js/js_plugin.hpp>
--- a/tests/plugin-ask/main.cpp Fri Nov 24 20:05:15 2017 +0100 +++ b/tests/plugin-ask/main.cpp Fri Nov 24 20:09:45 2017 +0100 @@ -21,7 +21,6 @@ #include <irccd/irccd.hpp> #include <irccd/server.hpp> -#include <irccd/service.hpp> #include "plugin_test.hpp"
--- a/tests/plugin-auth/main.cpp Fri Nov 24 20:05:15 2017 +0100 +++ b/tests/plugin-auth/main.cpp Fri Nov 24 20:09:45 2017 +0100 @@ -21,7 +21,6 @@ #include <irccd/irccd.hpp> #include <irccd/server.hpp> -#include <irccd/service.hpp> #include "plugin_test.hpp"
--- a/tests/plugin-hangman/main.cpp Fri Nov 24 20:05:15 2017 +0100 +++ b/tests/plugin-hangman/main.cpp Fri Nov 24 20:09:45 2017 +0100 @@ -24,7 +24,6 @@ #include <irccd/irccd.hpp> #include <irccd/server.hpp> -#include <irccd/service.hpp> #include "plugin_test.hpp"
--- a/tests/plugin-history/main.cpp Fri Nov 24 20:05:15 2017 +0100 +++ b/tests/plugin-history/main.cpp Fri Nov 24 20:09:45 2017 +0100 @@ -23,7 +23,6 @@ #include <irccd/irccd.hpp> #include <irccd/server.hpp> -#include <irccd/service.hpp> #include "plugin_test.hpp"
--- a/tests/plugin-logger/main.cpp Fri Nov 24 20:05:15 2017 +0100 +++ b/tests/plugin-logger/main.cpp Fri Nov 24 20:09:45 2017 +0100 @@ -25,7 +25,6 @@ #include <irccd/irccd.hpp> #include <irccd/logger.hpp> #include <irccd/server.hpp> -#include <irccd/service.hpp> #include "plugin_test.hpp"
--- a/tests/plugin-plugin/main.cpp Fri Nov 24 20:05:15 2017 +0100 +++ b/tests/plugin-plugin/main.cpp Fri Nov 24 20:09:45 2017 +0100 @@ -21,8 +21,8 @@ #include <irccd/irccd.hpp> #include <irccd/logger.hpp> +#include <irccd/plugin_service.hpp> #include <irccd/server.hpp> -#include <irccd/service.hpp> #include <irccd/string_util.hpp> #include "plugin_test.hpp"
--- a/tests/rules/main.cpp Fri Nov 24 20:05:15 2017 +0100 +++ b/tests/rules/main.cpp Fri Nov 24 20:09:45 2017 +0100 @@ -20,8 +20,7 @@ #include <boost/test/unit_test.hpp> #include <irccd/logger.hpp> -#include <irccd/rule.hpp> -#include <irccd/service.hpp> +#include <irccd/rule_service.hpp> namespace irccd {