diff --git a/configure.ac b/configure.ac index 475880ee9dfb5976ddbadb32b8c4817fe62ad583..7ebef1b4f3786dc05e2f4303e1e4f3181a860ad6 100644 --- a/configure.ac +++ b/configure.ac @@ -661,6 +661,7 @@ AC_CONFIG_FILES([Makefile \ test/Makefile\ test/sip/Makefile test/unitTest/Makefile \ + test/agent/Makefile \ man/Makefile \ doc/Makefile \ doc/doxygen/Makefile]) diff --git a/test/Makefile.am b/test/Makefile.am index c727e98ec1dea2f036108572fadbadbc2fe2ca52..1d65e7b9e3a5332e10f73e8e1a5ae680faeef084 100644 --- a/test/Makefile.am +++ b/test/Makefile.am @@ -1,5 +1,5 @@ -SUBDIRS = unitTest +SUBDIRS = unitTest agent SUBDIRS += sip if ENABLE_FUZZING SUBDIRS += fuzzing -endif \ No newline at end of file +endif diff --git a/test/agent/.gitignore b/test/agent/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..93d93c78cb8eeaea6050f5082cf303ee6a27a1dc --- /dev/null +++ b/test/agent/.gitignore @@ -0,0 +1,3 @@ +agent + + diff --git a/test/agent/Makefile.am b/test/agent/Makefile.am new file mode 100644 index 0000000000000000000000000000000000000000..731fc008abd6f33d22afd5ce9452d0d12b42e84c --- /dev/null +++ b/test/agent/Makefile.am @@ -0,0 +1,10 @@ +include $(top_srcdir)/globals.mk + +AM_CXXFLAGS += -I$(top_srcdir)/src -I.. \ + -DTOP_BUILDDIR=\"$$(cd "$(top_builddir)"; pwd)\" + +check_PROGRAMS = agent + +agent_SOURCES = agent.cpp agent.h utils.cpp utils.h bt.cpp bt.h main.cpp + +agent_LDADD = $(top_builddir)/src/libring.la -ldl diff --git a/test/agent/agent.cpp b/test/agent/agent.cpp new file mode 100644 index 0000000000000000000000000000000000000000..4f7aefc7717f25b7916cccc564ade170c73ff3db --- /dev/null +++ b/test/agent/agent.cpp @@ -0,0 +1,501 @@ +/* + * 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 <chrono> +#include <condition_variable> +#include <fstream> +#include <memory> +#include <mutex> +#include <thread> + +/* Third parties */ +#include <yaml-cpp/yaml.h> + +/* DRing */ +#include "account_const.h" +#include "dring/presencemanager_interface.h" +#include "dring/callmanager_interface.h" +#include "dring/configurationmanager_interface.h" +#include "dring/conversation_interface.h" + +/* 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); \ + } + +void +Agent::initBehavior() +{ + using std::bind; + + 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)); +} + +void +Agent::configure(const std::string& yaml_config) +{ + std::ifstream file = std::ifstream(yaml_config); + + AGENT_ASSERT(file.is_open(), "Failed to open configuration file `%s`", yaml_config.c_str()); + + YAML::Node node = YAML::Load(file); + + auto account = node["account-id"]; + + AGENT_ASSERT(account.IsScalar(), "Bad or missing field `account-id`"); + + accountID_ = account.as<std::string>(); + + auto peers = node["peers"]; + + AGENT_ASSERT(peers.IsSequence(), "Configuration node `peers` must be a sequence"); + + for (const auto& peer : peers) { + peers_.emplace_back(peer.as<std::string>()); + } + + 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); + }); + } + } +} + +void +Agent::ensureAccount() +{ + std::map<std::string, std::string> details; + + details = DRing::getAccountDetails(accountID_); + + if (details.empty()) { + 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] = ""; + + accountID_ = DRing::addAccount(details); + + wait_for_announcement_of(accountID_); + + details = DRing::getAccountDetails(accountID_); + } + + peerID_ = details.at(DRing::Account::ConfProperties::USERNAME); +} + +void +Agent::getConversations() +{ + conversations_ = DRing::getConversations(accountID_); +} + +void +Agent::installSignalHandlers() +{ + using namespace std::placeholders; + using std::bind; + + std::map<std::string, std::shared_ptr<DRing::CallbackWrapperBase>> handlers; + + handlers.insert(DRing::exportable_callback<DRing::CallSignal::IncomingCallWithMedia>( + bind(&Agent::Handler<const std::string&, + const std::string&, + const std::string&, + const std::vector<DRing::MediaMap>>::execute, + &onIncomingCall_, + _1, + _2, + _3, + _4))); + + handlers.insert(DRing::exportable_callback<DRing::CallSignal::StateChange>( + bind(&Agent::Handler<const std::string&, const std::string&, signed>::execute, + &onCallStateChanged_, + _1, + _2, + _3))); + + handlers.insert(DRing::exportable_callback<DRing::ConversationSignal::MessageReceived>( + bind(&Agent::Handler<const std::string&, + const std::string&, + std::map<std::string, std::string>>::execute, + &onMessageReceived_, + _1, + _2, + _3))); + + handlers.insert( + DRing::exportable_callback<DRing::ConversationSignal::ConversationRequestReceived>( + bind(&Agent::Handler<const std::string&, + const std::string&, + std::map<std::string, std::string>>::execute, + &onConversationRequestReceived_, + _1, + _2, + _3))); + + handlers.insert(DRing::exportable_callback<DRing::ConversationSignal::ConversationReady>( + bind(&Agent::Handler<const std::string&, const std::string&>::execute, + &onConversationReady_, + _1, + _2))); + + handlers.insert(DRing::exportable_callback<DRing::ConfigurationSignal::ContactAdded>( + bind(&Agent::Handler<const std::string&, const std::string&, bool>::execute, + &onContactAdded_, + _1, + _2, + _3))); + + DRing::registerSignalHandlers(handlers); +} + +void +Agent::registerStaticCallbacks() +{ + onIncomingCall_.add([=](const std::string& accountID, + const std::string& callID, + const std::string& peerDisplayName, + const std::vector<DRing::MediaMap> mediaList) { + (void) accountID; + (void) peerDisplayName; + + AGENT_INFO("Incoming call from `%s`", peerDisplayName.c_str()); + + AGENT_ASSERT(DRing::acceptWithMedia(callID, mediaList), + "Failed to accept call `%s`", + callID.c_str()); + + return true; + }); + + onMessageReceived_.add([=](const std::string& accountID, + const std::string& conversationID, + std::map<std::string, std::string> message) { + (void) accountID; + + /* Read only text message */ + if ("text/plain" != message.at("type")) { + return true; + } + + auto author = message.at("author"); + + /* Skip if sent by agent */ + if (peerID_ == author) { + return true; + } + + auto msg = message.at("body"); + + AGENT_INFO("Incomming message `%s` from %s", msg.c_str(), author.c_str()); + + /* Echo back */ + DRing::sendMessage(accountID_, conversationID, msg, ""); + + return true; + }); + + onConversationRequestReceived_.add([=](const std::string& accountID, + const std::string& conversationID, + std::map<std::string, std::string> meta) { + (void) meta; + + AGENT_INFO("Conversation request received for account %s", accountID.c_str()); + + DRing::acceptConversationRequest(accountID, conversationID); + + return true; + }); + + onConversationReady_.add([=](const std::string& accountID, const std::string& conversationID) { + (void) accountID; + conversations_.emplace_back(conversationID); + return true; + }); + + onContactAdded_.add([=](const std::string& accountID, const std::string& URI, bool confirmed) { + AGENT_INFO("Contact added `%s` : %s", URI.c_str(), confirmed ? "accepted" : "refused"); + if (confirmed) { + DRing::subscribeBuddy(accountID, URI, true); + } + return true; + }); +} + +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); + + cv->wait(lck); + + return true; +} + +void +Agent::run(const std::string& yaml_config) +{ + static Agent agent; + + agent.initBehavior(); + agent.configure(yaml_config); + agent.ensureAccount(); + 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 */ + } + } +} + +/* 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::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; + }); + + /* 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; +} + +bool +Agent::makeCall() +{ + 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; +} + +bool +Agent::wait() +{ + LOG_AGENT_STATE(); + + std::this_thread::sleep_for(std::chrono::seconds(30)); + + return true; +} diff --git a/test/agent/agent.h b/test/agent/agent.h new file mode 100644 index 0000000000000000000000000000000000000000..6e6419eebcace23e266d54a39e0afb622f728b84 --- /dev/null +++ b/test/agent/agent.h @@ -0,0 +1,112 @@ +/* + * 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 + +/* agent */ +#include "agent/bt.h" + +/* Dring */ +#include "dring/dring.h" + +/* std */ +#include <memory> +#include <mutex> +#include <string> +#include <vector> + +class Agent +{ + template<typename... Args> + class Handler + { + std::mutex mutex_; + std::vector<std::function<bool(Args...)>> callbacks_; + + public: + void add(std::function<bool(Args...)>&& cb) + { + std::unique_lock<std::mutex> lck(mutex_); + callbacks_.emplace_back(std::move(cb)); + } + + void execute(Args... args) + { + std::vector<std::function<bool(Args...)>> to_keep; + std::unique_lock<std::mutex> lck(mutex_); + + for (auto& cb : callbacks_) { + if (cb(args...)) { + to_keep.emplace_back(std::move(cb)); + } + } + + callbacks_.swap(to_keep); + } + }; + + /* Signal handlers */ + Handler<const std::string&, const std::string&, std::map<std::string, std::string>> + onMessageReceived_; + + Handler<const std::string&, const std::string&, std::map<std::string, std::string>> + onConversationRequestReceived_; + + Handler<const std::string&, const std::string&> onConversationReady_; + + Handler<const std::string&, const std::string&, signed> onCallStateChanged_; + + Handler<const std::string&, + const std::string&, + const std::string&, + const std::vector<DRing::MediaMap>> + onIncomingCall_; + + Handler<const std::string&, const std::string&, bool> onContactAdded_; + /* Initialize agent */ + void configure(const std::string& yaml_config); + void getConversations(); + void ensureAccount(); + void initBehavior(); + void installSignalHandlers(); + void registerStaticCallbacks(); + + /* Bookkeeping */ + std::string peerID_; + std::string accountID_; + std::vector<std::string> peers_; + std::vector<std::string> conversations_; + std::unique_ptr<BT::Node> root_; + std::vector<std::function<void(void)>> params_; + + /* Helper */ + void sendMessage(const std::string& to, const std::string& msg); + + /* Behavior */ + bool searchPeer(); + bool wait(); + bool echo(); + bool makeCall(); + bool False() { return false; } + bool True() { return true; } + +public: + static void run(const std::string& yaml_config); +}; diff --git a/test/agent/bt.cpp b/test/agent/bt.cpp new file mode 100644 index 0000000000000000000000000000000000000000..c03abc48b3745fd3e87684d005f6ea04bc91c61a --- /dev/null +++ b/test/agent/bt.cpp @@ -0,0 +1,60 @@ +/* + * 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 new file mode 100644 index 0000000000000000000000000000000000000000..18537f88aad2e58db5269d6c17326558ab827345 --- /dev/null +++ b/test/agent/bt.h @@ -0,0 +1,105 @@ +/* + * 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/jami-agent-config.yml b/test/agent/jami-agent-config.yml new file mode 100644 index 0000000000000000000000000000000000000000..18388dcce0546a7f30a41f9870bfc5361b0917ae --- /dev/null +++ b/test/agent/jami-agent-config.yml @@ -0,0 +1,16 @@ +behavior: + - search-peer +# - : [ echo, true ] +# - : [ make-call, true ] + - wait + +peers: + - "your-peer-id" + +account-id: "d65ebb0f1170ae44" + +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 new file mode 100644 index 0000000000000000000000000000000000000000..1c5ff51e8eda53957dfcc6a7a9a109fd61f450ea --- /dev/null +++ b/test/agent/jami-config.yml @@ -0,0 +1,63 @@ +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 new file mode 100644 index 0000000000000000000000000000000000000000..849071f619e9bafd7b702a61e85e6e7a4c4d4ef8 --- /dev/null +++ b/test/agent/main.cpp @@ -0,0 +1,36 @@ +/* + * 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" + +/* Jami */ +#include "dring.h" + +int +main(void) +{ + DRing::init(DRing::InitFlag(DRing::DRING_FLAG_DEBUG | DRing::DRING_FLAG_CONSOLE_LOG)); + + assert(DRing::start("jami-config.yml")); + + Agent::run("jami-agent-config.yml"); + + DRing::fini(); +} diff --git a/test/agent/utils.cpp b/test/agent/utils.cpp new file mode 100644 index 0000000000000000000000000000000000000000..f3bbcab5aa5a8d2fe885d6cff0fd6a6a07dc6880 --- /dev/null +++ b/test/agent/utils.cpp @@ -0,0 +1,89 @@ +/* + * 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 "dring.h" +#include "dring/account_const.h" +#include "dring/configurationmanager_interface.h" +#include "dring/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 new file mode 100644 index 0000000000000000000000000000000000000000..148fb4aa8295c3f1ab4118ac47fb644c4cf33931 --- /dev/null +++ b/test/agent/utils.h @@ -0,0 +1,45 @@ +/* + * 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));