diff --git a/test/agent/.gitignore b/test/agent/.gitignore index 93d93c78cb8eeaea6050f5082cf303ee6a27a1dc..a39bc478f1648bc10c8420d593657451c84907bd 100644 --- a/test/agent/.gitignore +++ b/test/agent/.gitignore @@ -1,3 +1,6 @@ agent - - +*.yml +*.txt +*.log +.gdbinit +*.gdb \ No newline at end of file diff --git a/test/agent/Makefile.am b/test/agent/Makefile.am index 349846a7cf01187f0a1e501625f98ef721f3696d..6dea50cbe4bab8bfea492d79cc281e3211a521b5 100644 --- a/test/agent/Makefile.am +++ b/test/agent/Makefile.am @@ -1,10 +1,15 @@ include $(top_srcdir)/globals.mk -AM_CXXFLAGS += -I$(top_srcdir)/src -I.. \ - -DTOP_BUILDDIR=\"$$(cd "$(top_builddir)"; pwd)\" +AM_CXXFLAGS += -I$(top_srcdir)/src -I.. $(GUILE_CFLAGS) -rdynamic +AM_LDFLAGS += $(GUILE_LIBS) check_PROGRAMS = agent -agent_SOURCES = agent.cpp agent.h utils.cpp utils.h bt.cpp bt.h main.cpp log.h +agent_SOURCES = \ + agent.cpp \ + agent.h \ + bindings.cpp \ + bindings.h \ + main.cpp -agent_LDADD = $(top_builddir)/src/libring.la -ldl +agent_LDADD = $(top_builddir)/src/libring.la diff --git a/test/agent/README.md b/test/agent/README.md new file mode 100644 index 0000000000000000000000000000000000000000..aa79a9eac62420717d10b39131269acb24c2ec8d --- /dev/null +++ b/test/agent/README.md @@ -0,0 +1,55 @@ +# Compile +To compile the agent, one has to enable it while configuring the daemon. + +```sh +./configure --enable-agent +``` + +then you need to recompile the contrib. This will compile Guile, which can take +some time. + +# Running the agent +The agent expects a Scheme file has its first parameter. This scheme file will +be interpreted by Guile. In the script, you can control the agent. + +Usage: +```sh +./agent ./examples/passive-agent.scm +``` + +# Guile bindings +In order for Guile to control the agent, bindings have to be added to the global +environment where the configuration file is being interpreted. This is done in +`main.cpp` in the function `install_scheme_primitive()`. All scheme bindings +should have the prefix `agent:` to be clear that the procedure is one that +control the agent. + +When a binding is called from Guile, the arguments passed are Scheme objects of +type `SCM`. This is an opaque type that is generic. In order to be clear on +what the underlying type needed by the primitive procedure is, one should add the +suffix of the type at the end. + +For example, `my_primitive_procedure()` expects that `some_variable_str` +will be of type `string`. This is enforced by using an assertion: +```c++ +static SCM my_primitive_procedure(SCM some_variable_str) +{ + AGENT_ASSERT(scm_is_string(some_variable_str), "`some_variable_str` must be of type string"); + ... +} +``` + +Here is another example where `my_second_primitive()` expects that +`some_variable_vector_or_str` to be of type `string` or `vector`: +```c++ +static SCM my_second_primitive(SCM some_variable_vector_or_str) +{ + AGENT_ASSERT(scm_is_string(some_variable_vector_or_str) || + scm_is_simple_vector(some_variable_vector_or_str), + "`scm_some_variable_vector_or_str` must either be of type vector or string"); + ... +} +``` + +# Writing scenarios +See `examples/` diff --git a/test/agent/agent.cpp b/test/agent/agent.cpp index a28f322513880b39d896bab9e2571f22f21227e4..c29e179e95fd9ab1f6417ecebc649404faaae77f 100644 --- a/test/agent/agent.cpp +++ b/test/agent/agent.cpp @@ -27,9 +27,6 @@ #include <mutex> #include <thread> -/* Third parties */ -#include <yaml-cpp/yaml.h> - /* DRing */ #include "account_const.h" #include "jami/presencemanager_interface.h" @@ -39,94 +36,251 @@ /* agent */ #include "agent/agent.h" -#include "agent/bt.h" -#include "agent/utils.h" using usize = size_t; -using u32 = uint32_t; - -#define LOG_AGENT_STATE() JAMI_INFO("AGENT: In state %s", __FUNCTION__) -#define AGENT_ERR(FMT, ARGS...) JAMI_ERR("AGENT: " FMT, ##ARGS) -#define AGENT_INFO(FMT, ARGS...) JAMI_INFO("AGENT: " FMT, ##ARGS) -#define AGENT_DBG(FMT, ARGS...) JAMI_DBG("AGENT: " FMT, ##ARGS) -#define AGENT_ASSERT(COND, MSG, ARGS...) \ - if (not(COND)) { \ - AGENT_ERR(MSG, ##ARGS); \ - exit(1); \ - } + +#define LOG_AGENT_STATE() AGENT_DBG("In state %s", __FUNCTION__) void -Agent::initBehavior() +Agent::searchForPeers(std::vector<std::string>& peers) { - using std::bind; + LOG_AGENT_STATE(); - BT::register_behavior("search-peer", bind(&Agent::searchPeer, this)); - BT::register_behavior("wait", bind(&Agent::wait, this)); - BT::register_behavior("echo", bind(&Agent::echo, this)); - BT::register_behavior("make-call", bind(&Agent::makeCall, this)); - BT::register_behavior("true", bind(&Agent::True, this)); - BT::register_behavior("false", bind(&Agent::False, this)); - BT::register_behavior("start-log-recording", bind(&Agent::startLogRecording, this)); - BT::register_behavior("stop-log-recording", bind(&Agent::stopLogRecording, this)); + for (auto it = peers.begin(); it != peers.end(); ++it) { + DRing::sendTrustRequest(accountID_, it->c_str()); + DRing::subscribeBuddy(accountID_, it->c_str(), true); + } } -void -Agent::configure(const std::string& yaml_config) +bool +Agent::ping(const std::string& conversation) { - std::ifstream file = std::ifstream(yaml_config); + LOG_AGENT_STATE(); + + auto cv = std::make_shared<std::condition_variable>(); + auto pongReceived = std::make_shared<std::atomic_bool>(false); + + std::string alphabet = "0123456789ABCDEF"; + std::string messageSent; + + for (usize i = 0; i < 16; ++i) { + messageSent.push_back(alphabet[rand() % alphabet.size()]); + } + + onMessageReceived_.add([=](const std::string& /* accountID */, + const std::string& conversationID, + std::map<std::string, std::string> message) { - AGENT_ASSERT(file.is_open(), "Failed to open configuration file `%s`", yaml_config.c_str()); + if ("text/plain" != message.at("type")) { + return true; + } + + auto msg = message.at("body"); + + if (pongReceived->load()) { + return false; + } + + if (conversationID == conversation and message.at("author") != peerID_ + and msg == "PONG:" + messageSent) { + *pongReceived = true; + cv->notify_one(); + return false; + } + + return true; + }); - YAML::Node node = YAML::Load(file); + AGENT_INFO("Sending ping `%s` to `%s`", messageSent.c_str(), conversation.c_str()); - auto context = node["record-context"]; + DRing::sendMessage(accountID_, conversation, messageSent, ""); - if (context.IsScalar()) { - context_ = context.as<std::string>(); + /* Waiting for echo */ + + std::mutex mutex; + std::unique_lock<std::mutex> lck(mutex); + + bool ret = std::cv_status::no_timeout == cv->wait_for(lck, std::chrono::seconds(30)) + and pongReceived->load(); + + AGENT_INFO("Pong %s", ret ? "received" : "missing"); + + return ret; +} + +std::string +Agent::someContact() const +{ + auto members = DRing::getConversationMembers(accountID_, someConversation()); + + for (const auto& member : members) { + if (member.at("uri") != peerID_) { + return member.at("uri"); + + } } - auto to = node["record-to"]; + return ""; +} - if (to.IsScalar()) { - recordTo_ = to.as<std::string>(); +std::string +Agent::someConversation() const +{ + if (conversations_.empty()) { + return ""; } - auto peers = node["peers"]; + auto it = conversations_.begin(); + + std::advance(it, rand() % conversations_.size()); + + return *it; +} + +bool +Agent::placeCall(const std::string& contact) +{ + LOG_AGENT_STATE(); + + auto cv = std::make_shared<std::condition_variable>(); - AGENT_ASSERT(peers.IsSequence(), "Configuration node `peers` must be a sequence"); + auto callID = DRing::placeCall(accountID_, contact); + auto success = std::make_shared<std::atomic<bool>>(false); + auto over = std::make_shared<std::atomic<bool>>(false); - for (const auto& peer : peers) { - peers_.emplace_back(peer.as<std::string>()); + if (callID.empty()) { + return false; } - root_ = BT::from_yaml(node["behavior"]); - - /* params */ - auto params = node["params"]; - - if (params) { - auto accountID_details = DRing::getAccountDetails(accountID_); - assert(params.IsSequence()); - for (const auto& param : params) { - assert(param.IsSequence()); - for (const auto& details : param) { - assert(details.IsMap()); - for (const auto& detail : details) { - auto first = detail.first.as<std::string>(); - auto second = detail.second.as<std::string>(); - accountID_details["Account." + first] = second; - } - } - params_.emplace_back([this, accountID_details = std::move(accountID_details)] { - DRing::setAccountDetails(accountID_, accountID_details); - }); + onCallStateChanged_.add([=](const std::string& call_id, const std::string& state, signed code) { + + AGENT_INFO("[call:%s] In state %s : %d", callID.c_str(), state.c_str(), code); + + if (call_id != callID) { + return true; + } + + bool ret = true; + + if ("CURRENT" == state) { + success->store(true); + } else if ("OVER" == state) { + over->store(true); + ret = false; } + + cv->notify_one(); + + return ret; + }); + + std::mutex mtx; + std::unique_lock<std::mutex> lck {mtx}; + + AGENT_INFO("Waiting for call %s", callID.c_str()); + + /* TODO - Parametize me */ + cv->wait_for(lck, std::chrono::seconds(30), [=]{ + return success->load() or over->load(); + }); + + if (success->load()) { + AGENT_INFO("[call:%s] to %s: SUCCESS", callID.c_str(), contact.c_str()); + DRing::hangUp(callID); + } else { + AGENT_INFO("[call:%s] to %s: FAIL", callID.c_str(), contact.c_str()); } + + if (not over->load()) { + cv->wait_for(lck, std::chrono::seconds(30), [=] { return over->load(); }); + } + + return success->load(); +} + +void +Agent::wait(std::chrono::seconds period) +{ + LOG_AGENT_STATE(); + + std::this_thread::sleep_for(period); +} + +void +Agent::setDetails(const std::map<std::string, std::string>& details) +{ + LOG_AGENT_STATE(); + + auto cv = std::make_shared<std::condition_variable>(); + auto cont = std::make_shared<std::atomic<bool>>(true); + + std::string info("Setting details:\n"); + + for (const auto& [key, value] : details) { + info += key + " = " + value + "\n"; + } + + AGENT_INFO("%s", info.c_str()); + + DRing::setAccountDetails(accountID_, details); +} + +void +Agent::activate(bool state) +{ + LOG_AGENT_STATE(); + + DRing::setAccountActive(accountID_, state); + + if (state) { + waitForAnnouncement(); + } +} + +void +Agent::exportToArchive(const std::string& path) +{ + LOG_AGENT_STATE(); + + AGENT_ASSERT(DRing::exportToFile(accountID_, + path), + "Failed to export account to `%s`", path.c_str()); +} + +void +Agent::importFromArchive(const std::string& path) +{ + LOG_AGENT_STATE(); + + std::map<std::string, std::string> details; + + details[DRing::Account::ConfProperties::TYPE] = "RING"; + details[DRing::Account::ConfProperties::DISPLAYNAME] = "AGENT"; + details[DRing::Account::ConfProperties::ALIAS] = "AGENT"; + details[DRing::Account::ConfProperties::ARCHIVE_PASSWORD] = ""; + details[DRing::Account::ConfProperties::ARCHIVE_PIN] = ""; + details[DRing::Account::ConfProperties::ARCHIVE_PATH] = path; + + AGENT_ASSERT(accountID_ == DRing::addAccount(details, accountID_), "Bad accountID"); + + details = DRing::getAccountDetails(accountID_); + + waitForAnnouncement(); + + AGENT_ASSERT("AGENT" == details.at(DRing::Account::ConfProperties::DISPLAYNAME), + "Bad display name"); + + peerID_ = details.at(DRing::Account::ConfProperties::USERNAME); + conversations_ = DRing::getConversations(accountID_); + + AGENT_INFO("Using account %s - %s", accountID_.c_str(), peerID_.c_str()); } void Agent::ensureAccount() { + LOG_AGENT_STATE(); + std::map<std::string, std::string> details; details = DRing::getAccountDetails(accountID_); @@ -140,22 +294,28 @@ Agent::ensureAccount() details[DRing::Account::ConfProperties::ARCHIVE_PATH] = ""; AGENT_ASSERT(accountID_ == DRing::addAccount(details, accountID_), "Bad accountID"); + } - wait_for_announcement_of(accountID_); + waitForAnnouncement(); - details = DRing::getAccountDetails(accountID_); - } + details = DRing::getAccountDetails(accountID_); AGENT_ASSERT("AGENT" == details.at(DRing::Account::ConfProperties::DISPLAYNAME), "Bad display name"); peerID_ = details.at(DRing::Account::ConfProperties::USERNAME); + conversations_ = DRing::getConversations(accountID_); + + AGENT_INFO("Using account %s - %s", accountID_.c_str(), peerID_.c_str()); } -void -Agent::getConversations() + +Agent& +Agent::instance() { - conversations_ = DRing::getConversations(accountID_); + static Agent agent; + + return agent; } void @@ -216,8 +376,19 @@ Agent::installSignalHandlers() _2, _3))); - handlers.insert(DRing::exportable_callback<DRing::ConfigurationSignal::MessageSend>( - bind(&Agent::onLogging, this, _1))); + handlers.insert(DRing::exportable_callback<DRing::ConfigurationSignal::RegistrationStateChanged>( + bind(&Agent::Handler<const std::string&, const std::string&, int, const std::string&>::execute, + &onRegistrationStateChanged_, + _1, + _2, + _3, + _4))); + + handlers.insert(DRing::exportable_callback<DRing::ConfigurationSignal::VolatileDetailsChanged>( + bind(&Agent::Handler<const std::string&, const std::map<std::string, std::string>&>::execute, + &onVolatileDetailsChanged_, + _1, + _2))); DRing::registerSignalHandlers(handlers); } @@ -232,7 +403,18 @@ Agent::registerStaticCallbacks() (void) accountID; (void) peerDisplayName; - AGENT_INFO("Incoming call from `%s`", peerDisplayName.c_str()); + std::string medias(""); + + for (const auto& media : mediaList) { + for (const auto& [key, value] : media) { + medias += key + "=" + value + "\n"; + } + } + + AGENT_INFO("Incoming call `%s` from `%s` with medias:\n`%s`", + callID.c_str(), + peerDisplayName.c_str(), + medias.c_str()); AGENT_ASSERT(DRing::acceptWithMedia(callID, mediaList), "Failed to accept call `%s`", @@ -263,7 +445,9 @@ Agent::registerStaticCallbacks() AGENT_INFO("Incomming message `%s` from %s", msg.c_str(), author.c_str()); /* Echo back */ - DRing::sendMessage(accountID_, conversationID, msg, ""); + if (0 != msg.rfind("PONG:", 0)) { + DRing::sendMessage(accountID_, conversationID, "PONG:" + msg, ""); + } return true; }); @@ -296,272 +480,51 @@ Agent::registerStaticCallbacks() } void -Agent::onLogging(const std::string& message) +Agent::waitForAnnouncement(std::chrono::seconds timeout) { - for (const auto& [context, logger] : loggers_) { - logger->pushMessage(message); - } -} - -bool -Agent::searchPeer() -{ - LOG_AGENT_STATE(); - - std::set<std::string> peers; - - /* Prune contacts already friend with */ - for (auto it = peers_.begin(); it != peers_.end(); ++it) { - bool prune = false; - for (const auto& conv : conversations_) { - if (conv == *it) { - prune = true; - break; - } - } - if (not prune) { - peers.emplace(*it); - } - } - auto cv = std::make_shared<std::condition_variable>(); - for (auto it = peers.begin(); it != peers.end(); ++it) { - DRing::sendTrustRequest(accountID_, it->c_str()); - DRing::subscribeBuddy(accountID_, it->c_str(), true); - } - - if (conversations_.size()) { - return true; - } - - onContactAdded_.add([=](const std::string&, const std::string&, bool) { - if (conversations_.size()) { - cv->notify_one(); - return false; - } - return true; - }); - std::mutex mtx; - std::unique_lock<std::mutex> lck(mtx); + std::unique_lock<std::mutex> lk {mtx}; - cv->wait(lck); + onVolatileDetailsChanged_.add([=](const std::string& accountID, + const std::map<std::string, std::string>& details) { - return true; -} + if (accountID_ != accountID) { + return true; + } -void -Agent::run(const std::string& yaml_config) -{ - static Agent agent; + try { + if ("true" + != details.at(DRing::Account::VolatileProperties::DEVICE_ANNOUNCED)) { + return true; + } + } catch (const std::out_of_range&) { + return true; + } - agent.initBehavior(); - agent.ensureAccount(); - agent.configure(yaml_config); - agent.getConversations(); - agent.installSignalHandlers(); - agent.registerStaticCallbacks(); - - if (agent.params_.size()) { - while (true) { - for (auto& cb : agent.params_) { - cb(); - (*agent.root_)(); - } - } - } else { - while ((*agent.root_) ()) { - /* Until root fails */ - } - } -} + cv->notify_one(); -/* Helper start here */ -void -Agent::sendMessage(const std::string& to, const std::string& msg) -{ - auto parent = ""; - - DRing::sendMessage(accountID_, to, msg, parent); -} - -/* Behavior start here */ - -bool -Agent::startLogRecording() -{ - class FileHandler : public LogHandler - { - std::ofstream out; - - public: - FileHandler(const std::string& context, const std::string& to) - : LogHandler(context) - , out(to) - {} - - virtual void pushMessage(const std::string& message) override - { - out << context_ << message << std::endl; - } - - virtual void flush() override { out.flush(); } - }; - - loggers_[context_] = std::make_unique<FileHandler>(context_, recordTo_); - - setMonitorLog(true); - - return true; -} - -bool -Agent::stopLogRecording() -{ - LOG_AGENT_STATE(); - - if (not loggers_.empty()) { - loggers_.erase(context_); - } - - if (loggers_.empty()) { - setMonitorLog(false); - } - - return true; -} - -bool -Agent::echo() -{ - LOG_AGENT_STATE(); - - if (conversations_.empty()) { - return false; - } - - auto it = conversations_.begin(); - - std::advance(it, rand() % conversations_.size()); - - auto cv = std::make_shared<std::condition_variable>(); - auto pongReceived = std::make_shared<std::atomic_bool>(false); - auto to = *it; - - std::string alphabet = "0123456789ABCDEF"; - std::string messageSent; - - onMessageReceived_.add([=](const std::string& accountID, - const std::string& conversationID, - std::map<std::string, std::string> message) { - (void) accountID; - (void) conversationID; - (void) message; - (void) conversationID; - - if ("text/plain" != message.at("type")) { - return true; - } - - auto msg = message.at("body"); - - if (pongReceived->load()) { - return false; - } - - if (to == message.at("author") and msg == messageSent) { - *pongReceived = true; - cv->notify_one(); - return false; - } - - return true; + return false; }); - /* Sending msg */ - for (usize i = 0; i < 16; ++i) { - messageSent.push_back(alphabet[rand() % alphabet.size()]); - } - - sendMessage(*it, messageSent); - - /* Waiting for echo */ - - std::mutex mutex; - std::unique_lock<std::mutex> lck(mutex); - - bool ret = std::cv_status::no_timeout == cv->wait_for(lck, std::chrono::seconds(30)) - and pongReceived->load(); - - return ret; + AGENT_ASSERT(std::cv_status::no_timeout == cv->wait_for(lk, timeout), + "Timeout while waiting for account announcement on DHT"); } -bool -Agent::makeCall() +void +Agent::init() { LOG_AGENT_STATE(); - if (conversations_.empty()) { - return false; - } - - auto it = conversations_.begin(); - - std::advance(it, rand() % conversations_.size()); - - auto cv = std::make_shared<std::condition_variable>(); - - onCallStateChanged_.add([=](const std::string&, const std::string& state, signed) { - if ("CURRENT" == state) { - cv->notify_one(); - return false; - } - - if ("OVER" == state) { - return false; - } - - return true; - }); - - auto members = DRing::getConversationMembers(accountID_, *it); - - std::string uri; - - for (const auto& member : members) { - if (member.at("uri") != peerID_) { - uri = member.at("uri"); - break; - } - } - - if (uri.empty()) { - return false; - } - - auto callID = DRing::placeCall(accountID_, uri); - - bool ret = true; - - std::mutex mtx; - std::unique_lock<std::mutex> lck {mtx}; - - if (std::cv_status::timeout == cv->wait_for(lck, std::chrono::seconds(30))) { - ret = false; - } - - DRing::hangUp(callID); - - return ret; + installSignalHandlers(); + registerStaticCallbacks(); } -bool -Agent::wait() +void +Agent::fini() { LOG_AGENT_STATE(); - std::this_thread::sleep_for(std::chrono::seconds(30)); - - return true; + DRing::unregisterSignalHandlers(); } diff --git a/test/agent/agent.h b/test/agent/agent.h index 5cbb348eb43100c4756d5936342ee19d03f5f5f5..21c446a3c47e57c94e2154578b04b0e349fd2092 100644 --- a/test/agent/agent.h +++ b/test/agent/agent.h @@ -20,19 +20,27 @@ #pragma once -/* agent */ -#include "agent/bt.h" -#include "agent/log.h" - /* Dring */ +#include "logger.h" #include "jami/jami.h" /* std */ +#include <chrono> +#include <map> #include <memory> #include <mutex> #include <string> #include <vector> +#define AGENT_ERR(FMT, ARGS...) JAMI_ERR("AGENT: " FMT, ##ARGS) +#define AGENT_INFO(FMT, ARGS...) JAMI_INFO("AGENT: " FMT, ##ARGS) +#define AGENT_DBG(FMT, ARGS...) JAMI_DBG("AGENT: " FMT, ##ARGS) +#define AGENT_ASSERT(COND, MSG, ARGS...) \ + if (not(COND)) { \ + AGENT_ERR(MSG, ##ARGS); \ + exit(1); \ + } + class Agent { template<typename... Args> @@ -82,41 +90,40 @@ class Agent Handler<const std::string&, const std::string&, bool> onContactAdded_; + Handler<const std::string&, const std::string&, int, const std::string&> + onRegistrationStateChanged_; + + Handler<const std::string&, const std::map<std::string, std::string>&> + onVolatileDetailsChanged_; + /* Initialize agent */ - void configure(const std::string& yaml_config); - void getConversations(); - void ensureAccount(); - void initBehavior(); void installSignalHandlers(); void registerStaticCallbacks(); /* Bookkeeping */ - std::string context_; - std::string recordTo_; std::string peerID_; - const std::string accountID_{"afafafafafafafaf"}; - std::vector<std::string> peers_; + const std::string accountID_ {"afafafafafafafaf"}; std::vector<std::string> conversations_; - std::unique_ptr<BT::Node> root_; - std::vector<std::function<void(void)>> params_; - std::map<std::string, std::unique_ptr<LogHandler>> loggers_; - - /* Event */ - void onLogging(const std::string& message); - - /* Helper */ - void sendMessage(const std::string& to, const std::string& msg); +public: /* Behavior */ - bool startLogRecording(); - bool stopLogRecording(); - bool searchPeer(); - bool wait(); - bool echo(); - bool makeCall(); - bool False() { return false; } - bool True() { return true; } + bool ping(const std::string& conversation); + bool placeCall(const std::string& contact); + std::string someContact() const; + std::string someConversation() const; + void setDetails(const std::map<std::string, std::string>& details); + void stopRecording(const std::string& context); + void startRecording(const std::string& context, const std::string& to); + void searchForPeers(std::vector<std::string>& peers); + void wait(std::chrono::seconds period); + void exportToArchive(const std::string& path); + void importFromArchive(const std::string& path); + void ensureAccount(); + void waitForAnnouncement(std::chrono::seconds timeout=std::chrono::seconds(30)); + void activate(bool state); -public: - static void run(const std::string& yaml_config); + void init(); + void fini(); + + static Agent& instance(); }; diff --git a/test/agent/bindings.cpp b/test/agent/bindings.cpp new file mode 100644 index 0000000000000000000000000000000000000000..4ab15351ca5b278cb8fd04f3ff1ae5d138144c26 --- /dev/null +++ b/test/agent/bindings.cpp @@ -0,0 +1,215 @@ +/* + * Copyright (C) 2021 Savoir-faire Linux Inc. + * + * Author: Olivier Dion <olivier.dion@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "agent/agent.h" +#include "agent/bindings.h" + +static std::string(scm_to_cxx_string)(SCM value_str, const char* value_name) +{ + AGENT_ASSERT(scm_is_string(value_str), "`%s` must be of type string", value_name); + + char* str_raw = scm_to_locale_string(value_str); + std::string ret(str_raw); + free(str_raw); + + return ret; +} + +static SCM +wait_binding(SCM period_long_optional) +{ + std::chrono::seconds period; + + if (SCM_UNBNDP(period_long_optional)) { + period = std::chrono::seconds::max(); + } else { + AGENT_ASSERT(scm_is_number(period_long_optional), "Invalid period"); + period = std::chrono::seconds(scm_to_uint(period_long_optional)); + } + + Agent::instance().wait(period); + + return SCM_UNDEFINED; +} + +static SCM +place_call_binding(SCM contact_str) +{ + AGENT_ASSERT(scm_is_string(contact_str), "Wrong type for contact"); + + return scm_from_bool(Agent::instance().placeCall(scm_to_cxx_string(contact_str))); +} + +static SCM +some_conversation_binding() +{ + auto contact = Agent::instance().someConversation(); + + return scm_from_utf8_string(contact.c_str()); +} + +static SCM +some_contact_binding() +{ + auto contact = Agent::instance().someContact(); + + return scm_from_utf8_string(contact.c_str()); +} + +static SCM +search_peer_binding(SCM peers_vector_or_str) +{ + std::vector<std::string> peers; + + if (scm_is_string(peers_vector_or_str)) { + peers.emplace_back(scm_to_cxx_string(peers_vector_or_str)); + } else { + AGENT_ASSERT(scm_is_simple_vector(peers_vector_or_str), + "peers_vector_or_str must be a simple vector or a string"); + } + + for (size_t i = 0; i < SCM_SIMPLE_VECTOR_LENGTH(peers_vector_or_str); ++i) { + SCM peer_str = SCM_SIMPLE_VECTOR_REF(peers_vector_or_str, i); + + peers.emplace_back(scm_to_cxx_string(peer_str)); + } + + Agent::instance().searchForPeers(peers); + + return SCM_UNDEFINED; +} + +static SCM +ping_binding(SCM contact_str) +{ + return scm_from_bool(Agent::instance().ping(scm_to_cxx_string(contact_str))); +} + +static SCM +set_details_binding(SCM details_alist) +{ + std::map<std::string, std::string> details; + + AGENT_ASSERT(scm_is_true(scm_list_p(details_alist)), "Bad format of details"); + + while (not scm_is_null(details_alist)) { + SCM detail_pair = scm_car(details_alist); + + AGENT_ASSERT(scm_is_pair(detail_pair), "Detail must be a pair"); + + auto car = scm_to_cxx_string(scm_car(detail_pair)); + auto cdr = scm_to_cxx_string(scm_cdr(detail_pair)); + + details[car] = cdr; + + details_alist = scm_cdr(details_alist); + } + + Agent::instance().setDetails(details); + + return SCM_UNDEFINED; +} + +static SCM +ensure_account_binding() +{ + Agent::instance().ensureAccount(); + + return SCM_UNDEFINED; +} + +static SCM +export_to_archive_binding(SCM path) +{ + Agent::instance().exportToArchive(scm_to_cxx_string(path)); + + return SCM_UNDEFINED; +} + +static SCM +import_from_archive_binding(SCM path) +{ + Agent::instance().importFromArchive(scm_to_cxx_string(path)); + + return SCM_UNDEFINED; +} + +static SCM +enable_binding() +{ + Agent::instance().activate(true); + + return SCM_UNDEFINED; +} + +static SCM +disable_binding() +{ + Agent::instance().activate(false); + + return SCM_UNDEFINED; +} + +/* + * Register Guile bindings here. + * + * 1. Name of the binding + * 2. Number of required argument to binding + * 3. Number of optional argument to binding + * 4. Number of rest argument to binding + * 5. Pointer to C function to call + * + * See info guile: + * + * Function: SCM scm_c_define_gsubr(const char *name, int req, int opt, int rst, fcn): + * + * Register a C procedure FCN as a “subr” — a primitive subroutine that can be + * called from Scheme. It will be associated with the given NAME and bind it in + * the "current environment". The arguments REQ, OPT and RST specify the number + * of required, optional and “rest” arguments respectively. The total number of + * these arguments should match the actual number of arguments to FCN, but may + * not exceed 10. The number of rest arguments should be 0 or 1. + * ‘scm_c_make_gsubr’ returns a value of type ‘SCM’ which is a “handle” for the + * procedure. + */ +void +install_scheme_primitives() +{ + auto define_primitive = [](const char* name, int req, int opt, int rst, void* func) { + AGENT_ASSERT(req + opt + rst <= 10, "Primitive binding `%s` has too many argument", name); + + AGENT_ASSERT(0 == rst or 1 == rst, "Rest argument for binding `%s` must be 0 or 1", name); + + scm_c_define_gsubr(name, req, opt, rst, func); + }; + + define_primitive("agent:search-for-peers", 1, 0, 0, (void*) search_peer_binding); + define_primitive("agent:place-call", 1, 0, 0, (void*) place_call_binding); + define_primitive("agent:some-contact", 0, 0, 0, (void*) some_contact_binding); + define_primitive("agent:some-conversation", 0, 0, 0, (void*) some_conversation_binding); + define_primitive("agent:wait", 0, 1, 0, (void*) wait_binding); + define_primitive("agent:ping", 1, 0, 0, (void*) ping_binding); + define_primitive("agent:set-details", 1, 0, 0, (void*) set_details_binding); + define_primitive("agent:ensure-account", 0, 0, 0, (void*) ensure_account_binding); + define_primitive("agent->archive", 1, 0, 0, (void*) export_to_archive_binding); + define_primitive("archive->agent", 1, 0, 0, (void*) import_from_archive_binding); + define_primitive("agent:enable", 0, 0, 0, (void*)enable_binding); + define_primitive("agent:disable", 0, 0, 0, (void*)disable_binding); +} diff --git a/test/agent/log.h b/test/agent/bindings.h similarity index 74% rename from test/agent/log.h rename to test/agent/bindings.h index 75ffed23784728ddad4e4b8d392818baa98a87d0..85ac943267cda372e6b0f0e56cbd1fe79c7ffb48 100644 --- a/test/agent/log.h +++ b/test/agent/bindings.h @@ -20,19 +20,8 @@ #pragma once -#include <string> +#include <libguile.h> -class LogHandler -{ -protected: - std::string context_; +#define scm_to_cxx_string(VAR) (scm_to_cxx_string)(VAR, #VAR) -public: - LogHandler(const std::string& context) - : context_(context) - {} - virtual ~LogHandler() = default; - - virtual void pushMessage(const std::string& message) = 0; - virtual void flush() = 0; -}; +extern void install_scheme_primitives(); diff --git a/test/agent/bt.cpp b/test/agent/bt.cpp deleted file mode 100644 index c03abc48b3745fd3e87684d005f6ea04bc91c61a..0000000000000000000000000000000000000000 --- a/test/agent/bt.cpp +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright (C) 2021 Savoir-faire Linux Inc. - * - * Author: Olivier Dion <olivier.dion@savoirfairelinux.com> - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 3 of the License, or - * (at your option) any later version. - * - * This program 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "agent/bt.h" - -namespace BT { - -std::map<std::string, std::function<bool(void)>> registered_behaviors; - -std::unique_ptr<Node> -from_yaml(YAML::Node behavior) -{ - std::unique_ptr<Node> node; - - if (behavior.IsSequence()) { - auto tmp = new Sequence(); - - for (const auto& sub_behavior : behavior) { - tmp->add(from_yaml(sub_behavior)); - } - - node.reset(dynamic_cast<Node*>(tmp)); - - } else if (behavior.IsMap()) { - auto tmp = new Selector(); - - for (const auto& kv : behavior) { - assert(kv.second.IsSequence()); - for (const auto& sub_behavior : kv.second) { - tmp->add(from_yaml(sub_behavior)); - } - } - - node.reset(dynamic_cast<Node*>(tmp)); - - } else { - node = std::make_unique<Execute>(behavior.as<std::string>()); - } - - return node; -} - -}; // namespace BT diff --git a/test/agent/bt.h b/test/agent/bt.h deleted file mode 100644 index 18537f88aad2e58db5269d6c17326558ab827345..0000000000000000000000000000000000000000 --- a/test/agent/bt.h +++ /dev/null @@ -1,105 +0,0 @@ -/* - * Copyright (C) 2021 Savoir-faire Linux Inc. - * - * Author: Olivier Dion <olivier.dion@savoirfairelinux.com> - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 3 of the License, or - * (at your option) any later version. - * - * This program 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#pragma once - -#include <cassert> -#include <functional> -#include <map> - -#include <yaml-cpp/yaml.h> - -/* Jami */ -#include "logger.h" - -namespace BT { - -extern std::map<std::string, std::function<bool(void)>> registered_behaviors; - -static inline void -register_behavior(const std::string& name, std::function<bool(void)>&& behavior) -{ - registered_behaviors[name] = std::move(behavior); -} - -class Node -{ -public: - virtual bool operator()(void) = 0; - - virtual ~Node() = default; -}; - -class Execute : public Node -{ - std::function<bool(void)> todo_ {}; - -public: - Execute(const std::string& behavior_name) - { - try { - todo_ = registered_behaviors.at(behavior_name); - } catch (const std::exception& E) { - JAMI_ERR("AGENT: Invalid behavior `%s`: %s", behavior_name.c_str(), E.what()); - } - } - - virtual bool operator()(void) override { return todo_(); } -}; - -struct Sequence : public Node -{ - std::vector<std::unique_ptr<Node>> nodes_; - -public: - virtual bool operator()(void) override - { - for (const auto& node : nodes_) { - if (not(*node)()) { - return false; - } - } - return true; - } - - void add(std::unique_ptr<Node> node) { nodes_.push_back(std::move(node)); } -}; - -struct Selector : public Node -{ - std::vector<std::unique_ptr<Node>> nodes_; - -public: - virtual bool operator()(void) override - { - for (const auto& node : nodes_) { - if ((*node)()) { - return true; - } - } - return false; - } - - void add(std::unique_ptr<Node> node) { nodes_.push_back(std::move(node)); } -}; - -extern std::unique_ptr<Node> from_yaml(YAML::Node behavior); - -}; // namespace BT diff --git a/test/agent/examples/active-agent.scm b/test/agent/examples/active-agent.scm new file mode 100644 index 0000000000000000000000000000000000000000..84078dd3fc7f1511a988017348bbfb55bde8e193 --- /dev/null +++ b/test/agent/examples/active-agent.scm @@ -0,0 +1,98 @@ +;;; This is an example of an active agent. + +;;; Here we define a simple variable named `peer` that references a string. +;;; +;;; You should change this to the Jami ID of an other agent or one of your +;;; account. +(define my-peer "358d385fc78edde27981caac4ec40fb963c8a066") + +;;; Here we define a variable named `details-matrix` that references a list of +;;; association lists. +;;; +;;; An association list is a list that has pairs of (key . value). +(define details-matrix + '((("Account.upnpEnabled" . "true") + ("TURN.enable" . "true")) + + (("Account.upnpEnabled" . "false") + ("TURN.enable" . "true")) + + (("Account.upnpEnabled" . "false") + ("TURN.enable" . "false")))) + +(define (scenario/call:details someone) + " + pre-conditions: SOMEONE is a string that references a valid contact in the agent account. + + Here we define a variable named `scenario/call` that references a procedure + that takes a single argument called `someone`. + + This procedure iterates over the details matrix defined above and changes the + details of the account, using `agent:set-details` for every association list in + the matrix. If it fails to place a call, then the `bad-call` exception is + thrown. Finally, the agent sleeps for 10 seconds using `agent:wait`. + " + (for-each (lambda (details) + (agent:set-details details) + (or (agent:place-call someone) + (throw 'bad-call))) + (agent:wait 10) + details-matrix)) + +(define (scenario/call:periodic someone) + " + pre-conditions: SOMEONE is a string that references a valid contact in the agent account. + + Place a call to SOMEONE periodically until it fails. + + NOTE! This is an example of a recursive procedure. In Scheme, tail + recursions are free." + (when (agent:place-call someone) + (scenario/call:periodic someone))) + +(define (scenario/call:nth someone cnt) + " + pre-conditions: SOMEONE is a string that references a valid contact in the agent account. + LEFT is a positive number. + + Place a call to SOMEONE CNT time. + " + (let ((fail 0)) + (do ((i 0 (1+ i))) + ((> i cnt)) + (unless (agent:place-call someone) + (set! fail (1+ fail)))) + fail) +) + +(define (scenario/ping conversation) + " + pre-conditions: CONVERSATION is a string that references a valid conversation in the agent account. + + Here we define a variable named `scenario/ping` that references a procedure + that takes a single argument called `conversation`. + + This procedure work just like `scenario/call`, but will send a random + message to CONVERSATION and expect to receive a pong of it, instead of + placing a call. + " + (for-each (lambda (details) + (agent:set-details details) + (or (agent:ping conversation) (throw 'bad-ping)) + (agent:wait 5)) + details-matrix)) + +;;; Set default account's details. +;(agent:set-details '(("Account.upnpEnabled" . "true") ("TURN.enable" . "true"))) + +;;; Search for our peer. +;(agent:search-for-peers my-peer) + +;;; Let's call our peer 50 times. +;(scenario/call:nth my-peer 50) + +;;; Wait for 100000 seconds. +(agent:ensure-account) +(agent:search-for-peers my-peer) + +(format #t "Failed ~a calls out of 1000" (scenario/call:nth my-peer 100)) diff --git a/test/agent/examples/passive-agent.scm b/test/agent/examples/passive-agent.scm new file mode 100644 index 0000000000000000000000000000000000000000..e78246648927bd3ae1215378e4b356e9c0e6b271 --- /dev/null +++ b/test/agent/examples/passive-agent.scm @@ -0,0 +1,29 @@ +;;; This is an example of a passive agent +;;; +;;; It will accept all trust request and answer every call + +(define details-matrix + '((("Account.upnpEnabled" . "true") + ("TURN.enable" . "true")) + + (("Account.upnpEnabled" . "false") + ("TURN.enable" . "true")) + + (("Account.upnpEnabled" . "false") + ("TURN.enable" . "false")))) + +(define (scenario/passive) + "Agent does nothing" + (agent:wait)) + +(define (scenario/passive:change-details) + "Agent changes its account details once in a while" + (for-each (lambda (details) + (agent:disable) + (agent:set-details details) + (agent:enable) + (agent:wait 15)) + details-matrix)) + +(agent:ensure-account) +(scenario/passive) diff --git a/test/agent/jami-agent-config.yml b/test/agent/jami-agent-config.yml deleted file mode 100644 index 4dae9ae59e622f3f6d5d8a5b2c45d40e5ead2b2c..0000000000000000000000000000000000000000 --- a/test/agent/jami-agent-config.yml +++ /dev/null @@ -1,19 +0,0 @@ -behavior: - - search-peer - - start-log-recording -# - : [ echo, true ] -# - : [ make-call, true ] - - stop-log-recording - - wait - -record-context: "My context: " -record-to: "out.txt" - -peers: - - "your-peer-id" - -params: - - [ upnpEnabled: "true", turnEnabled: "true" ] - # - [ upnpEnabled: "true", turnEnabled: "false" ] - # - [ upnpEnabled: "false", turnEnabled: "true" ] - # - [ upnpEnabled: "false", turnEnabled: "false" ] diff --git a/test/agent/jami-config.yml b/test/agent/jami-config.yml deleted file mode 100644 index 1c5ff51e8eda53957dfcc6a7a9a109fd61f450ea..0000000000000000000000000000000000000000 --- a/test/agent/jami-config.yml +++ /dev/null @@ -1,63 +0,0 @@ -accounts: - [] -preferences: - historyLimit: 0 - ringingTimeout: 30 - historyMaxCalls: 20 - md5Hash: false - order: 1479408070c09163/ - portNum: 5060 - searchBarDisplay: true - zoneToneChoice: North America -voipPreferences: - disableSecureDlgCheck: false - playDtmf: true - playTones: true - pulseLength: 250 - symmetric: true - zidFile: "" -audio: - alsa: - cardIn: 0 - cardOut: 0 - cardRing: 0 - plugin: default - smplRate: 44100 - alwaysRecording: false - audioApi: pulseaudio - automaticGainControl: false - captureMuted: false - noiseReduce: false - playbackMuted: false - pulse: - devicePlayback: "" - deviceRecord: "" - deviceRingtone: "" - portaudio: - devicePlayback: "" - deviceRecord: "" - deviceRingtone: "" - recordPath: "" - volumeMic: 1 - volumeSpkr: 1 - echoCanceller: system -video: - recordPreview: true - recordQuality: 0 - decodingAccelerated: true - encodingAccelerated: false - conferenceResolution: 1280x720 - devices: - [] -plugins: - pluginsEnabled: false - installedPlugins: - [] - loadedPlugins: - [] -shortcuts: - hangUp: "" - pickUp: "" - popupWindow: "" - toggleHold: "" - togglePickupHangup: "" \ No newline at end of file diff --git a/test/agent/main.cpp b/test/agent/main.cpp index 665208729a1427e480e310a282ceaeb7cb32046d..b6844bbdd9904ae474648e33da6b20cd103143b3 100644 --- a/test/agent/main.cpp +++ b/test/agent/main.cpp @@ -18,19 +18,85 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +/* agent */ #include "agent/agent.h" +#include "agent/bindings.h" /* Jami */ #include "jami.h" +/* Third parties */ +#include <gnutls/gnutls.h> +#include <libguile.h> + +/* std */ +#include <fstream> + +/* Guile helpers */ + +static SCM +main_body(void* path_raw) +{ + const char* path = (const char*)path_raw; + + if (0 == strcmp("-", path)) { + scm_c_primitive_load("/dev/stdin"); + } else { + scm_c_primitive_load(path); + } + + return SCM_UNDEFINED; +} + +static SCM +main_catch(void* nil, SCM key_sym, SCM rest_lst) +{ + (void) nil; + + SCM fmt_str = scm_from_utf8_string("Guile exception `~a`: ~a"); + SCM args_lst = scm_list_2(key_sym, rest_lst); + SCM to_print_str = scm_simple_format(SCM_BOOL_F, fmt_str, args_lst); + + char* to_print_raw = scm_to_locale_string(to_print_str); + + AGENT_ERR("%s\n", to_print_raw); + + free(to_print_raw); + + return SCM_UNDEFINED; +} + +void* +main_inner(void* agent_config_raw) /* In Guile context */ +{ + install_scheme_primitives(); + + Agent::instance().init(); + + scm_internal_catch(SCM_BOOL_T, main_body, agent_config_raw, main_catch, nullptr); + + Agent::instance().fini(); + + return nullptr; +} + int -main(void) +main(int argc, char* argv[]) { + if (argc < 2) { + printf("Usage: agent CONFIG\n"); + exit(EXIT_FAILURE); + } + + setenv("GUILE_LOAD_PATH", ".", 1); + + /* NOTE! It's very important to initialize the daemon before entering Guile!!! */ DRing::init(DRing::InitFlag(DRing::DRING_FLAG_DEBUG | DRing::DRING_FLAG_CONSOLE_LOG)); - assert(DRing::start("jami-config.yml")); + AGENT_ASSERT(DRing::start(""), "Failed to start daemon"); - Agent::run("jami-agent-config.yml"); + /* Entering guile context */ + scm_with_guile(main_inner, argv[1]); DRing::fini(); } diff --git a/test/agent/tools/make-account-archive b/test/agent/tools/make-account-archive new file mode 100755 index 0000000000000000000000000000000000000000..178200d10d228865912fc501609ee42fb61479cb --- /dev/null +++ b/test/agent/tools/make-account-archive @@ -0,0 +1,14 @@ +#!/bin/sh + +tmp=$(mktemp) + +export XDG_CONFIG_HOME=$tmp +export XDG_CACHE_HOME=$tmp +export XDG_DATA_HOME=$tmp + +archive=$1 + +./agent - <<EOF +(agent:ensure-account) +(agent->archive "${archive}") +EOF diff --git a/test/agent/utils.cpp b/test/agent/utils.cpp deleted file mode 100644 index c656ca7d68202019c49f3c06af7b64ba5c44398b..0000000000000000000000000000000000000000 --- a/test/agent/utils.cpp +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright (C) 2021 Savoir-faire Linux Inc. - * - * Author: Olivier Dion <olivier.dion>@savoirfairelinux.com> - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 3 of the License, or - * (at your option) any later version. - * - * This program 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/* std */ -#include <atomic> -#include <condition_variable> -#include <mutex> -#include <cassert> -#include <vector> - -/* DRing */ -#include "jami.h" -#include "jami/account_const.h" -#include "jami/configurationmanager_interface.h" -#include "jami/call_const.h" - -/* agent */ -#include "agent/utils.h" - -void -wait_for_announcement_of(const std::vector<std::string> accountIDs, std::chrono::seconds timeout) -{ - std::map<std::string, std::shared_ptr<DRing::CallbackWrapperBase>> confHandlers; - std::mutex mtx; - std::unique_lock<std::mutex> lk {mtx}; - std::condition_variable cv; - std::vector<std::atomic_bool> accountsReady(accountIDs.size()); - - confHandlers.insert( - DRing::exportable_callback<DRing::ConfigurationSignal::VolatileDetailsChanged>( - [&, - accountIDs = std::move(accountIDs)](const std::string& accountID, - const std::map<std::string, std::string>& details) { - for (size_t i = 0; i < accountIDs.size(); ++i) { - if (accountIDs[i] != accountID) { - continue; - } - - try { - if ("true" - != details.at(DRing::Account::VolatileProperties::DEVICE_ANNOUNCED)) { - continue; - } - } catch (const std::out_of_range&) { - continue; - } - - accountsReady[i] = true; - cv.notify_one(); - } - })); - - DRing::registerSignalHandlers(confHandlers); - - assert(cv.wait_for(lk, timeout, [&] { - for (const auto& rdy : accountsReady) { - if (not rdy) { - return false; - } - } - - return true; - })); - - DRing::unregisterSignalHandlers(); -} - -void -wait_for_announcement_of(const std::string& accountId, std::chrono::seconds timeout) -{ - wait_for_announcement_of(std::vector<std::string> {accountId}, timeout); -} diff --git a/test/agent/utils.h b/test/agent/utils.h deleted file mode 100644 index 148fb4aa8295c3f1ab4118ac47fb644c4cf33931..0000000000000000000000000000000000000000 --- a/test/agent/utils.h +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (C) 2021 Savoir-faire Linux Inc. - * - * Author: Olivier Dion <olivier.dion>@savoirfairelinux.com> - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 3 of the License, or - * (at your option) any later version. - * - * This program 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#pragma once - -#include <chrono> -#include <cstdint> -#include <map> -#include <string> - -constexpr size_t WAIT_FOR_ANNOUNCEMENT_TIMEOUT = 30; -constexpr size_t WAIT_FOR_REMOVAL_TIMEOUT = 30; - -extern void -wait_for_announcement_of(const std::vector<std::string> accountIDs, - std::chrono::seconds timeout = std::chrono::seconds(WAIT_FOR_ANNOUNCEMENT_TIMEOUT)); - -extern void -wait_for_announcement_of(const std::string& accountId, - std::chrono::seconds timeout = std::chrono::seconds(WAIT_FOR_ANNOUNCEMENT_TIMEOUT)); - -extern void -wait_for_removal_of(const std::vector<std::string> accounts, - std::chrono::seconds timeout = std::chrono::seconds(WAIT_FOR_REMOVAL_TIMEOUT)); - -extern void -wait_for_removal_of(const std::string& account, - std::chrono::seconds timeout = std::chrono::seconds(WAIT_FOR_REMOVAL_TIMEOUT));