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 - | 
