diff --git a/test/agent/Makefile.am b/test/agent/Makefile.am index 13687960893a6ff48eda255df745322681c9b406..34aae1f329efd2d5ef9ef7e89395cd6454a4a70a 100644 --- a/test/agent/Makefile.am +++ b/test/agent/Makefile.am @@ -1,15 +1,14 @@ include $(top_srcdir)/globals.mk -AM_CXXFLAGS += -I$(top_srcdir)/src -I.. $(GUILE_CFLAGS) -rdynamic +AM_CXXFLAGS += -I$(top_srcdir)/src -I./src $(GUILE_CFLAGS) -rdynamic AM_LDFLAGS += $(GUILE_LIBS) noinst_PROGRAMS = agent -agent_SOURCES = \ - agent.cpp \ - agent.h \ - bindings.cpp \ - bindings.h \ - main.cpp +agent_SOURCES = \ + src/main.cpp \ + src/utils.h \ + src/bindings/bindings.cpp \ + src/bindings/bindings.h agent_LDADD = $(top_builddir)/src/libring.la diff --git a/test/agent/agent.cpp b/test/agent/agent.cpp deleted file mode 100644 index 44ffdb2bcb4d64ffb24f86031022aa23fdb2162c..0000000000000000000000000000000000000000 --- a/test/agent/agent.cpp +++ /dev/null @@ -1,563 +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 <chrono> -#include <condition_variable> -#include <fstream> -#include <memory> -#include <mutex> -#include <thread> - -/* DRing */ -#include "account_const.h" -#include "jami/presencemanager_interface.h" -#include "jami/callmanager_interface.h" -#include "jami/configurationmanager_interface.h" -#include "jami/conversation_interface.h" - -/* agent */ -#include "agent/agent.h" - -using usize = size_t; - -#define LOG_AGENT_STATE() AGENT_DBG("In state %s", __FUNCTION__) - -void -Agent::searchForPeers(std::vector<std::string>& peers) -{ - LOG_AGENT_STATE(); - - for (auto it = peers.begin(); it != peers.end(); ++it) { - AGENT_INFO("Searching for peer %s", it->c_str()); - - DRing::sendTrustRequest(accountID_, it->c_str()); - DRing::subscribeBuddy(accountID_, it->c_str(), true); - } -} - -bool -Agent::ping(const std::string& conversation) -{ - LOG_AGENT_STATE(); - - std::mutex mtx; - std::condition_variable cv; - std::atomic<bool> pongReceived(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) { - 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) { - std::unique_lock lk(mtx); - pongReceived.store(true); - cv.notify_one(); - return false; - } - - return true; - }); - - AGENT_INFO("Sending ping `%s` to `%s`", messageSent.c_str(), conversation.c_str()); - - DRing::sendMessage(accountID_, conversation, messageSent, ""); - - /* Waiting for echo */ - - std::unique_lock<std::mutex> lk(mtx); - - bool ret = (std::cv_status::no_timeout == cv.wait_for(lk, 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"); - } - } - - return ""; -} - -std::string -Agent::someConversation() const -{ - if (conversations_.empty()) { - return ""; - } - - auto it = conversations_.begin(); - - std::advance(it, rand() % conversations_.size()); - - return *it; -} - -bool -Agent::placeCall(const std::string& contact) -{ - LOG_AGENT_STATE(); - - std::mutex mtx; - std::condition_variable cv; - bool success(false); - bool over(false); - - std::string callID = ""; - - 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); - - std::unique_lock lk(mtx); - - if (call_id != callID) { - return true; - } - - bool ret = true; - - if ("CURRENT" == state) { - success = true; - } else if ("OVER" == state) { - over = true; - ret = false; - } - - cv.notify_one(); - - return ret; - }); - - callID = DRing::placeCall(accountID_, contact); - - AGENT_INFO("Waiting for call %s", callID.c_str()); - - /* TODO - Parametize me */ - { - std::unique_lock lk(mtx); - cv.wait_for(lk, std::chrono::seconds(30), [&] { return success or over; }); - } - - if (success) { - 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) { - std::unique_lock lk(mtx); - cv.wait_for(lk, std::chrono::seconds(30), [&] { return over; }); - } - - return success; -} - -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); -} - -std::map<std::string, std::string> -Agent::getDetails() const -{ - return DRing::getAccountDetails(accountID_); -} - -void -Agent::activate(bool enable) -{ - LOG_AGENT_STATE(); - - DRing::sendRegister(accountID_, enable); - - if (enable) { - 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_); - - 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] = ""; - - AGENT_ASSERT(accountID_ == DRing::addAccount(details, accountID_), "Bad accountID"); - } - - waitForAnnouncement(); - - 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::waitForCallState(const std::string& wanted) -{ - LOG_AGENT_STATE(); - - std::mutex mtx; - std::condition_variable cv; - std::unique_lock lk(mtx); - - onCallStateChanged_.add( - [&](const std::string& /* call_id */, const std::string& state, signed /* code */) { - if (wanted == state) { - std::unique_lock lk(mtx); - cv.notify_one(); - - return false; - } - - return true; - }); - - cv.wait(lk); -} - -Agent& -Agent::instance() -{ - static Agent agent; - - return agent; -} - -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))); - - 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); -} - -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; - - 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`", - 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 */ - if (0 != msg.rfind("PONG:", 0)) { - DRing::sendMessage(accountID_, conversationID, "PONG:" + 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; - }); -} - -void -Agent::waitForAnnouncement(std::chrono::seconds timeout) -{ - LOG_AGENT_STATE(); - - std::condition_variable cv; - - std::mutex mtx; - - onVolatileDetailsChanged_.add( - [&](const std::string& accountID, const std::map<std::string, std::string>& details) { - if (accountID_ != accountID) { - return true; - } - - try { - if ("true" != details.at(DRing::Account::VolatileProperties::DEVICE_ANNOUNCED)) { - return true; - } - } catch (const std::out_of_range&) { - return true; - } - - std::unique_lock lk(mtx); - - cv.notify_one(); - - return false; - }); - - std::unique_lock lk(mtx); - - AGENT_ASSERT(std::cv_status::no_timeout == cv.wait_for(lk, timeout), - "Timeout while waiting for account announcement on DHT"); -} - -void -Agent::init() -{ - DRing::logging("console", "on"); - LOG_AGENT_STATE(); - - installSignalHandlers(); - registerStaticCallbacks(); -} - -void -Agent::fini() -{ - LOG_AGENT_STATE(); - - DRing::unregisterSignalHandlers(); -} diff --git a/test/agent/agent.h b/test/agent/agent.h deleted file mode 100644 index 16e1ffbd693abb17857337a1f6360b6ede4d8501..0000000000000000000000000000000000000000 --- a/test/agent/agent.h +++ /dev/null @@ -1,131 +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 - -/* 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> - 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_; - - 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 installSignalHandlers(); - void registerStaticCallbacks(); - - /* Bookkeeping */ - std::string peerID_; - const std::string accountID_ {"afafafafafafafaf"}; - std::vector<std::string> conversations_; - -public: - /* Behavior */ - 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); - std::map<std::string, std::string> getDetails() const; - 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); - void waitForCallState(const std::string& wanted="CURRENT"); - - void init(); - void fini(); - - static Agent& instance(); -}; diff --git a/test/agent/bindings.cpp b/test/agent/bindings.cpp deleted file mode 100644 index e00b5efa412437361f49505567891217eff6291e..0000000000000000000000000000000000000000 --- a/test/agent/bindings.cpp +++ /dev/null @@ -1,320 +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/agent.h" -#include "agent/bindings.h" - -static inline SCM -to_guile(bool b) -{ - return scm_from_bool(b); -} - -static inline SCM -to_guile(const std::string& str) -{ - return scm_from_utf8_string(str.c_str()); -} - -template<typename T> -static inline SCM -to_guile(const std::vector<T>& values) -{ - SCM vec = scm_make_vector(values.size(), SCM_UNDEFINED); - - for (size_t i = 0; i < values.size(); ++i) { - SCM_SIMPLE_VECTOR_SET(vec, i, to_guile(values[i])); - } - - return vec; -} - -template<typename K, typename V> -static inline SCM -to_guile(const std::map<K, V>& map) -{ - SCM assoc = SCM_EOL; - - for (auto const& [key, value] : map) { - SCM pair = scm_cons(to_guile(key), to_guile(value)); - assoc = scm_cons(pair, assoc); - } - - return assoc; -} - -template<typename... Args> -static inline SCM -pack_to_guile(Args... args) -{ - SCM lst = SCM_EOL; - std::vector<SCM> values = {to_guile(args)...}; - - while (values.size()) { - lst = scm_cons(values.back(), lst); - values.pop_back(); - } - - return lst; -} - -template<typename... Args> -static inline SCM -apply_to_guile(SCM body_proc, Args... args) -{ - AGENT_ASSERT(scm_is_true(scm_procedure_p(body_proc)), - "body_proc must be a procedure"); - - SCM arglst = pack_to_guile(args...); - - return scm_apply_0(body_proc, arglst); -} - -struct from_guile -{ - SCM value; - - from_guile(SCM val) - : value(val) - {} - - operator bool() - { - AGENT_ASSERT(scm_is_bool(value), "Scheme value must be of type bool"); - - return scm_to_bool(value); - } - - operator std::string() - { - AGENT_ASSERT(scm_is_string(value), "Scheme value must be of type string"); - - char* str_raw = scm_to_locale_string(value); - std::string ret(str_raw); - free(str_raw); - - return ret; - } - - template<typename T> - operator std::vector<T>() - { - AGENT_ASSERT(scm_is_simple_vector(value), "Scheme value must be a simple vector"); - - std::vector<T> ret; - - for (size_t i = 0; i < SCM_SIMPLE_VECTOR_LENGTH(value); ++i) { - SCM val = SCM_SIMPLE_VECTOR_REF(value, i); - - ret.emplace_back(from_guile(val)); - } - - return ret; - } - - template<typename K, typename V> - operator std::map<K, V>() - { - AGENT_ASSERT(scm_is_true(scm_list_p(value)), "Scheme value mut be a list"); - - std::map<K, V> ret; - - while (not scm_is_null(value)) { - SCM pair = scm_car(value); - - K key = from_guile(scm_car(pair)); - V val = from_guile(scm_cdr(pair)); - - ret[key] = val; - - value = scm_cdr(value); - } - - 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) -{ - return to_guile(Agent::instance().placeCall(from_guile(contact_str))); -} - -static SCM -some_conversation_binding() -{ - return to_guile(Agent::instance().someConversation()); -} - -static SCM -some_contact_binding() -{ - return to_guile(Agent::instance().someContact()); -} - -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(from_guile(peers_vector_or_str)); - } else { - peers = from_guile(peers_vector_or_str); - } - - Agent::instance().searchForPeers(peers); - - return SCM_UNDEFINED; -} - -static SCM -ping_binding(SCM contact_str) -{ - return to_guile(Agent::instance().ping(from_guile(contact_str))); -} - -static SCM -set_details_binding(SCM details_alist) -{ - Agent::instance().setDetails(from_guile(details_alist)); - - return SCM_UNDEFINED; -} - -static SCM -get_details_binding() -{ - return to_guile(Agent::instance().getDetails()); -} - -static SCM -ensure_account_binding() -{ - Agent::instance().ensureAccount(); - - return SCM_UNDEFINED; -} - -static SCM -export_to_archive_binding(SCM path) -{ - Agent::instance().exportToArchive(from_guile(path)); - - return SCM_UNDEFINED; -} - -static SCM -import_from_archive_binding(SCM path) -{ - Agent::instance().importFromArchive(from_guile(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; -} - -static SCM -wait_for_call_state(SCM wanted_state_str) -{ - Agent::instance().waitForCallState(from_guile(wanted_state_str)); - - 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:get-details", 0, 0, 0, (void*) get_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); - define_primitive("agent:wait-for-call-state", 1, 0, 0, (void*) wait_for_call_state); -} diff --git a/test/agent/src/bindings/bindings.cpp b/test/agent/src/bindings/bindings.cpp new file mode 100644 index 0000000000000000000000000000000000000000..3b71dfc66db2f948117ec7d3f0d7d833e6003815 --- /dev/null +++ b/test/agent/src/bindings/bindings.cpp @@ -0,0 +1,63 @@ +/* + * 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. + */ + +/* Agent */ +#include "bindings/bindings.h" + +/* Include module's bindings here */ + +void +install_scheme_primitives() +{ + /* Define modules here */ +} + +/* + * 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 +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); + scm_c_export(name, NULL); +} diff --git a/test/agent/bindings.h b/test/agent/src/bindings/bindings.h similarity index 87% rename from test/agent/bindings.h rename to test/agent/src/bindings/bindings.h index 85ac943267cda372e6b0f0e56cbd1fe79c7ffb48..fc88c69edd3adec0ecb28513085e7c6558749bdf 100644 --- a/test/agent/bindings.h +++ b/test/agent/src/bindings/bindings.h @@ -20,8 +20,11 @@ #pragma once +/* Guile */ #include <libguile.h> -#define scm_to_cxx_string(VAR) (scm_to_cxx_string)(VAR, #VAR) +/* Agent */ +#include "utils.h" +extern void define_primitive(const char* name, int req, int opt, int rst, void* func); extern void install_scheme_primitives(); diff --git a/test/agent/main.cpp b/test/agent/src/main.cpp similarity index 70% rename from test/agent/main.cpp rename to test/agent/src/main.cpp index 56c5e2a98b9400fcb1b9da2185ea3134fa1df3ac..8c4da751a178c581a1eb51815778ff7a98e416f1 100644 --- a/test/agent/main.cpp +++ b/test/agent/src/main.cpp @@ -18,56 +18,39 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -/* agent */ -#include "agent/agent.h" -#include "agent/bindings.h" +/* Agent */ +#include "bindings/bindings.h" +#include "utils.h" /* Jami */ #include "jami.h" /* Third parties */ -#include <gnutls/gnutls.h> #include <libguile.h> -/* std */ -#include <fstream> - -static void -fini() -{ - Agent::instance().fini(); - DRing::fini(); -} - struct args { int argc; char** argv; }; void* -main_inner(void* args_raw) /* In Guile context */ +main_in_guile(void* args_raw) { - struct args* args = (struct args*)args_raw; + struct args* args = static_cast<struct args*>(args_raw); install_scheme_primitives(); - Agent::instance().init(); - - atexit(fini); + atexit(DRing::fini); scm_shell(args->argc, args->argv); + /* unreachable */ return nullptr; } int 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!!! */ @@ -75,11 +58,8 @@ main(int argc, char* argv[]) AGENT_ASSERT(DRing::start(""), "Failed to start daemon"); - struct args args; - - args.argc = argc; - args.argv = argv; + struct args args = { argc, argv }; - /* Entering guile context */ - scm_with_guile(main_inner, (void*)&args); + /* Entering guile context - This never returns */ + scm_with_guile(main_in_guile, (void*)&args); } diff --git a/test/agent/src/utils.h b/test/agent/src/utils.h new file mode 100644 index 0000000000000000000000000000000000000000..be2d82b9f2af0bb4107e339fd7cdf4c538ac5791 --- /dev/null +++ b/test/agent/src/utils.h @@ -0,0 +1,168 @@ +/* + * 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 "logger.h" + +#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); \ + } + +static inline SCM +to_guile(bool b) +{ + return scm_from_bool(b); +} + +static inline SCM +to_guile(const std::string& str) +{ + return scm_from_utf8_string(str.c_str()); +} + +/* Forward declarations since we call to_guile() recursively for containers */ +template<typename T> static inline SCM to_guile(const std::vector<T>& values); +template<typename K, typename V> static inline SCM to_guile(const std::map<K, V>& map); + +template<typename T> +static inline SCM +to_guile(const std::vector<T>& values) +{ + SCM vec = scm_c_make_vector(values.size(), SCM_UNDEFINED); + + for (size_t i = 0; i < values.size(); ++i) { + SCM_SIMPLE_VECTOR_SET(vec, i, to_guile(values[i])); + } + + return vec; +} + +template<typename K, typename V> +static inline SCM +to_guile(const std::map<K, V>& map) +{ + SCM assoc = SCM_EOL; + + for (auto const& [key, value] : map) { + SCM pair = scm_cons(to_guile(key), to_guile(value)); + assoc = scm_cons(pair, assoc); + } + + return assoc; +} + +template<typename... Args> +static inline SCM +pack_to_guile(Args... args) +{ + SCM lst = SCM_EOL; + std::vector<SCM> values = {to_guile(args)...}; + + while (values.size()) { + lst = scm_cons(values.back(), lst); + values.pop_back(); + } + + return lst; +} + +template<typename... Args> +static inline SCM +apply_to_guile(SCM body_proc, Args... args) +{ + AGENT_ASSERT(scm_is_true(scm_procedure_p(body_proc)), + "body_proc must be a procedure"); + + SCM arglst = pack_to_guile(args...); + + return scm_apply_0(body_proc, arglst); +} + +struct from_guile +{ + SCM value; + + from_guile(SCM val) + : value(val) + {} + + operator bool() + { + AGENT_ASSERT(scm_is_bool(value), "Scheme value must be of type bool"); + + return scm_to_bool(value); + } + + operator std::string() + { + AGENT_ASSERT(scm_is_string(value), "Scheme value must be of type string"); + + char* str_raw = scm_to_locale_string(value); + std::string ret(str_raw); + free(str_raw); + + return ret; + } + + template<typename T> + operator std::vector<T>() + { + AGENT_ASSERT(scm_is_simple_vector(value), "Scheme value must be a simple vector"); + + std::vector<T> ret; + + ret.reserve(SCM_SIMPLE_VECTOR_LENGTH(value)); + + for (size_t i = 0; i < SCM_SIMPLE_VECTOR_LENGTH(value); ++i) { + SCM val = SCM_SIMPLE_VECTOR_REF(value, i); + + ret.emplace_back(from_guile(val)); + } + + return ret; + } + + template<typename K, typename V> + operator std::map<K, V>() + { + AGENT_ASSERT(scm_is_true(scm_list_p(value)), "Scheme value mut be a list"); + + std::map<K, V> ret; + + while (not scm_is_null(value)) { + SCM pair = scm_car(value); + + K key = from_guile(scm_car(pair)); + V val = from_guile(scm_cdr(pair)); + + ret[key] = val; + + value = scm_cdr(value); + } + + return ret; + } +};