diff --git a/.clang_format b/.clang_format new file mode 100644 index 00000000..83fba920 --- /dev/null +++ b/.clang_format @@ -0,0 +1,55 @@ +--- +# BasedOnStyle: Mozilla +Language: Cpp +AccessModifierOffset: -2 +ConstructorInitializerIndentWidth: 4 +AlignEscapedNewlinesLeft: false +AlignTrailingComments: true +AllowAllParametersOfDeclarationOnNextLine: false +AllowShortBlocksOnASingleLine: false +AllowShortIfStatementsOnASingleLine: false +AllowShortLoopsOnASingleLine: false +AllowShortFunctionsOnASingleLine: All +AlwaysBreakTemplateDeclarations: false +AlwaysBreakBeforeMultilineStrings: false +BreakBeforeBinaryOperators: false +BreakBeforeTernaryOperators: true +BreakConstructorInitializersBeforeComma: false +BinPackParameters: true +ColumnLimit: 80 +ConstructorInitializerAllOnOneLineOrOnePerLine: true +DerivePointerAlignment: true +ExperimentalAutoDetectBinPacking: false +IndentCaseLabels: true +IndentWrappedFunctionNames: false +IndentFunctionDeclarationAfterType: false +MaxEmptyLinesToKeep: 1 +KeepEmptyLinesAtTheStartOfBlocks: true +NamespaceIndentation: None +PenaltyBreakBeforeFirstCallParameter: 19 +PenaltyBreakComment: 300 +PenaltyBreakString: 1000 +PenaltyBreakFirstLessLess: 120 +PenaltyExcessCharacter: 1000000 +PenaltyReturnTypeOnItsOwnLine: 200 +PointerAlignment: Left +SpacesBeforeTrailingComments: 1 +Cpp11BracedListStyle: false +Standard: Cpp14 +IndentWidth: 2 +TabWidth: 4 +UseTab: Never +BreakBeforeBraces: Attach +SpacesInParentheses: false +SpacesInAngles: false +SpaceInEmptyParentheses: false +SpacesInCStyleCastParentheses: false +SpacesInContainerLiterals: true +SpaceBeforeAssignmentOperators: true +ContinuationIndentWidth: 4 +CommentPragmas: '^ IWYU pragma:' +ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH, for_each ] +SpaceBeforeParens: ControlStatements +DisableFormat: false +... + diff --git a/.gitignore b/.gitignore index c35d1084..11f27ca7 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ build/* CTestConfig.cmake *.orig CTestConfig.cmake -src/**/globals.hpp -test/**/test_suite.hpp +**/**/globals.hpp +**/**/test_suite.hpp .vagrant/* +**/wintermute.log diff --git a/CMakeLists.txt b/CMakeLists.txt index 7ecdcc65..4f9392ce 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -21,15 +21,12 @@ CMAKE_MINIMUM_REQUIRED(VERSION 2.8.12) PROJECT(Wintermute) # Add our CMake files into the mix. -SET(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") +SET(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake) INCLUDE(defaults) # Enable testing. ENABLE_TESTING() INCLUDE(CTest) -# Include the source code for the project. ADD_SUBDIRECTORY(src) - -# Include the test suite. ADD_SUBDIRECTORY(test) diff --git a/LICENSE.txt b/LICENSE.txt index d9ff4531..6cff1b9d 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,4 +1,4 @@ -Copyright (c) 2014 Jacky Alciné +Copyright (c) 2010, 2011, 2012, 2013, 2014 Jacky Alciné Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.markdown b/README.markdown index df456478..028410d1 100644 --- a/README.markdown +++ b/README.markdown @@ -20,6 +20,9 @@ self-driven hyper-connectivity of hardware and software. The whole purpose of this particular tree is to build a core library, a driver binary (more on that) and a few plugins to get the show started. Check out [this weblog post][post] to really understand the mission behind Wintermute. +_Yes, this is yet another Internet of Things project._ But this isn't meant to +be "web scale" or all of that. It's meant to work in a controlled environment +under specific constraints (most devices running Linux, etc). # Building & Contributing Building, requirement and installation information all live in diff --git a/TESTING.markdown b/TESTING.markdown index 8b736747..dfd41854 100644 --- a/TESTING.markdown +++ b/TESTING.markdown @@ -1,6 +1,23 @@ -# Testing Wintermute +# Testing Wintermute {#testing} -All of the tests in Wintermute live under the `test` directory. The testing -framework used for Wintermute is underlying QtTest to handle Qt-specific logic. -Just invoke the `test` to build all of the tests and run the test suite (using -CTest as the driver). +Testing's an important part of Wintermute. It provides a level of insurance of +contract upholding between classes and higher-level interactions and allows for +rapid application development without too much concern for the breaking of +existing software. The test harness used by Wintermute is [CxxTest][] and +[CTest][] is used to drive the whole test suite (via `make test`). Coverage +support is handled by `gcov` and memory checking is handled by `valgrind`. + +## Wintermute's Fixture Library {#testing-fixture-lib} + +Wintermute has a fixture library `wintermute-fixtures` that +allows test developers to write more introspective actions into Wintermute and +even take advantage of mock classes into Wintermute's operations that are +typically, by design, closed off. In essence, the fixture library is a +recompilation of the core Wintermute library with these additives, thus +preventing the parallel installation of said libraries. + +### Available Fixtures {#testing-fixture-lib-fixtures} + +### Available Macros {#testing-fixture-lib-macros} + +### Available Functions {#testing-fixture-lib-functions} diff --git a/Vagrantfile b/Vagrantfile index f305818f..18229a3a 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -3,7 +3,6 @@ VAGRANTFILE_API_VERSION = "2" Vagrant.configure(VAGRANTFILE_API_VERSION) do |config| - config.vm.box = 'chef/ubuntu-13.04' config.vm.box_check_update = true config.ssh.forward_agent = true @@ -18,4 +17,12 @@ Vagrant.configure(VAGRANTFILE_API_VERSION) do |config| s.path = "./test/bootstrap" s.args = "--before" end + + config.vm.define "target" do | t | + t.vm.box = 'chef/ubuntu-13.04' + end + + config.vm.define "travis" do | t | + t.vm.box = 'chef/ubuntu-12.04' + end end diff --git a/cmake/FindZeroMQCXX.cmake b/cmake/FindZeroMQCXX.cmake new file mode 100644 index 00000000..05a029b0 --- /dev/null +++ b/cmake/FindZeroMQCXX.cmake @@ -0,0 +1,9 @@ +# Find zmq.hpp + +INCLUDE(FindPkgConfig) + +PKG_SEARCH_MODULE(ZMQ REQUIRED + libzmq) + +set(ZEROMQCXX_LIBRARIES ${ZMQ_LIBRARIES}) +set(ZEROMQCXX_INCLUDE_DIR /usr/include) diff --git a/cmake/WintermuteDependencies.cmake b/cmake/WintermuteDependencies.cmake index d70e6771..f5f92c9e 100644 --- a/cmake/WintermuteDependencies.cmake +++ b/cmake/WintermuteDependencies.cmake @@ -35,6 +35,7 @@ INCLUDE(WintermuteVariables) PKG_SEARCH_MODULE(JsonCpp jsoncpp REQUIRED) PKG_SEARCH_MODULE(Log4Cxx liblog4cxx REQUIRED) PKG_SEARCH_MODULE(LibUv libuv REQUIRED) +PKG_SEARCH_MODULE(Uuid uuid REQUIRED) FIND_PACKAGE(LibConfig REQUIRED) # == Exported variables @@ -43,6 +44,7 @@ set(WINTERMUTE_INCLUDE_DIRS ${Log4Cxx_INCLUDE_DIRS} ${LibUv_INCLUDE_DIRS} ${LIBCONFIG_INCLUDE_DIRS} + ${Uuid_INCLUDE_DIRS} ) set(WINTERMUTE_LIBRARIES @@ -50,6 +52,7 @@ set(WINTERMUTE_LIBRARIES ${Log4Cxx_LIBRARIES} ${LibUv_LIBRARIES} ${LIBCONFIG_LIBRARIES} + ${Uuid_LIBRARIES} ) list(APPEND WINTERMUTE_COMPILE_FLAGS @@ -57,6 +60,7 @@ list(APPEND WINTERMUTE_COMPILE_FLAGS ${Log4Cxx_CFLAGS} ${LibUv_CFLAGS} ${LIBCONFIG_CLAGS} + ${Uuid_CFLAGS} ) list(APPEND WINTERMUTE_LINK_FLAGS @@ -64,6 +68,7 @@ list(APPEND WINTERMUTE_LINK_FLAGS ${Log4Cxx_LDFLAGS} ${LibUv_LDFLAGS} ${LIBCONFIG_LDFLAGS} + ${Uuid_LDFLAGS} ) # == Versioning diff --git a/cmake/WintermuteDocumentation.cmake b/cmake/WintermuteDocumentation.cmake index 069d74a6..dce81e4f 100644 --- a/cmake/WintermuteDocumentation.cmake +++ b/cmake/WintermuteDocumentation.cmake @@ -110,7 +110,7 @@ MACRO(doxygen_generate_documentation) SET(_doxy_args "${_doxy_config_path}") - ADD_CUSTOM_TARGET("${_doxy_TARGET}-docs" ALL + ADD_CUSTOM_TARGET("${_doxy_TARGET}-docs" COMMAND ${DOXYGEN_EXECUTABLE} ${_doxy_args} DEPENDS ${_doxy_BUILD_AFTER} WORKING_DIRECTORY ${_doxy_working_dir} diff --git a/cmake/WintermutePlugin.cmake b/cmake/WintermutePlugin.cmake new file mode 100644 index 00000000..d91b72d9 --- /dev/null +++ b/cmake/WintermutePlugin.cmake @@ -0,0 +1,111 @@ +# vim: set ts=2 sts=2 sw=2 fdm=indent +############################################################################### +# Author: Jacky Alciné +# +# Wintermute is free software; you can redistribute it and/or +# modify it under the terms of the GNU Library General Public +# License as published by the Free Software Foundation; either +# version 3 of the License, or (at your option) any later version. +# +# Wintermute is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Library General Public License for more details. +# +# You should have received a copy of the GNU Library General Public +# License along with Wintermute; if not, write to the +# Free Software Foundation, Inc., 59 Temple Place - Suite 330, +# Boston, MA 02111-1307, USA. +############################################################################### + +include(WintermuteSourceBuild + OPTIONAL + RESULT_VARIABLE WINTERMUTE_IS_SOURCE_BUILD + ) + +include(WintermuteTest) +include(WintermuteVariables) + +MACRO(wintermute_plugin_declare) + SET(options + ) + SET(oneValueArgs + NAME + TARGET + ) + SET(multiValueArgs + ) + + CMAKE_PARSE_ARGUMENTS(_wpd "${options}" + "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + wintermute_add_target_properties(${_wpd_TARGET}) + target_link_libraries(${_wpd_TARGET} + wintermute) + + set(_include_dirs ) + set(_libs ) + + if (EXISTS ${WINTERMUTE_IS_SOURCE_BUILD}) + set(_include_dirs + ${CMAKE_SOURCE_DIR}/src + ${CMAKE_BINARY_DIR}/src/wintermutecore + ) + else() + set(_include_dirs ${WINTERMUTE_INCLUDE_DIR}) + endif() + + target_include_directories(${_wpd_TARGET} BEFORE + PUBLIC ${_include_dirs}) +ENDMACRO(wintermute_plugin_declare) + +# TODO: Configure the test driver source file to work for the provided Plugin. +MACRO(wintermute_plugin_validate) + SET(options + ) + SET(oneValueArgs + TARGET + ) + SET(multiValueArgs + ) + + CMAKE_PARSE_ARGUMENTS(_wpv "${options}" + "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + SET(_plugin_driver_file + "${CMAKE_CURRENT_BINARY_DIR}/${_wpv_TARGET}-validator.hh") + SET(_test_driver_file ) + + IF (EXISTS ${WINTERMUTE_IS_SOURCE_BUILD}) + SET(_test_driver_file "${CMAKE_SOURCE_DIR}/cmake/plugin_driver.hh.in") + ELSE() + ENDIF() + + GET_TARGET_PROPERTY(_wpv_file ${_wpv_TARGET} LOCATION) + + CONFIGURE_FILE(${_test_driver_file} + ${_plugin_driver_file} + @ONLY) + + WINTERMUTE_ADD_TEST(plugin-verify ${_wpv_TARGET} ${_plugin_driver_file}) +ENDMACRO(wintermute_plugin_validate) + +MACRO(wintermute_plugin_add_test) + SET(options + ) + SET(oneValueArgs + PREFIX + HEADER + NAME + TARGET + ) + SET(multiValueArgs + ) + + CMAKE_PARSE_ARGUMENTS(_wpat "${options}" + "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + WINTERMUTE_ADD_TEST("plugin-${_wpat_PREFIX}" ${_wpat_NAME} ${_wpat_HEADER}) + TARGET_LINK_LIBRARIES("plugin-${_wpat_PREFIX}-${_wpat_NAME}" + ${_wpat_TARGET}) +ENDMACRO(wintermute_plugin_add_test) diff --git a/cmake/WintermuteTest.cmake b/cmake/WintermuteTest.cmake index 2800daac..775aba38 100644 --- a/cmake/WintermuteTest.cmake +++ b/cmake/WintermuteTest.cmake @@ -39,6 +39,7 @@ SET(_wntr_test_tpl SET(CXXTEST_TESTGEN_ARGS --template ${_wntr_test_tpl} + --runner=ErrorPrinter ) SET(WINTERMUTE_TEST_INCLUDE_DIRS diff --git a/cmake/WintermuteTestMacros.cmake b/cmake/WintermuteTestMacros.cmake index a953029a..b4405c53 100644 --- a/cmake/WintermuteTestMacros.cmake +++ b/cmake/WintermuteTestMacros.cmake @@ -52,7 +52,7 @@ else() endif() MACRO(wintermute_add_test _prefix _name _hdr) - SET(_target ${_prefix}_${_name}) + SET(_target ${_prefix}-${_name}) CXXTEST_ADD_TEST(${_target} ${_target}_test.cc ${_hdr}) WINTERMUTE_LINK_LIBRARIES(${_target}) WINTERMUTE_ADD_TARGET_PROPERTIES(${_target}) diff --git a/cmake/WintermuteVariables.cmake b/cmake/WintermuteVariables.cmake index e66684d9..dae3532b 100644 --- a/cmake/WintermuteVariables.cmake +++ b/cmake/WintermuteVariables.cmake @@ -30,12 +30,12 @@ SET(WINTERMUTE_COMPILE_FLAGS -Wunused -Wctor-dtor-privacy -Wenum-compare + -fmessage-length=0 ) SET(WINTERMUTE_COMPILE_FLAGS_DEBUG -O0 -g3 - -fexceptions -Wextra -Wno-conversion-null -Wno-deprecated @@ -47,7 +47,7 @@ SET(WINTERMUTE_COMPILE_FLAGS_DEBUG -Wunused-parameter -Wunused-variable -Wwrite-strings - -ftemplate-backtrace-limit=0 + -ggdb3 ) SET(WINTERMUTE_COMPILE_FLAGS_RELEASE @@ -73,7 +73,7 @@ if (CMAKE_BUILD_TYPE STREQUAL Debug) list(APPEND WINTERMUTE_COMPILE_FLAGS_DEBUG -fprofile-arcs -ftest-coverage - -fabi-version=4 + -gstabs+ ) elseif(CMAKE_CXX_COMPILER_ID STREQUAL "Clang") list(APPEND WINTERMUTE_COMPILE_FLAGS_DEBUG @@ -81,13 +81,14 @@ if (CMAKE_BUILD_TYPE STREQUAL Debug) endif() endif() -if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND CMAKE_CXX_COMPILER_VERSION MATCHES "^4.6") +if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND + CMAKE_CXX_COMPILER_VERSION MATCHES "^4.6") list(APPEND WINTERMUTE_COMPILE_FLAGS --std=c++0x ) else() list(APPEND WINTERMUTE_COMPILE_FLAGS - --std=c++11 + --std=c++14 ) endif() diff --git a/cmake/plugin_driver.hh.in b/cmake/plugin_driver.hh.in new file mode 100644 index 00000000..9ff7f206 --- /dev/null +++ b/cmake/plugin_driver.hh.in @@ -0,0 +1,37 @@ +/* + * Wintermute is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * Wintermute is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with Wintermute; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include +#include +#include "test_suite.hpp" + +#define PLUGIN_TEST_PATH "@_wpv_file@" + +using Wintermute::Plugin; + +class PluginVerifyTestSuite : public CxxTest::TestSuite +{ +public: + void testWorkWithPlugin() + { + Plugin::Ptr pluginPtr = Plugin::find(PLUGIN_TEST_PATH); + TSM_ASSERT ( "Plugin was loaded from disk.", pluginPtr ); + TSM_ASSERT ( "Plugin was released from memory.", Plugin::release(pluginPtr->name()) ); + } +}; + + diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 34809559..bc5b7d5d 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -19,3 +19,13 @@ ############################################################################### # == Root library. ADD_SUBDIRECTORY(wintermutecore) + +# == Standalone Plugins +ADD_SUBDIRECTORY(wintermute-daemon) +ADD_SUBDIRECTORY(wintermute-heartbeat) + +# == Transports +#ADD_SUBDIRECTORY(wintermute-transport-dbus) +ADD_SUBDIRECTORY(wintermute-transport-zeromq) +#ADD_SUBDIRECTORY(wintermute-transport-http) +#ADD_SUBDIRECTORY(wintermute-transport-gcm) diff --git a/src/wintermute-daemon/CMakeLists.txt b/src/wintermute-daemon/CMakeLists.txt new file mode 100644 index 00000000..577381f0 --- /dev/null +++ b/src/wintermute-daemon/CMakeLists.txt @@ -0,0 +1,51 @@ +# vim: set ts=2 sts=2 sw=2 fdm=indent +############################################################################### +# Author: Jacky Alciné +# +# Wintermute is free software; you can redistribute it and/or +# modify it under the terms of the GNU Library General Public +# License as published by the Free Software Foundation; either +# version 3 of the License, or (at your option) any later version. +# +# Wintermute is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Library General Public License for more details. +# +# You should have received a copy of the GNU Library General Public +# License along with Wintermute; if not, write to the +# Free Software Foundation, Inc., 59 Temple Place - Suite 330, +# Boston, MA 02111-1307, USA. +############################################################################### +CMAKE_MINIMUM_REQUIRED(VERSION 2.8.12) +PROJECT(WintermuteDaemon CXX) + +INCLUDE(WintermutePlugin) +INCLUDE(FindPkgConfig) + +SET(WINTERMUTE_DAEMON_VERSION 0.0.1-dev) + +SET(_srcs + plugin.cpp + plugin.cc + module.cpp + module.cc + ) + +CONFIGURE_FILE(globals.hpp.in + ${CMAKE_CURRENT_SOURCE_DIR}/globals.hpp + @ONLY + ) + +ADD_LIBRARY(wintermute-daemon + SHARED ${_srcs} + ) + +WINTERMUTE_PLUGIN_DECLARE( + NAME daemon + TARGET wintermute-daemon + ) + +WINTERMUTE_PLUGIN_VALIDATE(TARGET wintermute-daemon) + +ADD_SUBDIRECTORY(test) diff --git a/src/wintermute-daemon/README.markdown b/src/wintermute-daemon/README.markdown new file mode 100644 index 00000000..070afa1b --- /dev/null +++ b/src/wintermute-daemon/README.markdown @@ -0,0 +1,41 @@ +# Daemon Plugin for Wintermute {#daemon} + +The daemon plugin in Wintermute serves as a way to maintain plugins that +Wintermute needs in order to stabilize its remote procedure calling system like +module existence on a local machine and sending machines from a local machine +out over a network. It does this by reading the daemon's configuration options +and loading the designation plugins to be used for _relaying messages_ over a +stream (UNIX socket, TCP, etc) and starts that up. + +The next step would be loading the **heartbeat** plugin. The heartbeat plugin +collects heartbeat signals from other processes on the local system and allows +one to build a model of what the system looks in an elastic fashion. This model +isn't required for _any_ procedure calling but it's provided as a model for +services that'd like to ensure the existence of another module or call. + +After that, the **RPC relay** plugin, designated by the daemon's configuration, +is loaded and prepped to listen for incoming messages from the external network +and send outgoing messages to the outgoing network. + +## Heartbeat Plugin {#heartbeat} + +The heartbeat plugin is an utility plugin loaded into the daemon instance that +collects information from other instances of Wintermute running on the local +machine. It fetches this by sending out pings designated to be answered by +instances of Wintermute and collects pongs and records the time of said +collection to see which process is still currently active or is need of a kick. +Very simple in ideology, if you ask me. + +## Loading the RPC Relay Plugin {#rpc-relay} + +The RPC relay is meant to transport messages designated to go _off_ the current +network or come from _outside_ the current network and pass them back onto the +local network. This is handy for having instances of Wintermute running +on the same network but on different machines, perhaps for home automation or +distributed computing. By default, [ZeroMQ][] is used to handle local _and_ +remote communications between Wintermute. The addresses it listens to is +defined with compile-time defaults, `WINTERMUTE_ZMQ_ADDRESS_LOCAL` and +`WINTERMUTE_ZMQ_ADDRESS_REMOTE` but it also allows for added addresses in the +event that the internal defaults don't work on said machine. + +[ZeroMQ]: http://zeromq.org/ diff --git a/src/wintermute-daemon/globals.hpp.in b/src/wintermute-daemon/globals.hpp.in new file mode 100644 index 00000000..db611954 --- /dev/null +++ b/src/wintermute-daemon/globals.hpp.in @@ -0,0 +1,33 @@ +/* + Wintermute - A foundation for intelligent computing. + Copyright (c) 2010 - 2015 by Jacky Alcine + + Wintermute is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 3 of the License, or (at your option) any later version. + + Wintermute is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with Wintermute; if not, write to the + Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + */ + +#ifndef WINTERMUTE_DAEMON_GLOBALS_HPP_ +# define WINTERMUTE_DAEMON_GLOBALS_HPP_ + +#include + +#define WINTERMUTE_DAEMON_PLUGIN_NAME "DAEMON" +#define WINTERMUTE_DAEMON_DOMAIN WINTERMUTE_DOMAIN +#define WINTERMUTE_DAEMON_MODULE "daemon" + +// TODO: Provide a proper path for the daemon's configuration. +#define WINTERMUTE_DAEMON_CFG_PATH "@CMAKE_BINARY_DIR@/test/unit/sample" + +#endif diff --git a/src/wintermute-daemon/module.cc b/src/wintermute-daemon/module.cc new file mode 100644 index 00000000..183b81ca --- /dev/null +++ b/src/wintermute-daemon/module.cc @@ -0,0 +1,33 @@ +/* + Wintermute - A foundation for intelligent computing. + Copyright (c) 2010 - 2015 by Jacky Alcine + + Wintermute is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 3 of the License, or (at your option) any later version. + + Wintermute is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with Wintermute; if not, write to the + Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +#include "module.hh" + +using Wintermute::Events::SignalHandler; +using Wintermute::Daemon::ModulePrivate; + +ModulePrivate::ModulePrivate() : + signalHandler(make_shared(SIGINT)) +{ +} + +ModulePrivate::~ModulePrivate() +{ +} diff --git a/src/wintermute-daemon/module.cpp b/src/wintermute-daemon/module.cpp new file mode 100644 index 00000000..76f1e509 --- /dev/null +++ b/src/wintermute-daemon/module.cpp @@ -0,0 +1,71 @@ +/* + Wintermute - A foundation for intelligent computing. + Copyright (c) 2010 - 2015 by Jacky Alcine + + Wintermute is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 3 of the License, or (at your option) any later version. + + Wintermute is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with Wintermute; if not, write to the + Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +#include +#include +#include +#include "module.hpp" +#include "module.hh" + +using Wintermute::Method; +using Wintermute::Module; +using DaemonModule = Wintermute::Daemon::Module; +using Wintermute::Events::SignalEvent; + +DaemonModule::Module() : + Wintermute::Module(Module::Designation("in.wintermute", "daemon")), + d_ptr(new DaemonModule::_Prv) +{ + W_PRV(Daemon::Module); + + d->signalHandler->listenForEvent(W_EVENT_SIGNAL, + [&](const SignalEvent::Ptr& sigEvent){ + wdebug("Signal given..."); + assert(sigEvent); + }); +} + +DaemonModule::~Module() +{ +} + +void DaemonModule::startRelay() +{ + // TODO: Update 'Module' to have a generator method for this. + Method::Ptr startMethodCall = std::make_shared( + "startRelay", + Module::Designation(WINTERMUTE_DOMAIN, "heartbeat"), + designation() + ); + + startMethodCall->invoke(); +} + +void DaemonModule::stopRelay() +{ + // TODO: Update 'Module' to have a generator method for this. + Method::Ptr stopMethodCall = std::make_shared( + "stopRelay", + Module::Designation(WINTERMUTE_DOMAIN, "heartbeat"), + designation() + ); + + stopMethodCall->invoke(); +} diff --git a/src/wintermute-daemon/module.hh b/src/wintermute-daemon/module.hh new file mode 100644 index 00000000..6ca4bcf7 --- /dev/null +++ b/src/wintermute-daemon/module.hh @@ -0,0 +1,44 @@ +/* + Wintermute - A foundation for intelligent computing. + Copyright (c) 2010 - 2015 by Jacky Alcine + + Wintermute is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 3 of the License, or (at your option) any later version. + + Wintermute is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with Wintermute; if not, write to the + Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +#ifndef WINTERMUTE_DAEMON_MODULE_HH_ +#define WINTERMUTE_DAEMON_MODULE_HH_ + +#include +#include +#include +#include + +namespace Wintermute +{ +namespace Daemon +{ +class ModulePrivate +{ +public: + Events::SignalHandler::Ptr signalHandler; + + explicit ModulePrivate(); + virtual ~ModulePrivate(); +}; +} +} + +#endif diff --git a/src/wintermute-daemon/module.hpp b/src/wintermute-daemon/module.hpp new file mode 100644 index 00000000..d193c206 --- /dev/null +++ b/src/wintermute-daemon/module.hpp @@ -0,0 +1,43 @@ +/* + Wintermute - A foundation for intelligent computing. + Copyright (c) 2010 - 2015 by Jacky Alcine + + Wintermute is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 3 of the License, or (at your option) any later version. + + Wintermute is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with Wintermute; if not, write to the + Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + */ + +#ifndef WINTERMUTE_DAEMON_MODULE_HPP_ +#define WINTERMUTE_DAEMON_MODULE_HPP_ + +#include + +namespace Wintermute +{ +namespace Daemon +{ +class ModulePrivate; +class Module : public Wintermute::Module +{ + W_DEF_PRIVATE(Module); + +public: + explicit Module(); + virtual ~Module(); + void startRelay(); + void stopRelay(); +}; +} +} +#endif diff --git a/src/wintermute-daemon/plugin.cc b/src/wintermute-daemon/plugin.cc new file mode 100644 index 00000000..23250863 --- /dev/null +++ b/src/wintermute-daemon/plugin.cc @@ -0,0 +1,85 @@ +/* + Wintermute - A foundation for intelligent computing. + Copyright (c) 2010 - 2015 by Jacky Alcine + + Wintermute is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 3 of the License, or (at your option) any later version. + + Wintermute is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with Wintermute; if not, write to the + Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + */ + +#include +#include +#include +#include +#include "plugin.hh" + +using Wintermute::Plugin; +using Wintermute::Module; +using Wintermute::Util::Configuration; +using DaemonPluginPrivate = Wintermute::Daemon::PluginPrivate; +using std::to_string; + +DaemonPluginPrivate::PluginPrivate() : + module(make_shared()) +{ +} + +DaemonPluginPrivate::~PluginPrivate() +{ +} + +void DaemonPluginPrivate::loadHeartbeat() +{ + Plugin::Ptr heartbeatPluginPtr = Plugin::find("wintermute-heartbeat"); + const bool loadedPlugin = !heartbeatPluginPtr; + if (!loadedPlugin) + { + wdebug("Failed to load the heartbeat plugin."); + return; + } + else + { + winfo("Heartbeat plugin loaded."); + } + + // FIXME: So for now, we'll have the heartbeat plugin know whether or not if + // it should run as the pinger or the pingee by the existence of the daemon + // plugin. Realistically, there should be only one process on the machine + // that has the daemon plugin loaded so it's a contract we'll have to find a + // way to enforce. This way, we'd never have to link the daemon plugin + // directly to the heartbeat plugin to invoke particular actions on it. +} + +void DaemonPluginPrivate::unloadHeartbeat() +{ + const bool unloadedPlugin = Plugin::release("wintermute-heartbeat"); + if (!unloadedPlugin) + { + wdebug("Failed to unload the heartbeat plugin."); + return; + } + else + { + winfo("Heartbeat plugin unloaded."); + } +} + +Configuration::Ptr DaemonPluginPrivate::config() const +{ + Configuration::Ptr daemonCfg; + // TODO: Try a collection of options here. + daemonCfg = Configuration::obtainStore(WINTERMUTE_DAEMON_CFG_PATH); + + return daemonCfg; +} diff --git a/src/wintermute-daemon/plugin.cpp b/src/wintermute-daemon/plugin.cpp new file mode 100644 index 00000000..b4826c49 --- /dev/null +++ b/src/wintermute-daemon/plugin.cpp @@ -0,0 +1,131 @@ +/* + Wintermute - A foundation for intelligent computing. + Copyright (c) 2010 - 2015 by Jacky Alcine + + Wintermute is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 3 of the License, or (at your option) any later version. + + Wintermute is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with Wintermute; if not, write to the + Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + */ + +#include +#include +#include +#include +#include "plugin.hpp" +#include "plugin.hh" + +using Wintermute::Plugin; +using Wintermute::Method; +using Wintermute::Util::Configuration; +using DaemonPlugin = Wintermute::Daemon::Plugin; +using DaemonModule = Wintermute::Daemon::Module; +using DaemonPluginPrivate = Wintermute::Daemon::PluginPrivate; +using std::for_each; +using std::to_string; +using std::dynamic_pointer_cast; + +DaemonPlugin::Plugin() : + Wintermute::Plugin(WINTERMUTE_DAEMON_PLUGIN_NAME), + d_ptr(new DaemonPluginPrivate) +{ +} + +DaemonPlugin::~Plugin() +{ +} + +bool DaemonPlugin::startup() +{ + startDesignatedPlugins(); + startHeartbeatInstance(); + startModule(); + return true; +} + +bool DaemonPlugin::shutdown() +{ + stopModule(); + stopHeartbeatInstance(); + stopDesignatedPlugins(); + return true; +} + +Plugin::PluginType DaemonPlugin::type() const +{ + return Wintermute::Plugin::PluginTypeService; +} + +void DaemonPlugin::startHeartbeatInstance() +{ + wdebug("Starting up heartbeat instance..."); + W_PRV(DaemonPlugin); + d->loadHeartbeat(); + wdebug("Started heartbeat instance."); +} + +void DaemonPlugin::stopHeartbeatInstance() +{ + wdebug("Stopping heartbeat instance..."); + W_PRV(DaemonPlugin); + d->unloadHeartbeat(); + wdebug("Stopped heartbeat instance."); +} + +void DaemonPlugin::startModule() +{ + W_PRV(DaemonPlugin); + dynamic_pointer_cast(d->module)->startRelay(); +} + +void DaemonPlugin::stopModule() +{ + W_PRV(DaemonPlugin); + dynamic_pointer_cast(d->module)->stopRelay(); +} + +void DaemonPlugin::startDesignatedPlugins() +{ + W_PRV(const DaemonPlugin); + Configuration::Ptr daemonCfg = d->config(); + list pluginNames = daemonCfg->get("Start/Plugins", list()); + + for_each(pluginNames.begin(), pluginNames.end(), [&](const string& pluginName) + { + wdebug("Attempting to load plugin " + pluginName + "..."); + Plugin::Ptr designatedPluginPtr = Plugin::find(pluginName); + wwarn("Loaded designated plugin " + pluginName + "?" + + to_string((bool)(designatedPluginPtr))); + wdebug("Loaded plugin '" + pluginName + "'."); + }); + + Plugin::Ptr heartbeatPluginPtr = Plugin::find("wintermute-heartbeat"); + assert(heartbeatPluginPtr); +} + +void DaemonPlugin::stopDesignatedPlugins() +{ + W_PRV(const DaemonPlugin); + Configuration::Ptr daemonCfg = d->config(); + list pluginNames = daemonCfg->get("Start/Plugins", list()); + + for_each(pluginNames.begin(), pluginNames.end(), [&](const string& pluginName) + { + const bool pluginUnloaded = Plugin::release(pluginName); + wdebug("Attempt to unload designated plugin " + pluginName + "?" + + to_string((int)pluginUnloaded)); + }); + + const bool releaseHeartbeat = Plugin::release("wintermute-heartbeat"); + assert(releaseHeartbeat); +} diff --git a/src/wintermute-daemon/plugin.hh b/src/wintermute-daemon/plugin.hh new file mode 100644 index 00000000..fed1817f --- /dev/null +++ b/src/wintermute-daemon/plugin.hh @@ -0,0 +1,47 @@ +/* + Wintermute - A foundation for intelligent computing. + Copyright (c) 2010 - 2015 by Jacky Alcine + + Wintermute is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 3 of the License, or (at your option) any later version. + + Wintermute is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with Wintermute; if not, write to the + Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + */ + +#ifndef WINTERMUTE_DAEMON_PLUGIN_HH_ +#define WINTERMUTE_DAEMON_PLUGIN_HH_ + +#include +#include "globals.hpp" +#include "module.hpp" + +namespace Wintermute +{ + namespace Daemon { + class PluginPrivate + { + public: + explicit PluginPrivate(); + ~PluginPrivate(); + void loadHeartbeat(); + void unloadHeartbeat(); + Util::Configuration::Ptr config() const; + Daemon::Module::Ptr module; + + private: + /* data */ + }; + } +} + +#endif diff --git a/src/wintermute-daemon/plugin.hpp b/src/wintermute-daemon/plugin.hpp new file mode 100644 index 00000000..58b0ce1c --- /dev/null +++ b/src/wintermute-daemon/plugin.hpp @@ -0,0 +1,56 @@ +/* + Wintermute - A foundation for intelligent computing. + Copyright (c) 2010 - 2015 by Jacky Alcine + + Wintermute is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 3 of the License, or (at your option) any later version. + + Wintermute is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with Wintermute; if not, write to the + Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + */ + +#ifndef WINTERMUTE_DAEMON_PLUGIN_HPP_ +# define WINTERMUTE_DAEMON_PLUGIN_HPP_ + +#include +#include "globals.hpp" + +namespace Wintermute +{ +namespace Daemon +{ +class PluginPrivate; +class Plugin : public Wintermute::Plugin +{ + W_DEF_PRIVATE(Wintermute::Daemon::Plugin) + + public: + explicit Plugin(); + virtual ~Plugin(); + virtual bool startup(); + virtual bool shutdown(); + virtual Plugin::PluginType type() const; + + private: + void startHeartbeatInstance(); + void stopHeartbeatInstance(); + void startModule(); + void stopModule(); + void startDesignatedPlugins(); + void stopDesignatedPlugins(); +}; +} +} + +W_DECL_PLUGIN(Wintermute::Daemon::Plugin, WINTERMUTE_DAEMON_VERSION) + +#endif diff --git a/src/wintermute-daemon/test/CMakeLists.txt b/src/wintermute-daemon/test/CMakeLists.txt new file mode 100644 index 00000000..30f7fb85 --- /dev/null +++ b/src/wintermute-daemon/test/CMakeLists.txt @@ -0,0 +1,20 @@ +# vim: set ts=2 sts=2 sw=2 fdm=indent +############################################################################### +# Author: Jacky Alciné +# +# Wintermute is free software; you can redistribute it and/or +# modify it under the terms of the GNU Library General Public +# License as published by the Free Software Foundation; either +# version 3 of the License, or (at your option) any later version. +# +# Wintermute is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Library General Public License for more details. +# +# You should have received a copy of the GNU Library General Public +# License along with Wintermute; if not, write to the +# Free Software Foundation, Inc., 59 Temple Place - Suite 330, +# Boston, MA 02111-1307, USA. +############################################################################### +CMAKE_MINIMUM_REQUIRED(VERSION 2.8.12) diff --git a/src/wintermute-heartbeat/CMakeLists.txt b/src/wintermute-heartbeat/CMakeLists.txt new file mode 100644 index 00000000..e6654321 --- /dev/null +++ b/src/wintermute-heartbeat/CMakeLists.txt @@ -0,0 +1,57 @@ +# vim: set ts=2 sts=2 sw=2 fdm=indent +############################################################################### +# Author: Jacky Alciné +# +# Wintermute is free software; you can redistribute it and/or +# modify it under the terms of the GNU Library General Public +# License as published by the Free Software Foundation; either +# version 3 of the License, or (at your option) any later version. +# +# Wintermute is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Library General Public License for more details. +# +# You should have received a copy of the GNU Library General Public +# License along with Wintermute; if not, write to the +# Free Software Foundation, Inc., 59 Temple Place - Suite 330, +# Boston, MA 02111-1307, USA. +############################################################################### +CMAKE_MINIMUM_REQUIRED(VERSION 2.8.12) +PROJECT(WintermuteHeartbeat CXX) + +INCLUDE(WintermutePlugin) +INCLUDE(FindPkgConfig) + +PKG_SEARCH_MODULE(UUID uuid) + +SET(WINTERMUTE_HEARTBEAT_VERSION 0.0.1-dev) + +SET(_srcs + plugin.cpp + plugin.cc + module.cpp + module.cc + pong_message.cpp + ping_message.cpp + pong_module.cpp + ping_module.cpp + ) + +CONFIGURE_FILE(globals.hpp.in + ${CMAKE_CURRENT_SOURCE_DIR}/globals.hpp + @ONLY + ) + +ADD_LIBRARY(wintermute-heartbeat + SHARED ${_srcs} + ) + +WINTERMUTE_PLUGIN_DECLARE( + NAME heartbeat + TARGET wintermute-heartbeat + ) + +WINTERMUTE_PLUGIN_VALIDATE(TARGET wintermute-heartbeat) + +ADD_SUBDIRECTORY(test) diff --git a/src/wintermute-heartbeat/README.markdown b/src/wintermute-heartbeat/README.markdown new file mode 100644 index 00000000..0f06f853 --- /dev/null +++ b/src/wintermute-heartbeat/README.markdown @@ -0,0 +1,82 @@ +# Heartbeats in Wintermute {#heartbeat} + +The heartbeat plugin serves as Wintermute's means of bringing awareness to the +neighboring plugin irrespective of the underlying RPC logic used to communicate. +The idea behind this is to allow for the introduction of alternative RPC and +message broker tools without having to do too much of a rewrite of the system of +Wintermute. + +## Call Cycle + +When a new process starts up, considered a _node_, it sends out a SYN call to +inform the heartbeat instance of its existence. The heartbeat instance, in +return, replies to that call with an ACK call with its heartbeat specific UUID +as well as the UUIDs of its neighboring modules. + +SYN call: + +``` +{ + 'modules': ['module1', 'module2', 'module3'], + 'plugins': ['plugin1', 'plugin2'], +} +``` + +ACK call (empty): +``` +{ + 'uuid': 'b2c74df9-9c90-4024-a818-4a7ad0cc5d30' +} +``` + +This cycle continues indefinitely. + +## Notes + + * What should a client node do if it notices that its UUID has changed? + - What kind of assumptions can be made here? + - Do we classify this as a system restart? If so, why? + * How do we prevent forgery of message structure by outside sources? + **TODO**: Look into using libsodium to encrypt all messages. + +# Heartbeats in Wintermute {#heartbeat} + +The heartbeat plugin serves as Wintermute's means of bringing awareness to the +neighboring plugin irrespective of the underlying RPC logic used to communicate. +The idea behind this is to allow for the introduction of alternative RPC and +message broker tools without having to do too much of a rewrite of the system of +Wintermute. + +## Call Cycle + +When a new process starts up, considered a _node_, it sends out a SYN call to +inform the heartbeat instance of its existence. The heartbeat instance, in +return, replies to that call with an ACK call with its heartbeat specific UUID +as well as the UUIDs of its neighboring modules. + +SYN call: + +``` +{ + 'modules': ['module1', 'module2', 'module3'], + 'plugins': ['plugin1', 'plugin2'], +} +``` + +ACK call (empty): +``` +{ + 'uuid': 'b2c74df9-9c90-4024-a818-4a7ad0cc5d30' +} +``` + +This cycle continues indefinitely. + +## Notes + + * What should a client node do if it notices that its UUID has changed? + - What kind of assumptions can be made here? + - Do we classify this as a system restart? If so, why? + +--- +[^1]: diff --git a/src/wintermute-heartbeat/globals.hpp.in b/src/wintermute-heartbeat/globals.hpp.in new file mode 100644 index 00000000..9d154746 --- /dev/null +++ b/src/wintermute-heartbeat/globals.hpp.in @@ -0,0 +1,50 @@ +/* + Wintermute - A foundation for intelligent computing. + Copyright (c) 2010 - 2015 by Jacky Alcine + + Wintermute is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 3 of the License, or (at your option) any later version. + + Wintermute is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with Wintermute; if not, write to the + Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + */ + +#ifndef WINTERMUTE_HEARTBEAT_GLOBALS_HPP_ +# define WINTERMUTE_HEARTBEAT_GLOBALS_HPP_ + +#include + +/** + * Defines the name of the heartbeat plugin. + * @todo Document. + * @def WINTERMUTE_HEARTBEAT_PLUGIN_NAME + */ +#define WINTERMUTE_HEARTBEAT_PLUGIN_NAME "wintermute-heartbeat" + +/** + * Defines the base domain to be used for module calls. + * @todo Document + * @def WINTERMUTE_HEARTBEAT_DOMAIN + */ +#define WINTERMUTE_HEARTBEAT_DOMAIN WINTERMUTE_DOMAIN +#define WINTERMUTE_HEARTBEAT_MODULE "heartbeat" +#define WINTERMUTE_HEARTBEAT_MODULE_PONGER WINTERMUTE_HEARTBEAT_MODULE".ponger" +#define WINTERMUTE_HEARTBEAT_MODULE_PINGER WINTERMUTE_HEARTBEAT_MODULE".pinger" + +/** + * Defines the rate of message sending in milliseconds. + * @todo Document. + * @def WINTERMUTE_HEARTBEAT_INTERVAL + */ +#define WINTERMUTE_HEARTBEAT_INTERVAL 750 + +#endif diff --git a/src/wintermute-heartbeat/messages.hpp b/src/wintermute-heartbeat/messages.hpp new file mode 100644 index 00000000..560162c8 --- /dev/null +++ b/src/wintermute-heartbeat/messages.hpp @@ -0,0 +1,66 @@ +/* + Wintermute - A foundation for intelligent computing. + Copyright (c) 2010 - 2015 by Jacky Alcine + + Wintermute is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 3 of the License, or (at your option) any later version. + + Wintermute is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with Wintermute; if not, write to the + Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + */ + +#ifndef WINTERMUTE_HEARTBEAT_MESSAGES_HPP_ +# define WINTERMUTE_HEARTBEAT_MESSAGES_HPP_ + +#include +#include +#include "module.hpp" +#include "globals.hpp" + +namespace Wintermute +{ + namespace Heartbeat + { + /** + * A ping message to be sent by a child process of Wintermute. + * @sa Wintermute::Message + */ + class PingMessage : public Wintermute::Message + { + friend class Wintermute::Heartbeat::Module; + + protected: + explicit PingMessage(); + virtual ~PingMessage(); + + public: + static Message craft(); + }; + + /** + * A pong message to be sent by the heartbeat center of Wintermute. + */ + class PongMessage : public Wintermute::Message + { + friend class Wintermute::Heartbeat::Module; + + protected: + explicit PongMessage(); + virtual ~PongMessage(); + + public: + static Message craft(); + }; + } +} + +#endif diff --git a/src/wintermute-heartbeat/module.cc b/src/wintermute-heartbeat/module.cc new file mode 100644 index 00000000..383e2a87 --- /dev/null +++ b/src/wintermute-heartbeat/module.cc @@ -0,0 +1,105 @@ +/* + Wintermute - A foundation for intelligent computing. + Copyright (c) 2010 - 2015 by Jacky Alcine + + Wintermute is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 3 of the License, or (at your option) any later version. + + Wintermute is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with Wintermute; if not, write to the + Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + */ + +#include +#include +#include +#include "module.hh" +#include "ping_module.hpp" +#include "pong_module.hpp" +#include "plugin.hpp" + +using Wintermute::Heartbeat::ModulePrivate; +using HBModule = Wintermute::Heartbeat::Module; + +ModulePrivate::ModulePrivate() : + modeModule(nullptr), + mode(HBModule::ModeUndefined) +{ + determineMode(); +} + +ModulePrivate::~ModulePrivate() +{ + tearDownSubModule(); +} + +void ModulePrivate::determineMode() +{ + if (mode == HBModule::ModeUndefined) + { + const bool hasDaemon = + Module::Pool::instance()->has( + Module::Designation("daemon","in.wintermute") + ); + + mode = (hasDaemon) ? HBModule::ModePong : HBModule::ModePing; + } + else + { + wwarn("Already did detection of mode!"); + } + + wdebug("Determined mode to run heartbeat as " + to_string((int)mode) + "."); +} + +void ModulePrivate::setUpSubModule() +{ + wdebug("Setting up mode-specific module for heartbeat..."); + + switch (mode) + { + case HBModule::ModePing: + wdebug("This instance will be pinging."); + modeModule = make_shared(); + break; + case HBModule::ModePong: + wdebug("This instance will be ponging."); + modeModule = make_shared(); + break; + default: + wwarn("This instance will be doing nothing " + to_string((int)mode) + "."); + break; + } + + if (modeModule) + { + auto des = modeModule->designation(); + wdebug("Setting up heartbeat sub-module " + static_cast(des) + "..."); + modeModule->enable(); + wdebug("Set up heartbeat sub-module " + static_cast(des) + "."); + } +} + +void ModulePrivate::tearDownSubModule() +{ + if (modeModule) + { + auto des = modeModule->designation(); + wdebug("Tearing down heartbeat sub-module " + static_cast(des) + "..."); + modeModule->disable(); + modeModule = nullptr; + wdebug("Tore down heartbeat sub-module."); + } + else + { + wdebug("No sub module to tear down."); + } +} diff --git a/src/wintermute-heartbeat/module.cpp b/src/wintermute-heartbeat/module.cpp new file mode 100644 index 00000000..013d7502 --- /dev/null +++ b/src/wintermute-heartbeat/module.cpp @@ -0,0 +1,73 @@ +/* + Wintermute - A foundation for intelligent computing. + Copyright (c) 2010 - 2015 by Jacky Alcine + + Wintermute is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 3 of the License, or (at your option) any later version. + + Wintermute is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with Wintermute; if not, write to the + Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + */ + +#include "module.hpp" +#include "module.hh" +#include +#include + +using Wintermute::Module; +using HeartbeatModule = Wintermute::Heartbeat::Module; + +HeartbeatModule::Module() : + Wintermute::Module(Wintermute::Module::Designation( + WINTERMUTE_HEARTBEAT_MODULE, + WINTERMUTE_HEARTBEAT_DOMAIN + )), + d_ptr(make_shared()) +{ + W_PRV(Heartbeat::Module); + + auto disableModuleFunc = [this](const Events::Event::Ptr& event) + { + wdebug("Heartbeat sub module disabling..."); + this->d_ptr->tearDownSubModule(); + wdebug("Heartbeat sub module disabled."); + }; + + d->setUpSubModule(); + listenForEvent(WINTERMUTE_EVENT_MODULE_DISABLE, disableModuleFunc); +} + +HeartbeatModule::~Module() +{ +} + +bool HeartbeatModule::sendMessage(const Message& message) +{ + return false; +} + +bool HeartbeatModule::receiveMessage(const Message& message) const +{ + return false; +} + +HeartbeatModule::Mode HeartbeatModule::mode() const +{ + W_PRV(const Heartbeat::Module); + return d->mode; +} + +void HeartbeatModule::setMode(const HeartbeatModule::Mode modeToUse) +{ + W_PRV(Heartbeat::Module); + d->mode = modeToUse; +} diff --git a/src/wintermute-heartbeat/module.hh b/src/wintermute-heartbeat/module.hh new file mode 100644 index 00000000..9d749304 --- /dev/null +++ b/src/wintermute-heartbeat/module.hh @@ -0,0 +1,47 @@ +/* + Wintermute - A foundation for intelligent computing. + Copyright (c) 2010 - 2015 by Jacky Alcine + + Wintermute is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 3 of the License, or (at your option) any later version. + + Wintermute is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with Wintermute; if not, write to the + Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + */ + +#include +#include +#include +#include "module.hpp" + +using std::string; + +namespace Wintermute +{ + namespace Heartbeat + { + class ModulePrivate + { + public: + explicit ModulePrivate(); + virtual ~ModulePrivate(); + void setUpSubModule(); + void tearDownSubModule(); + Wintermute::Module::Ptr modeModule; + Heartbeat::Module::Mode mode; + + private: + void generateUuid(); + void determineMode(); + }; + } +} diff --git a/src/wintermute-heartbeat/module.hpp b/src/wintermute-heartbeat/module.hpp new file mode 100644 index 00000000..c601e580 --- /dev/null +++ b/src/wintermute-heartbeat/module.hpp @@ -0,0 +1,58 @@ +/* + Wintermute - A foundation for intelligent computing. + Copyright (c) 2010 - 2015 by Jacky Alcine + + Wintermute is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 3 of the License, or (at your option) any later version. + + Wintermute is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with Wintermute; if not, write to the + Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + */ + +#ifndef WINTERMUTE_HEARTBEAT_MODULE_HPP_ +# define WINTERMUTE_HEARTBEAT_MODULE_HPP_ + +#include +#include "globals.hpp" + +namespace Wintermute +{ +namespace Heartbeat +{ +class ModulePrivate; +class Module : public Wintermute::Module +{ + W_DEF_PRIVATE(Wintermute::Heartbeat::Module) +public: + W_DECL_PTR_TYPE(Wintermute::Heartbeat::Module) + + public: + explicit Module(); + virtual ~Module(); + + enum Mode { + ModeUndefined = 0x0, + ModePing = 0x1, + ModePong = 0x2 + }; + + void setMode(const Mode modeToUse); + Mode mode() const; + + protected: + virtual bool sendMessage(const Message& message); + virtual bool receiveMessage(const Message& message) const; +}; +} +} + +#endif diff --git a/src/wintermute-heartbeat/ping_message.cpp b/src/wintermute-heartbeat/ping_message.cpp new file mode 100644 index 00000000..e10e31aa --- /dev/null +++ b/src/wintermute-heartbeat/ping_message.cpp @@ -0,0 +1,80 @@ +/* + Wintermute - A foundation for intelligent computing. + Copyright (c) 2010 - 2015 by Jacky Alcine + + Wintermute is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 3 of the License, or (at your option) any later version. + + Wintermute is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with Wintermute; if not, write to the + Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + */ + +#include "messages.hpp" +#include +#include +#include +#include +#include + +using Wintermute::Message; +using Wintermute::Plugin; +using Wintermute::Heartbeat::PingMessage; +using Wintermute::Util::join_string; +using std::for_each; + +PingMessage::PingMessage() : Message() +{ +} + +PingMessage::~PingMessage() +{ +} + +Message PingMessage::craft() +{ + Message msg; + Message::HashType msgData; + list pluginNames, moduleNames; + const auto pluginList = Plugin::all(); + const auto moduleList = Module::Pool::instance()->modules(); + + if (!pluginList.empty()) + { + for ( auto pluginName : pluginList ) + { + pluginNames.push_back(pluginName); + } + } + + if (!moduleList.empty()) + { + for_each ( begin(moduleList), end(moduleList), + [&moduleNames](const Wintermute::Module::Ptr& modulePtr) + { + auto des = modulePtr->designation(); + string name = des.domain() + "." + des.name(); + moduleNames.push_back(name); + }); + } + + const auto pluginNamesStr = join_string(pluginNames, ","); + const auto moduleNamesStr = join_string(moduleNames, ","); + + msgData.emplace("modules", moduleNamesStr); + msgData.emplace("plugins", pluginNamesStr); + msg.setPayload(msgData); + msg.setReceiver(Module::Designation( + WINTERMUTE_HEARTBEAT_MODULE_PONGER, + WINTERMUTE_HEARTBEAT_DOMAIN + )); + return msg; +} diff --git a/src/wintermute-heartbeat/ping_module.cpp b/src/wintermute-heartbeat/ping_module.cpp new file mode 100644 index 00000000..e25ce429 --- /dev/null +++ b/src/wintermute-heartbeat/ping_module.cpp @@ -0,0 +1,72 @@ +/* + Wintermute - A foundation for intelligent computing. + Copyright (c) 2010 - 2015 by Jacky Alcine + + Wintermute is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 3 of the License, or (at your option) any later version. + + Wintermute is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with Wintermute; if not, write to the + Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + */ + +#include +#include "ping_module.hpp" +#include "globals.hpp" +#include "messages.hpp" + +using Wintermute::Heartbeat::PingModule; +using Wintermute::Heartbeat::PongMessage; +using Wintermute::Module; +using Wintermute::Events::Timer; +using Wintermute::Events::Event; +using std::placeholders::_1; + +PingModule::PingModule() : + Module(Wintermute::Module::Designation( + WINTERMUTE_HEARTBEAT_MODULE_PINGER, + WINTERMUTE_HEARTBEAT_DOMAIN + )), + timer(make_shared()) +{ + auto bindFunc = bind(&PingModule::onTimerElasped, *this, _1); + timer->setInterval(WINTERMUTE_HEARTBEAT_INTERVAL); + timer->listenForEvent(W_EVENT_TIMEOUT, bindFunc); + + listenForEvent(WINTERMUTE_EVENT_MODULE_ENABLE, + [this](const Event::Ptr& event) + { + this->timer->start(); + }); +} + +PingModule::~PingModule() +{ +} + +void PingModule::onTimerElasped(const Event::Ptr& event) +{ + assert(event); + wdebug("Timer elasped; sending ping message."); + const auto msg = PingMessage::craft(); + sendMessage(msg); + wdebug("Sent ping message."); +} + +bool PingModule::sendMessage(const Message& message) +{ + return Module::sendMessage(message); +} + +bool PingModule::receiveMessage(const Message& message) const +{ + return Module::receiveMessage(message); +} diff --git a/src/wintermute-heartbeat/ping_module.hpp b/src/wintermute-heartbeat/ping_module.hpp new file mode 100644 index 00000000..84b5b26d --- /dev/null +++ b/src/wintermute-heartbeat/ping_module.hpp @@ -0,0 +1,51 @@ +/* + Wintermute - A foundation for intelligent computing. + Copyright (c) 2010 - 2015 by Jacky Alcine + + Wintermute is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 3 of the License, or (at your option) any later version. + + Wintermute is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with Wintermute; if not, write to the + Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + */ + +#ifndef WINTERMUTE_HEARTBEAT_PING_MODULE_HPP_ +# define WINTERMUTE_HEARTBEAT_PING_MODULE_HPP_ + +#include "globals.hpp" +#include +#include + +namespace Wintermute +{ +namespace Heartbeat +{ +class PingModule : public Wintermute::Module +{ +public: + W_DECL_PTR_TYPE(PingModule) + + Events::Timer::Ptr timer; + void onTimerElasped(const Events::Event::Ptr& ); + + public: + explicit PingModule(); + virtual ~PingModule(); + + protected: + virtual bool sendMessage(const Message& message) override; + virtual bool receiveMessage(const Message& message) const override; +}; +} /* end namespace Heartbeat */ +} /* end namespace Wintermute */ + +#endif diff --git a/src/wintermute-heartbeat/plugin.cc b/src/wintermute-heartbeat/plugin.cc new file mode 100644 index 00000000..5c0852d6 --- /dev/null +++ b/src/wintermute-heartbeat/plugin.cc @@ -0,0 +1,37 @@ +/* + Wintermute - A foundation for intelligent computing. + Copyright (c) 2010 - 2015 by Jacky Alcine + + Wintermute is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 3 of the License, or (at your option) any later version. + + Wintermute is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with Wintermute; if not, write to the + Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + */ + +#include +#include +#include +#include "plugin.hh" + +using Wintermute::Plugin; +using HeartbeatPluginPrivate = Wintermute::Heartbeat::PluginPrivate; +using std::to_string; + +HeartbeatPluginPrivate::PluginPrivate() : + module() +{ +} + +HeartbeatPluginPrivate::~PluginPrivate() +{ +} diff --git a/src/wintermute-heartbeat/plugin.cpp b/src/wintermute-heartbeat/plugin.cpp new file mode 100644 index 00000000..a2a17463 --- /dev/null +++ b/src/wintermute-heartbeat/plugin.cpp @@ -0,0 +1,121 @@ +/* + Wintermute - A foundation for intelligent computing. + Copyright (c) 2010 - 2015 by Jacky Alcine + + Wintermute is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 3 of the License, or (at your option) any later version. + + Wintermute is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with Wintermute; if not, write to the + Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + */ + +#include +#include +#include +#include "plugin.hh" +#include "plugin.hpp" +#include "module.hpp" + +using Wintermute::Plugin; +using Wintermute::Tunnel; +using Wintermute::Events::Event; +using Wintermute::Events::Listener; +using HeartbeatPlugin = Wintermute::Heartbeat::Plugin; +using HeartbeatModule = Wintermute::Heartbeat::Module; +using HeartbeatPluginPrivate = Wintermute::Heartbeat::PluginPrivate; + +HeartbeatPlugin::Plugin() : + Wintermute::Plugin(WINTERMUTE_HEARTBEAT_PLUGIN_NAME), + d_ptr(make_shared()) +{ + wdebug("Heartbeat plugin created."); +} + +HeartbeatPlugin::~Plugin() +{ + wdebug("Heartbeat plugin down for the count."); +} + +bool HeartbeatPlugin::startup() +{ + W_PRV(HeartbeatPlugin); + + auto enableModuleFunc = [this](const Event::Ptr& eventPtr) + { + assert(eventPtr); + auto module = make_shared(); + assert(module); + wdebug("Starting up the heartbeat module..."); + module->enable(); + + this->d_ptr->module = module; + wdebug("Heartbeat root module enabled."); + assert(this->d_ptr->module); + }; + + auto disableModuleFunc = [this](const Event::Ptr& eventPtr) + { + assert(eventPtr); + wdebug("Disabling the module.."); + auto module = this->d_ptr->module; + + if (module) + { + module->disable(); + module.reset(); + wdebug("Heartbeat root module disabled."); + } + else + { + wdebug("No heartbeat root module to disable."); + } + + this->d_ptr->module = nullptr; + assert(!this->d_ptr->module); + }; + + d->startUpModuleListener = Tunnel::instance()->listenForEvent ( + W_EVENT_TUNNEL_START, + enableModuleFunc, + Listener::FrequencyOnce + ); + + d->shutDownModuleListener = Tunnel::instance()->listenForEvent ( + W_EVENT_TUNNEL_STOP, + disableModuleFunc, + Listener::FrequencyOnce + ); + + return true; +} + +bool HeartbeatPlugin::shutdown() +{ + W_PRV(HeartbeatPlugin); + if (d->module) + { + wdebug("Killing module since Tunnel wasn't stopped yet."); + d->shutDownModuleListener->invoke(nullptr); + } + + Tunnel::instance()->removeEventListener(d->shutDownModuleListener); + Tunnel::instance()->removeEventListener(d->startUpModuleListener); + + return !(d->module); +} + +Plugin::PluginType HeartbeatPlugin::type() const +{ + return Wintermute::Plugin::PluginTypeSupport; +} + +W_DECL_PLUGIN(Wintermute::Heartbeat::Plugin, WINTERMUTE_HEARTBEAT_VERSION) diff --git a/src/wintermute-heartbeat/plugin.hh b/src/wintermute-heartbeat/plugin.hh new file mode 100644 index 00000000..f8c7cb82 --- /dev/null +++ b/src/wintermute-heartbeat/plugin.hh @@ -0,0 +1,43 @@ +/* + Wintermute - A foundation for intelligent computing. + Copyright (c) 2010 - 2015 by Jacky Alcine + + Wintermute is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 3 of the License, or (at your option) any later version. + + Wintermute is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with Wintermute; if not, write to the + Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + */ + +#ifndef WINTERMUTE_HEARTBEAT_PLUGIN_HH_ +# define WINTERMUTE_HEARTBEAT_PLUGIN_HH_ + +#include "globals.hpp" +#include "module.hpp" +#include "plugin.hpp" + +namespace Wintermute +{ +namespace Heartbeat { +class PluginPrivate +{ +public: + explicit PluginPrivate(); + ~PluginPrivate(); + Heartbeat::Module::Ptr module; + Events::Listener::Ptr startUpModuleListener; + Events::Listener::Ptr shutDownModuleListener; +}; +} /* end namespace Heartbeat */ +} /* end namespace Wintermute */ + +#endif diff --git a/src/wintermute-heartbeat/plugin.hpp b/src/wintermute-heartbeat/plugin.hpp new file mode 100644 index 00000000..0d281544 --- /dev/null +++ b/src/wintermute-heartbeat/plugin.hpp @@ -0,0 +1,46 @@ +/* + Wintermute - A foundation for intelligent computing. + Copyright (c) 2010 - 2015 by Jacky Alcine + + Wintermute is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 3 of the License, or (at your option) any later version. + + Wintermute is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with Wintermute; if not, write to the + Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + */ + +#ifndef WINTERMUTE_HEARTBEAT_PLUGIN_HPP_ +# define WINTERMUTE_HEARTBEAT_PLUGIN_HPP_ + +#include +#include "globals.hpp" + +namespace Wintermute +{ +namespace Heartbeat +{ +class PluginPrivate; +class Plugin final : public Wintermute::Plugin +{ + W_DEF_PRIVATE(Wintermute::Heartbeat::Plugin) + + public: + explicit Plugin(); + ~Plugin(); + bool startup() override; + bool shutdown() override; + Plugin::PluginType type() const override; +}; +} +} + +#endif diff --git a/src/wintermute-heartbeat/pong_message.cpp b/src/wintermute-heartbeat/pong_message.cpp new file mode 100644 index 00000000..41da9b20 --- /dev/null +++ b/src/wintermute-heartbeat/pong_message.cpp @@ -0,0 +1,73 @@ +/* + Wintermute - A foundation for intelligent computing. + Copyright (c) 2010 - 2015 by Jacky Alcine + + Wintermute is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 3 of the License, or (at your option) any later version. + + Wintermute is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with Wintermute; if not, write to the + Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + */ + +#include "messages.hpp" +#include "pong_module.hpp" +#include + +using std::dynamic_pointer_cast; +using Wintermute::Message; +using Wintermute::Heartbeat::Module; +using Wintermute::Heartbeat::PongMessage; + +// TODO: Have this require a module to pull the UUID from. +PongMessage::PongMessage() : + Message() +{ +} + +PongMessage::~PongMessage() +{ +} + +/* + * TODO: Craft the required UUID message for this instance. + */ +Message PongMessage::craft() +{ + Message msg; + auto hbDes = Module::Designation( + WINTERMUTE_HEARTBEAT_MODULE_PONGER, + WINTERMUTE_HEARTBEAT_DOMAIN + ); + + auto modulePtr = Module::Pool::instance()->find(hbDes); + assert(modulePtr); + + auto pongModulePtr = dynamic_pointer_cast(modulePtr); + assert(pongModulePtr); + + if (!modulePtr || !pongModulePtr) + { + return msg; + } + + const string uuid = pongModulePtr->uuid; + + Message::HashType msgData; + msgData.emplace("uuid", uuid); + msg.setPayload(msgData); + msg.setReceiver(Module::Designation( + WINTERMUTE_HEARTBEAT_MODULE_PINGER, + WINTERMUTE_HEARTBEAT_DOMAIN + )); + + return msg; +} diff --git a/src/wintermute-heartbeat/pong_module.cpp b/src/wintermute-heartbeat/pong_module.cpp new file mode 100644 index 00000000..9d9d6704 --- /dev/null +++ b/src/wintermute-heartbeat/pong_module.cpp @@ -0,0 +1,82 @@ +/* + Wintermute - A foundation for intelligent computing. + Copyright (c) 2010 - 2015 by Jacky Alcine + + Wintermute is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 3 of the License, or (at your option) any later version. + + Wintermute is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with Wintermute; if not, write to the + Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + */ + +#include +#include +#include "pong_module.hpp" +#include "messages.hpp" + +using Wintermute::Heartbeat::PongModule; +using Wintermute::Module; +using Wintermute::Util::generate_uuid; +using Wintermute::Events::Timer; +using Wintermute::Events::Event; +using std::placeholders::_1; +using Wintermute::Heartbeat::PongMessage; + +PongModule::PongModule() : + Module(Wintermute::Module::Designation( + WINTERMUTE_HEARTBEAT_MODULE_PONGER, + WINTERMUTE_HEARTBEAT_DOMAIN + )), + timer(make_shared()), + uuid() +{ + regenerateUuid(); + auto bindFunc = bind(&PongModule::onTimerElasped, *this, _1); + timer->setInterval(WINTERMUTE_HEARTBEAT_INTERVAL); + timer->listenForEvent(W_EVENT_TIMEOUT, bindFunc); + + listenForEvent(WINTERMUTE_EVENT_MODULE_ENABLE, + [this](const Event::Ptr& event) + { + this->timer->start(); + }); +} + +PongModule::~PongModule() +{ +} + +void PongModule::onTimerElasped(const Event::Ptr& event) +{ + assert(event); + wdebug("Timer elasped; sending pong message."); + const auto msg = PongMessage::craft(); + sendMessage(msg); + wdebug("Sent pong message."); +} + +void PongModule::regenerateUuid() +{ + uuid = generate_uuid(); + wdebug("The UUID of this instance is '" + uuid + "'."); + assert(!uuid.empty()); +} + +bool PongModule::sendMessage(const Message& message) +{ + return Module::sendMessage(message); +} + +bool PongModule::receiveMessage(const Message& message) const +{ + return Module::receiveMessage(message); +} diff --git a/src/wintermute-heartbeat/pong_module.hpp b/src/wintermute-heartbeat/pong_module.hpp new file mode 100644 index 00000000..f69d477a --- /dev/null +++ b/src/wintermute-heartbeat/pong_module.hpp @@ -0,0 +1,50 @@ +/* + Wintermute - A foundation for intelligent computing. + Copyright (c) 2010 - 2015 by Jacky Alcine + + Wintermute is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 3 of the License, or (at your option) any later version. + + Wintermute is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with Wintermute; if not, write to the + Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + */ + +#ifndef WINTERMUTE_HEARTBEAT_PONG_MODULE_HPP_ +# define WINTERMUTE_HEARTBEAT_PONG_MODULE_HPP_ + +#include "globals.hpp" +#include + +namespace Wintermute +{ +namespace Heartbeat +{ +class PongModule : public Wintermute::Module +{ + Events::Timer::Ptr timer; + void onTimerElasped(const Events::Event::Ptr& ); + + public: + W_DECL_PTR_TYPE(PongModule) + explicit PongModule(); + ~PongModule() override; + string uuid; + void regenerateUuid(); + + protected: + virtual bool sendMessage(const Message& message); + virtual bool receiveMessage(const Message& message) const; +}; +} +} + +#endif diff --git a/src/wintermute-heartbeat/test/CMakeLists.txt b/src/wintermute-heartbeat/test/CMakeLists.txt new file mode 100644 index 00000000..56b8b405 --- /dev/null +++ b/src/wintermute-heartbeat/test/CMakeLists.txt @@ -0,0 +1,32 @@ +# vim: set ts=2 sts=2 sw=2 fdm=indent +############################################################################### +# Author: Jacky Alciné +# +# Wintermute is free software; you can redistribute it and/or +# modify it under the terms of the GNU Library General Public +# License as published by the Free Software Foundation; either +# version 3 of the License, or (at your option) any later version. +# +# Wintermute is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Library General Public License for more details. +# +# You should have received a copy of the GNU Library General Public +# License along with Wintermute; if not, write to the +# Free Software Foundation, Inc., 59 Temple Place - Suite 330, +# Boston, MA 02111-1307, USA. +############################################################################### +CMAKE_MINIMUM_REQUIRED(VERSION 2.8.12) + +macro(_add_heartbeat_test name file) + WINTERMUTE_PLUGIN_ADD_TEST( + PREFIX unit-heartbeat + TARGET wintermute-heartbeat + NAME ${name} + HEADER ${CMAKE_CURRENT_SOURCE_DIR}/${file} + ) +endmacro() + +_add_heartbeat_test(message-transport message-transport.hh) +_add_heartbeat_test(message-structure message-structure.hh) diff --git a/src/wintermute-heartbeat/test/message-structure.hh b/src/wintermute-heartbeat/test/message-structure.hh new file mode 100644 index 00000000..fed4a92a --- /dev/null +++ b/src/wintermute-heartbeat/test/message-structure.hh @@ -0,0 +1,208 @@ +/* + * Wintermute is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * Wintermute is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with Wintermute; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include "test_suite.hpp" +#include "../messages.hpp" +#include "../pong_module.hpp" +#include "../ping_module.hpp" +#include "../globals.hpp" +#include +#include + +using std::vector; +using std::pair; +using Wintermute::Module; +using Wintermute::Util::split_string; +using Wintermute::Message; +using MessageMap = Wintermute::Util::Serializable::Map; +using Wintermute::Heartbeat::PongMessage; +using Wintermute::Heartbeat::PingMessage; +using Wintermute::Heartbeat::PongModule; +using Wintermute::Heartbeat::PingModule; + +class HeartbeatMessageStructureTestSuite : public CxxTest::TestSuite +{ +public: + Plugin::Ptr pluginPtr = nullptr; + + void setUp() + { + TS_ASSERT ( !pluginPtr ); + pluginPtr = wntrtest_load_plugin ( "wintermute-heartbeat" ); + } + + void tearDown() + { + pluginPtr = nullptr; + wntrtest_unload_plugin ( WINTERMUTE_HEARTBEAT_PLUGIN_NAME ); + TS_ASSERT ( pluginPtr == nullptr ); + } + + void cycleHeartbeatPlugin() + { + tearDown(); + setUp(); + } + + MessageMap fetchPongMessage() + { + Module::Ptr modulePtr = make_shared(); + + TSM_ASSERT_EQUALS ( + "There's no other PongModule loaded.", + Module::Pool::instance()->find(modulePtr->designation()), + nullptr + ); + + modulePtr->enable(); + Message msg = PongMessage::craft(); + modulePtr->disable(); + modulePtr.reset(); + return (MessageMap) (msg.payload()); + } + + pair fetchPongMessageAndUuid() + { + auto modulePtr = make_shared(); + + TSM_ASSERT_EQUALS ( + "There's no other PongModule loaded.", + Module::Pool::instance()->find(modulePtr->designation()), + nullptr + ); + + const string uuid = modulePtr->uuid; + modulePtr->enable(); + Message msg = PongMessage::craft(); + modulePtr->disable(); + modulePtr.reset(); + return make_pair(uuid, (MessageMap) msg.payload()); + } + + MessageMap fetchPingMessage() + { + Message msg = PingMessage::craft(); + return (MessageMap) (msg.payload()); + } + + void checkKeysInMap(const vector& keys, const MessageMap& payload) + { + for ( const string expectedKey : keys ) + { + TSM_ASSERT ( + "The key '" + expectedKey + "' exists in the message payload.", + payload.count(expectedKey) == 1 + ); + } + } + + void testNoop() { } + + void testCheckPingMessagePluginValue() + { + MessageMap msg = fetchPingMessage(); + const vector expectedPlugins = { "wintermute-heartbeat" }; + const list obtainedPlugins = + split_string(msg.find("plugins")->second, std::regex(",")); + + TSM_ASSERT ( + "The 'plugins' key has the currently loaded plugins' names.", + std::equal ( + expectedPlugins.cbegin(), expectedPlugins.cend(), + obtainedPlugins.begin() + ) + ); + } + + void testCheckPingMessageModulesValue() + { + auto sampleModule = make_shared(); + sampleModule->enable(); + + list expectedModules, obtainedModules; + + const auto moduleList = Module::Pool::instance()->modules(); + for_each(begin(moduleList), end(moduleList), [&](const Module::Ptr& module) + { + auto des = module->designation(); + string craftedName = des.domain() + "." + des.name(); + expectedModules.push_back(craftedName); + }); + + MessageMap msg = fetchPingMessage(); + const string moduleStr = msg.find("modules")->second; + wdebug(moduleStr); + if (!moduleStr.empty()) + { + obtainedModules = split_string(moduleStr, std::regex(",")); + } + + TSM_ASSERT_EQUALS ( + "The 'modules' key has the currently loaded module names.", + expectedModules, + obtainedModules + ); + } + + void testCheckPongMessageUuid() + { + auto uuidAndMsg = fetchPongMessageAndUuid(); + auto msg = uuidAndMsg.second; + + const string obtainedUuid = msg.find("uuid")->second; + const string expectedUuid = uuidAndMsg.first; + + TSM_ASSERT_EQUALS ( + "The UUID value is as expected.", + obtainedUuid, + expectedUuid + ); + + TSM_ASSERT ( + "The UUID value is not empty.", + !obtainedUuid.empty() + ); + } + + void testCheckPongMessageUuidChanges() + { + /** + * @NOTE: This test is a bit funny since we make a new `PongModule` each time. + */ + MessageMap msg = fetchPongMessage(); + const string obtainedUuid = msg.find("uuid")->second; + + TSM_ASSERT_THROWS_NOTHING ( + "Cycle the Heartbeat plugin.", + cycleHeartbeatPlugin() + ); + + MessageMap expectedMsg = fetchPongMessage(); + const string expectedUuid = expectedMsg.find("uuid")->second; + + TSM_ASSERT_DIFFERS ( + "The Heartbeat plugin generates different UUIDs.", + obtainedUuid, + expectedUuid + ); + + TSM_ASSERT ( + "The UUID value is not empty.", + !obtainedUuid.empty() + ); + } +}; diff --git a/src/wintermute-heartbeat/test/message-transport.hh b/src/wintermute-heartbeat/test/message-transport.hh new file mode 100644 index 00000000..e596fbe8 --- /dev/null +++ b/src/wintermute-heartbeat/test/message-transport.hh @@ -0,0 +1,162 @@ +/* + * Wintermute is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * Wintermute is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with Wintermute; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include "test_suite.hpp" +#include "../globals.hpp" +#include "../messages.hpp" +#include +#include + +using Wintermute::Events::Timer; +using Wintermute::Events::Event; +using Wintermute::Events::Loop; +using Wintermute::Tunnel; +using Wintermute::Message; +using Wintermute::Heartbeat::PongMessage; +using Wintermute::Heartbeat::PingMessage; +using std::placeholders::_1; + +class HeartbeatMessageDeliveryTestSuite : public CxxTest::TestSuite +{ +public: + bool passed = false; + Plugin::Ptr pluginPtr; + const string pongerName = WINTERMUTE_HEARTBEAT_MODULE_PONGER; + const string pingerName = WINTERMUTE_HEARTBEAT_MODULE_PINGER; + + void setUp() + { + passed = false; + + Wintermute::Module::Pool::instance(); + Tunnel::instance(); + + TS_ASSERT ( !pluginPtr ); + pluginPtr = wntrtest_load_plugin ( "wintermute-heartbeat" ); + } + + void tearDown() + { + passed = false; + + wntrtest_unload_plugin ( WINTERMUTE_HEARTBEAT_PLUGIN_NAME ); + pluginPtr = nullptr; + TS_ASSERT ( pluginPtr == nullptr ); + } + + bool checkStructureOfPongMessage(const Message& msg) + { + const auto sender = msg.sender(); + const auto receiver = msg.receiver(); + + TSM_ASSERT_EQUALS ( + "PongMessage was sent from ponger module.", + sender.name(), + pongerName + ); + + TSM_ASSERT_EQUALS ( + "PongMessage was sent to pinger module.", + receiver.name(), + pingerName + ); + + return true; + } + + bool checkStructureOfPingMessage(const Message& msg) + { + const auto sender = msg.sender(); + const auto receiver = msg.receiver(); + + TSM_ASSERT_EQUALS ( + "PingMessage was sent from pinger module.", + sender.name(), + pingerName + ); + + TSM_ASSERT_EQUALS ( + "PingMessage was sent to ponger module.", + receiver.name(), + pongerName + ); + + return true; + } + + void checkForMessage(const Tunnel::MessageEvent::Direction &messageDirection, + std::function messageCheckFunc) + { + Tunnel::start(); + auto loop = Loop::primary(); + auto checkTunnelFunc = [this, &loop, &messageCheckFunc, &messageDirection](const Event::Ptr& event) + { + auto msgPtr = dynamic_pointer_cast(event); + assert(msgPtr); + + if (msgPtr->direction == messageDirection) + { + this->passed = messageCheckFunc(msgPtr->message); + + if (this->passed) + { + loop->stop(); + } + } + }; + + auto timerExpiredFunc = [&loop](const Event::Ptr& timerEvent) + { + assert(timerEvent); + TS_TRACE("Timeout reached; breaking out..."); + loop->stop(); + }; + + Timer::Ptr timerKill = make_shared(loop); + auto listener = + Tunnel::instance()->listenForEvent(W_EVENT_TUNNEL_MESSAGE, checkTunnelFunc); + timerKill->setInterval(KILL_TEST_TIMEOUT); + timerKill->listenForEvent(W_EVENT_TIMEOUT, timerExpiredFunc); + timerKill->start(); + + loop->run(); + + if (!passed) + { + TS_FAIL("No message was sent by any of the heartbeat modules."); + } + + Tunnel::instance()->removeEventListener(listener); + Tunnel::stop(); + } + + void testPongMessageTransport() + { + auto sampleDaemonModulePtr = make_shared(Wintermute::Module::Designation("daemon","in.wintermute")); + sampleDaemonModulePtr->enable(); + auto checkMsgFunc = std::bind(&HeartbeatMessageDeliveryTestSuite::checkStructureOfPongMessage, *this, _1); + checkForMessage(Tunnel::MessageEvent::DirectionOutgoing, checkMsgFunc); + sampleDaemonModulePtr->disable(); + sampleDaemonModulePtr = nullptr; + } + + void testPingMessageTransport() + { + auto checkMsgFunc = std::bind(&HeartbeatMessageDeliveryTestSuite::checkStructureOfPingMessage, *this, _1); + checkForMessage(Tunnel::MessageEvent::DirectionOutgoing, checkMsgFunc); + } +}; diff --git a/src/wintermute-transport-zeromq/CMakeLists.txt b/src/wintermute-transport-zeromq/CMakeLists.txt new file mode 100644 index 00000000..964f5e10 --- /dev/null +++ b/src/wintermute-transport-zeromq/CMakeLists.txt @@ -0,0 +1,59 @@ +# vim: set ts=2 sts=2 sw=2 fdm=indent +############################################################################### +# Author: Jacky Alciné +# +# Wintermute is free software; you can redistribute it and/or +# modify it under the terms of the GNU Library General Public +# License as published by the Free Software Foundation; either +# version 3 of the License, or (at your option) any later version. +# +# Wintermute is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Library General Public License for more details. +# +# You should have received a copy of the GNU Library General Public +# License along with Wintermute; if not, write to the +# Free Software Foundation, Inc., 59 Temple Place - Suite 330, +# Boston, MA 02111-1307, USA. +############################################################################### +CMAKE_MINIMUM_REQUIRED(VERSION 2.8.12) +PROJECT(WintermtueTransportZeroMQ CXX) + +include(WintermutePlugin) +FIND_PACKAGE(ZeroMQCXX REQUIRED) + +SET(WINTERMUTE_ZMQ_VERSION 0.0.1-dev) +SET(WINTERMUTE_ZMQ_IPC_ADDRESS ${CMAKE_CURRENT_BINARY_DIR}/wintermute.sock) + +SET(_srcs + plugin.cpp + dispatcher.cpp + receiver.cpp + ) + +CONFIGURE_FILE(globals.hpp.in + ${CMAKE_CURRENT_SOURCE_DIR}/globals.hpp + @ONLY + ) + +ADD_LIBRARY(transport-zmq + SHARED ${_srcs} + ) + +TARGET_LINK_LIBRARIES(transport-zmq + ${ZEROMQ_LIBS} zmqpp zmq + ) + +TARGET_INCLUDE_DIRECTORIES(transport-zmq + PUBLIC ${ZEROMQ_INCLUDE_DIRS} +) + +WINTERMUTE_PLUGIN_DECLARE( + NAME transport-zmq + TARGET transport-zmq + ) + +WINTERMUTE_PLUGIN_VALIDATE(TARGET transport-zmq) + +ADD_SUBDIRECTORY(test) diff --git a/src/wintermute-transport-zeromq/README.markdown b/src/wintermute-transport-zeromq/README.markdown new file mode 100644 index 00000000..083a258a --- /dev/null +++ b/src/wintermute-transport-zeromq/README.markdown @@ -0,0 +1,10 @@ +# [ZeroMQ][] Support for Wintermute +This library aims to extend Wintermute's ability to communicate with other +processes by making use of [ZeroMQ][], specifically in a publish-subscribe +fashion. There's a core socket that Wintermute communicates over in a +write-only fashion dubbed `wintermute.sock`. Each sub process has their own +dedicated input socket, dubbed `wintermute-${PID}.sock`. There's also a general +purpose TCP port used by Wintermute to allow for external communication with +all sub processes. + +[zeromq]: https://github.com/zeromq/zmqpp diff --git a/src/wintermute-transport-zeromq/dispatcher.cpp b/src/wintermute-transport-zeromq/dispatcher.cpp new file mode 100644 index 00000000..52f464b3 --- /dev/null +++ b/src/wintermute-transport-zeromq/dispatcher.cpp @@ -0,0 +1,100 @@ +/* + * Wintermute is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * Wintermute is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with Wintermute; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include +#include +#include +#include "globals.hpp" +#include "dispatcher.hpp" + +using std::to_string; +using std::for_each; +using Wintermute::ZMQDispatcher; + +ZMQDispatcher::ZMQDispatcher(const SharedPtr& theContext) : + Dispatcher(WINTERMUTE_ZMQ_TUNNEL_NAME), + context(theContext), + socketPtr(nullptr) +{ + wdebug("Building the ZeroMQ dispatcher..."); + assert(theContext); + wdebug("Built the ZeroMQ dispatcher."); +} + +void ZMQDispatcher::bindTo(const string& bindStr) +{ + wdebug("Binding to '" + bindStr + "'..."); + socketPtr->bind( bindStr ); + wdebug("Binded to '" + bindStr + "' via ZeroMQ."); +} + +ZMQDispatcher::~ZMQDispatcher() +{ + wdebug("Destroyed this ZeroMQ dispatcher."); +} + +bool ZMQDispatcher::send(const Message& message) +{ + assert(!message.isEmpty()); + assert(socketPtr); + + if (message.isEmpty()) + { + wwarn("Attempt to send empty message thwarted."); + return false; + } + + const string msgStr = static_cast(message); + assert(!msgStr.empty()); + + wdebug("Sending message " + msgStr + " over ZeroMQ..."); + const bool socketSentMessage = socketPtr->send(msgStr, + zmqpp::socket::dont_wait); + wdebug("Was message sent over ZeroMQ successfully? " + + to_string(socketSentMessage)); + + return socketSentMessage; +} + +list ZMQDispatcher::clients() const +{ + list clientList; + clientList.push_back(string("tcp://0.0.0.0:") + + to_string(WINTERMUTE_ZMQ_PORT)); + clientList.push_back(string("ipc://") + + WINTERMUTE_ZMQ_IPC_ADDRESS); + + return clientList; +} + +void ZMQDispatcher::start() +{ + auto socketType = zmqpp::socket_type::publish; + socketPtr = make_shared(*context, socketType); + assert(*socketPtr); + list clientList = clients(); + auto memFunc = std::mem_fn(&ZMQDispatcher::bindTo); + auto bindFunc = std::bind(memFunc, this, std::placeholders::_1); + for_each(clientList.begin(), clientList.end(), bindFunc); +} + +void ZMQDispatcher::stop() +{ + socketPtr->close(); + socketPtr.reset(); + socketPtr = nullptr; +} diff --git a/src/wintermute-transport-zeromq/dispatcher.hpp b/src/wintermute-transport-zeromq/dispatcher.hpp new file mode 100644 index 00000000..71555881 --- /dev/null +++ b/src/wintermute-transport-zeromq/dispatcher.hpp @@ -0,0 +1,45 @@ +/* + * Wintermute is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * Wintermute is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with Wintermute; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef WINTERMUTE_ZMQ_DISPATCHER_HPP_ +# define WINTERMUTE_ZMQ_DISPATCHER_HPP_ + +#include +#include +#include "globals.hpp" + +namespace Wintermute +{ +class ZMQDispatcher : public Tunnel::Dispatcher +{ +private: + SharedPtr context; + SharedPtr socketPtr; + void bindTo(const string& bindStr); + list clients() const; + +public: + W_DECL_PTR_TYPE(ZMQDispatcher) + explicit ZMQDispatcher(const SharedPtr& context); + virtual ~ZMQDispatcher(); + virtual bool send(const Message& message) final; + virtual void start(); + virtual void stop(); +}; +} + +#endif diff --git a/src/wintermutecore/globals.hh b/src/wintermute-transport-zeromq/globals.hpp.in similarity index 66% rename from src/wintermutecore/globals.hh rename to src/wintermute-transport-zeromq/globals.hpp.in index 1d3b6c22..74ee761c 100644 --- a/src/wintermutecore/globals.hh +++ b/src/wintermute-transport-zeromq/globals.hpp.in @@ -16,14 +16,16 @@ * Boston, MA 02111-1307, USA. */ -#ifndef WINTERMUTE_CORE_GLOBALS_HH_ -# define WINTERMUTE_CORE_GLOBALS_HH_ +#ifndef WINTERMUTE_ZMQ_GLOBALS_HPP +#define WINTERMUTE_ZMQ_GLOBALS_HPP -#define W_CHECK_UV(r, call) \ - if (r) { \ - printf("\n[%s] E: %s - %s\n", call, uv_err_name(r), uv_strerror(r)); \ - abort();\ - } \ +#include +#define WINTERMUTE_ZMQ_PLUGIN_NAME "ZMQ" +#define WINTERMUTE_ZMQ_TUNNEL_NAME "zmq" +#define WINTERMUTE_ZMQ_VERSION "@WINTERMUTE_ZMQ_VERSION@" +#define WINTERMUTE_ZMQ_PORT 9931 +#define WINTERMUTE_ZMQ_IPC_ADDRESS "@WINTERMUTE_ZMQ_IPC_ADDRESS@" +#define WINTERMUTE_ZMQ_FILTER "{" #endif diff --git a/src/wintermute-transport-zeromq/plugin.cpp b/src/wintermute-transport-zeromq/plugin.cpp new file mode 100644 index 00000000..486cbe92 --- /dev/null +++ b/src/wintermute-transport-zeromq/plugin.cpp @@ -0,0 +1,83 @@ +/* + * Wintermute is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * Wintermute is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with Wintermute; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include +#include "globals.hpp" +#include "plugin.hpp" +#include "dispatcher.hpp" +#include "receiver.hpp" + +using Wintermute::Plugin; +using Wintermute::ZMQPlugin; +using Wintermute::ZMQDispatcher; +using Wintermute::ZMQReceiver; + +ZMQPlugin::ZMQPlugin() : + Plugin(WINTERMUTE_ZMQ_PLUGIN_NAME), + context(nullptr), + receiver(nullptr), + dispatcher(nullptr) +{ + assert(!receiver); + assert(!dispatcher); + + context = make_shared(); + assert(context); +} + +ZMQPlugin::~ZMQPlugin() +{ + wdebug("Killed the ZeroMQ plugin."); +} + +bool ZMQPlugin::startup() +{ + wdebug("ZeroMQ plugin starting..."); + if (!receiver) + { + ZMQReceiver* r = new ZMQReceiver(context); + receiver.reset(r); + assert(receiver.unique()); + } + + if (!dispatcher) + { + dispatcher = make_shared(context); + assert(dispatcher); + } + + assert(Tunnel::registerReceiver(receiver)); + assert(Tunnel::registerDispatcher(dispatcher)); + wdebug("ZeroMQ plugin started."); + return true; +} + +bool ZMQPlugin::shutdown() +{ + wdebug("ZeroMQ plugin shutting down..."); + assert(Tunnel::unregisterDispatcher(dispatcher)); + assert(Tunnel::unregisterReceiver(receiver)); + receiver.reset(); + dispatcher.reset(); + wdebug("ZeroMQ plugin shut down."); + return true; +} + +Plugin::PluginType ZMQPlugin::type() const +{ + return Plugin::PluginTypeSupport; +} diff --git a/src/wintermute-transport-zeromq/plugin.hpp b/src/wintermute-transport-zeromq/plugin.hpp new file mode 100644 index 00000000..1a2565df --- /dev/null +++ b/src/wintermute-transport-zeromq/plugin.hpp @@ -0,0 +1,40 @@ +/* + * Wintermute is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * Wintermute is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with Wintermute; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include +#include +#include "dispatcher.hpp" +#include "receiver.hpp" + +namespace Wintermute +{ +class ZMQPlugin : public Plugin +{ +public: + explicit ZMQPlugin(); + virtual ~ZMQPlugin(); + virtual bool startup(); + virtual bool shutdown(); + virtual Plugin::PluginType type() const; + + SharedPtr context; + ZMQReceiver::Ptr receiver; + ZMQDispatcher::Ptr dispatcher; +}; +} + +W_DECL_PLUGIN(Wintermute::ZMQPlugin, WINTERMUTE_VERSION) diff --git a/src/wintermute-transport-zeromq/receiver.cpp b/src/wintermute-transport-zeromq/receiver.cpp new file mode 100644 index 00000000..24260a66 --- /dev/null +++ b/src/wintermute-transport-zeromq/receiver.cpp @@ -0,0 +1,244 @@ +/* + Wintermute - A foundation for intelligent computing. + Copyright (c) 2010 - 2015 by Jacky Alcine + + Wintermute is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 3 of the License, or (at your option) any later version. + + Wintermute is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with Wintermute; if not, write to the + Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + */ + +#include +#include +#include +#include +#include +#include "globals.hpp" +#include "receiver.hpp" + +using Wintermute::Message; +using Wintermute::ZMQReceiver; +using Wintermute::Util::Serializable; +using Wintermute::Events::Event; +using Wintermute::Events::Poller; +using Wintermute::Events::PollEvent; +using Wintermute::Tunnel; +using std::to_string; +using std::for_each; +using std::dynamic_pointer_cast; +using std::placeholders::_1; + +ZMQReceiver::ZMQReceiver(const SharedPtr& theContext) : + Receiver(WINTERMUTE_ZMQ_TUNNEL_NAME), + context(theContext), + socketPtr(nullptr), + pollerPtr(nullptr), + listenerReceived(nullptr) +{ + wdebug("ZMQReceiver building..."); + if (!context) + { + throw std::invalid_argument( + "ZMQReceiver: Need a pointer to a ZeroMQ context."); + } + wdebug("ZMQReceiver built."); +} + +ZMQReceiver::~ZMQReceiver() +{ + //stop(); + wdebug("Destroyed this ZeroMQ receiver."); +} + +void ZMQReceiver::start() +{ + connectToClients(); + attachPoller(); +} + +void ZMQReceiver::stop() +{ + detachPoller(); + disconnectFromClients(); +} + +list ZMQReceiver::clients() +{ + list clientList; + clientList.push_back("tcp://0.0.0.0:" + to_string(WINTERMUTE_ZMQ_PORT)); + clientList.push_back("ipc://" WINTERMUTE_ZMQ_IPC_ADDRESS); + + return clientList; +} + +void ZMQReceiver::connectToClients() +{ + assert(this); + auto socketType = zmqpp::socket_type::subscribe; + socketPtr = make_shared(*context, socketType); + assert(socketPtr); + + subscribeTo ( WINTERMUTE_ZMQ_FILTER ); + list clientList = clients(); + auto connectFunc = std::mem_fn(&ZMQReceiver::connectTo); + auto bindFunc = std::bind(connectFunc, this, _1); + for_each(clientList.begin(), clientList.end(), bindFunc); +} + +void ZMQReceiver::disconnectFromClients() +{ + list clientList = clients(); + auto disconnectFunc = std::mem_fn(&ZMQReceiver::disconnectFrom); + auto bindFunc = std::bind(disconnectFunc, this, _1); + for_each(clientList.begin(), clientList.end(), bindFunc); +} + +void ZMQReceiver::attachPoller() +{ + assert(socketPtr); + + int fd = 0; + socketPtr->get(zmqpp::socket_option::file_descriptor, fd); + assert(fd <= std::numeric_limits::max()); + + wdebug("Setting up poller for ZeroMQ's file descriptor " + to_string(fd) + + " on read-only mode..."); + + pollerPtr = make_shared(fd, Poller::PollReadable); + assert(pollerPtr); + attachPollerToSocket(); + + assert ( pollerPtr->start() ); +} + +void ZMQReceiver::attachPollerToSocket() +{ + Events::Listener::Callback cb = + [this](const Event::Ptr& event) -> void + { + assert(event); + + PollEvent::Ptr pollEvent; + bool wasMessageFound = false; + pollEvent = std::dynamic_pointer_cast(event); + + if (!pollEvent) + { + wdebug("Not a Poll event."); + return; // We were not meant to deal with this. + } + + Message fullMsg; + zmqpp::message zmqMsg; + auto socketPtrRef = this->socketPtr; + wdebug("Obtained a poll request from the ZeroMQ socket."); + + try + { + wasMessageFound = socketPtrRef->receive(zmqMsg, true); + } + catch (zmqpp::zmq_internal_exception& e) + { + wdebug(string("Couldn't poll from ZeroMQ socket: ") + e.what()); + } + + if (wasMessageFound) + { + wdebug("This message has " + to_string(zmqMsg.parts()) + " parts."); + // TODO: Handle import of message parts. + const string msgStr = zmqMsg.get(0); + wdebug("Received over ZeroMQ: " + msgStr); + fullMsg = msgStr; + handleMessage(fullMsg); + } + else + { + wdebug("Socket polled, but no message found for ZMQReceiver."); + } + }; + + listenerReceived = pollerPtr->listenForEvent(W_EVENT_POLLED, cb); + assert ( listenerReceived ); +} + +void ZMQReceiver::detachPoller() +{ + if (pollerPtr) + { + pollerPtr->stop(); + pollerPtr.reset(); + } + + if (listenerReceived) + { + listenerReceived.reset(); + } +} + +void ZMQReceiver::subscribeTo(const string& subscriptionStr) +{ + if (socketPtr) + { + socketPtr->set( zmqpp::socket_option::subscribe, subscriptionStr ); + wdebug("Subscribed to messages with '" + subscriptionStr + "' filter."); + } + else + { + wdebug("Can't subscribe to '" + + subscriptionStr + "' since socket is invalid."); + } +} + +void ZMQReceiver::connectTo(const string& connectionStr) +{ + if (socketPtr) + { + wdebug("Connecting to '" + connectionStr + "'..."); + socketPtr->connect( connectionStr ); + wdebug("Connected to '" + connectionStr + "' via ZeroMQ."); + } + else + { + wdebug("Can't connect to '" + + connectionStr + "' since socket is invalid."); + } +} + +void ZMQReceiver::disconnectFrom(const string& connectionStr) +{ + if (socketPtr) + { + wdebug("Disconnecting from '" + connectionStr + "'..."); + socketPtr->disconnect( connectionStr ); + wdebug("Disconnected from '" + connectionStr + "' via ZeroMQ."); + } + else + { + wdebug("Can't disconnect from '" + + connectionStr + "' since socket is invalid."); + } +} + +void ZMQReceiver::unsubscribeFrom(const string& subscriptionStr) +{ + if (socketPtr) + { + socketPtr->set( zmqpp::socket_option::unsubscribe, subscriptionStr ); + wdebug("Unsubscribed from messages with '" + subscriptionStr + "' filter."); + } + else + { + wdebug("Can't unsubscribe from '" + + subscriptionStr + "' since socket is invalid."); + } +} diff --git a/src/wintermute-transport-zeromq/receiver.hpp b/src/wintermute-transport-zeromq/receiver.hpp new file mode 100644 index 00000000..ed944d70 --- /dev/null +++ b/src/wintermute-transport-zeromq/receiver.hpp @@ -0,0 +1,55 @@ +/* + * Wintermute is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * Wintermute is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with Wintermute; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef WINTERMUTE_ZMQ_RECEIVER_HPP_ +# define WINTERMUTE_ZMQ_RECEIVER_HPP_ + +#include +#include +#include + +namespace Wintermute +{ +class ZMQReceiver : public Tunnel::Receiver +{ +private: + SharedPtr context; + SharedPtr socketPtr; + Events::Poller::Ptr pollerPtr; + Events::Listener::Ptr listenerReceived; + + list clients(); + void connectToClients(); + void disconnectFromClients(); + void attachPoller(); + void attachPollerToSocket(); + void detachPoller(); + void subscribeTo(const string& subscriptionStr); + void unsubscribeFrom(const string& subscriptionStr); + void connectTo(const string& connectionStr); + void disconnectFrom(const string& connectionStr); + +public: + W_DECL_PTR_TYPE(ZMQReceiver) + explicit ZMQReceiver(const SharedPtr& context); + virtual ~ZMQReceiver(); + virtual void start(); + virtual void stop(); +}; +} + +#endif diff --git a/src/wintermute-transport-zeromq/test/CMakeLists.txt b/src/wintermute-transport-zeromq/test/CMakeLists.txt new file mode 100644 index 00000000..07a1fc4b --- /dev/null +++ b/src/wintermute-transport-zeromq/test/CMakeLists.txt @@ -0,0 +1,27 @@ +# vim: set ts=2 sts=2 sw=2 fdm=indent +############################################################################### +# Author: Jacky Alciné +# +# Wintermute is free software; you can redistribute it and/or +# modify it under the terms of the GNU Library General Public +# License as published by the Free Software Foundation; either +# version 3 of the License, or (at your option) any later version. +# +# Wintermute is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Library General Public License for more details. +# +# You should have received a copy of the GNU Library General Public +# License along with Wintermute; if not, write to the +# Free Software Foundation, Inc., 59 Temple Place - Suite 330, +# Boston, MA 02111-1307, USA. +############################################################################### +CMAKE_MINIMUM_REQUIRED(VERSION 2.8.12) + +WINTERMUTE_PLUGIN_ADD_TEST( + PREFIX unit-zmq + TARGET transport-zmq + NAME tunnel + HEADER ${CMAKE_CURRENT_SOURCE_DIR}/tunnel.hh + ) diff --git a/src/wintermute-transport-zeromq/test/tunnel.hh b/src/wintermute-transport-zeromq/test/tunnel.hh new file mode 100644 index 00000000..ef60a4ce --- /dev/null +++ b/src/wintermute-transport-zeromq/test/tunnel.hh @@ -0,0 +1,206 @@ +/* + * Wintermute is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * Wintermute is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with Wintermute; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include "test_suite.hpp" +#include "../globals.hpp" +#include "../plugin.hpp" +#include "../receiver.hpp" +#include +#include +#include + +using Wintermute::Logging; +using Wintermute::Plugin; +using Wintermute::Tunnel; +using Wintermute::Plugin; +using std::dynamic_pointer_cast; + +#define KILL_COUNT 40 + +class TestZMQReceiver : public Wintermute::ZMQReceiver +{ + public: + explicit TestZMQReceiver(const SharedPtr& aContext) : + ZMQReceiver(aContext), + message(Wintermute::Message()) + { + wasCalled = true; + wdebug("Spun up a sample ZeroMQ receiver."); + } + + ~TestZMQReceiver() override + { + wdebug("Spun down a sample ZeroMQ receiver."); + } + + Wintermute::Message message; + bool wasCalled = false; +}; + +class ZMQTunnelTestSuite : public CxxTest::TestSuite +{ +public: + Plugin::Ptr pluginPtr; + void setUp() + { + TS_ASSERT ( !Plugin::hasPlugin(WINTERMUTE_ZMQ_PLUGIN_NAME) ); + const string libDir(TEST_BASE_DIR "/../lib"); + setenv(WINTERMUTE_ENV_PLUGIN_PATH, libDir.c_str() , 1); + pluginPtr = Plugin::find("transport-zmq"); + TS_ASSERT ( pluginPtr ); + werror ( "What? " + to_string(pluginPtr.unique())); + TS_ASSERT ( Plugin::hasPlugin(WINTERMUTE_ZMQ_PLUGIN_NAME) ); + TS_ASSERT ( Tunnel::knowsOfDispatcher(WINTERMUTE_ZMQ_TUNNEL_NAME) ); + TS_ASSERT ( Tunnel::knowsOfReceiver(WINTERMUTE_ZMQ_TUNNEL_NAME) ); + unsetenv(WINTERMUTE_ENV_PLUGIN_PATH); + } + + void tearDown() + { + TSM_ASSERT ( "Freed plugin", + Plugin::release(WINTERMUTE_ZMQ_PLUGIN_NAME) ); + TSM_ASSERT ( "Removed plugin.", + !Plugin::hasPlugin(WINTERMUTE_ZMQ_PLUGIN_NAME) ); + + TSM_ASSERT ( "Removed dispatcher.", + !Tunnel::knowsOfDispatcher(WINTERMUTE_ZMQ_TUNNEL_NAME) ); + TSM_ASSERT ( "Removed receiver.", + !Tunnel::knowsOfReceiver(WINTERMUTE_ZMQ_TUNNEL_NAME) ); + } + + void testSendOutMessage() + { + bool passed = false; + Loop::Ptr loop = Loop::primary(); + Wintermute::Message msg = craftRandomMessage(); + + SharedPtr zmqPluginPtr = + dynamic_pointer_cast(pluginPtr); + + SharedPtr sampleTestZMQReceiver = + make_shared(zmqPluginPtr->context); + + TSM_ASSERT ( + "Sample receiver built properly.", + sampleTestZMQReceiver + ); + + SharedPtr theZMQDispatcher = + dynamic_pointer_cast( + zmqPluginPtr->dispatcher->shared_from_this() + ); + + TSM_ASSERT ( + "Remove the default receiver.", + Tunnel::unregisterReceiver(WINTERMUTE_ZMQ_TUNNEL_NAME) + ); + + TSM_ASSERT ( + "No receiver exists from ZeroMQ.", + !Tunnel::knowsOfReceiver(WINTERMUTE_ZMQ_TUNNEL_NAME) + ); + + TSM_ASSERT ( + "Add in our fixture version of the receiver.", + Tunnel::registerReceiver(sampleTestZMQReceiver) + ); + + TSM_ASSERT ( + "Our test receiver stubs in place for ZeroMQ.", + Tunnel::knowsOfReceiver(WINTERMUTE_ZMQ_TUNNEL_NAME) + ); + + TSM_ASSERT ( + "There's a dispatcher in place for ZeroMQ.", + Tunnel::knowsOfDispatcher(WINTERMUTE_ZMQ_TUNNEL_NAME) + ); + + TSM_ASSERT ( + "The dispatcher's in good shape.", + theZMQDispatcher + ); + + Tunnel::instance()->listenForEvent(W_EVENT_TUNNEL_MESSAGE, + [&passed, &msg, &loop](const Wintermute::Events::Event::Ptr& event) + { + Tunnel::MessageEvent::Ptr msgPtr = + std::dynamic_pointer_cast(event); + + assert(msgPtr); + + if (msgPtr->direction == Tunnel::MessageEvent::DirectionIncoming) + { + wdebug("[test] Obtained a MessageEvent."); + wdebug("[test] Got " + static_cast(msgPtr->message)); + TSM_ASSERT_EQUALS ( + "Confirm that message sent = message received via ZeroMQ.", + msgPtr->message, + msg + ); + passed = true; + loop->stop(); + } + }); + + TSM_ASSERT_RELATION( + "Ensures that the Tunnel has a listener for incoming messages.", + std::greater, + Tunnel::instance()->eventListeners(W_EVENT_TUNNEL_MESSAGE).size(), + 1 + ); + + Timer::Ptr timerKill = make_shared(Loop::primary()); + Timer::Ptr timerSend = make_shared(Loop::primary()); + + timerKill->listenForEvent(W_EVENT_TIMEOUT, + [&loop](const Event::Ptr& timerEvent) + { + assert(timerEvent); + TS_TRACE("Timeout reached; breaking out..."); + loop->stop(); + }); + + timerSend->listenForEvent(W_EVENT_TIMEOUT, + [&msg, &passed, &loop](const Event::Ptr& timerEvent) + { + assert(timerEvent); + TSM_ASSERT_THROWS_NOTHING ( + "Sends a message over the wire using ZeroMQ.", + Tunnel::sendMessage ( msg ) + ); + + if (passed) + { + loop->stop(); + } + }); + + timerSend->setInterval(KILL_TEST_TIMEOUT / KILL_COUNT); + timerSend->start(); + timerKill->start(KILL_TEST_TIMEOUT); + Tunnel::start(); + + loop->run(); + Tunnel::stop(); + + if (!passed) + { + TS_FAIL("ZeroMQ receiver did not catch incoming message."); + } + } + +}; diff --git a/src/wintermutecore/CMakeLists.txt b/src/wintermutecore/CMakeLists.txt index a2f5d142..d7776657 100644 --- a/src/wintermutecore/CMakeLists.txt +++ b/src/wintermutecore/CMakeLists.txt @@ -31,6 +31,16 @@ set(_srcs ./util/configuration.cpp ./util/configuration.cc + # == Event related + ./event_loop.cpp + ./event_emitter.cpp + ./event_object.cpp + ./event_listener.cpp + ./event_poller.cpp + ./event_timer.cpp + ./event_signal_handler.cpp + ./event_signal_handler.cc + # == Plugin Related ./library.cc ./plugin.cc @@ -61,14 +71,6 @@ set(_srcs ./module_designation.cpp ./module_pool.cpp ./module_call.cpp - - # == Event related - ./event_loop.cpp - ./event_emitter.cpp - ./event_object.cpp - ./event_listener.cpp - ./event_poller.cpp - ./event_timer.cpp ) # Set up sources. diff --git a/src/wintermutecore/event_emittable.cpp b/src/wintermutecore/event_emittable.cpp deleted file mode 100644 index e69de29b..00000000 diff --git a/src/wintermutecore/event_emitter.cpp b/src/wintermutecore/event_emitter.cpp index 469b4634..30d4b01a 100644 --- a/src/wintermutecore/event_emitter.cpp +++ b/src/wintermutecore/event_emitter.cpp @@ -16,14 +16,16 @@ #include #include "event_emitter.hh" #include "events.hpp" +#include "logging.hpp" using namespace Wintermute::Events; using std::for_each; -using std::pair; -Emitter::Emitter(const Loop::Ptr& providedLoop) : d_ptr(new EmitterPrivate) +Emitter::Emitter(const Loop::Ptr& providedLoop) : + d_ptr(new EmitterPrivate) { W_PRV(Emitter); + if (providedLoop) { d->loop = providedLoop; @@ -36,6 +38,8 @@ Emitter::Emitter(const Loop::Ptr& providedLoop) : d_ptr(new EmitterPrivate) Emitter::~Emitter() { + W_PRV(Emitter); + d->listeners.clear(); } Loop::Ptr Emitter::loop() const @@ -47,11 +51,12 @@ Loop::Ptr Emitter::loop() const Listener::Ptr Emitter::listen(const string& eventName, Listener::Ptr& listener) { W_PRV(Emitter); - d->listeners.insert(make_pair(eventName, listener)); + d->listeners.emplace(eventName, listener); return listener; } -Listener::Ptr Emitter::listen(const string& eventName, Listener::Callback cb, const Listener::Frequency& freq) +Listener::Ptr Emitter::listen(const string& eventName, Listener::Callback cb, + const Listener::Frequency& freq) { Listener::Ptr listener = make_shared(cb); listener->frequency = freq; @@ -66,9 +71,10 @@ Listener::List Emitter::listeners(const string& eventName) const auto rangeOfElements = d->listeners.equal_range(eventName); for_each(rangeOfElements.first, rangeOfElements.second, - [&listenersFound](const pair& listenerFound) + [&listenersFound](const Listener::Map::value_type& listenerItr) { - listenersFound.push_back(listenerFound.second); + auto listenerFound = listenerItr.second; + listenersFound.push_back(listenerFound); }); return listenersFound; @@ -78,12 +84,15 @@ bool Emitter::stopListening(const Listener::Ptr& listener) { W_PRV(Emitter); auto listenerItr = find_if(begin(d->listeners), end(d->listeners), - [&listener](const pair& listenerPair) + [&listener](const Listener::Map::value_type& listenerPair) { - return (listenerPair.second == listener); + const auto obtainedListener = listenerPair.second; + return (obtainedListener == listener); }); - if (listenerItr != std::end(d->listeners)) { + if (listenerItr != std::end(d->listeners)) + { + wdebug("Removed found listener."); d->listeners.erase(listenerItr); return true; } @@ -93,15 +102,26 @@ bool Emitter::stopListening(const Listener::Ptr& listener) void Emitter::emit(const Event::Ptr& event) { + assert(event); Listener::List listenersForEvent = listeners(event->name()); - for_each(begin(listenersForEvent), end(listenersForEvent), - [&](Listener::Ptr & listener) + + if (!listenersForEvent.empty()) { - listener->invoke(event); + wdebug("Invoking " + event->name() + " with " + + to_string(listenersForEvent.size()) + " listeners ..."); - if (listener->frequency == Listener::FrequencyOnce) + auto invokeListenerFunc = [&](Listener::Ptr & listener) { - stopListening(listener); - } - }); + assert(listener); + listener->invoke(event); + + if (listener->frequency == Listener::FrequencyOnce) + { + stopListening(listener); + } + }; + + for_each(begin(listenersForEvent), end(listenersForEvent), + invokeListenerFunc); + } } diff --git a/src/wintermutecore/event_listener.cpp b/src/wintermutecore/event_listener.cpp index 9d51d11f..0f64df9a 100644 --- a/src/wintermutecore/event_listener.cpp +++ b/src/wintermutecore/event_listener.cpp @@ -17,16 +17,19 @@ #include "event_listener.hh" #include "events.hpp" +#include "logging.hpp" using namespace Wintermute::Events; -Listener::Listener(Callback callback) throw (std::invalid_argument): - d_ptr(new ListenerPrivate) +Listener::Listener(Callback callback) throw (std::invalid_argument) : + d_ptr(make_shared()) { + d_ptr = make_shared(); W_PRV(Listener); + if (!callback) { - throw std::invalid_argument("Empty callback functions cannot be used by listeners."); + throw std::invalid_argument("Invalid callback functions provided."); } d->callback = callback; @@ -42,6 +45,22 @@ void Listener::invoke(const Event::Ptr& event) throw (std::invalid_argument) { throw std::invalid_argument("An invalid Event pointer was provided."); } + W_PRV(Listener); - d->callback(event); + + if (d->callback) + { + wdebug("Invoking a callback for " + event->name() + "..."); + d->callback(event); + } + else + { + wdebug("No callback set up for this listener. A potential bug?"); + } + + if (d->callback) + { + wdebug("Invoking callback for " + event->name() + "..."); + d->callback(event); + } } diff --git a/src/wintermutecore/event_listener.hh b/src/wintermutecore/event_listener.hh index 05e752fa..146b4a59 100644 --- a/src/wintermutecore/event_listener.hh +++ b/src/wintermutecore/event_listener.hh @@ -25,6 +25,15 @@ class ListenerPrivate { public: Listener::Callback callback; + explicit ListenerPrivate() : + callback(nullptr) + { + } + + ~ListenerPrivate() + { + callback = Listener::Callback(nullptr); + } }; } } diff --git a/src/wintermutecore/event_loop.cpp b/src/wintermutecore/event_loop.cpp index bbb30822..14d64b4c 100644 --- a/src/wintermutecore/event_loop.cpp +++ b/src/wintermutecore/event_loop.cpp @@ -13,25 +13,30 @@ * Boston, MA 02111-1307, USA. */ +#include "logging.hpp" #include "events.hpp" #include "event_loop.hh" using namespace Wintermute::Events; -Loop::Loop(const bool useDefault) : d_ptr(new LoopPrivate) +Loop::Loop(const bool useDefault) : d_ptr(make_shared()) { W_PRV(Loop); useDefault ? d->useDefaultLoop() : d->createNewLoop(); - assert(d->loop != NULL); + assert(d->loop); + d->loop->data = this; + wdebug("Crafted loop."); } Loop::~Loop() { + wdebug("Loop destroyed."); } bool Loop::isRunning() const { W_PRV(Loop); + assert(d->loop); const bool isActive = (uv_loop_alive(d->loop) > 0); return isActive; } @@ -39,6 +44,7 @@ bool Loop::isRunning() const bool Loop::isPrimary() const { W_PRV(const Loop); + assert(d->loop); const bool isDefault = (uv_default_loop() == d->loop); return isDefault; } @@ -46,20 +52,33 @@ bool Loop::isPrimary() const bool Loop::run() { W_PRV(Loop); + assert(d->loop); + wdebug("Running loop..."); const int resultRun = uv_run(d->loop, UV_RUN_DEFAULT); + wdebug("Ran loop."); return resultRun == 0; } bool Loop::stop() { W_PRV(Loop); - const int resultRun = uv_loop_close(d->loop); - return resultRun == 0; + bool wasStopped = false; + wdebug("Stopping loop..."); + uv_stop(d->loop); + + wdebug("Loop stopped; closing loop..."); + //const int resultRun = uv_loop_close(d->loop); + //W_CHECK_UV(resultRun, "uv_loop_close"); + wasStopped = true; + + wdebug("Loop closed."); + return wasStopped; } Loop::Ptr Loop::primary() { Loop::Ptr primaryLoop = make_shared(true); assert(primaryLoop->isPrimary()); + wdebug("Handle to primary loop obtained."); return primaryLoop; } diff --git a/src/wintermutecore/event_loop.hh b/src/wintermutecore/event_loop.hh index cf3ac29b..344c3128 100644 --- a/src/wintermutecore/event_loop.hh +++ b/src/wintermutecore/event_loop.hh @@ -25,13 +25,13 @@ namespace Events class LoopPrivate { public: + uv_loop_t* loop; LoopPrivate() : loop(NULL) { } void useDefaultLoop() { - // NOTE: Should we clear out 'loop' if it's already there? loop = uv_default_loop(); assert(loop); } @@ -48,8 +48,6 @@ public: { // TODO: Close the loop if it's active. } - - uv_loop_t* loop; }; } } diff --git a/src/wintermutecore/event_poller.cpp b/src/wintermutecore/event_poller.cpp index 7dae4d2b..bb4145e6 100644 --- a/src/wintermutecore/event_poller.cpp +++ b/src/wintermutecore/event_poller.cpp @@ -14,6 +14,7 @@ */ #include +#include #include "events.hpp" #include "event_poller.hh" #include "event_loop.hh" @@ -21,28 +22,40 @@ using namespace Wintermute::Events; -void wintermute_event_poller_callback(uv_poll_t* handle, int status, int events) +void w_event_poller_cb(uv_poll_t* handle, int status, int events) { + assert(handle); + //assert(status && status < std::numeric_limits::max()); + if (status == 0) { - wdebug("Found " + to_string(events) + " events available from libuv."); + wdebug("Found " + to_string(events) + " polling events available from libuv."); PollerPrivate* dptr = (PollerPrivate*) handle->data; + assert(dptr); dptr->callback(dptr->q_ptr); } else { - // TODO: Handle errors whilst polling. + werror("Error polling: " + string(uv_strerror(status)) + string(uv_err_name(status))); } } -Poller::Poller(Poller::FileDescriptor aFd, Poller::PollDirection aDirection, - const Loop::Ptr& loopPtr) : d_ptr(new PollerPrivate(nullptr)) +Poller::Poller(const Poller::FileDescriptor& aFd, + const Poller::PollDirection& aDirection, + const Loop::Ptr& loopPtr) : d_ptr(new PollerPrivate) { + assert(d_ptr && d_ptr.get() != NULL); + if (aFd == 0) { throw std::invalid_argument("Provided an invalid file descriptor."); } + if ((int) aDirection > 2 || (int) aDirection < 0) + { + throw std::invalid_argument("Provided an invalid direction to listen for."); + } + if (!loopPtr) { throw std::invalid_argument("Provided an invalid loop pointer."); @@ -51,9 +64,12 @@ Poller::Poller(Poller::FileDescriptor aFd, Poller::PollDirection aDirection, W_PRV(Poller); d->q_ptr = make_shared(*this); d->emitter = make_shared(loopPtr); + d->handle.reset(new uv_poll_t); d->fd = aFd; d->loop = loopPtr; d->direction = aDirection; + assert(d->handle); + d->applyCallback(loopPtr->d_ptr->loop); } @@ -83,23 +99,48 @@ Poller::PollDirection Poller::direction() const Loop::Ptr Poller::loop() const { W_PRV(const Poller); + assert(d->loop); return d->loop; } bool Poller::start() { + if (isActive()) return true; W_PRV(Poller); int r = 0; - r = uv_poll_start(&d->handle, d->direction, &wintermute_event_poller_callback); - return r == 0; + const uv_poll_event uvDir = static_cast(d->direction); + assert(d->handle); + + wdebug("Starting poller for file descriptor " + to_string(fd()) + " in " + + to_string(uvDir) + "..."); + + r = uv_poll_start(d->handle.get(), uvDir, &w_event_poller_cb); + W_CHECK_UV(r, "uv_poll_start"); + + wdebug("Was poller for file descriptor " + to_string(fd()) + " started? " + + to_string( r == 0 )); + return (r == 0) && isActive(); } bool Poller::stop() { + if (!isActive()) return true; W_PRV(Poller); - int r; - r = uv_poll_stop(&d->handle); + wdebug("Stopping poller for file descriptor " + to_string(fd()) + "..."); + + int r = 0; + r = uv_poll_stop(d->handle.get()); + W_CHECK_UV(r, "uv_poll_stop"); + + wdebug("Was poller for file descriptor " + to_string(fd()) + " stopped? " + + to_string( r == 0 )); return r == 0; } - +bool Poller::isActive() const +{ + W_PRV(const Poller); + assert(d->handle); + auto hdl = (const uv_handle_t*) d->handle.get(); + return uv_is_active(hdl) > 0; +} diff --git a/src/wintermutecore/event_poller.hh b/src/wintermutecore/event_poller.hh index e935ff5b..b085a860 100644 --- a/src/wintermutecore/event_poller.hh +++ b/src/wintermutecore/event_poller.hh @@ -26,33 +26,45 @@ namespace Wintermute { namespace Events { -struct PollerPrivate +class PollerPrivate { +public: W_DEF_PUBLIC(Poller); int fd; Poller::PollDirection direction; Loop::Ptr loop; Emitter::Ptr emitter; function callback; - uv_poll_t handle; + SharedPtr handle; - explicit PollerPrivate(Poller::Ptr qPtr) : - q_ptr(qPtr), fd(0), direction(Poller::PollReadable) { } + explicit PollerPrivate(Poller::Ptr qPtr = nullptr) : + q_ptr(qPtr), fd(0), direction(Poller::PollReadable), loop(nullptr), + emitter(nullptr), callback(nullptr), handle(nullptr) + { + } void applyCallback(uv_loop_t* loopHdl) { - assert(q_ptr); - int r = 0; + assert(this); + assert(q_ptr && q_ptr.get() != NULL); + assert(loopHdl); assert(fd > 0); - r = uv_poll_init(loopHdl, &handle, fd); - assert ( r == 0 ); - handle.data = this; + + int r = 0; + r = uv_poll_init_socket(loopHdl, handle.get(), fd); + W_CHECK_UV(r, "uv_poll_init_socket"); + handle->data = this; + assert(handle->data); callback = [](Poller::Ptr& pollerPtr) -> void { - assert(pollerPtr); - wdebug("Obtained a poll event from the provided FD: " + to_string(pollerPtr->fd())); - Event::Ptr event = make_shared(pollerPtr); + assert(pollerPtr.get() != NULL); + wdebug("Obtained a poll event from the provided file descriptor: " + + to_string(pollerPtr->fd())); + + PollEvent::Ptr event = make_shared(pollerPtr); + assert(event && event.get() != NULL); + pollerPtr->emitEvent(event); }; assert(callback); diff --git a/src/wintermutecore/event_signal_handler.cc b/src/wintermutecore/event_signal_handler.cc new file mode 100644 index 00000000..36307b85 --- /dev/null +++ b/src/wintermutecore/event_signal_handler.cc @@ -0,0 +1,59 @@ +/* + * Wintermute is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with Wintermute; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include "logging.hpp" +#include "event_loop.hh" +#include "event_signal_handler.hh" + +using Wintermute::Events::Emitter; +using Wintermute::Events::SignalHandler; +using Wintermute::Events::SignalHandlerPrivate; + +SignalHandlerPrivate::SignalHandlerPrivate( const int& signalNumber) : + q_ptr(nullptr), signum(signalNumber), emitter(new Emitter) +{ + handle = make_shared(); + handle->data = (void*) this; // sigh. +} + +SignalHandlerPrivate::~SignalHandlerPrivate() +{ + const int signalStopped = uv_signal_stop(handle.get()); + if (signalStopped != 0) + { + // TODO: Handle error. + } +} + +void SignalHandlerPrivate::attachSignal() +{ + Loop::Ptr loop = Loop::primary(); + const int signalCreated = uv_signal_init(loop->d_func()->loop, handle.get()); + if (signalCreated != 0) + { + wwarn("Failed to create signal handler."); + // TODO: Handle error. + return; + } + + const int signalStarted = uv_signal_start(handle.get(), &w_event_signal_cb, signum); + if (signalStarted != 0) + { + // TODO: Handle error. + wwarn("Failed to start signal handler."); + handle = nullptr; + return; + } +} diff --git a/src/wintermutecore/event_signal_handler.cpp b/src/wintermutecore/event_signal_handler.cpp new file mode 100644 index 00000000..4f3f0179 --- /dev/null +++ b/src/wintermutecore/event_signal_handler.cpp @@ -0,0 +1,57 @@ +/* + * Wintermute is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with Wintermute; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include "events.hpp" +#include "event_signal_handler.hh" +#include "logging.hpp" + +using Wintermute::Events::Event; +using Wintermute::Events::Emitter; +using Wintermute::Events::SignalEvent; +using Wintermute::Events::SignalHandler; +using Wintermute::Events::SignalHandlerPrivate; + +void w_event_signal_cb(uv_signal_t* handle, int signum) +{ + wdebug("Obtained signal event for signal " + to_string(signum) + "..."); + SignalHandlerPrivate* dptr = (SignalHandlerPrivate*)(handle->data); + assert(dptr); + Event::Ptr event = make_shared(dptr->q_ptr); + dptr->emitter->emit(event); +} + +SignalHandler::SignalHandler(const int& signalNumber) : + d_ptr(new SignalHandlerPrivate(signalNumber)) +{ + W_PRV(SignalHandler); + d->q_ptr = make_shared(*this); + d->attachSignal(); +} + +SignalHandler::~SignalHandler() +{ +} + +Emitter::Ptr SignalHandler::emitter() const +{ + W_PRV(const SignalHandler); + return d->emitter; +} + +int SignalHandler::signal() const +{ + W_PRV(const SignalHandler); + return d->signum; +} diff --git a/src/wintermutecore/event_signal_handler.hh b/src/wintermutecore/event_signal_handler.hh new file mode 100644 index 00000000..992f3951 --- /dev/null +++ b/src/wintermutecore/event_signal_handler.hh @@ -0,0 +1,47 @@ +/* + * Wintermute is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with Wintermute; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef WINTERMUTE_EVENT_SIGNAL_HANDLER_HH_ +#define WINTERMUTE_EVENT_SIGNAL_HANDLER_HH_ + +#include +#include "events.hpp" + +namespace Wintermute +{ +namespace Events +{ +class SignalHandlerPrivate +{ + public: + W_DEF_PUBLIC(SignalHandler); + typedef uv_signal_t SignalType; + typedef SharedPtr SignalPtr; + + explicit SignalHandlerPrivate(const int& signum); + virtual ~SignalHandlerPrivate(); + void attachSignal(); + + SignalPtr handle; + int signum; + Emitter::Ptr emitter; + SignalHandler* qptr; +}; +} +} + +void w_event_signal_cb(uv_signal_t* handle, int signum); + +#endif diff --git a/src/wintermutecore/event_timer.cpp b/src/wintermutecore/event_timer.cpp index 97f94830..f1c973c9 100644 --- a/src/wintermutecore/event_timer.cpp +++ b/src/wintermutecore/event_timer.cpp @@ -21,7 +21,7 @@ using namespace Wintermute::Events; -void wintermute_event_timer_callback(uv_timer_t* handle) +void w_event_timer_cb(uv_timer_t* handle) { TimerPrivate* d = (TimerPrivate*) handle->data; assert(d); @@ -57,7 +57,7 @@ bool Timer::start(const uint64_t timeout) { W_PRV(Timer); int r = 0; - r = uv_timer_start(&d->handle, &wintermute_event_timer_callback, + r = uv_timer_start(&d->handle, &w_event_timer_cb, timeout, interval()); assert ( r == 0 ); d->active = true; diff --git a/src/wintermutecore/events.hpp b/src/wintermutecore/events.hpp index fdec87ea..2b83f7d2 100644 --- a/src/wintermutecore/events.hpp +++ b/src/wintermutecore/events.hpp @@ -21,16 +21,21 @@ #include #include +#define W_EVENT_POLLED "core.events.poll" +#define W_EVENT_TIMEOUT "core.events.timeout" +#define W_EVENT_SIGNAL "core.events.signal" + namespace Wintermute { namespace Events { -class LoopPrivate; class EventPrivate; +class LoopPrivate; class ListenerPrivate; class EmitterPrivate; class PollerPrivate; class TimerPrivate; +class SignalHandlerPrivate; /** * Serves as a basis for event loops in Wintermute. @@ -52,6 +57,7 @@ class Loop : W_DEF_SHAREABLE(Loop) public: friend class Poller; friend class Timer; + friend class SignalHandlerPrivate; W_DECL_PTR_TYPE(Loop) /** @@ -145,7 +151,7 @@ class Listener : W_DEF_SHAREABLE(Listener) public: W_DECL_PTR_TYPE(Listener) /** - * The known frequenices at which a Listener should be invoked. + * The known frequencies at which a Listener should be invoked. */ enum Frequency { @@ -176,7 +182,7 @@ class Listener : W_DEF_SHAREABLE(Listener) explicit Listener(Callback callback) throw (std::invalid_argument); ///< Destructor. - virtual ~Listener(); + ~Listener(); /** * Invokes the stored Callback function with the provided Event pointer. @@ -210,7 +216,7 @@ class Emitter : W_DEF_SHAREABLE(Emitter) explicit Emitter(const Loop::Ptr& loop = Loop::primary()); ///< Destructor. - virtual ~Emitter(); + ~Emitter(); /** * Listens for the named event to invoke the provided callback. @@ -265,32 +271,38 @@ class Emittable { public: /** - * Returns the underlying Emitter object used by this Emittable. + * Returns the underlying Emitter object used by this Emittable object. * @return A pointer to a Emitter object. + * @sa Wintermute::Events::Emitter */ virtual Emitter::Ptr emitter() const = 0; ///< @sa Wintermute::Events::Emitter::listen - Listener::Ptr listenForEvent(const string & name, Listener::Callback func, const Listener::Frequency& freq = Listener::FrequencyEvery) + inline Listener::Ptr listenForEvent(const string & name, Listener::Callback func, + const Listener::Frequency& freq = Listener::FrequencyEvery) { + assert(emitter()); return emitter()->listen(name, func, freq); } ///< @sa Wintermute::Events::Emitter::stopListening - bool removeEventListener(const Listener::Ptr& listener) + inline bool removeEventListener(const Listener::Ptr& listener) { + assert(emitter()); return emitter()->stopListening(listener); } ///< @sa Wintermute::Events::Emitter::listeners - Listener::List eventListeners(const string & eventName) const + inline Listener::List eventListeners(const string & eventName) const { + assert(emitter()); return emitter()->listeners(eventName); } ///< @sa Wintermute::Events::Emitter::emit - void emitEvent(const Event::Ptr & event) + inline void emitEvent(const Event::Ptr & event) { + assert(emitter()); emitter()->emit(event); } }; @@ -304,7 +316,7 @@ class Emittable * * [poll]: http://libuv.readthedocs.org/en/latest/handle.html#c.uv_fileno */ -class Poller : public Emittable +class Poller : public Emittable #ifndef DOXYGEN_SKIP , W_DEF_SHAREABLE(Poller) #endif @@ -318,8 +330,8 @@ class Poller : public Emittable */ enum PollDirection { - PollReadable = 0x1, ///< Emit events when we can read. - PollWritable = 0x2 ///< Emit events when we can write. + PollReadable = 1, ///< Emit events when we can read. + PollWritable = 2 ///< Emit events when we can write. } ; ///< Platform dependent implementation of the file descriptor type. @@ -335,7 +347,8 @@ class Poller : public Emittable * @param[in] poll The polling directions to listen for. * @param[in] loopPtr The Loop on which to work on. */ - explicit Poller(FileDescriptor fd, PollDirection poll = PollReadable, + explicit Poller(const FileDescriptor& fd, + const PollDirection& poll = PollReadable, const Loop::Ptr& loopPtr = Loop::primary()); ///< Destructor. @@ -356,6 +369,8 @@ class Poller : public Emittable ///< Stops the Poller. bool stop(); + ///< Determines if the Poller is active. + bool isActive() const; }; /** @@ -370,7 +385,7 @@ class PollEvent : public Event * @param thePoller The poller to use. */ PollEvent(Poller::Ptr& thePoller) : - Event("core.events.poll"), + Event(W_EVENT_POLLED), poller(thePoller) { } ///< Destructor. @@ -411,7 +426,7 @@ class Timer : public Emittable * @param[in] uint64_t The timeout in which to emit TimerEvent objects. * @return TRUE if successful. */ - bool start(const uint64_t timeout); + bool start(const uint64_t timeout = 0); ///< Stops this Timer. ///< @return TRUE if successful. @@ -439,7 +454,7 @@ class TimerEvent : public Event public: ///< Constructor. TimerEvent(Timer::Ptr theTimer) : - Event("core.events.timeout"), + Event(W_EVENT_TIMEOUT), timer(theTimer) { } ///< Destructor. @@ -449,7 +464,38 @@ class TimerEvent : public Event Timer::Ptr timer; }; +class SignalHandler : public Emittable +#ifndef DOXYGEN_SKIP + , W_DEF_SHAREABLE(SignalHandler) +#endif +{ + private: + W_DEF_PRIVATE(SignalHandler); + public: + W_DECL_PTR_TYPE(SignalHandler) + + explicit SignalHandler(const int& signalNumber); + virtual ~SignalHandler(); + ///< The signal handler's emitter. + virtual Emitter::Ptr emitter() const; + + ///< The signal this handler is listening to. + int signal() const; + + SignalHandler::Ptr signalHandler; +}; + +class SignalEvent : public Event +{ + public: + explicit SignalEvent(const SignalHandler::Ptr& handler) : + Event(W_EVENT_SIGNAL), signalHandler(handler) {} + + virtual ~SignalEvent() { } + + SignalHandler::Ptr signalHandler; +}; } } diff --git a/src/wintermutecore/globals.hpp.in b/src/wintermutecore/globals.hpp.in index 95342f6c..125562a2 100644 --- a/src/wintermutecore/globals.hpp.in +++ b/src/wintermutecore/globals.hpp.in @@ -19,28 +19,35 @@ #ifndef WINTERMUTE_CORE_GLOBALS_HPP #define WINTERMUTE_CORE_GLOBALS_HPP -#include "wintermute_export.hpp" +#include #include #include #include -#include +#include #include #include #include #include -#include #include +#include #include +#include "wintermute_export.hpp" using std::string; using std::make_shared; +using std::to_string; +using std::dynamic_pointer_cast; #define WINTERMUTE_VERSION "@WINTERMUTE_VERSION@" #define WINTERMUTE_CONFIG_INCLUDE_DIR "@WINTERMUTE_CONFIG_INCLUDE_DIR@" #define WINTERMUTE_LOGGER_ROOT_NAME "wntr" +#define WINTERMUTE_DOMAIN "in.wintermute" #define WINTERMUTE_ENV_PLUGIN_PATH "WINTERMUTE_PLUGIN_PATH" #define WINTERMUTE_ENV_LOG_LEVEL "WINTERMUTE_LOG_LEVEL" +#define WINTERMUTE_EVENT_MODULE_ENABLE "core.module.enable" +#define WINTERMUTE_EVENT_MODULE_DISABLE "core.module.disable" + #ifdef WINTERMUTE_EXPORT # define WINTERMUTE_EXPORT_PUBLIC __attribute__ ((visibility ("default"))) # define WINTERMUTE_EXPORT_PRIVATE __attribute__ ((visibility ("hidden"))) @@ -57,16 +64,29 @@ using std::make_shared; /// Defines inline instance-level methods to handle private instance data. #define W_DEF_PRIVATE(Class) \ using _Prv = Class##Private; \ + using _PrvPtr = SharedPtr<_Prv>; \ friend _Prv; \ - SharedPtr<_Prv> d_ptr = nullptr; \ - inline _Prv* d_func() const { return d_ptr.get(); } \ - inline _Prv* d_func() { return d_ptr.get(); } + _PrvPtr d_ptr = nullptr; \ + inline _PrvPtr d_func() const { \ + return Class::d_ptr; \ + } \ + inline _PrvPtr d_func() { \ + assert(d_ptr); \ + return Class::d_ptr; \ + } #define W_DEF_PRIVATE_PREDECL(_Class) \ - using _Pub = Class; \ - SharedPtr<_Pub> d_ptr; \ - inline _Pub* d_func() const { return d_ptr.get(); } \ - inline _Pub* d_func() { return d_ptr.get(); } + using _Prv = Class; \ + using _PrvPtr = SharedPtr q_ptr; \ - inline _Pub* q_func() const { return q_ptr.get(); } \ - inline _Pub* q_func() { return q_ptr.get(); } \ + inline _Pub* q_func() const { \ + assert(q_ptr); \ + return q_ptr.get(); \ + } \ + inline _Pub* q_func() { \ + assert(q_ptr); \ + return q_ptr.get(); \ + } #define W_DEF_PUBLIC_PREDECL(_Class) \ using _Pub = Class; \ SharedPtr<_Pub> q_ptr; \ - inline _Pub* q_func() const { return q_ptr.get(); } \ - inline _Pub* q_func() { return q_ptr.get(); } \ + inline _Pub* q_func() const { \ + assert(q_ptr); \ + return q_ptr.get(); \ + } \ + inline _Pub* q_func() { \ + assert(q_ptr); \ + return q_ptr.get(); \ + } /// Provides a shortcut to grab private data for an object with private data. -#define W_PRV(Class) Class##Private* const d = d_func(); -#define W_PRVP(_Class) _Class* const d = d_func(); +#define W_PRV(Class) SharedPtr const d = d_func(); +#define W_PRVP(_Class) SharedPtr<_Class> const d = d_func(); /// Provides a shortcut to grab private data for an static object with private data. -#define W_SPRV(Class) Class##Private* const d = Class::instance()->d_func(); -#define W_SPRVP(_Class, Class) _Class* const d = Class::instance()->d_func(); +#define W_SPRV(Class) \ + assert(Class::instance()); \ + SharedPtr const d = Class::instance()->d_func(); +#define W_SPRVP(_Class, Class) \ + assert(Class::instance()); \ + SharedPtr<_Class> const d = Class::instance()->d_func(); /// Provides a shortcut to grab public data for an object with public data. #define W_PUB(Class) Class* const q = q_func(); @@ -115,6 +151,7 @@ using std::make_shared; /** * Defines the logic needed to handle a singleton. * @ingroup Singletons + * @todo Make this friendly to use with std::make_shared. */ #define W_DEF_SINGLETON(Class) \ private: \ @@ -122,17 +159,30 @@ using std::make_shared; public: \ static inline SharedPtr instance() { \ if (!_instance) { \ - _instance.reset(new Class); \ + _instance.reset(new Class()); \ } \ + assert(_instance);\ return _instance; \ } /** - * Declares the instance of the singleton as a null pointer + * Declares the instance of the singleton as a null pointer * @ingroup Singletons */ #define W_DECLARE_SINGLETON(Class) \ - SharedPtr Class::_instance(nullptr); + SharedPtr Class::_instance; + +/** + * Helps with handling libuv errors. + */ +#define W_CHECK_UV(r, call) \ + if (r != 0) \ + { \ + std::ostringstream oStr; \ + oStr << "[" << call << "] uverr: " << uv_err_name(r) << " - " << uv_strerror(r); \ + werror(oStr.str()); \ + abort();\ + } // {{{ Platform-specific types diff --git a/src/wintermutecore/library.cc b/src/wintermutecore/library.cc index 8c115c55..ddaf50e2 100644 --- a/src/wintermutecore/library.cc +++ b/src/wintermutecore/library.cc @@ -21,49 +21,52 @@ using Wintermute::Library; using Wintermute::LibraryPrivate; +typedef std::unordered_map LibraryHandleMap; + +LibraryHandleMap LibraryPrivate::libraryHandles = LibraryHandleMap(); LibraryPrivate::LibraryPrivate() : - handlePtr(0), filename(""), loadState(Library::LoadUndefined) + handle(nullptr), filename(""), loadState(Library::LoadUndefined) { - handlePtr = new LibraryPrivate::HandlePtr; } -LibraryPrivate::HandlePtr* LibraryPrivate::claimHandleForFilename(const string& filenameToLoad, string& errorMessage) +bool LibraryPrivate::claimHandle(const string& filenameToLoad) { - wdebug("Claiming handle for library " + filenameToLoad + "..."); - LibraryPrivate::HandlePtr* handle = new LibraryPrivate::HandlePtr; - - const int openedLibrary = uv_dlopen(filenameToLoad.c_str(), handle); - - if (openedLibrary < 0) + auto itr = LibraryPrivate::libraryHandles.find(filenameToLoad); + if (itr != LibraryPrivate::libraryHandles.end()) { - const char* msg = uv_dlerror(handle); + wdebug("Fetching existing library handle."); + handle = itr->second; + return true; + } - if (msg) - { - errorMessage = msg; - werror("Failed to load library '" + filenameToLoad + "': " + errorMessage); - } - else - { - errorMessage = "Unknown error."; - wwarn("Failed to load library due to an unknown error; possibly file missing."); - } + LibraryPrivate::HandlePtr handlePtr = make_shared(); + const int handleOpened = uv_dlopen(filenameToLoad.c_str(), handlePtr.get()); - return nullptr; + if (handleOpened == 0) + { + auto pair = std::make_pair(filenameToLoad, handlePtr); + LibraryPrivate::libraryHandles.insert(pair); + handle = handlePtr; + return true; + } + else + { + wdebug("Failed to load library: " + string(uv_dlerror(handlePtr.get()))); } - winfo("Handle loaded successfully."); - errorMessage = ""; - - return handle; + handle = nullptr; + return false; } -bool LibraryPrivate::freeHandle(string& errorMessage) +bool LibraryPrivate::freeHandle() { - uv_dlclose(handlePtr); - errorMessage = uv_dlerror(handlePtr); - handlePtr = nullptr; + if (handle) + { + uv_dlclose(handle.get()); + LibraryPrivate::libraryHandles.erase(filename); + } + return true; } diff --git a/src/wintermutecore/library.cpp b/src/wintermutecore/library.cpp index 79b27054..299cbbf5 100644 --- a/src/wintermutecore/library.cpp +++ b/src/wintermutecore/library.cpp @@ -34,7 +34,6 @@ list collectFilePaths(const string& filePath) const string ourPathFromEnv = (envWintermutePluginPath) ? envWintermutePluginPath : ""; list prefixes; - // Compile a list of paths where this thing can be at. list filePaths = { filePath, @@ -101,34 +100,28 @@ Library::LoadState Library::load(const string& filenameToLoad) wdebug("Attempting to load library from the filename '" + filenameToLoad + "'..."); - string errorMessage; - auto libraryHandlePtr = d->claimHandleForFilename(filenameToLoad, errorMessage); + const bool handleClaimed = d->claimHandle(filenameToLoad); - if (!libraryHandlePtr) + if (!handleClaimed) { - d->handlePtr = nullptr; + d->handle = nullptr; d->loadState = LoadNotLoaded; - werror("Failed to claim handle for library: " + errorMessage); return LoadNotLoaded; } - // Update the internals to reflect current state. - d->handlePtr = libraryHandlePtr; d->loadState = LoadIsLoaded; d->filename = filenameToLoad; - return LoadIsLoaded; } Library::LoadState Library::unload() { W_PRV(Library); - string errorMessage; - const bool wasHandleFreed = d->freeHandle(errorMessage); + const bool wasHandleFreed = d->freeHandle(); if (!wasHandleFreed) { - werror("Failed to free library handle for " + d->filename + ": " + errorMessage); + werror("Failed to free library handle for " + d->filename + "."); return LoadNotLoaded; } @@ -160,7 +153,6 @@ Library::Ptr Library::find(const string& libraryQuery) { wdebug("Using path " + path + "..."); Library::Ptr sampleLibraryPtr = make_shared(path); - assert(sampleLibraryPtr); const bool wasLibraryLoaded = (sampleLibraryPtr->loadedStatus() == LoadIsLoaded); wdebug("Was library loaded from " + path + "? " + std::to_string(wasLibraryLoaded)); @@ -174,13 +166,12 @@ Library::Ptr Library::find(const string& libraryQuery) return libraryPtr; } -Library::FunctionPtr Library::resolveFunction(const string& functionName) const +Library::FunctionPtr Library::resolveFunction(const string& funcName) const { W_PRV(const Library); - Library::FunctionPtr functionPtr; - const char* errorMessage; + Library::FunctionPtr funcPtr; - wdebug("Resolving function " + functionName + " from " + d->filename); + wdebug("Resolving function " + funcName + " from " + d->filename + "..."); if (d->loadState != LoadIsLoaded) { @@ -188,29 +179,23 @@ Library::FunctionPtr Library::resolveFunction(const string& functionName) const return nullptr; } - if (functionName.empty()) + if (funcName.empty()) { wwarn("Cannot resolve a function with no name."); return nullptr; } - const int fcnResolved = uv_dlsym(d->handlePtr, functionName.c_str(), &functionPtr); + const int fcnResolved = uv_dlsym(d->handle.get(), funcName.c_str(), &funcPtr); if (fcnResolved < 0) { - errorMessage = uv_dlerror(d->handlePtr); - if (errorMessage != NULL) - { - wdebug("Failed to resolve function '" + functionName + " from " + d->filename + ": " + errorMessage); - } - else - { - wwarn("The function '" + functionName + "' was not found, empty reference obtained."); - } + const string errorMessage = uv_dlerror(d->handle.get()); + wdebug("Failed to resolve function '" + funcName + " from " + d->filename + + ": " + errorMessage); return nullptr; } - wtrace("Obtained function handle for '" + functionName + "' from " + d->filename + "."); - return functionPtr; + wtrace("Obtained function for '" + funcName + "' from " + d->filename + "."); + return funcPtr; } diff --git a/src/wintermutecore/library.hh b/src/wintermutecore/library.hh index 94812cf3..eb1f9049 100644 --- a/src/wintermutecore/library.hh +++ b/src/wintermutecore/library.hh @@ -17,6 +17,7 @@ #include #include +#include #include using std::string; @@ -24,13 +25,19 @@ using std::string; namespace Wintermute { class LibraryPrivate { public: + typedef uv_lib_t HandleType; + typedef SharedPtr HandlePtr; + explicit LibraryPrivate(); virtual ~LibraryPrivate(); - typedef uv_lib_t HandlePtr; - HandlePtr* claimHandleForFilename(const string& filename, string& errorMessage); - bool freeHandle(string& errorMessage); - HandlePtr* handlePtr; + bool claimHandle(const string& filename); + bool freeHandle(); + + HandlePtr handle; string filename; Library::LoadState loadState; + + private: + static std::unordered_map libraryHandles; }; } diff --git a/src/wintermutecore/library.hpp b/src/wintermutecore/library.hpp index ec6c92c2..a8071255 100644 --- a/src/wintermutecore/library.hpp +++ b/src/wintermutecore/library.hpp @@ -26,6 +26,7 @@ class LibraryPrivate; /** * A class to load libraries and their function symbols. * @ingroup Plugins + * * Represents a portable wrapper over the facilities required to load * dynamically loadable libraries. */ @@ -49,28 +50,31 @@ class Library LoadUndefined = 0x000, LoadNotLoaded = 0x100, LoadIsLoaded = 0x200, - LoadStateSuccess = LoadIsLoaded, - LoadStateFailure = LoadNotLoaded, + LoadStateSuccess = 0x210, + LoadStateFailure = 0x110, }; - /* Loads a library into memory. + /** Loads a library into memory. + * @param fileName The filepath, relative or absolute, of the library to load. + * @return LoadState + * * Using the provided 'filename', loads a library into memory. If the * load is successful ( a return value of LoadSuccess ), then filename() * will match the one provided. Otherwise, filename() != filename and the * return value will LoadSuccess != return_value. */ - LoadState load(const string & filename); + LoadState load(const string & filepath); - /* Unloads a library from memory. */ + /** Unloads a library from memory. */ LoadState unload(); - /* Obtains the filename for the library. */ + /** Obtains the filename for the library. */ string filename() const; - /* Determine the current load status of the plugin. */ + /** Determine the current load status of the plugin. */ LoadState loadedStatus() const; - /* Resolves a function from the library. */ + /** Resolves a function from the library. */ FunctionPtr resolveFunction(const string & functionName) const; /* Finds a plugin through a varity of means. diff --git a/src/wintermutecore/logging.hpp b/src/wintermutecore/logging.hpp index f17344fa..4d8779fa 100644 --- a/src/wintermutecore/logging.hpp +++ b/src/wintermutecore/logging.hpp @@ -33,10 +33,15 @@ namespace Wintermute * without much discomfort to the code base. */ class Logging +#ifdef DOXYGEN_SKIP + : W_DEF_SHARABLE(Logging) +#endif { explicit Logging(); public: - virtual ~Logging(); + W_DEF_SINGLETON(Logging); + + ~Logging(); /** Levels at which Wintermute is capable of reporting its log messages. */ enum Level { @@ -71,8 +76,6 @@ class Logging void trace(const string& message, const string& name); static void cleanup(); - - W_DEF_SINGLETON(Logging); }; } diff --git a/src/wintermutecore/message.hpp b/src/wintermutecore/message.hpp index be0a18ba..4892e90d 100644 --- a/src/wintermutecore/message.hpp +++ b/src/wintermutecore/message.hpp @@ -80,6 +80,8 @@ class WINTERMUTE_EXPORT_PUBLIC Message : bool operator==(const Message& other) const; bool operator!() const; + + using Serializable::operator=; }; } diff --git a/src/wintermutecore/method.hh b/src/wintermutecore/method.hh index d26fdbaf..4070cb62 100644 --- a/src/wintermutecore/method.hh +++ b/src/wintermutecore/method.hh @@ -27,8 +27,9 @@ using Wintermute::Util::Serializable; namespace Wintermute { -struct MethodPrivate +class MethodPrivate { +public: string callName; Module::Designation originatingModule; Module::Designation destinationModule; @@ -38,3 +39,4 @@ struct MethodPrivate #endif + diff --git a/src/wintermutecore/method.hpp b/src/wintermutecore/method.hpp index e21783bb..31387518 100644 --- a/src/wintermutecore/method.hpp +++ b/src/wintermutecore/method.hpp @@ -25,7 +25,7 @@ namespace Wintermute { -struct MethodPrivate; +class MethodPrivate; /** * Represents a method invocation for a Module. * Methods are meant to represent a call that's sent over the wire. It diff --git a/src/wintermutecore/module.cc b/src/wintermutecore/module.cc index aedb08be..e5241a44 100644 --- a/src/wintermutecore/module.cc +++ b/src/wintermutecore/module.cc @@ -19,10 +19,13 @@ #include "logging.hpp" using Wintermute::ModulePrivate; +using Wintermute::Events::Emitter; ModulePrivate::ModulePrivate(const Module::Designation& des) : - designation(des) + designation(des), + emitter(nullptr) { + emitter = make_shared(); calls.clear(); } @@ -30,4 +33,5 @@ ModulePrivate::~ModulePrivate() { wdebug("Flushing out calls for " + static_cast(designation) + "..."); calls.clear(); + wdebug("Cleared calls for " + static_cast(designation) + "."); } diff --git a/src/wintermutecore/module.cpp b/src/wintermutecore/module.cpp index 3b4e2566..d94f7f28 100644 --- a/src/wintermutecore/module.cpp +++ b/src/wintermutecore/module.cpp @@ -19,12 +19,15 @@ #include "module.hpp" #include "message.hpp" #include "logging.hpp" +#include "tunnel.hpp" using Wintermute::Module; using Wintermute::Call; +using Wintermute::Tunnel; using Wintermute::ModulePrivate; +using Wintermute::Events::Emitter; +using Wintermute::Events::Event; using std::to_string; -using std::dynamic_pointer_cast; Module::Module(const Module::Designation& aDesignation) : d_ptr(std::make_shared(aDesignation)) @@ -32,6 +35,12 @@ Module::Module(const Module::Designation& aDesignation) : wtrace("Module " + static_cast(aDesignation) + " started."); } +Emitter::Ptr Module::emitter() const +{ + W_PRV(const Module); + return d->emitter; +} + Module::Designation Module::designation() const { W_PRV(Module); @@ -47,8 +56,15 @@ bool Module::enable() } wdebug("Module " + static_cast(designation()) + " doesn't exist in the pool, adding.."); - Module::Ptr modulePtr = std::make_shared(*this); - return Module::Pool::instance()->add(modulePtr); + Module::Ptr modulePtr = shared_from_this(); + const bool moduleAdded = Module::Pool::instance()->add(modulePtr); + wdebug("Was module " + static_cast(designation()) + " added into pool? " + to_string((int) moduleAdded)); + + assert(moduleAdded); + auto eventPtr = make_shared(WINTERMUTE_EVENT_MODULE_ENABLE); + emitEvent(eventPtr); + + return moduleAdded; } bool Module::disable() @@ -65,19 +81,29 @@ bool Module::disable() assert(wasRemoved); // TODO: Clean this up. wdebug ( "Removed " + static_cast(theDesignation) + " from the pool."); + + auto eventPtr = make_shared(WINTERMUTE_EVENT_MODULE_DISABLE); + emitEvent(eventPtr); + return true; // It's not in the pool. } -bool Module::isEnabled() +bool Module::isEnabled() const { return Module::Pool::instance()->has(designation()); } -bool Module::sendMessage(const Message& message) const +bool Module::sendMessage(const Message& message) { - assert ( !message.isEmpty() ); - throw std::invalid_argument("This method has not been overridden."); - return false; + Message messageToSend = message; + if (messageToSend.sender() != designation()) + { + messageToSend.setSender(designation()); + } + + assert ( !messageToSend.isEmpty() ); + Tunnel::sendMessage(messageToSend); + return true; } bool Module::receiveMessage(const Message& message) const @@ -142,4 +168,3 @@ Module::Call::Ptr Module::call(const string& nameOfCall) const Module::~Module() { } - diff --git a/src/wintermutecore/module.hh b/src/wintermutecore/module.hh index a2ad2048..c6882843 100644 --- a/src/wintermutecore/module.hh +++ b/src/wintermutecore/module.hh @@ -26,7 +26,9 @@ public: W_DEF_PUBLIC(Module); Module::Designation designation; Module::Call::Map calls; + Events::Emitter::Ptr emitter; + explicit ModulePrivate(const Module::Designation& des); - virtual ~ModulePrivate(); + ~ModulePrivate(); }; } diff --git a/src/wintermutecore/module.hpp b/src/wintermutecore/module.hpp index 45fc3343..aca9913d 100644 --- a/src/wintermutecore/module.hpp +++ b/src/wintermutecore/module.hpp @@ -23,6 +23,7 @@ #include #include #include +#include #include using std::list; @@ -48,9 +49,9 @@ class ModuleCallPrivate; * and invoke signals. They allow for the flexibility one would expect from * Wintermute. */ -class Module +class Module : public Wintermute::Events::Emittable #ifndef DOXYGEN_SKIP - : W_DEF_SHAREABLE(Module) + , W_DEF_SHAREABLE(Module) #endif { public: @@ -131,6 +132,8 @@ class Module private: explicit Pool(); + using ModulePool = Module::Pool; + W_DEF_PRIVATE(ModulePool); protected: @@ -145,7 +148,7 @@ class Module bool add(Module::Ptr& module); /** - * Removes a module from the known list. + * Removes a module from the known list. * @param designation The designation of the module in question. * @return A boolean value on the success of the removal of said Module. * @sa Wintermute::Module::Pool::add @@ -222,6 +225,7 @@ class Module public Wintermute::Call { private: + using ModuleCall = Module::Call; W_DEF_PRIVATE(ModuleCall) W_SERIALIZABLE(Call) @@ -241,24 +245,24 @@ class Module /// Obtains the designation of this Module. Designation designation() const; + /// Determines if this Module is enabled. + bool isEnabled() const; + + /// Fetches the call in question. + Call::Ptr call(const string & callName) const; + /// Adds this module to the pool. bool enable(); /// Removes this module from the pool. bool disable(); - /// Determines if this Module is enabled. - bool isEnabled(); - /// Adds a call to this Module. bool addCall(Module::Call::Ptr & callToAdd); /// Removes a call with the provided name. bool removeCall(const string & callName); - /// Fetches the call in question. - Call::Ptr call(const string & callName) const; - protected: W_DEF_PRIVATE(Module) @@ -269,7 +273,10 @@ class Module virtual bool receiveMessage(const Message & message) const; /// Handles the act of sending out a message. - virtual bool sendMessage(const Message & message) const; + virtual bool sendMessage(const Message& message); + +private: + virtual Events::Emitter::Ptr emitter() const final; }; } diff --git a/src/wintermutecore/module_designation.cc b/src/wintermutecore/module_designation.cc index 3f46a2ca..a7bdec19 100644 --- a/src/wintermutecore/module_designation.cc +++ b/src/wintermutecore/module_designation.cc @@ -42,6 +42,5 @@ void DesignationPrivate::clone(const SharedPtr& other) size_t DesignationPrivate::Hash::operator()(const Module::Designation& des) const { hash hashStr_fn; - return hashStr_fn((string) des); } diff --git a/src/wintermutecore/module_designation.cpp b/src/wintermutecore/module_designation.cpp index 6f7fce4b..1f4f4c50 100644 --- a/src/wintermutecore/module_designation.cpp +++ b/src/wintermutecore/module_designation.cpp @@ -40,13 +40,13 @@ Module::Designation::Designation(const Designation& other) : Serializable(other) d->clone(other.d_ptr); } -Module::Designation::Designation(const string& jsonString) : +Module::Designation::Designation(const string& jsonString) : d_ptr (std::make_shared()) { deserialize(Serializable::fromString(jsonString)); } -Module::Designation::Designation() : +Module::Designation::Designation() : d_ptr (std::make_shared()) { W_PRV(Designation); @@ -103,6 +103,11 @@ Serializable::Map Module::Designation::serialize() const return theMap; } +bool Module::Designation::isLocal() const +{ + return true; +} + Module::Designation::~Designation() { } diff --git a/src/wintermutecore/module_pool.cc b/src/wintermutecore/module_pool.cc index b767ae62..250ae7a5 100644 --- a/src/wintermutecore/module_pool.cc +++ b/src/wintermutecore/module_pool.cc @@ -18,3 +18,13 @@ #include "module_pool.hh" using Wintermute::ModulePoolPrivate; + +ModulePoolPrivate::ModulePoolPrivate() + : modules() +{ +} + +ModulePoolPrivate::~ModulePoolPrivate() +{ + modules.clear(); +} diff --git a/src/wintermutecore/module_pool.cpp b/src/wintermutecore/module_pool.cpp index cf8e6771..9e60d239 100644 --- a/src/wintermutecore/module_pool.cpp +++ b/src/wintermutecore/module_pool.cpp @@ -26,6 +26,7 @@ using std::make_pair; using std::to_string; using std::end; using std::begin; +using std::dynamic_pointer_cast; W_DECLARE_SINGLETON(Module::Pool) @@ -40,22 +41,19 @@ Module::Pool::Pool() : d_ptr(new ModulePoolPrivate) Module::Ptr Module::Pool::find(const Module::Designation& designation) const { W_PRV(const ModulePool); - wdebug("Looking for the module designated as '" + (string) designation + "'..."); - auto itr = d->modules.find(designation); + Module::Ptr modulePtr = nullptr; + wdebug("Looking up module '" + (string) designation + "'..."); - if (itr != std::end(d->modules)) + if (d->modules.count(designation) != 1) { - wdebug("Found the module '" + (string) designation + "'."); - Module::Ptr modulePtr = itr->second; - assert(modulePtr); - return modulePtr; - } - else - { - wdebug("Could not find the module '" + (string) designation + "'."); + wdebug("The module " + static_cast(designation) + " was not found."); + return nullptr; } - return nullptr; + wdebug("Found the module '" + (designation.domain() + "." + designation.name()) + "'."); + modulePtr = d->modules.at(designation)->shared_from_this(); + assert(modulePtr); + return modulePtr; } Module::List Module::Pool::modules() const @@ -63,19 +61,17 @@ Module::List Module::Pool::modules() const W_PRV(const ModulePool); Module::List knownModules; - wdebug("Returning a list of " + to_string(d->modules.size()) + " known modules."); - if (!d->modules.empty()) { - for (auto itr = begin(d->modules); itr != end(d->modules); itr++) + for_each(begin(d->modules), end(d->modules), + [&](const std::pair& thePair) { - assert(itr->second); - knownModules.push_back(itr->second); - } - - assert ( knownModules.size() == d->modules.size() ); + assert(thePair.second); + knownModules.push_back(thePair.second); + }); } + assert ( knownModules.size() == d->modules.size() ); return knownModules; } @@ -83,9 +79,11 @@ bool Module::Pool::add(Module::Ptr& modulePtr) { W_PRV(ModulePool); + assert(modulePtr); + if (!has(modulePtr->designation())) { - Module::Designation designation = modulePtr->designation(); + auto designation = modulePtr->designation(); auto mapValue = make_pair(designation, modulePtr); auto returnTupleInsert = d->modules.insert(mapValue); const bool wasInserted = returnTupleInsert.second; @@ -106,7 +104,6 @@ bool Module::Pool::add(Module::Ptr& modulePtr) else { wwarn("Module '" + static_cast(modulePtr->designation()) + "' already exists in the pool."); - return false; } return false; @@ -122,31 +119,32 @@ bool Module::Pool::remove(const Module::Designation& designation) return false; } - Module::Ptr modulePtr(find(designation)); - - if (modulePtr) - { - const int count = d->modules.erase(designation); - wdebug("Module " + (string) designation + " found for deletion; was the module removed? (" + std::to_string(count) + ")"); - return count == 1; - } - else - { - wdebug("Module " + (string) designation + " was not found in the pool."); - return false; - } - return false; + Module::Ptr modulePtr = find(designation); + const int count = d->modules.erase(designation); + wdebug("Module " + (string) designation + " found for deletion; was the module removed? (" + std::to_string(count) + ")"); + return count == 1; } bool Module::Pool::has(const Module::Designation& designation) const { W_PRV(const ModulePool); - return d->modules.count(designation) != 0; + const auto itr = d->modules.find(designation); + return itr != std::end(d->modules); } Module::Pool::~Pool() { - W_PRV(ModulePool); - d->modules.clear(); -} + wdebug("Removing all modules..."); + const auto moduleList = modules(); + for ( auto modulePtr : moduleList ) + { + auto des = modulePtr->designation(); + wdebug(static_cast(des) + " being removed."); + remove(des); + wdebug(static_cast(des) + " removed for shutdown."); + modulePtr = nullptr; + } + + wdebug("All modules removed."); +} diff --git a/src/wintermutecore/module_pool.hh b/src/wintermutecore/module_pool.hh index 93ab34bf..3e18a31b 100644 --- a/src/wintermutecore/module_pool.hh +++ b/src/wintermutecore/module_pool.hh @@ -28,6 +28,8 @@ class ModulePoolPrivate public: typedef unordered_map Map; Map modules; + + explicit ModulePoolPrivate(); + ~ModulePoolPrivate(); }; } - diff --git a/src/wintermutecore/plugin.cc b/src/wintermutecore/plugin.cc index 788345d0..d8e4ac2f 100644 --- a/src/wintermutecore/plugin.cc +++ b/src/wintermutecore/plugin.cc @@ -19,10 +19,12 @@ #include "logging.hpp" #include "plugin.hh" +using std::end; +using std::to_string; using Wintermute::Plugin; using Wintermute::PluginPrivate; -PluginPrivate::PluginList PluginPrivate::plugins; +PluginPrivate::PluginMap PluginPrivate::plugins; PluginPrivate::PluginPrivate(const string& pluginName) : library(), name(pluginName) @@ -34,8 +36,30 @@ PluginPrivate::~PluginPrivate() { } -void PluginPrivate::registerPlugin(Plugin::Ptr& plugin) +bool PluginPrivate::registerPlugin(Plugin::Ptr& plugin) { - wdebug("Inserted " + plugin->name() + " into the namespace."); - PluginPrivate::plugins.insert(std::make_pair(plugin->name(), plugin)); + wdebug("Inserted " + plugin->name() + " into the pool."); + auto insertionStatus = PluginPrivate::plugins.emplace(plugin->name(), plugin); + wdebug("Was the plugin " + plugin->name() + " inserted? " + + to_string(insertionStatus.second)); + return insertionStatus.second; +} + +bool PluginPrivate::unregisterPlugin(const string& pluginName) +{ + wdebug("Removed " + pluginName + " from the pool."); + const auto erasedCount = PluginPrivate::plugins.erase(pluginName); + wdebug("Was the plugin " + pluginName + " removed? " + + to_string(erasedCount)); + return erasedCount == 1; +} + +Plugin::Ptr PluginPrivate::lookupPlugin(const string& pluginQuery) +{ + auto itr = PluginPrivate::plugins.find(pluginQuery); + assert(itr != end(PluginPrivate::plugins)); + Plugin::Ptr pluginPtr = itr->second; + assert(pluginPtr); + + return pluginPtr; } diff --git a/src/wintermutecore/plugin.cpp b/src/wintermutecore/plugin.cpp index 2270f979..2cb735b7 100644 --- a/src/wintermutecore/plugin.cpp +++ b/src/wintermutecore/plugin.cpp @@ -28,9 +28,11 @@ using Wintermute::PluginPrivate; bool isLibraryCompatible(Library::Ptr& libraryPtr) { + assert(libraryPtr); if (!libraryPtr) { - throw std::invalid_argument("Invalid pointer to library."); + werror("Obtained an invalid pointer to a library."); + //throw std::invalid_argument("Invalid pointer to library."); return false; } @@ -49,7 +51,7 @@ bool isLibraryCompatible(Library::Ptr& libraryPtr) const Version systemVersion = Version::system(); wdebug("Raw version string from library: " + versionString); - wdebug("System " + static_cast(systemVersion)+ " >= library min " + static_cast(libraryVersion)); + wdebug("System " + static_cast(systemVersion) + " >= library min " + static_cast(libraryVersion)); return systemVersion >= libraryVersion; } @@ -65,8 +67,11 @@ void loadPluginFromLibrary(Library::Ptr& libraryPtr, Plugin::Ptr& pluginPtr) return; } + // TODO: Add exception handling around 'ctorFunction'. pluginPtr.reset(ctorFunction()); + assert(pluginPtr); + if (!pluginPtr) { werror("Failed to create a instance of the plugin."); @@ -85,11 +90,16 @@ Plugin::Plugin(const string& pluginName) : d_ptr(new PluginPrivate(pluginName)) Plugin::~Plugin() { W_PRV(Plugin); - if (d->library->loadedStatus() == Library::LoadIsLoaded) + + if (d->library && d->library->loadedStatus() == Library::LoadIsLoaded) { wdebug("Unloading plugin's library prior to deallocation..."); d->library->unload(); } + else + { + wdebug("No library instance."); + } wdebug("Deallocated a plugin."); } @@ -97,7 +107,7 @@ Plugin::~Plugin() // TODO: Dice up this function. Plugin::Ptr Plugin::find(const string& pluginQuery) { - Plugin::Ptr pluginPtr; + Plugin::Ptr pluginPtr = nullptr; wdebug("Searching for a plugin identified by " + pluginQuery + " ..."); if (pluginQuery.empty()) @@ -110,10 +120,9 @@ Plugin::Ptr Plugin::find(const string& pluginQuery) if (hasPlugin(pluginQuery)) { wdebug("Plugin has been loaded before, returning known reference."); - auto itr = PluginPrivate::plugins.find(pluginQuery); - assert(itr != std::end(PluginPrivate::plugins)); - assert(itr->second); - return itr->second; + pluginPtr = PluginPrivate::lookupPlugin(pluginQuery); + assert(pluginPtr); + return pluginPtr; } else { @@ -178,12 +187,18 @@ bool Plugin::release(const string& pluginName) library->resolveFunction(WINTERMUTE_PLUGIN_DTOR_FUNCTION_NAME)); const bool freedPlugin = dtorFunction(plugin); + // NOTE: DO NOT use 'pluginPtr' after this point here. if (!freedPlugin) { werror("Failed to free plugin " + name + " from memory."); } + else + { + wdebug("Plugin " + name + " successfully freed."); + } + PluginPrivate::unregisterPlugin(pluginName); winfo("Plugin " + name + " unloaded."); return true; @@ -191,7 +206,8 @@ bool Plugin::release(const string& pluginName) bool Plugin::hasPlugin(const string& pluginName) { - return PluginPrivate::plugins.count(pluginName) != 0; + auto countOfPlugins = PluginPrivate::plugins.count(pluginName); + return countOfPlugins != 0; } string Plugin::name() const @@ -200,3 +216,14 @@ string Plugin::name() const return d->name; } +const list Plugin::all() +{ + list pluginNameList; + + for ( auto pluginPair : PluginPrivate::plugins ) + { + pluginNameList.push_back(pluginPair.first); + } + + return pluginNameList; +} diff --git a/src/wintermutecore/plugin.hh b/src/wintermutecore/plugin.hh index d53e03f3..06546e9d 100644 --- a/src/wintermutecore/plugin.hh +++ b/src/wintermutecore/plugin.hh @@ -34,15 +34,17 @@ public: typedef Plugin*(*CtorFunctionPtr)(void); typedef const char*(*VersionFunctionPtr)(void); typedef bool(*DtorFunctionPtr)(Plugin::Ptr&); - typedef unordered_map PluginList; + typedef unordered_map PluginMap; - static PluginList plugins; + static PluginMap plugins; Library::Ptr library; string name; explicit PluginPrivate(const string& pluginName); virtual ~PluginPrivate(); - static void registerPlugin(Plugin::Ptr& plugin); + static bool registerPlugin(Plugin::Ptr& plugin); + static bool unregisterPlugin(const string& pluginName); + static Plugin::Ptr lookupPlugin(const string& pluginQuery); }; } #endif diff --git a/src/wintermutecore/plugin.hpp b/src/wintermutecore/plugin.hpp index 3faad8bb..5a670c1c 100644 --- a/src/wintermutecore/plugin.hpp +++ b/src/wintermutecore/plugin.hpp @@ -18,10 +18,12 @@ #ifndef WINTERMUTE_PLUGIN_HPP #define WINTERMUTE_PLUGIN_HPP +#include #include #include using std::string; +using std::list; namespace Wintermute { @@ -33,6 +35,7 @@ class WINTERMUTE_EXPORT_PUBLIC Plugin : W_DEF_SHAREABLE(Plugin) #endif { + public: W_DEF_PRIVATE(Plugin) W_DECL_PTR_TYPE(Plugin) @@ -68,6 +71,8 @@ class WINTERMUTE_EXPORT_PUBLIC Plugin /* Queries the existence of this plugin in this instance of Wintermute. */ static bool hasPlugin(const string & pluginName); + static const list all(); + protected: /* Default constructor. */ explicit Plugin(const string & name); diff --git a/src/wintermutecore/tunnel.cc b/src/wintermutecore/tunnel.cc index f9304819..4c1947db 100644 --- a/src/wintermutecore/tunnel.cc +++ b/src/wintermutecore/tunnel.cc @@ -15,12 +15,20 @@ * Boston, MA 02111-1307, USA. */ +#include "logging.hpp" #include "tunnel.hh" using Wintermute::TunnelPrivate; +using Wintermute::Events::Emitter; +using Wintermute::Events::Loop; -TunnelPrivate::TunnelPrivate() +TunnelPrivate::TunnelPrivate() : + dispatchers(), + receivers(), + emitter(nullptr) { + emitter = make_shared(); + wdebug("Built up Tunnel's private data."); } TunnelPrivate::~TunnelPrivate() diff --git a/src/wintermutecore/tunnel.cpp b/src/wintermutecore/tunnel.cpp index 7422fa91..dd9c882a 100644 --- a/src/wintermutecore/tunnel.cpp +++ b/src/wintermutecore/tunnel.cpp @@ -27,62 +27,74 @@ using Wintermute::Events::Emitter; using Wintermute::Events::Loop; using Wintermute::Events::Event; using std::dynamic_pointer_cast; +using std::for_each; +using std::begin; +using std::end; +using std::make_pair; + +// TODO: Swap 'insert' for 'emplace'. W_DECLARE_SINGLETON(Tunnel) -Tunnel::Tunnel() : d_ptr(new TunnelPrivate) +Tunnel::Tunnel() : + d_ptr(make_shared()) { - Tunnel::d_ptr->emitter = make_shared(Loop::primary()); - // TODO: Add listener for incoming messages. } Tunnel::~Tunnel() { - //clearAllReceivers(); - //clearAllDispatchers(); + if (Tunnel::_instance) + { + d_ptr.reset(); + } } Emitter::Ptr Tunnel::emitter() const { W_SPRV(Tunnel); + if (d->emitter == nullptr) + { + d->emitter = make_shared(); + } + + assert(d->emitter); return d->emitter; } bool Tunnel::registerDispatcher(const Tunnel::Dispatcher::Ptr& dispatcher) { + assert(dispatcher); W_SPRV(Tunnel); - auto thePair = std::make_pair(dispatcher->name(), dispatcher); + auto thePair = make_pair(dispatcher->name(), dispatcher); d->dispatchers.insert(thePair); + return !knowsOfDispatcher(dispatcher->name()) == false; +} - // TODO: Move this into the constructor for the Dispatcher. - Tunnel::instance()->listenForEvent(W_EVENT_TUNNEL_MESSAGE, - [&dispatcher](const Event::Ptr& event) +bool Tunnel::unregisterDispatcher(const Tunnel::Dispatcher::Ptr& dispatcher) +{ + W_SPRV(Tunnel); + if (dispatcher) { - Tunnel::MessageEvent::Ptr msgEvent = - dynamic_pointer_cast(event); - - if (!msgEvent) return; // We were not meant to deal with this. - - if (msgEvent->direction == Tunnel::MessageEvent::DirectionOutgoing) - { - const Message message = msgEvent->message; - const string name = dispatcher->name(); - const bool wasSent = dispatcher->send(message); - if (!wasSent) - { - werror("Failed to send out a message using the '" + name + "' dispatcher!"); - } - } - }); + auto count = d->dispatchers.erase(dispatcher->name()); + return count == 1; + } - return !knowsOfDispatcher(dispatcher->name()) == false; + return false; } -bool Tunnel::unregisterDispatcher(const Tunnel::Dispatcher::Ptr& dispatcher) +bool Tunnel::unregisterDispatcher(const string& dispatcherName) { W_SPRV(Tunnel); - auto count = d->dispatchers.erase(dispatcher->name()); - return count == 1; + auto itr = d->dispatchers.find(dispatcherName); + if (itr == end(d->dispatchers)) + { + return false; + } + + Tunnel::Dispatcher::Ptr dispatcherPtr; + dispatcherPtr = d->dispatchers.find(dispatcherName)->second; + assert(dispatcherPtr); + return unregisterDispatcher(dispatcherPtr); } void Tunnel::clearAllDispatchers() @@ -111,33 +123,37 @@ Tunnel::Dispatcher::List Tunnel::dispatchers() bool Tunnel::registerReceiver(const Tunnel::Receiver::Ptr& receiver) { + assert(receiver); W_SPRV(Tunnel); - auto thePair = std::make_pair(receiver->name(), receiver); + auto thePair = make_pair(receiver->name(), receiver); d->receivers.insert(thePair); + return knowsOfReceiver(receiver->name()); +} - // TODO: Move this into the constructor for the Receiver. - receiver->listenForEvent("core.tunnel.message", [](const Events::Event::Ptr& eventPtr) +bool Tunnel::unregisterReceiver(const string& receiverName) +{ + W_SPRV(Tunnel); + auto itr = d->receivers.find(receiverName); + if (itr == std::end(d->receivers)) { - const Tunnel::MessageEvent::Ptr msgEvent = - std::dynamic_pointer_cast(eventPtr); - - if (!msgEvent) return; // We were not meant to deal with this. - - if (msgEvent->direction == Tunnel::MessageEvent::DirectionIncoming) - { - wdebug(msgEvent->message); - Tunnel::instance()->emitEvent(msgEvent); - } - }); + return false; + } - return !knowsOfReceiver(receiver->name()) == false; + Tunnel::Receiver::Ptr receiverPtr; + receiverPtr = d->receivers.find(receiverName)->second; + return unregisterReceiver(receiverPtr); } bool Tunnel::unregisterReceiver(const Tunnel::Receiver::Ptr& receiver) { W_SPRV(Tunnel); - auto count = d->receivers.erase(receiver->name()) == 1; - return count == 1; + if (receiver) + { + auto count = d->receivers.erase(receiver->name()) == 1; + return count == 1; + } + + return false; } void Tunnel::clearAllReceivers() @@ -166,8 +182,74 @@ Tunnel::Receiver::List Tunnel::receivers() void Tunnel::sendMessage(const Message& message) { - wdebug("Queuing message '" + static_cast(message) + "' for delivery..."); - Tunnel::MessageEvent::Ptr msgPtr = make_shared(message); + wdebug("Queuing message '" + static_cast(message) + + "' for delivery..."); + + Tunnel::MessageEvent::Ptr msgPtr(nullptr); + msgPtr = make_shared(message); msgPtr->direction = Tunnel::MessageEvent::DirectionOutgoing; instance()->emitEvent(msgPtr); + + wdebug("Message queued."); +} + +void Tunnel::start() +{ + W_SPRV(Tunnel); + auto startDispatcherFunc = + [](std::pair pair) + { + winfo("Starting the " + pair.second->name() + " dispatcher..."); + assert(pair.second); + pair.second->start(); + winfo("Started the " + pair.second->name() + " dispatcher."); + }; + auto startReceiverFunc = + [](std::pair pair) + { + winfo("Starting the " + pair.second->name() + " receiver..."); + assert(pair.second); + pair.second->start(); + winfo("Started the " + pair.second->name() + " receiver."); + }; + + wdebug("Starting the Tunnel..."); + for_each(begin(d->receivers), end(d->receivers), startReceiverFunc); + for_each(begin(d->dispatchers), end(d->dispatchers), startDispatcherFunc); + + auto eventPtr = make_shared(W_EVENT_TUNNEL_START); + assert(instance()->emitter()); + instance()->emitEvent(eventPtr); + + wdebug("Started the Tunnel."); +} + +void Tunnel::stop() +{ + auto d = Tunnel::_instance->d_func(); + + auto stopDispatcherFunc = + [](TunnelPrivate::DispatcherMap::value_type& pair) + { + winfo("Stopping the " + pair.second->name() + " dispatcher..."); + assert(pair.second); + pair.second->stop(); + winfo("Stopped the " + pair.second->name() + " dispatcher."); + }; + auto stopReceiverFunc = + [](TunnelPrivate::ReceiverMap::value_type& pair) + { + winfo("Stopping the " + pair.second->name() + " receiver..."); + assert(pair.second); + pair.second->stop(); + winfo("Stopped the " + pair.second->name() + " receiver."); + }; + + wdebug("Stopping the Tunnel..."); + for_each(begin(d->receivers), end(d->receivers), stopReceiverFunc); + for_each(begin(d->dispatchers), end(d->dispatchers), stopDispatcherFunc); + + instance()->emitEvent(make_shared(W_EVENT_TUNNEL_STOP)); + + wdebug("Stopped the Tunnel."); } diff --git a/src/wintermutecore/tunnel.hh b/src/wintermutecore/tunnel.hh index 16bac869..c4b59420 100644 --- a/src/wintermutecore/tunnel.hh +++ b/src/wintermutecore/tunnel.hh @@ -31,13 +31,11 @@ class TunnelPrivate public: typedef unordered_map DispatcherMap; typedef unordered_map ReceiverMap; - typedef stack MessageQueue; explicit TunnelPrivate(); - virtual ~TunnelPrivate(); + ~TunnelPrivate(); DispatcherMap dispatchers; ReceiverMap receivers; - MessageQueue obtainedMessages; Events::Emitter::Ptr emitter; }; } diff --git a/src/wintermutecore/tunnel.hpp b/src/wintermutecore/tunnel.hpp index aaf612b7..ec080bdb 100644 --- a/src/wintermutecore/tunnel.hpp +++ b/src/wintermutecore/tunnel.hpp @@ -24,6 +24,8 @@ #include #define W_EVENT_TUNNEL_MESSAGE "core.tunnel.message" +#define W_EVENT_TUNNEL_STOP "core.tunnel.stop" +#define W_EVENT_TUNNEL_START "core.tunnel.start" using std::list; @@ -31,8 +33,8 @@ namespace Wintermute { class Message; class TunnelPrivate; -struct DispatcherPrivate; -struct ReceiverPrivate; +class DispatcherPrivate; +class ReceiverPrivate; /** * @brief Handles the act of sending and receiving new messages. @@ -84,6 +86,8 @@ class Tunnel : public Events::Emittable { public: W_DECL_PTR_TYPE(Dispatcher) + + /* Defines a list of Dispatcher objects. */ typedef std::list List; /* Public destructor. */ @@ -95,10 +99,15 @@ class Tunnel : public Events::Emittable /* Sends out the provided message over the wire. */ virtual bool send(const Message & message) = 0; + /* The emitter that this Dispatcher uses. */ virtual Events::Emitter::Ptr emitter() const; + virtual void start() = 0; + + virtual void stop() = 0; + protected: - /* Protected constructor. */ + /* Constructor. */ explicit Dispatcher(const string & name); private: @@ -113,10 +122,10 @@ class Tunnel : public Events::Emittable * @sa Wintermute::Tunnel * @sa Wintermute::Message * - * `Receiver` classes are used by Wintermute to handle the act of obtaining - * a `Message` from any arbitrary format and converting it into a composite - * `Message` locally. This level of abstraction allows Wintermute to fetch a - * `Message` without any real concern from where or how the `Message`came + * Receiver classes are used by Wintermute to handle the act of obtaining + * a Message from any arbitrary format and converting it into a composite + * Message locally. This level of abstraction allows Wintermute to fetch a + * Message without any real concern from where or how the Message came * from or came to be, respectfully. * * @todo Look into adding ones that have some form of encryption. @@ -135,17 +144,21 @@ class Tunnel : public Events::Emittable // Public destructor. virtual ~Receiver(); - /** - * Obtains the name of this Receiver. - */ + /* Obtains the name of this Receiver. */ string name() const; - ///< The Emitter object used by this Receiver. + /* The Emitter object used by this Receiver. */ virtual Events::Emitter::Ptr emitter() const; + virtual void start() = 0; + + virtual void stop() = 0; + protected: explicit Receiver(const string & name); + void handleMessage(const Message& message); + private: W_DEF_PRIVATE(Receiver) }; @@ -156,6 +169,8 @@ class Tunnel : public Events::Emittable // Unregisters a dispatcher into the Tunnel. static bool unregisterDispatcher(const Dispatcher::Ptr& dispatcher); + static bool unregisterDispatcher(const string& dispatcherName); + // Removes all of the dispatchers from the Tunnel. static void clearAllDispatchers(); @@ -171,6 +186,8 @@ class Tunnel : public Events::Emittable // Unregisters a receiver into the Tunnel. static bool unregisterReceiver(const Receiver::Ptr& receiver); + static bool unregisterReceiver(const string& receiverName); + // Removes all of the receivers from the Tunnel. static void clearAllReceivers(); @@ -183,6 +200,10 @@ class Tunnel : public Events::Emittable // Sends a message through the Tunnel through all dispatchers. static void sendMessage(const Message& message); + static void start(); + + static void stop(); + /** * Represents an event relating to a Message. */ @@ -215,6 +236,6 @@ class Tunnel : public Events::Emittable Direction direction; }; }; -} +} /* end namespace Wintermute */ #endif diff --git a/src/wintermutecore/tunnel_dispatcher.cpp b/src/wintermutecore/tunnel_dispatcher.cpp index d38c4538..b07655e9 100644 --- a/src/wintermutecore/tunnel_dispatcher.cpp +++ b/src/wintermutecore/tunnel_dispatcher.cpp @@ -19,26 +19,58 @@ #include "tunnel.hpp" #include "logging.hpp" +using std::dynamic_pointer_cast; using Wintermute::Tunnel; using Wintermute::DispatcherPrivate; using Wintermute::Events::Emitter; +using Wintermute::Events::Event; +using Wintermute::Events::Loop; -Tunnel::Dispatcher::Dispatcher(const string& theName) : d_ptr(new DispatcherPrivate) +Tunnel::Dispatcher::Dispatcher(const string& theName) : + d_ptr(new DispatcherPrivate) { W_PRV(Dispatcher); d->name = theName; - d->emitter = make_shared(Tunnel::instance()->emitter()->loop()); + Loop::Ptr loop = Tunnel::instance()->emitter()->loop(); + d->emitter = make_shared(loop); + auto cb = [this](const Event::Ptr& event) + { + assert(event); + Tunnel::MessageEvent::Ptr msgEvent = + dynamic_pointer_cast(event); + assert(msgEvent); + + if (!msgEvent) return; // We were not meant to deal with this. + + if (msgEvent->direction == Tunnel::MessageEvent::DirectionOutgoing) + { + wdebug("Obtained a MessageEvent for sending a message for " + + this->name() + "."); + const Message message = msgEvent->message; + const bool wasSent = this->send(message); + + if (!wasSent) + { + werror("Failed to send out a message using the '" + this->name() + + "' dispatcher!"); + } + } + }; + + Tunnel::instance()->listenForEvent(W_EVENT_TUNNEL_MESSAGE, cb); wdebug("Built a new dispatcher for the tunnel called " + name() + "."); } string Tunnel::Dispatcher::name() const { W_PRV(const Dispatcher); + assert(!d->name.empty()); return d->name; } Emitter::Ptr Tunnel::Dispatcher::emitter() const { W_PRV(Dispatcher); + assert(d->emitter); return d->emitter; } @@ -46,4 +78,3 @@ Tunnel::Dispatcher::~Dispatcher() { wdebug("Destroyed the " + name() + " dispatcher."); } - diff --git a/src/wintermutecore/tunnel_dispatcher.hh b/src/wintermutecore/tunnel_dispatcher.hh index 985cde40..c22ff920 100644 --- a/src/wintermutecore/tunnel_dispatcher.hh +++ b/src/wintermutecore/tunnel_dispatcher.hh @@ -20,7 +20,7 @@ namespace Wintermute { -struct DispatcherPrivate +class DispatcherPrivate { public: string name; diff --git a/src/wintermutecore/tunnel_receiver.cpp b/src/wintermutecore/tunnel_receiver.cpp index b9248984..f249c1b7 100644 --- a/src/wintermutecore/tunnel_receiver.cpp +++ b/src/wintermutecore/tunnel_receiver.cpp @@ -22,13 +22,39 @@ using Wintermute::Tunnel; using Wintermute::ReceiverPrivate; using Wintermute::Events::Emitter; +using Wintermute::Events::Event; +using Wintermute::Events::Loop; -Tunnel::Receiver::Receiver(const string& theName) : d_ptr(new ReceiverPrivate) +Tunnel::Receiver::Receiver(const string& theName) : + d_ptr(new ReceiverPrivate) { W_PRV(Receiver); d->name = theName; - d->emitter = make_shared(Tunnel::instance()->emitter()->loop()); - wdebug("Built a new receiver for the tunnel called " + name() + "."); + Loop::Ptr loop = Tunnel::instance()->emitter()->loop(); + assert(loop); + d->emitter = make_shared(loop); + + auto cb = [](const Event::Ptr& eventPtr) + { + const Tunnel::MessageEvent::Ptr msgEvent = + std::dynamic_pointer_cast(eventPtr); + + if (!msgEvent) return; // We were not meant to deal with this. + + if (msgEvent->direction == Tunnel::MessageEvent::DirectionIncoming) + { + wdebug("Incoming message: " + (string) msgEvent->message); + Tunnel::instance()->emitEvent(msgEvent); + } + }; + + assert ( listenForEvent(W_EVENT_TUNNEL_MESSAGE, cb) ); + wdebug("Built a new receiver for the tunnel called '" + name() + "'."); +} + +Tunnel::Receiver::~Receiver() +{ + wdebug("Destroyed the " + name() + " receiver."); } string Tunnel::Receiver::name() const @@ -43,7 +69,12 @@ Emitter::Ptr Tunnel::Receiver::emitter() const return d->emitter; } -Tunnel::Receiver::~Receiver() +void Tunnel::Receiver::handleMessage(const Message& message) { - wdebug("Destroyed the " + name() + " receiver."); + wdebug("Obtained " + static_cast(message) + + "; raising in Tunnel..."); + MessageEvent::Ptr msgEvent = make_shared(message); + msgEvent->direction = MessageEvent::DirectionIncoming; + Tunnel::instance()->emitEvent(msgEvent); + wdebug("Raised Message in Tunnel."); } diff --git a/src/wintermutecore/tunnel_receiver.hh b/src/wintermutecore/tunnel_receiver.hh index 809701b0..97930f3e 100644 --- a/src/wintermutecore/tunnel_receiver.hh +++ b/src/wintermutecore/tunnel_receiver.hh @@ -20,7 +20,7 @@ namespace Wintermute { -struct ReceiverPrivate +class ReceiverPrivate { public: string name; diff --git a/src/wintermutecore/util.hh b/src/wintermutecore/util.hh index 6b1db81c..4b97f538 100644 --- a/src/wintermutecore/util.hh +++ b/src/wintermutecore/util.hh @@ -26,6 +26,7 @@ #include #include #include +#include using std::list; using std::regex; @@ -33,11 +34,14 @@ using std::string; using std::copy; using std::sregex_token_iterator; using std::back_inserter; +using std::begin; +using std::end; namespace Wintermute { namespace Util { + // TODO: Document. inline list split_string(const string& str, const regex& delim) { list tokens; @@ -47,6 +51,43 @@ namespace Util return tokens; } + + // TODO: Document. + inline string join_string(const list& tokens, const string& delim) + { + string resultingString; + + if (!tokens.empty()) + { + resultingString = tokens.front(); + + for_each(++begin(tokens), end(tokens), [&](const string& token) + { + resultingString += delim + token; + }); + } + + return resultingString; + } + + /** + * @fn Wintermute::Util::generate_uuid + * @return A UUID as a string. + * Generates a UUID. + */ + inline string generate_uuid() + { + uuid_t uuidObj; + uuid_generate_random(uuidObj); + string uuidStr; + + for (auto i = 0; i <= 15; i++) + { + uuidStr += std::to_string(uuidObj[i]); + } + + return uuidStr; + } } } diff --git a/src/wintermutecore/util/configuration.cpp b/src/wintermutecore/util/configuration.cpp index a885eedd..7d42052f 100644 --- a/src/wintermutecore/util/configuration.cpp +++ b/src/wintermutecore/util/configuration.cpp @@ -19,6 +19,7 @@ */ #include +#include "util.hh" #include "logging.hpp" #include "configuration.hh" #include "configuration.hpp" @@ -28,6 +29,8 @@ using Wintermute::Util::Serializable; using Wintermute::Util::ConfigurationPrivate; using libconfig::Setting; using std::to_string; +using Wintermute::Util::join_string; +using Wintermute::Util::split_string; Configuration::Configuration(const string& name) : d_ptr(new ConfigurationPrivate) @@ -112,6 +115,15 @@ bool Configuration::has(const string& key) const return d->config->exists(key); } +list Configuration::get(const string& key, + const list& defaultValue) const +{ + const string theOriginalValue = get(key, string()); + std::regex delim("[;]", std::regex::basic); + list value = split_string(theOriginalValue, delim); + return defaultValue; +} + #define _W_CFG_SET_VALUE(Value, Key, Type) \ W_PRV(Configuration); \ Setting* cfg = d->addSetting(Key, Type); \ @@ -138,3 +150,9 @@ bool Configuration::set(const string& key, const float& value) { _W_CFG_SET_VALUE(value, key, Setting::TypeFloat); } + +bool Configuration::set(const string& key, const list& value) +{ + const string finalString = join_string(value, ";"); + return set(key, finalString); +} diff --git a/src/wintermutecore/util/configuration.hpp b/src/wintermutecore/util/configuration.hpp index d8dacc56..0a0de253 100644 --- a/src/wintermutecore/util/configuration.hpp +++ b/src/wintermutecore/util/configuration.hpp @@ -61,6 +61,7 @@ class WINTERMUTE_EXPORT_PUBLIC Configuration bool has(const string& key) const; string get(const string& key, const string& defaultValue) const; + list get(const string& key, const list& defaultValue) const; bool get(const string& key, const bool& defaultValue) const; int get(const string& key, const int& defaultValue) const; float get(const string& key, const float& defaultValue) const; @@ -69,6 +70,7 @@ class WINTERMUTE_EXPORT_PUBLIC Configuration bool set(const string& key, const bool& value); bool set(const string& key, const int& value); bool set(const string& key, const float& value); + bool set(const string& key, const list& value); }; } } diff --git a/src/wintermutecore/util/invokable.hpp b/src/wintermutecore/util/invokable.hpp index 5266c3d5..5df71f85 100644 --- a/src/wintermutecore/util/invokable.hpp +++ b/src/wintermutecore/util/invokable.hpp @@ -28,7 +28,7 @@ namespace Wintermute namespace Util { template -struct InvokablePrivate; +class InvokablePrivate; /** * Represents the interface required to have an invokable object to Wintermute. diff --git a/src/wintermutecore/util/serializable.cpp b/src/wintermutecore/util/serializable.cpp index de70b70a..779b9f7e 100644 --- a/src/wintermutecore/util/serializable.cpp +++ b/src/wintermutecore/util/serializable.cpp @@ -112,3 +112,16 @@ Serializable::operator Serializable::Map() const { return serialize(); } + +Serializable& Serializable::operator =(const string& jsonString) +{ + Map map = fromString(jsonString); + deserialize(map); + return *this; +} + +Serializable& Serializable::operator =(const Map& data) +{ + deserialize(data); + return *this; +} diff --git a/src/wintermutecore/util/serializable.hpp b/src/wintermutecore/util/serializable.hpp index ae9cf67c..c11664a6 100644 --- a/src/wintermutecore/util/serializable.hpp +++ b/src/wintermutecore/util/serializable.hpp @@ -49,6 +49,12 @@ class Serializable /// Converts this serialized data into a map. operator Map() const; + /// Sets this object to the data stored in the stringified hash. + Serializable& operator =(const PlainType& jsonString); + + /// Sets this object to the data stored in the hash. + Serializable& operator =(const Map& data); + /** * Takes a map and makes it into to the plain data type. * @param data The key-value pairs used to craft said structure. diff --git a/test/bootstrap b/test/bootstrap index 04dc58df..dfba0c9b 100755 --- a/test/bootstrap +++ b/test/bootstrap @@ -17,7 +17,8 @@ bootstrap_build() { sudo apt-get update -qq echo -en "\n[build]: installing packages...\n" - sudo apt-get install -qq build-essential + sudo apt-get install -qq libtool automake autconf bison + sudo apt-get install -qq pkg-config sudo apt-get install -qq python-pip python sudo pip install cpp-coveralls --upgrade @@ -31,10 +32,9 @@ bootstrap_build() { sudo apt-get install -qq cxxtest sudo apt-get install -qq valgrind sudo apt-get install -qq lcov - sudo apt-get install -qq pkg-config - sudo apt-get remove -qq g++* - sudo apt-get install -qq g++-4.9 clang-3.5 sudo apt-get remove -qq g++-4.6 + sudo apt-get install -qq g++-4.9 clang-3.5 + sudo apt-get install -qq libzmq3-dev libzmqpp-dev install_libuv install_cmake @@ -59,6 +59,7 @@ install_libuv() { sudo apt-get install git libtool autoconf automake -qq; git clone git://github.com/libuv/libuv ~/libuv; + git checkout v1.2.1; cd ~/libuv; sh autogen.sh; ./configure --prefix=/usr; @@ -71,6 +72,10 @@ case $1 in bootstrap_build || exit 8 ;; "--generate" ) + if [[ $CXX == "gcc" ]]; then + export CXX="$(which g++-4.9)" + fi + cmake . -DCMAKE_CXX_COMPILER=$CXX -DCMAKE_BUILD_TYPE=Debug -DCMAKE_VERBOSE_MAKEFILE=ON -DCI_BUILD=ON || exit 4 ;; "--cmake" ) diff --git a/test/fixtures/wintermute_sample.cc b/test/fixtures/wintermute_sample.cc index 717b9dcd..66acb0f2 100644 --- a/test/fixtures/wintermute_sample.cc +++ b/test/fixtures/wintermute_sample.cc @@ -15,6 +15,7 @@ * Boston, MA 02111-1307, USA. */ +#define WINTER_SAMPLE 1 #include #include "test_suite.hpp" #include "wintermute_sample.hh" diff --git a/test/include/fixtures.hpp b/test/include/fixtures.hpp index 58c256e0..33b7f344 100644 --- a/test/include/fixtures.hpp +++ b/test/include/fixtures.hpp @@ -15,19 +15,73 @@ * Boston, MA 02111-1307, USA. */ +#include #include #include #include #include #include +#include #include -#include +#include #include "wintermutecore/event_loop.hh" +#include "wintermutecore/plugin.hpp" +#include "wintermutecore/plugin.hh" -using std::string; +using Wintermute::Plugin; + +#define KILL_TEST_TIMEOUT 2000 // TODO: Compile a list of faux values. +// {{{ Value Traits for CxxTest +CXXTEST_ENUM_TRAITS(Wintermute::Library::LoadState, + CXXTEST_ENUM_MEMBER(Wintermute::Library::LoadIsLoaded) + CXXTEST_ENUM_MEMBER(Wintermute::Library::LoadNotLoaded) + CXXTEST_ENUM_MEMBER(Wintermute::Library::LoadStateFailure) + CXXTEST_ENUM_MEMBER(Wintermute::Library::LoadStateSuccess) + CXXTEST_ENUM_MEMBER(Wintermute::Library::LoadUndefined) +); +// }}} + +Wintermute::Plugin::Ptr wntrtest_load_plugin(const string& pluginName) +{ + Plugin::Ptr pluginPtr = nullptr; + TSM_ASSERT ( + "Plugin " + pluginName + " has not been loaded already.", + !Plugin::hasPlugin(pluginName) + ); + + const string libDir(TEST_BASE_DIR "/../lib"); + setenv(WINTERMUTE_ENV_PLUGIN_PATH, libDir.c_str() , 1); + + TSM_ASSERT_THROWS_NOTHING ( + "Load and obtain a pointer to the Plugin.", + pluginPtr = Plugin::find(pluginName) + ); + + TSM_ASSERT ( + "Confirm that the plugin pointer is valid.", + pluginPtr != nullptr && pluginPtr + ); + + TSM_ASSERT ( + "Confirm that the plugin pool is aware of " + pluginName, + Plugin::hasPlugin(pluginName) == true + ); + + unsetenv(WINTERMUTE_ENV_PLUGIN_PATH); + return pluginPtr; +} + +void wntrtest_unload_plugin(const string& pluginName) +{ + TSM_ASSERT ( "Freed plugin", + Plugin::release(pluginName) ); + TSM_ASSERT ( "Removed plugin.", + !Plugin::hasPlugin(pluginName) ); +} + // Generates a random designation. Wintermute::Module::Designation craftRandomDesignation() { @@ -41,16 +95,55 @@ Wintermute::Message craftRandomMessage() return Wintermute::Message(data, craftRandomDesignation(), craftRandomDesignation()); } +class SamplePlugin : public Wintermute::Plugin +{ +public: + SamplePlugin(const string& newName = "sample") + : Wintermute::Plugin(newName) + { + } + + ~SamplePlugin() + { + } + + virtual bool startup() final { return true; } + virtual bool shutdown() final { return true; } + virtual PluginType type() const final { return PluginType::PluginTypeService; }; + + void addToPool() + { + Plugin::Ptr pluginPtr = shared_from_this(); + Wintermute::PluginPrivate::registerPlugin(pluginPtr); + } + + void removeFromPool() + { + Wintermute::PluginPrivate::unregisterPlugin(name()); + } +}; + class SampleModule : public Wintermute::Module { public: explicit SampleModule(const unsigned int index = 1) : Wintermute::Module( - Wintermute::Module::Designation("input", "test" + std::to_string(index) + ".wintermute.in") + Wintermute::Module::Designation( + "input", + "test" + std::to_string(index) + ".wintermute.in" + ) ) { - winfo("SampleModule: My name is " + (string) designation()); + winfo("SampleModule: My name is " + (string) designation() + "."); + } + + SampleModule(const Wintermute::Module::Designation& des) : + Module(des) + { + winfo("SampleModule: I'm pretending to be " + (string) des + "."); } + + virtual ~SampleModule() { } }; class SampleLoop : public Wintermute::Events::Loop @@ -77,6 +170,9 @@ class SampleDispatcher : public Wintermute::Tunnel::Dispatcher return message == sendingMessage; } + virtual void start() { } + virtual void stop() { } + Wintermute::Message message; }; @@ -96,9 +192,27 @@ class SampleReceiver : public Wintermute::Tunnel::Receiver emitEvent(msgPtr); } + virtual void start() { } + virtual void stop() { } + Wintermute::Message message; }; +class SampleConsumingDispatcher : public Wintermute::Tunnel::Dispatcher +{ +public: + explicit SampleConsumingDispatcher(const string& theName = "sample") : Wintermute::Tunnel::Dispatcher(theName) { } + virtual ~SampleConsumingDispatcher() { } + + virtual bool send (const Wintermute::Message& sendingMessage) + { + receiver->message = sendingMessage; + return true; + } + + SharedPtr receiver; +}; + class SampleVoidCall : public Wintermute::Call { public: @@ -106,7 +220,7 @@ class SampleVoidCall : public Wintermute::Call { Call::Function func = [ = ](const string & arguments) -> const string { - assert(arguments == arguments); + winfo(arguments); return string(); }; @@ -128,7 +242,7 @@ class SampleCallWithValue : public Wintermute::Call { Call::Function func = [&value](const string & arguments) -> const string { - assert(arguments == arguments); + winfo(arguments); return value; }; @@ -145,7 +259,7 @@ class SampleVoidModuleCall : public Wintermute::Module::Call { Call::Function func = [](const string & arguments) -> const string { - assert(arguments == arguments); + winfo(arguments); return string(); }; @@ -163,7 +277,7 @@ class SampleModuleCallWithValue : public Wintermute::Module::Call { Call::Function func = [ &value ](const string & arguments) -> const string { - assert(arguments == arguments); + winfo(arguments); return value; }; diff --git a/test/include/test_suite.hpp.in b/test/include/test_suite.hpp.in index 7ba546c6..4af95539 100644 --- a/test/include/test_suite.hpp.in +++ b/test/include/test_suite.hpp.in @@ -24,7 +24,10 @@ #define SAMPLE_PLUGIN_PATH "@CMAKE_BINARY_DIR@/lib/libwintermutesample.so" #define SAMPLE_TEST_FILE "@CMAKE_SOURCE_DIR@/CMakeLists.txt" +#ifndef WINTER_SAMPLE #include "fixtures.hpp" +#endif + #include #endif diff --git a/test/unit/event_emitter.hh b/test/unit/event_emitter.hh index 5614a87e..8bc3c4fe 100644 --- a/test/unit/event_emitter.hh +++ b/test/unit/event_emitter.hh @@ -38,7 +38,7 @@ public: emitter = make_shared(loop) ); - TS_ASSERT_THROWS ( + TS_ASSERT_THROWS( emitter2 = make_shared(nullptr), std::invalid_argument ); @@ -113,6 +113,7 @@ public: TS_ASSERT_THROWS_NOTHING ( emitter->listen("sample", onceListener) ); TS_ASSERT_THROWS_NOTHING ( emitter->listen("sample", allListener) ); TS_ASSERT_THROWS_NOTHING ( emitter->emit(event) ); + TS_ASSERT_THROWS_NOTHING ( emitter->emit(event) ); auto sampleListeners = emitter->listeners("sample"); auto allListenerItr = find(begin(sampleListeners), end(sampleListeners), allListener); diff --git a/test/unit/event_listener.hh b/test/unit/event_listener.hh index 8d07957e..e59d912d 100644 --- a/test/unit/event_listener.hh +++ b/test/unit/event_listener.hh @@ -61,4 +61,3 @@ public: }; - diff --git a/test/unit/event_poller.hh b/test/unit/event_poller.hh index 30eb5f53..c3deaec4 100644 --- a/test/unit/event_poller.hh +++ b/test/unit/event_poller.hh @@ -16,9 +16,10 @@ */ #include +#include +#include #include "test_suite.hpp" #include "wintermutecore/events.hpp" -#include "wintermutecore/globals.hh" using Wintermute::Events::Event; using Wintermute::Events::Loop; @@ -27,12 +28,12 @@ using std::dynamic_pointer_cast; SharedPtr open_local_socket_for_test(uv_loop_t* loopPtr) { - int r = 0; + auto nowTime = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()); + string nowStr = ctime(&nowTime); + string pathToSocket = TEST_BASE_DIR "/wintermute-test-" + nowStr + ".sock\0"; SharedPtr pipePtr = make_shared(); - r = uv_pipe_init(loopPtr, pipePtr.get(), 0); - W_CHECK_UV(r, "uv_pipe_init"); - uv_pipe_bind(pipePtr.get(), TEST_BASE_DIR "/wintermute-test.sock"); - W_CHECK_UV(r, "uv_pipe_bind"); + W_CHECK_UV(uv_pipe_init(loopPtr, pipePtr.get(), 0), "uv_pipe_init"); + W_CHECK_UV(uv_pipe_bind(pipePtr.get(), pathToSocket.c_str() ), "uv_pipe_bind"); return pipePtr; } @@ -70,8 +71,7 @@ public: { bool isCalled = false; int fileDesc = 0, r = 0; - SampleLoop::Ptr loop = make_shared(); - uv_loop_t* loopPtr = loop->uvLoop(); + Loop::Ptr loop = Loop::primary(); Poller::Ptr poller; auto invokeFunction = [&isCalled, &poller](const Event::Ptr & eventPtr) -> void { @@ -80,7 +80,7 @@ public: isCalled = true; }; - SharedPtr pipePtr = open_local_socket_for_test(loopPtr); + SharedPtr pipePtr = open_local_socket_for_test(uv_default_loop()); r = uv_fileno((uv_handle_t*)pipePtr.get(), &fileDesc); W_CHECK_UV(r, "uv_fileno"); TS_ASSERT_DIFFERS ( fileDesc, 0 ); @@ -89,12 +89,10 @@ public: TS_ASSERT ( poller ); poller->listenForEvent("core.events.poll", invokeFunction, Listener::FrequencyOnce); - TS_ASSERT ( poller->start() ); - TS_ASSERT ( loop->run() ); + TSM_ASSERT ( "Poller started.", poller->start() ); + TSM_ASSERT ( "Loop ran.", loop->run() ); TSM_ASSERT ( "Event 'core.events.poll' was emitted by poller.", isCalled ); uv_close((uv_handle_t*)pipePtr.get(), NULL); } }; - - diff --git a/test/unit/event_timer.hh b/test/unit/event_timer.hh index 0c505b6f..aedfa535 100644 --- a/test/unit/event_timer.hh +++ b/test/unit/event_timer.hh @@ -57,6 +57,7 @@ public: { assert(event); TS_TRACE("We get here."); + assert(event); timer->stop(); }, Listener::FrequencyEvery); timer->setInterval(0); diff --git a/test/unit/library.hh b/test/unit/library.hh index 1828b2bf..2831933d 100644 --- a/test/unit/library.hh +++ b/test/unit/library.hh @@ -23,124 +23,181 @@ using Wintermute::Logging; using Wintermute::Library; -Library::Ptr fetchWorkingLibrary() -{ - Library::Ptr libraryPtr = std::make_shared(SAMPLE_PLUGIN_PATH); - TSM_ASSERT ( "Library pointer allocated with full file path.", - libraryPtr ); - TSM_ASSERT_EQUALS ( "Library reports proper status.", - libraryPtr->loadedStatus(), - Library::LoadIsLoaded ); - TSM_ASSERT_EQUALS ( "Library reports provided file name.", - libraryPtr->filename(), - SAMPLE_PLUGIN_PATH ); - return libraryPtr; -} - class LibraryTestSuite : public CxxTest::TestSuite { public: + Library::Ptr libraryPtr; + void tearDown() { unsetenv(WINTERMUTE_ENV_PLUGIN_PATH); + if (!libraryPtr) + { + libraryPtr = nullptr; + } } void setUp() { - DISABLE_LOGGING; + if (libraryPtr && libraryPtr->loadedStatus() == Library::LoadIsLoaded) + { + TS_ASSERT ( libraryPtr->unload() ); + } + + if (!libraryPtr) + { + libraryPtr = nullptr; + } + } + + Library::Ptr fetchWorkingLibrary(const string& thePath = SAMPLE_PLUGIN_PATH) + { + libraryPtr = std::make_shared(thePath); + + TSM_ASSERT ( + "Library pointer allocated with " + thePath, + libraryPtr + ); + + TSM_ASSERT_EQUALS ( + "Library loads successfully.", + libraryPtr->loadedStatus(), + Library::LoadIsLoaded + ); + + return libraryPtr; } void testLoadLibraryWithNoName() { Library::Ptr libraryWithNoName = std::make_shared(""); - TSM_ASSERT ( "Library pointer allocated.", libraryWithNoName ); - TSM_ASSERT_EQUALS ( "Library not loaded.", libraryWithNoName->loadedStatus(), + + TSM_ASSERT ( "Library pointer allocated.", + libraryWithNoName ); + + TSM_ASSERT_EQUALS ( "Library not loaded.", + libraryWithNoName->loadedStatus(), Library::LoadUndefined ); } void testLoadLibraryWithFullFilePath() { - Library::Ptr libraryWithFullFilePath = std::make_shared(SAMPLE_PLUGIN_PATH); + Library::Ptr libraryWithFullFilePath = + std::make_shared(SAMPLE_PLUGIN_PATH); + TSM_ASSERT ( "Library pointer allocated with full file path.", libraryWithFullFilePath ); + TSM_ASSERT_EQUALS ( "Library reports proper status.", libraryWithFullFilePath->loadedStatus(), Library::LoadIsLoaded ); - TSM_ASSERT_EQUALS ( "Library reports provided file name;.", + + TSM_ASSERT_EQUALS ( "Library reports provided file name.", libraryWithFullFilePath->filename(), SAMPLE_PLUGIN_PATH ); } void testUnloadingLibrary() { - Library::Ptr libraryPtr = fetchWorkingLibrary(); + Library::Ptr aLibraryPtr = + std::make_shared(SAMPLE_PLUGIN_PATH); + TSM_ASSERT_EQUALS ( "Library unloaded successfully.", - libraryPtr->unload(), + aLibraryPtr->unload(), Library::LoadNotLoaded ); - TSM_ASSERT_EQUALS ( "Library status = LoadNotLoaded", - libraryPtr->loadedStatus(), + + TSM_ASSERT_EQUALS ( "Library status == LoadNotLoaded", + aLibraryPtr->loadedStatus(), Library::LoadNotLoaded ); + TSM_ASSERT ( "Wipe filename on unload.", - libraryPtr->filename().empty() ); + aLibraryPtr->filename().empty() ); } void testDiscoverLibraryByName() { - setenv(WINTERMUTE_ENV_PLUGIN_PATH, string(TEST_BASE_DIR "/fixtures").c_str(), 1); - Library::Ptr libraryByNamePtr = Library::find(SAMPLE_PLUGIN_NAME); - TSM_ASSERT ( "Library pointer allocated with full file path.", - libraryByNamePtr ); - TSM_ASSERT_EQUALS ( "Library reports proper status.", - libraryByNamePtr->loadedStatus(), - Library::LoadIsLoaded ); - TSM_ASSERT ( "Library reports provided file name.", - libraryByNamePtr->filename().find_last_of(SAMPLE_PLUGIN_FILE_NAME) != string::npos); + setenv(WINTERMUTE_ENV_PLUGIN_PATH, + string(TEST_BASE_DIR "/../lib").c_str(), 1); + + libraryPtr = Library::find(SAMPLE_PLUGIN_NAME); + + TSM_ASSERT ( + "Library pointer allocated.", + libraryPtr + ); + + TSM_ASSERT_EQUALS ( + "Library reports proper status.", + libraryPtr->loadedStatus(), + Library::LoadIsLoaded + ); + + TSM_ASSERT_DIFFERS ( + "Library reports provided file name.", + libraryPtr->filename().find_last_of(SAMPLE_PLUGIN_FILE_NAME), + string::npos + ); } void testDiscoverLibraryByRelativeFileName() { setenv(WINTERMUTE_ENV_PLUGIN_PATH, TEST_BASE_DIR, 1); - Library::Ptr libraryByRelativeFileNamePtr = Library::find("fixtures/" SAMPLE_PLUGIN_FILE_NAME); + + libraryPtr = Library::find("fixtures/" SAMPLE_PLUGIN_FILE_NAME); + TSM_ASSERT ( "Library pointer allocated with full file path.", - libraryByRelativeFileNamePtr ); + libraryPtr ); + TSM_ASSERT_EQUALS ( "Library reports proper status.", - libraryByRelativeFileNamePtr->loadedStatus(), + libraryPtr->loadedStatus(), Library::LoadIsLoaded ); - TSM_ASSERT ( "Library reports provided file name.", - libraryByRelativeFileNamePtr->filename().find_last_of(SAMPLE_PLUGIN_FILE_NAME) != string::npos); + + TSM_ASSERT_DIFFERS ( "Library reports provided file name.", + libraryPtr->filename().find_last_of(SAMPLE_PLUGIN_FILE_NAME), + string::npos); } void testDiscoverLibraryByFullFilePath() { - Library::Ptr libraryByFullFilePathPtr = Library::find(SAMPLE_PLUGIN_PATH); + libraryPtr = Library::find(SAMPLE_PLUGIN_PATH); + TSM_ASSERT ( "Library pointer allocated with name.", - libraryByFullFilePathPtr ); + libraryPtr ); + TSM_ASSERT_EQUALS ( "Library reports proper status.", - libraryByFullFilePathPtr->loadedStatus(), + libraryPtr->loadedStatus(), Library::LoadIsLoaded ); + TSM_ASSERT_EQUALS ( "Library reports provided file name.", - libraryByFullFilePathPtr->filename(), + libraryPtr->filename(), SAMPLE_PLUGIN_PATH ); } void testResolveNoFunctionFromLibrary() { - Library::Ptr libraryPtr = fetchWorkingLibrary(); + libraryPtr = fetchWorkingLibrary(); Library::FunctionPtr okFunctionPtr = libraryPtr->resolveFunction(""); TSM_ASSERT ( "Didn't resolved empty function name from library.", !okFunctionPtr ); } - void testResolveFunctionFromLibrary() + void NOtestResolveFunctionFromLibrary() { - Library::Ptr libraryPtr = fetchWorkingLibrary(); + libraryPtr = fetchWorkingLibrary(); Library::FunctionPtr okFunctionPtr = libraryPtr->resolveFunction("w_sample_test"); + TSM_ASSERT ( "Resolved function 'w_sample_test' from library.", okFunctionPtr ); - int (*aFunction)(void); + + int (*aFunction)(void) = 0; W_RESOLVE_FUNCTION(aFunction, okFunctionPtr); + + TSM_ASSERT ( "Valid function pointer obtained.", + aFunction); + TSM_ASSERT_EQUALS ( "Function invoked with expected return value.", aFunction(), 2014); } }; + diff --git a/test/unit/module_designation.hh b/test/unit/module_designation.hh index 7f251d78..eb3ce501 100644 --- a/test/unit/module_designation.hh +++ b/test/unit/module_designation.hh @@ -28,7 +28,6 @@ public: Module::Designation des("local", "in.wintermute.test"); TSM_ASSERT_EQUALS ( "Matches name()", des.name(), "local" ); TSM_ASSERT_EQUALS ( "Matches domain()", des.domain(), "in.wintermute.test" ); - //TSM_ASSERT_EQUALS ( "Matches pid()", des.pid(), getpid() ); } void testConstructWithCustomPID(void) @@ -36,7 +35,6 @@ public: Module::Designation des("local", "in.wintermute.test"); TS_ASSERT_EQUALS ( des.name(), "local" ); TS_ASSERT_EQUALS ( des.domain(), "in.wintermute.test" ); - //TS_ASSERT_EQUALS ( des.pid(), 300 ); } void testAttemptDeserialization(void) @@ -47,7 +45,6 @@ public: TS_ASSERT ( !des.isNull() ); TS_ASSERT_EQUALS ( des.name(), "wintermute" ); TS_ASSERT_EQUALS ( des.domain(), "me.jalcine" ); - //TS_ASSERT_EQUALS ( des.pid(), 300 ); TS_ASSERT_EQUALS ( desBuilt, des ); } @@ -56,14 +53,12 @@ public: TS_ASSERT_EQUALS ( Module::Designation("foo", "bar"), Module::Designation("foo", "bar") ); TS_ASSERT ( Module::Designation("foo", "foo") != Module::Designation("foo", "bar") ); TS_ASSERT ( Module::Designation("baz", "bar") != Module::Designation("foo", "bar") ); - //TS_ASSERT ( Module::Designation("foo", "bar") ); } void testLocalByDefault(void) { Module::Designation des("foo", "bar.nation"); - //TS_ASSERT_EQUALS ( des.pid(), getpid() ); - //TS_ASSERT ( static_cast(des).find(std::to_string(getpid())) != std::string::npos ); + TS_ASSERT ( des.isLocal() ); } void testIsActuallyNull(void) diff --git a/test/unit/module_pool.hh b/test/unit/module_pool.hh index 5b1ebdc3..12e8ddd0 100644 --- a/test/unit/module_pool.hh +++ b/test/unit/module_pool.hh @@ -19,46 +19,73 @@ #include #include -#define MAX_SIZE 10 - using Wintermute::Module; using std::begin; using std::end; using std::make_shared; +using std::dynamic_pointer_cast; class ModulePoolTestSuite : public CxxTest::TestSuite { public: - void testFindingModule() + Module::Ptr modulePtr; + short count = 1; + + void setUp() { - Module::Ptr modulePtr(new SampleModule); - TS_ASSERT ( modulePtr->enable() ); - TS_ASSERT ( Module::Pool::instance()->has(modulePtr->designation()) ); - TS_ASSERT ( modulePtr->disable() ); + assert(Module::Pool::instance()); + cout << endl << "============ SETUP ==============" << endl; + TS_ASSERT ( !modulePtr ); + ++count; + modulePtr = make_shared(count); + cout << endl << "============ END SETUP ==============" << endl; + } + + void tearDown() + { + cout << endl << "============ TEARDOWN ==============" << endl; + modulePtr.reset(); + modulePtr = nullptr; + cout << endl << "============ END TEARDOWN ==============" << endl; + } + + void testFindModuleInPool() + { + Module::Designation des = modulePtr->designation(); + TS_ASSERT ( !Module::Pool::instance()->has(des) ); } - void testAddingModule() + void testAddModuleToPool() { - Module::Ptr modulePtr(new SampleModule); + Module::Ptr foundModulePtr = nullptr; + assert(!foundModulePtr); + TS_ASSERT ( modulePtr->enable() ); TS_ASSERT ( modulePtr->isEnabled() ); TS_ASSERT ( Module::Pool::instance()->has(modulePtr->designation()) ); - Module::Ptr foundModulePtr = Module::Pool::instance()->find(modulePtr->designation()); - TS_ASSERT_EQUALS ( foundModulePtr->designation() , modulePtr->designation() ); + foundModulePtr = Module::Pool::instance()->find(modulePtr->designation()); + TS_ASSERT ( foundModulePtr ); + TS_ASSERT_EQUALS ( foundModulePtr->designation(), modulePtr->designation() ); + auto sampleModulePtr = dynamic_pointer_cast(foundModulePtr); + TS_ASSERT ( sampleModulePtr ); TS_ASSERT ( modulePtr->disable() ); } - void testRemovingModule() + void testRemoveModuleFromPool() { - Module::Ptr modulePtr(new SampleModule); + Module::Ptr foundModulePtr; + Module::Designation des = modulePtr->designation(); + TS_ASSERT ( modulePtr->enable() ); TS_ASSERT ( Module::Pool::instance()->has(modulePtr->designation()) ); TS_ASSERT ( modulePtr->disable() ); + TS_ASSERT ( !modulePtr->isEnabled() ); TS_ASSERT ( !Module::Pool::instance()->has(modulePtr->designation()) ); + modulePtr.reset(); - Module::Ptr foundModulePtr = Module::Pool::instance()->find(modulePtr->designation()); + foundModulePtr = Module::Pool::instance()->find(des); TS_ASSERT ( !foundModulePtr ); } }; diff --git a/test/unit/plugin.hh b/test/unit/plugin.hh index 2778406c..4c303e0f 100644 --- a/test/unit/plugin.hh +++ b/test/unit/plugin.hh @@ -34,9 +34,25 @@ Plugin::Ptr fetchWorkingPlugin() class PluginTestSuite : public CxxTest::TestSuite { public: + Plugin::Ptr pluginPtr = nullptr; + void setUp() { - DISABLE_LOGGING; + pluginPtr = nullptr; + } + + void tearDown() + { + if (pluginPtr) + { + TSM_ASSERT ( "Plugin released from library.", + Plugin::release(pluginPtr->name()) ); + } + + if (pluginPtr) + { + pluginPtr.reset(); + } } void testFindLoadedPlugins() @@ -47,14 +63,12 @@ public: { setenv(WINTERMUTE_ENV_PLUGIN_PATH, string(TEST_BASE_DIR "/fixtures").c_str(), 1); - Plugin::Ptr pluginPtr = Plugin::find(SAMPLE_PLUGIN_NAME); + pluginPtr = Plugin::find(SAMPLE_PLUGIN_NAME); unsetenv(WINTERMUTE_ENV_PLUGIN_PATH); } TSM_ASSERT ( "Plugin '" SAMPLE_PLUGIN_NAME "' found in listing.", Plugin::hasPlugin(SAMPLE_PLUGIN_NAME) == true); - - Plugin::release(SAMPLE_PLUGIN_NAME); } void testDontFindMissingPlugins() @@ -65,19 +79,17 @@ public: void testRetainsNameOfPlugin() { - Plugin::Ptr pluginPtr(fetchWorkingPlugin()); - TS_ASSERT ( pluginPtr ); + pluginPtr = fetchWorkingPlugin(); + TSM_ASSERT_EQUALS ( "Has the correct name", pluginPtr->name(), SAMPLE_PLUGIN_NAME); - TS_ASSERT ( Plugin::release(pluginPtr->name()) ); } void testExposesTypeOfPlugin() { - Plugin::Ptr pluginPtr(fetchWorkingPlugin()); - TS_ASSERT ( pluginPtr ); + pluginPtr = fetchWorkingPlugin(); + TSM_ASSERT ( "Isn't an undefined value.", pluginPtr->type() != Plugin::PluginTypeUndefined ); - TS_ASSERT ( Plugin::release(pluginPtr->name()) ); } }; diff --git a/test/unit/tunnel.hh b/test/unit/tunnel.hh index 1332a2a9..95fc0771 100644 --- a/test/unit/tunnel.hh +++ b/test/unit/tunnel.hh @@ -32,12 +32,20 @@ public: { } + void testRaisesEvents() + { + /// TODO: Check for 'W_EVENT_TUNNEL_START'. + /// TODO: Check for 'W_EVENT_TUNNEL_STOP'. + /// TODO: Check for 'W_EVENT_TUNNEL_MESSAGE'. + } + void testFindADispatcher(void) { Tunnel::Dispatcher::Ptr dispatcherPtr(new SampleDispatcher); TS_ASSERT(Tunnel::registerDispatcher(dispatcherPtr)); TS_ASSERT(Tunnel::knowsOfDispatcher("sample")); TS_ASSERT(Tunnel::unregisterDispatcher(dispatcherPtr)); + TS_ASSERT(!Tunnel::unregisterDispatcher("foobarzilla")); TS_ASSERT(!Tunnel::knowsOfDispatcher("foobarzilla")); } @@ -48,6 +56,7 @@ public: TS_ASSERT(Tunnel::registerReceiver(receiverPtr)); TS_ASSERT(Tunnel::knowsOfReceiver("sample")); TS_ASSERT(Tunnel::unregisterReceiver(receiverPtr)); + TS_ASSERT(!Tunnel::unregisterReceiver("foobarzilla")); TS_ASSERT(!Tunnel::knowsOfReceiver("foobarzilla")); }