// // json.cpp // // Copyright (c) 2019 2020 Andrea Bondavalli. All rights reserved. // // 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 // 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 . // #include #include #include #include #include #include #include "log.hpp" #include "utils.hpp" #include "json.hpp" static inline std::string remove_undesired_chars(const std::string& s) { std::regex html_regex("[^ A-Za-z0-9:~.,_/=%()\\r\\n\\t\?#-]?"); return std::regex_replace(s, html_regex, ""); } static std::string escape_json(const std::string& js) { std::string s(remove_undesired_chars(js)); std::ostringstream ss; for (auto c = s.cbegin(); c != s.cend(); c++) { switch (*c) { case '"': ss << "\\\""; break; case '\\': ss << "\\\\"; break; case '\b': ss << "\\b"; break; case '\f': ss << "\\f"; break; case '\n': ss << "\\n"; break; case '\r': ss << "\\r"; break; case '\t': ss << "\\t"; break; default: if ('\x00' <= *c && *c <= '\x1f') { ss << "\\u" << std::hex << std::setw(4) << std::setfill('0') << (int)*c; } else { ss << *c; } } } return ss.str(); } std::string config_to_json(const Config& config) { std::stringstream ss; ss << "{" << "\n \"http_port\": " << config.get_http_port() << ",\n \"rtsp_port\": " << config.get_rtsp_port() << ",\n \"http_base_dir\": \"" << config.get_http_base_dir() << "\"" << ",\n \"log_severity\": " << config.get_log_severity() << ",\n \"playout_delay\": " << config.get_playout_delay() << ",\n \"tic_frame_size_at_1fs\": " << config.get_tic_frame_size_at_1fs() << ",\n \"max_tic_frame_size\": " << config.get_max_tic_frame_size() << ",\n \"sample_rate\": " << config.get_sample_rate() << ",\n \"rtp_mcast_base\": \"" << escape_json(config.get_rtp_mcast_base()) << "\"" << ",\n \"rtp_port\": " << config.get_rtp_port() << ",\n \"ptp_domain\": " << unsigned(config.get_ptp_domain()) << ",\n \"ptp_dscp\": " << unsigned(config.get_ptp_dscp()) << ",\n \"sap_mcast_addr\": \"" << escape_json(config.get_sap_mcast_addr()) << "\"" << ",\n \"sap_interval\": " << config.get_sap_interval() << ",\n \"syslog_proto\": \"" << escape_json(config.get_syslog_proto()) << "\"" << ",\n \"syslog_server\": \"" << escape_json(config.get_syslog_server()) << "\"" << ",\n \"status_file\": \"" << escape_json(config.get_status_file()) << "\"" << ",\n \"interface_name\": \"" << escape_json(config.get_interface_name()) << "\"" << ",\n \"mdns_enabled\": " << std::boolalpha << config.get_mdns_enabled() << ",\n \"custom_node_id\": \"" << escape_json(config.get_custom_node_id()) << "\"" << ",\n \"node_id\": \"" << escape_json(config.get_node_id()) << "\"" << ",\n \"ptp_status_script\": \"" << escape_json(config.get_ptp_status_script()) << "\"" << ",\n \"mac_addr\": \"" << escape_json(config.get_mac_addr_str()) << "\"" << ",\n \"ip_addr\": \"" << escape_json(config.get_ip_addr_str()) << "\"" << ",\n \"auto_sinks_update\": " << std::boolalpha << config.get_auto_sinks_update() << "\n}\n"; return ss.str(); } std::string source_to_json(const StreamSource& source) { std::stringstream ss; ss << "\n {" << "\n \"id\": " << unsigned(source.id) << ",\n \"enabled\": " << std::boolalpha << source.enabled << ",\n \"name\": \"" << escape_json(source.name) << "\"" << ",\n \"io\": \"" << escape_json(source.io) << "\"" << ",\n \"max_samples_per_packet\": " << source.max_samples_per_packet << ",\n \"codec\": \"" << escape_json(source.codec) << "\"" << ",\n \"address\": \"" << escape_json(source.address) << "\"" << ",\n \"ttl\": " << unsigned(source.ttl) << ",\n \"payload_type\": " << unsigned(source.payload_type) << ",\n \"dscp\": " << +unsigned(source.dscp) << ",\n \"refclk_ptp_traceable\": " << std::boolalpha << source.refclk_ptp_traceable << ",\n \"map\": [ "; int i = 0; for (int value : source.map) { if (i++ > 0) ss << ", "; ss << value; } ss << " ]\n }"; return ss.str(); } std::string sink_to_json(const StreamSink& sink) { std::stringstream ss; ss << "\n {" << "\n \"id\": " << unsigned(sink.id) << ",\n \"name\": \"" << escape_json(sink.name) << "\"" << ",\n \"io\": \"" << escape_json(sink.io) << "\"" << ",\n \"use_sdp\": " << std::boolalpha << sink.use_sdp << ",\n \"source\": \"" << escape_json(sink.source) << "\"" << ",\n \"sdp\": \"" << escape_json(sink.sdp) << "\"" << ",\n \"delay\": " << sink.delay << ",\n \"ignore_refclk_gmid\": " << std::boolalpha << sink.ignore_refclk_gmid << ",\n \"map\": [ "; int i = 0; for (int value : sink.map) { if (i++ > 0) ss << ", "; ss << value; } ss << " ]\n }"; return ss.str(); } std::string sink_status_to_json(const SinkStreamStatus& status) { std::stringstream ss; ss << "{"; ss << "\n \"sink_flags\":\n {" << std::boolalpha << " \n \"rtp_seq_id_error\": " << status.is_rtp_seq_id_error << ", \n \"rtp_ssrc_error\": " << status.is_rtp_ssrc_error << ", \n \"rtp_payload_type_error\": " << status.is_rtp_payload_type_error << ", \n \"rtp_sac_error\": " << status.is_rtp_sac_error << ", \n \"receiving_rtp_packet\": " << status.is_receiving_rtp_packet << ", \n \"some_muted\": " << status.is_some_muted << ", \n \"all_muted\": " << status.is_all_muted << ", \n \"muted\": " << status.is_muted << "\n }," << "\n \"sink_min_time\": " << status.min_time << "\n}\n"; return ss.str(); } std::string ptp_config_to_json(const PTPConfig& ptp_config) { std::stringstream ss; ss << "{" << " \"domain\": " << unsigned(ptp_config.domain) << ", \"dscp\": " << unsigned(ptp_config.dscp) << " }\n"; return ss.str(); } std::string ptp_status_to_json(const PTPStatus& status) { std::stringstream ss; ss << "{" << " \"status\": \"" << escape_json(status.status) << "\"" << ", \"gmid\": \"" << escape_json(status.gmid) << "\"" << ", \"jitter\": " << status.jitter << " }\n"; return ss.str(); } std::string sources_to_json(const std::list& sources) { int count = 0; std::stringstream ss; ss << "{\n \"sources\": ["; for (auto const& source : sources) { if (count++) { ss << ", "; } ss << source_to_json(source); } ss << " ]\n}\n"; return ss.str(); } std::string sinks_to_json(const std::list& sinks) { int count = 0; std::stringstream ss; ss << "{\n \"sinks\": ["; for (auto const& sink : sinks) { if (count++) { ss << ", "; } ss << sink_to_json(sink); } ss << " ]\n}\n"; return ss.str(); } std::string streams_to_json(const std::list& sources, const std::list& sinks) { int count = 0; std::stringstream ss; ss << "{\n \"sources\": ["; for (auto const& source : sources) { if (count++) { ss << ", "; } ss << source_to_json(source); } count = 0; ss << " ],\n \"sinks\": ["; for (auto const& sink : sinks) { if (count++) { ss << ", "; } ss << sink_to_json(sink); } ss << " ]\n}\n"; return ss.str(); } std::string remote_source_to_json(const RemoteSource& source) { std::stringstream ss; ss << "\n {" << "\n \"source\": \"" << escape_json(source.source) << "\"" << ",\n \"id\": \"" << escape_json(source.id) << "\"" << ",\n \"name\": \"" << escape_json(source.name) << "\"" << ",\n \"domain\": \"" << escape_json(source.domain) << "\"" << ",\n \"address\": \"" << escape_json(source.address) << "\"" << ",\n \"sdp\": \"" << escape_json(source.sdp) << "\"" << ",\n \"last_seen\": " << unsigned(source.last_seen) << ",\n \"announce_period\": " << unsigned(source.announce_period) << " \n }"; return ss.str(); } std::string remote_sources_to_json(const std::list& sources) { int count = 0; std::stringstream ss; ss << "{\n \"remote_sources\": ["; for (auto const& source : sources) { if (count++) { ss << ", "; } ss << remote_source_to_json(source); } ss << " ]\n}\n"; return ss.str(); } Config json_to_config_(std::istream& js, Config& config) { try { boost::property_tree::ptree pt; boost::property_tree::read_json(js, pt); for (auto const& [key, val] : pt) { if (key == "http_port") { config.set_http_port(val.get_value()); } else if (key == "rtsp_port") { config.set_rtsp_port(val.get_value()); } else if (key == "http_base_dir") { config.set_http_base_dir( remove_undesired_chars(val.get_value())); } else if (key == "log_severity") { config.set_log_severity(val.get_value()); } else if (key == "interface_name") { config.set_interface_name( remove_undesired_chars(val.get_value())); } else if (key == "playout_delay") { config.set_playout_delay(val.get_value()); } else if (key == "tic_frame_size_at_1fs") { config.set_tic_frame_size_at_1fs(val.get_value()); } else if (key == "max_tic_frame_size") { config.set_max_tic_frame_size(val.get_value()); } else if (key == "sample_rate") { config.set_sample_rate(val.get_value()); } else if (key == "rtp_mcast_base") { config.set_rtp_mcast_base( remove_undesired_chars(val.get_value())); } else if (key == "rtp_port") { config.set_rtp_port(val.get_value()); } else if (key == "ptp_domain") { config.set_ptp_domain(val.get_value()); } else if (key == "ptp_dscp") { config.set_ptp_dscp(val.get_value()); } else if (key == "sap_mcast_addr") { config.set_sap_mcast_addr( remove_undesired_chars(val.get_value())); } else if (key == "sap_interval") { config.set_sap_interval(val.get_value()); } else if (key == "mdns_enabled") { config.set_mdns_enabled(val.get_value()); } else if (key == "status_file") { config.set_status_file( remove_undesired_chars(val.get_value())); } else if (key == "syslog_proto") { config.set_syslog_proto( remove_undesired_chars(val.get_value())); } else if (key == "syslog_server") { config.set_syslog_server( remove_undesired_chars(val.get_value())); } else if (key == "ptp_status_script") { config.set_ptp_status_script( remove_undesired_chars(val.get_value())); } else if (key == "custom_node_id") { config.set_custom_node_id( remove_undesired_chars(val.get_value())); } else if (key == "auto_sinks_update") { config.set_auto_sinks_update(val.get_value()); } else if (key == "mac_addr" || key == "ip_addr" || key == "node_id") { /* ignored */ } else { std::cerr << "Warning: unkown configuration option " << key << std::endl; } } } catch (boost::property_tree::json_parser::json_parser_error& je) { throw std::runtime_error("error parsing JSON at line " + std::to_string(je.line()) + " :" + je.message()); } catch (...) { throw std::runtime_error("failed to convert a number"); } return config; } Config json_to_config(std::istream& js, const Config& curConfig) { Config config(curConfig); return json_to_config_(js, config); } Config json_to_config(std::istream& js) { Config config; return json_to_config_(js, config); } Config json_to_config(const std::string& json, const Config& curConfig) { std::stringstream ss(json); return json_to_config(ss, curConfig); } Config json_to_config(const std::string& json) { std::stringstream ss(json); return json_to_config(ss); } StreamSource json_to_source(const std::string& id, const std::string& json) { /* JSON request "enabled": true, "name": "ALSA (on ubuntu)_1", "io": "Audio Device", "map": [ 0, 1, 2, 3, 4, 5, 6, 7 ], "max_samples_per_packet": 48, "codec": "L24", "address": "", "ttl": 15, "payload_type": 98, "dscp": 34, "refclk_ptp_traceable": false */ StreamSource source; try { boost::property_tree::ptree pt; std::stringstream ss(json); boost::property_tree::read_json(ss, pt); source.id = std::stoi(id); source.enabled = pt.get("enabled"); source.name = remove_undesired_chars(pt.get("name")); source.io = remove_undesired_chars(pt.get("io")); /* source map determite the association with ALSA output channels used to playing */ BOOST_FOREACH (boost::property_tree::ptree::value_type& v, pt.get_child("map")) { source.map.emplace_back(std::stoi(v.second.data())); } source.max_samples_per_packet = pt.get("max_samples_per_packet"); source.codec = remove_undesired_chars(pt.get("codec")); source.address = remove_undesired_chars(pt.get("address")); source.ttl = pt.get("ttl"); source.payload_type = pt.get("payload_type"); source.dscp = pt.get("dscp"); source.refclk_ptp_traceable = pt.get("refclk_ptp_traceable"); } catch (boost::property_tree::json_parser::json_parser_error& je) { throw std::runtime_error("error parsing JSON at line " + std::to_string(je.line()) + " :" + je.message()); } catch (...) { throw std::runtime_error("failed to convert a number"); } return source; } StreamSink json_to_sink(const std::string& id, const std::string& json) { /* JSON request "name": "ALSA (on ubuntu)_1", "io": "Audio Device", "use_sdp": true, "source": "...", "sdp": "...", "delay": 384, "ignore_refclk_gmid": false, "map": [ 0, 1, 2, 3, 4, 5, 6, 7 ] */ StreamSink sink; try { boost::property_tree::ptree pt; std::stringstream ss(json); boost::property_tree::read_json(ss, pt); sink.id = std::stoi(id); sink.name = remove_undesired_chars(pt.get("name")); sink.io = remove_undesired_chars(pt.get("io")); sink.source = remove_undesired_chars(pt.get("source")); sink.use_sdp = pt.get("use_sdp"); sink.sdp = remove_undesired_chars(pt.get("sdp")); sink.delay = pt.get("delay"); sink.ignore_refclk_gmid = pt.get("ignore_refclk_gmid"); /* source map determite the association with ALSA input channels used to recording */ BOOST_FOREACH (boost::property_tree::ptree::value_type& v, pt.get_child("map")) { sink.map.emplace_back(std::stoi(v.second.data())); } } catch (boost::property_tree::json_parser::json_parser_error& je) { throw std::runtime_error("error parsing JSON at line " + std::to_string(je.line()) + " :" + je.message()); } catch (...) { throw std::runtime_error("failed to convert a number"); } return sink; } PTPConfig json_to_ptp_config(const std::string& json) { PTPConfig ptpConfig; try { boost::property_tree::ptree pt; std::stringstream ss(json); boost::property_tree::read_json(ss, pt); ptpConfig.domain = pt.get("domain"); ptpConfig.dscp = pt.get("dscp"); } catch (boost::property_tree::json_parser::json_parser_error& je) { throw std::runtime_error("error parsing JSON at line " + std::to_string(je.line()) + " :" + je.message()); } return ptpConfig; } void json_to_sources(const std::string& json, std::list& sources) { std::stringstream ss(json); return json_to_sources(ss, sources); } static void parse_json_sources(boost::property_tree::ptree& pt, std::list& sources) { BOOST_FOREACH (auto const& v, pt.get_child("sources")) { StreamSource source; source.id = v.second.get("id"); source.enabled = v.second.get("enabled"); source.name = v.second.get("name"); source.io = v.second.get("io"); source.max_samples_per_packet = v.second.get("max_samples_per_packet"); source.codec = v.second.get("codec"); source.address = v.second.get("address"); source.ttl = v.second.get("ttl"); source.payload_type = v.second.get("payload_type"); source.dscp = v.second.get("dscp"); source.refclk_ptp_traceable = v.second.get("refclk_ptp_traceable"); /* source map determite the association with ALSA output channels used to playing */ BOOST_FOREACH (const boost::property_tree::ptree::value_type& vm, v.second.get_child("map")) { source.map.emplace_back(std::stoi(vm.second.data())); } sources.emplace_back(std::move(source)); } } void json_to_sources(std::istream& js, std::list& sources) { try { boost::property_tree::ptree pt; boost::property_tree::read_json(js, pt); parse_json_sources(pt, sources); } catch (boost::property_tree::json_parser::json_parser_error& je) { throw std::runtime_error("error parsing JSON at line " + std::to_string(je.line()) + " :" + je.message()); } } void json_to_sinks(const std::string& json, std::list& sinks) { std::stringstream ss(json); return json_to_sinks(ss, sinks); } static void parse_json_sinks(boost::property_tree::ptree& pt, std::list& sinks) { BOOST_FOREACH (auto const& v, pt.get_child("sinks")) { StreamSink sink; sink.id = v.second.get("id"); sink.name = v.second.get("name"); sink.io = v.second.get("io"); sink.source = v.second.get("source"); sink.use_sdp = v.second.get("use_sdp"); sink.sdp = v.second.get("sdp"); sink.delay = v.second.get("delay"); sink.ignore_refclk_gmid = v.second.get("ignore_refclk_gmid"); /* source map determite the association with ALSA input channels used to recording */ BOOST_FOREACH (const boost::property_tree::ptree::value_type& vm, v.second.get_child("map")) { sink.map.emplace_back(std::stoi(vm.second.data())); } sinks.emplace_back(std::move(sink)); } } void json_to_sinks(std::istream& js, std::list& sinks) { try { boost::property_tree::ptree pt; boost::property_tree::read_json(js, pt); parse_json_sinks(pt, sinks); } catch (boost::property_tree::json_parser::json_parser_error& je) { throw std::runtime_error("error parsing JSON at line " + std::to_string(je.line()) + " :" + je.message()); } } void json_to_streams(const std::string& json, std::list& sources, std::list& sinks) { std::stringstream ss(json); json_to_streams(ss, sources, sinks); } void json_to_streams(std::istream& js, std::list& sources, std::list& sinks) { try { boost::property_tree::ptree pt; boost::property_tree::read_json(js, pt); parse_json_sources(pt, sources); parse_json_sinks(pt, sinks); } catch (boost::property_tree::json_parser::json_parser_error& je) { throw std::runtime_error("error parsing JSON at line " + std::to_string(je.line()) + " :" + je.message()); } }