diff options
author | Matthias P. Braendli <matthias.braendli@mpb.li> | 2019-08-13 10:29:39 +0200 |
---|---|---|
committer | Matthias P. Braendli <matthias.braendli@mpb.li> | 2019-08-13 10:29:39 +0200 |
commit | a5c50a4f262f0a880734623f79d4dc2f1aa8a0a2 (patch) | |
tree | 1772ef47d98a68245c3d04d95637e5b9c1040904 /src | |
parent | 69aba72f0883c5effb5c3c2991d0c5257deb7409 (diff) | |
download | dabmod-a5c50a4f262f0a880734623f79d4dc2f1aa8a0a2.tar.gz dabmod-a5c50a4f262f0a880734623f79d4dc2f1aa8a0a2.tar.bz2 dabmod-a5c50a4f262f0a880734623f79d4dc2f1aa8a0a2.zip |
Pull in files from odr-mmbtools-common
Replace ASIO by simpler implementation, meaning that the telnet
RC now only supports a single connection.
Move Log, RC to lib/
Diffstat (limited to 'src')
-rw-r--r-- | src/Log.cpp | 191 | ||||
-rw-r--r-- | src/Log.h | 200 | ||||
-rw-r--r-- | src/RemoteControl.cpp | 588 | ||||
-rw-r--r-- | src/RemoteControl.h | 259 |
4 files changed, 0 insertions, 1238 deletions
diff --git a/src/Log.cpp b/src/Log.cpp deleted file mode 100644 index 4fc7ae3..0000000 --- a/src/Log.cpp +++ /dev/null @@ -1,191 +0,0 @@ -/* - Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012 - Her Majesty the Queen in Right of Canada (Communications Research - Center Canada) - - Copyright (C) 2018 - Matthias P. Braendli, matthias.braendli@mpb.li - - http://www.opendigitalradio.org - */ -/* - This file is part of ODR-DabMod. - - ODR-DabMod 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. - - ODR-DabMod 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 ODR-DabMod. If not, see <http://www.gnu.org/licenses/>. - */ - -#include <list> -#include <cstdarg> -#include <cinttypes> -#include <chrono> - -#include "Log.h" -#include "Utils.h" - -using namespace std; - -/* This is called etiLog because it was copy-pasted from ODR-DabMux, even - * though it doesn't make any more sense there than here. - * - * It is a singleton used in all parts of ODR-DabMod to output log messages. - */ -Logger etiLog; - -void Logger::register_backend(std::shared_ptr<LogBackend> backend) -{ - backends.push_back(backend); -} - - -void Logger::log(log_level_t lvl, const char* fmt, ...) -{ - int size = 100; - std::string str; - va_list ap; - while (1) { - str.resize(size); - va_start(ap, fmt); - int n = vsnprintf((char *)str.c_str(), size, fmt, ap); - va_end(ap); - if (n > -1 && n < size) { - str.resize(n); - break; - } - if (n > -1) - size = n + 1; - else - size *= 2; - } - - logstr(lvl, move(str)); -} - -void Logger::logstr(log_level_t lvl, std::string&& message) -{ - log_message_t m(lvl, move(message)); - m_message_queue.push(move(m)); -} - -void Logger::io_process() -{ - set_thread_name("logger"); - while (1) { - log_message_t m; - try { - m_message_queue.wait_and_pop(m); - } - catch (const ThreadsafeQueueWakeup&) { - break; - } - - auto message = m.message; - - /* Remove a potential trailing newline. - * It doesn't look good in syslog - */ - if (message[message.length()-1] == '\n') { - message.resize(message.length()-1); - } - - for (auto &backend : backends) { - backend->log(m.level, message); - } - - if (m.level != log_level_t::trace) { - std::lock_guard<std::mutex> guard(m_cerr_mutex); - std::cerr << levels_as_str[m.level] << " " << message << std::endl; - } - } -} - -LogLine Logger::level(log_level_t lvl) -{ - return LogLine(this, lvl); -} - -LogToFile::LogToFile(const std::string& filename) : name("FILE") -{ - FILE* fd = fopen(filename.c_str(), "a"); - if (fd == nullptr) { - fprintf(stderr, "Cannot open log file !"); - throw std::runtime_error("Cannot open log file !"); - } - - log_file.reset(fd); -} - -void LogToFile::log(log_level_t level, const std::string& message) -{ - if (level != log_level_t::trace) { - const char* log_level_text[] = { - "DEBUG", "INFO", "WARN", "ERROR", "ALERT", "EMERG"}; - - // fprintf is thread-safe - fprintf(log_file.get(), SYSLOG_IDENT ": %s: %s\n", - log_level_text[(size_t)level], message.c_str()); - fflush(log_file.get()); - } -} - -void LogToSyslog::log(log_level_t level, const std::string& message) -{ - if (level != log_level_t::trace) { - int syslog_level = LOG_EMERG; - switch (level) { - case trace: break; // Do not handle TRACE in syslog - case debug: syslog_level = LOG_DEBUG; break; - case info: syslog_level = LOG_INFO; break; - /* we don't have the notice level */ - case warn: syslog_level = LOG_WARNING; break; - case error: syslog_level = LOG_ERR; break; - default: syslog_level = LOG_CRIT; break; - case alert: syslog_level = LOG_ALERT; break; - case emerg: syslog_level = LOG_EMERG; break; - } - - syslog(syslog_level, SYSLOG_IDENT " %s", message.c_str()); - } -} - -LogTracer::LogTracer(const string& trace_filename) : name("TRACE") -{ - etiLog.level(info) << "Setting up TRACE to " << trace_filename; - - FILE* fd = fopen(trace_filename.c_str(), "a"); - if (fd == nullptr) { - fprintf(stderr, "Cannot open trace file !"); - throw std::runtime_error("Cannot open trace file !"); - } - m_trace_file.reset(fd); - - using namespace std::chrono; - auto now = steady_clock::now().time_since_epoch(); - m_trace_micros_startup = duration_cast<microseconds>(now).count(); - - fprintf(m_trace_file.get(), - "0,TRACER,startup at %" PRIu64 "\n", m_trace_micros_startup); -} - -void LogTracer::log(log_level_t level, const std::string& message) -{ - if (level == log_level_t::trace) { - using namespace std::chrono; - const auto now = steady_clock::now().time_since_epoch(); - const auto micros = duration_cast<microseconds>(now).count(); - - fprintf(m_trace_file.get(), "%" PRIu64 ",%s\n", - micros - m_trace_micros_startup, - message.c_str()); - } -} diff --git a/src/Log.h b/src/Log.h deleted file mode 100644 index 1253635..0000000 --- a/src/Log.h +++ /dev/null @@ -1,200 +0,0 @@ -/* - Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012 - Her Majesty the Queen in Right of Canada (Communications Research - Center Canada) - - Copyright (C) 2018 - Matthias P. Braendli, matthias.braendli@mpb.li - - http://www.opendigitalradio.org - */ -/* - This file is part of ODR-DabMod. - - ODR-DabMod 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. - - ODR-DabMod 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 ODR-DabMod. If not, see <http://www.gnu.org/licenses/>. - */ - -#pragma once - -#ifdef HAVE_CONFIG_H -# include "config.h" -#endif - -#include <stdarg.h> -#include <stdio.h> -#include <syslog.h> -#include <fstream> -#include <sstream> -#include <iostream> -#include <list> -#include <stdexcept> -#include <string> -#include <map> -#include <mutex> -#include <memory> -#include <thread> -#include "ThreadsafeQueue.h" - -#define SYSLOG_IDENT "ODR-DabMod" -#define SYSLOG_FACILITY LOG_LOCAL0 - -enum log_level_t {debug = 0, info, warn, error, alert, emerg, trace}; - -static const std::string levels_as_str[] = - { " ", " ", "WARN ", "ERROR", "ALERT", "EMERG", "TRACE"} ; - -/** Abstract class all backends must inherit from */ -class LogBackend { - public: - virtual ~LogBackend() {}; - virtual void log(log_level_t level, const std::string& message) = 0; - virtual std::string get_name() const = 0; -}; - -/** A Logging backend for Syslog */ -class LogToSyslog : public LogBackend { - public: - LogToSyslog() : name("SYSLOG") { - openlog(SYSLOG_IDENT, LOG_PID, SYSLOG_FACILITY); - } - - virtual ~LogToSyslog() { - closelog(); - } - - void log(log_level_t level, const std::string& message); - - std::string get_name() const { return name; } - - private: - const std::string name; - - LogToSyslog(const LogToSyslog& other) = delete; - const LogToSyslog& operator=(const LogToSyslog& other) = delete; -}; - -class LogToFile : public LogBackend { - public: - LogToFile(const std::string& filename); - void log(log_level_t level, const std::string& message); - std::string get_name() const { return name; } - - private: - const std::string name; - - struct FILEDeleter{ void operator()(FILE* fd){ if(fd) fclose(fd);}}; - std::unique_ptr<FILE, FILEDeleter> log_file; - - LogToFile(const LogToFile& other) = delete; - const LogToFile& operator=(const LogToFile& other) = delete; -}; - -class LogTracer : public LogBackend { - public: - LogTracer(const std::string& filename); - void log(log_level_t level, const std::string& message); - std::string get_name() const { return name; } - private: - std::string name; - uint64_t m_trace_micros_startup = 0; - - struct FILEDeleter{ void operator()(FILE* fd){ if(fd) fclose(fd);}}; - std::unique_ptr<FILE, FILEDeleter> m_trace_file; - - LogTracer(const LogTracer& other) = delete; - const LogTracer& operator=(const LogTracer& other) = delete; -}; - -class LogLine; - -struct log_message_t { - log_message_t(log_level_t _level, std::string&& _message) : - level(_level), - message(move(_message)) {} - - log_message_t() : - level(debug), - message("") {} - - log_level_t level; - std::string message; -}; - -class Logger { - public: - Logger() { - m_io_thread = std::thread(&Logger::io_process, this); - } - - Logger(const Logger& other) = delete; - const Logger& operator=(const Logger& other) = delete; - ~Logger() { - m_message_queue.trigger_wakeup(); - m_io_thread.join(); - } - - void register_backend(std::shared_ptr<LogBackend> backend); - - /* Log the message to all backends */ - void log(log_level_t level, const char* fmt, ...); - - void logstr(log_level_t level, std::string&& message); - - /* All logging IO is done in another thread */ - void io_process(void); - - /* Return a LogLine for the given level - * so that you can write etiLog.level(info) << "stuff = " << 21 */ - LogLine level(log_level_t level); - - private: - std::list<std::shared_ptr<LogBackend> > backends; - - ThreadsafeQueue<log_message_t> m_message_queue; - std::thread m_io_thread; - std::mutex m_cerr_mutex; -}; - -extern Logger etiLog; - -// Accumulate a line of logs, using same syntax as stringstream -// The line is logged when the LogLine gets destroyed -class LogLine { - public: - LogLine(const LogLine& logline); - const LogLine& operator=(const LogLine& other) = delete; - LogLine(Logger* logger, log_level_t level) : - logger_(logger) - { - level_ = level; - } - - // Push the new element into the stringstream - template <typename T> - LogLine& operator<<(T s) { - os << s; - return *this; - } - - ~LogLine() - { - logger_->logstr(level_, os.str()); - } - - private: - std::ostringstream os; - log_level_t level_; - Logger* logger_; -}; - diff --git a/src/RemoteControl.cpp b/src/RemoteControl.cpp deleted file mode 100644 index 1065456..0000000 --- a/src/RemoteControl.cpp +++ /dev/null @@ -1,588 +0,0 @@ -/* - Copyright (C) 2007, 2008, 2009, 2010, 2011, 2012 - Her Majesty the Queen in Right of Canada (Communications Research - Center Canada) - - Copyright (C) 2019 - Matthias P. Braendli, matthias.braendli@mpb.li - - http://www.opendigitalradio.org - */ -/* - This file is part of ODR-DabMod. - - ODR-DabMod 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. - - ODR-DabMod 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 ODR-DabMod. If not, see <http://www.gnu.org/licenses/>. - */ -#include <list> -#include <string> -#include <iostream> -#include <string> -#include <thread> -#include <functional> -#include <asio.hpp> -#include "RemoteControl.h" - -using asio::ip::tcp; - -using namespace std; - -RemoteControllers rcs; - -RemoteControllerTelnet::~RemoteControllerTelnet() -{ - m_active = false; - m_io_service.stop(); - - if (m_restarter_thread.joinable()) { - m_restarter_thread.join(); - } - - if (m_child_thread.joinable()) { - m_child_thread.join(); - } -} - -void RemoteControllerTelnet::restart() -{ - if (m_restarter_thread.joinable()) { - m_restarter_thread.join(); - } - - m_restarter_thread = std::thread( - &RemoteControllerTelnet::restart_thread, - this, 0); -} - -RemoteControllable::~RemoteControllable() { - rcs.remove_controllable(this); -} - -std::list<std::string> RemoteControllable::get_supported_parameters() const { - std::list<std::string> parameterlist; - for (const auto& param : m_parameters) { - parameterlist.push_back(param[0]); - } - return parameterlist; -} - -void RemoteControllers::add_controller(std::shared_ptr<BaseRemoteController> rc) { - m_controllers.push_back(rc); -} - -void RemoteControllers::enrol(RemoteControllable *rc) { - controllables.push_back(rc); -} - -void RemoteControllers::remove_controllable(RemoteControllable *rc) { - controllables.remove(rc); -} - -std::list< std::vector<std::string> > RemoteControllers::get_param_list_values(const std::string& name) { - RemoteControllable* controllable = get_controllable_(name); - - std::list< std::vector<std::string> > allparams; - for (auto ¶m : controllable->get_supported_parameters()) { - std::vector<std::string> item; - item.push_back(param); - try { - item.push_back(controllable->get_parameter(param)); - } - catch (const ParameterError &e) { - item.push_back(std::string("error: ") + e.what()); - } - - allparams.push_back(item); - } - return allparams; -} - -std::string RemoteControllers::get_param(const std::string& name, const std::string& param) { - RemoteControllable* controllable = get_controllable_(name); - return controllable->get_parameter(param); -} - -void RemoteControllers::check_faults() { - for (auto &controller : m_controllers) { - if (controller->fault_detected()) { - etiLog.level(warn) << - "Detected Remote Control fault, restarting it"; - controller->restart(); - } - } -} - -RemoteControllable* RemoteControllers::get_controllable_(const std::string& name) -{ - auto rc = std::find_if(controllables.begin(), controllables.end(), - [&](RemoteControllable* r) { return r->get_rc_name() == name; }); - - if (rc == controllables.end()) { - throw ParameterError("Module name unknown"); - } - else { - return *rc; - } -} - -void RemoteControllers::set_param( - const std::string& name, - const std::string& param, - const std::string& value) -{ - RemoteControllable* controllable = get_controllable_(name); - try { - return controllable->set_parameter(param, value); - } - catch (const ios_base::failure& e) { - etiLog.level(info) << "RC: Failed to set " << name << " " << param - << " to " << value << ": " << e.what(); - throw ParameterError("Cannot understand value"); - } -} - -// This runs in a separate thread, because -// it would take too long to be done in the main loop -// thread. -void RemoteControllerTelnet::restart_thread(long) -{ - m_active = false; - m_io_service.stop(); - - if (m_child_thread.joinable()) { - m_child_thread.join(); - } - - m_child_thread = std::thread(&RemoteControllerTelnet::process, this, 0); -} - -void RemoteControllerTelnet::handle_accept( - std::shared_ptr<asio::ip::tcp::socket> socket, - const asio::error_code& asio_error) -{ - - const std::string welcome = "ODR-DabMod Remote Control CLI\n" - "Write 'help' for help.\n" - "**********\n"; - const std::string prompt = "> "; - - std::string in_message; - size_t length; - - if (asio_error) { - etiLog.level(error) << "RC: Error accepting connection"; - return; - } - - try { - etiLog.level(info) << "RC: Accepted"; - - asio::error_code ignored_error; - - asio::write(*socket, asio::buffer(welcome), - asio::transfer_all(), - ignored_error); - - while (m_active && in_message != "quit") { - asio::write(*socket, asio::buffer(prompt), - asio::transfer_all(), - ignored_error); - - in_message = ""; - - asio::streambuf buffer; - length = asio::read_until(*socket, buffer, "\n", ignored_error); - - std::istream str(&buffer); - std::getline(str, in_message); - - if (length == 0) { - etiLog.level(info) << "RC: Connection terminated"; - break; - } - - while (in_message.length() > 0 && - (in_message[in_message.length()-1] == '\r' || - in_message[in_message.length()-1] == '\n')) { - in_message.erase(in_message.length()-1, 1); - } - - if (in_message.length() == 0) { - continue; - } - - etiLog.level(info) << "RC: Got message '" << in_message << "'"; - - dispatch_command(*socket, in_message); - } - etiLog.level(info) << "RC: Closing socket"; - socket->close(); - } - catch (const std::exception& e) - { - etiLog.level(error) << "Remote control caught exception: " << e.what(); - } -} - -void RemoteControllerTelnet::process(long) -{ - m_active = true; - - while (m_active) { - m_io_service.reset(); - - tcp::acceptor acceptor(m_io_service, tcp::endpoint( - asio::ip::address::from_string("127.0.0.1"), m_port) ); - - // Add a job to start accepting connections. - auto socket = make_shared<tcp::socket>(acceptor.get_io_service()); - - // Add an accept call to the service. This will prevent io_service::run() - // from returning. - etiLog.level(info) << "RC: Waiting for connection on port " << m_port; - acceptor.async_accept(*socket, - bind(&RemoteControllerTelnet::handle_accept, this, - socket, - std::placeholders::_1)); - - // Process event loop. - m_io_service.run(); - } - - etiLog.level(info) << "RC: Leaving"; - m_fault = true; -} - -static std::vector<std::string> tokenise(const std::string& message) { - stringstream ss(message); - std::vector<std::string> all_tokens; - std::string item; - - while (std::getline(ss, item, ' ')) { - all_tokens.push_back(move(item)); - } - return all_tokens; -} - - -void RemoteControllerTelnet::dispatch_command(tcp::socket& socket, string command) -{ - vector<string> cmd = tokenise(command); - - if (cmd[0] == "help") { - reply(socket, - "The following commands are supported:\n" - " list\n" - " * Lists the modules that are loaded and their parameters\n" - " show MODULE\n" - " * Lists all parameters and their values from module MODULE\n" - " get MODULE PARAMETER\n" - " * Gets the value for the specified PARAMETER from module MODULE\n" - " set MODULE PARAMETER VALUE\n" - " * Sets the value for the PARAMETER ofr module MODULE\n" - " quit\n" - " * Terminate this session\n" - "\n"); - } - else if (cmd[0] == "list") { - stringstream ss; - - if (cmd.size() == 1) { - for (auto &controllable : rcs.controllables) { - ss << controllable->get_rc_name() << endl; - - list< vector<string> > params = controllable->get_parameter_descriptions(); - for (auto ¶m : params) { - ss << "\t" << param[0] << " : " << param[1] << endl; - } - } - } - else { - reply(socket, "Too many arguments for command 'list'"); - } - - reply(socket, ss.str()); - } - else if (cmd[0] == "show") { - if (cmd.size() == 2) { - try { - stringstream ss; - list< vector<string> > r = rcs.get_param_list_values(cmd[1]); - for (auto ¶m_val : r) { - ss << param_val[0] << ": " << param_val[1] << endl; - } - reply(socket, ss.str()); - - } - catch (const ParameterError &e) { - reply(socket, e.what()); - } - } - else { - reply(socket, "Incorrect parameters for command 'show'"); - } - } - else if (cmd[0] == "get") { - if (cmd.size() == 3) { - try { - string r = rcs.get_param(cmd[1], cmd[2]); - reply(socket, r); - } - catch (const ParameterError &e) { - reply(socket, e.what()); - } - } - else { - reply(socket, "Incorrect parameters for command 'get'"); - } - } - else if (cmd[0] == "set") { - if (cmd.size() >= 4) { - try { - stringstream new_param_value; - for (size_t i = 3; i < cmd.size(); i++) { - new_param_value << cmd[i]; - - if (i+1 < cmd.size()) { - new_param_value << " "; - } - } - - rcs.set_param(cmd[1], cmd[2], new_param_value.str()); - reply(socket, "ok"); - } - catch (const ParameterError &e) { - reply(socket, e.what()); - } - catch (const exception &e) { - reply(socket, "Error: Invalid parameter value. "); - } - } - else { - reply(socket, "Incorrect parameters for command 'set'"); - } - } - else if (cmd[0] == "quit") { - reply(socket, "Goodbye"); - } - else { - reply(socket, "Message not understood"); - } -} - -void RemoteControllerTelnet::reply(tcp::socket& socket, string message) -{ - asio::error_code ignored_error; - stringstream ss; - ss << message << "\r\n"; - asio::write(socket, asio::buffer(ss.str()), - asio::transfer_all(), - ignored_error); -} - -#if defined(HAVE_ZEROMQ) - -RemoteControllerZmq::~RemoteControllerZmq() { - m_active = false; - m_fault = false; - - if (m_restarter_thread.joinable()) { - m_restarter_thread.join(); - } - - if (m_child_thread.joinable()) { - m_child_thread.join(); - } -} - -void RemoteControllerZmq::restart() -{ - if (m_restarter_thread.joinable()) { - m_restarter_thread.join(); - } - - m_restarter_thread = std::thread(&RemoteControllerZmq::restart_thread, this); -} - -// This runs in a separate thread, because -// it would take too long to be done in the main loop -// thread. -void RemoteControllerZmq::restart_thread() -{ - m_active = false; - - if (m_child_thread.joinable()) { - m_child_thread.join(); - } - - m_child_thread = std::thread(&RemoteControllerZmq::process, this); -} - -void RemoteControllerZmq::recv_all(zmq::socket_t& pSocket, std::vector<std::string> &message) -{ - bool more = true; - do { - zmq::message_t msg; - pSocket.recv(&msg); - std::string incoming((char*)msg.data(), msg.size()); - message.push_back(incoming); - more = msg.more(); - } while (more); -} - -void RemoteControllerZmq::send_ok_reply(zmq::socket_t &pSocket) -{ - zmq::message_t msg(2); - char repCode[2] = {'o', 'k'}; - memcpy ((void*) msg.data(), repCode, 2); - pSocket.send(msg, 0); -} - -void RemoteControllerZmq::send_fail_reply(zmq::socket_t &pSocket, const std::string &error) -{ - zmq::message_t msg1(4); - char repCode[4] = {'f', 'a', 'i', 'l'}; - memcpy ((void*) msg1.data(), repCode, 4); - pSocket.send(msg1, ZMQ_SNDMORE); - - zmq::message_t msg2(error.length()); - memcpy ((void*) msg2.data(), error.c_str(), error.length()); - pSocket.send(msg2, 0); -} - -void RemoteControllerZmq::process() -{ - m_fault = false; - - // create zmq reply socket for receiving ctrl parameters - try { - zmq::socket_t repSocket(m_zmqContext, ZMQ_REP); - - // connect the socket - int hwm = 100; - int linger = 0; - repSocket.setsockopt(ZMQ_RCVHWM, &hwm, sizeof(hwm)); - repSocket.setsockopt(ZMQ_SNDHWM, &hwm, sizeof(hwm)); - repSocket.setsockopt(ZMQ_LINGER, &linger, sizeof(linger)); - repSocket.bind(m_endpoint.c_str()); - - // create pollitem that polls the ZMQ sockets - zmq::pollitem_t pollItems[] = { {repSocket, 0, ZMQ_POLLIN, 0} }; - while (m_active) { - zmq::poll(pollItems, 1, 100); - std::vector<std::string> msg; - - if (pollItems[0].revents & ZMQ_POLLIN) { - recv_all(repSocket, msg); - - std::string command((char*)msg[0].data(), msg[0].size()); - - if (msg.size() == 1 && command == "ping") { - send_ok_reply(repSocket); - } - else if (msg.size() == 1 && command == "list") { - size_t cohort_size = rcs.controllables.size(); - for (auto &controllable : rcs.controllables) { - std::stringstream ss; - ss << "{ \"name\": \"" << controllable->get_rc_name() << "\"," << - " \"params\": { "; - - list< vector<string> > params = controllable->get_parameter_descriptions(); - size_t i = 0; - for (auto ¶m : params) { - if (i > 0) { - ss << ", "; - } - - ss << "\"" << param[0] << "\": " << - "\"" << param[1] << "\""; - - i++; - } - - ss << " } }"; - - std::string msg_s = ss.str(); - - zmq::message_t zmsg(ss.str().size()); - memcpy ((void*) zmsg.data(), msg_s.data(), msg_s.size()); - - int flag = (--cohort_size > 0) ? ZMQ_SNDMORE : 0; - repSocket.send(zmsg, flag); - } - } - else if (msg.size() == 2 && command == "show") { - std::string module((char*) msg[1].data(), msg[1].size()); - try { - list< vector<string> > r = rcs.get_param_list_values(module); - size_t r_size = r.size(); - for (auto ¶m_val : r) { - std::stringstream ss; - ss << param_val[0] << ": " << param_val[1] << endl; - zmq::message_t zmsg(ss.str().size()); - memcpy(zmsg.data(), ss.str().data(), ss.str().size()); - - int flag = (--r_size > 0) ? ZMQ_SNDMORE : 0; - repSocket.send(zmsg, flag); - } - } - catch (const ParameterError &err) { - send_fail_reply(repSocket, err.what()); - } - } - else if (msg.size() == 3 && command == "get") { - std::string module((char*) msg[1].data(), msg[1].size()); - std::string parameter((char*) msg[2].data(), msg[2].size()); - - try { - std::string value = rcs.get_param(module, parameter); - zmq::message_t zmsg(value.size()); - memcpy ((void*) zmsg.data(), value.data(), value.size()); - repSocket.send(zmsg, 0); - } - catch (const ParameterError &err) { - send_fail_reply(repSocket, err.what()); - } - } - else if (msg.size() == 4 && command == "set") { - std::string module((char*) msg[1].data(), msg[1].size()); - std::string parameter((char*) msg[2].data(), msg[2].size()); - std::string value((char*) msg[3].data(), msg[3].size()); - - try { - rcs.set_param(module, parameter, value); - send_ok_reply(repSocket); - } - catch (const ParameterError &err) { - send_fail_reply(repSocket, err.what()); - } - } - else { - send_fail_reply(repSocket, - "Unsupported command. commands: list, show, get, set"); - } - } - } - repSocket.close(); - } - catch (const zmq::error_t &e) { - etiLog.level(error) << "ZMQ RC error: " << std::string(e.what()); - } - catch (const std::exception& e) { - etiLog.level(error) << "ZMQ RC caught exception: " << e.what(); - m_fault = true; - } -} - -#endif - diff --git a/src/RemoteControl.h b/src/RemoteControl.h deleted file mode 100644 index 087b94a..0000000 --- a/src/RemoteControl.h +++ /dev/null @@ -1,259 +0,0 @@ -/* - Copyright (C) 2007, 2008, 2009, 2010, 2011, 2012 - Her Majesty the Queen in Right of Canada (Communications Research - Center Canada) - - Copyright (C) 2019 - Matthias P. Braendli, matthias.braendli@mpb.li - - http://www.opendigitalradio.org - - This module adds remote-control capability to some of the dabmod modules. - */ -/* - This file is part of ODR-DabMod. - - ODR-DabMod 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. - - ODR-DabMod 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 ODR-DabMod. If not, see <http://www.gnu.org/licenses/>. - */ - -#pragma once - -#ifdef HAVE_CONFIG_H -# include "config.h" -#endif - -#if defined(HAVE_ZEROMQ) -# include "zmq.hpp" -#endif - -#include <list> -#include <map> -#include <memory> -#include <string> -#include <atomic> -#include <iostream> -#include <thread> -#include <asio.hpp> -#include <stdexcept> - -#include "Log.h" - -#define RC_ADD_PARAMETER(p, desc) { \ - std::vector<std::string> p; \ - p.push_back(#p); \ - p.push_back(desc); \ - m_parameters.push_back(p); \ -} - -class ParameterError : public std::exception -{ - public: - ParameterError(std::string message) : m_message(message) {} - ~ParameterError() throw() {} - const char* what() const throw() { return m_message.c_str(); } - - private: - std::string m_message; -}; - -class RemoteControllable; - -/* Remote controllers (that recieve orders from the user) - * must implement BaseRemoteController - */ -class BaseRemoteController { - public: - /* When this returns one, the remote controller cannot be - * used anymore, and must be restarted by DabMod - */ - virtual bool fault_detected() = 0; - - /* In case of a fault, the remote controller can be - * restarted. - */ - virtual void restart() = 0; - - virtual ~BaseRemoteController() {} -}; - -/* Objects that support remote control must implement the following class */ -class RemoteControllable { - public: - RemoteControllable(const std::string& name) : - m_rc_name(name) {} - - RemoteControllable(const RemoteControllable& other) = delete; - RemoteControllable& operator=(const RemoteControllable& other) = delete; - - virtual ~RemoteControllable(); - - /* return a short name used to identify the controllable. - * It might be used in the commands the user has to type, so keep - * it short - */ - virtual std::string get_rc_name() const { return m_rc_name; } - - /* Return a list of possible parameters that can be set */ - virtual std::list<std::string> get_supported_parameters() const; - - /* Return a mapping of the descriptions of all parameters */ - virtual std::list< std::vector<std::string> > - get_parameter_descriptions() const - { - return m_parameters; - } - - /* Base function to set parameters. */ - virtual void set_parameter( - const std::string& parameter, - const std::string& value) = 0; - - /* Getting a parameter always returns a string. */ - virtual const std::string get_parameter(const std::string& parameter) const = 0; - - protected: - std::string m_rc_name; - std::list< std::vector<std::string> > m_parameters; -}; - -/* Holds all our remote controllers and controlled object. - */ -class RemoteControllers { - public: - void add_controller(std::shared_ptr<BaseRemoteController> rc); - void enrol(RemoteControllable *rc); - void remove_controllable(RemoteControllable *rc); - void check_faults(); - std::list< std::vector<std::string> > get_param_list_values(const std::string& name); - std::string get_param(const std::string& name, const std::string& param); - - void set_param( - const std::string& name, - const std::string& param, - const std::string& value); - - std::list<RemoteControllable*> controllables; - - private: - RemoteControllable* get_controllable_(const std::string& name); - - std::list<std::shared_ptr<BaseRemoteController> > m_controllers; -}; - -extern RemoteControllers rcs; - -/* Implements a Remote controller based on a simple telnet CLI - * that listens on localhost - */ -class RemoteControllerTelnet : public BaseRemoteController { - public: - RemoteControllerTelnet() - : m_active(false), - m_io_service(), - m_fault(false), - m_port(0) { } - - RemoteControllerTelnet(int port) - : m_active(port > 0), - m_io_service(), - m_fault(false), - m_port(port) - { - restart(); - } - - - RemoteControllerTelnet& operator=(const RemoteControllerTelnet& other) = delete; - RemoteControllerTelnet(const RemoteControllerTelnet& other) = delete; - - ~RemoteControllerTelnet(); - - virtual bool fault_detected() { return m_fault; } - - virtual void restart(); - - private: - void restart_thread(long); - - void process(long); - - void dispatch_command(asio::ip::tcp::socket& socket, - std::string command); - - void reply(asio::ip::tcp::socket& socket, std::string message); - - void handle_accept( - std::shared_ptr<asio::ip::tcp::socket> socket, - const asio::error_code& asio_error); - - std::atomic<bool> m_active; - - asio::io_service m_io_service; - - /* This is set to true if a fault occurred */ - std::atomic<bool> m_fault; - std::thread m_restarter_thread; - - std::thread m_child_thread; - - int m_port; -}; - -#if defined(HAVE_ZEROMQ) -/* Implements a Remote controller using zmq transportlayer - * that listens on localhost - */ -class RemoteControllerZmq : public BaseRemoteController { - public: - RemoteControllerZmq() - : m_active(false), m_fault(false), - m_zmqContext(1), - m_endpoint("") { } - - RemoteControllerZmq(const std::string& endpoint) - : m_active(not endpoint.empty()), m_fault(false), - m_zmqContext(1), - m_endpoint(endpoint), - m_child_thread(&RemoteControllerZmq::process, this) { } - - RemoteControllerZmq& operator=(const RemoteControllerZmq& other) = delete; - RemoteControllerZmq(const RemoteControllerZmq& other) = delete; - - ~RemoteControllerZmq(); - - virtual bool fault_detected() { return m_fault; } - - virtual void restart(); - - private: - void restart_thread(); - - void recv_all(zmq::socket_t &pSocket, std::vector<std::string> &message); - void send_ok_reply(zmq::socket_t &pSocket); - void send_fail_reply(zmq::socket_t &pSocket, const std::string &error); - void process(); - - std::atomic<bool> m_active; - - /* This is set to true if a fault occurred */ - std::atomic<bool> m_fault; - std::thread m_restarter_thread; - - zmq::context_t m_zmqContext; - - std::string m_endpoint; - std::thread m_child_thread; -}; -#endif - |