Mercurial > irccd
changeset 230:4f64b53ecd98
Irccd: update network code
line wrap: on
line diff
--- a/doc/doxygen/Doxyfile.in Mon Jul 18 22:39:14 2016 +0200 +++ b/doc/doxygen/Doxyfile.in Wed Aug 10 10:33:21 2016 +0200 @@ -397,7 +397,7 @@ # normally produced when WARNINGS is set to YES. # The default value is: NO. -EXTRACT_ALL = NO +EXTRACT_ALL = YES # If the EXTRACT_PRIVATE tag is set to YES all private members of a class will # be included in the documentation. @@ -415,7 +415,7 @@ # included in the documentation. # The default value is: NO. -EXTRACT_STATIC = NO +EXTRACT_STATIC = YES # If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) defined # locally in source files will be included in the documentation. If set to NO
--- a/irccd/main.cpp Mon Jul 18 22:39:14 2016 +0200 +++ b/irccd/main.cpp Wed Aug 10 10:33:21 2016 +0200 @@ -261,7 +261,7 @@ try { load(open(options), options); } catch (const std::exception &ex) { - log::warning() << "irccd: " << ex.what() << std::endl; + log::warning() << "error: " << ex.what() << std::endl; return 1; }
--- a/irccdctl/main.cpp Mon Jul 18 22:39:14 2016 +0200 +++ b/irccdctl/main.cpp Wed Aug 10 10:33:21 2016 +0200 @@ -19,6 +19,7 @@ #include <irccd/irccdctl.hpp> #include <irccd/logger.hpp> #include <irccd/path.hpp> +#include <irccd/system.hpp> using namespace irccd; @@ -29,14 +30,13 @@ path::setApplicationPath(argv[0]); log::setInterface(std::make_unique<log::Console>()); log::setVerbose(false); - net::init(); try { Irccdctl ctl; ctl.run(--argc, ++argv); } catch (const std::exception &ex) { - log::warning() << sys::programName() << ": " << ex.what() << std::endl; + log::warning() << "error: " << ex.what() << std::endl; std::exit(1); }
--- a/lib/irccd/CMakeSources.cmake Mon Jul 18 22:39:14 2016 +0200 +++ b/lib/irccd/CMakeSources.cmake Wed Aug 10 10:33:21 2016 +0200 @@ -2,6 +2,10 @@ HEADERS ${CMAKE_CURRENT_LIST_DIR}/alias.hpp ${CMAKE_CURRENT_LIST_DIR}/connection.hpp + ${CMAKE_CURRENT_LIST_DIR}/conn-state-checking.hpp + ${CMAKE_CURRENT_LIST_DIR}/conn-state-connecting.hpp + ${CMAKE_CURRENT_LIST_DIR}/conn-state-disconnected.hpp + ${CMAKE_CURRENT_LIST_DIR}/conn-state-ready.hpp ${CMAKE_CURRENT_LIST_DIR}/cmd-help.hpp ${CMAKE_CURRENT_LIST_DIR}/cmd-plugin-config.hpp ${CMAKE_CURRENT_LIST_DIR}/cmd-plugin-info.hpp @@ -80,6 +84,10 @@ SOURCES ${CMAKE_CURRENT_LIST_DIR}/alias.cpp ${CMAKE_CURRENT_LIST_DIR}/connection.cpp + ${CMAKE_CURRENT_LIST_DIR}/conn-state-checking.cpp + ${CMAKE_CURRENT_LIST_DIR}/conn-state-connecting.cpp + ${CMAKE_CURRENT_LIST_DIR}/conn-state-disconnected.cpp + ${CMAKE_CURRENT_LIST_DIR}/conn-state-ready.cpp ${CMAKE_CURRENT_LIST_DIR}/config.cpp ${CMAKE_CURRENT_LIST_DIR}/cmd-help.cpp ${CMAKE_CURRENT_LIST_DIR}/cmd-plugin-config.cpp
--- a/lib/irccd/cmd-help.cpp Mon Jul 18 22:39:14 2016 +0200 +++ b/lib/irccd/cmd-help.cpp Wed Aug 10 10:33:21 2016 +0200 @@ -25,7 +25,7 @@ namespace command { Help::Help() - : Command("help", "General") + : Command("help", "General", "Get help about a command") { } @@ -34,11 +34,6 @@ return {{ "command", true }}; } -std::string Help::help() const -{ - return "Get help about a command."; -} - nlohmann::json Help::request(Irccdctl &irccdctl, const CommandRequest &args) const { auto it = irccdctl.commandService().find(args.arg(0U)); @@ -46,7 +41,7 @@ if (!it) log::warning() << "there is no command named: " << args.arg(0U) << std::endl; else - log::warning() << it->usage() << std::flush; + log::warning() << it->help() << std::flush; return nullptr; }
--- a/lib/irccd/cmd-help.hpp Mon Jul 18 22:39:14 2016 +0200 +++ b/lib/irccd/cmd-help.hpp Wed Aug 10 10:33:21 2016 +0200 @@ -47,11 +47,6 @@ IRCCD_EXPORT std::vector<Arg> args() const override; /** - * \copydoc Command::help - */ - IRCCD_EXPORT std::string help() const override; - - /** * \copydoc Command::request */ IRCCD_EXPORT nlohmann::json request(Irccdctl &irccdctl, const CommandRequest &args) const override;
--- a/lib/irccd/cmd-plugin-config.cpp Mon Jul 18 22:39:14 2016 +0200 +++ b/lib/irccd/cmd-plugin-config.cpp Wed Aug 10 10:33:21 2016 +0200 @@ -59,15 +59,10 @@ } // !namespace PluginConfig::PluginConfig() - : Command("plugin-config", "Plugins") + : Command("plugin-config", "Plugins", "Get or set a plugin config variable") { } -std::string PluginConfig::help() const -{ - return "Get or set a plugin configuration option."; -} - std::vector<Command::Arg> PluginConfig::args() const { return {
--- a/lib/irccd/cmd-plugin-config.hpp Mon Jul 18 22:39:14 2016 +0200 +++ b/lib/irccd/cmd-plugin-config.hpp Wed Aug 10 10:33:21 2016 +0200 @@ -41,11 +41,6 @@ IRCCD_EXPORT PluginConfig(); /** - * \copydoc Command::help - */ - IRCCD_EXPORT std::string help() const override; - - /** * \copydoc Command::args */ IRCCD_EXPORT std::vector<Arg> args() const override;
--- a/lib/irccd/cmd-plugin-info.cpp Mon Jul 18 22:39:14 2016 +0200 +++ b/lib/irccd/cmd-plugin-info.cpp Wed Aug 10 10:33:21 2016 +0200 @@ -29,15 +29,10 @@ namespace command { PluginInfo::PluginInfo() - : Command("plugin-info", "Plugins") + : Command("plugin-info", "Plugins", "Get plugin information") { } -std::string PluginInfo::help() const -{ - return "Get plugin information."; -} - std::vector<Command::Arg> PluginInfo::args() const { return {{ "plugin", true }};
--- a/lib/irccd/cmd-plugin-info.hpp Mon Jul 18 22:39:14 2016 +0200 +++ b/lib/irccd/cmd-plugin-info.hpp Wed Aug 10 10:33:21 2016 +0200 @@ -42,11 +42,6 @@ IRCCD_EXPORT PluginInfo(); /** - * \copydoc Command::help - */ - IRCCD_EXPORT std::string help() const override; - - /** * \copydoc Command::args */ IRCCD_EXPORT std::vector<Arg> args() const override;
--- a/lib/irccd/cmd-plugin-list.cpp Mon Jul 18 22:39:14 2016 +0200 +++ b/lib/irccd/cmd-plugin-list.cpp Wed Aug 10 10:33:21 2016 +0200 @@ -29,15 +29,10 @@ namespace command { PluginList::PluginList() - : Command("plugin-list", "Plugins") + : Command("plugin-list", "Plugins", "Get the list of loaded plugins") { } -std::string PluginList::help() const -{ - return "Get the list of loaded plugins."; -} - nlohmann::json PluginList::exec(Irccd &irccd, const nlohmann::json &request) const { auto response = Command::exec(irccd, request);
--- a/lib/irccd/cmd-plugin-list.hpp Mon Jul 18 22:39:14 2016 +0200 +++ b/lib/irccd/cmd-plugin-list.hpp Wed Aug 10 10:33:21 2016 +0200 @@ -42,11 +42,6 @@ IRCCD_EXPORT PluginList(); /** - * \copydoc Command::help - */ - IRCCD_EXPORT std::string help() const override; - - /** * \copydoc Command::exec */ IRCCD_EXPORT nlohmann::json exec(Irccd &irccd, const nlohmann::json &request) const override;
--- a/lib/irccd/cmd-plugin-load.cpp Mon Jul 18 22:39:14 2016 +0200 +++ b/lib/irccd/cmd-plugin-load.cpp Wed Aug 10 10:33:21 2016 +0200 @@ -26,15 +26,10 @@ namespace command { PluginLoad::PluginLoad() - : Command("plugin-load", "Plugins") + : Command("plugin-load", "Plugins", "Load a plugin") { } -std::string PluginLoad::help() const -{ - return "Load a plugin."; -} - std::vector<Command::Arg> PluginLoad::args() const { return {{ "plugin", true }};
--- a/lib/irccd/cmd-plugin-load.hpp Mon Jul 18 22:39:14 2016 +0200 +++ b/lib/irccd/cmd-plugin-load.hpp Wed Aug 10 10:33:21 2016 +0200 @@ -42,11 +42,6 @@ IRCCD_EXPORT PluginLoad(); /** - * \copydoc Command::help - */ - IRCCD_EXPORT std::string help() const override; - - /** * \copydoc Command::args */ IRCCD_EXPORT std::vector<Arg> args() const override;
--- a/lib/irccd/cmd-plugin-reload.cpp Mon Jul 18 22:39:14 2016 +0200 +++ b/lib/irccd/cmd-plugin-reload.cpp Wed Aug 10 10:33:21 2016 +0200 @@ -27,15 +27,10 @@ namespace command { PluginReload::PluginReload() - : Command("plugin-reload", "Plugins") + : Command("plugin-reload", "Plugins", "Reload a plugin") { } -std::string PluginReload::help() const -{ - return "Reload a plugin."; -} - std::vector<Command::Arg> PluginReload::args() const { return {{ "plugin", true }};
--- a/lib/irccd/cmd-plugin-reload.hpp Mon Jul 18 22:39:14 2016 +0200 +++ b/lib/irccd/cmd-plugin-reload.hpp Wed Aug 10 10:33:21 2016 +0200 @@ -42,11 +42,6 @@ IRCCD_EXPORT PluginReload(); /** - * \copydoc Command::help - */ - IRCCD_EXPORT std::string help() const override; - - /** * \copydoc Command::args */ IRCCD_EXPORT std::vector<Arg> args() const override;
--- a/lib/irccd/cmd-plugin-unload.cpp Mon Jul 18 22:39:14 2016 +0200 +++ b/lib/irccd/cmd-plugin-unload.cpp Wed Aug 10 10:33:21 2016 +0200 @@ -26,15 +26,10 @@ namespace command { PluginUnload::PluginUnload() - : Command("plugin-unload", "Plugins") + : Command("plugin-unload", "Plugins", "Unload a plugin") { } -std::string PluginUnload::help() const -{ - return "Unload a plugin."; -} - std::vector<Command::Arg> PluginUnload::args() const { return {{ "plugin", true }};
--- a/lib/irccd/cmd-plugin-unload.hpp Mon Jul 18 22:39:14 2016 +0200 +++ b/lib/irccd/cmd-plugin-unload.hpp Wed Aug 10 10:33:21 2016 +0200 @@ -42,11 +42,6 @@ IRCCD_EXPORT PluginUnload(); /** - * \copydoc Command::help - */ - IRCCD_EXPORT std::string help() const override; - - /** * \copydoc Command::args */ IRCCD_EXPORT std::vector<Arg> args() const override;
--- a/lib/irccd/cmd-server-cmode.cpp Mon Jul 18 22:39:14 2016 +0200 +++ b/lib/irccd/cmd-server-cmode.cpp Wed Aug 10 10:33:21 2016 +0200 @@ -26,15 +26,10 @@ namespace command { ServerChannelMode::ServerChannelMode() - : Command("server-cmode", "Server") + : Command("server-cmode", "Server", "Change a channel mode") { } -std::string ServerChannelMode::help() const -{ - return ""; -} - std::vector<Command::Arg> ServerChannelMode::args() const { return {
--- a/lib/irccd/cmd-server-cmode.hpp Mon Jul 18 22:39:14 2016 +0200 +++ b/lib/irccd/cmd-server-cmode.hpp Wed Aug 10 10:33:21 2016 +0200 @@ -42,11 +42,6 @@ IRCCD_EXPORT ServerChannelMode(); /** - * \copydoc Command::help - */ - IRCCD_EXPORT std::string help() const override; - - /** * \copydoc Command::args */ IRCCD_EXPORT std::vector<Arg> args() const override;
--- a/lib/irccd/cmd-server-cnotice.cpp Mon Jul 18 22:39:14 2016 +0200 +++ b/lib/irccd/cmd-server-cnotice.cpp Wed Aug 10 10:33:21 2016 +0200 @@ -26,15 +26,10 @@ namespace command { ServerChannelNotice::ServerChannelNotice() - : Command("server-cnotice", "Server") + : Command("server-cnotice", "Server", "Send a channel notice") { } -std::string ServerChannelNotice::help() const -{ - return ""; -} - std::vector<Command::Arg> ServerChannelNotice::args() const { return {
--- a/lib/irccd/cmd-server-cnotice.hpp Mon Jul 18 22:39:14 2016 +0200 +++ b/lib/irccd/cmd-server-cnotice.hpp Wed Aug 10 10:33:21 2016 +0200 @@ -51,11 +51,6 @@ IRCCD_EXPORT ServerChannelNotice(); /** - * \copydoc Command::help - */ - IRCCD_EXPORT std::string help() const override; - - /** * \copydoc Command::args */ IRCCD_EXPORT std::vector<Arg> args() const override;
--- a/lib/irccd/cmd-server-connect.cpp Mon Jul 18 22:39:14 2016 +0200 +++ b/lib/irccd/cmd-server-connect.cpp Wed Aug 10 10:33:21 2016 +0200 @@ -35,15 +35,10 @@ namespace command { ServerConnect::ServerConnect() - : Command("server-connect", "Server") + : Command("server-connect", "Server", "Connect to a server") { } -std::string ServerConnect::help() const -{ - return "Connect to a server."; -} - std::vector<Command::Option> ServerConnect::options() const { return {
--- a/lib/irccd/cmd-server-connect.hpp Mon Jul 18 22:39:14 2016 +0200 +++ b/lib/irccd/cmd-server-connect.hpp Wed Aug 10 10:33:21 2016 +0200 @@ -42,11 +42,6 @@ IRCCD_EXPORT ServerConnect(); /** - * \copydoc Command::help - */ - IRCCD_EXPORT std::string help() const override; - - /** * \copydoc Command::options */ IRCCD_EXPORT std::vector<Option> options() const override;
--- a/lib/irccd/cmd-server-disconnect.cpp Mon Jul 18 22:39:14 2016 +0200 +++ b/lib/irccd/cmd-server-disconnect.cpp Wed Aug 10 10:33:21 2016 +0200 @@ -26,15 +26,10 @@ namespace command { ServerDisconnect::ServerDisconnect() - : Command("server-disconnect", "Server") + : Command("server-disconnect", "Server", "Disconnect one or more servers") { } -std::string ServerDisconnect::help() const -{ - return ""; -} - std::vector<Command::Arg> ServerDisconnect::args() const { return {{ "server", false }};
--- a/lib/irccd/cmd-server-disconnect.hpp Mon Jul 18 22:39:14 2016 +0200 +++ b/lib/irccd/cmd-server-disconnect.hpp Wed Aug 10 10:33:21 2016 +0200 @@ -42,11 +42,6 @@ IRCCD_EXPORT ServerDisconnect(); /** - * \copydoc Command::help - */ - IRCCD_EXPORT std::string help() const override; - - /** * Get list of arguments required. * * \return the arguments required
--- a/lib/irccd/cmd-server-info.cpp Mon Jul 18 22:39:14 2016 +0200 +++ b/lib/irccd/cmd-server-info.cpp Wed Aug 10 10:33:21 2016 +0200 @@ -28,15 +28,10 @@ namespace command { ServerInfo::ServerInfo() - : Command("server-info", "Server") + : Command("server-info", "Server", "Get server information") { } -std::string ServerInfo::help() const -{ - return ""; -} - std::vector<Command::Arg> ServerInfo::args() const { return {{ "server", true }};
--- a/lib/irccd/cmd-server-info.hpp Mon Jul 18 22:39:14 2016 +0200 +++ b/lib/irccd/cmd-server-info.hpp Wed Aug 10 10:33:21 2016 +0200 @@ -42,11 +42,6 @@ IRCCD_EXPORT ServerInfo(); /** - * \copydoc Command::help - */ - IRCCD_EXPORT std::string help() const override; - - /** * \copydoc Command::args */ IRCCD_EXPORT std::vector<Arg> args() const override;
--- a/lib/irccd/cmd-server-invite.cpp Mon Jul 18 22:39:14 2016 +0200 +++ b/lib/irccd/cmd-server-invite.cpp Wed Aug 10 10:33:21 2016 +0200 @@ -26,15 +26,10 @@ namespace command { ServerInvite::ServerInvite() - : Command("server-invite", "Server") + : Command("server-invite", "Server", "Invite someone into a channel") { } -std::string ServerInvite::help() const -{ - return ""; -} - std::vector<Command::Arg> ServerInvite::args() const { return {
--- a/lib/irccd/cmd-server-invite.hpp Mon Jul 18 22:39:14 2016 +0200 +++ b/lib/irccd/cmd-server-invite.hpp Wed Aug 10 10:33:21 2016 +0200 @@ -42,11 +42,6 @@ IRCCD_EXPORT ServerInvite(); /** - * \copydoc Command::help - */ - IRCCD_EXPORT std::string help() const override; - - /** * \copydoc Command::args */ IRCCD_EXPORT std::vector<Arg> args() const override;
--- a/lib/irccd/cmd-server-join.cpp Mon Jul 18 22:39:14 2016 +0200 +++ b/lib/irccd/cmd-server-join.cpp Wed Aug 10 10:33:21 2016 +0200 @@ -26,15 +26,10 @@ namespace command { ServerJoin::ServerJoin() - : Command("server-join", "Server") + : Command("server-join", "Server", "Join a channel") { } -std::string ServerJoin::help() const -{ - return ""; -} - std::vector<Command::Arg> ServerJoin::args() const { return {
--- a/lib/irccd/cmd-server-join.hpp Mon Jul 18 22:39:14 2016 +0200 +++ b/lib/irccd/cmd-server-join.hpp Wed Aug 10 10:33:21 2016 +0200 @@ -42,11 +42,6 @@ IRCCD_EXPORT ServerJoin(); /** - * \copydoc Command::help - */ - IRCCD_EXPORT std::string help() const override; - - /** * \copydoc Command::args */ IRCCD_EXPORT std::vector<Arg> args() const override;
--- a/lib/irccd/cmd-server-kick.cpp Mon Jul 18 22:39:14 2016 +0200 +++ b/lib/irccd/cmd-server-kick.cpp Wed Aug 10 10:33:21 2016 +0200 @@ -26,15 +26,10 @@ namespace command { ServerKick::ServerKick() - : Command("server-kick", "Server") + : Command("server-kick", "Server", "Kick someone from a channel") { } -std::string ServerKick::help() const -{ - return ""; -} - std::vector<Command::Arg> ServerKick::args() const { return {
--- a/lib/irccd/cmd-server-kick.hpp Mon Jul 18 22:39:14 2016 +0200 +++ b/lib/irccd/cmd-server-kick.hpp Wed Aug 10 10:33:21 2016 +0200 @@ -42,11 +42,6 @@ IRCCD_EXPORT ServerKick(); /** - * \copydoc Command::help - */ - IRCCD_EXPORT std::string help() const override; - - /** * \copydoc Command::args */ IRCCD_EXPORT std::vector<Arg> args() const override;
--- a/lib/irccd/cmd-server-list.cpp Mon Jul 18 22:39:14 2016 +0200 +++ b/lib/irccd/cmd-server-list.cpp Wed Aug 10 10:33:21 2016 +0200 @@ -28,15 +28,10 @@ namespace command { ServerList::ServerList() - : Command("server-list", "Server") + : Command("server-list", "Server", "Get the list of servers") { } -std::string ServerList::help() const -{ - return ""; -} - nlohmann::json ServerList::exec(Irccd &irccd, const nlohmann::json &) const { auto json = nlohmann::json::object(); @@ -52,9 +47,14 @@ void ServerList::result(Irccdctl &, const nlohmann::json &response) const { - if (response.count("list") != 0) - for (const auto &n : response["list"]) - std::cout << n.dump() << std::endl; + auto list = response.find("list"); + + if (list == response.end()) + return; + + for (auto v : *list) + if (v.is_string()) + std::cout << v.get<std::string>() << std::endl; } } // !command
--- a/lib/irccd/cmd-server-list.hpp Mon Jul 18 22:39:14 2016 +0200 +++ b/lib/irccd/cmd-server-list.hpp Wed Aug 10 10:33:21 2016 +0200 @@ -42,11 +42,6 @@ IRCCD_EXPORT ServerList(); /** - * \copydoc Command::help - */ - IRCCD_EXPORT std::string help() const override; - - /** * \copydoc Command::exec */ IRCCD_EXPORT nlohmann::json exec(Irccd &irccd, const nlohmann::json &request) const override;
--- a/lib/irccd/cmd-server-me.cpp Mon Jul 18 22:39:14 2016 +0200 +++ b/lib/irccd/cmd-server-me.cpp Wed Aug 10 10:33:21 2016 +0200 @@ -26,15 +26,10 @@ namespace command { ServerMe::ServerMe() - : Command("server-me", "Server") + : Command("server-me", "Server", "Send an action emote") { } -std::string ServerMe::help() const -{ - return ""; -} - std::vector<Command::Arg> ServerMe::args() const { return {
--- a/lib/irccd/cmd-server-me.hpp Mon Jul 18 22:39:14 2016 +0200 +++ b/lib/irccd/cmd-server-me.hpp Wed Aug 10 10:33:21 2016 +0200 @@ -42,11 +42,6 @@ IRCCD_EXPORT ServerMe(); /** - * \copydoc Command::help - */ - IRCCD_EXPORT std::string help() const override; - - /** * \copydoc Command::args */ IRCCD_EXPORT std::vector<Arg> args() const override;
--- a/lib/irccd/cmd-server-message.cpp Mon Jul 18 22:39:14 2016 +0200 +++ b/lib/irccd/cmd-server-message.cpp Wed Aug 10 10:33:21 2016 +0200 @@ -26,15 +26,10 @@ namespace command { ServerMessage::ServerMessage() - : Command("server-message", "Server") + : Command("server-message", "Server", "Send a message") { } -std::string ServerMessage::help() const -{ - return ""; -} - std::vector<Command::Arg> ServerMessage::args() const { return {
--- a/lib/irccd/cmd-server-message.hpp Mon Jul 18 22:39:14 2016 +0200 +++ b/lib/irccd/cmd-server-message.hpp Wed Aug 10 10:33:21 2016 +0200 @@ -42,11 +42,6 @@ IRCCD_EXPORT ServerMessage(); /** - * \copydoc Command::help - */ - IRCCD_EXPORT std::string help() const override; - - /** * \copydoc Command::args */ IRCCD_EXPORT std::vector<Arg> args() const override;
--- a/lib/irccd/cmd-server-mode.cpp Mon Jul 18 22:39:14 2016 +0200 +++ b/lib/irccd/cmd-server-mode.cpp Wed Aug 10 10:33:21 2016 +0200 @@ -26,15 +26,10 @@ namespace command { ServerMode::ServerMode() - : Command("server-mode", "Server") + : Command("server-mode", "Server", "Change your mode") { } -std::string ServerMode::help() const -{ - return ""; -} - std::vector<Command::Arg> ServerMode::args() const { return {
--- a/lib/irccd/cmd-server-mode.hpp Mon Jul 18 22:39:14 2016 +0200 +++ b/lib/irccd/cmd-server-mode.hpp Wed Aug 10 10:33:21 2016 +0200 @@ -42,11 +42,6 @@ IRCCD_EXPORT ServerMode(); /** - * \copydoc Command::help - */ - IRCCD_EXPORT std::string help() const override; - - /** * \copydoc Command::args */ IRCCD_EXPORT std::vector<Arg> args() const override;
--- a/lib/irccd/cmd-server-nick.cpp Mon Jul 18 22:39:14 2016 +0200 +++ b/lib/irccd/cmd-server-nick.cpp Wed Aug 10 10:33:21 2016 +0200 @@ -26,15 +26,10 @@ namespace command { ServerNick::ServerNick() - : Command("server-nick", "Server") + : Command("server-nick", "Server", "Change your nickname") { } -std::string ServerNick::help() const -{ - return ""; -} - std::vector<Command::Arg> ServerNick::args() const { return {
--- a/lib/irccd/cmd-server-nick.hpp Mon Jul 18 22:39:14 2016 +0200 +++ b/lib/irccd/cmd-server-nick.hpp Wed Aug 10 10:33:21 2016 +0200 @@ -42,11 +42,6 @@ IRCCD_EXPORT ServerNick(); /** - * \copydoc Command::help - */ - IRCCD_EXPORT std::string help() const override; - - /** * \copydoc Command::args */ IRCCD_EXPORT std::vector<Arg> args() const override;
--- a/lib/irccd/cmd-server-notice.cpp Mon Jul 18 22:39:14 2016 +0200 +++ b/lib/irccd/cmd-server-notice.cpp Wed Aug 10 10:33:21 2016 +0200 @@ -26,15 +26,10 @@ namespace command { ServerNotice::ServerNotice() - : Command("server-notice", "Server") + : Command("server-notice", "Server", "Send a private notice") { } -std::string ServerNotice::help() const -{ - return ""; -} - std::vector<Command::Arg> ServerNotice::args() const { return {
--- a/lib/irccd/cmd-server-notice.hpp Mon Jul 18 22:39:14 2016 +0200 +++ b/lib/irccd/cmd-server-notice.hpp Wed Aug 10 10:33:21 2016 +0200 @@ -42,11 +42,6 @@ IRCCD_EXPORT ServerNotice(); /** - * \copydoc Command::help - */ - IRCCD_EXPORT std::string help() const override; - - /** * \copydoc Command::args */ IRCCD_EXPORT std::vector<Arg> args() const override;
--- a/lib/irccd/cmd-server-part.cpp Mon Jul 18 22:39:14 2016 +0200 +++ b/lib/irccd/cmd-server-part.cpp Wed Aug 10 10:33:21 2016 +0200 @@ -26,15 +26,10 @@ namespace command { ServerPart::ServerPart() - : Command("server-part", "Server") + : Command("server-part", "Server", "Leave a channel") { } -std::string ServerPart::help() const -{ - return ""; -} - std::vector<Command::Arg> ServerPart::args() const { return {
--- a/lib/irccd/cmd-server-part.hpp Mon Jul 18 22:39:14 2016 +0200 +++ b/lib/irccd/cmd-server-part.hpp Wed Aug 10 10:33:21 2016 +0200 @@ -42,11 +42,6 @@ IRCCD_EXPORT ServerPart(); /** - * \copydoc Command::help - */ - IRCCD_EXPORT std::string help() const override; - - /** * \copydoc Command::args */ IRCCD_EXPORT std::vector<Arg> args() const override;
--- a/lib/irccd/cmd-server-reconnect.cpp Mon Jul 18 22:39:14 2016 +0200 +++ b/lib/irccd/cmd-server-reconnect.cpp Wed Aug 10 10:33:21 2016 +0200 @@ -26,15 +26,10 @@ namespace command { ServerReconnect::ServerReconnect() - : Command("server-reconnect", "Server") + : Command("server-reconnect", "Server", "Force reconnection of one or more servers") { } -std::string ServerReconnect::help() const -{ - return ""; -} - std::vector<Command::Arg> ServerReconnect::args() const { return {{ "server", false }};
--- a/lib/irccd/cmd-server-reconnect.hpp Mon Jul 18 22:39:14 2016 +0200 +++ b/lib/irccd/cmd-server-reconnect.hpp Wed Aug 10 10:33:21 2016 +0200 @@ -42,11 +42,6 @@ IRCCD_EXPORT ServerReconnect(); /** - * \copydoc Command::help - */ - IRCCD_EXPORT std::string help() const override; - - /** * \copydoc Command::args */ IRCCD_EXPORT std::vector<Arg> args() const override;
--- a/lib/irccd/cmd-server-topic.cpp Mon Jul 18 22:39:14 2016 +0200 +++ b/lib/irccd/cmd-server-topic.cpp Wed Aug 10 10:33:21 2016 +0200 @@ -26,15 +26,10 @@ namespace command { ServerTopic::ServerTopic() - : Command("server-topic", "Server") + : Command("server-topic", "Server", "Change a channel topic") { } -std::string ServerTopic::help() const -{ - return ""; -} - std::vector<Command::Arg> ServerTopic::args() const { return {
--- a/lib/irccd/cmd-server-topic.hpp Mon Jul 18 22:39:14 2016 +0200 +++ b/lib/irccd/cmd-server-topic.hpp Wed Aug 10 10:33:21 2016 +0200 @@ -42,11 +42,6 @@ IRCCD_EXPORT ServerTopic(); /** - * \copydoc Command::help - */ - IRCCD_EXPORT std::string help() const override; - - /** * \copydoc Command::args */ IRCCD_EXPORT std::vector<Arg> args() const override;
--- a/lib/irccd/cmd-watch.cpp Mon Jul 18 22:39:14 2016 +0200 +++ b/lib/irccd/cmd-watch.cpp Wed Aug 10 10:33:21 2016 +0200 @@ -198,7 +198,8 @@ } // !namespace Watch::Watch() - : Command("watch", "General") + : Command( + "watch", "General", "Start watching irccd events") { } @@ -207,17 +208,6 @@ return {{ "format", "f", "format", "format", "output format" }}; } -std::string Watch::help() const -{ - std::ostringstream oss; - - oss << "Start watching irccd events.\n\n"; - oss << "You can use different output formats, native which is a human readable\n"; - oss << "format or json, pretty formatted json."; - - return oss.str(); -} - nlohmann::json Watch::request(Irccdctl &ctl, const CommandRequest &request) const { std::string format = request.optionOr("format", "native"); @@ -225,9 +215,9 @@ if (format != "native" && format != "json") throw std::invalid_argument("invalid format given: " + format); - for (;;) { + while (ctl.connection().isConnected()) { try { - auto object = ctl.connection().next(-1); + auto object = ctl.next(); auto event = object.find("event"); if (event == object.end() || !event->is_string()) @@ -249,8 +239,6 @@ } } - throw std::runtime_error("connection lost"); - return nullptr; }
--- a/lib/irccd/cmd-watch.hpp Mon Jul 18 22:39:14 2016 +0200 +++ b/lib/irccd/cmd-watch.hpp Wed Aug 10 10:33:21 2016 +0200 @@ -47,11 +47,6 @@ IRCCD_EXPORT std::vector<Option> options() const override; /** - * \copydoc Command::help - */ - IRCCD_EXPORT std::string help() const override; - - /** * \copydoc Command::request */ IRCCD_EXPORT nlohmann::json request(Irccdctl &irccdctl, const CommandRequest &args) const override;
--- a/lib/irccd/command.cpp Mon Jul 18 22:39:14 2016 +0200 +++ b/lib/irccd/command.cpp Wed Aug 10 10:33:21 2016 +0200 @@ -143,6 +143,45 @@ { std::ostringstream oss; + oss << m_name << " "; + + // Options. + auto optlist = options(); + + if (optlist.size() > 0) { + for (const auto &opt : optlist) { + oss << "["; + + /* + * Long options are too big so only show them in the help + * command usage or only if no short option is available. + */ + if (opt.simpleKey().size() > 0) + oss << "-" << opt.simpleKey(); + else if (opt.longKey().size() > 0) + oss << " --" << opt.longKey(); + + oss << (opt.arg().empty() ? "" : " ") << opt.arg() << "] "; + } + } + + // Arguments. + auto argslist = args(); + + if (argslist.size() > 0) { + for (const auto &arg : argslist) + oss << (arg.required() ? "" : "[") + << arg.name() + << (arg.required() ? "" : "]") << " "; + } + + return oss.str(); +} + +std::string Command::help() const +{ + std::ostringstream oss; + oss << "usage: " << sys::programName() << " " << m_name; // Options summary. @@ -158,7 +197,7 @@ } // Description. - oss << "\n\n" << help() << "\n\n"; + oss << "\n\n" << m_description << "\n\n"; // Options. if (options().size() > 0) {
--- a/lib/irccd/command.hpp Mon Jul 18 22:39:14 2016 +0200 +++ b/lib/irccd/command.hpp Wed Aug 10 10:33:21 2016 +0200 @@ -336,6 +336,7 @@ private: std::string m_name; std::string m_category; + std::string m_description; bool m_visible; public: @@ -346,11 +347,16 @@ * \pre category must not be empty * \param name the command name (e.g. server-list) * \param category the category (e.g. Server) + * \param description a one line description with no dots, no new line * \param visible true if the command should be visible without verbosity */ - inline Command(std::string name, std::string category, bool visible = true) noexcept + inline Command(std::string name, + std::string category, + std::string description, + bool visible = true) noexcept : m_name(std::move(name)) , m_category(std::move(category)) + , m_description(std::move(description)) , m_visible(visible) { assert(!m_name.empty()); @@ -385,6 +391,16 @@ } /** + * Get the command description. + * + * \return the description + */ + inline const std::string &description() const noexcept + { + return m_description; + } + + /** * Hide the command in non-verbose mode. * * \return true if the command should be visible in non-verbose mode @@ -406,7 +422,7 @@ * * \return the help message */ - virtual std::string help() const = 0; + IRCCD_EXPORT std::string help() const; /** * Get the supported irccdctl options. @@ -489,7 +505,7 @@ * What to do when receiving the response from irccd. * * This default implementation just check for an error string and shows it if any. - * + * * \param irccdctl the irccdctl instance * \param response the JSON response */
--- a/lib/irccd/config.cpp Mon Jul 18 22:39:14 2016 +0200 +++ b/lib/irccd/config.cpp Wed Aug 10 10:33:21 2016 +0200 @@ -174,9 +174,9 @@ } if (ipv6) - transport = std::make_shared<TransportServerIp>(AF_INET6, move(address), port, !ipv4); + transport = std::make_shared<TransportServerIpv6>(move(address), port, !ipv4); else if (ipv4) - transport = std::make_shared<TransportServerIp>(AF_INET, move(address), port); + transport = std::make_shared<TransportServerIp>(move(address), port); else throw std::invalid_argument("transport: domain must at least have ipv4 or ipv6"); @@ -193,7 +193,7 @@ if (it == sc.end()) throw std::invalid_argument("transport: missing 'path' parameter"); - return std::make_shared<TransportServerUnix>(it->value()); + return std::make_shared<TransportServerLocal>(it->value()); #else (void)sc;
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lib/irccd/conn-state-checking.cpp Wed Aug 10 10:33:21 2016 +0200 @@ -0,0 +1,101 @@ +/* + * conn-state-checking.cpp -- verify irccd instance + * + * Copyright (c) 2013-2016 David Demelier <markand@malikania.fr> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <format.h> + +#include "conn-state-checking.hpp" +#include "conn-state-disconnected.hpp" +#include "conn-state-ready.hpp" +#include "sysconfig.hpp" + +using namespace fmt::literals; + +namespace irccd { + +void Connection::CheckingState::verifyProgram(const nlohmann::json &json) const +{ + auto prog = json.find("program"); + + if (prog == json.end() || !prog->is_string() || prog->get<std::string>() != "irccd") + throw std::runtime_error("not an irccd instance"); +} + +void Connection::CheckingState::verifyVersion(Connection &cnx, const nlohmann::json &json) const +{ + auto getVersionVar = [&] (auto key) { + auto it = json.find(key); + + if (it == json.end() || !it->is_number_unsigned()) + throw std::runtime_error("invalid irccd instance"); + + return *it; + }; + + Info info{ + getVersionVar("major"), + getVersionVar("minor"), + getVersionVar("patch") + }; + + // Ensure compatibility. + if (info.major != IRCCD_VERSION_MAJOR || info.minor > IRCCD_VERSION_MINOR) + throw std::runtime_error("server version too recent {}.{}.{} vs {}.{}.{}"_format( + info.major, info.minor, info.patch, + IRCCD_VERSION_MAJOR, IRCCD_VERSION_MINOR, IRCCD_VERSION_PATCH)); + + // Successfully connected. + cnx.m_stateNext = std::make_unique<ReadyState>(); + cnx.onConnect(info); +} + +void Connection::CheckingState::verify(Connection &cnx) const +{ + auto msg = util::nextNetwork(cnx.m_input); + + if (msg.empty()) + return; + + try { + auto json = nlohmann::json::parse(msg); + + verifyProgram(json); + verifyVersion(cnx, json); + } catch (const std::exception &ex) { + cnx.m_stateNext = std::make_unique<DisconnectedState>(); + cnx.onDisconnect(ex.what()); + } +} + +Connection::Status Connection::CheckingState::status() const noexcept +{ + return Checking; +} + +void Connection::CheckingState::prepare(Connection &cnx, fd_set &in, fd_set &) +{ + FD_SET(cnx.m_socket.handle(), &in); +} + +void Connection::CheckingState::sync(Connection &cnx, fd_set &, fd_set &) +{ + cnx.syncInput(); + + verify(cnx); +} + +} // !irccd
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lib/irccd/conn-state-checking.hpp Wed Aug 10 10:33:21 2016 +0200 @@ -0,0 +1,65 @@ +/* + * conn-state-checking.hpp -- verify irccd instance + * + * Copyright (c) 2013-2016 David Demelier <markand@malikania.fr> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef IRCCD_CONN_STATE_CHECKING_HPP +#define IRCCD_CONN_STATE_CHECKING_HPP + +/** + * \file conn-state-checking.hpp + * \brief Verify irccd instance and version + */ + +#include "conn-state.hpp" + +namespace irccd { + +/** + * \brief State for veryfing connection. + * + * This state is used when socket connection is complete but we have not + * verified that the endpoint is an irccd instance. + * + * This state also verifies that the irccd daemon is compatible with + * our library. + */ +class Connection::CheckingState : public Connection::State { +private: + void verifyProgram(const nlohmann::json &json) const; + void verifyVersion(Connection &cnx, const nlohmann::json &json) const; + void verify(Connection &cnx) const; + +public: + /** + * \copydoc State::status + */ + Status status() const noexcept override; + + /** + * \copydoc State::prepare + */ + void prepare(Connection &cnt, fd_set &in, fd_set &out) override; + + /** + * \copydoc State::sync + */ + void sync(Connection &cnt, fd_set &in, fd_set &out) override; +}; + +} // !irccd + +#endif // !IRCCD_CONN_STATE_CHECKING_HPP
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lib/irccd/conn-state-connecting.cpp Wed Aug 10 10:33:21 2016 +0200 @@ -0,0 +1,54 @@ +/* + * conn-state-connecting.cpp -- connection is in progress + * + * Copyright (c) 2013-2016 David Demelier <markand@malikania.fr> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "conn-state-checking.hpp" +#include "conn-state-connecting.hpp" +#include "conn-state-disconnected.hpp" + +namespace irccd { + +Connection::Status Connection::ConnectingState::status() const noexcept +{ + return Connecting; +} + +void Connection::ConnectingState::prepare(Connection &cnx, fd_set &, fd_set &out) +{ + FD_SET(cnx.m_socket.handle(), &out); +} + +void Connection::ConnectingState::sync(Connection &cnx, fd_set &, fd_set &out) +{ + if (!FD_ISSET(cnx.m_socket.handle(), &out)) + return; + + try { + auto errc = cnx.m_socket.get<int>(SOL_SOCKET, SO_ERROR); + + if (errc != 0) { + cnx.m_stateNext = std::make_unique<DisconnectedState>(); + cnx.onDisconnect(net::error(errc)); + } else + cnx.m_stateNext = std::make_unique<CheckingState>(); + } catch (const std::exception &ex) { + cnx.m_stateNext = std::make_unique<DisconnectedState>(); + cnx.onDisconnect(ex.what()); + } +} + +} // !irccd
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lib/irccd/conn-state-connecting.hpp Wed Aug 10 10:33:21 2016 +0200 @@ -0,0 +1,57 @@ +/* + * conn-state-connecting.hpp -- connection is in progress + * + * Copyright (c) 2013-2016 David Demelier <markand@malikania.fr> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef IRCCD_CONN_STATE_CONNECTING_HPP +#define IRCCD_CONN_STATE_CONNECTING_HPP + +/** + * \file conn-state-connecting.hpp + * \brief Connection is in progress. + */ + +#include "conn-state.hpp" + +namespace irccd { + +/** + * \brief State to complete socket connection. + * + * This state is used to complete the socket connection if it has not been + * completed immediately. + */ +class Connection::ConnectingState : public Connection::State { +public: + /** + * \copydoc State::status + */ + Status status() const noexcept override; + + /** + * \copydoc State::prepare + */ + void prepare(Connection &cnt, fd_set &in, fd_set &out) override; + + /** + * \copydoc State::sync + */ + void sync(Connection &cnt, fd_set &in, fd_set &out) override; +}; + +} // !irccd + +#endif // !IRCCD_CONN_STATE_CONNECTING_HPP
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lib/irccd/conn-state-disconnected.cpp Wed Aug 10 10:33:21 2016 +0200 @@ -0,0 +1,36 @@ +/* + * conn-state-disconnected.cpp -- disconnected state + * + * Copyright (c) 2013-2016 David Demelier <markand@malikania.fr> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "conn-state-disconnected.hpp" + +namespace irccd { + +Connection::Status Connection::DisconnectedState::status() const noexcept +{ + return Disconnected; +} + +void Connection::DisconnectedState::prepare(Connection &, fd_set &, fd_set &) +{ +} + +void Connection::DisconnectedState::sync(Connection &, fd_set &, fd_set &) +{ +} + +} // !irccd
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lib/irccd/conn-state-disconnected.hpp Wed Aug 10 10:33:21 2016 +0200 @@ -0,0 +1,57 @@ +/* + * conn-state-disconnected.hpp -- disconnected state + * + * Copyright (c) 2013-2016 David Demelier <markand@malikania.fr> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef IRCCD_CONN_STATE_DISCONNECTED_HPP +#define IRCCD_CONN_STATE_DISCONNECTED_HPP + +/** + * \file conn-state-disconnected.hpp + * \brief Disconnected. + */ +#include "conn-state.hpp" + +namespace irccd { + +/** + * \brief Disconnected state. + * + * No-op state. + * + * This state does nothing, it is the default one and the last one. + */ +class Connection::DisconnectedState : public Connection::State { +public: + /** + * \copydoc State::status + */ + Status status() const noexcept override; + + /** + * \copydoc State::prepare + */ + void prepare(Connection &cnt, fd_set &in, fd_set &out) override; + + /** + * \copydoc State::sync + */ + void sync(Connection &cnt, fd_set &in, fd_set &out) override; +}; + +} // !irccd + +#endif // !IRCCD_CONN_STATE_DISCONNECTED_HPP
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lib/irccd/conn-state-ready.cpp Wed Aug 10 10:33:21 2016 +0200 @@ -0,0 +1,72 @@ +/* + * conn-state-ready.cpp -- connection is ready for I/O + * + * Copyright (c) 2013-2016 David Demelier <markand@malikania.fr> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "conn-state-ready.hpp" + +namespace irccd { + +namespace { + +void parse(Connection &cnx, const std::string &message) +{ + try { + auto json = nlohmann::json::parse(message); + + if (!json.is_object()) + return; + + cnx.onMessage(json); + } catch (const std::exception &) { + } +} + +} // !namespace + +Connection::Status Connection::ReadyState::status() const noexcept +{ + return Ready; +} + +void Connection::ReadyState::prepare(Connection &cnx, fd_set &in, fd_set &out) +{ + FD_SET(cnx.m_socket.handle(), &in); + + if (!cnx.m_output.empty()) + FD_SET(cnx.m_socket.handle(), &out); +} + +void Connection::ReadyState::sync(Connection &cnx, fd_set &in, fd_set &out) +{ + if (FD_ISSET(cnx.m_socket.handle(), &out)) + cnx.syncOutput(); + + if (FD_ISSET(cnx.m_socket.handle(), &in)) { + cnx.syncInput(); + + std::string msg; + + do { + msg = util::nextNetwork(cnx.m_input); + + if (!msg.empty()) + parse(cnx, msg); + } while (!msg.empty()); + } +} + +} // !irccd
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lib/irccd/conn-state-ready.hpp Wed Aug 10 10:33:21 2016 +0200 @@ -0,0 +1,60 @@ +/* + * conn-state-ready.hpp -- connection is ready for I/O + * + * Copyright (c) 2013-2016 David Demelier <markand@malikania.fr> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef IRCCD_CONN_STATE_READY_HPP +#define IRCCD_CONN_STATE_READY_HPP + +/** + * \file conn-state-ready.hpp + * \brief Connection is ready. + */ + +#include "conn-state.hpp" + +namespace irccd { + +/** + * \brief Ready state. + * + * This state is used when the connection to irccd is complete, including + * irccd daemon verification and optional handshaking. + * + * It's the only state that may trigger onEvent and onResponse signals + * from the Connection. + */ +class Connection::ReadyState : public Connection::State { +public: + /** + * \copydoc State::status + */ + Status status() const noexcept override; + + /** + * \copydoc State::prepare + */ + void prepare(Connection &cnt, fd_set &in, fd_set &out) override; + + /** + * \copydoc State::sync + */ + void sync(Connection &cnt, fd_set &in, fd_set &out) override; +}; + +} // !irccd + +#endif // !IRCCD_CONN_STATE_READY_HPP
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lib/irccd/conn-state.hpp Wed Aug 10 10:33:21 2016 +0200 @@ -0,0 +1,75 @@ +/* + * conn-state.hpp -- abstract state for Connection object + * + * Copyright (c) 2013-2016 David Demelier <markand@malikania.fr> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef IRCCD_CONN_STATE_HPP +#define IRCCD_CONN_STATE_HPP + +/** + * \file conn-state.hpp + * \brief State for Connection. + */ + +#include "connection.hpp" + +namespace irccd { + +/** + * \brief Abstract state interface for Connection + * + * Abstract state interface for Connection. + * + * The Connection is event based, you should not throw exceptions from the + * prepare or sync functions, instead you should change the Connection state + * and emit the onDisconnect signal. + */ +class Connection::State { +public: + /** + * Return the state. + * + * \return the state + */ + virtual Status status() const noexcept = 0; + + /** + * Prepare the input and output sets. + * + * You should not change the connection state in this function. + * + * \param cnt the connection object + * \param in the input set + * \param out the output set + */ + virtual void prepare(Connection &cnt, fd_set &in, fd_set &out) = 0; + + /** + * Synchronize network I/O in the implementation. + * + * You should change the connection state using cnx.m_nextState + * if needed. + * + * \param cnt the connection object + * \param in the input set + * \param out the output set + */ + virtual void sync(Connection &cnt, fd_set &in, fd_set &out) = 0; +}; + +} // !irccd + +#endif // !IRCCD_CONN_STATE_HPP
--- a/lib/irccd/connection.cpp Mon Jul 18 22:39:14 2016 +0200 +++ b/lib/irccd/connection.cpp Wed Aug 10 10:33:21 2016 +0200 @@ -16,34 +16,102 @@ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ +#include <stdexcept> + #include "connection.hpp" -#include "logger.hpp" +#include "conn-state-connecting.hpp" +#include "conn-state-checking.hpp" +#include "conn-state-disconnected.hpp" #include "util.hpp" namespace irccd { -nlohmann::json Connection::next(const std::string &name, int timeout) +void Connection::syncInput() { - m_timer.reset(); + try { + std::string buffer; + + buffer.resize(512); + buffer.resize(m_socket.recv(&buffer[0], buffer.size())); + + if (buffer.empty()) + throw std::runtime_error("connection lost"); + + m_input += std::move(buffer); + } catch (const std::exception &ex) { + m_stateNext = std::make_unique<DisconnectedState>(); + onDisconnect(ex.what()); + } +} - for (;;) { - auto object = next(clamp(timeout)); - auto response = object.find("response"); +void Connection::syncOutput() +{ + try { + auto ns = m_socket.send(m_output.data(), m_output.length()); - if (response != object.end() && util::json::toString(*response) == name) - return object; + if (ns > 0) + m_output.erase(0, ns); + } catch (const std::exception &ex) { + m_stateNext = std::make_unique<DisconnectedState>(); + onDisconnect(ex.what()); } +} - throw std::runtime_error("connection lost"); +Connection::Connection() + : m_state(std::make_unique<DisconnectedState>()) +{ +} + +Connection::~Connection() = default; + +Connection::Status Connection::status() const noexcept +{ + return m_state->status(); } -void Connection::verify(const std::string &name, int timeout) +void Connection::connect(const net::Address &address) +{ + assert(status() == Disconnected); + + try { + m_socket = net::TcpSocket(address.domain(), 0); + m_socket.set(net::option::SockBlockMode(false)); + m_socket.connect(address); + m_state = std::make_unique<CheckingState>(); + } catch (const net::WouldBlockError &) { + m_state = std::make_unique<ConnectingState>(); + } catch (const std::exception &ex) { + m_state = std::make_unique<DisconnectedState>(); + onDisconnect(ex.what()); + } +} + +void Connection::prepare(fd_set &in, fd_set &out, net::Handle &max) { - auto object = next(name, timeout); - auto value = object.at("status").dump(); + try { + m_state->prepare(*this, in, out); + + if (m_socket.handle() > max) + max = m_socket.handle(); + } catch (const std::exception &ex) { + m_state = std::make_unique<DisconnectedState>(); + onDisconnect(ex.what()); + } +} - if (!value.empty() && value != "ok") - throw std::runtime_error(object.at("error").dump()); +void Connection::sync(fd_set &in, fd_set &out) +{ + try { + m_state->sync(*this, in, out); + + if (m_stateNext) { + m_state = std::move(m_stateNext); + m_stateNext = nullptr; + } + } catch (const std::exception &ex) { + m_state = std::make_unique<DisconnectedState>(); + onDisconnect(ex.what()); + } } } // !irccd
--- a/lib/irccd/connection.hpp Mon Jul 18 22:39:14 2016 +0200 +++ b/lib/irccd/connection.hpp Wed Aug 10 10:33:21 2016 +0200 @@ -25,211 +25,192 @@ */ #include <cassert> -#include <stdexcept> +#include <memory> +#include <string> -#include "elapsed-timer.hpp" -#include "json.hpp" #include "net.hpp" -#include "sysconfig.hpp" -#include "system.hpp" -#include "util.hpp" +#include "signals.hpp" +#include "service.hpp" namespace irccd { /** - * \class Connection - * \brief Abstract class for connecting to irccd from Ip or Local addresses. + * \brief Low level connection to irccd instance. + * + * This class is an event-based connection to an irccd instance. You can use + * it directly if you want to issue commands to irccd in an asynchronous way. + * + * Being asynchronous makes mixing the event loop with this connection easier. + * + * It is implemented as a finite state machine as it may requires several + * roundtrips between the controller and irccd. + * + * Be aware that there are no namespaces for commands, if you plan to use + * Irccdctl class and you also connect the onMessage signal, irccdctl will also + * use it. Do not use irccdctl directly if this is a concern. + * + * The state may change as following. + * + * [o] + * | +----------------------------+ + * v v | + * +--------------+ +----------+ +----------------+ + * | Disconnected |-->| Checking |---->| Authenticating | + * +--------------+ +----------+ +----------------+ + * ^ | ^ | + * | | | v + * | | +------------+ +-------+ + * | +----->| Connecting |<--| Ready | + * | +------------+ +-------+ + * | | + * ------------------------------------+ + * + * Note: authenticating state is not implemented yet. */ -class Connection { -protected: +class Connection : public Service { +public: /** - * Timer to track elapsed time. + * \brief The current connection state. */ - ElapsedTimer m_timer; + enum Status { + Disconnected, //!< Socket is closed + Connecting, //!< Connection is in progress + Checking, //!< Connection is checking irccd daemon + Authenticating, //!< Connection is authenticating + Ready //!< Socket is ready for I/O + }; /** - * Clamp the time to wait to be sure that it will be never less than 0. + * \brief Irccd information. + */ + class Info { + public: + unsigned short major; + unsigned short minor; + unsigned short patch; + }; + + /** + * onConnect + * -------------------------------------------------------------- + * + * Connection was successfull. + */ + Signal<const Info &> onConnect; + + /** + * onMessage + * --------------------------------------------------------------- + * + * Upon message. */ - inline int clamp(int timeout) noexcept - { - return timeout < 0 ? -1 : (timeout - (int)m_timer.elapsed() < 0) ? 0 : (timeout - m_timer.elapsed()); - } + Signal<const nlohmann::json &> onMessage; + + /** + * onDisconnect + * -------------------------------------------------------------- + * + * A fatal error occured resulting in disconnection. + */ + Signal<const std::string &> onDisconnect; + +private: + std::string m_input; + std::string m_output; + +public: + /** + * \brief TEST + */ + class State; + class DisconnectedState; + class ConnectingState; + /** + * \brief TEST + */ + class CheckingState; + class ReadyState; + +private: + std::unique_ptr<State> m_state; + std::unique_ptr<State> m_stateNext; + +protected: + net::TcpSocket m_socket{net::Invalid}; + + void syncInput(); + void syncOutput(); public: /** * Default constructor. */ - Connection() = default; + Connection(); /** * Default destructor. */ - virtual ~Connection() = default; - - /** - * Wait for the next requested response. - * - * \param name the response name - * \param timeout the optional timeout - * \return the object - * \throw net::Error on errors or on timeout - */ - IRCCD_EXPORT nlohmann::json next(const std::string &name, int timeout = 30000); - - /** - * Just wait if the operation succeeded. - * - * \param name the response name - * \param timeout the timeout - */ - IRCCD_EXPORT void verify(const std::string &name, int timeout = 30000); - - /** - * Try to connect to the host. - * - * \param timeout the maximum time in milliseconds - * \throw net::Error on errors or timeout - */ - virtual void connect(int timeout = 30000) = 0; + virtual ~Connection(); /** - * Try to send the message in 30 seconds. The message must not end with \\r\\n\\r\\n, it is added automatically. + * Send an asynchronous request to irccd. * - * \pre msg must not be empty - * \param msg the message to send - * \param timeout the maximum time in milliseconds - * \throw net::Error on errors + * \pre json.is_object + * \param json the JSON object */ - virtual void send(std::string msg, int timeout = 30000) = 0; + inline void request(const nlohmann::json &json) + { + assert(json.is_object()); + + m_output += json.dump(); + m_output += "\r\n\r\n"; + } /** - * Get the next event from irccd. - * - * This functions throws if the connection is lost. + * Get the underlying socket handle. * - * \param timeout the maximum time in milliseconds - * \return the next event - * \throw net::Error on errors or disconnection + * \return the handle */ - virtual nlohmann::json next(int timeout = 30000) = 0; -}; - -/** - * \class ConnectionBase - * \brief Implementation for Ip or Local. - */ -template <typename Address> -class ConnectionBase : public Connection { -private: - net::SocketTcp<Address> m_socket; - net::Listener<> m_listener; - Address m_address; - - // Input buffer. - std::string m_input; - -public: - /** - * Construct the socket but do not connect immediately. - * - * \param address the address - */ - inline ConnectionBase(Address address) - : m_address(std::move(address)) + inline net::Handle handle() const noexcept { - m_socket.set(net::option::SockBlockMode{false}); - m_listener.set(m_socket.handle(), net::Condition::Readable); + return m_socket.handle(); } /** - * \copydoc Connection::connect - */ - void connect(int timeout) override; - - /** - * \copydoc Connection::send - */ - void send(std::string msg, int timeout) override; - - /** - * \copydoc Connection::next(int) + * Shorthand for state() != Disconnected. + * + * \return true if state() != Disconnected */ - nlohmann::json next(int timeout) override; -}; - -template <typename Address> -void ConnectionBase<Address>::connect(int timeout) -{ - net::Condition cond; - - m_socket.connect(m_address, cond); - m_timer.reset(); - - while (cond != net::Condition::None) { - if (timeout >= 0 && m_timer.elapsed() >= static_cast<unsigned>(timeout)) - throw std::runtime_error("timeout while connecting"); - - m_listener.remove(m_socket.handle()); - m_listener.set(m_socket.handle(), cond); - m_listener.wait(timeout - m_timer.elapsed()); - m_socket.resumeConnect(cond); + inline bool isConnected() const noexcept + { + return status() != Disconnected; } - m_listener.set(m_socket.handle(), net::Condition::Readable); -} - -template <typename Address> -void ConnectionBase<Address>::send(std::string msg, int timeout) -{ - assert(!msg.empty()); - - // Add termination. - msg += "\r\n\r\n"; - - m_listener.remove(m_socket.handle()); - m_listener.set(m_socket.handle(), net::Condition::Writable); - m_timer.reset(); - - while (!msg.empty()) { - // Do not wait the time that is already passed. - m_listener.wait(clamp(timeout)); - - // Try to send at most as possible. - msg.erase(0, m_socket.send(msg)); - } - - // Timeout? - if (!msg.empty()) - throw std::runtime_error("operation timed out while sending to irccd"); -} + /** + * Get the current state. + * + * \return the state + */ + Status status() const noexcept; -template <typename Address> -nlohmann::json ConnectionBase<Address>::next(int timeout) -{ - // Maybe there is already something. - std::string buffer = util::nextNetwork(m_input); - - m_listener.remove(m_socket.handle()); - m_listener.set(m_socket.handle(), net::Condition::Readable); - m_timer.reset(); + /** + * Initiate connection to irccd. + * + * \pre state() == Disconnected + * \param address the address + */ + virtual void connect(const net::Address &address); - // Read if there is nothing. - while (buffer.empty()) { - // Wait and read. - m_listener.wait(clamp(timeout)); - m_input += m_socket.recv(512); + /** + * \copydoc Service::prepare + */ + void prepare(fd_set &in, fd_set &out, net::Handle &max) override; - // Finally try. - buffer = util::nextNetwork(m_input); - } - - auto value = nlohmann::json::parse(buffer); - - if (!value.is_object()) - throw std::invalid_argument("invalid message received"); - - return value; -} + /** + * \copydoc Service::sync + */ + void sync(fd_set &in, fd_set &out) override; +}; } // !irccd
--- a/lib/irccd/irccdctl.cpp Mon Jul 18 22:39:14 2016 +0200 +++ b/lib/irccd/irccdctl.cpp Wed Aug 10 10:33:21 2016 +0200 @@ -16,67 +16,44 @@ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ -#include <algorithm> -#include <cassert> -#include <iterator> -#include <memory> -#include <string> -#include <unordered_map> - #include <format.h> #include "command.hpp" +#include "connection.hpp" #include "elapsed-timer.hpp" #include "fs.hpp" #include "ini.hpp" #include "irccdctl.hpp" -#include "json.hpp" -#include "net.hpp" #include "logger.hpp" #include "options.hpp" #include "path.hpp" -#include "sysconfig.hpp" #include "system.hpp" #include "util.hpp" -namespace irccd { +using namespace std::string_literals; using namespace fmt::literals; -using namespace std::placeholders; -using namespace std::chrono_literals; -using namespace std::string_literals; - -/* - * Config file format - * ------------------------------------------------------------------ - * - * [connect] - * type = "ip | unix" - * - * # if ip - * host = "" - * port = number - * domain = "ipv4 | ipv6", default: ipv4 - * - * # if unix - * path = "" - * - * [alias] - * name = replacement - */ - -/* - * Initialize a connection from the configuration file - * ------------------------------------------------------------------ - */ +namespace irccd { void Irccdctl::usage() const { - // TODO: CHANGE + bool first = true; + + for (const auto &cmd : m_commandService.commands()) { + log::warning() << (first ? "usage: " : " ") << sys::programName() << " " + << cmd->usage() << std::endl; + first = false; + } + + std::exit(1); +} + +void Irccdctl::help() const +{ log::warning() << "usage: " << sys::programName() << " [options...] <command> [command-options...] [command-args...]\n\n"; log::warning() << "General options:\n"; - log::warning() << "\tc, --config file\tspecify the configuration file\n"; + log::warning() << "\t-c, --config file\tspecify the configuration file\n"; log::warning() << "\t--help\t\t\tshow this help\n"; log::warning() << "\t-t, --type type\t\tspecify connection type\n"; log::warning() << "\t-v, --verbose\t\tbe verbose\n\n"; @@ -85,63 +62,50 @@ log::warning() << "\t-p, --port port\t\tuse the specified port number\n\n"; log::warning() << "Available options for type unix (-t, --type):\n"; log::warning() << "\t-P, --path file\t\tconnect to the specified socket file\n\n"; - log::warning() << "General commands:\n"; - log::warning() << "\thelp\t\t\tShow an help topic\n"; - log::warning() << "\twatch\t\t\tStart listening to irccd\n\n"; - log::warning() << "Plugin management:\n"; - log::warning() << "\tplugin-config\t\tGet or set a plugin configuration option\n"; - log::warning() << "\tplugin-info\t\tGet plugin information\n"; - log::warning() << "\tplugin-list\t\tList all loaded plugins\n"; - log::warning() << "\tplugin-load\t\tLoad a plugin\n"; - log::warning() << "\tplugin-reload\t\tReload a plugin\n"; - log::warning() << "\tplugin-unload\t\tUnload a plugin\n\n"; - log::warning() << "Server management:\n"; - log::warning() << "\tserver-cmode\t\tChange a channel mode\n"; - log::warning() << "\tserver-cnotice\t\tSend a channel notice\n"; - log::warning() << "\tserver-connect\t\tConnect to a server\n"; - log::warning() << "\tserver-disconnect\tDisconnect from a server\n"; - log::warning() << "\tserver-info\t\tGet server information\n"; - log::warning() << "\tserver-invite\t\tInvite someone to a channel\n"; - log::warning() << "\tserver-join\t\tJoin a channel\n"; - log::warning() << "\tserver-kick\t\tKick someone from a channel\n"; - log::warning() << "\tserver-list\t\tList all servers\n"; - log::warning() << "\tserver-me\t\tSend a CTCP Action (same as /me)\n"; - log::warning() << "\tserver-message\t\tSend a message to someone or a channel\n"; - log::warning() << "\tserver-mode\t\tChange a user mode\n"; - log::warning() << "\tserver-notice\t\tSend a private notice\n"; - log::warning() << "\tserver-nick\t\tChange your nickname\n"; - log::warning() << "\tserver-part\t\tLeave a channel\n"; - log::warning() << "\tserver-reconnect\tReconnect one or all servers\n"; - log::warning() << "\tserver-topic\t\tChange a channel topic\n"; + log::warning() << "Available commands:\n"; + + for (const auto &cmd : m_commandService.commands()) + log::warning() << "\t" << std::left << std::setw(32) + << cmd->name() << cmd->description() << std::endl; + log::warning() << "\nFor more information on a command, type " << sys::programName() << " help <command>" << std::endl; + std::exit(1); } +/* + * Configuration file parsing. + * ------------------------------------------------------------------- + */ + +/* + * readConnectIp + * ------------------------------------------------------------------- + * + * Extract IP connection information from the config file. + * + * [connect] + * type = "ip" + * host = "ip or hostname" + * port = "port number or service" + * domain = "ipv4 or ipv6" (Optional, default: ipv4) + */ void Irccdctl::readConnectIp(const ini::Section &sc) { ini::Section::const_iterator it; - // Host. - std::string host; + std::string host, port; if ((it = sc.find("host")) == sc.end()) throw std::invalid_argument("missing host parameter"); host = it->value(); - // Port. - std::uint16_t port; - if ((it = sc.find("port")) == sc.end()) throw std::invalid_argument("missing port parameter"); - try { - port = util::toNumber<std::uint16_t>(it->value()); - } catch (...) { - throw std::invalid_argument("invalid port number: " + it->value()); - } + port = it->value(); - // Domain. int domain = AF_INET; if ((it = sc.find("domain")) != sc.end()) { @@ -153,10 +117,21 @@ throw std::invalid_argument("invalid domain: " + it->value()); } - m_connection = std::make_unique<ConnectionBase<net::address::Ip>>(net::address::Ip::resolve(host, std::to_string(port), domain)); + m_address = net::resolveOne(host, port, domain, SOCK_STREAM); + m_connection = std::make_unique<Connection>(); } -void Irccdctl::readConnectUnix(const ini::Section &sc) +/* + * readConnectLocal + * ------------------------------------------------------------------- + * + * Extract local connection for Unix. + * + * [connect] + * type = "unix" + * path = "path to socket file" + */ +void Irccdctl::readConnectLocal(const ini::Section &sc) { #if !defined(IRCCD_SYSTEM_WINDOWS) auto it = sc.find("path"); @@ -164,7 +139,8 @@ if (it == sc.end()) throw std::invalid_argument("missing path parameter"); - m_connection = std::make_unique<ConnectionBase<net::address::Local>>(net::address::Local(it->value(), false)); + m_address = net::local::create(it->value()); + m_connection = std::make_unique<Connection>(); #else (void)sc; @@ -172,6 +148,12 @@ #endif } +/* + * readConnect + * ------------------------------------------------------------------- + * + * Generic function for reading the [connect] section. + */ void Irccdctl::readConnect(const ini::Section &sc) { auto it = sc.find("type"); @@ -182,11 +164,20 @@ if (it->value() == "ip") readConnectIp(sc); else if (it->value() == "unix") - readConnectUnix(sc); + readConnectLocal(sc); else throw std::invalid_argument("invalid type given: " + it->value()); } +/* + * readGeneral + * ------------------------------------------------------------------- + * + * Read the general section. + * + * [general] + * verbose = true + */ void Irccdctl::readGeneral(const ini::Section &sc) { auto verbose = sc.find("verbose"); @@ -195,65 +186,73 @@ log::setVerbose(util::isBoolean(verbose->value())); } +/* + * readAliases + * ------------------------------------------------------------------- + * + * Read aliases for irccdctl. + * + * [alias] + * name = ( "command", "arg1, "...", "argn" ) + */ void Irccdctl::readAliases(const ini::Section &sc) { - for (const ini::Option &option : sc) { + for (const auto &option : sc) { // This is the alias name. Alias alias(option.key()); - if (m_commandService.contains(option.key())) - throw std::invalid_argument("there is already a command named " + option.key()); - // Iterate over the list of commands to execute for this alias. - for (const std::string &repl : option) { + for (const auto &repl : option) { // This is the alias split string. - std::vector<std::string> list = util::split(repl, " \t"); + auto list = util::split(repl, " \t"); if (list.size() < 1) throw std::invalid_argument("alias require at least one argument"); // First argument is the command/alias to execute. - std::string command = list[0]; - - // This is the alias arguments. - std::vector<AliasArg> args; - - for (auto it = list.begin() + 1; it != list.end(); ++it) - args.push_back(std::move(*it)); + auto command = list[0]; - alias.push_back({std::move(command), std::move(args)}); + // Remove command name and puts arguments. + alias.push_back({std::move(command), std::vector<AliasArg>(list.begin() + 1, list.end())}); } - // Show for debugging purpose. - log::debug("alias {}:"_format(option.key())); - - for (const auto &cmd : alias) - log::debug(" {} {}"_format(cmd.command(), util::join(cmd.args().begin(), cmd.args().end(), ' '))); - m_aliases.emplace(option.key(), std::move(alias)); } } -void Irccdctl::read(const std::string &path, const option::Result &options) +void Irccdctl::read(const std::string &path) { - ini::Document doc = ini::readFile(path); - ini::Document::const_iterator it = doc.find("connect"); + try { + ini::Document doc = ini::readFile(path); + ini::Document::const_iterator it; - // Do not try to read [connect] if specified at command line. - if (it != doc.end() && options.count("-t") == 0 && options.count("--type") == 0) - readConnect(*it); - if ((it = doc.find("general")) != doc.end()) - readGeneral(*it); - if ((it = doc.find("alias")) != doc.end()) - readAliases(*it); + if (!m_connection && (it = doc.find("connect")) != doc.end()) + readConnect(*it); + if ((it = doc.find("general")) != doc.end()) + readGeneral(*it); + if ((it = doc.find("alias")) != doc.end()) + readAliases(*it); + } catch (const std::exception &ex) { + log::warning() << path << ": " << ex.what() << std::endl; + } } /* - * Initialize a connection from the command line. - * ------------------------------------------------------------------ + * Command line parsing. + * ------------------------------------------------------------------- */ -void Irccdctl::parseConnectIp(const option::Result &options, bool ipv6) +/* + * parseConnectIp + * ------------------------------------------------------------------ + * + * Parse internet connection from command line. + * + * -t ip | ipv6 + * -h host or ip + * -p port + */ +void Irccdctl::parseConnectIp(const option::Result &options) { option::Result::const_iterator it; @@ -266,24 +265,34 @@ host = it->second; // Port (-p or --port). - std::uint16_t port; + std::string port; if ((it = options.find("-p")) == options.end() && (it = options.find("--port")) == options.end()) throw std::invalid_argument("missing port argument (-p or --port)"); - try { - port = util::toNumber<std::uint16_t>(it->second); - } catch (...) { - throw std::invalid_argument("invalid port number: " + it->second); - } + port = it->second; // Domain - int domain = (ipv6) ? AF_INET6 : AF_INET; + int domain = AF_INET; - m_connection = std::make_unique<ConnectionBase<net::address::Ip>>(net::address::Ip::resolve(host, std::to_string(port), domain, SOCK_STREAM)); + if ((it = options.find("-t")) != options.end()) + domain = it->second == "ipv6" ? AF_INET6 : AF_INET; + else if ((it = options.find("--type")) != options.end()) + domain = it->second == "ipv6" ? AF_INET6: AF_INET; + + m_address = net::resolveOne(host, port, domain, SOCK_STREAM); + m_connection = std::make_unique<Connection>(); } -void Irccdctl::parseConnectUnix(const option::Result &options) +/* + * parseConnectLocal + * ------------------------------------------------------------------ + * + * Parse local connection. + * + * -P file + */ +void Irccdctl::parseConnectLocal(const option::Result &options) { #if !defined(IRCCD_SYSTEM_WINDOWS) option::Result::const_iterator it; @@ -291,7 +300,8 @@ if ((it = options.find("-P")) == options.end() && (it = options.find("--path")) == options.end()) throw std::invalid_argument("missing path parameter (-P or --path)"); - m_connection = std::make_unique<ConnectionBase<net::address::Local>>(net::address::Local(it->second, false)); + m_address = net::local::create(it->second, false); + m_connection = std::make_unique<Connection>(); #else (void)options; @@ -299,6 +309,12 @@ #endif } +/* + * parseConnect + * ------------------------------------------------------------------ + * + * Generic parsing of command line option for connection. + */ void Irccdctl::parseConnect(const option::Result &options) { assert(options.count("-t") > 0 || options.count("--type") > 0); @@ -308,14 +324,14 @@ if (it == options.end()) it = options.find("--type"); if (it->second == "ip" || it->second == "ipv6") - return parseConnectIp(options, it->second == "ipv6"); + return parseConnectIp(options); if (it->second == "unix") - return parseConnectUnix(options); + return parseConnectLocal(options); throw std::invalid_argument("invalid type given: " + it->second); } -option::Result Irccdctl::parse(int &argc, char **&argv) const +option::Result Irccdctl::parse(int &argc, char **&argv) { // 1. Parse command line options. option::Options def{ @@ -354,6 +370,41 @@ return result; } +nlohmann::json Irccdctl::next(const std::string id) +{ + ElapsedTimer timer; + + while (m_input.empty() && m_connection->isConnected() && timer.elapsed() < m_timeout) + m_connection->poll(); + + if (m_input.empty()) + return nlohmann::json(); + + nlohmann::json value; + + if (id == "") { + value = m_input[0]; + m_input.erase(m_input.begin()); + } else { + auto it = std::find_if(m_input.begin(), m_input.end(), [&] (const auto &v) { + auto rt = v.find("response"); + + if (rt != v.end() && rt->is_string() && *rt == id) + return true; + + return false; + }); + + // Remove the previous messages. + if (it != m_input.end()) { + value = *it; + m_input.erase(m_input.begin(), it); + } + } + + return value; +} + void Irccdctl::exec(const Command &cmd, std::vector<std::string> args) { // 1. Build options from command line arguments. @@ -394,10 +445,10 @@ request.push_back({"command", cmd.name()}); // 5. Send the command. - m_connection->send(request.dump(), 30000); + m_connection->request(request); // 6. Parse the result. - cmd.result(*this, m_connection->next(cmd.name(), 30000)); + cmd.result(*this, next(cmd.name())); } void Irccdctl::exec(const Alias &alias, std::vector<std::string> argsCopy) @@ -458,36 +509,10 @@ } } -void Irccdctl::connect() -{ - log::info("{}: connecting to irccd..."_format(sys::programName())); - - // Try to connect. - m_connection->connect(30000); - - // Get irccd information. - auto object = m_connection->next(30000); - auto program = object.find("program"); - - if (program == object.end() || !program->is_string() || program->get<std::string>() != "irccd") - throw std::runtime_error("not an irccd server"); - - // Get values. - m_major = util::json::toInt(object["major"]); - m_minor = util::json::toInt(object["minor"]); - m_patch = util::json::toInt(object["patch"]); - m_javascript = util::json::toBool(object["javascript"]); - m_ssl = util::json::toBool(object["ssl"]); - - log::info() << std::boolalpha; - log::info("{}: connected to irccd {}.{}.{}"_format(sys::programName(), m_major, m_minor, m_patch)); - log::info("{}: javascript: {}, ssl support: {}"_format(sys::programName(), m_javascript, m_ssl)); -} - void Irccdctl::run(int argc, char **argv) { // 1. Read command line arguments. - option::Result result = parse(argc, argv); + auto result = parse(argc, argv); /* * 2. Open optional config by command line or by searching it @@ -505,17 +530,19 @@ auto it = result.find("-c"); if (it != result.end() || (it = result.find("--config")) != result.end()) - read(it->second, result); + read(it->second); else { for (const std::string &dir : path::list(path::PathConfig)) { std::string path = dir + "irccdctl.conf"; - if (fs::exists(path)) - read(path, result); + if (fs::exists(path)) { + read(path); + break; + } } } } catch (const std::exception &ex) { - log::warning("{}: {}"_format(sys::programName(), ex.what())); + log::warning() << sys::programName() << ": " << ex.what() << std::endl; std::exit(1); } @@ -531,8 +558,23 @@ std::exit(1); } - connect(); - } + m_connection->onDisconnect.connect([this] (auto reason) { + log::warning() << "connection lost to irccd: " << reason << std::endl; + }); + m_connection->onConnect.connect([this] (auto info) { + log::info() << "connected to irccd " + << info.major << "." + << info.minor << "." + << info.patch << std::endl; + }); + m_connection->onMessage.connect([this] (auto msg) { + m_input.push_back(std::move(msg)); + }); + + m_connection->connect(m_address); + } else if (argc == 1) + help(); + // NOTREACHED // Build a vector of arguments. std::vector<std::string> args;
--- a/lib/irccd/irccdctl.hpp Mon Jul 18 22:39:14 2016 +0200 +++ b/lib/irccd/irccdctl.hpp Wed Aug 10 10:33:21 2016 +0200 @@ -24,22 +24,24 @@ * \brief Base class for irccdctl front end. */ -#include <cassert> #include <map> #include <memory> #include <string> +#include "connection.hpp" #include "alias.hpp" -#include "connection.hpp" #include "options.hpp" #include "service-command.hpp" +#include <json.hpp> + namespace irccd { -class Command; +class Connection; namespace ini { +class Document; class Section; } // !ini @@ -49,36 +51,36 @@ */ class Irccdctl { private: - // Irccd's information. - unsigned short m_major{0}; - unsigned short m_minor{0}; - unsigned short m_patch{0}; - - // Irccd's compilation option. - bool m_javascript{true}; - bool m_ssl{true}; - // Commands. CommandService m_commandService; + // Connection handler. std::unique_ptr<Connection> m_connection; - std::unordered_map<std::string, Alias> m_aliases; + std::uint32_t m_timeout{30000}; + net::Address m_address; + + // Aliases. + std::map<std::string, Alias> m_aliases; + + // Incoming data. + std::vector<nlohmann::json> m_input; void usage() const; + void help() const; + // Parse configuration file. void readConnectIp(const ini::Section &sc); - void readConnectUnix(const ini::Section &sc); + void readConnectLocal(const ini::Section &sc); void readConnect(const ini::Section &sc); void readGeneral(const ini::Section &sc); void readAliases(const ini::Section &sc); - void read(const std::string &path, const option::Result &options); + void read(const std::string &path); - void parseConnectIp(const option::Result &options, bool ipv6); - void parseConnectUnix(const option::Result &options); + // Parse command line options. + void parseConnectIp(const option::Result &options); + void parseConnectLocal(const option::Result &options); void parseConnect(const option::Result &options); - option::Result parse(int &argc, char **&argv) const; - - void connect(); + option::Result parse(int &argc, char **&argv); public: /** @@ -91,6 +93,30 @@ return m_commandService; } + inline const Connection &connection() const noexcept + { + return *m_connection; + } + + inline Connection &connection() noexcept + { + return *m_connection; + } + + /** + * Get the next response with the given id. + * + * If the response id is not provided, get the next incoming message. + * + * Otherwise, if the id is provided, all other previous messages will be + * discarded. + * + * \param id the response id (e.g. server-message) + * \return the next message + * \warning this may skip previous events + */ + IRCCD_EXPORT nlohmann::json next(const std::string id = ""); + /** * Execute the given command and wait for its result. * @@ -115,16 +141,6 @@ IRCCD_EXPORT void exec(std::vector<std::string> args); /** - * Get the connection. - * - * \return the connection - */ - inline Connection &connection() noexcept - { - return *m_connection; - } - - /** * Run the irccdctl front end. * * \param argc the number of arguments
--- a/lib/irccd/net.hpp Mon Jul 18 22:39:14 2016 +0200 +++ b/lib/irccd/net.hpp Wed Aug 10 10:33:21 2016 +0200 @@ -1,5 +1,5 @@ /* - * net.hpp -- portable C++ socket wrappers + * net.hpp -- portable C++ socket wrapper * * Copyright (c) 2013-2016 David Demelier <markand@malikania.fr> * @@ -63,36 +63,53 @@ /** * \page Networking Networking * - * - \subpage net-configuration * - \subpage net-options * - \subpage net-concepts */ /** - * \page net-configuration Configuration + * \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. + * 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 + * - **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. + * **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. + * 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. + * 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`). @@ -108,25 +125,8 @@ */ /** - * \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 net::init function and net::finish functions. - * - * - **NET_NO_SSL**: (bool) Set to 0 if you don't have access to OpenSSL library. - * - * - **NET_NO_AUTO_SSL_INIT**: (bool) Set to 0 if you don't want Socket class with Tls to automatically init - * the OpenSSL library. You will need to call net::ssl::init and net::ssl::finish. - */ - -/** * \page net-concepts Concepts * - * - \subpage net-concept-address * - \subpage net-concept-backend * - \subpage net-concept-option * - \subpage net-concept-stream @@ -134,81 +134,13 @@ */ /** - * \page net-concept-address Address (Concept) - * - * An address is used in many place for creating, binding, connecting, receiving and sending. They are implemented as - * templates to allow any type of address and to make sure the same address is used for a given socket. - * - * This concepts requires the following functions: - * - * # Address (constructor) - * - * The address must have the following constructor overloads. - * - * **Note**: the user can add custom constructors. - * - * ## Synopsis - * - * ```` - * Address(); // (0); - * Address(const sockaddr *sa, socklen_t length); // (1) - * ```` - * - * ## Arguments - * - * - **ss**: the storage to construct from, - * - **length**: the storage length. - * - * # domain - * - * Get the domain (e.g. AF_INET). - * - * ## Synopsis - * - * ```` - * int domain() const noexcept; - * ```` - * - * ## Returns - * - * The domain. - * - * # address - * - * Get the underlying address. - * - * ## Synopsis - * - * ```` - * const sockaddr *address() const noexcept; - * ```` - * - * ## Returns - * - * The address. - * - * # length - * - * Get the underlying length. - * - * ## Synopsis - * - * ```` - * socklen_t length() const noexcept; - * ```` - * - * ## Returns - * - * The length. - */ - -/** * \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. + * 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. + * The backend must be default constructible, it is highly encouraged to be move + * constructible. * * This concepts requires the following functions: * @@ -233,7 +165,8 @@ * ## Synopsis * * ```` - * void set(const ListenerTable &table, Handle handle, Condition condition, bool add); + * void set(const ListenerTable &table, Handle handle, Condition condition, + * bool add); * ```` * * ## Arguments @@ -241,7 +174,7 @@ * - **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 at all. + * - **add**: hint set to true if the handle is not currently registered. * * # unset * @@ -250,7 +183,8 @@ * ## Synopsis * * ```` - * void unset(const ListenerTable &table, Handle handle, Condition condition, bool remove); + * void unset(const ListenerTable &table, Handle handle, Condition condition, + * bool remove); * ```` * * ## Arguments @@ -285,7 +219,8 @@ * * 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 errno code. + * If an operation is not available, provides the function but throws an + * exception with ENOSYS message. * * This concepts requires the following functions: * @@ -306,8 +241,8 @@ * ## Synopsis * * ```` - * template <typename Address, typename Protocol> - * inline void set(Socket<Address, Protocol> &sc) const; + * template <typename Address> + * void set(Socket &sc) const; * ```` * * ## Arguments @@ -321,8 +256,8 @@ * ## Synopsis * * ```` - * template <typename Address, typename Protocol> - * inline bool get(Socket<Address, Protocol> &sc) const; + * template <typename Address> + * T get(Socket &sc) const; * ```` * * ## Arguments @@ -353,95 +288,49 @@ * * The type of socket. * - * # create - * - * Function called immediately after creation of socket. Interface must provides this function even if the - * interface does not require anything. - * - * ## Synopsis - * - * ```` - * template <typename Address> - * void create(Socket<Address, Tcp> &) const noexcept; - * ```` - * * # connect * - * Initial connect function. - * - * In this function, the interface receive the socket, address and a condition (initially set to None). If the - * underlying socket is marked non-blocking and the operation would block, the interface must set the condition - * to the required one. + * Connect to the given address. * * ## Synopsis * * ```` - * template <typename Address, typename Protocol> - * void connect(Socket<Address, Protocol> &sc, const sockaddr *address, socklen_t length, Condition &cond); + * void connect(const sockaddr *address, socklen_t length); // (0) + * void connect(const Address &address); // 1 (Optional) * ```` * * ## Arguments * - * - **sc**: the socket, * - **address**: the address, - * - **length**: the address length, - * - **cond**: the condition to update. - * - * # resumeConnect - * - * Continue the connection. + * - **length**: the address length. * - * ## Synopsis + * ## Throws * - * ```` - * template <typename Address, typename Protocol> - * void resumeConnect(Socket<Address, Protocol> &sc, Condition &cond) - * ```` - * - * ## Arguments - * - * - **sc**: the socket, - * - **cond**: the condition to update. + * - net::WouldBlockError: if the operation would block, + * - net::Error: on other error. * * # accept * * Accept a new client. * - * If the interface has no pending connection, an invalid socket SHOULD be returned, otherwise return the client and - * set the condition if the accept process is not complete yet. - * - * The interface MUST stores the client information into address and length parameters, they are guaranted to never be - * null. + * 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 * * ```` - * template <typename Address, typename Protocol> - * Socket<Address, Protocol> accept(Socket<Address, Protocol> &sc, sockaddr *address, socklen_t *length, Condition &cond); + * Socket accept(); * ```` * - * ## Arguments + * ## Returns * - * - **sc**: the socket, - * - **address**: the information address, - * - **length**: the address initial length (sockaddr_storage), - * - **cond**: the condition to update. - * - * # resumeAccept - * - * Continue the accept process on the returned client. + * The new socket. * - * ## Synopsis + * ## Throws * - * ```` - * template <typename Address, typename Protocol> - * void accept(Socket<Address, Protocol> &sc, Condition &cond) const noexcept; - * ```` - * - * ## Arguments - * - * - **sc**: the socket, - * - **cond**: the condition to update. + * - net::WouldBlockError: if the operation would block, + * - net::Error: on other error. * * # recv * @@ -450,21 +339,23 @@ * ## Synopsis * * ```` - * template <typename Address> - * std::size_t recv(Socket<Address, Tcp> &sc, void *data, std::size_t length, Condition &cond); + * unsigned recv(void *data, unsigned length); * ```` * * ## Arguments * - * - **sc**: the socket, * - **data**: the destination buffer, - * - **length**: the destination buffer length, - * - **cond**: the condition to update. + * - **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. @@ -472,20 +363,22 @@ * ## Synopsis * * ```` - * template <typename Address> - * std::size_t send(Socket<Address, Tcp> &sc, const void *data, std::size_t length, Condition &cond); + * unsigned send(const void *data, unsigned length); * ```` * * ## Arguments * - * - **sc**: the socket, * - **data**: the data to send, - * - **length**: the data length, - * - **cond**: the condition to update. + * - **length**: the data length. * * ## Returns * * The number of bytes sent. + * + * ## Throws + * + * - net::WouldBlockError: if the operation would block, + * - net::Error: on other error. */ /** @@ -514,42 +407,50 @@ * ## Synopsis * * ```` - * template <typename Address, typename Protocol> - * std::size_t recvfrom(Socket<Address, Protocol> &sc, void *data, std::size_t length, sockaddr *address, socklen_t *addrlen, Condition &cond); + * unsigned recvfrom(void *data, unsigned length, sockaddr *address, + * socklen_t *addrlen); + * unsigned recvfrom(void *data, unsigned length, Address *source) * ```` * * ## Arguments * - * - **sc**: the socket, * - **data**: the data, * - **length**: the length, * - **address**: the source address, - * - **addrlen**: the source address in/out length, - * - **cond**: the condition. + * - **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 * * ```` - * template <typename Address, typename Protocol> - * std::size_t sendto(Socket<Address, Protocol> &sc, const void *data, std::size_t length, const sockaddr *address, socklen_t addrlen, Condition &cond); + * unsigned sendto(const void *data, unsigned length, const sockaddr *address, + * socklen_t addrlen); + * unsigned sendto(const void *data, unsigned length, const Address &address); * ```` * * ## Arguments * - * - **sc**: the socket, * - **data**: the data to send, * - **length**: the data length, * - **address**: the destination address, - * - **addrlen**: the destination address length, - * - **cond**: the condition. + * - **addrlen**: the destination address length. * * ## Returns * * The number of bytes sent. + * + * ## Throws + * + * - net::WouldBlockError: if the operation would block, + * - net::Error: on other error. */ /* @@ -557,24 +458,27 @@ * ------------------------------------------------------------------ */ -// Include Windows headers before because it brings _WIN32_WINNT if not specified by the user. +/* + * Include Windows headers before because it brings _WIN32_WINNT if not + * specified by the user. + */ #if defined(_WIN32) -# include <WinSock2.h> -# include <WS2tcpip.h> +# 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> +# 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> @@ -613,23 +517,23 @@ */ #if defined(_WIN32) -# if _WIN32_WINNT >= 0x0600 && !defined(NET_HAVE_POLL) -# define NET_HAVE_POLL -# endif +# 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 +# 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 +# if !defined(NET_HAVE_EPOLL) +# define NET_HAVE_EPOLL +# endif +# if !defined(NET_HAVE_POLL) +# define NET_HAVE_POLL +# endif #endif /* @@ -641,26 +545,26 @@ * \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 +# 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 +# if defined(_WIN32) +# if _WIN32_WINNT >= 0x0600 +# define NET_HAVE_INET_NTOP +# endif +# else +# define NET_HAVE_INET_NTOP +# endif #endif /* @@ -674,35 +578,35 @@ * \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 +# 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 +# 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 +# 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 +# if !defined(NET_DEFAULT_BACKEND) +# define NET_DEFAULT_BACKEND Select +# endif #endif #if defined(NET_HAVE_POLL) && !defined(_WIN32) -# include <poll.h> +# include <poll.h> #endif namespace irccd { @@ -759,7 +663,8 @@ * Portable constants. * ------------------------------------------------------------------ * - * These constants are needed to check functions return codes, they are rarely needed in end user code. + * These constants are needed to check functions return codes, they are rarely + * needed in end user code. */ #if defined(_WIN32) @@ -799,7 +704,8 @@ } /** - * Initialize the socket library. Except if you defined NET_NO_AUTO_INIT, you don't need to call this + * 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 @@ -816,7 +722,10 @@ WSADATA wsa; WSAStartup(MAKEWORD(2, 2), &wsa); - // If NET_NO_AUTO_INIT is not set then the user must also call finish himself. + /* + * 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 @@ -856,7 +765,8 @@ } /** - * Get the last socket system error. The error is set from errno or from WSAGetLastError on Windows. + * Get the last socket system error. The error is set from errno or from + * WSAGetLastError on Windows. * * \return a string message */ @@ -871,6 +781,9 @@ #if !defined(NET_NO_SSL) +/** + * \brief SSL namespace + */ namespace ssl { /** @@ -878,13 +791,13 @@ * \brief Which OpenSSL method to use. */ enum Method { - Tlsv1, //!< TLS v1.2 (recommended) - Sslv3 //!< SSLv3 + 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. + * 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 { @@ -926,95 +839,118 @@ */ /** - * \brief Base class for sockets error + * \brief Base class for sockets error. */ class Error : public std::exception { -public: - /** - * \enum Code - * \brief Which kind of error - */ - enum Code { - Timeout, ///!< The action did timeout - System, ///!< There is a system error - Other ///!< Other custom error - }; - private: - Code m_code; - std::string m_function; - std::string m_error; + std::string m_message; public: /** - * Constructor that use the last system error. + * Construct the error using the specified error from the system. * - * \param code which kind of error - * \param function the function name + * \param code the error code + * \warning the code must be a Winsock error or errno on Unix */ - inline Error(Code code, std::string function) - : m_code(code) - , m_function(std::move(function)) - , m_error(error()) + inline Error(int code) noexcept + : m_message(error(code)) { } /** - * Constructor that use the system error set by the user. + * Construct the error using the custom message. * - * \param code which kind of error - * \param function the function name - * \param n the error + * \param message the message */ - inline Error(Code code, std::string function, int n) - : m_code(code) - , m_function(std::move(function)) - , m_error(error(n)) + inline Error(std::string message) noexcept + : m_message(std::move(message)) { } /** - * Constructor that set the error specified by the user. - * - * \param code which kind of error - * \param function the function name - * \param error the error + * Construct the error using the last message from the system. */ - inline Error(Code code, std::string function, std::string error) - : m_code(code) - , m_function(std::move(function)) - , m_error(std::move(error)) + inline Error() noexcept +#if defined(_WIN32) + : Error(WSAGetLastError()) +#else + : Error(errno) +#endif { } /** - * Get which function has triggered the error. - * - * \return the function name (e.g connect) - */ - inline const std::string &function() const noexcept - { - return m_function; - } - - /** - * The error code. - * - * \return the code - */ - inline Code code() const noexcept - { - return m_code; - } - - /** * Get the error (only the error content). * * \return the error */ const char *what() const noexcept override { - return m_error.c_str(); + 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"; } }; @@ -1028,14 +964,11 @@ /** * \enum Condition * \brief Define the required condition for the socket. - * - * As explained in Action enumeration, some operations required to be called several times, before calling these - * operations, the user must wait the socket to be readable or writable. This can be checked with Socket::condition. */ enum class Condition { - None, //!< No condition is required - Readable = (1 << 0), //!< The socket must be readable - Writable = (1 << 1) //!< The socket must be writable + None, //!< No condition is required + Readable = (1 << 0), //!< The socket must be readable + Writable = (1 << 1) //!< The socket must be writable }; /** @@ -1127,1695 +1060,38 @@ return v1; } -/* - * Base Socket class - * ------------------------------------------------------------------ - * - * This base class has operations that are common to all types of sockets but you usually instanciate - * a SocketTcp or SocketUdp +/** + * \brief Generic socket address storage. + * \ingroup net-module-addresses */ - -/** - * \brief Base socket class for socket operations. - * - * **Important:** When using non-blocking sockets, some considerations must be taken. See the implementation of the - * underlying protocol for more details. - * - * When using non-blocking sockets, it is important to pass the condition to functions which may block, they indicate - * the condition to wait to perform or continue the operation if they would block. - * - * For example, when trying to connect with non-blocking, user should do the following: - * - * 1. Call Socket::connect() with the condition, - * 2. Loop until condition is not set to Condition::None (or an exception is thrown), - * 3. Wait with a listener for the condition to be ready (see Listener::poll), - * 4. Call Socket::resumeConnect() with the condition again. - * - * \see protocol::Tls - * \see protocol::Tcp - * \see protocol::Udp - */ -template <typename Address, typename Protocol> -class Socket { +class Address { private: - Protocol m_proto; - -protected: - /** - * The native handle. - */ - Handle m_handle{Invalid}; + sockaddr_storage m_storage; + socklen_t m_length; public: /** - * Create a socket handle. - * - * This is the primary function and the only one that creates the socket handle, all other constructors - * are just overloaded functions. - * - * \param domain the domain AF_* - * \param type the type SOCK_* - * \param protocol the protocol - * \param iface the implementation - * \throw net::Error on errors + * Construct empty address. */ - Socket(int domain, int type, int protocol, Protocol iface = {}) - : m_proto(std::move(iface)) - { -#if !defined(NET_NO_AUTO_INIT) - init(); -#endif - m_handle = ::socket(domain, type, protocol); - - if (m_handle == Invalid) - throw Error(Error::System, "socket"); - - m_proto.create(*this); - } - - /** - * This tries to create a socket. - * - * Domain and type are determined by the Address and Protocol object. - * - * \param address which type of address - * \param protocol the protocol - * \throw net::Error on errors - */ - explicit inline Socket(const Address &address = {}, Protocol protocol = {}) - : Socket(address.domain(), protocol.type(), 0, std::move(protocol)) - { - } - - /** - * Create the socket with an already defined handle and its protocol. - * - * \param handle the handle - * \param protocol the protocol - */ - explicit inline Socket(Handle handle, Protocol protocol = {}) noexcept - : m_proto(std::move(protocol)) - , m_handle(handle) - { - } - - /** - * Create an invalid socket. Can be used when you cannot instanciate the socket immediately. - */ - explicit inline Socket(std::nullptr_t) noexcept - : m_handle(Invalid) + inline Address() noexcept + : m_storage{} + , m_length(0) { } /** - * Copy constructor deleted. - */ - Socket(const Socket &) = delete; - - /** - * Transfer ownership from other to this. - * - * \param other the other socket - */ - inline Socket(Socket &&other) noexcept - : m_proto(std::move(other.m_proto)) - , m_handle(other.m_handle) - { - other.m_handle = Invalid; - } - - /** - * Default destructor. - */ - virtual ~Socket() - { - close(); - } - - /** - * Access the implementation. - * - * \return the implementation - * \warning use this function with care - */ - inline const Protocol &protocol() const noexcept - { - return m_proto; - } - - /** - * Overloaded function. - * - * \return the implementation - */ - inline Protocol &protocol() noexcept - { - return m_proto; - } - - /** - * Tells if the socket is not invalid. - * - * \return true if not invalid - */ - inline bool isOpen() const noexcept - { - return m_handle != Invalid; - } - - /** - * Set an option for the socket. Wrapper of setsockopt(2). - * - * \pre isOpen() - * \param level the setting level - * \param name the name - * \param arg the value - * \throw net::Error on errors - */ - template <typename Argument> - inline void set(int level, int name, const Argument &arg) - { - assert(m_handle != Invalid); - - if (setsockopt(m_handle, level, name, (ConstArg)&arg, sizeof (arg)) == Failure) - throw Error(Error::System, "set"); - } - - /** - * Object-oriented option setter. - * - * The object must have `set(Socket<Address, Protocol> &) const`. - * - * \pre isOpen() - * \param option the option - * \throw net::Error on errors - */ - template <typename Option> - inline void set(const Option &option) - { - assert(m_handle != Invalid); - - option.set(*this); - } - - /** - * Get an option for the socket. Wrapper of getsockopt(2). - * - * \pre isOpen() - * \param level the setting level - * \param name the name - * \return the value - * \throw net::Error on errors - */ - template <typename Argument> - Argument get(int level, int name) - { - assert(m_handle != Invalid); - - Argument desired, result{}; - socklen_t size = sizeof (result); - - if (getsockopt(m_handle, level, name, (Arg)&desired, &size) == Failure) - throw Error(Error::System, "get"); - - std::memcpy(&result, &desired, size); - - return result; - } - - /** - * Object-oriented option getter. - * - * The object must have `T get(Socket<Address, Protocol> &) const`, T can be any type and it is the value - * returned from this function. + * Construct address from existing one. * - * \pre isOpen() - * \return the same value as get() in the option - * \throw net::Error on errors - */ - template <typename Option> - inline auto get() -> decltype(std::declval<Option>().get(*this)) - { - assert(m_handle != Invalid); - - return Option().get(*this); - } - - /** - * Get the native handle. - * - * \return the handle - * \warning Not portable - */ - inline Handle handle() const noexcept - { - return m_handle; - } - - /** - * Bind using a native address. - * - * \pre isOpen() - * \param address the address - * \param length the size - * \throw net::Error on errors - */ - inline void bind(const sockaddr *address, socklen_t length) - { - assert(m_handle != Invalid); - - if (::bind(m_handle, address, length) == Failure) - throw Error(Error::System, "bind"); - } - - /** - * Overload that takes an address. - * - * \pre isOpen() - * \param address the address - * \throw net::Error on errors - */ - inline void bind(const Address &address) - { - assert(m_handle != Invalid); - - if (::bind(m_handle, address.address(), address.length()) == Failure) - throw Error(Error::System, "bind"); - } - - /** - * Listen for pending connection. - * - * \pre isOpen() - * \param max the maximum number - * \throw net::Error on errors - */ - inline void listen(int max = 128) - { - assert(m_handle != Invalid); - - if (::listen(this->m_handle, max) == Failure) - throw Error(Error::System, "listen"); - } - - /** - * Get the local name. This is a wrapper of getsockname(). - * - * \pre isOpen() - * \return the address - * \throw Error on failures - */ - 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(Error::System, "getsockname"); - - 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(Error::System, "getpeername"); - - return Address(reinterpret_cast<sockaddr *>(&ss), length); - } - - /** - * Initialize connection to the given address. - * - * \pre isOpen() - * \param address the address - * \param length the address length - * \param cond the condition - * \throw net::Error on failures - */ - inline void connect(const sockaddr *address, socklen_t length, Condition &cond) - { - assert(m_handle != Invalid); - - cond = Condition::None; - - m_proto.connect(*this, address, length, cond); - } - - /** - * Overloaded function. - * - * \pre isOpen() + * \pre address != nullptr * \param address the address * \param length the address length - * \throw net::Error on failures */ - inline void connect(const sockaddr *address, socklen_t length) - { - Condition dummy; - - connect(address, length, dummy); - } - - /** - * Overloaded function. - * - * \pre isOpen() - * \param address the address - * \param cond the condition - * \throw net::Error on failures - */ - inline void connect(const Address &address, Condition &cond) - { - connect(address.address(), address.length(), cond); - } - - /** - * Overloaded function. - * - * \pre isOpen() - * \param address the address - * \throw net::Error on failures - */ - inline void connect(const Address &address) - { - Condition dummy; - - connect(address.address(), address.length(), dummy); - } - - /** - * Continue connect process. - * - * \pre isOpen() - * \param cond the condition require for next selection - * \throw net::Error on failures - */ - inline void resumeConnect(Condition &cond) - { - assert(m_handle != Invalid); - - cond = Condition::None; - - m_proto.resumeConnect(*this, cond); - } - - /** - * Continue connect process. - * - * \pre isOpen() - * \throw net::Error on failures - */ - inline void resumeConnect() - { - Condition dummy; - - resumeConnect(dummy); - } - - /** - * Accept a new client. - * - * If no connection is available immediately, returns an invalid socket. - * - * \pre isOpen() - * \param address the client information - * \param cond the condition to wait to complete accept on the **client** - * \return the new client or an invalid if no client is immediately available - * \throw net::Error on failures - */ - Socket<Address, Protocol> accept(Address &address, Condition &cond) - { - assert(m_handle != Invalid); - - sockaddr_storage storage; - socklen_t length = sizeof (storage); - - cond = Condition::None; - - Socket<Address, Protocol> client = m_proto.accept(*this, reinterpret_cast<sockaddr *>(&storage), &length, cond); - - address = Address(reinterpret_cast<sockaddr *>(&storage), length); - - return client; - } - - /** - * Overloaded function. - * - * \pre isOpen() - * \param address the client information - * \return the new client or an invalid if no client is immediately available - * \throw net::Error on failures - */ - inline Socket<Address, Protocol> accept(Address &address) - { - Condition dummy; - - return accept(address, dummy); - } - - /** - * Overlaoded function. - * - * \pre isOpen() - * \return the new client or an invalid if no client is immediately available - * \throw net::Error on failures - */ - inline Socket<Address, Protocol> accept() - { - Address da; - Condition dc; - - return accept(da, dc); - } - - /** - * Continue accept process. - * - * \pre isOpen() - * \param cond the condition - * \throw net::Error on failures - * \note This should be called on the returned client from accept - */ - inline void resumeAccept(Condition &cond) - { - assert(m_handle != Invalid); - - cond = Condition::None; - - m_proto.resumeAccept(*this, cond); - } - - /** - * Overloaded function. - * - * \pre isOpen() - * \throw net::Error on failures - */ - inline void resumeAccept() - { - Condition dummy; - - resumeAccept(dummy); - } - - /** - * Receive some data. - * - * \pre isOpen() - * \param data the destination buffer - * \param length the data length - * \param cond the condition - * \return the number of bytes received - * \throw net::Error on failures - */ - inline std::size_t recv(void *data, std::size_t length, Condition &cond) - { - assert(m_handle != Invalid); - - cond = Condition::None; - - return m_proto.recv(*this, data, length, cond); - } - - /** - * Overloaded function. - * - * \pre isOpen() - * \param data the destination buffer - * \param length the data length - * \return the number of bytes received - * \throw net::Error on failures - */ - inline std::size_t recv(void *data, std::size_t length) - { - Condition dummy; - - return recv(data, length, dummy); - } - - /** - * Overloaded function. - * - * \pre isOpen() - * \param count number of bytes desired - * \param cond the condition - * \return the result string - * \throw net::Error on failures - */ - std::string recv(std::size_t count, Condition &cond) - { - assert(m_handle != Invalid); - - std::string result; - - result.resize(count); - auto n = recv(const_cast<char *>(result.data()), count, cond); - result.resize(n); - - return result; - } - - /** - * Overloaded function. - * - * \pre isOpen() - * \param count number of bytes desired - * \return the result string - * \throw net::Error on failures - */ - inline std::string recv(std::size_t count) - { - Condition dummy; - - return recv(count, dummy); - } - - /** - * Send some data. - * - * \pre isOpen() - * \param data the data to send - * \param length the length - * \param cond the condition - * \return the number of bytes sent - * \throw net::Error on failures - */ - inline std::size_t send(const void *data, std::size_t length, Condition &cond) - { - assert(m_handle != Invalid); - - cond = Condition::None; - - return m_proto.send(*this, data, length, cond); - } - - /** - * Overloaded function. - * - * \pre isOpen() - * \param data the data to send - * \param length the length - * \return the number of bytes sent - * \throw net::Error on failures - */ - inline std::size_t send(const void *data, std::size_t length) - { - Condition dummy; - - return send(data, length, dummy); - } - - /** - * Overloaded function. - * - * \pre isOpen() - * \param data the data to send - * \param cond the condition - * \return the number of bytes sent - * \throw net::Error on failures - */ - inline std::size_t send(const std::string &data, Condition &cond) - { - return send(data.c_str(), data.length(), cond); - } - - /** - * Overloaded function. - * - * \pre isOpen() - * \param data the data to send - * \return the number of bytes sent - * \throw net::Error on failures - */ - inline std::size_t send(const std::string &data) - { - Condition dummy; - - return send(data.c_str(), data.length(), dummy); - } - - /** - * Send some data to the given client. - * - * \pre isOpen() - * \param data the data - * \param length the length - * \param address the client address - * \param addrlen the client address length - * \param cond the condition - * \return the number of bytes sent - * \throw net::Error on failures - */ - inline std::size_t sendto(const void *data, std::size_t length, const sockaddr *address, socklen_t addrlen, Condition &cond) - { - assert(m_handle != Invalid); - - cond = Condition::None; - - return m_proto.sendto(*this, data, length, address, addrlen, cond); - } - - /** - * Overloaded function. - * - * \pre isOpen() - * \param data the data - * \param length the length - * \param address the client address - * \param addrlen the client address length - * \return the number of bytes sent - * \throw net::Error on failures - */ - inline std::size_t sendto(const void *data, std::size_t length, const sockaddr *address, socklen_t addrlen) - { - Condition dummy; - - return send(data, length, address, addrlen, dummy); - } - - /** - * Overloaded function. - * - * \pre isOpen() - * \param data the data - * \param length the length - * \param address the client address - * \param cond the condition - * \return the number of bytes sent - * \throw net::Error on failures - */ - inline std::size_t sendto(const void *data, std::size_t length, const Address &address, Condition &cond) - { - return sendto(data, length, address.address(), address.length(), cond); - } - - /** - * Overloaded function. - * - * \pre isOpen() - * \param data the data - * \param length the length - * \param address the client address - * \return the number of bytes sent - * \throw net::Error on failures - */ - inline std::size_t sendto(const void *data, std::size_t length, const Address &address) - { - Condition dummy; - - return sendto(data, length, address.address(), address.length(), dummy); - } - - /** - * Overloaded function. - * - * \pre isOpen() - * \param data the data - * \param cond the condition - * \param address the client address - * \return the number of bytes sent - * \throw net::Error on failures - */ - inline std::size_t sendto(const std::string &data, const Address &address, Condition &cond) - { - return sendto(data.c_str(), data.length(), address.address(), address.length(), cond); - } - - /** - * Overloaded function. - * - * \pre isOpen() - * \param data the data - * \param address the client address - * \return the number of bytes sent - * \throw net::Error on failures - */ - inline std::size_t sendto(const std::string &data, const Address &address) - { - Condition dummy; - - return sendto(data.c_str(), data.length(), address.address(), address.length(), dummy); - } - - /** - * Receive some data from a client. - * - * \pre isOpen() - * \param data the destination buffer - * \param length the buffer length - * \param address the client information - * \param addrlen the client address initial length - * \param cond the condition - * \return the number of bytes received - * \throw net::Error on failures - */ - inline std::size_t recvfrom(void *data, std::size_t length, sockaddr *address, socklen_t *addrlen, Condition &cond) - { - assert(m_handle != Invalid); - - cond = Condition::None; - - return m_proto.recvfrom(*this, data, length, address, addrlen, cond); - } - - /** - * Overloaded function. - * - * \pre isOpen() - * \param data the destination buffer - * \param length the buffer length - * \param address the client information - * \param addrlen the client address initial length - * \return the number of bytes received - * \throw net::Error on failures - */ - inline std::size_t recvfrom(void *data, std::size_t length, sockaddr *address, socklen_t *addrlen) - { - Condition dummy; - - return recvfrom(data, length, address, addrlen, dummy); - } - - /** - * Overloaded function. - * - * \pre isOpen() - * \param data the destination buffer - * \param length the buffer length - * \param address the client information - * \param cond the condition - * \return the number of bytes received - * \throw net::Error on failures - */ - std::size_t recvfrom(void *data, std::size_t length, Address &address, Condition &cond) - { - sockaddr_storage storage; - socklen_t addrlen = sizeof (sockaddr_storage); - - auto n = recvfrom(data, length, reinterpret_cast<sockaddr *>(&storage), &addrlen, cond); - - if (n != 0 && cond == Condition::None) - address = Address(reinterpret_cast<sockaddr *>(&storage), addrlen); - - return n; - } - - /** - * Overloaded function. - * - * \pre isOpen() - * \param data the destination buffer - * \param length the buffer length - * \param address the client information - * \return the number of bytes received - * \throw net::Error on failures - */ - inline std::size_t recvfrom(void *data, std::size_t length, Address &address) - { - Condition dummy; - - return recvfrom(data, length, address, dummy); - } - - /** - * Overloaded function. - * - * \pre isOpen() - * \param data the destination buffer - * \param length the buffer length - * \return the number of bytes received - * \throw net::Error on failures - */ - inline std::size_t recvfrom(void *data, std::size_t length) - { - Address da; - Condition dc; - - return recvfrom(data, length, da, dc); - } - - /** - * Overloaded function. - * - * \pre isOpen() - * \param count the number of bytes desired - * \param address the client information - * \param cond the condition - * \return the result string - * \throw net::Error on failures - */ - std::string recvfrom(std::size_t count, Address &address, Condition &cond) - { - std::string result; - - result.resize(count); - auto n = recvfrom(const_cast<char *>(result.data()), count, address, cond); - result.resize(n); - - return result; - } - - /** - * Overloaded function. - * - * \pre isOpen() - * \param count the number of bytes desired - * \param address the client information - * \return the result string - * \throw net::Error on failures - */ - inline std::string recvfrom(std::size_t count, Address &address) - { - Condition dummy; - - return recvfrom(count, address, dummy); - } - - /** - * Overloaded function. - * - * \pre isOpen() - * \param count the number of bytes desired - * \return the result string - * \throw net::Error on failures - */ - inline std::string recvfrom(std::size_t count) - { - Address da; - Condition dc; - - return recvfrom(count, da, dc); - } - - /** - * Close the socket. - * - * Automatically called from the destructor. - */ - void close() - { - if (m_handle != Invalid) { -#if defined(_WIN32) - ::closesocket(m_handle); -#else - ::close(m_handle); -#endif - m_handle = Invalid; - } - } - - /** - * Assignment operator forbidden. - * - * \return *this - */ - Socket &operator=(const Socket &) = delete; - - /** - * Transfer ownership from other to this. The other socket is left - * invalid and will not be closed. - * - * \param other the other socket - * \return this - */ - Socket &operator=(Socket &&other) noexcept - { - m_handle = other.m_handle; - m_proto = std::move(other.m_proto); - - other.m_handle = Invalid; - - return *this; - } -}; - -/** - * Compare two sockets. - * - * \param s1 the first socket - * \param s2 the second socket - * \return true if they equals - */ -template <typename Address, typename Protocol> -inline bool operator==(const Socket<Address, Protocol> &s1, const Socket<Address, Protocol> &s2) -{ - return s1.handle() == s2.handle(); -} - -/** - * Compare two sockets. - * - * \param s1 the first socket - * \param s2 the second socket - * \return true if they are different - */ -template <typename Address, typename Protocol> -inline bool operator!=(const Socket<Address, Protocol> &s1, const Socket<Address, Protocol> &s2) -{ - return s1.handle() != s2.handle(); -} - -/** - * Compare two sockets. - * - * \param s1 the first socket - * \param s2 the second socket - * \return true if s1 < s2 - */ -template <typename Address, typename Protocol> -inline bool operator<(const Socket<Address, Protocol> &s1, const Socket<Address, Protocol> &s2) -{ - return s1.handle() < s2.handle(); -} - -/** - * Compare two sockets. - * - * \param s1 the first socket - * \param s2 the second socket - * \return true if s1 > s2 - */ -template <typename Address, typename Protocol> -inline bool operator>(const Socket<Address, Protocol> &s1, const Socket<Address, Protocol> &s2) -{ - return s1.handle() > s2.handle(); -} - -/** - * Compare two sockets. - * - * \param s1 the first socket - * \param s2 the second socket - * \return true if s1 <= s2 - */ -template <typename Address, typename Protocol> -inline bool operator<=(const Socket<Address, Protocol> &s1, const Socket<Address, Protocol> &s2) -{ - return s1.handle() <= s2.handle(); -} - -/** - * Compare two sockets. - * - * \param s1 the first socket - * \param s2 the second socket - * \return true if s1 >= s2 - */ -template <typename Address, typename Protocol> -inline bool operator>=(const Socket<Address, Protocol> &s1, const Socket<Address, Protocol> &s2) -{ - return s1.handle() >= s2.handle(); -} - -/** - * \brief Predefined protocols. - */ -namespace protocol { - -/** - * \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 Tcp { -public: - /** - * Socket type. - * - * \return SOCK_STREAM - */ - inline int type() const noexcept - { - return SOCK_STREAM; - } - - /** - * Do nothing. - * - * This function is just present for compatibility, it should never be called. - */ - template <typename Address> - inline void create(Socket<Address, Tcp> &) const noexcept - { - } - - /** - * Initiate connection. - * - * \param sc the socket - * \param address the address - * \param length the address length - * \param cond the condition - */ - template <typename Address, typename Protocol> - void connect(Socket<Address, Protocol> &sc, const sockaddr *address, socklen_t length, Condition &cond) - { - if (::connect(sc.handle(), address, length) == Failure) { - /* - * Determine if the error comes from a non-blocking connect that cannot be - * accomplished yet. - */ -#if defined(_WIN32) - int error = WSAGetLastError(); - - if (error == WSAEWOULDBLOCK) - cond = Condition::Writable; - else - throw Error(Error::System, "connect", error); -#else - if (errno == EINPROGRESS) - cond = Condition::Writable; - else - throw Error(Error::System, "connect"); -#endif - } - } - - /** - * Resume the connection. - * - * Just check for SOL_SOCKET/SO_ERROR. - * - * User is responsible to wait before the socket is writable, otherwise behavior is undefined. - * - * \param sc the socket - * \param cond the condition - */ - template <typename Address, typename Protocol> - void resumeConnect(Socket<Address, Protocol> &sc, Condition &cond) - { - int error = sc.template get<int>(SOL_SOCKET, SO_ERROR); - -#if defined(_WIN32) - if (error == WSAEWOULDBLOCK) - cond = Condition::Writable; - else if (error != 0) - throw Error(Error::System, "connect", error); -#else - if (error == EINPROGRESS) - cond = Condition::Writable; - else if (error != 0) - throw Error(Error::System, "connect", error); -#endif - } - - /** - * Accept a new client. - * - * If there are no pending connection, an invalid socket is returned, condition is left to Condition::None. - * - * \param sc the socket - * \param address the address - * \param length the length - * \return the new socket - */ - template <typename Address, typename Protocol> - Socket<Address, Protocol> accept(Socket<Address, Protocol> &sc, sockaddr *address, socklen_t *length, Condition &) - { - Handle handle = ::accept(sc.handle(), address, length); - - if (handle == Invalid) - return Socket<Address, Protocol>(); - - return Socket<Address, Protocol>(handle); - } - - /** - * Resume accept process. - * - * No-op for TCP. - */ - template <typename Address, typename Protocol> - inline void resumeAcept(Socket<Address, Protocol> &, Condition &) const noexcept - { - } - - /** - * Receive some data. - * - * \param sc the socket - * \param data the destination buffer - * \param length the buffer length - * \param cond the condition - * \return the number of byte received - */ - template <typename Address> - std::size_t recv(Socket<Address, Tcp> &sc, void *data, std::size_t length, Condition &cond) - { - int max = length > INT_MAX ? INT_MAX : static_cast<int>(length); - int nbread = ::recv(sc.handle(), (Arg)data, max, 0); - - if (nbread == Failure) { -#if defined(_WIN32) - int error = WSAGetLastError(); - - if (error == WSAEWOULDBLOCK) { - nbread = 0; - cond = Condition::Readable; - } else - throw Error(Error::System, "recv", error); -#else - if (errno == EAGAIN || errno == EWOULDBLOCK) { - nbread = 0; - cond = Condition::Readable; - } else - throw Error(Error::System, "recv"); -#endif - } - - return static_cast<std::size_t>(nbread); - } - - /** - * Send some data. - * - * \param sc the socket - * \param data the data to send - * \param length the length - * \param cond the condition - * \return the number of bytes sent - */ - template <typename Address> - std::size_t send(Socket<Address, Tcp> &sc, const void *data, std::size_t length, Condition &cond) - { - int max = length > INT_MAX ? INT_MAX : static_cast<int>(length); - int nbsent = ::send(sc.handle(), (ConstArg)data, max, 0); - - if (nbsent == Failure) { -#if defined(_WIN32) - int error = WSAGetLastError(); - - if (error == WSAEWOULDBLOCK) { - nbsent = 0; - cond = Condition::Writable; - } else - throw Error(Error::System, "send", error); -#else - if (errno == EAGAIN || errno == EWOULDBLOCK) { - nbsent = 0; - cond = Condition::Writable; - } else - throw Error(Error::System, "send"); -#endif - } - - return static_cast<unsigned>(nbsent); - } -}; - -/** - * \brief Clear UDP type. - * - * This class is the basic implementation of UDP sockets. - */ -class Udp { -public: - /** - * Socket type. - * - * \return SOCK_DGRAM - */ - inline int type() const noexcept - { - return SOCK_DGRAM; - } - - /** - * Do nothing. - */ - template <typename Address, typename Protocol> - inline void create(Socket<Address, Protocol> &) noexcept - { - } - - /** - * Receive some data. - * - * \param sc the socket - * \param data the data - * \param length the length - * \param address the source address - * \param addrlen the source address in/out length - * \param cond the condition - * \return the number of bytes received - */ - template <typename Address, typename Protocol> - std::size_t recvfrom(Socket<Address, Protocol> &sc, void *data, std::size_t length, sockaddr *address, socklen_t *addrlen, Condition &cond) - { - int max = length > INT_MAX ? INT_MAX : static_cast<int>(length); - int nbread; - - nbread = ::recvfrom(sc.handle(), (Arg)data, max, 0, address, addrlen); - - if (nbread == Failure) { -#if defined(_WIN32) - int error = WSAGetLastError(); - - if (error == WSAEWOULDBLOCK) { - nbread = 0; - cond = Condition::Writable; - } else - throw Error(Error::System, "recvfrom"); -#else - if (errno == EAGAIN || errno == EWOULDBLOCK) { - nbread = 0; - cond = Condition::Writable; - } else - throw Error(Error::System, "recvfrom"); -#endif - } - - return static_cast<unsigned>(nbread); - } - - /** - * Send some data. - * - * \param sc the socket - * \param data the data to send - * \param length the data length - * \param address the destination address - * \param addrlen the destination address length - * \param cond the condition - * \return the number of bytes sent - */ - template <typename Address, typename Protocol> - std::size_t sendto(Socket<Address, Protocol> &sc, const void *data, std::size_t length, const sockaddr *address, socklen_t addrlen, Condition &cond) - { - int max = length > INT_MAX ? INT_MAX : static_cast<int>(length); - int nbsent; - - nbsent = ::sendto(sc.handle(), (ConstArg)data, max, 0, address, addrlen); - if (nbsent == Failure) { -#if defined(_WIN32) - int error = WSAGetLastError(); - - if (error == WSAEWOULDBLOCK) { - nbsent = 0; - cond = Condition::Writable; - } else - throw Error(Error::System, "sendto", error); -#else - if (errno == EAGAIN || errno == EWOULDBLOCK) { - nbsent = 0; - cond = Condition::Writable; - } else - throw Error(Error::System, "sendto"); -#endif - } - - return static_cast<unsigned>(nbsent); - } -}; - -#if !defined(NET_NO_SSL) - -/** - * \brief Experimental TLS support. - * \ingroup net-module-tls - * \warning This class is highly experimental. - */ -class Tls : private Tcp { -private: - using Context = std::shared_ptr<SSL_CTX>; - using Ssl = std::unique_ptr<SSL, void (*)(SSL *)>; - - // OpenSSL objects. - Context m_context; - Ssl m_ssl{nullptr, nullptr}; - - // Status. - bool m_tcpconnected{false}; - - /* - * User definable parameters. - */ - ssl::Method m_method{ssl::Tlsv1}; - std::string m_key; - std::string m_certificate; - bool m_verify{false}; - - // Construct with a context and ssl, for Tls::accept. - Tls(Context context, Ssl ssl) - : m_context(std::move(context)) - , m_ssl(std::move(ssl)) - { - } - - 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(const std::string &func, Condition &cond, Function &&function) - { - auto ret = function(); - - if (ret <= 0) { - int no = SSL_get_error(m_ssl.get(), ret); - - switch (no) { - case SSL_ERROR_WANT_READ: - cond = Condition::Readable; - break; - case SSL_ERROR_WANT_WRITE: - cond = Condition::Writable; - break; - default: - throw Error(Error::System, func, error()); - } - } - } - - template <typename Address, typename Protocol> - void doConnect(Socket<Address, Protocol> &, Condition &cond) - { - wrap("connect", cond, [&] () -> int { - return SSL_connect(m_ssl.get()); - }); - } - - template <typename Address, typename Protocol> - void doAccept(Socket<Address, Protocol> &, Condition &cond) - { - wrap("accept", cond, [&] () -> int { - return SSL_accept(m_ssl.get()); - }); - } - -public: - /** - * \copydoc Tcp::type - */ - inline int type() const noexcept - { - return SOCK_STREAM; - } - - /** - * Empty TLS constructor. - */ - inline Tls() - { -#if !defined(NET_NO_SSL_AUTO_INIT) - ssl::init(); -#endif - } - - /** - * Set the method. - * - * \param method the method - * \pre the socket must not be already created - */ - inline void setMethod(ssl::Method method) noexcept - { - assert(!m_context); - assert(!m_ssl); - - m_method = method; - } - - /** - * Use the specified private key file. - * - * \param file the path to the private key - */ - inline void setPrivateKey(std::string file) noexcept - { - m_key = std::move(file); - } - - /** - * Use the specified certificate file. - * - * \param file the path to the file - */ - inline void setCertificate(std::string file) noexcept - { - m_certificate = std::move(file); - } - - /** - * Set to true if we must verify the certificate and private key. - * - * \param verify the mode - */ - inline void setVerify(bool verify = true) noexcept - { - m_verify = verify; - } - - /** - * Initialize the SSL objects after have created. - * - * \param sc the socket - * \throw net::Error on errors - */ - template <typename Address> - void create(Socket<Address, Tls> &sc) - { - auto method = (m_method == ssl::Tlsv1) ? TLSv1_method() : SSLv23_method(); - - m_context = Context(SSL_CTX_new(method), SSL_CTX_free); - m_ssl = Ssl(SSL_new(m_context.get()), SSL_free); - - SSL_set_fd(m_ssl.get(), static_cast<int>(sc.handle())); - - /* - * Load certificates, the wrap function requires a condition so just add a dummy value. - */ - Condition dummy; - - if (m_certificate.size() > 0) - wrap("SSL_CTX_use_certificate_file", dummy, [&] () -> int { - return SSL_CTX_use_certificate_file(m_context.get(), m_certificate.c_str(), SSL_FILETYPE_PEM); - }); - if (m_key.size() > 0) - wrap("SSL_CTX_use_PrivateKey_file", dummy, [&] () -> int { - return SSL_CTX_use_PrivateKey_file(m_context.get(), m_key.c_str(), SSL_FILETYPE_PEM); - }); - if (m_verify && !SSL_CTX_check_private_key(m_context.get())) - throw Error(Error::System, "(openssl)", "unable to verify key"); - } - - /** - * Initiate connection. - * - * \param sc the socket - * \param address the address - * \param length the address length - * \param cond the condition - */ - template <typename Address, typename Protocol> - void connect(Socket<Address, Protocol> &sc, const sockaddr *address, socklen_t length, Condition &cond) - { - // 1. Connect using raw TCP. - Tcp::connect(sc, address, length, cond); - - // 2. If the connection is complete (e.g. non-blocking), try handshake. - if (cond == Condition::None) { - m_tcpconnected = true; - doConnect(sc, cond); - } - } - - /** - * Resume the connection. - * - * \param sc the socket - * \param cond the condition to wait - */ - template <typename Address, typename Protocol> - void connect(Socket<Address, Protocol> &sc, Condition &cond) - { - // 1. Be sure to complete standard connect before. - if (!m_tcpconnected) { - Tcp::connect(sc, cond); - m_tcpconnected = (cond == Condition::None); - } - - // 2. Do SSL connect. - if (m_tcpconnected) - doConnect(sc, cond); - } - - /** - * Accept a new client. - * - * If there are no pending connection, an invalid socket is returned, condition is left to Condition::None. - * - * \param sc the socket - * \param address the address - * \param length the length - * \param cond the condition to wait - * \return the new socket - */ - template <typename Address> - Socket<Address, Tls> accept(Socket<Address, Tls> &sc, sockaddr *address, socklen_t *length, Condition &cond) - { - // 1. TCP returns empty client if no pending connection is available. - auto client = Tcp::accept(sc, address, length, cond); - - // 2. If a client is available, try initial accept. - if (client.isOpen()) { - Tls &proto = client.protocol(); - - // 2.1. Share the context. - proto.m_context = m_context; - - // 2.2. Create new SSL instance. - proto.m_ssl = Ssl(SSL_new(m_context.get()), SSL_free); - - SSL_set_fd(proto.m_ssl.get(), static_cast<int>(client.handle())); - - // 2.3. Try accept process on the **new** client. - proto.doAccept(client, cond); - } - - return client; - } - - /** - * Resume accept process. - * - * \param sc the socket - * \param cond the condition to wait - * \throw net::Error on failures - */ - template <typename Address, typename Protocol> - inline void accept(Socket<Address, Protocol> &sc, Condition &cond) - { - doAccept(sc, cond); - } - - /** - * Receive some data. - * - * \param data the destination buffer - * \param length the buffer length - * \param cond the condition - * \return the number of bytes received - */ - template <typename Address> - std::size_t recv(Socket<Address, Tls> &, void *data, std::size_t length, Condition &cond) - { - int max = length > INT_MAX ? INT_MAX : static_cast<int>(length); - int nbread = 0; - - wrap("recv", cond, [&] () -> int { - return (nbread = SSL_read(m_ssl.get(), data, max)); - }); - - return static_cast<std::size_t>(nbread < 0 ? 0 : nbread); - } - - /** - * Send some data. - * - * \param data the data to send - * \param length the length - * \param cond the condition - * \return the number of bytes sent - */ - template <typename Address> - std::size_t send(Socket<Address, Tls> &, const void *data, std::size_t length, Condition &cond) - { - int max = length > INT_MAX ? INT_MAX : static_cast<int>(length); - int nbsent = 0; - - wrap("send", cond, [&] () -> int { - return (nbsent = SSL_write(m_ssl.get(), data, max)); - }); - - return static_cast<std::size_t>(nbsent < 0 ? 0 : nbsent); - } -}; - -#endif // !NET_NO_SSL - -} // !protocol - -/** - * \brief Predefined addresses. - */ -namespace address { - -/** - * \brief Generic address. - * \ingroup net-module-addresses - * - * This address can store anything that fits into a sockaddr_storage. - */ -class GenericAddress { -private: - sockaddr_storage m_address; - socklen_t m_length{0}; - -public: - /** - * Construct a null address. - */ - inline GenericAddress() noexcept - { - std::memset(&m_address, 0, sizeof (sockaddr_storage)); - } - - /** - * Construct an address. - * - * \pre address is not null - * \pre length <= sizeof (sockaddr_storage) - * \param address the address to copy - * \param length the address length - */ - inline GenericAddress(const sockaddr *address, socklen_t length) noexcept + inline Address(const sockaddr *address, socklen_t length) noexcept : m_length(length) { assert(address); - assert(static_cast<unsigned>(length) <= sizeof (sockaddr_storage)); - - std::memset(&m_address, 0, sizeof (sockaddr_storage)); - std::memcpy(&m_address, address, length); - } - - /** - * Get the address family. - * - * \return the address family - */ - inline int domain() const noexcept - { - return m_address.ss_family; + + std::memcpy(&m_storage, address, length); } /** @@ -2823,19 +1099,41 @@ * * \return the address */ - inline sockaddr *address() noexcept + inline const sockaddr *get() const noexcept { - return reinterpret_cast<sockaddr *>(&m_address); + return reinterpret_cast<const sockaddr *>(&m_storage); + } + + /** + * Overloaded function + * + * \return the address + */ + inline sockaddr *get() noexcept + { + return reinterpret_cast<sockaddr *>(&m_storage); } /** - * Overloaded function. + * Get the underlying address as the given type (e.g sockaddr_in). * - * \return the address + * \return the address reference */ - inline const sockaddr *address() const noexcept + template <typename T> + inline const T &as() const noexcept { - return reinterpret_cast<const sockaddr *>(&m_address); + 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); } /** @@ -2847,649 +1145,45 @@ { return m_length; } -}; - -/** - * Compare two generic addresses. - * - * \param a1 the first address - * \param a2 the second address - * \return true if they equal - */ -inline bool operator==(const GenericAddress &a1, const GenericAddress &a2) noexcept -{ - return a1.length() == a2.length() && std::memcmp(a1.address(), a2.address(), 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 GenericAddress &a1, const GenericAddress &a2) noexcept -{ - return !(a1 == a2); -} - -/** - * \brief Generic IP address. - * \ingroup net-module-addresses - * - * You can use this address instead of Ipv4 or Ipv6 if you don't know which address to use at runtime. However, - * when creating your socket, you will need to define the correct domain. - */ -class Ip { -private: - union { - sockaddr_in6 m_sin6; - sockaddr_in m_sin; - }; - - int m_domain; - -public: - /** - * Create IP address, defaults to IPv4. - * - * \pre domain must be AF_INET or AF_INET6 - * \param domain the domain - */ - inline Ip(int domain = AF_INET) noexcept - : m_domain(domain) - { - assert(domain == AF_INET || domain == AF_INET6); - - std::memset(&m_sin, 0, sizeof (sockaddr_in)); - } /** - * Create an IP address on the specific ip address. + * Get the address domain. * - * \pre domain must be AF_INET or AF_INET6 - * \param ip the address or "*" for any - * \param port the port - * \param domain the domain - * \warning If NET_HAVE_INET_PTON is undefined, host can not be other than "*" - * \throw net::Error on failures or if inet_pton is unavailable - */ - inline Ip(const std::string &ip, std::uint16_t port, int domain) - : Ip(domain) - { - if (m_domain == AF_INET) - make(ip, port, m_sin); - else - make(ip, port, m_sin6); - } - - /** - * Create the IP address from the storage. - * - * \pre the storage domain must be AF_INET or AF_INET6 - * \param ss the the storage - * \param length the storage length - */ - inline Ip(const sockaddr *ss, socklen_t length) noexcept - : Ip(ss->sa_family) - { - assert(ss->sa_family == AF_INET || ss->sa_family == AF_INET6); - - if (ss->sa_family == AF_INET) - std::memcpy(&m_sin, ss, length); - else - std::memcpy(&m_sin6, ss, length); - } - - /** - * Get the domain. - * - * \return AF_INET or AF_INET6 + * \return the domain */ inline int domain() const noexcept { - return m_domain; - } - - /** - * Get the underlying address, may be a sockaddr_in or sockaddr_in6. - * - * \return the address - */ - inline const sockaddr *address() const noexcept - { - return m_domain == AF_INET ? reinterpret_cast<const sockaddr *>(&m_sin) : reinterpret_cast<const sockaddr *>(&m_sin6); - } - - /** - * Get the address length. - * - * \return the address length - */ - inline socklen_t length() const noexcept - { - return m_domain == AF_INET ? sizeof (sockaddr_in) : sizeof (sockaddr_in6); - } - - /** - * Retrieve the port. - * - * \return the port - */ - inline std::uint16_t port() const noexcept - { - return m_domain == AF_INET ? ntohs(m_sin.sin_port) : ntohs(m_sin6.sin6_port); - } - - /** - * Get the ip address. - * - * \return the ip address - * \throw net::Error on errors or if inet_ntop is unavailable - */ - inline std::string ip() const - { - return m_domain == AF_INET ? ip(m_sin) : ip(m_sin6); - } - - /** - * Prepare the sockaddr_in structure with the given ip. - * - * \param ip the ip address - * \param port the port - * \param sin the Ipv4 address - * \throw net::Error if inet_pton is unavailable - */ - static void make(const std::string &ip, std::uint16_t port, sockaddr_in &sin) - { -#if !defined(NET_NO_AUTO_INIT) - net::init(); -#endif - - sin.sin_family = AF_INET; - sin.sin_port = htons(port); - - if (ip == "*") - sin.sin_addr.s_addr = INADDR_ANY; -#if defined(NET_HAVE_INET_PTON) - else if (inet_pton(AF_INET, ip.c_str(), &sin.sin_addr) <= 0) - throw Error(Error::System, "inet_pton"); -#else - else - throw Error(Error::System, "inet_pton", std::strerror(ENOSYS)); -#endif - } - - /** - * Prepare the sockaddr_in structure with the given ip. - * - * \param ip the ip address - * \param port the port - * \param sin6 the Ipv6 address - * \throw net::Error if inet_pton is unavailable - */ - static void make(const std::string &ip, std::uint16_t port, sockaddr_in6 &sin6) - { -#if !defined(NET_NO_AUTO_INIT) - net::init(); -#endif - - sin6.sin6_family = AF_INET6; - sin6.sin6_port = htons(port); - - if (ip == "*") - sin6.sin6_addr = in6addr_any; -#if defined(NET_HAVE_INET_PTON) - else if (inet_pton(AF_INET6, ip.c_str(), &sin6.sin6_addr) <= 0) - throw Error(Error::System, "inet_pton"); -#else - else - throw Error(Error::System, "inet_pton", std::strerror(ENOSYS)); -#endif - } - - /** - * Get the underlying ip from the given address. - * - * \param sin the Ipv4 address - * \return the ip address - * \throw net::Error if inet_ntop is unavailable - */ - static std::string ip(const sockaddr_in &sin) - { -#if !defined(NET_NO_AUTO_INIT) - net::init(); -#endif - -#if !defined(NET_HAVE_INET_NTOP) - (void)sin; - - throw Error(Error::System, "inet_ntop", std::strerror(ENOSYS)); -#else - char result[INET_ADDRSTRLEN + 1]; - - std::memset(result, 0, sizeof (result)); - - if (!inet_ntop(AF_INET, const_cast<in_addr *>(&sin.sin_addr), result, sizeof (result))) - throw Error(Error::System, "inet_ntop"); - - return result; -#endif - } - - /** - * Get the underlying ip from the given address. - * - * \param sin6 the Ipv6 address - * \return the ip address - * \throw net::Error if inet_ntop is unavailable - */ - static std::string ip(const sockaddr_in6 &sin6) - { -#if !defined(NET_NO_AUTO_INIT) - net::init(); -#endif - -#if !defined(NET_HAVE_INET_NTOP) - (void)sin6; - - throw Error(Error::System, "inet_ntop", std::strerror(ENOSYS)); -#else - char result[INET6_ADDRSTRLEN]; - - std::memset(result, 0, sizeof (result)); - - if (!inet_ntop(AF_INET6, const_cast<in6_addr *>(&sin6.sin6_addr), result, sizeof (result))) - throw Error(Error::System, "inet_ntop"); - - return result; -#endif - } - - /** - * Resolve an hostname. - * - * This function wraps getaddrinfo and returns the first result. - * - * \param host the hostname - * \param service the service name (port or name) - * \param domain the domain (e.g. AF_INET) - * \param type the socket type (e.g. SOCK_STREAM) - * \return the resolved address - * \throw net::Error on failures - */ - static Ip resolve(const std::string &host, const std::string &service, int domain = AF_INET, int type = SOCK_STREAM) - { - assert(domain == AF_INET || domain == AF_INET6); -#if !defined(NET_NO_AUTO_INIT) - net::init(); -#endif - - struct addrinfo hints, *res; - - std::memset(&hints, 0, sizeof (struct addrinfo)); - hints.ai_family = domain; - hints.ai_socktype = type; - - int e = getaddrinfo(host.c_str(), service.c_str(), &hints, &res); - - if (e != 0) - throw Error(Error::System, "getaddrinfo", gai_strerror(e)); - - Ip ip(res->ai_addr, res->ai_addrlen); - - freeaddrinfo(res); - - return ip; + return m_storage.ss_family; } }; /** - * \brief Ipv4 only address. - * \ingroup net-module-addresses - */ -class Ipv4 { -private: - sockaddr_in m_sin; - -public: - /** - * Create an Ipv4 address. - */ - inline Ipv4() noexcept - { - std::memset(&m_sin, 0, sizeof (sockaddr_in)); - } - - /** - * Create an Ipv4 address on the specific ip address. - * - * \param ip the address or "*" for any - * \param port the port - * \warning If NET_HAVE_INET_PTON is undefined, host can not be other than "*" - * \throw net::Error on failures or if inet_pton is unavailable - */ - inline Ipv4(const std::string &ip, std::uint16_t port) - : Ipv4() - { - Ip::make(ip, port, m_sin); - } - - /** - * Create the IP address from the storage. - * - * \pre the storage domain must be AF_INET - * \param ss the the storage - * \param length the storage length - */ - inline Ipv4(const sockaddr *ss, socklen_t length) noexcept - { - assert(ss->sa_family == AF_INET); - - std::memcpy(&m_sin, ss, length); - } - - /** - * Get the domain. - * - * \return AF_INET - */ - inline int domain() const noexcept - { - return AF_INET; - } - - /** - * Get the underlying address. - * - * \return the address - */ - inline const sockaddr *address() const noexcept - { - return reinterpret_cast<const sockaddr *>(&m_sin); - } - - /** - * Get the address length. - * - * \return the size of sockaddr_in - */ - inline socklen_t length() const noexcept - { - return sizeof (sockaddr_in); - } - - /** - * Get the port. - * - * \return the port - */ - inline std::uint16_t port() const noexcept - { - return ntohs(m_sin.sin_port); - } - - /** - * Get the ip address. - * - * \return the ip address - * \throw net::Error on errors or if inet_ntop is unavailable - */ - inline std::string ip() const - { - return Ip::ip(m_sin); - } - - /** - * Same as Ip::resolve with AF_INET as domain. - * - * \param host the hostname - * \param service the service name (port or name) - * \param type the socket type (e.g. SOCK_STREAM) - * \return the resolved address - * \throw net::Error on failures - */ - static Ipv4 resolve(const std::string &host, const std::string &service, int type = SOCK_STREAM) - { - Ip result = Ip::resolve(host, service, AF_INET, type); - - return Ipv4(result.address(), result.length()); - } -}; - -/** - * \brief Ipv4 only address. - * \ingroup net-module-addresses - */ -class Ipv6 { -private: - sockaddr_in6 m_sin6; - -public: - /** - * Create an Ipv6 address. - */ - inline Ipv6() noexcept - { - std::memset(&m_sin6, 0, sizeof (sockaddr_in6)); - } - - /** - * Create an Ipv6 address on the specific ip address. - * - * \param ip the address or "*" for any - * \param port the port - * \warning If NET_HAVE_INET_PTON is undefined, host can not be other than "*" - * \throw net::Error on failures or if inet_pton is unavailable - */ - inline Ipv6(const std::string &ip, std::uint16_t port) - : Ipv6() - { - Ip::make(ip, port, m_sin6); - } - - /** - * Create the IP address from the storage. - * - * \pre the storage domain must be AF_INET6 - * \param ss the the storage - * \param length the storage length - */ - inline Ipv6(const sockaddr *ss, socklen_t length) noexcept - { - assert(ss->sa_family == AF_INET6); - - std::memcpy(&m_sin6, ss, length); - } - - /** - * Get the domain. - * - * \return AF_INET6 - */ - inline int domain() const noexcept - { - return AF_INET6; - } - - /** - * Get the underlying address. - * - * \return the address - */ - inline const sockaddr *address() const noexcept - { - return reinterpret_cast<const sockaddr *>(&m_sin6); - } - - /** - * Get the address length. - * - * \return the size of sockaddr_in - */ - inline socklen_t length() const noexcept - { - return sizeof (sockaddr_in6); - } - - /** - * Get the port. - * - * \return the port - */ - inline std::uint16_t port() const noexcept - { - return ntohs(m_sin6.sin6_port); - } - - /** - * Get the ip address. - * - * \return the ip address - * \throw net::Error on errors or if inet_ntop is unavailable - */ - inline std::string ip() const - { - return Ip::ip(m_sin6); - } - - /** - * Same as Ip::resolve with AF_INET6 as domain. - * - * \param host the hostname - * \param service the service name (port or name) - * \param type the socket type (e.g. SOCK_STREAM) - * \return the resolved address - * \throw net::Error on failures - */ - static Ipv6 resolve(const std::string &host, const std::string &service, int type = SOCK_STREAM) - { - Ip result = Ip::resolve(host, service, AF_INET6, type); - - return Ipv6(result.address(), result.length()); - } -}; - -#if !defined(_WIN32) - -/** - * \brief unix family sockets - * \ingroup net-module-addresses - * - * Create an address to a specific path. Only available on Unix. - */ -class Local { -private: - sockaddr_un m_sun; - std::string m_path; - -public: - /** - * Get the domain AF_LOCAL. - * - * \return AF_LOCAL - */ - inline int domain() const noexcept - { - return AF_LOCAL; - } - - /** - * Default constructor. - */ - inline Local() noexcept - { - std::memset(&m_sun, 0, sizeof (sockaddr_un)); - } - - /** - * Construct an address to a path. - * - * \param path the path - * \param rm remove the file before (default: false) - */ - Local(std::string path, bool rm = false) noexcept - : m_path(std::move(path)) - { - // Silently remove the file even if it fails. - if (rm) - ::remove(m_path.c_str()); - - // Copy the path. - std::memset(m_sun.sun_path, 0, sizeof (m_sun.sun_path)); - std::strncpy(m_sun.sun_path, m_path.c_str(), sizeof (m_sun.sun_path) - 1); - - // Set the parameters. - m_sun.sun_family = AF_LOCAL; - } - - /** - * Construct an unix address from a storage address. - * - * \pre storage's domain must be AF_LOCAL - * \param ss the storage - * \param length the length - */ - Local(const sockaddr *ss, socklen_t length) noexcept - { - assert(ss->sa_family == AF_LOCAL); - - std::memcpy(&m_sun, ss, length); - m_path = reinterpret_cast<const sockaddr_un &>(m_sun).sun_path; - } - - /** - * Get the sockaddr_un. - * - * \return the address - */ - inline const sockaddr *address() const noexcept - { - return reinterpret_cast<const sockaddr *>(&m_sun); - } - - /** - * Get the address length. - * - * \return the length - */ - inline socklen_t length() const noexcept - { -#if defined(NET_HAVE_SUN_LEN) - return SUN_LEN(&m_sun); -#else - return sizeof (m_sun); -#endif - } -}; - -#endif // !_WIN32 - -/** * \brief Address iterator. * \ingroup net-module-addresses * \see resolve * * This iterator can be used to try to connect to an host. * - * When you use net::resolve with unspecified domain or socket type, the function may retrieve several different addresses that you can - * iterate over to try to connect to. + * 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 - * net::SocketTcpIp sc; - * net::AddressIterator end, it = net::resolve("hostname.test", "80"); + * 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. + * When an iterator equals to a default constructed iterator, it is considered + * not dereferenceable. */ -class AddressIterator : public std::iterator<std::forward_iterator_tag, GenericAddress> { +class AddressIterator : public std::iterator<std::forward_iterator_tag, Address> { private: - std::vector<GenericAddress> m_addresses; + std::vector<Address> m_addresses; std::size_t m_index{0}; public: @@ -3507,7 +1201,7 @@ * \param addresses the addresses * \param index the first index */ - inline AddressIterator(std::vector<GenericAddress> addresses, std::size_t index = 0) noexcept + inline AddressIterator(std::vector<Address> addresses, std::size_t index = 0) noexcept : m_addresses(std::move(addresses)) , m_index(index) { @@ -3520,7 +1214,7 @@ * \pre this is dereferenceable * \return the generic address */ - inline const GenericAddress &operator*() const noexcept + inline const Address &operator*() const noexcept { assert(m_index <= m_addresses.size()); @@ -3533,7 +1227,7 @@ * \pre this is dereferenceable * \return the generic address */ - inline GenericAddress &operator*() noexcept + inline Address &operator*() noexcept { assert(m_index <= m_addresses.size()); @@ -3546,7 +1240,7 @@ * \pre this is dereferenceable * \return the generic address */ - inline const GenericAddress *operator->() const noexcept + inline const Address *operator->() const noexcept { assert(m_index <= m_addresses.size()); @@ -3559,7 +1253,7 @@ * \pre this is dereferenceable * \return the generic address */ - inline GenericAddress *operator->() noexcept + inline Address *operator->() noexcept { assert(m_index <= m_addresses.size()); @@ -3628,7 +1322,1201 @@ return !(i1 == i2); } -} // !address +/** + * Compare two generic addresses. + * + * \param a1 the first address + * \param a2 the second address + * \return true if they equal + */ +inline bool operator==(const Address &a1, const Address &a2) noexcept +{ + return a1.length() == a2.length() && std::memcmp(a1.get(), a2.get(), a1.length()) == 0; +} + +/** + * Compare two generic addresses. + * + * \param a1 the first address + * \param a2 the second address + * \return false if they equal + */ +inline bool operator!=(const Address &a1, const Address &a2) noexcept +{ + return !(a1 == a2); +} + +/** + * \brief Base socket class. + */ +class Socket { +protected: + /** + * The native handle. + */ + Handle m_handle{Invalid}; + +public: + /** + * Create a socket handle. + * + * This is the primary function and the only one that creates the socket + * handle, all other constructors are just overloaded functions. + * + * \param domain the domain AF_* + * \param type the type SOCK_* + * \param protocol the protocol + * \throw Error on errors + */ + Socket(int domain, int type, int protocol) + { +#if !defined(NET_NO_AUTO_INIT) + init(); +#endif + m_handle = ::socket(domain, type, protocol); + + if (m_handle == Invalid) + throw Error(); + } + + /** + * Create the socket with an already defined handle and its protocol. + * + * \param handle the handle + */ + explicit inline Socket(Handle handle) noexcept + : m_handle(handle) + { + } + + /** + * Create an invalid socket. Can be used when you cannot instanciate the + * socket immediately. + */ + explicit inline Socket(std::nullptr_t) noexcept + : m_handle(Invalid) + { + } + + /** + * Copy constructor deleted. + */ + Socket(const Socket &) = delete; + + /** + * Transfer ownership from other to this. + * + * \param other the other socket + */ + inline Socket(Socket &&other) noexcept + : m_handle(other.m_handle) + { + other.m_handle = Invalid; + } + + /** + * Default destructor. + */ + virtual ~Socket() + { + close(); + } + + /** + * Tells if the socket is not invalid. + * + * \return true if not invalid + */ + inline bool isOpen() const noexcept + { + return m_handle != Invalid; + } + + /** + * Set an option for the socket. Wrapper of setsockopt(2). + * + * \pre isOpen() + * \param level the setting level + * \param name the name + * \param arg the value + * \throw Error on errors + */ + template <typename Argument> + inline void set(int level, int name, const Argument &arg) + { + assert(m_handle != Invalid); + + if (::setsockopt(m_handle, level, name, (ConstArg)&arg, sizeof (arg)) == Failure) + throw Error(); + } + + /** + * Object-oriented option setter. + * + * The object must have `set(Socket &) const`. + * + * \pre isOpen() + * \param option the option + * \throw Error on errors + */ + template <typename Option> + inline void set(const Option &option) + { + assert(m_handle != Invalid); + + option.set(*this); + } + + /** + * Get an option for the socket. Wrapper of getsockopt(2). + * + * \pre isOpen() + * \param level the setting level + * \param name the name + * \return the value + * \throw Error on errors + */ + template <typename Argument> + Argument get(int level, int name) + { + assert(m_handle != Invalid); + + Argument desired, result{}; + socklen_t size = sizeof (result); + + if (::getsockopt(m_handle, level, name, (Arg)&desired, &size) == Failure) + throw Error(); + + std::memcpy(&result, &desired, size); + + return result; + } + + /** + * Object-oriented option getter. + * + * The object must have `T get(Socket &) const`, T can be any type and it is + * the value returned from this function. + * + * \pre isOpen() + * \return the same value as get() in the option + * \throw Error on errors + */ + template <typename Option> + inline auto get() -> decltype(std::declval<Option>().get(*this)) + { + assert(m_handle != Invalid); + + return Option().get(*this); + } + + /** + * Get the native handle. + * + * \return the handle + * \warning Not portable + */ + inline Handle handle() const noexcept + { + return m_handle; + } + + /** + * Bind using a native address. + * + * \pre isOpen() + * \param address the address + * \param length the size + * \throw Error on errors + */ + inline void bind(const sockaddr *address, socklen_t length) + { + assert(m_handle != Invalid); + + if (::bind(m_handle, address, length) == Failure) + throw Error(); + } + + /** + * Overload that takes an address. + * + * \pre isOpen() + * \param address the address + * \throw Error on errors + */ + inline void bind(const Address &address) + { + assert(m_handle != Invalid); + + bind(address.get(), address.length()); + } + + /** + * Listen for pending connection. + * + * \pre isOpen() + * \param max the maximum number + * \throw Error on errors + */ + inline void listen(int max = 128) + { + assert(m_handle != Invalid); + + if (::listen(m_handle, max) == Failure) + throw Error(); + } + + /** + * Get the local name. This is a wrapper of getsockname(). + * + * \pre isOpen() + * \return the address + * \throw Error on failures + */ + Address getsockname() const + { + assert(m_handle != Invalid); + + sockaddr_storage ss; + socklen_t length = sizeof (sockaddr_storage); + + if (::getsockname(m_handle, reinterpret_cast<sockaddr *>(&ss), &length) == Failure) + throw Error(); + + return Address(reinterpret_cast<sockaddr *>(&ss), length); + } + + /** + * Get connected address. This is a wrapper for getpeername(). + * + * \pre isOpen() + * \return the address + * \throw Error on failures + */ + Address getpeername() const + { + assert(m_handle != Invalid); + + sockaddr_storage ss; + socklen_t length = sizeof (sockaddr_storage); + + if (::getpeername(m_handle, reinterpret_cast<sockaddr *>(&ss), &length) == Failure) + throw Error(); + + return Address(reinterpret_cast<sockaddr *>(&ss), length); + } + + /** + * Close the socket. + * + * Automatically called from the destructor. + */ + void close() + { + if (m_handle != Invalid) { +#if defined(_WIN32) + ::closesocket(m_handle); +#else + ::close(m_handle); +#endif + m_handle = Invalid; + } + } + + /** + * Assignment operator forbidden. + * + * \return *this + */ + Socket &operator=(const Socket &) = delete; + + /** + * Transfer ownership from other to this. The other socket is left + * invalid and will not be closed. + * + * \param other the other socket + * \return this + */ + Socket &operator=(Socket &&other) noexcept + { + m_handle = other.m_handle; + other.m_handle = Invalid; + + return *this; + } +}; + +/** + * Compare two sockets. + * + * \param s1 the first socket + * \param s2 the second socket + * \return true if they equals + */ +inline bool operator==(const Socket &s1, const Socket &s2) +{ + return s1.handle() == s2.handle(); +} + +/** + * Compare two sockets. + * + * \param s1 the first socket + * \param s2 the second socket + * \return true if they are different + */ +inline bool operator!=(const Socket &s1, const Socket &s2) +{ + return s1.handle() != s2.handle(); +} + +/** + * Compare two sockets. + * + * \param s1 the first socket + * \param s2 the second socket + * \return true if s1 < s2 + */ +inline bool operator<(const Socket &s1, const Socket &s2) +{ + return s1.handle() < s2.handle(); +} + +/** + * Compare two sockets. + * + * \param s1 the first socket + * \param s2 the second socket + * \return true if s1 > s2 + */ +inline bool operator>(const Socket &s1, const Socket &s2) +{ + return s1.handle() > s2.handle(); +} + +/** + * Compare two sockets. + * + * \param s1 the first socket + * \param s2 the second socket + * \return true if s1 <= s2 + */ +inline bool operator<=(const Socket &s1, const Socket &s2) +{ + return s1.handle() <= s2.handle(); +} + +/** + * Compare two sockets. + * + * \param s1 the first socket + * \param s2 the second socket + * \return true if s1 >= s2 + */ +inline bool operator>=(const Socket &s1, const Socket &s2) +{ + return s1.handle() >= s2.handle(); +} + +/** + * \brief Clear TCP implementation. + * \ingroup net-module-tcp + * + * This is the basic TCP protocol that implements recv, send, connect and accept + * as wrappers of the usual C functions. + */ +class TcpSocket : public Socket { +public: + /** + * Inherited constructors. + */ + using Socket::Socket; + + /** + * Construct a TCP socket. + * + * \param domain the domain + * \param protocol the protocol + * \throw Error on errors + */ + inline TcpSocket(int domain, int protocol) + : Socket(domain, SOCK_STREAM, protocol) + { + } + + /** + * Get the type of the socket. + * + * \return the type + */ + inline int type() const noexcept + { + return SOCK_STREAM; + } + + /** + * Initiate connection. + * + * \param address the address + * \param length the address length + * \throw WouldBlockError if the socket is marked non-blocking and + * connection cannot be established immediately + * \throw Error on other errors + */ + void connect(const sockaddr *address, socklen_t length) + { + if (::connect(this->m_handle, address, length) == Failure) { +#if defined(_WIN32) + int error = WSAGetLastError(); + + if (error == WSAEWOULDBLOCK) + throw WouldBlockError(); + else + throw Error(error); +#else + if (errno == EINPROGRESS) + throw WouldBlockError(); + else + throw Error(); +#endif + } + } + + /** + * Overloaded function. + * + * \param address the address + * \throw WouldBlockError if the socket is marked non-blocking and + * connection cannot be established immediately + * \throw Error on other errors + */ + void connect(const Address &address) + { + connect(address.get(), address.length()); + } + + /** + * Accept a new client. + * + * If there are no pending connection, an invalid socket is returned. + * + * \return the new socket + * \throw WouldBlockError if the socket is marked non-blocking and no + * connection are available + * \throw Error on other errors + */ + TcpSocket accept() + { + Handle handle = ::accept(this->m_handle, nullptr, 0); + + if (handle == Invalid) { +#if defined(_WIN32) + int error = WSAGetLastError(); + + if (error == WSAEWOULDBLOCK) + throw WouldBlockError(); + else + throw Error(error); +#else + if (errno == EWOULDBLOCK || errno == EAGAIN) + throw WouldBlockError(); + else + throw Error(); +#endif + } + + return TcpSocket(handle); + } + + /** + * Receive some data. + * + * \param data the destination buffer + * \param length the buffer length + * \return the number of bytes received + */ + unsigned recv(void *data, unsigned length) + { + int max = length > INT_MAX ? INT_MAX : static_cast<int>(length); + int nbread = ::recv(this->m_handle, (Arg)data, max, 0); + + if (nbread == Failure) { +#if defined(_WIN32) + int error = WSAGetLastError(); + + if (error == WSAEWOULDBLOCK) + throw WouldBlockError(); + else + throw Error(error); +#else + if (errno == EAGAIN || errno == EWOULDBLOCK) + throw WouldBlockError(); + else + throw Error(); +#endif + } + + return static_cast<unsigned>(nbread); + } + + /** + * Send some data. + * + * \param data the data to send + * \param length the length + * \return the number of bytes sent + */ + unsigned send(const void *data, unsigned length) + { + int max = length > INT_MAX ? INT_MAX : static_cast<int>(length); + int nbsent = ::send(this->m_handle, (ConstArg)data, max, 0); + + if (nbsent == Failure) { +#if defined(_WIN32) + int error = WSAGetLastError(); + + if (error == WSAEWOULDBLOCK) + throw WouldBlockError(); + else + throw Error(); +#else + if (errno == EAGAIN || errno == EWOULDBLOCK) + throw WouldBlockError(); + else + throw Error(); +#endif + } + + return static_cast<unsigned>(nbsent); + } +}; + +/** + * \brief Clear UDP type. + * + * This class is the basic implementation of UDP sockets. + */ +class UdpSocket : public Socket { +public: + /** + * Inherited constructors. + */ + using Socket::Socket; + + /** + * Construct a TCP socket. + * + * \param domain the domain + * \param protocol the protocol + * \throw Error on errors + */ + inline UdpSocket(int domain, int protocol) + : Socket(domain, SOCK_DGRAM, protocol) + { + } + + /** + * Get the type of the socket. + * + * \return the type + */ + inline int type() const noexcept + { + return SOCK_DGRAM; + } + + /** + * Receive some data. + * + * \param data the data + * \param length the length + * \param address the source address + * \param addrlen the source address in/out length + * \return the number of bytes received + * \throw WouldBlockError if the socket is marked non-blocking and the + * operation would block + * \throw Error on other errors + */ + unsigned recvfrom(void *data, unsigned length, sockaddr *address, socklen_t *addrlen) + { + int max = length > INT_MAX ? INT_MAX : static_cast<int>(length); + int nbread = ::recvfrom(this->m_handle, (Arg)data, max, 0, address, addrlen); + + if (nbread == Failure) { +#if defined(_WIN32) + int error = WSAGetLastError(); + + if (error == EWOULDBLOCK) + throw WouldBlockError(); + else + throw Error(error); +#else + if (errno == EAGAIN || errno == EWOULDBLOCK) + throw WouldBlockError(); + else + throw Error(); +#endif + } + + return static_cast<unsigned>(nbread); + } + + /** + * Overloaded function. + * + * \param data the data + * \param length the length + * \param source the source information (optional) + * \throw WouldBlockError if the socket is marked non-blocking and the + * operation would block + * \throw Error on other errors + */ + inline unsigned recvfrom(void *data, unsigned length, Address *source = nullptr) + { + sockaddr_storage st; + socklen_t socklen = sizeof (sockaddr_storage); + + auto nr = recvfrom(data, length, reinterpret_cast<sockaddr *>(&st), &socklen); + + if (source) + *source = Address(reinterpret_cast<const sockaddr *>(&st), socklen); + + return nr; + } + + /** + * Send some data. + * + * \param data the data to send + * \param length the data length + * \param address the destination address + * \param addrlen the destination address length + * \return the number of bytes sent + * \throw WouldBlockError if the socket is marked non-blocking and the + * operation would block + * \throw Error on other errors + */ + unsigned sendto(const void *data, unsigned length, const sockaddr *address, socklen_t addrlen) + { + int max = length > INT_MAX ? INT_MAX : static_cast<int>(length); + int nbsent = ::sendto(this->m_handle, (ConstArg)data, max, 0, address, addrlen); + + if (nbsent == Failure) { +#if defined(_WIN32) + int error = WSAGetLastError(); + + if (error == EWOULDBLOCK) + throw WouldBlockError(); + else + throw Error(error); +#else + if (errno == EAGAIN || errno == EWOULDBLOCK) + throw WouldBlockError(); + else + throw Error(); +#endif + } + + return static_cast<unsigned>(nbsent); + } + + /** + * Overloaded function + * + * \param data the data to send + * \param length the data length + * \param address the destination address + * \return the number of bytes sent + * \throw WouldBlockError if the socket is marked non-blocking and the + * operation would block + * \throw Error on other errors + */ + inline unsigned sendto(const void *data, unsigned length, const Address &address) + { + return sendto(data, length, address.get(), address.length()); + } +}; + +#if !defined(NET_NO_SSL) + +/** + * \brief Experimental TLS support. + * \ingroup net-module-tls + * \warning This class is highly experimental. + */ +class TlsSocket : public Socket { +public: + /** + * \brief SSL connection mode. + */ + enum Mode { + Server, //!< Use Server when you accept a socket server side, + Client //!< Use Client when you connect to a server. + }; + +private: + using Context = std::shared_ptr<SSL_CTX>; + using Ssl = std::unique_ptr<SSL, void (*)(SSL *)>; + + // Determine if we created a TlsSocket from a temporary or a lvalue. + bool m_mustclose{false}; + + Context m_context; + Ssl m_ssl{nullptr, nullptr}; + + inline std::string error() + { + BIO *bio = BIO_new(BIO_s_mem()); + char *buf = nullptr; + + ERR_print_errors(bio); + + std::size_t length = BIO_get_mem_data (bio, &buf); + std::string result(buf, length); + + BIO_free(bio); + + return result; + } + + template <typename Function> + void wrap(Function &&function) + { + auto ret = function(); + + if (ret <= 0) { + int no = SSL_get_error(m_ssl.get(), ret); + + switch (no) { + case SSL_ERROR_WANT_READ: + throw WantReadError(); + case SSL_ERROR_WANT_WRITE: + throw WantWriteError(); + default: + throw Error(error()); + } + } + } + + void create(Mode mode, const SSL_METHOD *method) + { +#if !defined(NET_NO_SSL_AUTO_INIT) + ssl::init(); +#endif + m_context = Context(SSL_CTX_new(method), SSL_CTX_free); + m_ssl = Ssl(SSL_new(m_context.get()), SSL_free); + + SSL_set_fd(m_ssl.get(), this->m_handle); + + if (mode == Server) + SSL_set_accept_state(m_ssl.get()); + else + SSL_set_connect_state(m_ssl.get()); + } + +public: + /** + * Create a socket around an existing one. + * + * The original socket is moved to this instance and must not be used + * anymore. + * + * \param sock the TCP socket + * \param mode the mode + * \param method the method + */ + TlsSocket(TcpSocket &&sock, Mode mode = Server, const SSL_METHOD *method = TLSv1_method()) + : Socket(std::move(sock)) + , m_mustclose(true) + { + create(mode, method); + } + + /** + * Wrap a socket around an existing one without taking ownership. + * + * The original socket must still exist until this TlsSocket is closed. + * + * \param sock the TCP socket + * \param mode the mode + * \param method the method + */ + TlsSocket(TcpSocket &sock, Mode mode = Server, const SSL_METHOD *method = TLSv1_method()) + : Socket(sock.handle()) + { + create(mode, method); + } + + /** + * Destroy the socket if owned. + */ + ~TlsSocket() + { + /** + * If the socket has been created from a rvalue this class owns the + * socket and will close it in the parent destructor. + * + * Otherwise, when created from a lvalue, mark this socket as invalid + * to avoid double close'ing it as two sockets points to the same + * descriptor. + */ + if (!m_mustclose) + m_handle = Invalid; + } + + /** + * Get the type of socket. + * + * \return the type + */ + inline int type() const noexcept + { + return SOCK_STREAM; + } + + /** + * Use the specified private key file. + * + * \param file the path to the private key + * \param type the type of file + */ + inline void setPrivateKey(std::string file, int type = SSL_FILETYPE_PEM) + { + if (SSL_use_PrivateKey_file(m_ssl.get(), file.c_str(), type) != 1) + throw Error(error()); + } + + /** + * Use the specified certificate file. + * + * \param file the path to the file + * \param type the type of file + */ + inline void setCertificate(std::string file, int type = SSL_FILETYPE_PEM) + { + if (SSL_use_certificate_file(m_ssl.get(), file.c_str(), type) != 1) + throw Error(error()); + } + + /** + * Do handshake, needed in some case when you have non blocking sockets. + */ + void handshake() + { + wrap([&] () -> int { + return SSL_do_handshake(m_ssl.get()); + }); + } + + /** + * Receive some data. + * + * \param data the destination buffer + * \param length the buffer length + * \return the number of bytes received + */ + unsigned recv(void *data, unsigned length) + { + int max = length > INT_MAX ? INT_MAX : static_cast<int>(length); + int nbread = 0; + + wrap([&] () -> int { + return (nbread = SSL_read(m_ssl.get(), data, max)); + }); + + return static_cast<unsigned>(nbread < 0 ? 0 : nbread); + } + + /** + * Send some data. + * + * \param data the data to send + * \param length the length + * \return the number of bytes sent + */ + unsigned send(const void *data, unsigned length) + { + int max = length > INT_MAX ? INT_MAX : static_cast<int>(length); + int nbsent = 0; + + wrap([&] () -> int { + return (nbsent = SSL_write(m_ssl.get(), data, max)); + }); + + return static_cast<unsigned>(nbsent < 0 ? 0 : nbsent); + } +}; + +#endif // !NET_NO_SSL + +/** + * \brief IPv4 functions. + */ +namespace ipv4 { + +/** + * Create an address to bind on any. + * + * \param port the port + * \return the address + */ +inline Address any(std::uint16_t port) +{ + sockaddr_in sin; + socklen_t length = sizeof (sockaddr_in); + + std::memset(&sin, 0, sizeof (sockaddr_in)); + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = INADDR_ANY; + sin.sin_port = htons(port); + + return Address(reinterpret_cast<const sockaddr *>(&sin), length); +} + +/** + * Create an address from a IPv4 string. + * + * \param ip the ip address + * \param port the port + * \return the address + * \throw Error if inet_pton is unavailable + */ +inline Address pton(const std::string &ip, std::uint16_t port) +{ +#if defined(NET_HAVE_INET_PTON) +#if !defined(NET_NO_AUTO_INIT) + init(); +#endif + + sockaddr_in sin; + socklen_t length = sizeof (sockaddr_in); + + std::memset(&sin, 0, sizeof (sockaddr_in)); + sin.sin_family = AF_INET; + sin.sin_port = htons(port); + + if (inet_pton(AF_INET, ip.c_str(), &sin.sin_addr) <= 0) + throw Error(); + + return Address(reinterpret_cast<const sockaddr *>(&sin), length); +#else + (void)ip; + (void)port; + + throw Error(std::strerror(ENOSYS)); +#endif +} + +/** + * Get the underlying ip from the given address. + * + * \pre address.domain() == AF_INET + * \param address the IPv6 address + * \return the ip address + * \throw Error if inet_ntop is unavailable + */ +inline std::string ntop(const Address &address) +{ + assert(address.domain() == AF_INET); + +#if !defined(NET_NO_AUTO_INIT) + init(); +#endif + +#if defined(NET_HAVE_INET_NTOP) + char result[INET_ADDRSTRLEN + 1]; + + std::memset(result, 0, sizeof (result)); + + if (!inet_ntop(AF_INET, const_cast<in_addr *>(&address.as<sockaddr_in>().sin_addr), result, sizeof (result))) + throw Error(); + + return result; +#else + (void)address; + + throw Error(std::strerror(ENOSYS)); +#endif +} + +/** + * Get the port from the IPv4 address. + * + * \pre address.domain() == AF_INET4 + * \param address the address + * \return the port + */ +inline std::uint16_t port(const Address &address) noexcept +{ + assert(address.domain() == AF_INET); + + return ntohs(address.as<sockaddr_in>().sin_port); +} + +} // !ipv4 + +/** + * \brief IPv6 functions. + */ +namespace ipv6 { + +/** + * Create an address to bind on any. + * + * \param port the port + * \return the address + */ +inline Address any(std::uint16_t port) +{ + sockaddr_in6 sin6; + socklen_t length = sizeof (sockaddr_in6); + + std::memset(&sin6, 0, sizeof (sockaddr_in6)); + sin6.sin6_family = AF_INET6; + sin6.sin6_addr = in6addr_any; + sin6.sin6_port = htons(port); + + return Address(reinterpret_cast<const sockaddr *>(&sin6), length); +} + +/** + * Create an address from a IPv4 string. + * + * \param ip the ip address + * \param port the port + * \return the address + * \throw Error if inet_pton is unavailable + */ +inline Address pton(const std::string &ip, std::uint16_t port) +{ +#if defined(NET_HAVE_INET_PTON) +#if !defined(NET_NO_AUTO_INIT) + init(); +#endif + + sockaddr_in6 sin6; + socklen_t length = sizeof (sockaddr_in6); + + std::memset(&sin6, 0, sizeof (sockaddr_in6)); + sin6.sin6_family = AF_INET6; + sin6.sin6_port = htons(port); + + if (inet_pton(AF_INET6, ip.c_str(), &sin6.sin6_addr) <= 0) + throw Error(); + + return Address(reinterpret_cast<const sockaddr *>(&sin6), length); +#else + (void)ip; + (void)port; + + throw Error(std::strerror(ENOSYS)); +#endif +} + +/** + * Get the underlying ip from the given address. + * + * \pre address.domain() == AF_INET6 + * \param address the IPv6 address + * \return the ip address + * \throw Error if inet_ntop is unavailable + */ +inline std::string ntop(const Address &address) +{ + assert(address.domain() == AF_INET6); + +#if defined(NET_HAVE_INET_NTOP) +#if !defined(NET_NO_AUTO_INIT) + init(); +#endif + + char ret[INET6_ADDRSTRLEN]; + + std::memset(ret, 0, sizeof (ret)); + + if (!inet_ntop(AF_INET6, const_cast<in6_addr *>(&address.as<sockaddr_in6>().sin6_addr), ret, sizeof (ret))) + throw Error(); + + return ret; +#else + (void)address; + + throw Error(std::strerror(ENOSYS)); +#endif +} + +/** + * Get the port from the IPv6 address. + * + * \pre address.domain() == AF_INET6 + * \param address the address + * \return the port + */ +inline std::uint16_t port(const Address &address) noexcept +{ + assert(address.domain() == AF_INET6); + + return ntohs(address.as<sockaddr_in6>().sin6_port); +} + +} // !ipv6 + +#if !defined(_WIN32) + +/** + * \brief Unix domain functions. + */ +namespace local { + +/** + * Construct an address to a path. + * + * \pre !path.empty() + * \param path the path + * \param rm remove the file before (default: false) + */ +inline Address create(const std::string &path, bool rm = false) noexcept +{ + assert(!path.empty()); + + // Silently remove the file even if it fails. + if (rm) + remove(path.c_str()); + + sockaddr_un sun; + socklen_t length; + + std::memset(sun.sun_path, 0, sizeof (sun.sun_path)); + std::strncpy(sun.sun_path, path.c_str(), sizeof (sun.sun_path) - 1); + + sun.sun_family = AF_LOCAL; + +#if defined(NET_HAVE_SUN_LEN) + length = SUN_LEN(&sun); +#else + length = sizeof (sun); +#endif + + return Address(reinterpret_cast<const sockaddr *>(&sun), length); +} + +/** + * Get the path from the address. + * + * \pre address.domain() == AF_LOCAL + * \param address the local address + * \return the path to the socket file + */ +inline std::string path(const Address &address) +{ + assert(address.domain() == AF_LOCAL); + + return reinterpret_cast<const sockaddr_un *>(address.get())->sun_path; +} + +} // !local + +#endif // !_WIN32 /** * \brief Predefined options. @@ -3638,7 +2526,8 @@ /** * \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. + * \warning On Windows, it's not possible to check if the socket is blocking or + * not. */ class SockBlockMode { private: @@ -3663,8 +2552,7 @@ * \param sc the socket * \throw Error on errors */ - template <typename Address, typename Protocol> - void set(Socket<Address, Protocol> &sc) const + void set(Socket &sc) const { #if defined(O_NONBLOCK) && !defined(_WIN32) int flags; @@ -3678,12 +2566,12 @@ flags |= O_NONBLOCK; if (fcntl(sc.handle(), F_SETFL, flags) < 0) - throw Error(Error::System, "fcntl"); + throw Error(); #else unsigned long flags = (m_value) ? 0 : 1; if (ioctlsocket(sc.handle(), FIONBIO, &flags) == Failure) - throw Error(Error::System, "fcntl"); + throw Error(); #endif } @@ -3694,20 +2582,19 @@ * \return the value * \throw Error on errors */ - template <typename Address, typename Protocol> - bool get(Socket<Address, Protocol> &sc) const + bool get(Socket &sc) const { #if defined(O_NONBLOCK) && !defined(_WIN32) int flags = fcntl(sc.handle(), F_GETFL, 0); if (flags < 0) - throw Error(Error::System, "fcntl"); + throw Error(); return !(flags & O_NONBLOCK); #else (void)sc; - throw Error(Error::Other, "get", std::strerror(ENOSYS)); + throw Error(std::strerror(ENOSYS)); #endif } }; @@ -3737,8 +2624,7 @@ * \param sc the socket * \throw Error on errors */ - template <typename Address, typename Protocol> - inline void set(Socket<Address, Protocol> &sc) const + inline void set(Socket &sc) const { sc.set(SOL_SOCKET, SO_RCVBUF, m_value); } @@ -3750,10 +2636,9 @@ * \return the value * \throw Error on errors */ - template <typename Address, typename Protocol> - inline int get(Socket<Address, Protocol> &sc) const + inline int get(Socket &sc) const { - return sc.template get<int>(SOL_SOCKET, SO_RCVBUF); + return sc.get<int>(SOL_SOCKET, SO_RCVBUF); } }; @@ -3784,8 +2669,7 @@ * \param sc the socket * \throw Error on errors */ - template <typename Address, typename Protocol> - inline void set(Socket<Address, Protocol> &sc) const + inline void set(Socket &sc) const { sc.set(SOL_SOCKET, SO_REUSEADDR, m_value ? 1 : 0); } @@ -3797,10 +2681,9 @@ * \return the value * \throw Error on errors */ - template <typename Address, typename Protocol> - inline bool get(Socket<Address, Protocol> &sc) const + inline bool get(Socket &sc) const { - return sc.template get<int>(SOL_SOCKET, SO_REUSEADDR) != 0; + return sc.get<int>(SOL_SOCKET, SO_REUSEADDR) != 0; } }; @@ -3829,8 +2712,7 @@ * \param sc the socket * \throw Error on errors */ - template <typename Address, typename Protocol> - inline void set(Socket<Address, Protocol> &sc) const + inline void set(Socket &sc) const { sc.set(SOL_SOCKET, SO_SNDBUF, m_value); } @@ -3842,10 +2724,9 @@ * \return the value * \throw Error on errors */ - template <typename Address, typename Protocol> - inline int get(Socket<Address, Protocol> &sc) const + inline int get(Socket &sc) const { - return sc.template get<int>(SOL_SOCKET, SO_SNDBUF); + return sc.get<int>(SOL_SOCKET, SO_SNDBUF); } }; @@ -3876,8 +2757,7 @@ * \param sc the socket * \throw Error on errors */ - template <typename Address, typename Protocol> - inline void set(Socket<Address, Protocol> &sc) const + inline void set(Socket &sc) const { sc.set(IPPROTO_TCP, TCP_NODELAY, m_value ? 1 : 0); } @@ -3889,10 +2769,9 @@ * \return the value * \throw Error on errors */ - template <typename Address, typename Protocol> - inline bool get(Socket<Address, Protocol> &sc) const + inline bool get(Socket &sc) const { - return sc.template get<int>(IPPROTO_TCP, TCP_NODELAY) != 0; + return sc.get<int>(IPPROTO_TCP, TCP_NODELAY) != 0; } }; @@ -3900,7 +2779,8 @@ * \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 + * 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 { @@ -3926,8 +2806,7 @@ * \param sc the socket * \throw Error on errors */ - template <typename Address, typename Protocol> - inline void set(Socket<Address, Protocol> &sc) const + inline void set(Socket &sc) const { sc.set(IPPROTO_IPV6, IPV6_V6ONLY, m_value ? 1 : 0); } @@ -3939,10 +2818,9 @@ * \return the value * \throw Error on errors */ - template <typename Address, typename Protocol> - inline bool get(Socket<Address, Protocol> &sc) const + inline bool get(Socket &sc) const { - return sc.template get<int>(IPPROTO_IPV6, IPV6_V6ONLY) != 0; + return sc.get<int>(IPPROTO_IPV6, IPV6_V6ONLY) != 0; } }; @@ -3956,8 +2834,8 @@ */ class ListenerStatus { public: - Handle socket; //!< which socket is ready - Condition flags; //!< the flags + Handle socket; //!< which socket is ready + Condition flags; //!< the flags }; /** @@ -4019,20 +2897,20 @@ ev.data.fd = h; if (epoll_ctl(m_handle, op, h, &ev) < 0) - throw Error(Error::System, "epoll_ctl"); + throw Error(); } public: /** * Create epoll. * - * \throw net::Error on failures + * \throw Error on failures */ inline Epoll() : m_handle(epoll_create1(0)) { if (m_handle < 0) - throw Error(Error::System, "epoll_create"); + throw Error(); } /** @@ -4066,15 +2944,15 @@ } /** - * For set and unset, we need to apply the whole flags required, so if the socket - * was set to Connection::Readable and user *8adds** Connection::Writable, we must - * place both. + * 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 net::Error on failures + * \throw Error on failures */ void set(const ListenerTable &table, Handle h, Condition condition, bool add) { @@ -4097,7 +2975,7 @@ * \param h the handle * \param condition the condition * \param add set to true if the socket is new to the backend - * \throw net::Error on failures + * \throw Error on failures */ void unset(const ListenerTable &table, Handle h, Condition condition, bool remove) { @@ -4113,7 +2991,7 @@ * * \param ms the milliseconds timeout * \return the sockets ready - * \throw net::Error on failures + * \throw Error on failures */ std::vector<ListenerStatus> wait(const ListenerTable &, int ms) { @@ -4121,9 +2999,9 @@ std::vector<ListenerStatus> result; if (ret == 0) - throw Error(Error::Timeout, "epoll_wait", std::strerror(ETIMEDOUT)); + throw TimeoutError(); if (ret < 0) - throw Error(Error::System, "epoll_wait"); + throw Error(); for (int i = 0; i < ret; ++i) result.push_back(ListenerStatus{m_events[i].data.fd, toCondition(m_events[i].events)}); @@ -4172,20 +3050,20 @@ EV_SET(&ev, h, filter, kflags, 0, 0, nullptr); if (kevent(m_handle, &ev, 1, nullptr, 0, nullptr) < 0) - throw Error(Error::System, "kevent"); + throw Error(); } public: /** * Create kqueue. * - * \throw net::Error on failures + * \throw Error on failures */ inline Kqueue() : m_handle(kqueue()) { if (m_handle < 0) - throw Error(Error::System, "kqueue"); + throw Error(); } /** @@ -4224,7 +3102,7 @@ * \param h the handle * \param condition the condition * \param add set to true if the socket is new to the backend - * \throw net::Error on failures + * \throw Error on failures */ void set(const ListenerTable &, Handle h, Condition condition, bool add) { @@ -4242,7 +3120,7 @@ * \param h the handle * \param condition the condition * \param remove set to true if the socket is completely removed - * \throw net::Error on failures + * \throw Error on failures */ void unset(const ListenerTable &, Handle h, Condition condition, bool remove) { @@ -4259,7 +3137,7 @@ * * \param ms the milliseconds timeout * \return the sockets ready - * \throw net::Error on failures + * \throw Error on failures */ std::vector<ListenerStatus> wait(const ListenerTable &, int ms) { @@ -4273,9 +3151,9 @@ int nevents = kevent(m_handle, nullptr, 0, &m_result[0], m_result.capacity(), pts); if (nevents == 0) - throw Error(Error::Timeout, "kevent", std::strerror(ETIMEDOUT)); + throw TimeoutError(); if (nevents < 0) - throw Error(Error::System, "kevent"); + throw Error(); for (int i = 0; i < nevents; ++i) { sockets.push_back(ListenerStatus{ @@ -4334,9 +3212,11 @@ Condition condition = Condition::None; /* - * Poll implementations mark the socket differently regarding the disconnection of a socket. + * 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. + * 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; @@ -4366,7 +3246,7 @@ * \param h the handle * \param condition the condition * \param add set to true if the socket is new to the backend - * \throw net::Error on failures + * \throw Error on failures */ void set(const ListenerTable &, Handle h, Condition condition, bool add) { @@ -4387,7 +3267,7 @@ * \param h the handle * \param condition the condition * \param remove set to true if the socket is completely removed - * \throw net::Error on failures + * \throw Error on failures */ void unset(const ListenerTable &, Handle h, Condition condition, bool remove) { @@ -4406,7 +3286,7 @@ * * \param ms the milliseconds timeout * \return the sockets ready - * \throw net::Error on failures + * \throw Error on failures */ std::vector<ListenerStatus> wait(const ListenerTable &, int ms) { @@ -4417,9 +3297,9 @@ #endif if (result == 0) - throw Error(Error::Timeout, "select", std::strerror(ETIMEDOUT)); + throw TimeoutError(); if (result < 0) - throw Error(Error::System, "poll"); + throw Error(); std::vector<ListenerStatus> sockets; @@ -4437,7 +3317,8 @@ * \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. + * This class is the fallback of any other method, it is not preferred at all + * for many reasons. */ class Select { public: @@ -4471,7 +3352,7 @@ * \param table the listener table * \param ms the milliseconds timeout * \return the sockets ready - * \throw net::Error on failures + * \throw Error on failures */ std::vector<ListenerStatus> wait(const ListenerTable &table, int ms) { @@ -4502,9 +3383,9 @@ auto error = ::select(static_cast<int>(max + 1), &readset, &writeset, nullptr, towait); if (error == Failure) - throw Error(Error::System, "select"); + throw Error(); if (error == 0) - throw Error(Error::Timeout, "select", std::strerror(ETIMEDOUT)); + throw TimeoutError(); std::vector<ListenerStatus> sockets; @@ -4691,7 +3572,8 @@ /** * Remove completely the socket from the listener. * - * It is a shorthand for unset(sc, Condition::Readable | Condition::Writable); + * It is a shorthand for unset(sc, Condition::Readable | + * Condition::Writable); * * \param sc the socket */ @@ -4720,7 +3602,8 @@ } /** - * Select a socket. Waits for a specific amount of time specified as the duration. + * Select a socket. Waits for a specific amount of time specified as the + * duration. * * \param duration the duration * \return the socket ready @@ -4772,101 +3655,6 @@ }; /** - * \ingroup net-module-tcp - * \brief Helper to create TCP sockets. - */ -template <typename Address> -using SocketTcp = Socket<Address, protocol::Tcp>; - -/** - * \ingroup net-module-tcp - * \brief Helper to create TCP/Ipv4 or TCP/Ipv6 sockets. - */ -using SocketTcpIp = Socket<address::Ip, protocol::Tcp>; - -/** - * \ingroup net-module-tcp - * \brief Helper to create TCP/Ipv4 sockets. - */ -using SocketTcpIpv4 = Socket<address::Ipv4, protocol::Tcp>; - -/** - * \ingroup net-module-tcp - * \brief Helper to create TCP/Ipv6 sockets. - */ -using SocketTcpIpv6 = Socket<address::Ipv6, protocol::Tcp>; - -/** - * \ingroup net-module-udp - * \brief Helper to create UDP sockets. - */ -template <typename Address> -using SocketUdp = Socket<Address, protocol::Udp>; - -/** - * \ingroup net-module-udp - * \brief Helper to create UDP/Ipv4 or UDP/Ipv6 sockets. - */ -using SocketUdpIp = Socket<address::Ip, protocol::Udp>; - -/** - * \ingroup net-module-udp - * \brief Helper to create UDP/Ipv4 sockets. - */ -using SocketUdpIpv4 = Socket<address::Ipv4, protocol::Udp>; - -/** - * \ingroup net-module-udp - * \brief Helper to create UDP/Ipv6 sockets. - */ -using SocketUdpIpv6 = Socket<address::Ipv6, protocol::Udp>; - -#if !defined(_WIN32) - -/** - * \ingroup net-module-tcp - * \brief Helper to create TCP/Local sockets. - */ -using SocketTcpLocal = Socket<address::Local, protocol::Tcp>; - -/** - * \ingroup net-module-udp - * \brief Helper to create UDP/Local sockets. - */ -using SocketUdpLocal = Socket<address::Local, protocol::Udp>; - -#endif - -#if !defined(NET_NO_SSL) - -/** - * \ingroup net-module-tls - * \brief Helper to create TLS sockets. - */ -template <typename Address> -using SocketTls = Socket<Address, protocol::Tls>; - -/** - * \ingroup net-module-tls - * \brief Helper to create TLS/Ipv4 or TLS/Ipv6 sockets. - */ -using SocketTlsIp = Socket<address::Ip, protocol::Tls>; - -/** - * \ingroup net-module-tls - * \brief Helper to create TLS/Ipv4 sockets. - */ -using SocketTlsIpv4 = Socket<address::Ip, protocol::Tls>; - -/** - * \ingroup net-module-tls - * \brief Helper to create TLS/Ipv6 sockets. - */ -using SocketTlsIpv6 = Socket<address::Ip, protocol::Tls>; - -#endif - -/** * \ingroup net-module-resolv * * Resolve an hostname immediately. @@ -4876,12 +3664,15 @@ * \param domain the domain (e.g. AF_INET) * \param type the type (e.g. SOCK_STREAM) * \return the address iterator - * \throw net::Error on failures + * \throw Error on failures */ -inline address::AddressIterator resolve(const std::string &host, const std::string &service, int domain = AF_UNSPEC, int type = 0) +inline AddressIterator resolve(const std::string &host, + const std::string &service, + int domain = AF_UNSPEC, + int type = 0) { #if !defined(NET_NO_AUTO_INIT) - net::init(); + init(); #endif struct addrinfo hints, *res, *p; @@ -4893,32 +3684,19 @@ int e = getaddrinfo(host.c_str(), service.c_str(), &hints, &res); if (e != 0) - throw Error(Error::System, "getaddrinfo", gai_strerror(e)); - - std::vector<address::GenericAddress> addresses; + throw Error(gai_strerror(e)); + + std::vector<Address> addresses; for (p = res; p != nullptr; p = p->ai_next) - addresses.push_back(address::GenericAddress(p->ai_addr, p->ai_addrlen)); - - return address::AddressIterator(addresses, 0); + addresses.push_back(Address(p->ai_addr, p->ai_addrlen)); + + return AddressIterator(addresses, 0); } /** - * Overloaded function. + * \ingroup net-module-resolv * - * \param sc the parent socket - * \param host the hostname - * \param service the service name - * \return the address iterator - * \throw net::Error on failures - */ -template <typename Address, typename Protocol> -address::AddressIterator resolve(const Socket<Address, Protocol> &sc, const std::string &host, const std::string &service) -{ - return resolve(host, service, Address().domain(), sc.protocol().type()); -} - -/** * Resolve the first address. * * \param host the hostname @@ -4926,37 +3704,22 @@ * \param domain the domain (e.g. AF_INET) * \param type the type (e.g. SOCK_STREAM) * \return the first generic address available - * \throw net::Error on failures + * \throw Error on failures * \note do not use AF_UNSPEC and 0 as type for this function */ -inline address::GenericAddress resolveOne(const std::string &host, const std::string &service, int domain, int type) +inline Address resolveOne(const std::string &host, const std::string &service, int domain, int type) { - address::AddressIterator end; - address::AddressIterator it = resolve(host, service, domain, type); + AddressIterator it = resolve(host, service, domain, type); + AddressIterator end; if (it == end) - throw Error(Error::Other, "resolveOne", "no address available"); + throw Error("no address available"); return *it; } -/** - * Overloaded function - * - * \param sc the parent socket - * \param host the hostname - * \param service the service name - * \return the first generic address available - * \throw net::Error on failures - */ -template <typename Address, typename Protocol> -address::GenericAddress resolveOne(const Socket<Address, Protocol> &sc, const std::string &host, const std::string &service) -{ - return resolveOne(host, service, Address().domain(), sc.protocol().type()); -} +} // !net } // !irccd -} // !net - #endif // !IRCCD_NET_HPP
--- a/lib/irccd/plugin-js.hpp Mon Jul 18 22:39:14 2016 +0200 +++ b/lib/irccd/plugin-js.hpp Wed Aug 10 10:33:21 2016 +0200 @@ -108,7 +108,7 @@ } /** - * \copydoc Plugin::setConfig + * \copydoc Plugin::setFormats */ void setFormats(PluginFormats formats) override {
--- a/lib/irccd/server-state-connected.hpp Mon Jul 18 22:39:14 2016 +0200 +++ b/lib/irccd/server-state-connected.hpp Wed Aug 10 10:33:21 2016 +0200 @@ -35,12 +35,12 @@ class ConnectedState : public State { public: /** - * \copydoc ServerState::prepare + * \copydoc State::prepare */ IRCCD_EXPORT void prepare(Server &server, fd_set &setinput, fd_set &setoutput, net::Handle &maxfd) override; /** - * \copydoc ServerState::ident + * \copydoc State::ident */ IRCCD_EXPORT std::string ident() const override; };
--- a/lib/irccd/server-state-connecting.hpp Mon Jul 18 22:39:14 2016 +0200 +++ b/lib/irccd/server-state-connecting.hpp Wed Aug 10 10:33:21 2016 +0200 @@ -40,12 +40,12 @@ public: /** - * \copydoc ServerState::prepare + * \copydoc State::prepare */ IRCCD_EXPORT void prepare(Server &server, fd_set &setinput, fd_set &setoutput, net::Handle &maxfd) override; /** - * \copydoc ServerState::ident + * \copydoc State::ident */ IRCCD_EXPORT std::string ident() const override; };
--- a/lib/irccd/server-state-disconnected.hpp Mon Jul 18 22:39:14 2016 +0200 +++ b/lib/irccd/server-state-disconnected.hpp Wed Aug 10 10:33:21 2016 +0200 @@ -39,12 +39,12 @@ public: /** - * \copydoc ServerState::prepare + * \copydoc State::prepare */ IRCCD_EXPORT void prepare(Server &server, fd_set &setinput, fd_set &setoutput, net::Handle &maxfd) override; /** - * \copydoc ServerState::ident + * \copydoc State::ident */ IRCCD_EXPORT std::string ident() const override; };
--- a/lib/irccd/server-state.hpp Mon Jul 18 22:39:14 2016 +0200 +++ b/lib/irccd/server-state.hpp Wed Aug 10 10:33:21 2016 +0200 @@ -38,7 +38,6 @@ class Server; /** - * \class ServerState * \brief Server current state. */ class State {
--- a/lib/irccd/server.hpp Mon Jul 18 22:39:14 2016 +0200 +++ b/lib/irccd/server.hpp Wed Aug 10 10:33:21 2016 +0200 @@ -676,7 +676,7 @@ /** * Set the realname. * - * \param name the username + * \param realname the username * \note the username will be changed on the next connection */ inline void setRealname(std::string realname) noexcept
--- a/lib/irccd/service-command.hpp Mon Jul 18 22:39:14 2016 +0200 +++ b/lib/irccd/service-command.hpp Wed Aug 10 10:33:21 2016 +0200 @@ -49,6 +49,11 @@ */ IRCCD_EXPORT CommandService(); + inline const std::vector<std::shared_ptr<Command>> &commands() const noexcept + { + return m_commands; + } + /** * Tells if a command exists. *
--- a/lib/irccd/service-interrupt.cpp Mon Jul 18 22:39:14 2016 +0200 +++ b/lib/irccd/service-interrupt.cpp Wed Aug 10 10:33:21 2016 +0200 @@ -16,20 +16,24 @@ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ +#include <array> + #include "logger.hpp" #include "service-interrupt.hpp" namespace irccd { InterruptService::InterruptService() + : m_in(AF_INET, 0) + , m_out(AF_INET, 0) { // Bind a socket to any port. m_in.set(net::option::SockReuseAddress(true)); - m_in.bind(net::address::Ipv4("*", 0)); + m_in.bind(net::ipv4::any(0)); m_in.listen(1); // Do the socket pair. - m_out.connect(net::address::Ipv4("127.0.0.1", m_in.getsockname().port())); + m_out.connect(net::ipv4::pton("127.0.0.1", net::ipv4::port(m_in.getsockname()))); m_in = m_in.accept(); m_out.set(net::option::SockBlockMode(false)); } @@ -45,9 +49,11 @@ void InterruptService::sync(fd_set &in, fd_set &) { if (FD_ISSET(m_in.handle(), &in)) { + static std::array<char, 32> tmp; + try { log::debug("irccd: interrupt service recv"); - m_in.recv(32); + m_in.recv(tmp.data(), 32); } catch (const std::exception &ex) { log::warning() << "irccd: interrupt service error: " << ex.what() << std::endl; } @@ -57,8 +63,10 @@ void InterruptService::interrupt() noexcept { try { + static char byte; + log::debug("irccd: interrupt service send"); - m_out.send(" "); + m_out.send(&byte, 1); } catch (const std::exception &ex) { log::warning() << "irccd: interrupt service error: " << ex.what() << std::endl; }
--- a/lib/irccd/service-interrupt.hpp Mon Jul 18 22:39:14 2016 +0200 +++ b/lib/irccd/service-interrupt.hpp Wed Aug 10 10:33:21 2016 +0200 @@ -34,8 +34,8 @@ */ class InterruptService : public Service { private: - net::SocketTcpIpv4 m_in; - net::SocketTcpIpv4 m_out; + net::TcpSocket m_in; + net::TcpSocket m_out; public: /**
--- a/lib/irccd/service-server.cpp Mon Jul 18 22:39:14 2016 +0200 +++ b/lib/irccd/service-server.cpp Wed Aug 10 10:33:21 2016 +0200 @@ -89,7 +89,7 @@ { "channel", ev.channel }, { "mode", ev.mode }, { "argument", ev.argument } - }).dump()); + })); m_irccd.post(EventHandler{ev.server->name(), ev.origin, ev.channel, [=] (Plugin &) -> std::string { @@ -114,7 +114,7 @@ { "origin", ev.origin }, { "channel", ev.channel }, { "message", ev.message } - }).dump(0)); + })); m_irccd.post(EventHandler{ev.server->name(), ev.origin, ev.channel, [=] (Plugin &) -> std::string { @@ -133,7 +133,7 @@ m_irccd.transportService().broadcast(nlohmann::json::object({ { "event", "onConnect" }, { "server", ev.server->name() } - }).dump(0)); + })); m_irccd.post(EventHandler{ev.server->name(), /* origin */ "", /* channel */ "", [=] (Plugin &) -> std::string { @@ -157,7 +157,7 @@ { "server", ev.server->name() }, { "origin", ev.origin }, { "channel", ev.channel } - }).dump(0)); + })); m_irccd.post(EventHandler{ev.server->name(), ev.origin, ev.channel, [=] (Plugin &) -> std::string { @@ -180,7 +180,7 @@ { "server", ev.server->name() }, { "origin", ev.origin }, { "channel", ev.channel } - }).dump(0)); + })); m_irccd.post(EventHandler{ev.server->name(), ev.origin, ev.channel, [=] (Plugin &) -> std::string { @@ -207,7 +207,7 @@ { "channel", ev.channel }, { "target", ev.target }, { "reason", ev.reason } - }).dump(0)); + })); m_irccd.post(EventHandler{ev.server->name(), ev.origin, ev.channel, [=] (Plugin &) -> std::string { @@ -232,7 +232,7 @@ { "origin", ev.origin }, { "channel", ev.channel }, { "message", ev.message } - }).dump(0)); + })); m_irccd.post(EventHandler{ev.server->name(), ev.origin, ev.channel, [=] (Plugin &plugin) -> std::string { @@ -265,7 +265,7 @@ { "origin", ev.origin }, { "target", ev.channel }, { "message", ev.message } - }).dump(0)); + })); m_irccd.post(EventHandler{ev.server->name(), ev.origin, ev.channel, [=] (Plugin &) -> std::string { @@ -288,7 +288,7 @@ { "server", ev.server->name() }, { "origin", ev.origin }, { "mode", ev.mode } - }).dump()); + })); m_irccd.post(EventHandler{ev.server->name(), ev.origin, /* channel */ "", [=] (Plugin &) -> std::string { @@ -316,7 +316,7 @@ { "server", ev.server->name() }, { "channel", ev.channel }, { "names", std::move(names) } - }).dump()); + })); m_irccd.post(EventHandler{ev.server->name(), /* origin */ "", ev.channel, [=] (Plugin &) -> std::string { @@ -339,7 +339,7 @@ { "server", ev.server->name() }, { "origin", ev.origin }, { "nickname", ev.nickname } - }).dump()); + })); m_irccd.post(EventHandler{ev.server->name(), ev.origin, /* channel */ "", [=] (Plugin &) -> std::string { @@ -362,7 +362,7 @@ { "server", ev.server->name() }, { "origin", ev.origin }, { "message", ev.message } - }).dump()); + })); m_irccd.post(EventHandler{ev.server->name(), ev.origin, /* channel */ "", [=] (Plugin &) -> std::string { @@ -387,7 +387,7 @@ { "origin", ev.origin }, { "channel", ev.channel }, { "reason", ev.reason } - }).dump()); + })); m_irccd.post(EventHandler{ev.server->name(), ev.origin, ev.channel, [=] (Plugin &) -> std::string { @@ -410,7 +410,7 @@ { "server", ev.server->name() }, { "origin", ev.origin }, { "message", ev.message } - }).dump()); + })); m_irccd.post(EventHandler{ev.server->name(), ev.origin, /* channel */ "", [=] (Plugin &plugin) -> std::string { @@ -443,7 +443,7 @@ { "origin", ev.origin }, { "channel", ev.channel }, { "topic", ev.topic } - }).dump()); + })); m_irccd.post(EventHandler{ev.server->name(), ev.origin, ev.channel, [=] (Plugin &) -> std::string { @@ -471,7 +471,7 @@ { "username", ev.whois.user }, { "host", ev.whois.host }, { "realname", ev.whois.realname } - }).dump()); + })); m_irccd.post(EventHandler{ev.server->name(), /* origin */ "", /* channel */ "", [=] (Plugin &) -> std::string {
--- a/lib/irccd/service-transport.cpp Mon Jul 18 22:39:14 2016 +0200 +++ b/lib/irccd/service-transport.cpp Wed Aug 10 10:33:21 2016 +0200 @@ -75,7 +75,7 @@ response.push_back({"response", *name}); // 5. Send the result. - tc->send(response.dump()); + tc->send(response); }); } @@ -107,14 +107,8 @@ } // Transport clients. - for (const auto &client : m_clients) { - FD_SET(client->handle(), &in); - - if (client->hasOutput()) - FD_SET(client->handle(), &out); - if (client->handle() > max) - max = client->handle(); - } + for (const auto &client : m_clients) + client->prepare(in, out, max); } void TransportService::sync(fd_set &in, fd_set &out) @@ -132,7 +126,7 @@ std::weak_ptr<TransportClient> ptr(client); // Send some information. - nlohmann::json object = nlohmann::json::object({ + auto object = nlohmann::json::object({ { "program", "irccd" }, { "major", IRCCD_VERSION_MAJOR }, { "minor", IRCCD_VERSION_MINOR }, @@ -146,19 +140,29 @@ object.push_back({"ssl", true}); #endif - client->send(object.dump()); + try { + client->send(object); - // Connect signals. - client->onCommand.connect(std::bind(&TransportService::handleCommand, this, ptr, _1)); - client->onDie.connect(std::bind(&TransportService::handleDie, this, ptr)); + // Connect signals. + client->onCommand.connect(std::bind(&TransportService::handleCommand, this, ptr, _1)); + client->onDie.connect(std::bind(&TransportService::handleDie, this, ptr)); - // Register it. - m_clients.push_back(std::move(client)); + // Register it. + m_clients.push_back(std::move(client)); + } catch (const std::exception &ex) { + log::info() << "transport: client disconnected: " << ex.what() << std::endl; + } } // Transport clients. - for (const auto &client : m_clients) - client->sync(in, out); + for (const auto &client : m_clients) { + try { + client->sync(in, out); + } catch (const std::exception &ex) { + log::info() << "transport: client disconnected: " << ex.what() << std::endl; + handleDie(client); + } + } } void TransportService::add(std::shared_ptr<TransportServer> ts) @@ -166,11 +170,13 @@ m_servers.push_back(std::move(ts)); } -void TransportService::broadcast(std::string data) +void TransportService::broadcast(const nlohmann::json &json) { + assert(json.is_object()); + // Asynchronous send. for (const auto &client : m_clients) - client->send(data); + client->send(json); } } // !irccd
--- a/lib/irccd/service-transport.hpp Mon Jul 18 22:39:14 2016 +0200 +++ b/lib/irccd/service-transport.hpp Wed Aug 10 10:33:21 2016 +0200 @@ -75,9 +75,10 @@ /** * Send data to all clients. * - * \param data the data + * \pre object.is_object() + * \param object the json object */ - IRCCD_EXPORT void broadcast(std::string data); + IRCCD_EXPORT void broadcast(const nlohmann::json &object); }; } // !irccd
--- a/lib/irccd/service.hpp Mon Jul 18 22:39:14 2016 +0200 +++ b/lib/irccd/service.hpp Wed Aug 10 10:33:21 2016 +0200 @@ -78,6 +78,26 @@ { util::unused(in, out); } + + /** + * Convenient function for polling events with a timeout. + * + * \param timeout the timeout in milliseconds + */ + virtual void poll(int timeout = -1) + { + fd_set in, out; + timeval tv = {0, timeout * 1000}; + + FD_ZERO(&in); + FD_ZERO(&out); + + net::Handle max = 0; + + prepare(in, out, max); + select(max + 1, &in, &out, nullptr, timeout < 0 ? nullptr : &tv); + sync(in, out); + } }; } // !irccd
--- a/lib/irccd/transport-client.cpp Mon Jul 18 22:39:14 2016 +0200 +++ b/lib/irccd/transport-client.cpp Wed Aug 10 10:33:21 2016 +0200 @@ -20,35 +20,85 @@ #include "logger.hpp" #include "transport-client.hpp" -using json = nlohmann::json; - namespace irccd { void TransportClient::parse(const std::string &message) { - auto document = json::parse(message); + auto document = nlohmann::json::parse(message); + + if (document.is_object()) + onCommand(document); +} + +void TransportClient::receive() +{ + try { + std::string buffer; - if (!document.is_object()) - throw std::invalid_argument("the message is not a valid JSON object"); + buffer.resize(512); + buffer.resize(m_socket.recv(&buffer[0], buffer.size())); - onCommand(document); + if (buffer.empty()) + onDie(); + + m_input += std::move(buffer); + } catch (const std::exception &) { + onDie(); + } } -void TransportClient::sync(fd_set &setinput, fd_set &setoutput) +void TransportClient::send() { - if (FD_ISSET(handle(), &setinput)) { + try { + auto ns = m_socket.send(&m_output[0], m_output.size()); + + if (ns == 0) + onDie(); + + m_output.erase(0, ns); + } catch (const std::exception &ex) { + onDie(); + } +} + +void TransportClient::prepare(fd_set &in, fd_set &out, net::Handle &max) +{ + if (m_socket.handle() > max) + max = m_socket.handle(); + + FD_SET(m_socket.handle(), &in); + + if (!m_output.empty()) + FD_SET(m_socket.handle(), &out); +} + +void TransportClient::sync(fd_set &in, fd_set &out) +{ + // Do some I/O. + if (FD_ISSET(m_socket.handle(), &in)) { log::debug() << "transport: receiving to input buffer" << std::endl; receive(); } - if (FD_ISSET(handle(), &setoutput)) { + if (FD_ISSET(m_socket.handle(), &out)) { log::debug() << "transport: sending outgoing buffer" << std::endl; send(); } + + // Flush the queue. + for (std::size_t pos; (pos = m_input.find("\r\n\r\n")) != std::string::npos; ) { + auto message = m_input.substr(0, pos); + + m_input.erase(m_input.begin(), m_input.begin() + pos + 4); + + parse(message); + } } -void TransportClient::send(std::string message) +void TransportClient::send(const nlohmann::json &json) { - m_output += message; + assert(json.is_object()); + + m_output += json.dump(); m_output += "\r\n\r\n"; }
--- a/lib/irccd/transport-client.hpp Mon Jul 18 22:39:14 2016 +0200 +++ b/lib/irccd/transport-client.hpp Wed Aug 10 10:33:21 2016 +0200 @@ -64,8 +64,9 @@ Signal<> onDie; protected: - std::string m_input; //!< input buffer - std::string m_output; //!< output buffer + net::TcpSocket m_socket; //!< socket + std::string m_input; //!< input buffer + std::string m_output; //!< output buffer /** * Parse input buffer. @@ -74,123 +75,27 @@ */ void parse(const std::string &buffer); - /** - * Start receiving data. - */ - virtual void receive() = 0; - - /** - * Start sending data. - */ - virtual void send() = 0; +private: + void receive(); + void send(); public: + inline TransportClient(net::TcpSocket socket) + : m_socket(std::move(socket)) + { + } + /** * Virtual destructor defaulted. */ virtual ~TransportClient() = default; - /** - * Send or receive data, called after a select. - * - * \param setinput the input fd_set - * \param setoutput the output fd_set - */ - IRCCD_EXPORT void sync(fd_set &setinput, fd_set &setoutput); - - /** - * Send some data, it will be pushed to the outgoing buffer. - * - * This function appends "\r\n\r\n" after the message so you don't have - * to do it manually. - * - * \param message the message - */ - IRCCD_EXPORT void send(std::string message); - - /** - * Tell if the client has data pending for output. - * - * \return true if has pending data to write - */ - inline bool hasOutput() const noexcept - { - return !m_output.empty(); - } - - /** - * Get the underlying socket handle. - * - * \return the socket - */ - virtual net::Handle handle() noexcept = 0; -}; - -/** - * \brief Template class for Tcp and Ssl sockets - */ -template <typename Address> -class TransportClientBase : public TransportClient { -private: - net::SocketTcp<Address> m_socket; - -protected: - void send() override; - void receive() override; + IRCCD_EXPORT void send(const nlohmann::json &json); -public: - /** - * Create a client. - * - * \param socket the socket - */ - inline TransportClientBase(net::SocketTcp<Address> socket) - : m_socket(std::move(socket)) - { - } - - /** - * \copydoc TransportClient::handle - */ - net::Handle handle() noexcept override - { - return m_socket.handle(); - } -}; - -template <typename Address> -void TransportClientBase<Address>::receive() -{ - try { - auto message = m_socket.recv(512); + IRCCD_EXPORT virtual void prepare(fd_set &in, fd_set &out, net::Handle &max); - if (message.empty()) - onDie(); - - m_input += message; - } catch (const std::exception &) { - onDie(); - } - - std::string::size_type pos; - while ((pos = m_input.find("\r\n\r\n")) != std::string::npos) { - /* - * Make a copy and erase it in case that onComplete function - * throws. - */ - auto message = m_input.substr(0, pos); - - m_input.erase(m_input.begin(), m_input.begin() + pos + 4); - - parse(message); - } -} - -template <typename Address> -void TransportClientBase<Address>::send() -{ - m_output.erase(0, m_socket.send(m_output)); -} + IRCCD_EXPORT virtual void sync(fd_set &in, fd_set &out); +}; } // !irccd
--- a/lib/irccd/transport-server.cpp Mon Jul 18 22:39:14 2016 +0200 +++ b/lib/irccd/transport-server.cpp Wed Aug 10 10:33:21 2016 +0200 @@ -30,33 +30,47 @@ namespace irccd { /* - * TransportServerIp + * TransportServerIpv6 * ------------------------------------------------------------------ */ -TransportServerIp::TransportServerIp(int domain, const std::string &address, int port, bool ipv6only) - : m_socket(domain, SOCK_STREAM, 0) +TransportServerIpv6::TransportServerIpv6(const std::string &address, std::uint16_t port, bool ipv6only) + : TransportServer(net::TcpSocket(AF_INET6, 0)) { m_socket.set(net::option::SockReuseAddress(true)); // Disable or enable IPv4 when using IPv6. - if (domain == AF_INET6) - m_socket.set(net::option::Ipv6Only(ipv6only)); + if (ipv6only) + m_socket.set(net::option::Ipv6Only(true)); - m_socket.bind(net::address::Ip(address, port, domain)); + if (address == "*") + m_socket.bind(net::ipv6::any(port)); + else + m_socket.bind(net::ipv6::pton(address, port)); + m_socket.listen(); log::info() << "transport: listening on " << address << ", port " << port << std::endl; } -net::Handle TransportServerIp::handle() noexcept +/* + * TransportServerIp + * ------------------------------------------------------------------ + */ + +TransportServerIp::TransportServerIp(const std::string &address, std::uint16_t port) + : TransportServer(net::TcpSocket(AF_INET, 0)) { - return m_socket.handle(); -} + m_socket.set(net::option::SockReuseAddress(true)); -std::shared_ptr<TransportClient> TransportServerIp::accept() -{ - return std::make_shared<TransportClientBase<net::address::Ip>>(m_socket.accept()); + if (address == "*") + m_socket.bind(net::ipv4::any(port)); + else + m_socket.bind(net::ipv4::pton(address, port)); + + m_socket.listen(); + + log::info() << "transport: listening on " << address << ", port " << port << std::endl; } /* @@ -66,30 +80,21 @@ #if !defined(IRCCD_SYSTEM_WINDOWS) -TransportServerUnix::TransportServerUnix(std::string path) - : m_path(std::move(path)) +TransportServerLocal::TransportServerLocal(std::string path) + : TransportServer(net::TcpSocket(AF_LOCAL, 0)) + , m_path(std::move(path)) { - m_socket.bind(net::address::Local{m_path, true}); + m_socket.bind(net::local::create(m_path, true)); m_socket.listen(); log::info() << "transport: listening on " << m_path << std::endl; } -TransportServerUnix::~TransportServerUnix() +TransportServerLocal::~TransportServerLocal() { ::remove(m_path.c_str()); } -net::Handle TransportServerUnix::handle() noexcept -{ - return m_socket.handle(); -} - -std::shared_ptr<TransportClient> TransportServerUnix::accept() -{ - return std::make_shared<TransportClientBase<net::address::Local>>(m_socket.accept()); -} - #endif } // !irccd
--- a/lib/irccd/transport-server.hpp Mon Jul 18 22:39:14 2016 +0200 +++ b/lib/irccd/transport-server.hpp Wed Aug 10 10:33:21 2016 +0200 @@ -34,8 +34,7 @@ namespace irccd { /** - * \class TransportServer - * \brief Bring networking between irccd and irccdctl + * \brief Bring networking between irccd and irccdctl. * * This class contains a master sockets for listening to TCP connections, it is then processed by irccd. * @@ -56,11 +55,30 @@ TransportServer &operator=(const TransportServer &) = delete; TransportServer &operator=(TransportServer &&) = delete; +protected: + /** + * The socket handle. + */ + net::TcpSocket m_socket; + public: /** * Default constructor. */ - TransportServer() = default; + inline TransportServer(net::TcpSocket socket) + : m_socket(std::move(socket)) + { + } + + /** + * Get the socket handle for this transport. + * + * \return the handle + */ + inline net::Handle handle() const noexcept + { + return m_socket.handle(); + } /** * Destructor defaulted. @@ -68,63 +86,52 @@ virtual ~TransportServer() = default; /** - * Retrieve the underlying socket handle. - * - * \return the socket - */ - virtual net::Handle handle() noexcept = 0; - - /** * Accept a new client depending on the domain. * * \return the new client */ - virtual std::shared_ptr<TransportClient> accept() = 0; + virtual std::unique_ptr<TransportClient> accept() + { + return std::make_unique<TransportClient>(m_socket.accept()); + } }; /** - * \class TransportServerIp - * \brief Base class for both IPv4 and IPv6 servers. + * \brief Create IP transport. */ class TransportServerIp : public TransportServer { -protected: - /** - * The TCP/IP socket. - */ - net::SocketTcp<net::address::Ip> m_socket; - public: /** - * Create a IP transport, use IPv6 or IPv4 address. + * Constructor. * - * \param domain AF_INET or AF_INET6 - * \param address the address or "*" for any + * \param address the address (* for any) * \param port the port number - * \param ipv6only set to true to disable IPv4 - * \throw net::Error on failures */ - IRCCD_EXPORT TransportServerIp(int domain, const std::string &address, int port, bool ipv6only = true); + IRCCD_EXPORT TransportServerIp(const std::string &address, std::uint16_t port); +}; +/** + * \brief Create IPv6 transport. + */ +class TransportServerIpv6 : public TransportServer { +public: /** - * \copydoc TransportServer::handle + * Constructor. + * + * \param address the address (* for any) + * \param port the port number + * \param ipv6only set to true to disable ipv4 completely */ - IRCCD_EXPORT net::Handle handle() noexcept override; - - /** - * \copydoc TransportServer::accept - */ - IRCCD_EXPORT std::shared_ptr<TransportClient> accept() override; + IRCCD_EXPORT TransportServerIpv6(const std::string &address, std::uint16_t port, bool ipv6only = true); }; #if !defined(IRCCD_SYSTEM_WINDOWS) /** - * \class TransportServerUnix * \brief Implementation of transports for Unix sockets. */ -class TransportServerUnix : public TransportServer { +class TransportServerLocal : public TransportServer { private: - net::SocketTcp<net::address::Local> m_socket; std::string m_path; public: @@ -133,22 +140,12 @@ * * \param path the path */ - IRCCD_EXPORT TransportServerUnix(std::string path); + IRCCD_EXPORT TransportServerLocal(std::string path); /** * Destroy the transport and remove the file. */ - IRCCD_EXPORT ~TransportServerUnix(); - - /** - * \copydoc TransportServer::handle - */ - IRCCD_EXPORT net::Handle handle() noexcept override; - - /** - * \copydoc TransportServer::accept - */ - IRCCD_EXPORT std::shared_ptr<TransportClient> accept() override; + IRCCD_EXPORT ~TransportServerLocal(); }; #endif // !_WIN32
--- a/lib/irccd/util.hpp Mon Jul 18 22:39:14 2016 +0200 +++ b/lib/irccd/util.hpp Wed Aug 10 10:33:21 2016 +0200 @@ -543,8 +543,9 @@ * * \param json the json value * \param key the property key - * \param def the default value - * \return the boolean + * \param min the minimum value + * \param max the maximum value + * \return the value */ template <typename T> inline T getIntRange(const nlohmann::json &json, @@ -560,8 +561,9 @@ * * \param json the json value * \param key the property key - * \param def the default value - * \return the boolean + * \param min the minimum value + * \param max the maximum value + * \return value */ template <typename T> inline T getUintRange(const nlohmann::json &json,