From 98df43e9f23b5981bbe4bffccb35d3d3259c474e Mon Sep 17 00:00:00 2001 From: Olivier Dion <olivier.dion@savoirfairelinux.com> Date: Tue, 20 Jul 2021 11:49:23 -0400 Subject: [PATCH] agent: Use Guile By removing the YAML configuration, the agent is now configured via a Scheme file interpreted by Guile. There's no need for behavior trees now since it can be reproduced naturally in the Scheme language. See README.md for how to compile and run. Change-Id: Ibbf9ed0f965cee71bce63c0f8e8c12862f37176d --- test/agent/.gitignore | 7 +- test/agent/Makefile.am | 13 +- test/agent/README.md | 55 +++ test/agent/agent.cpp | 601 ++++++++++++-------------- test/agent/agent.h | 69 +-- test/agent/bindings.cpp | 215 +++++++++ test/agent/{log.h => bindings.h} | 17 +- test/agent/bt.cpp | 60 --- test/agent/bt.h | 105 ----- test/agent/examples/active-agent.scm | 98 +++++ test/agent/examples/passive-agent.scm | 29 ++ test/agent/jami-agent-config.yml | 19 - test/agent/jami-config.yml | 63 --- test/agent/main.cpp | 72 ++- test/agent/tools/make-account-archive | 14 + test/agent/utils.cpp | 89 ---- test/agent/utils.h | 45 -- 17 files changed, 817 insertions(+), 754 deletions(-) create mode 100644 test/agent/README.md create mode 100644 test/agent/bindings.cpp rename test/agent/{log.h => bindings.h} (74%) delete mode 100644 test/agent/bt.cpp delete mode 100644 test/agent/bt.h create mode 100644 test/agent/examples/active-agent.scm create mode 100644 test/agent/examples/passive-agent.scm delete mode 100644 test/agent/jami-agent-config.yml delete mode 100644 test/agent/jami-config.yml create mode 100755 test/agent/tools/make-account-archive delete mode 100644 test/agent/utils.cpp delete mode 100644 test/agent/utils.h diff --git a/test/agent/.gitignore b/test/agent/.gitignore index 93d93c78cb..a39bc478f1 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 349846a7cf..6dea50cbe4 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 0000000000..aa79a9eac6 --- /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 a28f322513..c29e179e95 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 5cbb348eb4..21c446a3c4 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 0000000000..4ab15351ca --- /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 75ffed2378..85ac943267 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 c03abc48b3..0000000000 --- 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 18537f88aa..0000000000 --- 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 0000000000..84078dd3fc --- /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 0000000000..e782466489 --- /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 4dae9ae59e..0000000000 --- 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 1c5ff51e8e..0000000000 --- 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 665208729a..b6844bbdd9 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 0000000000..178200d10d --- /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 c656ca7d68..0000000000 --- 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 148fb4aa82..0000000000 --- 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)); -- GitLab