diff options
Diffstat (limited to 'lib')
-rw-r--r-- | lib/Json.cpp | 122 | ||||
-rw-r--r-- | lib/Json.h | 63 | ||||
-rw-r--r-- | lib/RemoteControl.cpp | 57 | ||||
-rw-r--r-- | lib/RemoteControl.h | 12 | ||||
-rw-r--r-- | lib/Socket.cpp | 2 | ||||
-rw-r--r-- | lib/ThreadsafeQueue.h | 54 |
6 files changed, 289 insertions, 21 deletions
diff --git a/lib/Json.cpp b/lib/Json.cpp new file mode 100644 index 0000000..4dc2f25 --- /dev/null +++ b/lib/Json.cpp @@ -0,0 +1,122 @@ +/* + Copyright (C) 2007, 2008, 2009, 2010, 2011, 2012 + Her Majesty the Queen in Right of Canada (Communications Research + Center Canada) + + Copyright (C) 2023 + Matthias P. Braendli, matthias.braendli@mpb.li + + http://www.opendigitalradio.org + */ +/* + 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, see <https://www.gnu.org/licenses/>. + */ +#include <string> +#include <iostream> +#include <sstream> +#include <iomanip> +#include <string> +#include <algorithm> + +#include "Json.h" + +namespace json { + static std::string escape_json(const std::string &s) { + std::ostringstream o; + for (auto c = s.cbegin(); c != s.cend(); c++) { + switch (*c) { + case '"': o << "\\\""; break; + case '\\': o << "\\\\"; break; + case '\b': o << "\\b"; break; + case '\f': o << "\\f"; break; + case '\n': o << "\\n"; break; + case '\r': o << "\\r"; break; + case '\t': o << "\\t"; break; + default: + if ('\x00' <= *c && *c <= '\x1f') { + o << "\\u" + << std::hex << std::setw(4) << std::setfill('0') << static_cast<int>(*c); + } else { + o << *c; + } + } + } + return o.str(); + } + + std::string map_to_json(const map_t& values) { + std::ostringstream ss; + ss << "{ "; + size_t ix = 0; + for (const auto& element : values) { + if (ix > 0) { + ss << ","; + } + + ss << "\"" << escape_json(element.first) << "\": "; + ss << value_to_json(element.second); + + ix++; + } + ss << " }"; + + return ss.str(); + } + + std::string value_to_json(const value_t& value) + { + std::ostringstream ss; + + if (std::holds_alternative<std::string>(value.v)) { + ss << "\"" << escape_json(std::get<std::string>(value.v)) << "\""; + } + else if (std::holds_alternative<double>(value.v)) { + ss << std::fixed << std::get<double>(value.v); + } + else if (std::holds_alternative<ssize_t>(value.v)) { + ss << std::get<ssize_t>(value.v); + } + else if (std::holds_alternative<size_t>(value.v)) { + ss << std::get<size_t>(value.v); + } + else if (std::holds_alternative<bool>(value.v)) { + ss << (std::get<bool>(value.v) ? "true" : "false"); + } + else if (std::holds_alternative<std::nullopt_t>(value.v)) { + ss << "null"; + } + else if (std::holds_alternative<std::vector<json::value_t> >(value.v)) { + const auto& vec = std::get<std::vector<json::value_t> >(value.v); + ss << "[ "; + size_t list_ix = 0; + for (const auto& list_element : vec) { + if (list_ix > 0) { + ss << ","; + } + ss << value_to_json(list_element); + list_ix++; + } + ss << "]"; + } + else if (std::holds_alternative<std::shared_ptr<json::map_t> >(value.v)) { + const map_t& v = *std::get<std::shared_ptr<json::map_t> >(value.v); + ss << map_to_json(v); + } + else { + throw std::logic_error("variant alternative not handled"); + } + + return ss.str(); + } +} diff --git a/lib/Json.h b/lib/Json.h new file mode 100644 index 0000000..65aa668 --- /dev/null +++ b/lib/Json.h @@ -0,0 +1,63 @@ +/* + Copyright (C) 2007, 2008, 2009, 2010, 2011, 2012 + Her Majesty the Queen in Right of Canada (Communications Research + Center Canada) + + Copyright (C) 2023 + Matthias P. Braendli, matthias.braendli@mpb.li + + http://www.opendigitalradio.org + + This module adds remote-control capability to some of the dabmux/dabmod modules. + */ +/* + 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, see <https://www.gnu.org/licenses/>. + */ + +#pragma once + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include <vector> +#include <memory> +#include <optional> +#include <stdexcept> +#include <string> +#include <unordered_map> +#include <variant> + +namespace json { + + // STL containers are not required to support incomplete types, + // hence the shared_ptr + + struct value_t { + std::variant< + std::shared_ptr<std::unordered_map<std::string, value_t>>, + std::vector<value_t>, + std::string, + double, + size_t, + ssize_t, + bool, + std::nullopt_t> v; + }; + + using map_t = std::unordered_map<std::string, value_t>; + + std::string map_to_json(const map_t& values); + std::string value_to_json(const value_t& value); +} diff --git a/lib/RemoteControl.cpp b/lib/RemoteControl.cpp index 30dcb60..dca3373 100644 --- a/lib/RemoteControl.cpp +++ b/lib/RemoteControl.cpp @@ -25,6 +25,8 @@ #include <list> #include <string> #include <iostream> +#include <sstream> +#include <iomanip> #include <string> #include <algorithm> @@ -102,6 +104,18 @@ std::list< std::vector<std::string> > RemoteControllers::get_param_list_values(c return allparams; } + + +std::string RemoteControllers::get_showjson() { + json::map_t root; + for (auto &controllable : rcs.controllables) { + root[controllable->get_rc_name()].v = + std::make_shared<json::map_t>(controllable->get_all_values()); + } + + return json::map_to_json(root); +} + std::string RemoteControllers::get_param(const std::string& name, const std::string& param) { RemoteControllable* controllable = get_controllable_(name); return controllable->get_parameter(param); @@ -123,7 +137,7 @@ RemoteControllable* RemoteControllers::get_controllable_(const std::string& name [&](RemoteControllable* r) { return r->get_rc_name() == name; }); if (rc == controllables.end()) { - throw ParameterError("Module name unknown"); + throw ParameterError(string{"Module name '"} + name + "' unknown"); } else { return *rc; @@ -427,10 +441,15 @@ void RemoteControllerZmq::recv_all(zmq::socket_t& pSocket, std::vector<std::stri 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(); + const auto zresult = pSocket.recv(msg); + if (zresult) { + std::string incoming((char*)msg.data(), msg.size()); + message.push_back(incoming); + more = msg.more(); + } + else { + more = false; + } } while (more); } @@ -457,6 +476,7 @@ void RemoteControllerZmq::send_fail_reply(zmq::socket_t &pSocket, const std::str void RemoteControllerZmq::process() { m_fault = false; + m_active = true; // create zmq reply socket for receiving ctrl parameters try { @@ -514,8 +534,21 @@ void RemoteControllerZmq::process() repSocket.send(zmsg, (--cohort_size > 0) ? zmq::send_flags::sndmore : zmq::send_flags::none); } } + else if (msg.size() == 1 && command == "showjson") { + try { + std::string json = rcs.get_showjson(); + + zmq::message_t zmsg(json.size()); + memcpy(zmsg.data(), json.data(), json.size()); + + repSocket.send(zmsg, zmq::send_flags::none); + } + catch (const ParameterError &err) { + send_fail_reply(repSocket, err.what()); + } + } else if (msg.size() == 2 && command == "show") { - std::string module((char*) msg[1].data(), msg[1].size()); + const 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(); @@ -533,8 +566,8 @@ void RemoteControllerZmq::process() } } 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()); + const std::string module((char*) msg[1].data(), msg[1].size()); + const std::string parameter((char*) msg[2].data(), msg[2].size()); try { std::string value = rcs.get_param(module, parameter); @@ -547,9 +580,9 @@ void RemoteControllerZmq::process() } } 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()); + const std::string module((char*) msg[1].data(), msg[1].size()); + const std::string parameter((char*) msg[2].data(), msg[2].size()); + const std::string value((char*) msg[3].data(), msg[3].size()); try { rcs.set_param(module, parameter, value); @@ -561,7 +594,7 @@ void RemoteControllerZmq::process() } else { send_fail_reply(repSocket, - "Unsupported command. commands: list, show, get, set"); + "Unsupported command. commands: list, show, get, set, showjson"); } } } diff --git a/lib/RemoteControl.h b/lib/RemoteControl.h index 2358b3a..26f30d9 100644 --- a/lib/RemoteControl.h +++ b/lib/RemoteControl.h @@ -3,7 +3,7 @@ Her Majesty the Queen in Right of Canada (Communications Research Center Canada) - Copyright (C) 2019 + Copyright (C) 2023 Matthias P. Braendli, matthias.braendli@mpb.li http://www.opendigitalradio.org @@ -36,6 +36,8 @@ #endif #include <list> +#include <unordered_map> +#include <variant> #include <map> #include <memory> #include <string> @@ -46,6 +48,7 @@ #include "Log.h" #include "Socket.h" +#include "Json.h" #define RC_ADD_PARAMETER(p, desc) { \ std::vector<std::string> p; \ @@ -113,13 +116,13 @@ class RemoteControllable { } /* Base function to set parameters. */ - virtual void set_parameter( - const std::string& parameter, - const std::string& value) = 0; + 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; + virtual const json::map_t get_all_values() const = 0; + protected: std::string m_rc_name; std::list< std::vector<std::string> > m_parameters; @@ -135,6 +138,7 @@ class RemoteControllers { 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); + std::string get_showjson(); void set_param( const std::string& name, diff --git a/lib/Socket.cpp b/lib/Socket.cpp index 10ec1ca..b71c01e 100644 --- a/lib/Socket.cpp +++ b/lib/Socket.cpp @@ -893,7 +893,7 @@ ssize_t TCPClient::recv(void *buffer, size_t length, int flags, int timeout_ms) return 0; } - return 0; + throw std::logic_error("unreachable"); } void TCPClient::reconnect() diff --git a/lib/ThreadsafeQueue.h b/lib/ThreadsafeQueue.h index 815dfe0..8b385d6 100644 --- a/lib/ThreadsafeQueue.h +++ b/lib/ThreadsafeQueue.h @@ -2,7 +2,7 @@ Copyright (C) 2007, 2008, 2009, 2010, 2011 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) - Copyright (C) 2018 + Copyright (C) 2023 Matthias P. Braendli, matthias.braendli@mpb.li An implementation for a threadsafe queue, depends on C++11 @@ -32,6 +32,7 @@ #include <condition_variable> #include <queue> #include <utility> +#include <cassert> /* This queue is meant to be used by two threads. One producer * that pushes elements into the queue, and one consumer that @@ -69,7 +70,6 @@ public: } size_t queue_size = the_queue.size(); lock.unlock(); - the_rx_notification.notify_one(); return queue_size; @@ -93,11 +93,57 @@ public: return queue_size; } + struct push_overflow_result { bool overflowed; size_t new_size; }; + + /* Push one element into the queue, and if queue is + * full remove one element from the other end. + * + * max_size == 0 is not allowed. + * + * returns the new queue size and a flag if overflow occurred. + */ + push_overflow_result push_overflow(T const& val, size_t max_size) + { + assert(max_size > 0); + std::unique_lock<std::mutex> lock(the_mutex); + + bool overflow = false; + while (the_queue.size() >= max_size) { + overflow = true; + the_queue.pop(); + } + the_queue.push(val); + const size_t queue_size = the_queue.size(); + lock.unlock(); + + the_rx_notification.notify_one(); + + return {overflow, queue_size}; + } + + push_overflow_result push_overflow(T&& val, size_t max_size) + { + assert(max_size > 0); + std::unique_lock<std::mutex> lock(the_mutex); + + bool overflow = false; + while (the_queue.size() >= max_size) { + overflow = true; + the_queue.pop(); + } + the_queue.emplace(std::move(val)); + const size_t queue_size = the_queue.size(); + lock.unlock(); + + the_rx_notification.notify_one(); + + return {overflow, queue_size}; + } + + /* Push one element into the queue, but wait until the * queue size goes below the threshold. * - * Notify waiting thread. - * * returns the new queue size. */ size_t push_wait_if_full(T const& val, size_t threshold) |